LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_reserves_purse.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 166 261 63.6 %
Date: 2025-07-09 07:38:29 Functions: 6 6 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_reserves_purse.c
      18             :  * @brief Handle /reserves/$RID/purse requests; parses the POST and JSON and
      19             :  *        verifies the coin 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_json_lib.h"
      30             : #include "taler/taler_kyclogic_lib.h"
      31             : #include "taler/taler_mhd_lib.h"
      32             : #include "taler-exchange-httpd_common_kyc.h"
      33             : #include "taler-exchange-httpd_reserves_purse.h"
      34             : #include "taler-exchange-httpd_responses.h"
      35             : #include "taler/taler_exchangedb_lib.h"
      36             : #include "taler-exchange-httpd_keys.h"
      37             : 
      38             : 
      39             : /**
      40             :  * Closure for #purse_transaction.
      41             :  */
      42             : struct ReservePurseContext
      43             : {
      44             : 
      45             :   /**
      46             :    * Kept in a DLL.
      47             :    */
      48             :   struct ReservePurseContext *next;
      49             : 
      50             :   /**
      51             :    * Kept in a DLL.
      52             :    */
      53             :   struct ReservePurseContext *prev;
      54             : 
      55             :   /**
      56             :    * Our request context.
      57             :    */
      58             :   struct TEH_RequestContext *rc;
      59             : 
      60             :   /**
      61             :    * Handle for legitimization check.
      62             :    */
      63             :   struct TEH_LegitimizationCheckHandle *lch;
      64             : 
      65             :   /**
      66             :    * Response to return. Note that the response must
      67             :    * be queued or destroyed by the callee.  NULL
      68             :    * if the legitimization check was successful and the handler should return
      69             :    * a handler-specific result.
      70             :    */
      71             :   struct MHD_Response *response;
      72             : 
      73             :   /**
      74             :    * Payto URI for the reserve.
      75             :    */
      76             :   struct TALER_NormalizedPayto payto_uri;
      77             : 
      78             :   /**
      79             :    * Public key of the account (reserve) we are creating a purse for.
      80             :    */
      81             :   union TALER_AccountPublicKeyP account_pub;
      82             : 
      83             :   /**
      84             :    * Signature of the reserve affirming the merge.
      85             :    */
      86             :   struct TALER_ReserveSignatureP reserve_sig;
      87             : 
      88             :   /**
      89             :    * Purse fee the client is willing to pay.
      90             :    */
      91             :   struct TALER_Amount purse_fee;
      92             : 
      93             :   /**
      94             :    * Total amount already put into the purse.
      95             :    */
      96             :   struct TALER_Amount deposit_total;
      97             : 
      98             :   /**
      99             :    * Merge time.
     100             :    */
     101             :   struct GNUNET_TIME_Timestamp merge_timestamp;
     102             : 
     103             :   /**
     104             :    * Our current time.
     105             :    */
     106             :   struct GNUNET_TIME_Timestamp exchange_timestamp;
     107             : 
     108             :   /**
     109             :    * Details about an encrypted contract, if any.
     110             :    */
     111             :   struct TALER_EncryptedContract econtract;
     112             : 
     113             :   /**
     114             :    * Merge key for the purse.
     115             :    */
     116             :   struct TALER_PurseMergePublicKeyP merge_pub;
     117             : 
     118             :   /**
     119             :    * Merge affirmation by the @e merge_pub.
     120             :    */
     121             :   struct TALER_PurseMergeSignatureP merge_sig;
     122             : 
     123             :   /**
     124             :    * Signature of the client affiming this request.
     125             :    */
     126             :   struct TALER_PurseContractSignatureP purse_sig;
     127             : 
     128             :   /**
     129             :    * Fundamental details about the purse.
     130             :    */
     131             :   struct TEH_PurseDetails pd;
     132             : 
     133             :   /**
     134             :    * Hash of the @e payto_uri.
     135             :    */
     136             :   struct TALER_NormalizedPaytoHashP h_payto;
     137             : 
     138             :   /**
     139             :    * KYC status of the operation.
     140             :    */
     141             :   struct TALER_EXCHANGEDB_KycStatus kyc;
     142             : 
     143             :   /**
     144             :    * Minimum age for deposits into this purse.
     145             :    */
     146             :   uint32_t min_age;
     147             : 
     148             :   /**
     149             :    * Flags for the operation.
     150             :    */
     151             :   enum TALER_WalletAccountMergeFlags flags;
     152             : 
     153             :   /**
     154             :    * HTTP status code for @a response, or 0
     155             :    */
     156             :   unsigned int http_status;
     157             : 
     158             :   /**
     159             :    * Do we lack an @e econtract?
     160             :    */
     161             :   bool no_econtract;
     162             : 
     163             :   /**
     164             :    * Set to true if the purse_fee was not given in the REST request.
     165             :    */
     166             :   bool no_purse_fee;
     167             : 
     168             : };
     169             : 
     170             : /**
     171             :  * Kept in a DLL.
     172             :  */
     173             : static struct ReservePurseContext *rpc_head;
     174             : 
     175             : /**
     176             :  * Kept in a DLL.
     177             :  */
     178             : static struct ReservePurseContext *rpc_tail;
     179             : 
     180             : 
     181             : void
     182          21 : TEH_reserves_purse_cleanup ()
     183             : {
     184             :   struct ReservePurseContext *rpc;
     185             : 
     186          21 :   while (NULL != (rpc = rpc_head))
     187             :   {
     188           0 :     GNUNET_CONTAINER_DLL_remove (rpc_head,
     189             :                                  rpc_tail,
     190             :                                  rpc);
     191           0 :     MHD_resume_connection (rpc->rc->connection);
     192             :   }
     193          21 : }
     194             : 
     195             : 
     196             : /**
     197             :  * Function called to iterate over KYC-relevant
     198             :  * transaction amounts for a particular time range.
     199             :  * Called within a database transaction, so must
     200             :  * not start a new one.
     201             :  *
     202             :  * @param cls a `struct ReservePurseContext`
     203             :  * @param limit maximum time-range for which events
     204             :  *        should be fetched (timestamp in the past)
     205             :  * @param cb function to call on each event found,
     206             :  *        events must be returned in reverse chronological
     207             :  *        order
     208             :  * @param cb_cls closure for @a cb
     209             :  * @return transaction status
     210             :  */
     211             : static enum GNUNET_DB_QueryStatus
     212           2 : amount_iterator (void *cls,
     213             :                  struct GNUNET_TIME_Absolute limit,
     214             :                  TALER_EXCHANGEDB_KycAmountCallback cb,
     215             :                  void *cb_cls)
     216             : {
     217           2 :   struct ReservePurseContext *rpc = cls;
     218             :   enum GNUNET_DB_QueryStatus qs;
     219             :   enum GNUNET_GenericReturnValue ret;
     220             : 
     221           2 :   ret = cb (cb_cls,
     222           2 :             &rpc->pd.target_amount,
     223             :             GNUNET_TIME_absolute_get ());
     224           2 :   GNUNET_break (GNUNET_SYSERR != ret);
     225           2 :   if (GNUNET_OK != ret)
     226           0 :     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     227           2 :   qs = TEH_plugin->select_merge_amounts_for_kyc_check (
     228           2 :     TEH_plugin->cls,
     229           2 :     &rpc->h_payto,
     230             :     limit,
     231             :     cb,
     232             :     cb_cls);
     233           2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     234             :               "Got %d additional transactions for this merge and limit %llu\n",
     235             :               qs,
     236             :               (unsigned long long) limit.abs_value_us);
     237           2 :   GNUNET_break (qs >= 0);
     238           2 :   return qs;
     239             : }
     240             : 
     241             : 
     242             : /**
     243             :  * Function called with the result of a legitimization
     244             :  * check.
     245             :  *
     246             :  * @param cls closure
     247             :  * @param lcr legitimization check result
     248             :  */
     249             : static void
     250          14 : reserve_purse_legi_cb (
     251             :   void *cls,
     252             :   const struct TEH_LegitimizationCheckResult *lcr)
     253             : {
     254          14 :   struct ReservePurseContext *rpc = cls;
     255             : 
     256          14 :   rpc->lch = NULL;
     257          14 :   rpc->http_status = lcr->http_status;
     258          14 :   rpc->response = lcr->response;
     259          14 :   rpc->kyc = lcr->kyc;
     260          14 :   GNUNET_CONTAINER_DLL_remove (rpc_head,
     261             :                                rpc_tail,
     262             :                                rpc);
     263          14 :   MHD_resume_connection (rpc->rc->connection);
     264          14 :   TALER_MHD_daemon_trigger ();
     265          14 : }
     266             : 
     267             : 
     268             : /**
     269             :  * Execute database transaction for /reserves/$PID/purse.  Runs the transaction
     270             :  * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
     271             :  * a MHD response.  IF it returns an hard error, the transaction logic MUST
     272             :  * queue a MHD response and set @a mhd_ret.  IF it returns the soft error
     273             :  * code, the function MAY be called again to retry and MUST not queue a MHD
     274             :  * response.
     275             :  *
     276             :  * @param cls a `struct ReservePurseContext`
     277             :  * @param connection MHD request context
     278             :  * @param[out] mhd_ret set to MHD status on error
     279             :  * @return transaction status
     280             :  */
     281             : static enum GNUNET_DB_QueryStatus
     282          13 : purse_transaction (void *cls,
     283             :                    struct MHD_Connection *connection,
     284             :                    MHD_RESULT *mhd_ret)
     285             : {
     286          13 :   struct ReservePurseContext *rpc = cls;
     287             :   enum GNUNET_DB_QueryStatus qs;
     288             : 
     289             :   {
     290          13 :     bool in_conflict = true;
     291             : 
     292             :     /* 1) store purse */
     293          13 :     qs = TEH_plugin->insert_purse_request (
     294          13 :       TEH_plugin->cls,
     295          13 :       &rpc->pd.purse_pub,
     296          13 :       &rpc->merge_pub,
     297             :       rpc->pd.purse_expiration,
     298          13 :       &rpc->pd.h_contract_terms,
     299             :       rpc->min_age,
     300             :       rpc->flags,
     301          13 :       &rpc->purse_fee,
     302          13 :       &rpc->pd.target_amount,
     303          13 :       &rpc->purse_sig,
     304             :       &in_conflict);
     305          13 :     if (qs < 0)
     306             :     {
     307           0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     308           0 :         return qs;
     309           0 :       GNUNET_break (0);
     310           0 :       *mhd_ret =
     311           0 :         TALER_MHD_reply_with_error (
     312             :           connection,
     313             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     314             :           TALER_EC_GENERIC_DB_STORE_FAILED,
     315             :           "insert purse request");
     316           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     317             :     }
     318          13 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     319           0 :       return qs;
     320          13 :     if (in_conflict)
     321             :     {
     322             :       struct TALER_PurseMergePublicKeyP merge_pub;
     323             :       struct GNUNET_TIME_Timestamp purse_expiration;
     324             :       struct TALER_PrivateContractHashP h_contract_terms;
     325             :       struct TALER_Amount target_amount;
     326             :       struct TALER_Amount balance;
     327             :       struct TALER_PurseContractSignatureP purse_sig;
     328             :       uint32_t min_age;
     329             : 
     330           0 :       TEH_plugin->rollback (TEH_plugin->cls);
     331           0 :       qs = TEH_plugin->get_purse_request (
     332           0 :         TEH_plugin->cls,
     333           0 :         &rpc->pd.purse_pub,
     334             :         &merge_pub,
     335             :         &purse_expiration,
     336             :         &h_contract_terms,
     337             :         &min_age,
     338             :         &target_amount,
     339             :         &balance,
     340             :         &purse_sig);
     341           0 :       if (qs <= 0)
     342             :       {
     343           0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
     344           0 :         GNUNET_break (0 != qs);
     345           0 :         TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
     346           0 :         *mhd_ret = TALER_MHD_reply_with_error (
     347             :           connection,
     348             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     349             :           TALER_EC_GENERIC_DB_FETCH_FAILED,
     350             :           "select purse request");
     351           0 :         return GNUNET_DB_STATUS_HARD_ERROR;
     352             :       }
     353             :       *mhd_ret
     354           0 :         = TALER_MHD_REPLY_JSON_PACK (
     355             :             connection,
     356             :             MHD_HTTP_CONFLICT,
     357             :             TALER_JSON_pack_ec (
     358             :               TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA),
     359             :             TALER_JSON_pack_amount ("amount",
     360             :                                     &target_amount),
     361             :             GNUNET_JSON_pack_uint64 ("min_age",
     362             :                                      min_age),
     363             :             GNUNET_JSON_pack_timestamp ("purse_expiration",
     364             :                                         purse_expiration),
     365             :             GNUNET_JSON_pack_data_auto ("purse_sig",
     366             :                                         &purse_sig),
     367             :             GNUNET_JSON_pack_data_auto ("h_contract_terms",
     368             :                                         &h_contract_terms),
     369             :             GNUNET_JSON_pack_data_auto ("merge_pub",
     370             :                                         &merge_pub));
     371           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     372             :     }
     373             : 
     374             :   }
     375             : 
     376             :   /* 2) create purse with reserve (and debit reserve for purse creation!) */
     377             :   {
     378          13 :     bool in_conflict = true;
     379          13 :     bool insufficient_funds = true;
     380          13 :     bool no_reserve = true;
     381             : 
     382          13 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     383             :                 "Creating purse with flags %d\n",
     384             :                 rpc->flags);
     385          13 :     qs = TEH_plugin->do_reserve_purse (
     386          13 :       TEH_plugin->cls,
     387          13 :       &rpc->pd.purse_pub,
     388          13 :       &rpc->merge_sig,
     389             :       rpc->merge_timestamp,
     390          13 :       &rpc->reserve_sig,
     391             :       (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
     392          13 :        == rpc->flags)
     393             :       ? NULL
     394             :       : &rpc->purse_fee,
     395          13 :       &rpc->account_pub.reserve_pub,
     396             :       &in_conflict,
     397             :       &no_reserve,
     398             :       &insufficient_funds);
     399          13 :     if (qs < 0)
     400             :     {
     401           0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     402           2 :         return qs;
     403           0 :       TALER_LOG_WARNING (
     404             :         "Failed to store purse merge information in database\n");
     405           0 :       *mhd_ret =
     406           0 :         TALER_MHD_reply_with_error (
     407             :           connection,
     408             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     409             :           TALER_EC_GENERIC_DB_STORE_FAILED,
     410             :           "do reserve purse");
     411           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     412             :     }
     413          13 :     if (in_conflict)
     414             :     {
     415             :       /* same purse already merged into a different reserve!? */
     416             :       struct TALER_PurseContractPublicKeyP purse_pub;
     417             :       struct TALER_PurseMergeSignatureP merge_sig;
     418             :       struct GNUNET_TIME_Timestamp merge_timestamp;
     419             :       char *partner_url;
     420             :       struct TALER_ReservePublicKeyP reserve_pub;
     421             :       bool refunded;
     422             : 
     423           0 :       TEH_plugin->rollback (TEH_plugin->cls);
     424           0 :       qs = TEH_plugin->select_purse_merge (
     425           0 :         TEH_plugin->cls,
     426             :         &purse_pub,
     427             :         &merge_sig,
     428             :         &merge_timestamp,
     429             :         &partner_url,
     430             :         &reserve_pub,
     431             :         &refunded);
     432           0 :       if (qs <= 0)
     433             :       {
     434           0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
     435           0 :         GNUNET_break (0 != qs);
     436           0 :         TALER_LOG_WARNING (
     437             :           "Failed to fetch purse merge information from database\n");
     438           0 :         *mhd_ret = TALER_MHD_reply_with_error (
     439             :           connection,
     440             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     441             :           TALER_EC_GENERIC_DB_FETCH_FAILED,
     442             :           "select purse merge");
     443           0 :         return GNUNET_DB_STATUS_HARD_ERROR;
     444             :       }
     445           0 :       if (refunded)
     446             :       {
     447             :         /* This is a bit of a strange case ... */
     448           0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     449             :                     "Purse was already refunded\n");
     450           0 :         *mhd_ret = TALER_MHD_reply_with_error (
     451             :           connection,
     452             :           MHD_HTTP_GONE,
     453             :           TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
     454             :           NULL);
     455           0 :         GNUNET_free (partner_url);
     456           0 :         return GNUNET_DB_STATUS_HARD_ERROR;
     457             :       }
     458             :       *mhd_ret
     459           0 :         = TALER_MHD_REPLY_JSON_PACK (
     460             :             connection,
     461             :             MHD_HTTP_CONFLICT,
     462             :             TALER_JSON_pack_ec (
     463             :               TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA),
     464             :             GNUNET_JSON_pack_string ("partner_url",
     465             :                                      NULL == partner_url
     466             :                                      ? TEH_base_url
     467             :                                      : partner_url),
     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_data_auto ("reserve_pub",
     473             :                                         &reserve_pub));
     474           0 :       GNUNET_free (partner_url);
     475           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     476             :     }
     477          13 :     if ( (no_reserve) &&
     478             :          ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
     479           0 :             == rpc->flags) ||
     480           0 :            (! TALER_amount_is_zero (&rpc->purse_fee)) ) )
     481             :     {
     482             :       *mhd_ret
     483           0 :         = TALER_MHD_REPLY_JSON_PACK (
     484             :             connection,
     485             :             MHD_HTTP_NOT_FOUND,
     486             :             TALER_JSON_pack_ec (
     487             :               TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN));
     488           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     489             :     }
     490          13 :     if (insufficient_funds)
     491             :     {
     492             :       *mhd_ret
     493           2 :         = TALER_MHD_REPLY_JSON_PACK (
     494             :             connection,
     495             :             MHD_HTTP_CONFLICT,
     496             :             TALER_JSON_pack_ec (
     497             :               TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS));
     498           2 :       return GNUNET_DB_STATUS_HARD_ERROR;
     499             :     }
     500             :   }
     501             :   /* 3) if present, persist contract */
     502          11 :   if (! rpc->no_econtract)
     503             :   {
     504          11 :     bool in_conflict = true;
     505             : 
     506          11 :     qs = TEH_plugin->insert_contract (TEH_plugin->cls,
     507          11 :                                       &rpc->pd.purse_pub,
     508          11 :                                       &rpc->econtract,
     509             :                                       &in_conflict);
     510          11 :     if (qs < 0)
     511             :     {
     512           0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     513           0 :         return qs;
     514           0 :       TALER_LOG_WARNING ("Failed to store purse information in database\n");
     515           0 :       *mhd_ret = TALER_MHD_reply_with_error (
     516             :         connection,
     517             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     518             :         TALER_EC_GENERIC_DB_STORE_FAILED,
     519             :         "purse purse contract");
     520           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     521             :     }
     522          11 :     if (in_conflict)
     523             :     {
     524             :       struct TALER_EncryptedContract econtract;
     525             :       struct GNUNET_HashCode h_econtract;
     526             : 
     527           0 :       qs = TEH_plugin->select_contract_by_purse (
     528           0 :         TEH_plugin->cls,
     529           0 :         &rpc->pd.purse_pub,
     530             :         &econtract);
     531           0 :       if (qs <= 0)
     532             :       {
     533           0 :         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     534           0 :           return qs;
     535           0 :         GNUNET_break (0 != qs);
     536           0 :         TALER_LOG_WARNING (
     537             :           "Failed to store fetch contract information from database\n");
     538           0 :         *mhd_ret = TALER_MHD_reply_with_error (
     539             :           connection,
     540             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     541             :           TALER_EC_GENERIC_DB_FETCH_FAILED,
     542             :           "select contract");
     543           0 :         return GNUNET_DB_STATUS_HARD_ERROR;
     544             :       }
     545           0 :       GNUNET_CRYPTO_hash (econtract.econtract,
     546             :                           econtract.econtract_size,
     547             :                           &h_econtract);
     548             :       *mhd_ret
     549           0 :         = TALER_MHD_REPLY_JSON_PACK (
     550             :             connection,
     551             :             MHD_HTTP_CONFLICT,
     552             :             TALER_JSON_pack_ec (
     553             :               TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
     554             :             GNUNET_JSON_pack_data_auto ("h_econtract",
     555             :                                         &h_econtract),
     556             :             GNUNET_JSON_pack_data_auto ("econtract_sig",
     557             :                                         &econtract.econtract_sig),
     558             :             GNUNET_JSON_pack_data_auto ("contract_pub",
     559             :                                         &econtract.contract_pub));
     560           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     561             :     }
     562             :   }
     563          11 :   return qs;
     564             : }
     565             : 
     566             : 
     567             : /**
     568             :  * Function to clean up our rh_ctx in @a rc
     569             :  *
     570             :  * @param[in,out] rc context to clean up
     571             :  */
     572             : static void
     573          14 : rpc_cleaner (struct TEH_RequestContext *rc)
     574             : {
     575          14 :   struct ReservePurseContext *rpc = rc->rh_ctx;
     576             : 
     577          14 :   if (NULL != rpc->lch)
     578             :   {
     579           0 :     TEH_legitimization_check_cancel (rpc->lch);
     580           0 :     rpc->lch = NULL;
     581             :   }
     582          14 :   GNUNET_free (rpc->econtract.econtract);
     583          14 :   GNUNET_free (rpc->payto_uri.normalized_payto);
     584          14 :   GNUNET_free (rpc);
     585          14 : }
     586             : 
     587             : 
     588             : MHD_RESULT
     589          28 : TEH_handler_reserves_purse (
     590             :   struct TEH_RequestContext *rc,
     591             :   const struct TALER_ReservePublicKeyP *reserve_pub,
     592             :   const json_t *root)
     593             : {
     594          28 :   struct ReservePurseContext *rpc = rc->rh_ctx;
     595          28 :   struct MHD_Connection *connection = rc->connection;
     596             : 
     597          28 :   if (NULL == rpc)
     598             :   {
     599          14 :     rpc = GNUNET_new (struct ReservePurseContext);
     600          14 :     rc->rh_ctx = rpc;
     601          14 :     rc->rh_cleaner = &rpc_cleaner;
     602          14 :     rpc->rc = rc;
     603          14 :     rpc->account_pub.reserve_pub = *reserve_pub;
     604             :     rpc->exchange_timestamp
     605          14 :       = GNUNET_TIME_timestamp_get ();
     606          14 :     rpc->no_purse_fee = true;
     607             : 
     608             : 
     609             :     {
     610             :       struct GNUNET_JSON_Specification spec[] = {
     611          14 :         TALER_JSON_spec_amount ("purse_value",
     612             :                                 TEH_currency,
     613             :                                 &rpc->pd.target_amount),
     614          14 :         GNUNET_JSON_spec_uint32 ("min_age",
     615             :                                  &rpc->min_age),
     616          14 :         GNUNET_JSON_spec_mark_optional (
     617             :           TALER_JSON_spec_amount ("purse_fee",
     618             :                                   TEH_currency,
     619             :                                   &rpc->purse_fee),
     620             :           &rpc->no_purse_fee),
     621          14 :         GNUNET_JSON_spec_mark_optional (
     622             :           TALER_JSON_spec_econtract ("econtract",
     623             :                                      &rpc->econtract),
     624             :           &rpc->no_econtract),
     625          14 :         GNUNET_JSON_spec_fixed_auto ("merge_pub",
     626             :                                      &rpc->merge_pub),
     627          14 :         GNUNET_JSON_spec_fixed_auto ("merge_sig",
     628             :                                      &rpc->merge_sig),
     629          14 :         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
     630             :                                      &rpc->reserve_sig),
     631          14 :         GNUNET_JSON_spec_fixed_auto ("purse_pub",
     632             :                                      &rpc->pd.purse_pub),
     633          14 :         GNUNET_JSON_spec_fixed_auto ("purse_sig",
     634             :                                      &rpc->purse_sig),
     635          14 :         GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
     636             :                                      &rpc->pd.h_contract_terms),
     637          14 :         GNUNET_JSON_spec_timestamp ("merge_timestamp",
     638             :                                     &rpc->merge_timestamp),
     639          14 :         GNUNET_JSON_spec_timestamp ("purse_expiration",
     640             :                                     &rpc->pd.purse_expiration),
     641          14 :         GNUNET_JSON_spec_end ()
     642             :       };
     643             : 
     644             :       {
     645             :         enum GNUNET_GenericReturnValue res;
     646             : 
     647          14 :         res = TALER_MHD_parse_json_data (connection,
     648             :                                          root,
     649             :                                          spec);
     650          14 :         if (GNUNET_SYSERR == res)
     651             :         {
     652           0 :           GNUNET_break (0);
     653           0 :           return MHD_NO; /* hard failure */
     654             :         }
     655          14 :         if (GNUNET_NO == res)
     656             :         {
     657           0 :           GNUNET_break_op (0);
     658           0 :           return MHD_YES; /* failure */
     659             :         }
     660             :       }
     661             :     }
     662             : 
     663             :     rpc->payto_uri
     664          14 :       = TALER_reserve_make_payto (TEH_base_url,
     665          14 :                                   &rpc->account_pub.reserve_pub);
     666          14 :     TALER_normalized_payto_hash (rpc->payto_uri,
     667             :                                  &rpc->h_payto);
     668          14 :     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
     669             : 
     670             : 
     671          14 :     if (GNUNET_OK !=
     672          14 :         TALER_wallet_purse_merge_verify (rpc->payto_uri,
     673             :                                          rpc->merge_timestamp,
     674          14 :                                          &rpc->pd.purse_pub,
     675          14 :                                          &rpc->merge_pub,
     676          14 :                                          &rpc->merge_sig))
     677             :     {
     678             :       MHD_RESULT ret;
     679             : 
     680           0 :       GNUNET_break_op (0);
     681           0 :       ret = TALER_MHD_reply_with_error (
     682             :         connection,
     683             :         MHD_HTTP_FORBIDDEN,
     684             :         TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID,
     685           0 :         rpc->payto_uri.normalized_payto);
     686           0 :       return ret;
     687             :     }
     688          14 :     GNUNET_assert (GNUNET_OK ==
     689             :                    TALER_amount_set_zero (TEH_currency,
     690             :                                           &rpc->deposit_total));
     691          14 :     if (GNUNET_TIME_timestamp_cmp (rpc->pd.purse_expiration,
     692             :                                    <,
     693             :                                    rpc->exchange_timestamp))
     694             :     {
     695           0 :       GNUNET_break_op (0);
     696           0 :       return TALER_MHD_reply_with_error (
     697             :         connection,
     698             :         MHD_HTTP_BAD_REQUEST,
     699             :         TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW,
     700             :         NULL);
     701             :     }
     702          14 :     if (GNUNET_TIME_absolute_is_never (
     703             :           rpc->pd.purse_expiration.abs_time))
     704             :     {
     705           0 :       GNUNET_break_op (0);
     706           0 :       return TALER_MHD_reply_with_error (
     707             :         connection,
     708             :         MHD_HTTP_BAD_REQUEST,
     709             :         TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER,
     710             :         NULL);
     711             :     }
     712             : 
     713             :     {
     714             :       struct TEH_KeyStateHandle *keys;
     715             :       const struct TEH_GlobalFee *gf;
     716             : 
     717          14 :       keys = TEH_keys_get_state ();
     718          14 :       if (NULL == keys)
     719             :       {
     720           0 :         GNUNET_break (0);
     721           0 :         return TALER_MHD_reply_with_error (
     722             :           connection,
     723             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     724             :           TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
     725             :           NULL);
     726             :       }
     727          14 :       gf = TEH_keys_global_fee_by_time (keys,
     728             :                                         rpc->exchange_timestamp);
     729          14 :       if (NULL == gf)
     730             :       {
     731           0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     732             :                     "Cannot create purse: global fees not configured!\n");
     733           0 :         return TALER_MHD_reply_with_error (
     734             :           connection,
     735             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     736             :           TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
     737             :           NULL);
     738             :       }
     739          14 :       if (rpc->no_purse_fee)
     740             :       {
     741           6 :         rpc->flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
     742           6 :         GNUNET_assert (GNUNET_OK ==
     743             :                        TALER_amount_set_zero (TEH_currency,
     744             :                                               &rpc->purse_fee));
     745             :       }
     746             :       else
     747             :       {
     748           8 :         rpc->flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
     749           8 :         if (-1 ==
     750           8 :             TALER_amount_cmp (&rpc->purse_fee,
     751             :                               &gf->fees.purse))
     752             :         {
     753             :           /* rpc->purse_fee is below gf.fees.purse! */
     754           0 :           GNUNET_break_op (0);
     755           0 :           return TALER_MHD_reply_with_error (
     756             :             connection,
     757             :             MHD_HTTP_BAD_REQUEST,
     758             :             TALER_EC_EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW,
     759             :             TALER_amount2s (&gf->fees.purse));
     760             :         }
     761             :       }
     762             :     }
     763             : 
     764          14 :     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
     765          14 :     if (GNUNET_OK !=
     766          14 :         TALER_wallet_purse_create_verify (
     767             :           rpc->pd.purse_expiration,
     768          14 :           &rpc->pd.h_contract_terms,
     769          14 :           &rpc->merge_pub,
     770             :           rpc->min_age,
     771          14 :           &rpc->pd.target_amount,
     772          14 :           &rpc->pd.purse_pub,
     773          14 :           &rpc->purse_sig))
     774             :     {
     775           0 :       GNUNET_break_op (0);
     776           0 :       return TALER_MHD_reply_with_error (
     777             :         connection,
     778             :         MHD_HTTP_FORBIDDEN,
     779             :         TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
     780             :         NULL);
     781             :     }
     782          14 :     if (GNUNET_OK !=
     783          14 :         TALER_wallet_account_merge_verify (
     784             :           rpc->merge_timestamp,
     785          14 :           &rpc->pd.purse_pub,
     786             :           rpc->pd.purse_expiration,
     787          14 :           &rpc->pd.h_contract_terms,
     788          14 :           &rpc->pd.target_amount,
     789          14 :           &rpc->purse_fee,
     790             :           rpc->min_age,
     791             :           rpc->flags,
     792          14 :           &rpc->account_pub.reserve_pub,
     793          14 :           &rpc->reserve_sig))
     794             :     {
     795           0 :       GNUNET_break_op (0);
     796           0 :       return TALER_MHD_reply_with_error (
     797             :         connection,
     798             :         MHD_HTTP_FORBIDDEN,
     799             :         TALER_EC_EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID,
     800             :         NULL);
     801             :     }
     802          28 :     if ( (! rpc->no_econtract) &&
     803             :          (GNUNET_OK !=
     804          14 :           TALER_wallet_econtract_upload_verify (
     805          14 :             rpc->econtract.econtract,
     806             :             rpc->econtract.econtract_size,
     807          14 :             &rpc->econtract.contract_pub,
     808          14 :             &rpc->pd.purse_pub,
     809          14 :             &rpc->econtract.econtract_sig))
     810             :          )
     811             :     {
     812           0 :       TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n");
     813           0 :       return TALER_MHD_reply_with_error (connection,
     814             :                                          MHD_HTTP_FORBIDDEN,
     815             :                                          TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
     816             :                                          NULL);
     817             :     }
     818             :     {
     819             :       struct TALER_FullPayto fake_full_payto;
     820             : 
     821          14 :       GNUNET_asprintf (&fake_full_payto.full_payto,
     822             :                        "%s?receiver-name=wallet",
     823             :                        rpc->payto_uri.normalized_payto);
     824          28 :       rpc->lch = TEH_legitimization_check (
     825          14 :         &rpc->rc->async_scope_id,
     826             :         TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
     827             :         fake_full_payto,
     828          14 :         &rpc->h_payto,
     829          14 :         &rpc->account_pub,
     830             :         &amount_iterator,
     831             :         rpc,
     832             :         &reserve_purse_legi_cb,
     833             :         rpc);
     834          14 :       GNUNET_free (fake_full_payto.full_payto);
     835             :     }
     836          14 :     GNUNET_assert (NULL != rpc->lch);
     837          14 :     MHD_suspend_connection (rc->connection);
     838          14 :     GNUNET_CONTAINER_DLL_insert (rpc_head,
     839             :                                  rpc_tail,
     840             :                                  rpc);
     841          14 :     return MHD_YES;
     842             :   }
     843          14 :   if (NULL != rpc->response)
     844           0 :     return MHD_queue_response (connection,
     845             :                                rpc->http_status,
     846             :                                rpc->response);
     847          14 :   if (! rpc->kyc.ok)
     848             :   {
     849           1 :     if (0 == rpc->kyc.requirement_row)
     850             :     {
     851           0 :       GNUNET_break (0);
     852           0 :       return TALER_MHD_reply_with_error (
     853             :         connection,
     854             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     855             :         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     856             :         "requirement row not set");
     857             :     }
     858           1 :     return TEH_RESPONSE_reply_kyc_required (
     859             :       connection,
     860           1 :       &rpc->h_payto,
     861           1 :       &rpc->kyc,
     862             :       false);
     863             :   }
     864             : 
     865             :   {
     866             :     MHD_RESULT mhd_ret;
     867             : 
     868          13 :     if (GNUNET_OK !=
     869          13 :         TEH_DB_run_transaction (connection,
     870             :                                 "execute reserve purse",
     871             :                                 TEH_MT_REQUEST_RESERVE_PURSE,
     872             :                                 &mhd_ret,
     873             :                                 &purse_transaction,
     874             :                                 rpc))
     875             :     {
     876           2 :       return mhd_ret;
     877             :     }
     878             :   }
     879             : 
     880             :   /* generate regular response */
     881             :   {
     882             :     MHD_RESULT res;
     883             : 
     884          11 :     res = TEH_RESPONSE_reply_purse_created (
     885             :       connection,
     886             :       rpc->exchange_timestamp,
     887          11 :       &rpc->deposit_total,
     888          11 :       &rpc->pd);
     889          11 :     return res;
     890             :   }
     891             : }
     892             : 
     893             : 
     894             : /* end of taler-exchange-httpd_reserves_purse.c */

Generated by: LCOV version 1.16