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