exchange_api_get_aml_measures.c (17282B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023, 2024 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 lib/exchange_api_get_aml_measures.c 19 * @brief Implementation of the GET /aml/$OFFICER_PUB/measures request 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <microhttpd.h> /* just for HTTP status codes */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_curl_lib.h> 26 #include "taler/taler_exchange_service.h" 27 #include "taler/taler_json_lib.h" 28 #include "exchange_api_handle.h" 29 #include "taler/taler_signatures.h" 30 #include "exchange_api_curl_defaults.h" 31 32 33 /** 34 * Scrap buffer of temporary arrays. 35 */ 36 struct Scrap 37 { 38 /** 39 * Kept in DLL. 40 */ 41 struct Scrap *next; 42 43 /** 44 * Kept in DLL. 45 */ 46 struct Scrap *prev; 47 48 /** 49 * Pointer to our allocation. 50 */ 51 const char **ptr; 52 }; 53 54 55 /** 56 * @brief A GET /aml/$OFFICER_PUB/measures Handle 57 */ 58 struct TALER_EXCHANGE_AmlGetMeasuresHandle 59 { 60 61 /** 62 * The url for this request. 63 */ 64 char *url; 65 66 /** 67 * Handle for the request. 68 */ 69 struct GNUNET_CURL_Job *job; 70 71 /** 72 * Function to call with the result. 73 */ 74 TALER_EXCHANGE_AmlMeasuresCallback measures_cb; 75 76 /** 77 * Closure for @e measures_cb. 78 */ 79 void *measures_cb_cls; 80 81 /** 82 * HTTP headers for the job. 83 */ 84 struct curl_slist *job_headers; 85 86 /** 87 * Head of scrap list. 88 */ 89 struct Scrap *scrap_head; 90 91 /** 92 * Tail of scrap list. 93 */ 94 struct Scrap *scrap_tail; 95 }; 96 97 98 /** 99 * Parse AML measures. 100 * 101 * @param jroots JSON object with measure data 102 * @param[out] roots where to write the result 103 * @return #GNUNET_OK on success 104 */ 105 static enum GNUNET_GenericReturnValue 106 parse_aml_roots ( 107 const json_t *jroots, 108 struct TALER_EXCHANGE_AvailableAmlMeasures *roots) 109 { 110 const json_t *obj; 111 const char *name; 112 size_t idx = 0; 113 114 json_object_foreach ((json_t *) jroots, name, obj) 115 { 116 struct TALER_EXCHANGE_AvailableAmlMeasures *root 117 = &roots[idx++]; 118 struct GNUNET_JSON_Specification spec[] = { 119 GNUNET_JSON_spec_string ("check_name", 120 &root->check_name), 121 GNUNET_JSON_spec_string ("prog_name", 122 &root->prog_name), 123 GNUNET_JSON_spec_mark_optional ( 124 GNUNET_JSON_spec_object_const ("context", 125 &root->context), 126 NULL), 127 GNUNET_JSON_spec_end () 128 }; 129 130 if (GNUNET_OK != 131 GNUNET_JSON_parse (obj, 132 spec, 133 NULL, 134 NULL)) 135 { 136 GNUNET_break_op (0); 137 return GNUNET_SYSERR; 138 } 139 root->measure_name = name; 140 } 141 return GNUNET_OK; 142 } 143 144 145 /** 146 * Create array of length @a len in scrap book. 147 * 148 * @param[in,out] lh context for allocations 149 * @param len length of array 150 * @return scrap array 151 */ 152 static const char ** 153 make_scrap ( 154 struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, 155 unsigned int len) 156 { 157 struct Scrap *s = GNUNET_new (struct Scrap); 158 159 s->ptr = GNUNET_new_array (len, 160 const char *); 161 GNUNET_CONTAINER_DLL_insert (lh->scrap_head, 162 lh->scrap_tail, 163 s); 164 return s->ptr; 165 } 166 167 168 /** 169 * Free all scrap space. 170 * 171 * @param[in,out] lh scrap context 172 */ 173 static void 174 free_scrap (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh) 175 { 176 struct Scrap *s; 177 178 while (NULL != (s = lh->scrap_head)) 179 { 180 GNUNET_CONTAINER_DLL_remove (lh->scrap_head, 181 lh->scrap_tail, 182 s); 183 GNUNET_free (s->ptr); 184 GNUNET_free (s); 185 } 186 } 187 188 189 /** 190 * Convert JSON array of strings to string array. 191 * 192 * @param j JSON array to convert 193 * @param[out] a array to initialize 194 * @return true on success 195 */ 196 static bool 197 j_to_a (const json_t *j, 198 const char **a) 199 { 200 const json_t *e; 201 size_t idx; 202 203 json_array_foreach ((json_t *) j, idx, e) 204 { 205 if (NULL == (a[idx] = json_string_value (e))) 206 return false; 207 } 208 return true; 209 } 210 211 212 /** 213 * Parse AML programs. 214 * 215 * @param[in,out] lh context for allocations 216 * @param jprogs JSON object with program data 217 * @param[out] progs where to write the result 218 * @return #GNUNET_OK on success 219 */ 220 static enum GNUNET_GenericReturnValue 221 parse_aml_programs ( 222 struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, 223 const json_t *jprogs, 224 struct TALER_EXCHANGE_AvailableAmlPrograms *progs) 225 { 226 const json_t *obj; 227 const char *name; 228 size_t idx = 0; 229 230 json_object_foreach ((json_t *) jprogs, name, obj) 231 { 232 struct TALER_EXCHANGE_AvailableAmlPrograms *prog 233 = &progs[idx++]; 234 const json_t *jcontext; 235 const json_t *jinputs; 236 struct GNUNET_JSON_Specification spec[] = { 237 GNUNET_JSON_spec_string ("description", 238 &prog->description), 239 GNUNET_JSON_spec_array_const ("context", 240 &jcontext), 241 GNUNET_JSON_spec_array_const ("inputs", 242 &jinputs), 243 GNUNET_JSON_spec_end () 244 }; 245 unsigned int len; 246 const char **ptr; 247 248 if (GNUNET_OK != 249 GNUNET_JSON_parse (obj, 250 spec, 251 NULL, 252 NULL)) 253 { 254 GNUNET_break_op (0); 255 return GNUNET_SYSERR; 256 } 257 prog->prog_name = name; 258 prog->contexts_length 259 = (unsigned int) json_array_size (jcontext); 260 prog->inputs_length 261 = (unsigned int) json_array_size (jinputs); 262 len = prog->contexts_length + prog->inputs_length; 263 if ( ((unsigned long long) len) != 264 (unsigned long long) json_array_size (jcontext) 265 + (unsigned long long) json_array_size (jinputs) ) 266 { 267 GNUNET_break_op (0); 268 return GNUNET_SYSERR; 269 } 270 ptr = make_scrap (lh, 271 len); 272 prog->contexts = ptr; 273 if (! j_to_a (jcontext, 274 prog->contexts)) 275 { 276 GNUNET_break_op (0); 277 return GNUNET_SYSERR; 278 } 279 prog->inputs = &ptr[prog->contexts_length]; 280 if (! j_to_a (jinputs, 281 prog->inputs)) 282 { 283 GNUNET_break_op (0); 284 return GNUNET_SYSERR; 285 } 286 } 287 return GNUNET_OK; 288 } 289 290 291 /** 292 * Parse AML measures. 293 * 294 * @param[in,out] lh context for allocations 295 * @param jchecks JSON object with measure data 296 * @param[out] checks where to write the result 297 * @return #GNUNET_OK on success 298 */ 299 static enum GNUNET_GenericReturnValue 300 parse_aml_checks ( 301 struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, 302 const json_t *jchecks, 303 struct TALER_EXCHANGE_AvailableKycChecks *checks) 304 { 305 const json_t *obj; 306 const char *name; 307 size_t idx = 0; 308 309 json_object_foreach ((json_t *) jchecks, name, obj) 310 { 311 struct TALER_EXCHANGE_AvailableKycChecks *check 312 = &checks[idx++]; 313 const json_t *jrequires; 314 const json_t *joutputs; 315 struct GNUNET_JSON_Specification spec[] = { 316 GNUNET_JSON_spec_string ("description", 317 &check->description), 318 GNUNET_JSON_spec_mark_optional ( 319 GNUNET_JSON_spec_object_const ("description_i18n", 320 &check->description_i18n), 321 NULL), 322 GNUNET_JSON_spec_array_const ("requires", 323 &jrequires), 324 GNUNET_JSON_spec_array_const ("outputs", 325 &joutputs), 326 GNUNET_JSON_spec_string ("fallback", 327 &check->fallback), 328 GNUNET_JSON_spec_end () 329 }; 330 unsigned int len; 331 const char **ptr; 332 333 if (GNUNET_OK != 334 GNUNET_JSON_parse (obj, 335 spec, 336 NULL, 337 NULL)) 338 { 339 GNUNET_break_op (0); 340 return GNUNET_SYSERR; 341 } 342 check->check_name = name; 343 344 check->requires_length 345 = (unsigned int) json_array_size (jrequires); 346 check->outputs_length 347 = (unsigned int) json_array_size (joutputs); 348 len = check->requires_length + check->outputs_length; 349 if ( ((unsigned long long) len) != 350 (unsigned long long) json_array_size (jrequires) 351 + (unsigned long long) json_array_size (joutputs) ) 352 { 353 GNUNET_break_op (0); 354 return GNUNET_SYSERR; 355 } 356 ptr = make_scrap (lh, 357 len); 358 check->requires = ptr; 359 if (! j_to_a (jrequires, 360 check->requires)) 361 { 362 GNUNET_break_op (0); 363 return GNUNET_SYSERR; 364 } 365 check->outputs = &ptr[check->requires_length]; 366 if (! j_to_a (joutputs, 367 check->outputs)) 368 { 369 GNUNET_break_op (0); 370 return GNUNET_SYSERR; 371 } 372 } 373 return GNUNET_OK; 374 } 375 376 377 /** 378 * Parse the provided decision data from the "200 OK" response. 379 * 380 * @param[in,out] lh handle (callback may be zero'ed out) 381 * @param json json reply with the data for one coin 382 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 383 */ 384 static enum GNUNET_GenericReturnValue 385 parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, 386 const json_t *json) 387 { 388 struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = { 389 .hr.reply = json, 390 .hr.http_status = MHD_HTTP_OK 391 }; 392 const json_t *jroots; 393 const json_t *jprograms; 394 const json_t *jchecks; 395 struct GNUNET_JSON_Specification spec[] = { 396 GNUNET_JSON_spec_object_const ("roots", 397 &jroots), 398 GNUNET_JSON_spec_object_const ("programs", 399 &jprograms), 400 GNUNET_JSON_spec_object_const ("checks", 401 &jchecks), 402 GNUNET_JSON_spec_end () 403 }; 404 405 if (GNUNET_OK != 406 GNUNET_JSON_parse (json, 407 spec, 408 NULL, 409 NULL)) 410 { 411 GNUNET_break_op (0); 412 return GNUNET_SYSERR; 413 } 414 lr.details.ok.roots_length 415 = (unsigned int) json_object_size (jroots); 416 lr.details.ok.programs_length 417 = (unsigned int) json_object_size (jprograms); 418 lr.details.ok.checks_length 419 = (unsigned int) json_object_size (jchecks); 420 if ( ( ((size_t) lr.details.ok.roots_length) 421 != json_object_size (jroots)) || 422 ( ((size_t) lr.details.ok.programs_length) 423 != json_object_size (jprograms)) || 424 ( ((size_t) lr.details.ok.checks_length) 425 != json_object_size (jchecks)) ) 426 { 427 GNUNET_break_op (0); 428 return GNUNET_SYSERR; 429 } 430 431 { 432 struct TALER_EXCHANGE_AvailableAmlMeasures roots[ 433 GNUNET_NZL (lr.details.ok.roots_length)]; 434 struct TALER_EXCHANGE_AvailableAmlPrograms progs[ 435 GNUNET_NZL (lr.details.ok.programs_length)]; 436 struct TALER_EXCHANGE_AvailableKycChecks checks[ 437 GNUNET_NZL (lr.details.ok.checks_length)]; 438 enum GNUNET_GenericReturnValue ret; 439 440 memset (roots, 441 0, 442 sizeof (roots)); 443 memset (progs, 444 0, 445 sizeof (progs)); 446 memset (checks, 447 0, 448 sizeof (checks)); 449 lr.details.ok.roots = roots; 450 lr.details.ok.programs = progs; 451 lr.details.ok.checks = checks; 452 ret = parse_aml_roots (jroots, 453 roots); 454 if (GNUNET_OK == ret) 455 ret = parse_aml_programs (lh, 456 jprograms, 457 progs); 458 if (GNUNET_OK == ret) 459 ret = parse_aml_checks (lh, 460 jchecks, 461 checks); 462 if (GNUNET_OK == ret) 463 { 464 lh->measures_cb (lh->measures_cb_cls, 465 &lr); 466 lh->measures_cb = NULL; 467 } 468 free_scrap (lh); 469 return ret; 470 } 471 } 472 473 474 /** 475 * Function called when we're done processing the 476 * HTTP /aml/$OFFICER_PUB/measures request. 477 * 478 * @param cls the `struct TALER_EXCHANGE_AmlGetMeasuresHandle` 479 * @param response_code HTTP response code, 0 on error 480 * @param response parsed JSON result, NULL on error 481 */ 482 static void 483 handle_lookup_finished (void *cls, 484 long response_code, 485 const void *response) 486 { 487 struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh = cls; 488 const json_t *j = response; 489 struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = { 490 .hr.reply = j, 491 .hr.http_status = (unsigned int) response_code 492 }; 493 494 lh->job = NULL; 495 switch (response_code) 496 { 497 case 0: 498 lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 499 break; 500 case MHD_HTTP_OK: 501 if (GNUNET_OK != 502 parse_measures_ok (lh, 503 j)) 504 { 505 GNUNET_break_op (0); 506 lr.hr.http_status = 0; 507 lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 508 break; 509 } 510 GNUNET_assert (NULL == lh->measures_cb); 511 TALER_EXCHANGE_aml_get_measures_cancel (lh); 512 return; 513 case MHD_HTTP_NO_CONTENT: 514 break; 515 case MHD_HTTP_BAD_REQUEST: 516 lr.hr.ec = TALER_JSON_get_error_code (j); 517 lr.hr.hint = TALER_JSON_get_error_hint (j); 518 /* This should never happen, either us or the exchange is buggy 519 (or API version conflict); just pass JSON reply to the application */ 520 break; 521 case MHD_HTTP_FORBIDDEN: 522 lr.hr.ec = TALER_JSON_get_error_code (j); 523 lr.hr.hint = TALER_JSON_get_error_hint (j); 524 /* Nothing really to verify, exchange says this coin was not melted; we 525 should pass the JSON reply to the application */ 526 break; 527 case MHD_HTTP_INTERNAL_SERVER_ERROR: 528 lr.hr.ec = TALER_JSON_get_error_code (j); 529 lr.hr.hint = TALER_JSON_get_error_hint (j); 530 /* Server had an internal issue; we should retry, but this API 531 leaves this to the application */ 532 break; 533 default: 534 /* unexpected response code */ 535 GNUNET_break_op (0); 536 lr.hr.ec = TALER_JSON_get_error_code (j); 537 lr.hr.hint = TALER_JSON_get_error_hint (j); 538 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 539 "Unexpected response code %u/%d for get AML measures\n", 540 (unsigned int) response_code, 541 (int) lr.hr.ec); 542 break; 543 } 544 if (NULL != lh->measures_cb) 545 lh->measures_cb (lh->measures_cb_cls, 546 &lr); 547 TALER_EXCHANGE_aml_get_measures_cancel (lh); 548 } 549 550 551 struct TALER_EXCHANGE_AmlGetMeasuresHandle * 552 TALER_EXCHANGE_aml_get_measures ( 553 struct GNUNET_CURL_Context *ctx, 554 const char *exchange_url, 555 const struct TALER_AmlOfficerPrivateKeyP *officer_priv, 556 TALER_EXCHANGE_AmlMeasuresCallback cb, 557 void *cb_cls) 558 { 559 struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh; 560 CURL *eh; 561 struct TALER_AmlOfficerPublicKeyP officer_pub; 562 struct TALER_AmlOfficerSignatureP officer_sig; 563 char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 564 + 32]; 565 566 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 567 &officer_pub.eddsa_pub); 568 TALER_officer_aml_query_sign (officer_priv, 569 &officer_sig); 570 { 571 char pub_str[sizeof (officer_pub) * 2]; 572 char *end; 573 574 end = GNUNET_STRINGS_data_to_string ( 575 &officer_pub, 576 sizeof (officer_pub), 577 pub_str, 578 sizeof (pub_str)); 579 *end = '\0'; 580 GNUNET_snprintf (arg_str, 581 sizeof (arg_str), 582 "aml/%s/measures", 583 pub_str); 584 } 585 lh = GNUNET_new (struct TALER_EXCHANGE_AmlGetMeasuresHandle); 586 lh->measures_cb = cb; 587 lh->measures_cb_cls = cb_cls; 588 lh->url = TALER_url_join (exchange_url, 589 arg_str, 590 NULL); 591 if (NULL == lh->url) 592 { 593 GNUNET_free (lh); 594 return NULL; 595 } 596 eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); 597 if (NULL == eh) 598 { 599 GNUNET_break (0); 600 GNUNET_free (lh->url); 601 GNUNET_free (lh); 602 return NULL; 603 } 604 { 605 char *hdr; 606 char sig_str[sizeof (officer_sig) * 2]; 607 char *end; 608 609 end = GNUNET_STRINGS_data_to_string ( 610 &officer_sig, 611 sizeof (officer_sig), 612 sig_str, 613 sizeof (sig_str)); 614 *end = '\0'; 615 616 GNUNET_asprintf (&hdr, 617 "%s: %s", 618 TALER_AML_OFFICER_SIGNATURE_HEADER, 619 sig_str); 620 lh->job_headers = curl_slist_append (NULL, 621 hdr); 622 GNUNET_free (hdr); 623 lh->job_headers = curl_slist_append (lh->job_headers, 624 "Content-type: application/json"); 625 lh->job = GNUNET_CURL_job_add2 (ctx, 626 eh, 627 lh->job_headers, 628 &handle_lookup_finished, 629 lh); 630 } 631 return lh; 632 } 633 634 635 void 636 TALER_EXCHANGE_aml_get_measures_cancel ( 637 struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh) 638 { 639 if (NULL != lh->job) 640 { 641 GNUNET_CURL_job_cancel (lh->job); 642 lh->job = NULL; 643 } 644 curl_slist_free_all (lh->job_headers); 645 GNUNET_free (lh->url); 646 GNUNET_free (lh); 647 } 648 649 650 /* end of exchange_api_get_aml_measures.c */