LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_purses_merge.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 144 215 67.0 %
Date: 2025-07-02 14:24:08 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2022-2024 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 taler-exchange-httpd_purses_merge.c
      18             :  * @brief Handle /purses/$PID/merge requests; parses the POST and JSON and
      19             :  *        verifies the reserve signature before handing things off
      20             :  *        to the database.
      21             :  * @author Christian Grothoff
      22             :  */
      23             : #include "taler/platform.h"
      24             : #include <gnunet/gnunet_util_lib.h>
      25             : #include <gnunet/gnunet_json_lib.h>
      26             : #include <jansson.h>
      27             : #include <microhttpd.h>
      28             : #include <pthread.h>
      29             : #include "taler/taler_dbevents.h"
      30             : #include "taler/taler_json_lib.h"
      31             : #include "taler/taler_kyclogic_lib.h"
      32             : #include "taler/taler_mhd_lib.h"
      33             : #include "taler-exchange-httpd_common_kyc.h"
      34             : #include "taler-exchange-httpd_purses_merge.h"
      35             : #include "taler-exchange-httpd_responses.h"
      36             : #include "taler/taler_exchangedb_lib.h"
      37             : #include "taler-exchange-httpd_keys.h"
      38             : 
      39             : 
      40             : /**
      41             :  * Closure for #merge_transaction.
      42             :  */
      43             : struct PurseMergeContext
      44             : {
      45             : 
      46             :   /**
      47             :    * Kept in a DLL.
      48             :    */
      49             :   struct PurseMergeContext *next;
      50             : 
      51             :   /**
      52             :    * Kept in a DLL.
      53             :    */
      54             :   struct PurseMergeContext *prev;
      55             : 
      56             :   /**
      57             :    * Our request.
      58             :    */
      59             :   struct TEH_RequestContext *rc;
      60             : 
      61             :   /**
      62             :    * Handle for the legitimization check.
      63             :    */
      64             :   struct TEH_LegitimizationCheckHandle *lch;
      65             : 
      66             :   /**
      67             :    * Fees that apply to this operation.
      68             :    */
      69             :   const struct TALER_WireFeeSet *wf;
      70             : 
      71             :   /**
      72             :    * Base URL of the exchange provider hosting the reserve.
      73             :    */
      74             :   char *provider_url;
      75             : 
      76             :   /**
      77             :    * URI of the account the purse is to be merged into.
      78             :    * Must be of the form 'payto://taler-reserve/$EXCHANGE_URL/RESERVE_PUB'.
      79             :    */
      80             :   struct TALER_NormalizedPayto payto_uri;
      81             : 
      82             :   /**
      83             :    * Response to return, if set.
      84             :    */
      85             :   struct MHD_Response *response;
      86             : 
      87             :   /**
      88             :    * Public key of the purse we are creating.
      89             :    */
      90             :   struct TALER_PurseContractPublicKeyP purse_pub;
      91             : 
      92             :   /**
      93             :    * Total amount to be put into the purse.
      94             :    */
      95             :   struct TALER_Amount target_amount;
      96             : 
      97             :   /**
      98             :    * Current amount in the purse.
      99             :    */
     100             :   struct TALER_Amount balance;
     101             : 
     102             :   /**
     103             :    * When should the purse expire.
     104             :    */
     105             :   struct GNUNET_TIME_Timestamp purse_expiration;
     106             : 
     107             :   /**
     108             :    * When the client signed the merge.
     109             :    */
     110             :   struct GNUNET_TIME_Timestamp merge_timestamp;
     111             : 
     112             :   /**
     113             :    * Our current time.
     114             :    */
     115             :   struct GNUNET_TIME_Timestamp exchange_timestamp;
     116             : 
     117             :   /**
     118             :    * Merge key for the purse.
     119             :    */
     120             :   struct TALER_PurseMergePublicKeyP merge_pub;
     121             : 
     122             :   /**
     123             :    * Signature of the reservce affiming this request.
     124             :    */
     125             :   struct TALER_ReserveSignatureP reserve_sig;
     126             : 
     127             :   /**
     128             :    * Signature of the client affiming the merge.
     129             :    */
     130             :   struct TALER_PurseMergeSignatureP merge_sig;
     131             : 
     132             :   /**
     133             :    * Public key of the reserve (account), as extracted from @e payto_uri.
     134             :    */
     135             :   union TALER_AccountPublicKeyP account_pub;
     136             : 
     137             :   /**
     138             :    * Hash of the contract terms of the purse.
     139             :    */
     140             :   struct TALER_PrivateContractHashP h_contract_terms;
     141             : 
     142             :   /**
     143             :    * Hash of the @e payto_uri.
     144             :    */
     145             :   struct TALER_NormalizedPaytoHashP h_payto;
     146             : 
     147             :   /**
     148             :    * KYC status of the operation.
     149             :    */
     150             :   struct TALER_EXCHANGEDB_KycStatus kyc;
     151             : 
     152             :   /**
     153             :    * HTTP status to return with @e response, or 0.
     154             :    */
     155             :   unsigned int http_status;
     156             : 
     157             :   /**
     158             :    * Minimum age for deposits into this purse.
     159             :    */
     160             :   uint32_t min_age;
     161             : 
     162             :   /**
     163             :    * Set to true if this request was suspended.
     164             :    */
     165             :   bool suspended;
     166             : };
     167             : 
     168             : 
     169             : /**
     170             :  * Kept in a DLL.
     171             :  */
     172             : static struct PurseMergeContext *pmc_head;
     173             : 
     174             : /**
     175             :  * Kept in a DLL.
     176             :  */
     177             : static struct PurseMergeContext *pmc_tail;
     178             : 
     179             : 
     180             : void
     181          21 : TEH_purses_merge_cleanup ()
     182             : {
     183             :   struct PurseMergeContext *pmc;
     184             : 
     185          21 :   while (NULL != (pmc = pmc_head))
     186             :   {
     187           0 :     GNUNET_CONTAINER_DLL_remove (pmc_head,
     188             :                                  pmc_tail,
     189             :                                  pmc);
     190           0 :     MHD_resume_connection (pmc->rc->connection);
     191             :   }
     192          21 : }
     193             : 
     194             : 
     195             : /**
     196             :  * Function called with the result of a legitimization
     197             :  * check.
     198             :  *
     199             :  * @param cls closure
     200             :  * @param lcr legitimization check result
     201             :  */
     202             : static void
     203           6 : legi_result_cb (
     204             :   void *cls,
     205             :   const struct TEH_LegitimizationCheckResult *lcr)
     206             : {
     207           6 :   struct PurseMergeContext *pmc = cls;
     208             : 
     209           6 :   pmc->lch = NULL;
     210           6 :   MHD_resume_connection (pmc->rc->connection);
     211           6 :   GNUNET_CONTAINER_DLL_remove (pmc_head,
     212             :                                pmc_tail,
     213             :                                pmc);
     214           6 :   TALER_MHD_daemon_trigger ();
     215           6 :   if (NULL != lcr->response)
     216             :   {
     217           0 :     pmc->response = lcr->response;
     218           0 :     pmc->http_status = lcr->http_status;
     219           0 :     return;
     220             :   }
     221           6 :   pmc->kyc = lcr->kyc;
     222             : }
     223             : 
     224             : 
     225             : /**
     226             :  * Send confirmation of purse creation success to client.
     227             :  *
     228             :  * @param pmc details about the request that succeeded
     229             :  * @return MHD result code
     230             :  */
     231             : static MHD_RESULT
     232           3 : reply_merge_success (const struct PurseMergeContext *pmc)
     233             : {
     234           3 :   struct MHD_Connection *connection = pmc->rc->connection;
     235             :   struct TALER_ExchangePublicKeyP pub;
     236             :   struct TALER_ExchangeSignatureP sig;
     237             :   enum TALER_ErrorCode ec;
     238             :   struct TALER_Amount merge_amount;
     239             : 
     240           3 :   if (0 <
     241           3 :       TALER_amount_cmp (&pmc->balance,
     242             :                         &pmc->target_amount))
     243             :   {
     244           0 :     GNUNET_break (0);
     245           0 :     return TALER_MHD_REPLY_JSON_PACK (
     246             :       connection,
     247             :       MHD_HTTP_INTERNAL_SERVER_ERROR,
     248             :       TALER_JSON_pack_amount ("balance",
     249             :                               &pmc->balance),
     250             :       TALER_JSON_pack_amount ("target_amount",
     251             :                               &pmc->target_amount));
     252             :   }
     253           3 :   if ( (NULL == pmc->provider_url) ||
     254           0 :        (0 == strcmp (pmc->provider_url,
     255             :                      TEH_base_url)) )
     256             :   {
     257             :     /* wad fee is always zero if we stay at our own exchange */
     258           3 :     merge_amount = pmc->target_amount;
     259             :   }
     260             :   else
     261             :   {
     262             : #if WAD_NOT_IMPLEMENTED /* #7271 */
     263             :     /* FIXME: figure out partner, lookup wad fee by partner! #7271 */
     264             :     if (0 >
     265             :         TALER_amount_subtract (&merge_amount,
     266             :                                &pmc->target_amount,
     267             :                                &wad_fee))
     268             :     {
     269             :       GNUNET_assert (GNUNET_OK ==
     270             :                      TALER_amount_set_zero (TEH_currency,
     271             :                                             &merge_amount));
     272             :     }
     273             : #else
     274           0 :     merge_amount = pmc->target_amount;
     275             : #endif
     276             :   }
     277           3 :   if (TALER_EC_NONE !=
     278           3 :       (ec = TALER_exchange_online_purse_merged_sign (
     279             :          &TEH_keys_exchange_sign_,
     280             :          pmc->exchange_timestamp,
     281             :          pmc->purse_expiration,
     282             :          &merge_amount,
     283             :          &pmc->purse_pub,
     284             :          &pmc->h_contract_terms,
     285             :          &pmc->account_pub.reserve_pub,
     286           3 :          (NULL != pmc->provider_url)
     287             :          ? pmc->provider_url
     288             :          : TEH_base_url,
     289             :          &pub,
     290             :          &sig)))
     291             :   {
     292           0 :     GNUNET_break (0);
     293           0 :     return TALER_MHD_reply_with_ec (connection,
     294             :                                     ec,
     295             :                                     NULL);
     296             :   }
     297           3 :   return TALER_MHD_REPLY_JSON_PACK (
     298             :     connection,
     299             :     MHD_HTTP_OK,
     300             :     TALER_JSON_pack_amount ("merge_amount",
     301             :                             &merge_amount),
     302             :     GNUNET_JSON_pack_timestamp ("exchange_timestamp",
     303             :                                 pmc->exchange_timestamp),
     304             :     GNUNET_JSON_pack_data_auto ("exchange_sig",
     305             :                                 &sig),
     306             :     GNUNET_JSON_pack_data_auto ("exchange_pub",
     307             :                                 &pub));
     308             : }
     309             : 
     310             : 
     311             : /**
     312             :  * Function called to iterate over KYC-relevant
     313             :  * transaction amounts for a particular time range.
     314             :  * Called within a database transaction, so must
     315             :  * not start a new one.
     316             :  *
     317             :  * @param cls a `struct PurseMergeContext`
     318             :  * @param limit maximum time-range for which events
     319             :  *        should be fetched (timestamp in the past)
     320             :  * @param cb function to call on each event found,
     321             :  *        events must be returned in reverse chronological
     322             :  *        order
     323             :  * @param cb_cls closure for @a cb
     324             :  * @return transaction status
     325             :  */
     326             : static enum GNUNET_DB_QueryStatus
     327           2 : amount_iterator (void *cls,
     328             :                  struct GNUNET_TIME_Absolute limit,
     329             :                  TALER_EXCHANGEDB_KycAmountCallback cb,
     330             :                  void *cb_cls)
     331             : {
     332           2 :   struct PurseMergeContext *pmc = cls;
     333             :   enum GNUNET_GenericReturnValue ret;
     334             :   enum GNUNET_DB_QueryStatus qs;
     335             : 
     336           2 :   ret = cb (cb_cls,
     337           2 :             &pmc->target_amount,
     338             :             GNUNET_TIME_absolute_get ());
     339           2 :   GNUNET_break (GNUNET_SYSERR != ret);
     340           2 :   if (GNUNET_OK != ret)
     341           0 :     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     342           2 :   qs = TEH_plugin->select_merge_amounts_for_kyc_check (
     343           2 :     TEH_plugin->cls,
     344           2 :     &pmc->h_payto,
     345             :     limit,
     346             :     cb,
     347             :     cb_cls);
     348           2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     349             :               "Got %d additional transactions for this merge and limit %llu\n",
     350             :               qs,
     351             :               (unsigned long long) limit.abs_value_us);
     352           2 :   GNUNET_break (qs >= 0);
     353           2 :   return qs;
     354             : }
     355             : 
     356             : 
     357             : /**
     358             :  * Execute database transaction for /purses/$PID/merge.  Runs the transaction
     359             :  * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
     360             :  * a MHD response.  IF it returns an hard error, the transaction logic MUST
     361             :  * queue a MHD response and set @a mhd_ret.  IF it returns the soft error
     362             :  * code, the function MAY be called again to retry and MUST not queue a MHD
     363             :  * response.
     364             :  *
     365             :  * @param cls a `struct PurseMergeContext`
     366             :  * @param connection MHD request context
     367             :  * @param[out] mhd_ret set to MHD status on error
     368             :  * @return transaction status
     369             :  */
     370             : static enum GNUNET_DB_QueryStatus
     371           5 : merge_transaction (void *cls,
     372             :                    struct MHD_Connection *connection,
     373             :                    MHD_RESULT *mhd_ret)
     374             : {
     375           5 :   struct PurseMergeContext *pmc = cls;
     376             :   enum GNUNET_DB_QueryStatus qs;
     377           5 :   bool in_conflict = true;
     378           5 :   bool no_balance = true;
     379           5 :   bool no_partner = true;
     380             : 
     381           5 :   qs = TEH_plugin->do_purse_merge (
     382           5 :     TEH_plugin->cls,
     383           5 :     &pmc->purse_pub,
     384           5 :     &pmc->merge_sig,
     385             :     pmc->merge_timestamp,
     386           5 :     &pmc->reserve_sig,
     387           5 :     pmc->provider_url,
     388           5 :     &pmc->account_pub.reserve_pub,
     389             :     &no_partner,
     390             :     &no_balance,
     391             :     &in_conflict);
     392           5 :   if (qs < 0)
     393             :   {
     394           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     395           0 :       return qs;
     396           0 :     GNUNET_break (0);
     397           0 :     *mhd_ret =
     398           0 :       TALER_MHD_reply_with_error (connection,
     399             :                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
     400             :                                   TALER_EC_GENERIC_DB_STORE_FAILED,
     401             :                                   "purse merge");
     402           0 :     return qs;
     403             :   }
     404           5 :   if (no_partner)
     405             :   {
     406           0 :     *mhd_ret =
     407           0 :       TALER_MHD_reply_with_error (connection,
     408             :                                   MHD_HTTP_NOT_FOUND,
     409             :                                   TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN,
     410           0 :                                   pmc->provider_url);
     411           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     412             :   }
     413           5 :   if (no_balance)
     414             :   {
     415           0 :     *mhd_ret =
     416           0 :       TALER_MHD_reply_with_error (connection,
     417             :                                   MHD_HTTP_PAYMENT_REQUIRED,
     418             :                                   TALER_EC_EXCHANGE_PURSE_NOT_FULL,
     419             :                                   NULL);
     420           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     421             :   }
     422           5 :   if (in_conflict)
     423             :   {
     424             :     struct TALER_PurseMergeSignatureP merge_sig;
     425             :     struct GNUNET_TIME_Timestamp merge_timestamp;
     426           2 :     char *partner_url = NULL;
     427             :     struct TALER_ReservePublicKeyP reserve_pub;
     428             :     bool refunded;
     429             : 
     430           2 :     qs = TEH_plugin->select_purse_merge (TEH_plugin->cls,
     431           2 :                                          &pmc->purse_pub,
     432             :                                          &merge_sig,
     433             :                                          &merge_timestamp,
     434             :                                          &partner_url,
     435             :                                          &reserve_pub,
     436             :                                          &refunded);
     437           2 :     if (qs <= 0)
     438             :     {
     439           0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     440           0 :         return qs;
     441           0 :       TALER_LOG_WARNING (
     442             :         "Failed to fetch merge purse information from database\n");
     443           0 :       *mhd_ret =
     444           0 :         TALER_MHD_reply_with_error (connection,
     445             :                                     MHD_HTTP_INTERNAL_SERVER_ERROR,
     446             :                                     TALER_EC_GENERIC_DB_FETCH_FAILED,
     447             :                                     "select purse merge");
     448           0 :       return qs;
     449             :     }
     450           2 :     if (refunded)
     451             :     {
     452           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     453             :                   "Purse was already refunded\n");
     454           0 :       *mhd_ret = TALER_MHD_reply_with_error (connection,
     455             :                                              MHD_HTTP_GONE,
     456             :                                              TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
     457             :                                              NULL);
     458           0 :       GNUNET_free (partner_url);
     459           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     460             :     }
     461           2 :     if (0 !=
     462           2 :         GNUNET_memcmp (&merge_sig,
     463             :                        &pmc->merge_sig))
     464             :     {
     465           2 :       *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
     466             :         connection,
     467             :         MHD_HTTP_CONFLICT,
     468             :         GNUNET_JSON_pack_timestamp ("merge_timestamp",
     469             :                                     merge_timestamp),
     470             :         GNUNET_JSON_pack_data_auto ("merge_sig",
     471             :                                     &merge_sig),
     472             :         GNUNET_JSON_pack_allow_null (
     473             :           GNUNET_JSON_pack_string ("partner_url",
     474             :                                    partner_url)),
     475             :         GNUNET_JSON_pack_data_auto ("reserve_pub",
     476             :                                     &reserve_pub));
     477           2 :       GNUNET_free (partner_url);
     478           2 :       return GNUNET_DB_STATUS_HARD_ERROR;
     479             :     }
     480             :     /* idempotent! */
     481           0 :     *mhd_ret = reply_merge_success (pmc);
     482           0 :     GNUNET_free (partner_url);
     483           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     484             :   }
     485             : 
     486           3 :   return qs;
     487             : }
     488             : 
     489             : 
     490             : /**
     491             :  * Purse-merge-specific cleanup routine. Function called
     492             :  * upon completion of the request that should
     493             :  * clean up @a rh_ctx. Can be NULL.
     494             :  *
     495             :  * @param rc request context to clean up
     496             :  */
     497             : static void
     498           6 : clean_purse_merge_rc (struct TEH_RequestContext *rc)
     499             : {
     500           6 :   struct PurseMergeContext *pmc = rc->rh_ctx;
     501             : 
     502           6 :   if (NULL != pmc->lch)
     503             :   {
     504           0 :     TEH_legitimization_check_cancel (pmc->lch);
     505           0 :     pmc->lch = NULL;
     506             :   }
     507           6 :   GNUNET_free (pmc->provider_url);
     508           6 :   GNUNET_free (pmc);
     509           6 : }
     510             : 
     511             : 
     512             : MHD_RESULT
     513          12 : TEH_handler_purses_merge (
     514             :   struct TEH_RequestContext *rc,
     515             :   const struct TALER_PurseContractPublicKeyP *purse_pub,
     516             :   const json_t *root)
     517             : {
     518          12 :   struct PurseMergeContext *pmc = rc->rh_ctx;
     519             : 
     520          12 :   if (NULL == pmc)
     521             :   {
     522           6 :     pmc = GNUNET_new (struct PurseMergeContext);
     523           6 :     rc->rh_ctx = pmc;
     524           6 :     rc->rh_cleaner = &clean_purse_merge_rc;
     525           6 :     pmc->rc = rc;
     526           6 :     pmc->purse_pub = *purse_pub;
     527             :     pmc->exchange_timestamp
     528           6 :       = GNUNET_TIME_timestamp_get ();
     529             : 
     530             :     {
     531             :       struct GNUNET_JSON_Specification spec[] = {
     532           6 :         TALER_JSON_spec_normalized_payto_uri ("payto_uri",
     533             :                                               &pmc->payto_uri),
     534           6 :         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
     535             :                                      &pmc->reserve_sig),
     536           6 :         GNUNET_JSON_spec_fixed_auto ("merge_sig",
     537             :                                      &pmc->merge_sig),
     538           6 :         GNUNET_JSON_spec_timestamp ("merge_timestamp",
     539             :                                     &pmc->merge_timestamp),
     540           6 :         GNUNET_JSON_spec_end ()
     541             :       };
     542             :       enum GNUNET_GenericReturnValue res;
     543             : 
     544           6 :       res = TALER_MHD_parse_json_data (rc->connection,
     545             :                                        root,
     546             :                                        spec);
     547           6 :       if (GNUNET_SYSERR == res)
     548             :       {
     549           0 :         GNUNET_break (0);
     550           0 :         return MHD_NO; /* hard failure */
     551             :       }
     552           6 :       if (GNUNET_NO == res)
     553             :       {
     554           0 :         GNUNET_break_op (0);
     555           0 :         return MHD_YES; /* failure */
     556             :       }
     557             :     }
     558             : 
     559             :     {
     560             :       struct TALER_PurseContractSignatureP purse_sig;
     561             :       enum GNUNET_DB_QueryStatus qs;
     562             : 
     563             :       /* Fetch purse details */
     564           6 :       qs = TEH_plugin->get_purse_request (
     565           6 :         TEH_plugin->cls,
     566           6 :         &pmc->purse_pub,
     567             :         &pmc->merge_pub,
     568             :         &pmc->purse_expiration,
     569             :         &pmc->h_contract_terms,
     570             :         &pmc->min_age,
     571             :         &pmc->target_amount,
     572             :         &pmc->balance,
     573             :         &purse_sig);
     574           6 :       switch (qs)
     575             :       {
     576           0 :       case GNUNET_DB_STATUS_HARD_ERROR:
     577           0 :         GNUNET_break (0);
     578           0 :         return TALER_MHD_reply_with_error (
     579             :           rc->connection,
     580             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     581             :           TALER_EC_GENERIC_DB_FETCH_FAILED,
     582             :           "select purse request");
     583           0 :       case GNUNET_DB_STATUS_SOFT_ERROR:
     584           0 :         GNUNET_break (0);
     585           0 :         return TALER_MHD_reply_with_error (
     586             :           rc->connection,
     587             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     588             :           TALER_EC_GENERIC_DB_FETCH_FAILED,
     589             :           "select purse request");
     590           0 :       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     591           0 :         return TALER_MHD_reply_with_error (
     592             :           rc->connection,
     593             :           MHD_HTTP_NOT_FOUND,
     594             :           TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
     595             :           NULL);
     596           6 :       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     597             :         /* continued below */
     598           6 :         break;
     599             :       }
     600             :     }
     601             : 
     602             :     /* check signatures */
     603           6 :     if (GNUNET_OK !=
     604           6 :         TALER_wallet_purse_merge_verify (
     605             :           pmc->payto_uri,
     606             :           pmc->merge_timestamp,
     607           6 :           &pmc->purse_pub,
     608           6 :           &pmc->merge_pub,
     609           6 :           &pmc->merge_sig))
     610             :     {
     611           0 :       GNUNET_break_op (0);
     612           0 :       return TALER_MHD_reply_with_error (
     613             :         rc->connection,
     614             :         MHD_HTTP_FORBIDDEN,
     615             :         TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE,
     616             :         NULL);
     617             :     }
     618             : 
     619             :     /* parse 'payto_uri' into pmc->account_pub and provider_url */
     620           6 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     621             :                 "Received payto: `%s'\n",
     622             :                 pmc->payto_uri.normalized_payto);
     623           6 :     if ( (0 != strncmp (pmc->payto_uri.normalized_payto,
     624             :                         "payto://taler-reserve/",
     625           6 :                         strlen ("payto://taler-reserve/"))) &&
     626           6 :          (0 != strncmp (pmc->payto_uri.normalized_payto,
     627             :                         "payto://taler-reserve-http/",
     628             :                         strlen ("payto://taler-reserve-http/"))) )
     629             :     {
     630           0 :       GNUNET_break_op (0);
     631           0 :       return TALER_MHD_reply_with_error (
     632             :         rc->connection,
     633             :         MHD_HTTP_BAD_REQUEST,
     634             :         TALER_EC_GENERIC_PARAMETER_MALFORMED,
     635             :         "payto_uri");
     636             :     }
     637             : 
     638             :     {
     639             :       bool http;
     640             :       const char *host;
     641             :       const char *slash;
     642             : 
     643           6 :       http = (0 == strncmp (pmc->payto_uri.normalized_payto,
     644             :                             "payto://taler-reserve-http/",
     645             :                             strlen ("payto://taler-reserve-http/")));
     646          12 :       host = &pmc->payto_uri.normalized_payto[http
     647             :                             ? strlen ("payto://taler-reserve-http/")
     648           6 :                             : strlen ("payto://taler-reserve/")];
     649           6 :       slash = strchr (host,
     650             :                       '/');
     651           6 :       if (NULL == slash)
     652             :       {
     653           0 :         GNUNET_break_op (0);
     654           0 :         return TALER_MHD_reply_with_error (
     655             :           rc->connection,
     656             :           MHD_HTTP_BAD_REQUEST,
     657             :           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     658             :           "payto_uri");
     659             :       }
     660           6 :       GNUNET_asprintf (&pmc->provider_url,
     661             :                        "%s://%.*s/",
     662             :                        http ? "http" : "https",
     663           6 :                        (int) (slash - host),
     664             :                        host);
     665           6 :       slash++;
     666           6 :       if (GNUNET_OK !=
     667           6 :           GNUNET_STRINGS_string_to_data (
     668             :             slash,
     669             :             strlen (slash),
     670           6 :             &pmc->account_pub.reserve_pub,
     671             :             sizeof (pmc->account_pub.reserve_pub)))
     672             :       {
     673           0 :         GNUNET_break_op (0);
     674           0 :         return TALER_MHD_reply_with_error (
     675             :           rc->connection,
     676             :           MHD_HTTP_BAD_REQUEST,
     677             :           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     678             :           "payto_uri");
     679             :       }
     680             :     }
     681           6 :     TALER_normalized_payto_hash (pmc->payto_uri,
     682             :                                  &pmc->h_payto);
     683           6 :     if (0 == strcmp (pmc->provider_url,
     684             :                      TEH_base_url))
     685             :     {
     686             :       /* we use NULL to represent 'self' as the provider */
     687           6 :       GNUNET_free (pmc->provider_url);
     688             :     }
     689             :     else
     690             :     {
     691           0 :       char *method = GNUNET_strdup ("FIXME-WAD #7271");
     692             : 
     693             :       /* FIXME-#7271: lookup wire method by pmc.provider_url! */
     694           0 :       pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp,
     695             :                                        method);
     696           0 :       if (NULL == pmc->wf)
     697             :       {
     698             :         MHD_RESULT res;
     699             : 
     700           0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     701             :                     "Cannot merge purse: wire fees not configured!\n");
     702           0 :         res = TALER_MHD_reply_with_error (
     703             :           rc->connection,
     704             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     705             :           TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING,
     706             :           method);
     707           0 :         GNUNET_free (method);
     708           0 :         return res;
     709             :       }
     710           0 :       GNUNET_free (method);
     711             :     }
     712             : 
     713             :     {
     714             :       struct TALER_Amount zero_purse_fee;
     715             : 
     716           6 :       GNUNET_assert (GNUNET_OK ==
     717             :                      TALER_amount_set_zero (
     718             :                        pmc->target_amount.currency,
     719             :                        &zero_purse_fee));
     720           6 :       if (GNUNET_OK !=
     721           6 :           TALER_wallet_account_merge_verify (
     722             :             pmc->merge_timestamp,
     723           6 :             &pmc->purse_pub,
     724             :             pmc->purse_expiration,
     725           6 :             &pmc->h_contract_terms,
     726           6 :             &pmc->target_amount,
     727             :             &zero_purse_fee,
     728             :             pmc->min_age,
     729             :             TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
     730           6 :             &pmc->account_pub.reserve_pub,
     731           6 :             &pmc->reserve_sig))
     732             :       {
     733           0 :         GNUNET_break_op (0);
     734           0 :         return TALER_MHD_reply_with_error (
     735             :           rc->connection,
     736             :           MHD_HTTP_FORBIDDEN,
     737             :           TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE,
     738             :           NULL);
     739             :       }
     740             :     }
     741             :     {
     742             :       struct TALER_FullPayto fake_full_payto;
     743             : 
     744           6 :       GNUNET_asprintf (&fake_full_payto.full_payto,
     745             :                        "%s?receiver-name=wallet",
     746             :                        pmc->payto_uri.normalized_payto);
     747          12 :       pmc->lch = TEH_legitimization_check (
     748           6 :         &rc->async_scope_id,
     749             :         TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
     750             :         fake_full_payto,
     751           6 :         &pmc->h_payto,
     752           6 :         &pmc->account_pub,
     753             :         &amount_iterator,
     754             :         pmc,
     755             :         &legi_result_cb,
     756             :         pmc);
     757           6 :       GNUNET_free (fake_full_payto.full_payto);
     758             :     }
     759           6 :     GNUNET_assert (NULL != pmc->lch);
     760           6 :     MHD_suspend_connection (rc->connection);
     761           6 :     GNUNET_CONTAINER_DLL_insert (pmc_head,
     762             :                                  pmc_tail,
     763             :                                  pmc);
     764           6 :     return MHD_YES;
     765             :   }
     766           6 :   if (NULL != pmc->response)
     767             :   {
     768           0 :     return MHD_queue_response (rc->connection,
     769             :                                pmc->http_status,
     770             :                                pmc->response);
     771             :   }
     772           6 :   if (! pmc->kyc.ok)
     773           1 :     return TEH_RESPONSE_reply_kyc_required (
     774             :       rc->connection,
     775           1 :       &pmc->h_payto,
     776           1 :       &pmc->kyc,
     777             :       false);
     778             : 
     779             :   /* execute merge transaction */
     780             :   {
     781             :     MHD_RESULT mhd_ret;
     782             : 
     783           5 :     if (GNUNET_OK !=
     784           5 :         TEH_DB_run_transaction (rc->connection,
     785             :                                 "execute purse merge",
     786             :                                 TEH_MT_REQUEST_PURSE_MERGE,
     787             :                                 &mhd_ret,
     788             :                                 &merge_transaction,
     789             :                                 pmc))
     790             :     {
     791           2 :       return mhd_ret;
     792             :     }
     793             :   }
     794             : 
     795             :   {
     796           3 :     struct TALER_PurseEventP rep = {
     797           3 :       .header.size = htons (sizeof (rep)),
     798           3 :       .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
     799             :       .purse_pub = pmc->purse_pub
     800             :     };
     801             : 
     802           3 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     803             :                 "Notifying about purse merge\n");
     804           3 :     TEH_plugin->event_notify (TEH_plugin->cls,
     805             :                               &rep.header,
     806             :                               NULL,
     807             :                               0);
     808             :   }
     809             : 
     810             :   /* generate regular response */
     811           3 :   return reply_merge_success (pmc);
     812             : }
     813             : 
     814             : 
     815             : /* end of taler-exchange-httpd_purses_merge.c */

Generated by: LCOV version 1.16