exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */