exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_reserves_close.c (15168B)


      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 TEH_reserves_close_cleanup ()
    158 {
    159   struct ReserveCloseContext *rcc;
    160 
    161   while (NULL != (rcc = rcc_head))
    162   {
    163     GNUNET_CONTAINER_DLL_remove (rcc_head,
    164                                  rcc_tail,
    165                                  rcc);
    166     MHD_resume_connection (rcc->rc->connection);
    167   }
    168 }
    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 reply_reserve_close_success (
    179   const struct ReserveCloseContext *rhc)
    180 {
    181   struct MHD_Connection *connection = rhc->rc->connection;
    182   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 reserve_close_legi_cb (
    199   void *cls,
    200   const struct TEH_LegitimizationCheckResult *lcr)
    201 {
    202   struct ReserveCloseContext *rcc = cls;
    203 
    204   rcc->lch = NULL;
    205   rcc->http_status = lcr->http_status;
    206   rcc->response = lcr->response;
    207   rcc->kyc = lcr->kyc;
    208   GNUNET_CONTAINER_DLL_remove (rcc_head,
    209                                rcc_tail,
    210                                rcc);
    211   MHD_resume_connection (rcc->rc->connection);
    212   rcc->resumed = true;
    213   rcc->suspended = false;
    214   TALER_MHD_daemon_trigger ();
    215 }
    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 amount_it (void *cls,
    236            struct GNUNET_TIME_Absolute limit,
    237            TALER_EXCHANGEDB_KycAmountCallback cb,
    238            void *cb_cls)
    239 {
    240   struct ReserveCloseContext *rcc = cls;
    241   enum GNUNET_GenericReturnValue ret;
    242 
    243   ret = cb (cb_cls,
    244             &rcc->balance,
    245             GNUNET_TIME_absolute_get ());
    246   GNUNET_break (GNUNET_SYSERR != ret);
    247   if (GNUNET_OK != ret)
    248     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    249   rcc->qs
    250     = TEH_plugin->iterate_reserve_close_info (
    251         TEH_plugin->cls,
    252         &rcc->kyc_payto,
    253         limit,
    254         cb,
    255         cb_cls);
    256   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 reserve_close_transaction (
    277   void *cls,
    278   struct MHD_Connection *connection,
    279   MHD_RESULT *mhd_ret)
    280 {
    281   struct ReserveCloseContext *rcc = cls;
    282   enum GNUNET_DB_QueryStatus qs;
    283   struct TALER_FullPayto payto_uri = {
    284     .full_payto = NULL
    285   };
    286   const struct TALER_WireFeeSet *wf;
    287 
    288   qs = TEH_plugin->select_reserve_close_info (
    289     TEH_plugin->cls,
    290     &rcc->reserve_pub,
    291     &rcc->balance,
    292     &payto_uri);
    293   switch (qs)
    294   {
    295   case GNUNET_DB_STATUS_HARD_ERROR:
    296     GNUNET_break (0);
    297     *mhd_ret
    298       = 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     return qs;
    304   case GNUNET_DB_STATUS_SOFT_ERROR:
    305     return qs;
    306   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    307     *mhd_ret
    308       = TALER_MHD_reply_with_error (
    309           connection,
    310           MHD_HTTP_NOT_FOUND,
    311           TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
    312           NULL);
    313     return GNUNET_DB_STATUS_HARD_ERROR;
    314   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    315     break;
    316   }
    317 
    318   if ( (NULL == rcc->payto_uri.full_payto) &&
    319        (NULL == payto_uri.full_payto) )
    320   {
    321     *mhd_ret
    322       = TALER_MHD_reply_with_error (
    323           connection,
    324           MHD_HTTP_CONFLICT,
    325           TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT,
    326           NULL);
    327     return GNUNET_DB_STATUS_HARD_ERROR;
    328   }
    329 
    330   if ( (! rcc->resumed) &&
    331        (NULL != rcc->payto_uri.full_payto) &&
    332        ( (NULL == payto_uri.full_payto) ||
    333          (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     TALER_full_payto_normalize_and_hash (rcc->payto_uri,
    341                                          &rcc->kyc_payto);
    342     rcc->lch = TEH_legitimization_check (
    343       &rcc->rc->async_scope_id,
    344       TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
    345       rcc->payto_uri,
    346       &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     GNUNET_assert (NULL != rcc->lch);
    353     GNUNET_CONTAINER_DLL_insert (rcc_head,
    354                                  rcc_tail,
    355                                  rcc);
    356     MHD_suspend_connection (rcc->rc->connection);
    357     rcc->suspended = true;
    358     GNUNET_free (payto_uri.full_payto);
    359     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    360   }
    361   rcc->kyc.ok = true;
    362   if (NULL == rcc->payto_uri.full_payto)
    363     rcc->payto_uri = payto_uri;
    364 
    365   {
    366     char *method;
    367 
    368     method = TALER_payto_get_method (rcc->payto_uri.full_payto);
    369     wf = TEH_wire_fees_by_time (rcc->timestamp,
    370                                 method);
    371     if (NULL == wf)
    372     {
    373       GNUNET_break (0);
    374       *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       GNUNET_free (method);
    380       GNUNET_free (payto_uri.full_payto);
    381       return GNUNET_DB_STATUS_HARD_ERROR;
    382     }
    383     GNUNET_free (method);
    384   }
    385 
    386   if (0 >
    387       TALER_amount_subtract (&rcc->wire_amount,
    388                              &rcc->balance,
    389                              &wf->closing))
    390   {
    391     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    392                 "Client attempted to close reserve with insufficient balance.\n");
    393     GNUNET_assert (GNUNET_OK ==
    394                    TALER_amount_set_zero (TEH_currency,
    395                                           &rcc->wire_amount));
    396     *mhd_ret = reply_reserve_close_success (rcc);
    397     GNUNET_free (payto_uri.full_payto);
    398     return GNUNET_DB_STATUS_HARD_ERROR;
    399   }
    400 
    401   qs = TEH_plugin->insert_close_request (
    402     TEH_plugin->cls,
    403     &rcc->reserve_pub,
    404     payto_uri,
    405     &rcc->reserve_sig,
    406     rcc->timestamp,
    407     &rcc->balance,
    408     &wf->closing);
    409   GNUNET_free (payto_uri.full_payto);
    410   rcc->payto_uri.full_payto = NULL;
    411   if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    412   {
    413     GNUNET_break (0);
    414     *mhd_ret
    415       = TALER_MHD_reply_with_error (connection,
    416                                     MHD_HTTP_INTERNAL_SERVER_ERROR,
    417                                     TALER_EC_GENERIC_DB_FETCH_FAILED,
    418                                     "insert_close_request");
    419     return qs;
    420   }
    421   if (qs <= 0)
    422   {
    423     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    424     return qs;
    425   }
    426   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 reserve_close_cleanup (struct TEH_RequestContext *rc)
    439 {
    440   struct ReserveCloseContext *rcc = rc->rh_ctx;
    441 
    442   if (NULL != rcc->lch)
    443   {
    444     TEH_legitimization_check_cancel (rcc->lch);
    445     rcc->lch = NULL;
    446   }
    447   GNUNET_free (rcc);
    448 }
    449 
    450 
    451 MHD_RESULT
    452 TEH_handler_reserves_close (
    453   struct TEH_RequestContext *rc,
    454   const struct TALER_ReservePublicKeyP *reserve_pub,
    455   const json_t *root)
    456 {
    457   struct ReserveCloseContext *rcc = rc->rh_ctx;
    458   MHD_RESULT mhd_ret;
    459 
    460   if (NULL == rcc)
    461   {
    462     rcc = GNUNET_new (struct ReserveCloseContext);
    463     rc->rh_ctx = rcc;
    464     rc->rh_cleaner = &reserve_close_cleanup;
    465     rcc->reserve_pub = *reserve_pub;
    466     rcc->rc = rc;
    467 
    468     {
    469       struct GNUNET_JSON_Specification spec[] = {
    470         GNUNET_JSON_spec_timestamp ("request_timestamp",
    471                                     &rcc->timestamp),
    472         GNUNET_JSON_spec_mark_optional (
    473           TALER_JSON_spec_full_payto_uri ("payto_uri",
    474                                           &rcc->payto_uri),
    475           NULL),
    476         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    477                                      &rcc->reserve_sig),
    478         GNUNET_JSON_spec_end ()
    479       };
    480 
    481       {
    482         enum GNUNET_GenericReturnValue res;
    483 
    484         res = TALER_MHD_parse_json_data (rc->connection,
    485                                          root,
    486                                          spec);
    487         if (GNUNET_SYSERR == res)
    488         {
    489           GNUNET_break (0);
    490           return MHD_NO; /* hard failure */
    491         }
    492         if (GNUNET_NO == res)
    493         {
    494           GNUNET_break_op (0);
    495           return MHD_YES; /* failure */
    496         }
    497       }
    498     }
    499 
    500     {
    501       struct GNUNET_TIME_Timestamp now;
    502 
    503       now = GNUNET_TIME_timestamp_get ();
    504       if (! GNUNET_TIME_absolute_approx_eq (
    505             now.abs_time,
    506             rcc->timestamp.abs_time,
    507             TIMESTAMP_TOLERANCE))
    508       {
    509         GNUNET_break_op (0);
    510         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     if (NULL != rcc->payto_uri.full_payto)
    519       TALER_full_payto_hash (rcc->payto_uri,
    520                              &rcc->h_payto);
    521     if (GNUNET_OK !=
    522         TALER_wallet_reserve_close_verify (
    523           rcc->timestamp,
    524           &rcc->h_payto,
    525           &rcc->reserve_pub,
    526           &rcc->reserve_sig))
    527     {
    528       GNUNET_break_op (0);
    529       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   if (NULL != rcc->response)
    537     return MHD_queue_response (rc->connection,
    538                                rcc->http_status,
    539                                rcc->response);
    540   if (rcc->resumed &&
    541       (! rcc->kyc.ok) )
    542   {
    543     if (0 == rcc->kyc.requirement_row)
    544     {
    545       GNUNET_break (0);
    546       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     return TEH_RESPONSE_reply_kyc_required (
    553       rc->connection,
    554       &rcc->kyc_payto,
    555       &rcc->kyc,
    556       false);
    557   }
    558 
    559   if (GNUNET_OK !=
    560       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     return mhd_ret;
    568   }
    569   if (rcc->suspended)
    570     return MHD_YES;
    571   return reply_reserve_close_success (rcc);
    572 }
    573 
    574 
    575 /* end of taler-exchange-httpd_reserves_close.c */