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