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