sync

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

sync-httpd_backup_post.c (32696B)


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