sync

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

sync_api_upload.c (12535B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2019 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 /**
     21  * @file lib/sync_api_upload.c
     22  * @brief Implementation of the upload POST
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include <curl/curl.h>
     27 #include <jansson.h>
     28 #include <microhttpd.h> /* just for HTTP status codes */
     29 #include <gnunet/gnunet_util_lib.h>
     30 #include <gnunet/gnunet_curl_lib.h>
     31 #include <taler/taler_signatures.h>
     32 #include <taler/taler_json_lib.h>
     33 #include "sync_service.h"
     34 #include "sync_api_curl_defaults.h"
     35 
     36 
     37 /**
     38  * @brief Handle for an upload operation.
     39  */
     40 struct SYNC_UploadOperation
     41 {
     42 
     43   /**
     44    * The url for this request.
     45    */
     46   char *url;
     47 
     48   /**
     49    * Handle for the request.
     50    */
     51   struct GNUNET_CURL_Job *job;
     52 
     53   /**
     54    * Reference to the execution context.
     55    */
     56   struct GNUNET_CURL_Context *ctx;
     57 
     58   /**
     59    * Function to call with the result.
     60    */
     61   SYNC_UploadCallback cb;
     62 
     63   /**
     64    * Closure for @e cb.
     65    */
     66   void *cb_cls;
     67 
     68   /**
     69    * Payment URI we received from the service, or NULL.
     70    */
     71   char *pay_uri;
     72 
     73   /**
     74    * Hash of the data we are uploading.
     75    */
     76   struct GNUNET_HashCode new_upload_hash;
     77 };
     78 
     79 
     80 /**
     81  * Function called when we're done processing the
     82  * HTTP /backup request.
     83  *
     84  * @param cls the `struct SYNC_UploadOperation`
     85  * @param response_code HTTP response code, 0 on error
     86  * @param data data we downloaded
     87  * @param data_size number of bytes in @a data
     88  */
     89 static void
     90 handle_upload_finished (void *cls,
     91                         long response_code,
     92                         const void *data,
     93                         size_t data_size)
     94 {
     95   struct SYNC_UploadOperation *uo = cls;
     96   struct SYNC_UploadDetails ud = {
     97     .http_status = (unsigned int) response_code,
     98     .ec = TALER_EC_INVALID
     99   };
    100 
    101   uo->job = NULL;
    102   switch (response_code)
    103   {
    104   case 0:
    105     break;
    106   case MHD_HTTP_NO_CONTENT:
    107     ud.us = SYNC_US_SUCCESS;
    108     ud.details.success.curr_backup_hash = &uo->new_upload_hash;
    109     ud.ec = TALER_EC_NONE;
    110     break;
    111   case MHD_HTTP_NOT_MODIFIED:
    112     ud.us = SYNC_US_SUCCESS;
    113     ud.details.success.curr_backup_hash = &uo->new_upload_hash;
    114     ud.ec = TALER_EC_NONE;
    115     break;
    116   case MHD_HTTP_BAD_REQUEST:
    117     GNUNET_break (0);
    118     ud.ec = TALER_JSON_get_error_code2 (data,
    119                                         data_size);
    120     break;
    121   case MHD_HTTP_PAYMENT_REQUIRED:
    122     ud.us = SYNC_US_PAYMENT_REQUIRED;
    123     ud.details.payment_required.payment_request = uo->pay_uri;
    124     ud.ec = TALER_EC_NONE;
    125     break;
    126   case MHD_HTTP_FORBIDDEN:
    127     GNUNET_break (0);
    128     ud.ec = TALER_JSON_get_error_code2 (data,
    129                                         data_size);
    130     break;
    131   case MHD_HTTP_CONFLICT:
    132     ud.us = SYNC_US_CONFLICTING_BACKUP;
    133     GNUNET_CRYPTO_hash (data,
    134                         data_size,
    135                         &ud.details.recovered_backup.existing_backup_hash);
    136     ud.details.recovered_backup.existing_backup_size
    137       = data_size;
    138     ud.details.recovered_backup.existing_backup
    139       = data;
    140     ud.ec = TALER_EC_NONE;
    141     break;
    142   case MHD_HTTP_GONE:
    143     ud.ec = TALER_JSON_get_error_code2 (data,
    144                                         data_size);
    145     break;
    146   case MHD_HTTP_LENGTH_REQUIRED:
    147     GNUNET_break (0);
    148     break;
    149   case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE:
    150     ud.ec = TALER_JSON_get_error_code2 (data,
    151                                         data_size);
    152     break;
    153   case MHD_HTTP_TOO_MANY_REQUESTS:
    154     ud.ec = TALER_JSON_get_error_code2 (data,
    155                                         data_size);
    156     break;
    157   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    158     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    159                 "Internal server error: `%.*s\n",
    160                 (int) data_size,
    161                 (const char *) data);
    162     break;
    163   }
    164   if (NULL != uo->cb)
    165   {
    166     uo->cb (uo->cb_cls,
    167             &ud);
    168     uo->cb = NULL;
    169   }
    170   SYNC_upload_cancel (uo);
    171 }
    172 
    173 
    174 /**
    175  * Handle HTTP header received by curl.
    176  *
    177  * @param buffer one line of HTTP header data
    178  * @param size size of an item
    179  * @param nitems number of items passed
    180  * @param userdata our `struct SYNC_DownloadOperation *`
    181  * @return `size * nitems`
    182  */
    183 static size_t
    184 handle_header (char *buffer,
    185                size_t size,
    186                size_t nitems,
    187                void *userdata)
    188 {
    189   struct SYNC_UploadOperation *uo = userdata;
    190   size_t total = size * nitems;
    191   char *ndup;
    192   const char *hdr_type;
    193   char *hdr_val;
    194   char *sp;
    195 
    196   ndup = GNUNET_strndup (buffer,
    197                          total);
    198   hdr_type = strtok_r (ndup,
    199                        ":",
    200                        &sp);
    201   if (NULL == hdr_type)
    202   {
    203     GNUNET_free (ndup);
    204     return total;
    205   }
    206   hdr_val = strtok_r (NULL,
    207                       "",
    208                       &sp);
    209   if (NULL == hdr_val)
    210   {
    211     GNUNET_free (ndup);
    212     return total;
    213   }
    214   if (' ' == *hdr_val)
    215     hdr_val++;
    216   if (0 == strcasecmp (hdr_type,
    217                        "Taler"))
    218   {
    219     size_t len;
    220 
    221     /* found payment URI we care about! */
    222     uo->pay_uri = GNUNET_strdup (hdr_val);
    223     len = strlen (uo->pay_uri);
    224     while ( (len > 0) &&
    225             ( ('\n' == uo->pay_uri[len - 1]) ||
    226               ('\r' == uo->pay_uri[len - 1]) ) )
    227     {
    228       len--;
    229       uo->pay_uri[len] = '\0';
    230     }
    231   }
    232   GNUNET_free (ndup);
    233   return total;
    234 }
    235 
    236 
    237 struct SYNC_UploadOperation *
    238 SYNC_upload (struct GNUNET_CURL_Context *ctx,
    239              const char *base_url,
    240              struct SYNC_AccountPrivateKeyP *priv,
    241              const struct GNUNET_HashCode *prev_backup_hash,
    242              size_t backup_size,
    243              const void *backup,
    244              enum SYNC_PaymentOptions po,
    245              const char *paid_order_id,
    246              SYNC_UploadCallback cb,
    247              void *cb_cls)
    248 {
    249   struct SYNC_AccountSignatureP account_sig;
    250   struct SYNC_UploadOperation *uo;
    251   CURL *eh;
    252   struct curl_slist *job_headers;
    253   struct SYNC_UploadSignaturePS usp = {
    254     .purpose.purpose = htonl (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD),
    255     .purpose.size = htonl (sizeof (usp))
    256   };
    257 
    258   if (NULL != prev_backup_hash)
    259     usp.old_backup_hash = *prev_backup_hash;
    260   GNUNET_CRYPTO_hash (backup,
    261                       backup_size,
    262                       &usp.new_backup_hash);
    263   GNUNET_CRYPTO_eddsa_sign (&priv->eddsa_priv,
    264                             &usp,
    265                             &account_sig.eddsa_sig);
    266 
    267   /* setup our HTTP headers */
    268   job_headers = NULL;
    269   {
    270     struct curl_slist *ext;
    271     char *val;
    272     char *hdr;
    273 
    274     /* Set Sync-Signature header */
    275     val = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
    276                                                sizeof (account_sig));
    277     GNUNET_asprintf (&hdr,
    278                      "Sync-Signature: %s",
    279                      val);
    280     GNUNET_free (val);
    281     ext = curl_slist_append (job_headers,
    282                              hdr);
    283     GNUNET_free (hdr);
    284     if (NULL == ext)
    285     {
    286       GNUNET_break (0);
    287       curl_slist_free_all (job_headers);
    288       return NULL;
    289     }
    290     job_headers = ext;
    291 
    292     /* set If-None-Match header */
    293     val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_backup_hash,
    294                                                sizeof (struct GNUNET_HashCode));
    295     GNUNET_asprintf (&hdr,
    296                      "%s: \"%s\"",
    297                      MHD_HTTP_HEADER_IF_NONE_MATCH,
    298                      val);
    299     GNUNET_free (val);
    300     ext = curl_slist_append (job_headers,
    301                              hdr);
    302     GNUNET_free (hdr);
    303     if (NULL == ext)
    304     {
    305       GNUNET_break (0);
    306       curl_slist_free_all (job_headers);
    307       return NULL;
    308     }
    309     job_headers = ext;
    310 
    311     /* Setup If-Match header */
    312     if (NULL != prev_backup_hash)
    313     {
    314       val = GNUNET_STRINGS_data_to_string_alloc (&usp.old_backup_hash,
    315                                                  sizeof (struct
    316                                                          GNUNET_HashCode));
    317       GNUNET_asprintf (&hdr,
    318                        "If-Match: \"%s\"",
    319                        val);
    320       GNUNET_free (val);
    321       ext = curl_slist_append (job_headers,
    322                                hdr);
    323       GNUNET_free (hdr);
    324       if (NULL == ext)
    325       {
    326         GNUNET_break (0);
    327         curl_slist_free_all (job_headers);
    328         return NULL;
    329       }
    330       job_headers = ext;
    331     }
    332   }
    333   /* Finished setting up headers */
    334 
    335   uo = GNUNET_new (struct SYNC_UploadOperation);
    336   uo->new_upload_hash = usp.new_backup_hash;
    337   {
    338     char *path;
    339     char *account_s;
    340     struct SYNC_AccountPublicKeyP pub;
    341 
    342     GNUNET_CRYPTO_eddsa_key_get_public (&priv->eddsa_priv,
    343                                         &pub.eddsa_pub);
    344     account_s = GNUNET_STRINGS_data_to_string_alloc (&pub,
    345                                                      sizeof (pub));
    346     GNUNET_asprintf (&path,
    347                      "backups/%s",
    348                      account_s);
    349     GNUNET_free (account_s);
    350     if  (0 != (po & SYNC_PO_FRESH_ORDER))
    351     {
    352       uo->url = (0 != (po & SYNC_PO_FORCE_PAYMENT))
    353                 ? TALER_url_join (base_url,
    354                                   path,
    355                                   "fresh",
    356                                   "y",
    357                                   "pay",
    358                                   "y",
    359                                   (NULL != paid_order_id)
    360                                   ? "paying"
    361                                   : NULL,
    362                                   paid_order_id,
    363                                   NULL)
    364                 : TALER_url_join (base_url,
    365                                   path,
    366                                   "fresh",
    367                                   "y",
    368                                   (NULL != paid_order_id)
    369                                   ? "paying"
    370                                   : NULL,
    371                                   paid_order_id,
    372                                   NULL);
    373     }
    374     else
    375     {
    376       uo->url = (0 != (po & SYNC_PO_FORCE_PAYMENT))
    377                 ? TALER_url_join (base_url,
    378                                   path,
    379                                   "pay",
    380                                   "y",
    381                                   (NULL != paid_order_id)
    382                                   ? "paying"
    383                                   : NULL,
    384                                   paid_order_id,
    385                                   NULL)
    386                 : TALER_url_join (base_url,
    387                                   path,
    388                                   (NULL != paid_order_id)
    389                                   ? "paying"
    390                                   : NULL,
    391                                   paid_order_id,
    392                                   NULL);
    393     }
    394 
    395     GNUNET_free (path);
    396   }
    397   uo->ctx = ctx;
    398   uo->cb = cb;
    399   uo->cb_cls = cb_cls;
    400   eh = SYNC_curl_easy_get_ (uo->url);
    401   GNUNET_assert (CURLE_OK ==
    402                  curl_easy_setopt (eh,
    403                                    CURLOPT_POSTFIELDS,
    404                                    backup));
    405   GNUNET_assert (CURLE_OK ==
    406                  curl_easy_setopt (eh,
    407                                    CURLOPT_POSTFIELDSIZE,
    408                                    (long) backup_size));
    409   GNUNET_assert (CURLE_OK ==
    410                  curl_easy_setopt (eh,
    411                                    CURLOPT_HEADERFUNCTION,
    412                                    &handle_header));
    413   GNUNET_assert (CURLE_OK ==
    414                  curl_easy_setopt (eh,
    415                                    CURLOPT_HEADERDATA,
    416                                    uo));
    417   uo->job = GNUNET_CURL_job_add_raw (ctx,
    418                                      eh,
    419                                      job_headers,
    420                                      &handle_upload_finished,
    421                                      uo);
    422   curl_slist_free_all (job_headers);
    423   return uo;
    424 }
    425 
    426 
    427 void
    428 SYNC_upload_cancel (struct SYNC_UploadOperation *uo)
    429 {
    430   if (NULL != uo->job)
    431   {
    432     GNUNET_CURL_job_cancel (uo->job);
    433     uo->job = NULL;
    434   }
    435   GNUNET_free (uo->pay_uri);
    436   GNUNET_free (uo->url);
    437   GNUNET_free (uo);
    438 }
    439 
    440 
    441 /* end of sync_api_upload.c */