exchange

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

bank_api_transfer.c (10457B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2015--2023, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU 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 General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file bank-lib/bank_api_transfer.c
     19  * @brief Implementation of the /transfer/ requests of the bank's HTTP API
     20  * @author Christian Grothoff
     21  */
     22 #include "bank_api_common.h"
     23 #include <microhttpd.h> /* just for HTTP status codes */
     24 #include "taler/taler_signatures.h"
     25 #include "taler/taler_curl_lib.h"
     26 #include "taler/taler_bank_service.h"
     27 
     28 
     29 GNUNET_NETWORK_STRUCT_BEGIN
     30 
     31 /**
     32  * Data structure serialized in the prepare stage.
     33  */
     34 struct WirePackP
     35 {
     36   /**
     37    * Random unique identifier for the request.
     38    */
     39   struct GNUNET_HashCode request_uid;
     40 
     41   /**
     42    * Amount to be transferred.
     43    */
     44   struct TALER_AmountNBO amount;
     45 
     46   /**
     47    * Wire transfer identifier to use.
     48    */
     49   struct TALER_WireTransferIdentifierRawP wtid;
     50 
     51   /**
     52    * Length of the payto:// URL of the target account,
     53    * including 0-terminator, in network byte order.
     54    */
     55   uint32_t account_len GNUNET_PACKED;
     56 
     57   /**
     58    * Length of the exchange's base URL,
     59    * including 0-terminator, in network byte order.
     60    */
     61   uint32_t exchange_url_len GNUNET_PACKED;
     62 
     63 };
     64 
     65 GNUNET_NETWORK_STRUCT_END
     66 
     67 
     68 void
     69 TALER_BANK_prepare_transfer (
     70   const struct TALER_FullPayto destination_account_payto_uri,
     71   const struct TALER_Amount *amount,
     72   const char *exchange_base_url,
     73   const struct TALER_WireTransferIdentifierRawP *wtid,
     74   const char *extra_wire_transfer_subject,
     75   void **buf,
     76   size_t *buf_size)
     77 {
     78   const char *payto = destination_account_payto_uri.full_payto;
     79   struct WirePackP *wp;
     80   size_t d_len = strlen (payto) + 1;
     81   size_t u_len = strlen (exchange_base_url) + 1;
     82   size_t x_len = (NULL == extra_wire_transfer_subject)
     83     ? 0
     84     : strlen (extra_wire_transfer_subject) + 1;
     85   char *end;
     86 
     87   if ( (d_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
     88        (u_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
     89        (x_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
     90        (d_len + u_len + x_len + sizeof (*wp) >= GNUNET_MAX_MALLOC_CHECKED) )
     91   {
     92     GNUNET_break (0); /* that's some long URL... */
     93     *buf = NULL;
     94     *buf_size = 0;
     95     return;
     96   }
     97   *buf_size = sizeof (*wp) + d_len + u_len + x_len;
     98   wp = GNUNET_malloc (*buf_size);
     99   GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE,
    100                                     &wp->request_uid);
    101   TALER_amount_hton (&wp->amount,
    102                      amount);
    103   wp->wtid = *wtid;
    104   wp->account_len = htonl ((uint32_t) d_len);
    105   wp->exchange_url_len = htonl ((uint32_t) u_len);
    106   end = (char *) &wp[1];
    107   GNUNET_memcpy (end,
    108                  payto,
    109                  d_len);
    110   GNUNET_memcpy (end + d_len,
    111                  exchange_base_url,
    112                  u_len);
    113   GNUNET_memcpy (end + d_len + u_len,
    114                  extra_wire_transfer_subject,
    115                  x_len);
    116   *buf = (char *) wp;
    117 }
    118 
    119 
    120 /**
    121  * @brief Handle for an active wire transfer.
    122  */
    123 struct TALER_BANK_TransferHandle
    124 {
    125 
    126   /**
    127    * The url for this request.
    128    */
    129   char *request_url;
    130 
    131   /**
    132    * POST context.
    133    */
    134   struct TALER_CURL_PostContext post_ctx;
    135 
    136   /**
    137    * Handle for the request.
    138    */
    139   struct GNUNET_CURL_Job *job;
    140 
    141   /**
    142    * Function to call with the result.
    143    */
    144   TALER_BANK_TransferCallback cb;
    145 
    146   /**
    147    * Closure for @a cb.
    148    */
    149   void *cb_cls;
    150 
    151 };
    152 
    153 
    154 /**
    155  * Function called when we're done processing the
    156  * HTTP /transfer request.
    157  *
    158  * @param cls the `struct TALER_BANK_TransferHandle`
    159  * @param response_code HTTP response code, 0 on error
    160  * @param response parsed JSON result, NULL on error
    161  */
    162 static void
    163 handle_transfer_finished (void *cls,
    164                           long response_code,
    165                           const void *response)
    166 {
    167   struct TALER_BANK_TransferHandle *th = cls;
    168   const json_t *j = response;
    169   struct TALER_BANK_TransferResponse tr = {
    170     .http_status = response_code,
    171     .response = j
    172   };
    173 
    174   th->job = NULL;
    175   switch (response_code)
    176   {
    177   case 0:
    178     tr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    179     break;
    180   case MHD_HTTP_OK:
    181     {
    182       struct GNUNET_JSON_Specification spec[] = {
    183         GNUNET_JSON_spec_uint64 ("row_id",
    184                                  &tr.details.ok.row_id),
    185         GNUNET_JSON_spec_timestamp ("timestamp",
    186                                     &tr.details.ok.timestamp),
    187         GNUNET_JSON_spec_end ()
    188       };
    189 
    190       if (GNUNET_OK !=
    191           GNUNET_JSON_parse (j,
    192                              spec,
    193                              NULL, NULL))
    194       {
    195         GNUNET_break_op (0);
    196         tr.http_status = 0;
    197         tr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    198         break;
    199       }
    200     }
    201     break;
    202   case MHD_HTTP_BAD_REQUEST:
    203     /* This should never happen, either us or the bank is buggy
    204        (or API version conflict); just pass JSON reply to the application */
    205     GNUNET_break_op (0);
    206     tr.ec = TALER_JSON_get_error_code (j);
    207     break;
    208   case MHD_HTTP_UNAUTHORIZED:
    209     /* Nothing really to verify, bank says our credentials are
    210        invalid. We should pass the JSON reply to the application. */
    211     tr.ec = TALER_JSON_get_error_code (j);
    212     break;
    213   case MHD_HTTP_NOT_FOUND:
    214     /* Nothing really to verify, endpoint wrong -- could be user unknown */
    215     tr.ec = TALER_JSON_get_error_code (j);
    216     break;
    217   case MHD_HTTP_CONFLICT:
    218     /* Nothing really to verify. Server says we used the same transfer request
    219        UID before, but with different details.  Should not happen if the user
    220        properly used #TALER_BANK_prepare_transfer() and our PRNG is not
    221        broken... */
    222     tr.ec = TALER_JSON_get_error_code (j);
    223     break;
    224   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    225     /* Server had an internal issue; we should retry, but this API
    226        leaves this to the application */
    227     tr.ec = TALER_JSON_get_error_code (j);
    228     break;
    229   default:
    230     /* unexpected response code */
    231     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    232                 "Unexpected response code %u\n",
    233                 (unsigned int) response_code);
    234     GNUNET_break (0);
    235     tr.ec = TALER_JSON_get_error_code (j);
    236     break;
    237   }
    238   th->cb (th->cb_cls,
    239           &tr);
    240   TALER_BANK_transfer_cancel (th);
    241 }
    242 
    243 
    244 struct TALER_BANK_TransferHandle *
    245 TALER_BANK_transfer (
    246   struct GNUNET_CURL_Context *ctx,
    247   const struct TALER_BANK_AuthenticationData *auth,
    248   const void *buf,
    249   size_t buf_size,
    250   TALER_BANK_TransferCallback cc,
    251   void *cc_cls)
    252 {
    253   struct TALER_BANK_TransferHandle *th;
    254   json_t *transfer_obj;
    255   CURL *eh;
    256   const struct WirePackP *wp = buf;
    257   uint32_t d_len;
    258   uint32_t u_len;
    259   uint32_t x_len;
    260   const char *destination_account_uri;
    261   const char *exchange_base_url;
    262   const char *extra_metadata;
    263   struct TALER_Amount amount;
    264 
    265   if (sizeof (*wp) > buf_size)
    266   {
    267     GNUNET_break (0);
    268     return NULL;
    269   }
    270   d_len = ntohl (wp->account_len);
    271   u_len = ntohl (wp->exchange_url_len);
    272   if ( (sizeof (*wp) + d_len + u_len > buf_size) ||
    273        (d_len > buf_size) ||
    274        (u_len > buf_size) ||
    275        (d_len + u_len > buf_size) )
    276   {
    277     GNUNET_break (0);
    278     return NULL;
    279   }
    280   x_len = buf_size - (sizeof (*wp) + d_len + u_len);
    281   destination_account_uri = (const char *) &wp[1];
    282   exchange_base_url = destination_account_uri + d_len;
    283   if ( ('\0' != destination_account_uri[d_len - 1]) ||
    284        ('\0' != exchange_base_url[u_len - 1]) )
    285   {
    286     GNUNET_break (0);
    287     return NULL;
    288   }
    289   if (0 != x_len)
    290   {
    291     extra_metadata = destination_account_uri + d_len + u_len;
    292     if ('\0' != extra_metadata[x_len - 1])
    293     {
    294       GNUNET_break (0);
    295       return NULL;
    296     }
    297   }
    298   else
    299   {
    300     extra_metadata = NULL;
    301   }
    302 
    303   if (NULL == auth->wire_gateway_url)
    304   {
    305     GNUNET_break (0);
    306     return NULL;
    307   }
    308   TALER_amount_ntoh (&amount,
    309                      &wp->amount);
    310   th = GNUNET_new (struct TALER_BANK_TransferHandle);
    311   th->cb = cc;
    312   th->cb_cls = cc_cls;
    313   th->request_url = TALER_url_join (auth->wire_gateway_url,
    314                                     "transfer",
    315                                     NULL);
    316   if (NULL == th->request_url)
    317   {
    318     GNUNET_free (th);
    319     GNUNET_break (0);
    320     return NULL;
    321   }
    322   transfer_obj = GNUNET_JSON_PACK (
    323     GNUNET_JSON_pack_data_auto ("request_uid",
    324                                 &wp->request_uid),
    325     TALER_JSON_pack_amount ("amount",
    326                             &amount),
    327     GNUNET_JSON_pack_string ("exchange_base_url",
    328                              exchange_base_url),
    329     GNUNET_JSON_pack_allow_null (
    330       GNUNET_JSON_pack_string ("metadata",
    331                                extra_metadata)),
    332     GNUNET_JSON_pack_data_auto ("wtid",
    333                                 &wp->wtid),
    334     GNUNET_JSON_pack_string ("credit_account",
    335                              destination_account_uri));
    336   if (NULL == transfer_obj)
    337   {
    338     GNUNET_break (0);
    339     return NULL;
    340   }
    341   eh = curl_easy_init ();
    342   if ( (NULL == eh) ||
    343        (GNUNET_OK !=
    344         TALER_BANK_setup_auth_ (eh,
    345                                 auth)) ||
    346        (CURLE_OK !=
    347         curl_easy_setopt (eh,
    348                           CURLOPT_URL,
    349                           th->request_url)) ||
    350        (GNUNET_OK !=
    351         TALER_curl_easy_post (&th->post_ctx,
    352                               eh,
    353                               transfer_obj)) )
    354   {
    355     GNUNET_break (0);
    356     TALER_BANK_transfer_cancel (th);
    357     if (NULL != eh)
    358       curl_easy_cleanup (eh);
    359     json_decref (transfer_obj);
    360     return NULL;
    361   }
    362   json_decref (transfer_obj);
    363   th->job = GNUNET_CURL_job_add2 (ctx,
    364                                   eh,
    365                                   th->post_ctx.headers,
    366                                   &handle_transfer_finished,
    367                                   th);
    368   return th;
    369 }
    370 
    371 
    372 void
    373 TALER_BANK_transfer_cancel (struct TALER_BANK_TransferHandle *th)
    374 {
    375   if (NULL != th->job)
    376   {
    377     GNUNET_CURL_job_cancel (th->job);
    378     th->job = NULL;
    379   }
    380   TALER_curl_easy_post_finished (&th->post_ctx);
    381   GNUNET_free (th->request_url);
    382   GNUNET_free (th);
    383 }
    384 
    385 
    386 /* end of bank_api_transfer.c */