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