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