LCOV - code coverage report
Current view: top level - sync - sync-httpd_backup_post.c (source / functions) Hit Total Coverage
Test: GNU Taler sync coverage report Lines: 193 301 64.1 %
Date: 2021-08-30 06:54:48 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14