sync

Backup service to store encrypted wallet databases (experimental)
Log | Files | Refs | Submodules | README | LICENSE

sync_api_block_operation_common.c (15312B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Lesser General Public License as
      7   published by the Free Software Foundation; either version 2.1,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU Lesser General Public License for more details.
     14 
     15   You should have received a copy of the GNU Lesser General Public
     16   License along with TALER; see the file COPYING.LGPL.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file lib/sync_api_block_operation_common.c
     21  * @brief Shared implementation for block append, update, and delete
     22  * @author Iván Ávalos
     23  */
     24 #include "sync_api_block_operation_common.h"
     25 
     26 
     27 char *
     28 SYNC_block_operation_make_url_ (
     29   const char *base_url,
     30   const struct SYNC_AccountPrivateKeyP *priv,
     31   const char *collection,
     32   size_t id_size,
     33   const void *id)
     34 {
     35   struct SYNC_AccountPublicKeyP pub;
     36   char *pub_s;
     37   char *id_s;
     38   char *path;
     39   char *url;
     40 
     41   GNUNET_CRYPTO_eddsa_key_get_public (&priv->eddsa_priv,
     42                                       &pub.eddsa_pub);
     43   pub_s = GNUNET_STRINGS_data_to_string_alloc (&pub,
     44                                                sizeof (pub));
     45   id_s = GNUNET_STRINGS_data_to_string_alloc (id,
     46                                               id_size);
     47   GNUNET_asprintf (&path,
     48                    "backups/%s/%s/%s",
     49                    pub_s,
     50                    collection,
     51                    id_s);
     52   GNUNET_free (pub_s);
     53   GNUNET_free (id_s);
     54   url = TALER_url_join (base_url,
     55                         path,
     56                         NULL);
     57   GNUNET_free (path);
     58   return url;
     59 }
     60 
     61 
     62 void
     63 SYNC_block_operation_add_etag_ (
     64   struct SYNC_BlockOperation *op,
     65   const char *header,
     66   const struct GNUNET_HashCode *hash)
     67 {
     68   char *val;
     69   char *hdr;
     70 
     71   val = GNUNET_STRINGS_data_to_string_alloc (hash,
     72                                              sizeof (*hash));
     73   GNUNET_asprintf (&hdr,
     74                    "%s: \"%s\"",
     75                    header,
     76                    val);
     77   GNUNET_free (val);
     78   op->post_ctx.headers = curl_slist_append (op->post_ctx.headers,
     79                                             hdr);
     80   GNUNET_free (hdr);
     81 }
     82 
     83 
     84 /**
     85  * Handle HTTP header received by curl.
     86  *
     87  * @param buffer one line of HTTP header data
     88  * @param size size of an item
     89  * @param nitems number of items passed
     90  * @param userdata our `struct SYNC_BlockOperation *`
     91  * @return `size * nitems`
     92  */
     93 size_t
     94 SYNC_block_operation_handle_header_ (char *buffer,
     95                                      size_t size,
     96                                      size_t nitems,
     97                                      void *userdata)
     98 {
     99   struct SYNC_BlockOperation *op = userdata;
    100   size_t total = size * nitems;
    101   char *ndup;
    102   const char *hdr_type;
    103   char *hdr_val;
    104   char *sp;
    105 
    106   ndup = GNUNET_strndup (buffer,
    107                          total);
    108   hdr_type = strtok_r (ndup,
    109                        ":",
    110                        &sp);
    111   if (NULL == hdr_type)
    112   {
    113     GNUNET_free (ndup);
    114     return total;
    115   }
    116   hdr_val = strtok_r (NULL,
    117                       "",
    118                       &sp);
    119   if (NULL == hdr_val)
    120   {
    121     GNUNET_free (ndup);
    122     return total;
    123   }
    124   if (' ' == *hdr_val)
    125     hdr_val++;
    126   if (0 == strcasecmp (hdr_type,
    127                        "Taler"))
    128   {
    129     size_t len;
    130 
    131     /* A second "Taler:" header would leak the first one */
    132     GNUNET_free (op->pay_uri);
    133     op->pay_uri = GNUNET_strdup (hdr_val);
    134     len = strlen (op->pay_uri);
    135     while ( (len > 0) &&
    136             ( ('\n' == op->pay_uri[len - 1]) ||
    137               ('\r' == op->pay_uri[len - 1]) ) )
    138     {
    139       len--;
    140       op->pay_uri[len] = '\0';
    141     }
    142   }
    143   GNUNET_free (ndup);
    144   return total;
    145 }
    146 
    147 
    148 /**
    149  * Function called when we're done processing the
    150  * HTTP /backups/$KEY/blocks/$NONCE request.
    151  *
    152  * @param cls the `struct SYNC_BlockOperation`
    153  * @param response_code HTTP response code, 0 on error
    154  * @param response response body parsed as JSON, or NULL
    155  */
    156 void
    157 SYNC_block_operation_handle_finished_ (void *cls,
    158                                        long response_code,
    159                                        const void *response)
    160 {
    161   struct SYNC_BlockOperation *op = cls;
    162   const json_t *json = response;
    163   struct SYNC_BlockOperationDetails ud = {
    164     .http_status = (unsigned int) response_code,
    165     .ec = TALER_EC_INVALID
    166   };
    167 
    168   op->job = NULL;
    169   switch (response_code)
    170   {
    171   case 0:
    172     break;
    173   case MHD_HTTP_NO_CONTENT:
    174     ud.bos = SYNC_BOS_SUCCESS;
    175     ud.ec = TALER_EC_NONE;
    176     break;
    177   case MHD_HTTP_NOT_MODIFIED:
    178     ud.bos = SYNC_BOS_NOT_MODIFIED;
    179     ud.ec = TALER_EC_NONE;
    180     break;
    181   case MHD_HTTP_PAYMENT_REQUIRED:
    182     ud.bos = SYNC_BOS_PAYMENT_REQUIRED;
    183     ud.pay_uri = op->pay_uri;
    184     ud.ec = TALER_EC_NONE;
    185     break;
    186   case MHD_HTTP_BAD_REQUEST:
    187     ud.ec = (NULL != json)
    188             ? TALER_JSON_get_error_code (json)
    189             : TALER_EC_GENERIC_PARAMETER_MALFORMED;
    190     break;
    191   case MHD_HTTP_FORBIDDEN:
    192     ud.ec = (NULL != json)
    193             ? TALER_JSON_get_error_code (json)
    194             : TALER_EC_SYNC_INVALID_SIGNATURE;
    195     break;
    196   case MHD_HTTP_CONTENT_TOO_LARGE:
    197   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    198   default:
    199     if ( (response_code >= 400) &&
    200          (response_code < 500) )
    201       ud.bos = SYNC_BOS_CLIENT_ERROR;
    202     else if (response_code >= 500)
    203       ud.bos = SYNC_BOS_SERVER_ERROR;
    204     else
    205       ud.bos = SYNC_BOS_HTTP_ERROR;
    206     ud.ec = (NULL != json)
    207             ? TALER_JSON_get_error_code (json)
    208             : TALER_EC_GENERIC_INVALID_RESPONSE;
    209     break;
    210   }
    211   if (NULL != op->cb)
    212   {
    213     op->cb (op->cb_cls,
    214             &ud);
    215     op->cb = NULL;
    216   }
    217   SYNC_block_operation_cancel (op);
    218 }
    219 
    220 
    221 /**
    222  * Build the upload signature covering the relink of a block.
    223  *
    224  * A relink never changes the data or the references, so the signature
    225  * covers the block's stored hash and refs_hash on both sides and its
    226  * new links (the nonces of the previous and next blocks).
    227  *
    228  * @param priv private key of the account
    229  * @param entry the block being relinked (from a block list)
    230  * @param new_prev the block's new predecessor, NULL for none
    231  * @param new_next the block's new successor, NULL for none
    232  * @param[out] sig signature to create
    233  */
    234 static void
    235 relink_sign (const struct SYNC_AccountPrivateKeyP *priv,
    236              const struct SYNC_BlockListEntry *entry,
    237              const struct SYNC_BlockNonce *new_prev,
    238              const struct SYNC_BlockNonce *new_next,
    239              struct SYNC_AccountSignatureP *sig)
    240 {
    241   SYNC_block_upload_sign (priv,
    242                           &entry->nonce,
    243                           new_prev,
    244                           new_next,
    245                           &entry->hash,
    246                           &entry->hash,
    247                           &entry->refs_hash,
    248                           sig);
    249 }
    250 
    251 
    252 /**
    253  * Serialize a relink object (relink_prev/relink_next): the new
    254  * signature of the relinked neighbour.
    255  *
    256  * @param sig upload signature covering the relink
    257  * @return JSON object to embed in the request body
    258  */
    259 static json_t *
    260 relink_serialize (const struct SYNC_AccountSignatureP *sig)
    261 {
    262   return GNUNET_JSON_PACK (
    263     GNUNET_JSON_pack_data_auto ("upload_sig",
    264                                 sig));
    265 }
    266 
    267 
    268 struct SYNC_BlockOperation *
    269 SYNC_block_operation_internal_ (
    270   struct GNUNET_CURL_Context *ctx,
    271   const char *base_url,
    272   const struct SYNC_AccountPrivateKeyP *priv,
    273   const struct SYNC_BlockOperationArgs *args,
    274   SYNC_BlockOperationCallback cb,
    275   void *cb_cls,
    276   bool is_delete)
    277 {
    278   struct SYNC_BlockOperation *op;
    279   struct SYNC_AccountSignatureP account_sig;
    280   struct SYNC_AccountSignatureP relink_sig;
    281   struct GNUNET_HashCode new_data_hash;
    282   struct GNUNET_HashCode refs_hash;
    283   CURL *eh;
    284   json_t *req;
    285 
    286   if (NULL != args->block_data)
    287   {
    288     /* Compute hash of block data */
    289     GNUNET_CRYPTO_hash (args->block_data,
    290                         args->block_data_size,
    291                         &new_data_hash);
    292   }
    293 
    294   /* Also rejects a repeated UID, which the wire format cannot carry */
    295   if (GNUNET_OK !=
    296       SYNC_object_refs_hash (args->refs_len,
    297                              args->object_uids,
    298                              args->object_incs,
    299                              &refs_hash))
    300   {
    301     GNUNET_break (0);
    302     return NULL;
    303   }
    304 
    305   /* Build signature */
    306   if (is_delete)
    307   {
    308     SYNC_block_delete_sign (priv,
    309                             args->nonce,
    310                             args->prev_nonce,
    311                             args->next_nonce,
    312                             args->block_hash,
    313                             &refs_hash,
    314                             &account_sig);
    315   }
    316   else
    317   {
    318     SYNC_block_upload_sign (priv,
    319                             args->nonce,
    320                             args->prev_nonce,
    321                             args->next_nonce,
    322                             args->old_data_hash,
    323                             &new_data_hash,
    324                             &refs_hash,
    325                             &account_sig);
    326   }
    327 
    328   /* Allocate operation handle */
    329   op = GNUNET_new (struct SYNC_BlockOperation);
    330   op->ctx = ctx;
    331   op->cb = cb;
    332   op->cb_cls = cb_cls;
    333   op->post_ctx.disable_compression = true;
    334   op->url = SYNC_block_operation_make_url_ (base_url,
    335                                             priv,
    336                                             "blocks",
    337                                             sizeof (*args->nonce),
    338                                             args->nonce);
    339 
    340   /* Build JSON body */
    341   if (is_delete)
    342   {
    343     json_t *relink_prev = NULL;
    344     json_t *relink_next = NULL;
    345 
    346     if (NULL != args->prev_entry)
    347     {
    348       relink_sign (priv,
    349                    args->prev_entry,
    350                    args->prev_entry->have_prev_nonce
    351                    ? &args->prev_entry->prev_nonce
    352                    : NULL,
    353                    args->next_nonce,
    354                    &relink_sig);
    355       relink_prev = relink_serialize (&relink_sig);
    356     }
    357 
    358     if (NULL != args->next_entry)
    359     {
    360       relink_sign (priv,
    361                    args->next_entry,
    362                    args->prev_nonce,
    363                    args->next_entry->have_next_nonce
    364                    ? &args->next_entry->next_nonce
    365                    : NULL,
    366                    &relink_sig);
    367       relink_next = relink_serialize (&relink_sig);
    368     }
    369 
    370     req = GNUNET_JSON_PACK (
    371       GNUNET_JSON_pack_data_auto ("delete_sig",
    372                                   &account_sig),
    373       GNUNET_JSON_pack_allow_null (
    374         GNUNET_JSON_pack_data_auto ("prev_nonce",
    375                                     args->prev_nonce)),
    376       GNUNET_JSON_pack_allow_null (
    377         GNUNET_JSON_pack_data_auto ("next_nonce",
    378                                     args->next_nonce)),
    379       GNUNET_JSON_pack_allow_null (
    380         GNUNET_JSON_pack_object_steal (
    381           "object_refs",
    382           SYNC_object_refs_serialize (args->refs_len,
    383                                       args->object_uids,
    384                                       args->object_incs))),
    385       GNUNET_JSON_pack_allow_null (
    386         GNUNET_JSON_pack_object_steal ("relink_prev",
    387                                        relink_prev)),
    388       GNUNET_JSON_pack_allow_null (
    389         GNUNET_JSON_pack_object_steal ("relink_next",
    390                                        relink_next)));
    391   }
    392   else
    393   {
    394     json_t *relink_prev = NULL;
    395 
    396     if (NULL != args->prev_entry)
    397     {
    398       relink_sign (priv,
    399                    args->prev_entry,
    400                    args->prev_entry->have_prev_nonce
    401                    ? &args->prev_entry->prev_nonce
    402                    : NULL,
    403                    args->nonce,
    404                    &relink_sig);
    405       relink_prev = relink_serialize (&relink_sig);
    406     }
    407 
    408     req = GNUNET_JSON_PACK (
    409       GNUNET_JSON_pack_data_auto ("upload_sig",
    410                                   &account_sig),
    411       GNUNET_JSON_pack_allow_null (
    412         GNUNET_JSON_pack_data_auto ("prev_nonce",
    413                                     args->prev_nonce)),
    414       GNUNET_JSON_pack_allow_null (
    415         GNUNET_JSON_pack_data_auto ("next_nonce",
    416                                     args->next_nonce)),
    417       GNUNET_JSON_pack_data_varsize ("data",
    418                                      args->block_data,
    419                                      args->block_data_size),
    420       GNUNET_JSON_pack_allow_null (
    421         GNUNET_JSON_pack_object_steal (
    422           "object_refs",
    423           SYNC_object_refs_serialize (args->refs_len,
    424                                       args->object_uids,
    425                                       args->object_incs))),
    426       GNUNET_JSON_pack_allow_null (
    427         GNUNET_JSON_pack_object_steal ("relink_prev",
    428                                        relink_prev)));
    429   }
    430 
    431   /* Set up CURL easy handle */
    432   eh = SYNC_curl_easy_get_ (op->url);
    433   if (NULL == eh)
    434   {
    435     GNUNET_break (0);
    436     json_decref (req);
    437     goto cleanup;
    438   }
    439   GNUNET_assert (CURLE_OK ==
    440                  curl_easy_setopt (eh,
    441                                    CURLOPT_HEADERFUNCTION,
    442                                    &SYNC_block_operation_handle_header_));
    443   GNUNET_assert (CURLE_OK ==
    444                  curl_easy_setopt (eh,
    445                                    CURLOPT_HEADERDATA,
    446                                    op));
    447 
    448   if (GNUNET_OK !=
    449       TALER_curl_easy_post (&op->post_ctx,
    450                             eh,
    451                             req))
    452   {
    453     GNUNET_break (0);
    454     json_decref (req);
    455     curl_easy_cleanup (eh);
    456     goto cleanup;
    457   }
    458   json_decref (req);
    459 
    460   /* Override method to PUT/DELETE as needed */
    461   if (! is_delete && NULL != args->old_data_hash)
    462   {
    463     GNUNET_assert (CURLE_OK ==
    464                    curl_easy_setopt (eh,
    465                                      CURLOPT_CUSTOMREQUEST,
    466                                      "PUT"));
    467   }
    468   if (is_delete)
    469   {
    470     GNUNET_assert (CURLE_OK ==
    471                    curl_easy_setopt (eh,
    472                                      CURLOPT_CUSTOMREQUEST,
    473                                      "DELETE"));
    474   }
    475 
    476   SYNC_block_operation_add_etag_ (op,
    477                                   MHD_HTTP_HEADER_IF_NONE_MATCH,
    478                                   &new_data_hash);
    479   /* Add If-Match header for updates and deletes */
    480   if (NULL != args->old_data_hash)
    481     SYNC_block_operation_add_etag_ (op,
    482                                     MHD_HTTP_HEADER_IF_MATCH,
    483                                     args->old_data_hash);
    484   if (is_delete)
    485     SYNC_block_operation_add_etag_ (op,
    486                                     MHD_HTTP_HEADER_IF_MATCH,
    487                                     args->block_hash);
    488 
    489   op->job = GNUNET_CURL_job_add2 (ctx,
    490                                   eh,
    491                                   op->post_ctx.headers,
    492                                   &SYNC_block_operation_handle_finished_,
    493                                   op);
    494   return op;
    495 
    496 cleanup:
    497   GNUNET_free (op->url);
    498   GNUNET_free (op);
    499   return NULL;
    500 }
    501 
    502 
    503 void
    504 SYNC_block_operation_cancel (struct SYNC_BlockOperation *op)
    505 {
    506   if (NULL != op->job)
    507   {
    508     GNUNET_CURL_job_cancel (op->job);
    509     op->job = NULL;
    510   }
    511   TALER_curl_easy_post_finished (&op->post_ctx);
    512   GNUNET_free (op->pay_uri);
    513   GNUNET_free (op->url);
    514   GNUNET_free (op);
    515 }
    516 
    517 
    518 /* end of sync_api_block_operation_common.c */