sync

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

sync-httpd2_backup-post.c (30612B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2019--2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file sync-httpd_backup_post.c
     18  * @brief functions to handle incoming requests for backups
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 struct BackupContext;
     23 #define TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE struct BackupContext
     24 #define TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE struct BackupContext
     25 #include "sync-httpd2.h"
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include "sync-httpd2_backup.h"
     28 #include <microhttpd2.h>
     29 #include <taler/taler_json_lib.h>
     30 #include <taler/taler_mhd2_lib.h>
     31 #include <taler/taler_merchant_service.h>
     32 #include <taler/taler_signatures.h>
     33 
     34 
     35 /**
     36  * How long do we hold an HTTP client connection if
     37  * we are awaiting payment before giving up?
     38  */
     39 #define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \
     40           GNUNET_TIME_UNIT_MINUTES, 30)
     41 
     42 
     43 /**
     44  * Context for an upload operation.
     45  */
     46 struct BackupContext
     47 {
     48 
     49   /**
     50    * Context for cleanup logic.
     51    */
     52   struct TM_HandlerContext hc;
     53 
     54   /**
     55    * Signature of the account holder.
     56    */
     57   struct SYNC_AccountSignatureP account_sig;
     58 
     59   /**
     60    * Public key of the account holder.
     61    */
     62   struct SYNC_AccountPublicKeyP account;
     63 
     64   /**
     65    * Hash of the previous upload, or zeros if first upload.
     66    */
     67   struct GNUNET_HashCode old_backup_hash;
     68 
     69   /**
     70    * Hash of the upload we are receiving right now (as promised
     71    * by the client, to be verified!).
     72    */
     73   struct GNUNET_HashCode new_backup_hash;
     74 
     75   /**
     76    * Claim token, all zeros if not known. Only set if @e existing_order_id is non-NULL.
     77    */
     78   struct TALER_ClaimTokenP token;
     79 
     80   /**
     81    * Kept in DLL for shutdown handling while suspended.
     82    */
     83   struct BackupContext *next;
     84 
     85   /**
     86    * Kept in DLL for shutdown handling while suspended.
     87    */
     88   struct BackupContext *prev;
     89 
     90   /**
     91    * Used while suspended for resumption.
     92    */
     93   struct MHD_Request *request;
     94 
     95   /**
     96    * To be returned upon resuming.
     97    */
     98   struct MHD_Response *resp;
     99 
    100   /**
    101    * Used while we are awaiting proposal creation.
    102    */
    103   struct TALER_MERCHANT_PostPrivateOrdersHandle *po;
    104 
    105   /**
    106    * Used while we are waiting payment.
    107    */
    108   struct TALER_MERCHANT_GetPrivateOrderHandle *omgh;
    109 
    110   /**
    111    * Order under which the client promised payment, or NULL.
    112    */
    113   const char *order_id;
    114 
    115   /**
    116    * Order ID for the client that we found in our database.
    117    */
    118   char *existing_order_id;
    119 
    120   /**
    121    * Timestamp of the order in @e existing_order_id. Used to
    122    * select the most recent unpaid offer.
    123    */
    124   struct GNUNET_TIME_Timestamp existing_order_timestamp;
    125 
    126   /**
    127    * Do not look for an existing order, force a fresh order to be created.
    128    */
    129   bool force_fresh_order;
    130 };
    131 
    132 
    133 /**
    134  * Kept in DLL for shutdown handling while suspended.
    135  */
    136 static struct BackupContext *bc_head;
    137 
    138 /**
    139  * Kept in DLL for shutdown handling while suspended.
    140  */
    141 static struct BackupContext *bc_tail;
    142 
    143 
    144 /**
    145  * Service is shutting down, resume all MHD requests NOW.
    146  */
    147 void
    148 SH_resume_all_bc ()
    149 {
    150   struct BackupContext *bc;
    151 
    152   while (NULL != (bc = bc_head))
    153   {
    154     GNUNET_CONTAINER_DLL_remove (bc_head,
    155                                  bc_tail,
    156                                  bc);
    157     MHD_request_resume (bc->request);
    158     if (NULL != bc->po)
    159     {
    160       TALER_MERCHANT_post_private_orders_cancel (bc->po);
    161       bc->po = NULL;
    162     }
    163     if (NULL != bc->omgh)
    164     {
    165       TALER_MERCHANT_get_private_order_cancel (bc->omgh);
    166       bc->omgh = NULL;
    167     }
    168   }
    169 }
    170 
    171 
    172 /**
    173  * Function called to clean up a backup context.
    174  *
    175  * @param cls the `struct BackupContext`
    176  * @param data the details about the event
    177  * @param request_app_context the application request context
    178  */
    179 static void
    180 cleanup_ctx (void *cls,
    181              const struct MHD_RequestEndedData *data,
    182              void *request_app_context)
    183 {
    184   struct BackupContext *bc = cls;
    185 
    186   GNUNET_assert (cls == request_app_context);
    187   if (NULL != bc->po)
    188   {
    189     TALER_MERCHANT_post_private_orders_cancel (bc->po);
    190     bc->po = NULL;
    191   }
    192   if (NULL != bc->omgh)
    193   {
    194     TALER_MERCHANT_get_private_order_cancel (bc->omgh);
    195     bc->omgh = NULL;
    196   }
    197   GNUNET_free (bc->existing_order_id);
    198   GNUNET_free (bc);
    199 }
    200 
    201 
    202 /**
    203  * Transmit a payment request for @a order_id on @a request
    204  *
    205  * @param order_id our backend's order ID
    206  * @param token the claim token generated by the merchant (NULL if
    207  *        it wasn't generated).
    208  * @return MHD response to use
    209  */
    210 static struct MHD_Response *
    211 make_payment_request (const char *order_id,
    212                       const struct TALER_ClaimTokenP *token)
    213 {
    214   struct MHD_Response *resp;
    215 
    216   /* request payment via Taler */
    217   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    218               "Creating payment request for order `%s'\n",
    219               order_id);
    220   resp = MHD_response_from_empty (MHD_HTTP_STATUS_PAYMENT_REQUIRED);
    221   TALER_MHD2_add_global_headers (resp,
    222                                  false);
    223   {
    224     char *hdr;
    225     const char *pfx;
    226     char *hn;
    227     struct GNUNET_Buffer hdr_buf = { 0 };
    228 
    229     if (0 == strncasecmp ("https://",
    230                           SH_backend_url,
    231                           strlen ("https://")))
    232     {
    233       pfx = "taler://";
    234       hn = &SH_backend_url[strlen ("https://")];
    235     }
    236     else if (0 == strncasecmp ("http://",
    237                                SH_backend_url,
    238                                strlen ("http://")))
    239     {
    240       pfx = "taler+http://";
    241       hn = &SH_backend_url[strlen ("http://")];
    242     }
    243     else
    244     {
    245       GNUNET_break (0);
    246       MHD_response_destroy (resp);
    247       return NULL;
    248     }
    249     if (0 == strlen (hn))
    250     {
    251       GNUNET_break (0);
    252       MHD_response_destroy (resp);
    253       return NULL;
    254     }
    255 
    256     GNUNET_buffer_write_str (&hdr_buf, pfx);
    257     GNUNET_buffer_write_str (&hdr_buf, "pay/");
    258     GNUNET_buffer_write_str (&hdr_buf, hn);
    259     GNUNET_buffer_write_path (&hdr_buf, order_id);
    260     /* No session ID */
    261     GNUNET_buffer_write_path (&hdr_buf, "");
    262     if (NULL != token)
    263     {
    264       GNUNET_buffer_write_str (&hdr_buf, "?c=");
    265       GNUNET_buffer_write_data_encoded (&hdr_buf,
    266                                         token,
    267                                         sizeof (*token));
    268     }
    269     hdr = GNUNET_buffer_reap_str (&hdr_buf);
    270     GNUNET_break (MHD_SC_OK ==
    271                   MHD_response_add_header (resp,
    272                                            "Taler",
    273                                            hdr));
    274     GNUNET_free (hdr);
    275   }
    276   return resp;
    277 }
    278 
    279 
    280 /**
    281  * Callbacks of this type are used to serve the result of submitting a
    282  * /contract request to a merchant.
    283  *
    284  * @param cls our `struct BackupContext`
    285  * @param por response details
    286  */
    287 static void
    288 proposal_cb (
    289   struct BackupContext *bc,
    290   const struct TALER_MERCHANT_PostPrivateOrdersResponse *por)
    291 {
    292   enum SYNC_DB_QueryStatus qs;
    293 
    294   bc->po = NULL;
    295   GNUNET_CONTAINER_DLL_remove (bc_head,
    296                                bc_tail,
    297                                bc);
    298   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    299               "Resuming request with order `%s'\n",
    300               bc->order_id);
    301   MHD_request_resume (bc->request);
    302   SH_trigger_daemon ();
    303   if (MHD_HTTP_STATUS_OK != por->hr.http_status)
    304   {
    305     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    306                 "Backend returned status %u/%u\n",
    307                 por->hr.http_status,
    308                 (unsigned int) por->hr.ec);
    309     GNUNET_break_op (0);
    310     bc->resp = TALER_MHD2_MAKE_JSON_PACK (
    311       MHD_HTTP_STATUS_BAD_GATEWAY,
    312       TALER_JSON_pack_ec (TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR),
    313       GNUNET_JSON_pack_uint64 ("backend-ec",
    314                                por->hr.ec),
    315       GNUNET_JSON_pack_uint64 ("backend-http-status",
    316                                por->hr.http_status),
    317       GNUNET_JSON_pack_allow_null (
    318         GNUNET_JSON_pack_object_incref ("backend-reply",
    319                                         (json_t *) por->hr.reply)));
    320     return;
    321   }
    322   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    323               "Storing payment request for order `%s'\n",
    324               por->details.ok.order_id);
    325   qs = SYNCDB_store_payment_TR (
    326     &bc->account,
    327     por->details.ok.order_id,
    328     por->details.ok.token,
    329     &SH_annual_fee);
    330   if (0 >= qs)
    331   {
    332     GNUNET_break (0);
    333     bc->resp = TALER_MHD2_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
    334                                       "Failed to persist payment request in sync database");
    335     GNUNET_assert (NULL != bc->resp);
    336     return;
    337   }
    338   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    339               "Obtained fresh order `%s'\n",
    340               por->details.ok.order_id);
    341   bc->resp = make_payment_request (por->details.ok.order_id,
    342                                    por->details.ok.token);
    343   GNUNET_assert (NULL != bc->resp);
    344 }
    345 
    346 
    347 /**
    348  * Function called on all pending payments for the right
    349  * account.
    350  *
    351  * @param cls closure, our `struct BackupContext`
    352  * @param timestamp for how long have we been waiting
    353  * @param order_id order id in the backend
    354  * @param token claim token to use (or NULL for none)
    355  * @param amount how much is the order for
    356  */
    357 static void
    358 ongoing_payment_cb (void *cls,
    359                     struct GNUNET_TIME_Timestamp timestamp,
    360                     const char *order_id,
    361                     const struct TALER_ClaimTokenP *token,
    362                     const struct TALER_Amount *amount)
    363 {
    364   struct BackupContext *bc = cls;
    365 
    366   (void) amount;
    367   if (0 != TALER_amount_cmp (amount,
    368                              &SH_annual_fee))
    369     return; /* can't reuse, fees changed */
    370   if ( (NULL == bc->existing_order_id) ||
    371        (GNUNET_TIME_timestamp_cmp (bc->existing_order_timestamp,
    372                                    <,
    373                                    timestamp)) )
    374   {
    375     GNUNET_free (bc->existing_order_id);
    376     bc->existing_order_id = GNUNET_strdup (order_id);
    377     bc->existing_order_timestamp = timestamp;
    378     if (NULL != token)
    379       bc->token = *token;
    380   }
    381 }
    382 
    383 
    384 /**
    385  * Callback to process a GET /check-payment request
    386  *
    387  * @param cls our `struct BackupContext`
    388  * @param osr order status
    389  */
    390 static void
    391 check_payment_cb (
    392   struct BackupContext *bc,
    393   const struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
    394 {
    395   const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;
    396 
    397   /* refunds are not supported, verify */
    398   bc->omgh = NULL;
    399   GNUNET_CONTAINER_DLL_remove (bc_head,
    400                                bc_tail,
    401                                bc);
    402   MHD_request_resume (bc->request);
    403   SH_trigger_daemon ();
    404   switch (hr->http_status)
    405   {
    406   case 0:
    407     /* Likely timeout, complain! */
    408     bc->resp = TALER_MHD2_make_error (
    409       TALER_EC_SYNC_GENERIC_BACKEND_TIMEOUT,
    410       NULL);
    411     return;
    412   case MHD_HTTP_STATUS_OK:
    413     break; /* handled below */
    414   default:
    415     /* Unexpected backend response */
    416     bc->resp = TALER_MHD2_MAKE_JSON_PACK (
    417       MHD_HTTP_STATUS_BAD_GATEWAY,
    418       GNUNET_JSON_pack_uint64 ("code",
    419                                TALER_EC_SYNC_GENERIC_BACKEND_ERROR),
    420       GNUNET_JSON_pack_string ("hint",
    421                                TALER_ErrorCode_get_hint (
    422                                  TALER_EC_SYNC_GENERIC_BACKEND_ERROR)),
    423       GNUNET_JSON_pack_uint64 ("backend-ec",
    424                                (json_int_t) hr->ec),
    425       GNUNET_JSON_pack_uint64 ("backend-http-status",
    426                                (json_int_t) hr->http_status),
    427       GNUNET_JSON_pack_allow_null (
    428         GNUNET_JSON_pack_object_incref ("backend-reply",
    429                                         (json_t *) hr->reply)));
    430     return;
    431   }
    432 
    433   GNUNET_assert (MHD_HTTP_STATUS_OK == hr->http_status);
    434   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    435               "Payment status checked: %d\n",
    436               osr->details.ok.status);
    437   switch (osr->details.ok.status)
    438   {
    439   case TALER_MERCHANT_OSC_PAID:
    440     {
    441       enum SYNC_DB_QueryStatus qs;
    442 
    443       qs = SYNCDB_increment_lifetime_TR (
    444         &bc->account,
    445         bc->order_id,
    446         GNUNET_TIME_UNIT_YEARS);                               /* always annual */
    447       if (0 <= qs)
    448         return; /* continue as planned */
    449       GNUNET_break (0);
    450       bc->resp = TALER_MHD2_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
    451                                         "increment lifetime");
    452       GNUNET_assert (NULL != bc->resp);
    453       return; /* continue as planned */
    454     }
    455   case TALER_MERCHANT_OSC_UNPAID:
    456   case TALER_MERCHANT_OSC_CLAIMED:
    457     break;
    458   }
    459   if (NULL != bc->existing_order_id)
    460   {
    461     /* repeat payment request */
    462     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    463                 "Repeating payment request\n");
    464     bc->resp = make_payment_request (bc->existing_order_id,
    465                                      (GNUNET_YES == GNUNET_is_zero (&bc->token))
    466                                      ? NULL
    467                                      : &bc->token);
    468     GNUNET_assert (NULL != bc->resp);
    469     return;
    470   }
    471   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    472               "Timeout waiting for payment\n");
    473   bc->resp = TALER_MHD2_make_error (TALER_EC_SYNC_PAYMENT_GENERIC_TIMEOUT,
    474                                     "Timeout awaiting promised payment");
    475   GNUNET_assert (NULL != bc->resp);
    476 }
    477 
    478 
    479 /**
    480  * Helper function used to ask our backend to await
    481  * a payment for the user's account.
    482  *
    483  * @param bc context to begin payment for.
    484  * @param timeout when to give up trying
    485  * @param order_id which order to check for the payment
    486  */
    487 static void
    488 await_payment (struct BackupContext *bc,
    489                struct GNUNET_TIME_Relative timeout,
    490                const char *order_id)
    491 {
    492   GNUNET_CONTAINER_DLL_insert (bc_head,
    493                                bc_tail,
    494                                bc);
    495   bc->order_id = order_id;
    496   bc->omgh = TALER_MERCHANT_get_private_order_create (
    497     SH_ctx,
    498     SH_backend_url,
    499     order_id);
    500   GNUNET_assert (GNUNET_OK ==
    501                  TALER_MERCHANT_get_private_order_set_options (
    502                    bc->omgh,
    503                    TALER_MERCHANT_get_private_order_option_timeout (timeout)));
    504   GNUNET_assert (TALER_EC_NONE ==
    505                  TALER_MERCHANT_get_private_order_start (
    506                    bc->omgh,
    507                    &check_payment_cb,
    508                    bc));
    509   SH_trigger_curl ();
    510 }
    511 
    512 
    513 /**
    514  * Helper function used to ask our backend to begin
    515  * processing a payment for the user's account.
    516  * May perform asynchronous operations by suspending the request
    517  * if required.
    518  *
    519  * @param bc context to begin payment for.
    520  * @param pay_req #GNUNET_YES if payment was explicitly requested,
    521  *                #GNUNET_NO if payment is needed
    522  * @param[out] suspend set to true if the request should be suspended
    523  * @return MHD response
    524  */
    525 static struct MHD_Response *
    526 begin_payment (struct BackupContext *bc,
    527                int pay_req,
    528                bool *suspend)
    529 {
    530   *suspend = false;
    531   if (! bc->force_fresh_order)
    532   {
    533     enum GNUNET_DB_QueryStatus qs;
    534 
    535     qs = SYNCDB_lookup_pending_payments_by_account_TR (
    536       &bc->account,
    537       &ongoing_payment_cb,
    538       bc);
    539     if (qs < 0)
    540     {
    541       GNUNET_break (0);
    542       return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
    543                                     "pending payments");
    544     }
    545     if (NULL != bc->existing_order_id)
    546     {
    547       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    548                   "Have existing order, waiting for `%s' to complete\n",
    549                   bc->existing_order_id);
    550       await_payment (bc,
    551                      GNUNET_TIME_UNIT_ZERO /* no long polling */,
    552                      bc->existing_order_id);
    553       *suspend = true;
    554       return NULL;
    555     }
    556   }
    557   GNUNET_CONTAINER_DLL_insert (bc_head,
    558                                bc_tail,
    559                                bc);
    560   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    561               "Suspending request while creating order at `%s'\n",
    562               SH_backend_url);
    563   *suspend = true;
    564   {
    565     json_t *order;
    566 
    567     order = GNUNET_JSON_PACK (
    568       TALER_JSON_pack_amount ("amount",
    569                               &SH_annual_fee),
    570       GNUNET_JSON_pack_string ("summary",
    571                                "annual fee for sync service"),
    572       GNUNET_JSON_pack_string ("fulfillment_url",
    573                                SH_fulfillment_url));
    574     bc->po = TALER_MERCHANT_post_private_orders_create (SH_ctx,
    575                                                         SH_backend_url,
    576                                                         order);
    577     json_decref (order);
    578   }
    579   GNUNET_assert (TALER_EC_NONE ==
    580                  TALER_MERCHANT_post_private_orders_start (
    581                    bc->po,
    582                    &proposal_cb,
    583                    bc));
    584   SH_trigger_curl ();
    585   return NULL;
    586 }
    587 
    588 
    589 /**
    590  * We got some query status from the DB.  Handle the error cases.
    591  * May perform asynchronous operations by suspending the request
    592  * if required.
    593  *
    594  * @param bc request to handle status for
    595  * @param qs query status to handle
    596  * @param[out] suspend set to true if the request should be suspended
    597  * @return MHD response to return
    598  */
    599 static struct MHD_Response *
    600 handle_database_error (struct BackupContext *bc,
    601                        enum SYNC_DB_QueryStatus qs,
    602                        bool *suspend)
    603 {
    604   *suspend = false;
    605   switch (qs)
    606   {
    607   case SYNC_DB_OLD_BACKUP_MISSING:
    608     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    609                 "Update failed: no existing backup\n");
    610     return TALER_MHD2_make_error (TALER_EC_SYNC_PREVIOUS_BACKUP_UNKNOWN,
    611                                   NULL);
    612   case SYNC_DB_OLD_BACKUP_MISMATCH:
    613     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    614                 "Conflict detected, returning existing backup\n");
    615     return SH_make_backup (&bc->account,
    616                            MHD_HTTP_STATUS_CONFLICT);
    617   case SYNC_DB_PAYMENT_REQUIRED:
    618     {
    619       struct MHD_StringNullable order_id;
    620 
    621       if ( (MHD_YES !=
    622             MHD_request_get_value (bc->request,
    623                                    MHD_VK_URI_QUERY_PARAM,
    624                                    "paying",
    625                                    &order_id)) ||
    626            (NULL == order_id.cstr) )
    627       {
    628         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    629                     "Payment required, starting payment process\n");
    630         return begin_payment (bc,
    631                               GNUNET_NO,
    632                               suspend);
    633       }
    634       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    635                   "Payment required, awaiting completion of `%s'\n",
    636                   order_id.cstr);
    637       await_payment (bc,
    638                      CHECK_PAYMENT_GENERIC_TIMEOUT,
    639                      order_id.cstr);
    640       *suspend = true;
    641       return NULL;
    642     }
    643   case SYNC_DB_HARD_ERROR:
    644     GNUNET_break (0);
    645     return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_COMMIT_FAILED,
    646                                   NULL);
    647   case SYNC_DB_SOFT_ERROR:
    648     GNUNET_break (0);
    649     return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_SOFT_FAILURE,
    650                                   NULL);
    651   case SYNC_DB_NO_RESULTS:
    652     GNUNET_assert (0);
    653     return NULL;
    654   /* intentional fall-through! */
    655   case SYNC_DB_ONE_RESULT:
    656     GNUNET_assert (0);
    657     return NULL;
    658   }
    659   GNUNET_break (0);
    660   return NULL;
    661 }
    662 
    663 
    664 /**
    665  * Function to process data uploaded by a client.
    666  *
    667  * @param upload_cls our `struct BackupContext *`
    668  * @param request the request is being processed
    669  * @param content_data_size the size of the @a content_data,
    670  *                          zero when all data have been processed
    671  * @param[in] content_data the uploaded content data,
    672  *                         may be modified in the callback,
    673  *                         valid only until return from the callback,
    674  *                         NULL when all data have been processed
    675  * @return action specifying how to proceed:
    676  *         #MHD_upload_action_continue() to continue upload (for incremental
    677  *         upload processing only),
    678  *         #MHD_upload_action_suspend() to stop reading the upload until
    679  *         the request is resumed,
    680  *         #MHD_upload_action_abort_request() to close the socket,
    681  *         or a response to discard the rest of the upload and transmit
    682  *         the response
    683  */
    684 static const struct MHD_UploadAction *
    685 handle_upload (void *upload_cls,
    686                struct MHD_Request *request,
    687                size_t content_data_size,
    688                void *content_data)
    689 {
    690   struct BackupContext *bc = upload_cls;
    691 
    692   if (NULL != bc->resp)
    693   {
    694     /* This can happen on resume */
    695     return MHD_upload_action_from_response (request,
    696                                             bc->resp);
    697   }
    698   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    699               "Processing %llu bytes of upload data\n",
    700               (unsigned long long) content_data_size);
    701   /* Check hash matches */
    702   {
    703     struct GNUNET_HashCode our_hash;
    704 
    705     GNUNET_CRYPTO_hash (content_data,
    706                         content_data_size,
    707                         &our_hash);
    708     if (0 != GNUNET_memcmp (&our_hash,
    709                             &bc->new_backup_hash))
    710     {
    711       GNUNET_break_op (0);
    712       return MHD_upload_action_from_response (
    713         request,
    714         TALER_MHD2_make_error (
    715           TALER_EC_SYNC_INVALID_UPLOAD,
    716           NULL));
    717     }
    718   }
    719 
    720   /* store backup to database */
    721   {
    722     enum SYNC_DB_QueryStatus qs;
    723 
    724     if (GNUNET_is_zero (&bc->old_backup_hash))
    725     {
    726       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    727                   "Uploading first backup to account\n");
    728       qs = SYNCDB_store_backup_TR (
    729         &bc->account,
    730         &bc->account_sig,
    731         &bc->new_backup_hash,
    732         content_data_size,
    733         content_data);
    734     }
    735     else
    736     {
    737       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    738                   "Uploading existing backup of account\n");
    739       qs = SYNCDB_update_backup_TR (
    740         &bc->account,
    741         &bc->old_backup_hash,
    742         &bc->account_sig,
    743         &bc->new_backup_hash,
    744         content_data_size,
    745         content_data);
    746     }
    747     if (qs < 0)
    748     {
    749       bool suspend;
    750       struct MHD_Response *resp;
    751 
    752       resp = handle_database_error (bc,
    753                                     qs,
    754                                     &suspend);
    755       if (suspend)
    756         return MHD_upload_action_suspend (request);
    757       return MHD_upload_action_from_response (request,
    758                                               resp);
    759     }
    760     if (0 == qs)
    761     {
    762       /* database says nothing actually changed, 304 (could
    763          theoretically happen if another equivalent upload succeeded
    764          since we last checked!) */
    765       struct MHD_Response *resp;
    766 
    767       resp = MHD_response_from_empty (MHD_HTTP_STATUS_NOT_MODIFIED);
    768       TALER_MHD2_add_global_headers (resp,
    769                                      false);
    770       return MHD_upload_action_from_response (request,
    771                                               resp);
    772     }
    773   }
    774 
    775   /* generate main (204) standard success reply */
    776   {
    777     struct MHD_Response *resp;
    778 
    779     resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT);
    780     TALER_MHD2_add_global_headers (resp,
    781                                    false);
    782     return MHD_upload_action_from_response (request,
    783                                             resp);
    784   }
    785 }
    786 
    787 
    788 const struct MHD_Action *
    789 SH_backup_post (struct MHD_Request *request,
    790                 const struct SYNC_AccountPublicKeyP *account,
    791                 uint_fast64_t upload_size)
    792 {
    793   struct BackupContext *bc;
    794   union MHD_RequestInfoFixedData fi;
    795 
    796   GNUNET_assert (MHD_SC_OK ==
    797                  MHD_request_get_info_fixed (
    798                    request,
    799                    MHD_REQUEST_INFO_FIXED_APP_CONTEXT,
    800                    &fi));
    801   bc = *fi.v_app_context_ppvoid;
    802   if (NULL == bc)
    803   {
    804     /* first call, setup internals */
    805     bc = GNUNET_new (struct BackupContext);
    806     bc->request = request;
    807     bc->account = *account;
    808 #if BUG
    809     GNUNET_assert (MHD_SC_OK ==
    810                    MHD_REQUEST_SET_OPTIONS (
    811                      MHD_R_OPTION_TERMINATION_CALLBACK (&cleanup_ctx,
    812                                                         bc)));
    813 #endif
    814     *fi.v_app_context_ppvoid = bc;
    815   }
    816   if (NULL != bc->resp)
    817     return MHD_action_from_response (request,
    818                                      bc->resp);
    819   if ( (upload_size / 1024 / 1024 >= SH_upload_limit_mb) ||
    820        (upload_size > SIZE_MAX) )
    821   {
    822     GNUNET_break_op (0);
    823     return TALER_MHD2_reply_with_error (
    824       request,
    825       MHD_HTTP_STATUS_CONTENT_TOO_LARGE,
    826       TALER_EC_SYNC_EXCESSIVE_CONTENT_LENGTH,
    827       NULL);
    828   }
    829   {
    830     struct MHD_StringNullable fresh;
    831 
    832     if (MHD_YES ==
    833         MHD_request_get_value (request,
    834                                MHD_VK_URI_QUERY_PARAM,
    835                                "fresh",
    836                                &fresh))
    837       bc->force_fresh_order = true;
    838   }
    839   {
    840     struct MHD_StringNullable im;
    841 
    842     if ( (MHD_YES ==
    843           MHD_request_get_value (request,
    844                                  MHD_VK_HEADER,
    845                                  MHD_HTTP_HEADER_IF_MATCH,
    846                                  &im)) &&
    847          ( (2 >= im.len) ||
    848            ('"' != im.cstr[0]) ||
    849            ('"' != im.cstr[im.len - 1]) ||
    850            (GNUNET_OK !=
    851             GNUNET_STRINGS_string_to_data (im.cstr + 1,
    852                                            im.len - 2,
    853                                            &bc->old_backup_hash,
    854                                            sizeof (bc->old_backup_hash))) ) )
    855     {
    856       GNUNET_break_op (0);
    857       return TALER_MHD2_reply_with_error (
    858         request,
    859         MHD_HTTP_STATUS_BAD_REQUEST,
    860         TALER_EC_SYNC_BAD_IF_MATCH,
    861         NULL);
    862     }
    863   }
    864   {
    865     struct MHD_StringNullable sig_s;
    866 
    867     if ( (MHD_YES !=
    868           MHD_request_get_value (request,
    869                                  MHD_VK_HEADER,
    870                                  "Sync-Signature",
    871                                  &sig_s)) ||
    872          (GNUNET_OK !=
    873           GNUNET_STRINGS_string_to_data (sig_s.cstr,
    874                                          sig_s.len,
    875                                          &bc->account_sig,
    876                                          sizeof (bc->account_sig))) )
    877     {
    878       GNUNET_break_op (0);
    879       return TALER_MHD2_reply_with_error (
    880         request,
    881         MHD_HTTP_STATUS_BAD_REQUEST,
    882         TALER_EC_SYNC_BAD_SYNC_SIGNATURE,
    883         NULL);
    884     }
    885   }
    886   {
    887     struct MHD_StringNullable etag;
    888 
    889     if ( (MHD_YES !=
    890           MHD_request_get_value (request,
    891                                  MHD_VK_HEADER,
    892                                  MHD_HTTP_HEADER_IF_NONE_MATCH,
    893                                  &etag)) ||
    894          (2 >= etag.len) ||
    895          ('"' != etag.cstr[0]) ||
    896          ('"' != etag.cstr[etag.len - 1]) ||
    897          (GNUNET_OK !=
    898           GNUNET_STRINGS_string_to_data (etag.cstr + 1,
    899                                          etag.len - 2,
    900                                          &bc->new_backup_hash,
    901                                          sizeof (bc->new_backup_hash))) )
    902     {
    903       GNUNET_break_op (0);
    904       return TALER_MHD2_reply_with_error (
    905         request,
    906         MHD_HTTP_STATUS_BAD_REQUEST,
    907         TALER_EC_SYNC_BAD_IF_NONE_MATCH,
    908         NULL);
    909     }
    910   }
    911   /* validate signature */
    912   {
    913     struct SYNC_UploadSignaturePS usp = {
    914       .purpose.size = htonl (sizeof (usp)),
    915       .purpose.purpose = htonl (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD),
    916       .old_backup_hash = bc->old_backup_hash,
    917       .new_backup_hash = bc->new_backup_hash
    918     };
    919 
    920     if (GNUNET_OK !=
    921         GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD,
    922                                     &usp,
    923                                     &bc->account_sig.eddsa_sig,
    924                                     &account->eddsa_pub))
    925     {
    926       GNUNET_break_op (0);
    927       return TALER_MHD2_reply_with_error (
    928         request,
    929         MHD_HTTP_STATUS_FORBIDDEN,
    930         TALER_EC_SYNC_INVALID_SIGNATURE,
    931         NULL);
    932     }
    933   }
    934 
    935   /* Check database to see if the transaction is permissible */
    936   {
    937     struct GNUNET_HashCode hc;
    938     enum SYNC_DB_QueryStatus qs;
    939 
    940     qs = SYNCDB_lookup_account_TR (
    941       account,
    942       &hc);
    943     if (qs < 0)
    944     {
    945       bool suspend;
    946       struct MHD_Response *resp;
    947 
    948       resp = handle_database_error (bc,
    949                                     qs,
    950                                     &suspend);
    951       if (suspend)
    952         return MHD_action_suspend (request);
    953       return MHD_action_from_response (request,
    954                                        resp);
    955     }
    956     if (SYNC_DB_NO_RESULTS == qs)
    957       memset (&hc,
    958               0,
    959               sizeof (hc));
    960     if (0 == GNUNET_memcmp (&hc,
    961                             &bc->new_backup_hash))
    962     {
    963       /* Refuse upload: we already have that backup! */
    964       struct MHD_Response *resp;
    965 
    966       resp = MHD_response_from_empty (MHD_HTTP_STATUS_NOT_MODIFIED);
    967       TALER_MHD2_add_global_headers (resp,
    968                                      false);
    969       return MHD_action_from_response (request,
    970                                        resp);
    971     }
    972     if (0 != GNUNET_memcmp (&hc,
    973                             &bc->old_backup_hash))
    974     {
    975       /* Refuse upload: if-none-match failed! */
    976       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    977                   "Conflict detected, returning existing backup\n");
    978       return MHD_action_from_response (
    979         request,
    980         SH_make_backup (account,
    981                         MHD_HTTP_STATUS_CONFLICT));
    982     }
    983   }
    984   /* check if the client insists on paying */
    985   {
    986     struct MHD_StringNullable order_req;
    987 
    988     if (MHD_YES ==
    989         MHD_request_get_value (request,
    990                                MHD_VK_URI_QUERY_PARAM,
    991                                "pay",
    992                                &order_req))
    993     {
    994       struct MHD_Response *resp;
    995       bool suspend;
    996 
    997       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    998                   "Payment requested, starting payment process\n");
    999       resp = begin_payment (bc,
   1000                             GNUNET_YES,
   1001                             &suspend);
   1002       if (suspend)
   1003         return MHD_action_suspend (request);
   1004       return MHD_action_from_response (request,
   1005                                        resp);
   1006     }
   1007   }
   1008   /* ready to begin upload! */
   1009   return MHD_action_process_upload_full (request,
   1010                                          upload_size,
   1011                                          &handle_upload,
   1012                                          bc);
   1013 }