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