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 */