LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_reserves_close.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 112 157 71.3 %
Date: 2025-06-22 12:09:43 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2022 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_close.c
      18             :  * @brief Handle /reserves/$RESERVE_PUB/close requests
      19             :  * @author Florian Dold
      20             :  * @author Benedikt Mueller
      21             :  * @author Christian Grothoff
      22             :  */
      23             : #include "taler/platform.h"
      24             : #include <gnunet/gnunet_util_lib.h>
      25             : #include <jansson.h>
      26             : #include "taler/taler_kyclogic_lib.h"
      27             : #include "taler/taler_mhd_lib.h"
      28             : #include "taler/taler_json_lib.h"
      29             : #include "taler/taler_dbevents.h"
      30             : #include "taler-exchange-httpd_common_kyc.h"
      31             : #include "taler-exchange-httpd_keys.h"
      32             : #include "taler-exchange-httpd_reserves_close.h"
      33             : #include "taler-exchange-httpd_responses.h"
      34             : 
      35             : 
      36             : /**
      37             :  * How far do we allow a client's time to be off when
      38             :  * checking the request timestamp?
      39             :  */
      40             : #define TIMESTAMP_TOLERANCE \
      41             :         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
      42             : 
      43             : 
      44             : /**
      45             :  * Closure for #reserve_close_transaction.
      46             :  */
      47             : struct ReserveCloseContext
      48             : {
      49             : 
      50             :   /**
      51             :    * Kept in a DLL.
      52             :    */
      53             :   struct ReserveCloseContext *next;
      54             : 
      55             :   /**
      56             :    * Kept in a DLL.
      57             :    */
      58             :   struct ReserveCloseContext *prev;
      59             : 
      60             :   /**
      61             :    * Our request context.
      62             :    */
      63             :   struct TEH_RequestContext *rc;
      64             : 
      65             :   /**
      66             :    * Handle for legitimization check.
      67             :    */
      68             :   struct TEH_LegitimizationCheckHandle *lch;
      69             : 
      70             :   /**
      71             :    * Where to wire the funds, may be NULL.
      72             :    */
      73             :   struct TALER_FullPayto payto_uri;
      74             : 
      75             :   /**
      76             :    * Response to return. Note that the response must
      77             :    * be queued or destroyed by the callee.  NULL
      78             :    * if the legitimization check was successful and the handler should return
      79             :    * a handler-specific result.
      80             :    */
      81             :   struct MHD_Response *response;
      82             : 
      83             :   /**
      84             :    * Public key of the reserve the inquiry is about.
      85             :    */
      86             :   struct TALER_ReservePublicKeyP reserve_pub;
      87             : 
      88             :   /**
      89             :    * Timestamp of the request.
      90             :    */
      91             :   struct GNUNET_TIME_Timestamp timestamp;
      92             : 
      93             :   /**
      94             :    * Client signature approving the request.
      95             :    */
      96             :   struct TALER_ReserveSignatureP reserve_sig;
      97             : 
      98             :   /**
      99             :    * Amount that will be wired (after closing fees).
     100             :    */
     101             :   struct TALER_Amount wire_amount;
     102             : 
     103             :   /**
     104             :    * Current balance of the reserve.
     105             :    */
     106             :   struct TALER_Amount balance;
     107             : 
     108             :   /**
     109             :    * Hash of the @e payto_uri, if given (otherwise zero).
     110             :    */
     111             :   struct TALER_FullPaytoHashP h_payto;
     112             : 
     113             :   /**
     114             :    * KYC status for the request.
     115             :    */
     116             :   struct TALER_EXCHANGEDB_KycStatus kyc;
     117             : 
     118             :   /**
     119             :    * Hash of the payto-URI that was used for the KYC decision.
     120             :    */
     121             :   struct TALER_NormalizedPaytoHashP kyc_payto;
     122             : 
     123             :   /**
     124             :    * Query status from the amount_it() helper function.
     125             :    */
     126             :   enum GNUNET_DB_QueryStatus qs;
     127             : 
     128             :   /**
     129             :    * HTTP status code for @a response, or 0
     130             :    */
     131             :   unsigned int http_status;
     132             : 
     133             :   /**
     134             :    * Set to true if the request was suspended.
     135             :    */
     136             :   bool suspended;
     137             : 
     138             :   /**
     139             :    * Set to true if the request was suspended.
     140             :    */
     141             :   bool resumed;
     142             : };
     143             : 
     144             : 
     145             : /**
     146             :  * Kept in a DLL.
     147             :  */
     148             : static struct ReserveCloseContext *rcc_head;
     149             : 
     150             : /**
     151             :  * Kept in a DLL.
     152             :  */
     153             : static struct ReserveCloseContext *rcc_tail;
     154             : 
     155             : 
     156             : void
     157          21 : TEH_reserves_close_cleanup ()
     158             : {
     159             :   struct ReserveCloseContext *rcc;
     160             : 
     161          21 :   while (NULL != (rcc = rcc_head))
     162             :   {
     163           0 :     GNUNET_CONTAINER_DLL_remove (rcc_head,
     164             :                                  rcc_tail,
     165             :                                  rcc);
     166           0 :     MHD_resume_connection (rcc->rc->connection);
     167             :   }
     168          21 : }
     169             : 
     170             : 
     171             : /**
     172             :  * Send reserve close to client.
     173             :  *
     174             :  * @param rhc reserve close to return
     175             :  * @return MHD result code
     176             :  */
     177             : static MHD_RESULT
     178           2 : reply_reserve_close_success (
     179             :   const struct ReserveCloseContext *rhc)
     180             : {
     181           2 :   struct MHD_Connection *connection = rhc->rc->connection;
     182           2 :   return TALER_MHD_REPLY_JSON_PACK (
     183             :     connection,
     184             :     MHD_HTTP_OK,
     185             :     TALER_JSON_pack_amount ("wire_amount",
     186             :                             &rhc->wire_amount));
     187             : }
     188             : 
     189             : 
     190             : /**
     191             :  * Function called with the result of a legitimization
     192             :  * check.
     193             :  *
     194             :  * @param cls closure
     195             :  * @param lcr legitimization check result
     196             :  */
     197             : static void
     198           4 : reserve_close_legi_cb (
     199             :   void *cls,
     200             :   const struct TEH_LegitimizationCheckResult *lcr)
     201             : {
     202           4 :   struct ReserveCloseContext *rcc = cls;
     203             : 
     204           4 :   rcc->lch = NULL;
     205           4 :   rcc->http_status = lcr->http_status;
     206           4 :   rcc->response = lcr->response;
     207           4 :   rcc->kyc = lcr->kyc;
     208           4 :   GNUNET_CONTAINER_DLL_remove (rcc_head,
     209             :                                rcc_tail,
     210             :                                rcc);
     211           4 :   MHD_resume_connection (rcc->rc->connection);
     212           4 :   rcc->resumed = true;
     213           4 :   rcc->suspended = false;
     214           4 :   TALER_MHD_daemon_trigger ();
     215           4 : }
     216             : 
     217             : 
     218             : /**
     219             :  * Function called to iterate over KYC-relevant
     220             :  * transaction amounts for a particular time range.
     221             :  * Called within a database transaction, so must
     222             :  * not start a new one.
     223             :  *
     224             :  * @param cls closure, identifies the event type and
     225             :  *        account to iterate over events for
     226             :  * @param limit maximum time-range for which events
     227             :  *        should be fetched (timestamp in the past)
     228             :  * @param cb function to call on each event found,
     229             :  *        events must be returned in reverse chronological
     230             :  *        order
     231             :  * @param cb_cls closure for @a cb
     232             :  * @return transaction status
     233             :  */
     234             : static enum GNUNET_DB_QueryStatus
     235           4 : amount_it (void *cls,
     236             :            struct GNUNET_TIME_Absolute limit,
     237             :            TALER_EXCHANGEDB_KycAmountCallback cb,
     238             :            void *cb_cls)
     239             : {
     240           4 :   struct ReserveCloseContext *rcc = cls;
     241             :   enum GNUNET_GenericReturnValue ret;
     242             : 
     243           4 :   ret = cb (cb_cls,
     244           4 :             &rcc->balance,
     245             :             GNUNET_TIME_absolute_get ());
     246           4 :   GNUNET_break (GNUNET_SYSERR != ret);
     247           4 :   if (GNUNET_OK != ret)
     248           0 :     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     249             :   rcc->qs
     250           8 :     = TEH_plugin->iterate_reserve_close_info (
     251           4 :         TEH_plugin->cls,
     252           4 :         &rcc->kyc_payto,
     253             :         limit,
     254             :         cb,
     255             :         cb_cls);
     256           4 :   return rcc->qs;
     257             : }
     258             : 
     259             : 
     260             : /**
     261             :  * Function implementing /reserves/$RID/close transaction.  Given the public
     262             :  * key of a reserve, return the associated transaction close.  Runs the
     263             :  * transaction logic; IF it returns a non-error code, the transaction logic
     264             :  * MUST NOT queue a MHD response.  IF it returns an hard error, the
     265             :  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF it
     266             :  * returns the soft error code, the function MAY be called again to retry and
     267             :  * MUST not queue a MHD response.
     268             :  *
     269             :  * @param cls a `struct ReserveCloseContext *`
     270             :  * @param connection MHD request which triggered the transaction
     271             :  * @param[out] mhd_ret set to MHD response status for @a connection,
     272             :  *             if transaction failed (!); unused
     273             :  * @return transaction status
     274             :  */
     275             : static enum GNUNET_DB_QueryStatus
     276           6 : reserve_close_transaction (
     277             :   void *cls,
     278             :   struct MHD_Connection *connection,
     279             :   MHD_RESULT *mhd_ret)
     280             : {
     281           6 :   struct ReserveCloseContext *rcc = cls;
     282             :   enum GNUNET_DB_QueryStatus qs;
     283           6 :   struct TALER_FullPayto payto_uri = {
     284             :     .full_payto = NULL
     285             :   };
     286             :   const struct TALER_WireFeeSet *wf;
     287             : 
     288           6 :   qs = TEH_plugin->select_reserve_close_info (
     289           6 :     TEH_plugin->cls,
     290           6 :     &rcc->reserve_pub,
     291             :     &rcc->balance,
     292             :     &payto_uri);
     293           6 :   switch (qs)
     294             :   {
     295           0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     296           0 :     GNUNET_break (0);
     297             :     *mhd_ret
     298           0 :       = TALER_MHD_reply_with_error (
     299             :           connection,
     300             :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     301             :           TALER_EC_GENERIC_DB_FETCH_FAILED,
     302             :           "select_reserve_close_info");
     303           0 :     return qs;
     304           0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     305           0 :     return qs;
     306           0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     307             :     *mhd_ret
     308           0 :       = TALER_MHD_reply_with_error (
     309             :           connection,
     310             :           MHD_HTTP_NOT_FOUND,
     311             :           TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
     312             :           NULL);
     313           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     314           6 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     315           6 :     break;
     316             :   }
     317             : 
     318           6 :   if ( (NULL == rcc->payto_uri.full_payto) &&
     319           0 :        (NULL == payto_uri.full_payto) )
     320             :   {
     321             :     *mhd_ret
     322           0 :       = TALER_MHD_reply_with_error (
     323             :           connection,
     324             :           MHD_HTTP_CONFLICT,
     325             :           TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT,
     326             :           NULL);
     327           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     328             :   }
     329             : 
     330           6 :   if ( (! rcc->resumed) &&
     331           4 :        (NULL != rcc->payto_uri.full_payto) &&
     332           8 :        ( (NULL == payto_uri.full_payto) ||
     333           4 :          (0 != TALER_full_payto_cmp (payto_uri,
     334             :                                      rcc->payto_uri)) ) )
     335             :   {
     336             :     /* KYC check may be needed: we're not returning
     337             :        the money to the account that funded the reserve
     338             :        in the first place. */
     339             : 
     340           4 :     TALER_full_payto_normalize_and_hash (rcc->payto_uri,
     341             :                                          &rcc->kyc_payto);
     342           8 :     rcc->lch = TEH_legitimization_check (
     343           4 :       &rcc->rc->async_scope_id,
     344             :       TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
     345             :       rcc->payto_uri,
     346           4 :       &rcc->kyc_payto,
     347             :       NULL, /* no account_pub: this is about the origin/destination account */
     348             :       &amount_it,
     349             :       rcc,
     350             :       &reserve_close_legi_cb,
     351             :       rcc);
     352           4 :     GNUNET_assert (NULL != rcc->lch);
     353           4 :     GNUNET_CONTAINER_DLL_insert (rcc_head,
     354             :                                  rcc_tail,
     355             :                                  rcc);
     356           4 :     MHD_suspend_connection (rcc->rc->connection);
     357           4 :     rcc->suspended = true;
     358           4 :     GNUNET_free (payto_uri.full_payto);
     359           4 :     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     360             :   }
     361           2 :   rcc->kyc.ok = true;
     362           2 :   if (NULL == rcc->payto_uri.full_payto)
     363           0 :     rcc->payto_uri = payto_uri;
     364             : 
     365             :   {
     366             :     char *method;
     367             : 
     368           2 :     method = TALER_payto_get_method (rcc->payto_uri.full_payto);
     369           2 :     wf = TEH_wire_fees_by_time (rcc->timestamp,
     370             :                                 method);
     371           2 :     if (NULL == wf)
     372             :     {
     373           0 :       GNUNET_break (0);
     374           0 :       *mhd_ret = TALER_MHD_reply_with_error (
     375             :         connection,
     376             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     377             :         TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
     378             :         method);
     379           0 :       GNUNET_free (method);
     380           0 :       GNUNET_free (payto_uri.full_payto);
     381           0 :       return GNUNET_DB_STATUS_HARD_ERROR;
     382             :     }
     383           2 :     GNUNET_free (method);
     384             :   }
     385             : 
     386           2 :   if (0 >
     387           2 :       TALER_amount_subtract (&rcc->wire_amount,
     388           2 :                              &rcc->balance,
     389             :                              &wf->closing))
     390             :   {
     391           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     392             :                 "Client attempted to close reserve with insufficient balance.\n");
     393           0 :     GNUNET_assert (GNUNET_OK ==
     394             :                    TALER_amount_set_zero (TEH_currency,
     395             :                                           &rcc->wire_amount));
     396           0 :     *mhd_ret = reply_reserve_close_success (rcc);
     397           0 :     GNUNET_free (payto_uri.full_payto);
     398           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     399             :   }
     400             : 
     401           2 :   qs = TEH_plugin->insert_close_request (
     402           2 :     TEH_plugin->cls,
     403           2 :     &rcc->reserve_pub,
     404             :     payto_uri,
     405           2 :     &rcc->reserve_sig,
     406             :     rcc->timestamp,
     407           2 :     &rcc->balance,
     408             :     &wf->closing);
     409           2 :   GNUNET_free (payto_uri.full_payto);
     410           2 :   rcc->payto_uri.full_payto = NULL;
     411           2 :   if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     412             :   {
     413           0 :     GNUNET_break (0);
     414             :     *mhd_ret
     415           0 :       = TALER_MHD_reply_with_error (connection,
     416             :                                     MHD_HTTP_INTERNAL_SERVER_ERROR,
     417             :                                     TALER_EC_GENERIC_DB_FETCH_FAILED,
     418             :                                     "insert_close_request");
     419           0 :     return qs;
     420             :   }
     421           2 :   if (qs <= 0)
     422             :   {
     423           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     424           0 :     return qs;
     425             :   }
     426           2 :   return qs;
     427             : }
     428             : 
     429             : 
     430             : /**
     431             :  * Cleanup routine. Function called
     432             :  * upon completion of the request that should
     433             :  * clean up @a rh_ctx. Can be NULL.
     434             :  *
     435             :  * @param rc request to clean up context for
     436             :  */
     437             : static void
     438           4 : reserve_close_cleanup (struct TEH_RequestContext *rc)
     439             : {
     440           4 :   struct ReserveCloseContext *rcc = rc->rh_ctx;
     441             : 
     442           4 :   if (NULL != rcc->lch)
     443             :   {
     444           0 :     TEH_legitimization_check_cancel (rcc->lch);
     445           0 :     rcc->lch = NULL;
     446             :   }
     447           4 :   GNUNET_free (rcc);
     448           4 : }
     449             : 
     450             : 
     451             : MHD_RESULT
     452           8 : TEH_handler_reserves_close (
     453             :   struct TEH_RequestContext *rc,
     454             :   const struct TALER_ReservePublicKeyP *reserve_pub,
     455             :   const json_t *root)
     456             : {
     457           8 :   struct ReserveCloseContext *rcc = rc->rh_ctx;
     458             :   MHD_RESULT mhd_ret;
     459             : 
     460           8 :   if (NULL == rcc)
     461             :   {
     462           4 :     rcc = GNUNET_new (struct ReserveCloseContext);
     463           4 :     rc->rh_ctx = rcc;
     464           4 :     rc->rh_cleaner = &reserve_close_cleanup;
     465           4 :     rcc->reserve_pub = *reserve_pub;
     466           4 :     rcc->rc = rc;
     467             : 
     468             :     {
     469             :       struct GNUNET_JSON_Specification spec[] = {
     470           4 :         GNUNET_JSON_spec_timestamp ("request_timestamp",
     471             :                                     &rcc->timestamp),
     472           4 :         GNUNET_JSON_spec_mark_optional (
     473             :           TALER_JSON_spec_full_payto_uri ("payto_uri",
     474             :                                           &rcc->payto_uri),
     475             :           NULL),
     476           4 :         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
     477             :                                      &rcc->reserve_sig),
     478           4 :         GNUNET_JSON_spec_end ()
     479             :       };
     480             : 
     481             :       {
     482             :         enum GNUNET_GenericReturnValue res;
     483             : 
     484           4 :         res = TALER_MHD_parse_json_data (rc->connection,
     485             :                                          root,
     486             :                                          spec);
     487           4 :         if (GNUNET_SYSERR == res)
     488             :         {
     489           0 :           GNUNET_break (0);
     490           0 :           return MHD_NO; /* hard failure */
     491             :         }
     492           4 :         if (GNUNET_NO == res)
     493             :         {
     494           0 :           GNUNET_break_op (0);
     495           0 :           return MHD_YES; /* failure */
     496             :         }
     497             :       }
     498             :     }
     499             : 
     500             :     {
     501             :       struct GNUNET_TIME_Timestamp now;
     502             : 
     503           4 :       now = GNUNET_TIME_timestamp_get ();
     504           4 :       if (! GNUNET_TIME_absolute_approx_eq (
     505             :             now.abs_time,
     506             :             rcc->timestamp.abs_time,
     507             :             TIMESTAMP_TOLERANCE))
     508             :       {
     509           0 :         GNUNET_break_op (0);
     510           0 :         return TALER_MHD_reply_with_error (
     511             :           rc->connection,
     512             :           MHD_HTTP_BAD_REQUEST,
     513             :           TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
     514             :           NULL);
     515             :       }
     516             :     }
     517             : 
     518           4 :     if (NULL != rcc->payto_uri.full_payto)
     519           4 :       TALER_full_payto_hash (rcc->payto_uri,
     520             :                              &rcc->h_payto);
     521           4 :     if (GNUNET_OK !=
     522           4 :         TALER_wallet_reserve_close_verify (
     523             :           rcc->timestamp,
     524           4 :           &rcc->h_payto,
     525           4 :           &rcc->reserve_pub,
     526           4 :           &rcc->reserve_sig))
     527             :     {
     528           0 :       GNUNET_break_op (0);
     529           0 :       return TALER_MHD_reply_with_error (
     530             :         rc->connection,
     531             :         MHD_HTTP_FORBIDDEN,
     532             :         TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE,
     533             :         NULL);
     534             :     }
     535             :   }
     536           8 :   if (NULL != rcc->response)
     537           0 :     return MHD_queue_response (rc->connection,
     538             :                                rcc->http_status,
     539             :                                rcc->response);
     540           8 :   if (rcc->resumed &&
     541           4 :       (! rcc->kyc.ok) )
     542             :   {
     543           2 :     if (0 == rcc->kyc.requirement_row)
     544             :     {
     545           0 :       GNUNET_break (0);
     546           0 :       return TALER_MHD_reply_with_error (
     547             :         rc->connection,
     548             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     549             :         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     550             :         "requirement row not set");
     551             :     }
     552           2 :     return TEH_RESPONSE_reply_kyc_required (
     553             :       rc->connection,
     554           2 :       &rcc->kyc_payto,
     555           2 :       &rcc->kyc,
     556             :       false);
     557             :   }
     558             : 
     559           6 :   if (GNUNET_OK !=
     560           6 :       TEH_DB_run_transaction (rc->connection,
     561             :                               "reserve close",
     562             :                               TEH_MT_REQUEST_OTHER,
     563             :                               &mhd_ret,
     564             :                               &reserve_close_transaction,
     565             :                               rcc))
     566             :   {
     567           0 :     return mhd_ret;
     568             :   }
     569           6 :   if (rcc->suspended)
     570           4 :     return MHD_YES;
     571           2 :   return reply_reserve_close_success (rcc);
     572             : }
     573             : 
     574             : 
     575             : /* end of taler-exchange-httpd_reserves_close.c */

Generated by: LCOV version 1.16