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