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 (21694B)


      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_mark_optional (
    305         GNUNET_JSON_spec_string ("new_measures",
    306                                  &decision->new_measures),
    307         NULL),
    308       GNUNET_JSON_spec_mark_optional (
    309         GNUNET_JSON_spec_fixed_auto ("decider_pub",
    310                                      &decision->decider_pub),
    311         NULL),
    312       GNUNET_JSON_spec_mark_optional (
    313         GNUNET_JSON_spec_string ("decider_name",
    314                                  &decision->decider_name),
    315         NULL),
    316       GNUNET_JSON_spec_mark_optional (
    317         GNUNET_JSON_spec_uint64 ("kyc_attributes_rowid",
    318                                  &decision->kyc_attributes_rowid),
    319         NULL),
    320       GNUNET_JSON_spec_timestamp ("decision_time",
    321                                   &decision->decision_time),
    322       GNUNET_JSON_spec_mark_optional (
    323         GNUNET_JSON_spec_object_const ("properties",
    324                                        &decision->properties),
    325         NULL),
    326       GNUNET_JSON_spec_object_const ("limits",
    327                                      &jlimits),
    328       GNUNET_JSON_spec_bool ("to_investigate",
    329                              &decision->to_investigate),
    330       GNUNET_JSON_spec_bool ("is_active",
    331                              &decision->is_active),
    332       GNUNET_JSON_spec_end ()
    333     };
    334 
    335     GNUNET_assert (idx < records_ar_length);
    336     if (GNUNET_OK !=
    337         GNUNET_JSON_parse (obj,
    338                            spec,
    339                            NULL,
    340                            NULL))
    341     {
    342       GNUNET_break_op (0);
    343       return GNUNET_SYSERR;
    344     }
    345 
    346     if (GNUNET_OK !=
    347         parse_limits (adgh,
    348                       jlimits,
    349                       &decision->limits,
    350                       &rule_off,
    351                       &mp_off))
    352     {
    353       GNUNET_break_op (0);
    354       return GNUNET_SYSERR;
    355     }
    356   }
    357   return GNUNET_OK;
    358 }
    359 
    360 
    361 /**
    362  * Parse the provided decision data from the "200 OK" response.
    363  *
    364  * @param[in,out] adgh handle (callback may be zero'ed out)
    365  * @param json json reply with the data
    366  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
    367  */
    368 static enum GNUNET_GenericReturnValue
    369 parse_get_aml_decisions_ok (struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh,
    370                             const json_t *json)
    371 {
    372   struct TALER_EXCHANGE_GetAmlDecisionsResponse lr = {
    373     .hr.reply = json,
    374     .hr.http_status = MHD_HTTP_OK
    375   };
    376   const json_t *jrecords;
    377   struct GNUNET_JSON_Specification spec[] = {
    378     GNUNET_JSON_spec_array_const ("records",
    379                                   &jrecords),
    380     GNUNET_JSON_spec_end ()
    381   };
    382 
    383   if (GNUNET_OK !=
    384       GNUNET_JSON_parse (json,
    385                          spec,
    386                          NULL,
    387                          NULL))
    388   {
    389     GNUNET_break_op (0);
    390     return GNUNET_SYSERR;
    391   }
    392 
    393   lr.details.ok.records_length = json_array_size (jrecords);
    394 
    395   /* First pass: count total rules and measures across all records */
    396   {
    397     size_t total_rules = 0;
    398     size_t total_measures = 0;
    399     const json_t *obj;
    400     size_t idx;
    401 
    402     json_array_foreach ((json_t *) jrecords, idx, obj)
    403     {
    404       const json_t *jlimits = json_object_get (obj, "limits");
    405       const json_t *jrules;
    406 
    407       if (NULL == jlimits)
    408         continue;
    409       jrules = json_object_get (jlimits, "rules");
    410       if (NULL == jrules)
    411         continue;
    412       total_rules += json_array_size (jrules);
    413 
    414       {
    415         const json_t *jrule;
    416         size_t ridx;
    417 
    418         json_array_foreach ((json_t *) jrules, ridx, jrule)
    419         {
    420           const json_t *jmeasures = json_object_get (jrule, "measures");
    421 
    422           if (NULL != jmeasures)
    423             total_measures += json_array_size (jmeasures);
    424         }
    425       }
    426     }
    427 
    428     adgh->all_rules = GNUNET_new_array (
    429       GNUNET_NZL (total_rules),
    430       struct TALER_EXCHANGE_GetAmlDecisionsKycRule);
    431     adgh->all_mp = GNUNET_new_array (
    432       GNUNET_NZL (total_measures),
    433       const char *);
    434   }
    435 
    436   {
    437     struct TALER_EXCHANGE_GetAmlDecisionsDecision records[
    438       GNUNET_NZL (lr.details.ok.records_length)];
    439     enum GNUNET_GenericReturnValue ret;
    440 
    441     memset (records,
    442             0,
    443             sizeof (records));
    444     lr.details.ok.records = records;
    445     ret = parse_aml_decisions (adgh,
    446                                jrecords,
    447                                lr.details.ok.records_length,
    448                                records);
    449     if (GNUNET_OK == ret)
    450     {
    451       adgh->cb (adgh->cb_cls,
    452                 &lr);
    453       adgh->cb = NULL;
    454     }
    455     GNUNET_free (adgh->all_rules);
    456     adgh->all_rules = NULL;
    457     GNUNET_free (adgh->all_mp);
    458     adgh->all_mp = NULL;
    459     return ret;
    460   }
    461 }
    462 
    463 
    464 /**
    465  * Function called when we're done processing the
    466  * HTTP /aml/$OFFICER_PUB/decisions request.
    467  *
    468  * @param cls the `struct TALER_EXCHANGE_GetAmlDecisionsHandle`
    469  * @param response_code HTTP response code, 0 on error
    470  * @param response parsed JSON result, NULL on error
    471  */
    472 static void
    473 handle_get_aml_decisions_finished (void *cls,
    474                                    long response_code,
    475                                    const void *response)
    476 {
    477   struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh = cls;
    478   const json_t *j = response;
    479   struct TALER_EXCHANGE_GetAmlDecisionsResponse lr = {
    480     .hr.reply = j,
    481     .hr.http_status = (unsigned int) response_code
    482   };
    483 
    484   adgh->job = NULL;
    485   switch (response_code)
    486   {
    487   case 0:
    488     lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    489     break;
    490   case MHD_HTTP_OK:
    491     if (GNUNET_OK !=
    492         parse_get_aml_decisions_ok (adgh,
    493                                     j))
    494     {
    495       GNUNET_break_op (0);
    496       lr.hr.http_status = 0;
    497       lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    498       break;
    499     }
    500     GNUNET_assert (NULL == adgh->cb);
    501     TALER_EXCHANGE_get_aml_decisions_cancel (adgh);
    502     return;
    503   case MHD_HTTP_NO_CONTENT:
    504     break;
    505   case MHD_HTTP_BAD_REQUEST:
    506 #if DEBUG
    507     json_dumpf (j,
    508                 stderr,
    509                 JSON_INDENT (2));
    510 #endif
    511     lr.hr.ec = TALER_JSON_get_error_code (j);
    512     lr.hr.hint = TALER_JSON_get_error_hint (j);
    513     break;
    514   case MHD_HTTP_FORBIDDEN:
    515     lr.hr.ec = TALER_JSON_get_error_code (j);
    516     lr.hr.hint = TALER_JSON_get_error_hint (j);
    517     break;
    518   case MHD_HTTP_NOT_FOUND:
    519     lr.hr.ec = TALER_JSON_get_error_code (j);
    520     lr.hr.hint = TALER_JSON_get_error_hint (j);
    521     break;
    522   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    523     lr.hr.ec = TALER_JSON_get_error_code (j);
    524     lr.hr.hint = TALER_JSON_get_error_hint (j);
    525     break;
    526   default:
    527     /* unexpected response code */
    528     GNUNET_break_op (0);
    529     lr.hr.ec = TALER_JSON_get_error_code (j);
    530     lr.hr.hint = TALER_JSON_get_error_hint (j);
    531     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    532                 "Unexpected response code %u/%d for GET AML decisions\n",
    533                 (unsigned int) response_code,
    534                 (int) lr.hr.ec);
    535     break;
    536   }
    537   if (NULL != adgh->cb)
    538     adgh->cb (adgh->cb_cls,
    539               &lr);
    540   TALER_EXCHANGE_get_aml_decisions_cancel (adgh);
    541 }
    542 
    543 
    544 struct TALER_EXCHANGE_GetAmlDecisionsHandle *
    545 TALER_EXCHANGE_get_aml_decisions_create (
    546   struct GNUNET_CURL_Context *ctx,
    547   const char *url,
    548   const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
    549 {
    550   struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh;
    551 
    552   adgh = GNUNET_new (struct TALER_EXCHANGE_GetAmlDecisionsHandle);
    553   adgh->ctx = ctx;
    554   adgh->base_url = GNUNET_strdup (url);
    555   adgh->officer_priv = *officer_priv;
    556   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
    557                                       &adgh->officer_pub.eddsa_pub);
    558   adgh->options.limit = -20;
    559   adgh->options.offset = INT64_MAX;
    560   adgh->options.active = TALER_EXCHANGE_YNA_ALL;
    561   adgh->options.investigation = TALER_EXCHANGE_YNA_ALL;
    562   return adgh;
    563 }
    564 
    565 
    566 enum GNUNET_GenericReturnValue
    567 TALER_EXCHANGE_get_aml_decisions_set_options_ (
    568   struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh,
    569   unsigned int num_options,
    570   const struct TALER_EXCHANGE_GetAmlDecisionsOptionValue options[])
    571 {
    572   for (unsigned int i = 0; i < num_options; i++)
    573   {
    574     const struct TALER_EXCHANGE_GetAmlDecisionsOptionValue *opt = &options[i];
    575 
    576     switch (opt->option)
    577     {
    578     case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_END:
    579       return GNUNET_OK;
    580     case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_LIMIT:
    581       adgh->options.limit = opt->details.limit;
    582       break;
    583     case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_OFFSET:
    584       if (opt->details.offset > INT64_MAX)
    585       {
    586         GNUNET_break (0);
    587         return GNUNET_NO;
    588       }
    589       adgh->options.offset = opt->details.offset;
    590       break;
    591     case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_H_PAYTO:
    592       adgh->options.h_payto = opt->details.h_payto;
    593       break;
    594     case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_ACTIVE:
    595       adgh->options.active = opt->details.active;
    596       break;
    597     case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_INVESTIGATION:
    598       adgh->options.investigation = opt->details.investigation;
    599       break;
    600     default:
    601       GNUNET_break (0);
    602       return GNUNET_SYSERR;
    603     }
    604   }
    605   return GNUNET_OK;
    606 }
    607 
    608 
    609 enum TALER_ErrorCode
    610 TALER_EXCHANGE_get_aml_decisions_start (
    611   struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh,
    612   TALER_EXCHANGE_GetAmlDecisionsCallback cb,
    613   TALER_EXCHANGE_GET_AML_DECISIONS_RESULT_CLOSURE *cb_cls)
    614 {
    615   CURL *eh;
    616   char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 32];
    617   struct curl_slist *job_headers = NULL;
    618 
    619   adgh->cb = cb;
    620   adgh->cb_cls = cb_cls;
    621 
    622   /* Build AML officer signature */
    623   TALER_officer_aml_query_sign (&adgh->officer_priv,
    624                                 &adgh->officer_sig);
    625 
    626   /* Build the path component: aml/{officer_pub}/decisions */
    627   {
    628     char pub_str[sizeof (adgh->officer_pub) * 2];
    629     char *end;
    630 
    631     end = GNUNET_STRINGS_data_to_string (
    632       &adgh->officer_pub,
    633       sizeof (adgh->officer_pub),
    634       pub_str,
    635       sizeof (pub_str));
    636     *end = '\0';
    637     GNUNET_snprintf (arg_str,
    638                      sizeof (arg_str),
    639                      "aml/%s/decisions",
    640                      pub_str);
    641   }
    642 
    643   /* Build URL with optional query parameters */
    644   {
    645     char limit_s[24];
    646     char offset_s[24];
    647     char payto_s[sizeof (*adgh->options.h_payto) * 2 + 1];
    648     int64_t limit = adgh->options.limit;
    649     uint64_t offset = adgh->options.offset;
    650     bool omit_limit = (-20 == limit);
    651     bool omit_offset = ( ( (limit < 0) && ((uint64_t) INT64_MAX == offset) ) ||
    652                          ( (limit > 0) && (0 == offset) ) );
    653 
    654     GNUNET_snprintf (limit_s,
    655                      sizeof (limit_s),
    656                      "%lld",
    657                      (long long) limit);
    658     GNUNET_snprintf (offset_s,
    659                      sizeof (offset_s),
    660                      "%llu",
    661                      (unsigned long long) offset);
    662 
    663     if (NULL != adgh->options.h_payto)
    664     {
    665       char *end;
    666 
    667       end = GNUNET_STRINGS_data_to_string (
    668         adgh->options.h_payto,
    669         sizeof (*adgh->options.h_payto),
    670         payto_s,
    671         sizeof (payto_s) - 1);
    672       *end = '\0';
    673     }
    674 
    675     adgh->url = TALER_url_join (
    676       adgh->base_url,
    677       arg_str,
    678       "limit",
    679       omit_limit ? NULL : limit_s,
    680       "offset",
    681       omit_offset ? NULL : offset_s,
    682       "h_payto",
    683       (NULL != adgh->options.h_payto) ? payto_s : NULL,
    684       "active",
    685       (TALER_EXCHANGE_YNA_ALL != adgh->options.active)
    686       ? TALER_yna_to_string (adgh->options.active)
    687       : NULL,
    688       "investigation",
    689       (TALER_EXCHANGE_YNA_ALL != adgh->options.investigation)
    690       ? TALER_yna_to_string (adgh->options.investigation)
    691       : NULL,
    692       NULL);
    693   }
    694 
    695   if (NULL == adgh->url)
    696     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    697 
    698   eh = TALER_EXCHANGE_curl_easy_get_ (adgh->url);
    699   if (NULL == eh)
    700   {
    701     GNUNET_break (0);
    702     GNUNET_free (adgh->url);
    703     adgh->url = NULL;
    704     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    705   }
    706 
    707   /* Build job headers with AML officer signature */
    708   {
    709     char *hdr;
    710     char sig_str[sizeof (adgh->officer_sig) * 2];
    711     char *end;
    712 
    713     end = GNUNET_STRINGS_data_to_string (
    714       &adgh->officer_sig,
    715       sizeof (adgh->officer_sig),
    716       sig_str,
    717       sizeof (sig_str));
    718     *end = '\0';
    719 
    720     GNUNET_asprintf (&hdr,
    721                      "%s: %s",
    722                      TALER_AML_OFFICER_SIGNATURE_HEADER,
    723                      sig_str);
    724     job_headers = curl_slist_append (NULL,
    725                                      hdr);
    726     GNUNET_free (hdr);
    727   }
    728 
    729   adgh->job = GNUNET_CURL_job_add2 (adgh->ctx,
    730                                     eh,
    731                                     job_headers,
    732                                     &handle_get_aml_decisions_finished,
    733                                     adgh);
    734   curl_slist_free_all (job_headers);
    735 
    736   if (NULL == adgh->job)
    737   {
    738     GNUNET_free (adgh->url);
    739     adgh->url = NULL;
    740     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    741   }
    742   return TALER_EC_NONE;
    743 }
    744 
    745 
    746 void
    747 TALER_EXCHANGE_get_aml_decisions_cancel (
    748   struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh)
    749 {
    750   if (NULL != adgh->job)
    751   {
    752     GNUNET_CURL_job_cancel (adgh->job);
    753     adgh->job = NULL;
    754   }
    755   GNUNET_free (adgh->all_rules);
    756   GNUNET_free (adgh->all_mp);
    757   GNUNET_free (adgh->url);
    758   GNUNET_free (adgh->base_url);
    759   GNUNET_free (adgh);
    760 }
    761 
    762 
    763 /* end of exchange_api_get-aml-OFFICER_PUB-decisions.c */