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-measures.c (20911B)


      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-measures.c
     19  * @brief Implementation of the GET /aml/$OFFICER_PUB/measures 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-measures.h"
     27 #include "exchange_api_handle.h"
     28 #include "taler/taler_signatures.h"
     29 #include "exchange_api_curl_defaults.h"
     30 
     31 
     32 /**
     33  * Scrap buffer of temporary arrays.
     34  */
     35 struct Scrap
     36 {
     37   /**
     38    * Kept in DLL.
     39    */
     40   struct Scrap *next;
     41 
     42   /**
     43    * Kept in DLL.
     44    */
     45   struct Scrap *prev;
     46 
     47   /**
     48    * Pointer to our allocation.
     49    */
     50   const char **ptr;
     51 };
     52 
     53 
     54 /**
     55  * @brief A GET /aml/$OFFICER_PUB/measures Handle
     56  */
     57 struct TALER_EXCHANGE_GetAmlMeasuresHandle
     58 {
     59 
     60   /**
     61    * The base URL of the exchange.
     62    */
     63   char *base_url;
     64 
     65   /**
     66    * The full URL for this request, set during _start.
     67    */
     68   char *url;
     69 
     70   /**
     71    * Handle for the request.
     72    */
     73   struct GNUNET_CURL_Job *job;
     74 
     75   /**
     76    * Function to call with the result.
     77    */
     78   TALER_EXCHANGE_GetAmlMeasuresCallback cb;
     79 
     80   /**
     81    * Closure for @e cb.
     82    */
     83   TALER_EXCHANGE_GET_AML_MEASURES_RESULT_CLOSURE *cb_cls;
     84 
     85   /**
     86    * Reference to the execution context.
     87    */
     88   struct GNUNET_CURL_Context *ctx;
     89 
     90   /**
     91    * Public key of the AML officer.
     92    */
     93   struct TALER_AmlOfficerPublicKeyP officer_pub;
     94 
     95   /**
     96    * Private key of the AML officer (for signing).
     97    */
     98   struct TALER_AmlOfficerPrivateKeyP officer_priv;
     99 
    100   /**
    101    * Head of scrap list.
    102    */
    103   struct Scrap *scrap_head;
    104 
    105   /**
    106    * Tail of scrap list.
    107    */
    108   struct Scrap *scrap_tail;
    109 };
    110 
    111 
    112 /**
    113  * Create array of length @a len in scrap book.
    114  *
    115  * @param[in,out] amh context for allocations
    116  * @param len length of array
    117  * @return scrap array
    118  */
    119 static const char **
    120 make_scrap (
    121   struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh,
    122   unsigned int len)
    123 {
    124   struct Scrap *s = GNUNET_new (struct Scrap);
    125 
    126   s->ptr = GNUNET_new_array (len,
    127                              const char *);
    128   GNUNET_CONTAINER_DLL_insert (amh->scrap_head,
    129                                amh->scrap_tail,
    130                                s);
    131   return s->ptr;
    132 }
    133 
    134 
    135 /**
    136  * Free all scrap space.
    137  *
    138  * @param[in,out] amh scrap context
    139  */
    140 static void
    141 free_scrap (struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh)
    142 {
    143   struct Scrap *s;
    144 
    145   while (NULL != (s = amh->scrap_head))
    146   {
    147     GNUNET_CONTAINER_DLL_remove (amh->scrap_head,
    148                                  amh->scrap_tail,
    149                                  s);
    150     GNUNET_free (s->ptr);
    151     GNUNET_free (s);
    152   }
    153 }
    154 
    155 
    156 /**
    157  * Convert JSON array of strings to string array.
    158  *
    159  * @param j JSON array to convert
    160  * @param[out] a array to initialize
    161  * @return true on success
    162  */
    163 static bool
    164 j_to_a (const json_t *j,
    165         const char **a)
    166 {
    167   const json_t *e;
    168   size_t idx;
    169 
    170   json_array_foreach ((json_t *) j, idx, e)
    171   {
    172     if (NULL == (a[idx] = json_string_value (e)))
    173       return false;
    174   }
    175   return true;
    176 }
    177 
    178 
    179 /**
    180  * Parse AML root measures.
    181  *
    182  * @param jroots JSON object with measure data
    183  * @param[out] roots where to write the result
    184  * @return #GNUNET_OK on success
    185  */
    186 static enum GNUNET_GenericReturnValue
    187 parse_aml_roots (
    188   const json_t *jroots,
    189   struct TALER_EXCHANGE_GetAmlMeasuresMeasureInfo *roots)
    190 {
    191   const json_t *obj;
    192   const char *name;
    193   size_t idx = 0;
    194 
    195   json_object_foreach ((json_t *) jroots, name, obj)
    196   {
    197     struct TALER_EXCHANGE_GetAmlMeasuresMeasureInfo *root = &roots[idx++];
    198     struct GNUNET_JSON_Specification spec[] = {
    199       GNUNET_JSON_spec_string ("check_name",
    200                                &root->check_name),
    201       GNUNET_JSON_spec_mark_optional (
    202         GNUNET_JSON_spec_string ("prog_name",
    203                                  &root->prog_name),
    204         NULL),
    205       GNUNET_JSON_spec_mark_optional (
    206         GNUNET_JSON_spec_object_const ("context",
    207                                        &root->context),
    208         NULL),
    209       GNUNET_JSON_spec_mark_optional (
    210         GNUNET_JSON_spec_string ("operation_type",
    211                                  &root->operation_type),
    212         NULL),
    213       GNUNET_JSON_spec_mark_optional (
    214         GNUNET_JSON_spec_bool ("voluntary",
    215                                &root->voluntary),
    216         NULL),
    217       GNUNET_JSON_spec_end ()
    218     };
    219 
    220     if (GNUNET_OK !=
    221         GNUNET_JSON_parse (obj,
    222                            spec,
    223                            NULL,
    224                            NULL))
    225     {
    226       GNUNET_break_op (0);
    227       return GNUNET_SYSERR;
    228     }
    229     root->measure_name = name;
    230   }
    231   return GNUNET_OK;
    232 }
    233 
    234 
    235 /**
    236  * Parse AML programs.
    237  *
    238  * @param[in,out] amh context for allocations
    239  * @param jprogs JSON object with program data
    240  * @param[out] progs where to write the result
    241  * @return #GNUNET_OK on success
    242  */
    243 static enum GNUNET_GenericReturnValue
    244 parse_aml_programs (
    245   struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh,
    246   const json_t *jprogs,
    247   struct TALER_EXCHANGE_GetAmlMeasuresProgramRequirement *progs)
    248 {
    249   const json_t *obj;
    250   const char *name;
    251   size_t idx = 0;
    252 
    253   json_object_foreach ((json_t *) jprogs, name, obj)
    254   {
    255     struct TALER_EXCHANGE_GetAmlMeasuresProgramRequirement *prog = &progs[idx++]
    256     ;
    257     const json_t *jcontext;
    258     const json_t *jinputs;
    259     struct GNUNET_JSON_Specification spec[] = {
    260       GNUNET_JSON_spec_string ("description",
    261                                &prog->description),
    262       GNUNET_JSON_spec_array_const ("context",
    263                                     &jcontext),
    264       GNUNET_JSON_spec_array_const ("inputs",
    265                                     &jinputs),
    266       GNUNET_JSON_spec_end ()
    267     };
    268     unsigned int len;
    269     const char **ptr;
    270 
    271     if (GNUNET_OK !=
    272         GNUNET_JSON_parse (obj,
    273                            spec,
    274                            NULL,
    275                            NULL))
    276     {
    277       GNUNET_break_op (0);
    278       return GNUNET_SYSERR;
    279     }
    280     prog->prog_name = name;
    281     prog->contexts_length = json_array_size (jcontext);
    282     prog->inputs_length = json_array_size (jinputs);
    283     len = (unsigned int) (prog->contexts_length + prog->inputs_length);
    284     if ( ((size_t) len) != prog->contexts_length + prog->inputs_length)
    285     {
    286       GNUNET_break_op (0);
    287       return GNUNET_SYSERR;
    288     }
    289     ptr = make_scrap (amh, len);
    290     prog->contexts = ptr;
    291     if (! j_to_a (jcontext, prog->contexts))
    292     {
    293       GNUNET_break_op (0);
    294       return GNUNET_SYSERR;
    295     }
    296     prog->inputs = &ptr[prog->contexts_length];
    297     if (! j_to_a (jinputs, prog->inputs))
    298     {
    299       GNUNET_break_op (0);
    300       return GNUNET_SYSERR;
    301     }
    302   }
    303   return GNUNET_OK;
    304 }
    305 
    306 
    307 /**
    308  * Parse KYC checks.
    309  *
    310  * @param[in,out] amh context for allocations
    311  * @param jchecks JSON object with check data
    312  * @param[out] checks where to write the result
    313  * @return #GNUNET_OK on success
    314  */
    315 static enum GNUNET_GenericReturnValue
    316 parse_aml_checks (
    317   struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh,
    318   const json_t *jchecks,
    319   struct TALER_EXCHANGE_GetAmlMeasuresCheckInfo *checks)
    320 {
    321   const json_t *obj;
    322   const char *name;
    323   size_t idx = 0;
    324 
    325   json_object_foreach ((json_t *) jchecks, name, obj)
    326   {
    327     struct TALER_EXCHANGE_GetAmlMeasuresCheckInfo *check = &checks[idx++];
    328     const json_t *jrequires;
    329     const json_t *joutputs;
    330     struct GNUNET_JSON_Specification spec[] = {
    331       GNUNET_JSON_spec_string ("description",
    332                                &check->description),
    333       GNUNET_JSON_spec_mark_optional (
    334         GNUNET_JSON_spec_object_const ("description_i18n",
    335                                        &check->description_i18n),
    336         NULL),
    337       GNUNET_JSON_spec_array_const ("requires",
    338                                     &jrequires),
    339       GNUNET_JSON_spec_array_const ("outputs",
    340                                     &joutputs),
    341       GNUNET_JSON_spec_string ("fallback",
    342                                &check->fallback),
    343       GNUNET_JSON_spec_end ()
    344     };
    345     unsigned int len;
    346     const char **ptr;
    347 
    348     if (GNUNET_OK !=
    349         GNUNET_JSON_parse (obj,
    350                            spec,
    351                            NULL,
    352                            NULL))
    353     {
    354       GNUNET_break_op (0);
    355       return GNUNET_SYSERR;
    356     }
    357     check->check_name = name;
    358     check->requires_length = json_array_size (jrequires);
    359     check->outputs_length = json_array_size (joutputs);
    360     len = (unsigned int) (check->requires_length + check->outputs_length);
    361     if ( ((size_t) len) != check->requires_length + check->outputs_length)
    362     {
    363       GNUNET_break_op (0);
    364       return GNUNET_SYSERR;
    365     }
    366     ptr = make_scrap (amh, len);
    367     check->requires = ptr;
    368     if (! j_to_a (jrequires, check->requires))
    369     {
    370       GNUNET_break_op (0);
    371       return GNUNET_SYSERR;
    372     }
    373     check->outputs = &ptr[check->requires_length];
    374     if (! j_to_a (joutputs, check->outputs))
    375     {
    376       GNUNET_break_op (0);
    377       return GNUNET_SYSERR;
    378     }
    379   }
    380   return GNUNET_OK;
    381 }
    382 
    383 
    384 /**
    385  * Parse default KYC rules from the default_rules array.
    386  *
    387  * @param[in,out] amh context for allocations
    388  * @param jrules JSON array with rule data
    389  * @param[out] rules where to write the result
    390  * @return #GNUNET_OK on success
    391  */
    392 static enum GNUNET_GenericReturnValue
    393 parse_default_rules (
    394   struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh,
    395   const json_t *jrules,
    396   struct TALER_EXCHANGE_GetAmlMeasuresKycRule *rules)
    397 {
    398   const json_t *obj;
    399   size_t idx;
    400 
    401   json_array_foreach ((json_t *) jrules, idx, obj)
    402   {
    403     struct TALER_EXCHANGE_GetAmlMeasuresKycRule *rule = &rules[idx];
    404     const json_t *jmeasures;
    405     struct GNUNET_JSON_Specification spec[] = {
    406       TALER_JSON_spec_kycte ("operation_type",
    407                              &rule->operation_type),
    408       GNUNET_JSON_spec_mark_optional (
    409         GNUNET_JSON_spec_string ("rule_name",
    410                                  &rule->rule_name),
    411         NULL),
    412       TALER_JSON_spec_amount_any ("threshold",
    413                                   &rule->threshold),
    414       GNUNET_JSON_spec_relative_time ("timeframe",
    415                                       &rule->timeframe),
    416       GNUNET_JSON_spec_array_const ("measures",
    417                                     &jmeasures),
    418       GNUNET_JSON_spec_mark_optional (
    419         GNUNET_JSON_spec_bool ("exposed",
    420                                &rule->exposed),
    421         NULL),
    422       GNUNET_JSON_spec_mark_optional (
    423         GNUNET_JSON_spec_bool ("is_and_combinator",
    424                                &rule->is_and_combinator),
    425         NULL),
    426       GNUNET_JSON_spec_int64 ("display_priority",
    427                               &rule->display_priority),
    428       GNUNET_JSON_spec_end ()
    429     };
    430 
    431     if (GNUNET_OK !=
    432         GNUNET_JSON_parse (obj,
    433                            spec,
    434                            NULL,
    435                            NULL))
    436     {
    437       GNUNET_break_op (0);
    438       return GNUNET_SYSERR;
    439     }
    440     rule->measures_length = json_array_size (jmeasures);
    441     {
    442       const char **ptr = make_scrap (amh,
    443                                      (unsigned int) rule->measures_length);
    444 
    445       rule->measures = ptr;
    446       if (! j_to_a (jmeasures,
    447                     ptr))
    448       {
    449         GNUNET_break_op (0);
    450         return GNUNET_SYSERR;
    451       }
    452     }
    453   }
    454   return GNUNET_OK;
    455 }
    456 
    457 
    458 /**
    459  * Parse the provided measures data from the "200 OK" response.
    460  *
    461  * @param[in,out] amh handle (callback may be zero'ed out)
    462  * @param json json reply with the data
    463  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
    464  */
    465 static enum GNUNET_GenericReturnValue
    466 parse_get_aml_measures_ok (struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh,
    467                            const json_t *json)
    468 {
    469   struct TALER_EXCHANGE_GetAmlMeasuresResponse lr = {
    470     .hr.reply = json,
    471     .hr.http_status = MHD_HTTP_OK
    472   };
    473   const json_t *jroots;
    474   const json_t *jprograms;
    475   const json_t *jchecks;
    476   const json_t *jdefault_rules = NULL;
    477   struct GNUNET_JSON_Specification spec[] = {
    478     GNUNET_JSON_spec_object_const ("roots",
    479                                    &jroots),
    480     GNUNET_JSON_spec_object_const ("programs",
    481                                    &jprograms),
    482     GNUNET_JSON_spec_object_const ("checks",
    483                                    &jchecks),
    484     GNUNET_JSON_spec_mark_optional (
    485       GNUNET_JSON_spec_array_const ("default_rules",
    486                                     &jdefault_rules),
    487       NULL),
    488     GNUNET_JSON_spec_end ()
    489   };
    490 
    491   if (GNUNET_OK !=
    492       GNUNET_JSON_parse (json,
    493                          spec,
    494                          NULL,
    495                          NULL))
    496   {
    497     GNUNET_break_op (0);
    498     return GNUNET_SYSERR;
    499   }
    500 
    501   lr.details.ok.roots_length = json_object_size (jroots);
    502   lr.details.ok.programs_length = json_object_size (jprograms);
    503   lr.details.ok.checks_length = json_object_size (jchecks);
    504   lr.details.ok.default_rules_length =
    505     (NULL != jdefault_rules) ? json_array_size (jdefault_rules) : 0;
    506 
    507   {
    508     struct TALER_EXCHANGE_GetAmlMeasuresMeasureInfo *roots;
    509     struct TALER_EXCHANGE_GetAmlMeasuresProgramRequirement *progs;
    510     struct TALER_EXCHANGE_GetAmlMeasuresCheckInfo *checks;
    511     struct TALER_EXCHANGE_GetAmlMeasuresKycRule *drules;
    512     enum GNUNET_GenericReturnValue ret;
    513 
    514     if ( (lr.details.ok.roots_length >
    515           GNUNET_MAX_MALLOC_CHECKED / sizeof (*roots)) ||
    516          (lr.details.ok.programs_length >
    517           GNUNET_MAX_MALLOC_CHECKED / sizeof (*progs)) ||
    518          (lr.details.ok.checks_length >
    519           GNUNET_MAX_MALLOC_CHECKED / sizeof (*checks)) ||
    520          (lr.details.ok.default_rules_length >
    521           GNUNET_MAX_MALLOC_CHECKED / sizeof (*drules)) )
    522     {
    523       GNUNET_break_op (0);
    524       return GNUNET_SYSERR;
    525     }
    526     roots = GNUNET_new_array (
    527       GNUNET_NZL (lr.details.ok.roots_length),
    528       struct TALER_EXCHANGE_GetAmlMeasuresMeasureInfo);
    529     progs = GNUNET_new_array (
    530       GNUNET_NZL (lr.details.ok.programs_length),
    531       struct TALER_EXCHANGE_GetAmlMeasuresProgramRequirement);
    532     checks = GNUNET_new_array (
    533       GNUNET_NZL (lr.details.ok.checks_length),
    534       struct TALER_EXCHANGE_GetAmlMeasuresCheckInfo);
    535     drules = GNUNET_new_array (
    536       GNUNET_NZL (lr.details.ok.default_rules_length),
    537       struct TALER_EXCHANGE_GetAmlMeasuresKycRule);
    538     lr.details.ok.roots = roots;
    539     lr.details.ok.programs = progs;
    540     lr.details.ok.checks = checks;
    541     lr.details.ok.default_rules = drules;
    542 
    543     ret = parse_aml_roots (jroots, roots);
    544     if (GNUNET_OK == ret)
    545       ret = parse_aml_programs (amh, jprograms, progs);
    546     if (GNUNET_OK == ret)
    547       ret = parse_aml_checks (amh, jchecks, checks);
    548     if ( (GNUNET_OK == ret) && (NULL != jdefault_rules) )
    549       ret = parse_default_rules (amh, jdefault_rules, drules);
    550     if (GNUNET_OK == ret)
    551     {
    552       amh->cb (amh->cb_cls,
    553                &lr);
    554       amh->cb = NULL;
    555     }
    556     GNUNET_free (roots);
    557     GNUNET_free (progs);
    558     GNUNET_free (checks);
    559     GNUNET_free (drules);
    560     free_scrap (amh);
    561     return ret;
    562   }
    563 }
    564 
    565 
    566 /**
    567  * Function called when we're done processing the
    568  * HTTP GET /aml/$OFFICER_PUB/measures request.
    569  *
    570  * @param cls the `struct TALER_EXCHANGE_GetAmlMeasuresHandle`
    571  * @param response_code HTTP response code, 0 on error
    572  * @param response parsed JSON result, NULL on error
    573  */
    574 static void
    575 handle_get_aml_measures_finished (void *cls,
    576                                   long response_code,
    577                                   const void *response)
    578 {
    579   struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh = cls;
    580   const json_t *j = response;
    581   struct TALER_EXCHANGE_GetAmlMeasuresResponse lr = {
    582     .hr.reply = j,
    583     .hr.http_status = (unsigned int) response_code
    584   };
    585 
    586   amh->job = NULL;
    587   switch (response_code)
    588   {
    589   case 0:
    590     lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    591     break;
    592   case MHD_HTTP_OK:
    593     if (GNUNET_OK !=
    594         parse_get_aml_measures_ok (amh,
    595                                    j))
    596     {
    597       GNUNET_break_op (0);
    598       lr.hr.http_status = 0;
    599       lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    600       break;
    601     }
    602     GNUNET_assert (NULL == amh->cb);
    603     TALER_EXCHANGE_get_aml_measures_cancel (amh);
    604     return;
    605   case MHD_HTTP_NO_CONTENT:
    606     break;
    607   case MHD_HTTP_BAD_REQUEST:
    608     lr.hr.ec = TALER_JSON_get_error_code (j);
    609     lr.hr.hint = TALER_JSON_get_error_hint (j);
    610     break;
    611   case MHD_HTTP_FORBIDDEN:
    612     lr.hr.ec = TALER_JSON_get_error_code (j);
    613     lr.hr.hint = TALER_JSON_get_error_hint (j);
    614     break;
    615   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    616     lr.hr.ec = TALER_JSON_get_error_code (j);
    617     lr.hr.hint = TALER_JSON_get_error_hint (j);
    618     break;
    619   default:
    620     /* unexpected response code */
    621     GNUNET_break_op (0);
    622     lr.hr.ec = TALER_JSON_get_error_code (j);
    623     lr.hr.hint = TALER_JSON_get_error_hint (j);
    624     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    625                 "Unexpected response code %u/%d for GET AML measures\n",
    626                 (unsigned int) response_code,
    627                 (int) lr.hr.ec);
    628     break;
    629   }
    630   if (NULL != amh->cb)
    631     amh->cb (amh->cb_cls,
    632              &lr);
    633   TALER_EXCHANGE_get_aml_measures_cancel (amh);
    634 }
    635 
    636 
    637 struct TALER_EXCHANGE_GetAmlMeasuresHandle *
    638 TALER_EXCHANGE_get_aml_measures_create (
    639   struct GNUNET_CURL_Context *ctx,
    640   const char *url,
    641   const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
    642 {
    643   struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh;
    644 
    645   amh = GNUNET_new (struct TALER_EXCHANGE_GetAmlMeasuresHandle);
    646   amh->ctx = ctx;
    647   amh->base_url = GNUNET_strdup (url);
    648   amh->officer_priv = *officer_priv;
    649   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
    650                                       &amh->officer_pub.eddsa_pub);
    651   return amh;
    652 }
    653 
    654 
    655 enum TALER_ErrorCode
    656 TALER_EXCHANGE_get_aml_measures_start (
    657   struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh,
    658   TALER_EXCHANGE_GetAmlMeasuresCallback cb,
    659   TALER_EXCHANGE_GET_AML_MEASURES_RESULT_CLOSURE *cb_cls)
    660 {
    661   CURL *eh;
    662   struct TALER_AmlOfficerSignatureP officer_sig;
    663   char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 32];
    664   struct curl_slist *job_headers = NULL;
    665 
    666   amh->cb = cb;
    667   amh->cb_cls = cb_cls;
    668 
    669   /* Build AML officer signature */
    670   TALER_officer_aml_query_sign (&amh->officer_priv,
    671                                 &officer_sig);
    672 
    673   /* Build the path component: aml/{officer_pub}/measures */
    674   {
    675     char pub_str[sizeof (amh->officer_pub) * 2];
    676     char *end;
    677 
    678     end = GNUNET_STRINGS_data_to_string (
    679       &amh->officer_pub,
    680       sizeof (amh->officer_pub),
    681       pub_str,
    682       sizeof (pub_str));
    683     *end = '\0';
    684     GNUNET_snprintf (arg_str,
    685                      sizeof (arg_str),
    686                      "aml/%s/measures",
    687                      pub_str);
    688   }
    689 
    690   amh->url = TALER_url_join (amh->base_url,
    691                              arg_str,
    692                              NULL);
    693   if (NULL == amh->url)
    694     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    695 
    696   eh = TALER_EXCHANGE_curl_easy_get_ (amh->url);
    697   if (NULL == eh)
    698   {
    699     GNUNET_break (0);
    700     GNUNET_free (amh->url);
    701     amh->url = NULL;
    702     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    703   }
    704 
    705   /* Build job headers with AML officer signature */
    706   {
    707     char *hdr;
    708     char sig_str[sizeof (officer_sig) * 2];
    709     char *end;
    710 
    711     end = GNUNET_STRINGS_data_to_string (
    712       &officer_sig,
    713       sizeof (officer_sig),
    714       sig_str,
    715       sizeof (sig_str));
    716     *end = '\0';
    717 
    718     GNUNET_asprintf (&hdr,
    719                      "%s: %s",
    720                      TALER_AML_OFFICER_SIGNATURE_HEADER,
    721                      sig_str);
    722     job_headers = curl_slist_append (NULL,
    723                                      hdr);
    724     GNUNET_free (hdr);
    725   }
    726 
    727   amh->job = GNUNET_CURL_job_add2 (amh->ctx,
    728                                    eh,
    729                                    job_headers,
    730                                    &handle_get_aml_measures_finished,
    731                                    amh);
    732   curl_slist_free_all (job_headers);
    733 
    734   if (NULL == amh->job)
    735   {
    736     GNUNET_free (amh->url);
    737     amh->url = NULL;
    738     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    739   }
    740   return TALER_EC_NONE;
    741 }
    742 
    743 
    744 void
    745 TALER_EXCHANGE_get_aml_measures_cancel (
    746   struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh)
    747 {
    748   if (NULL != amh->job)
    749   {
    750     GNUNET_CURL_job_cancel (amh->job);
    751     amh->job = NULL;
    752   }
    753   free_scrap (amh);
    754   GNUNET_free (amh->url);
    755   GNUNET_free (amh->base_url);
    756   GNUNET_free (amh);
    757 }
    758 
    759 
    760 /* end of exchange_api_get-aml-OFFICER_PUB-measures.c */