exchange

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

taler-exchange-kyc-tester.c (51734B)


      1 /*
      2    This file is part of TALER
      3    Copyright (C) 2022, 2024 Taler Systems SA
      4 
      5    TALER is free software; you can redistribute it and/or modify it under the
      6    terms of the GNU Affero 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 Affero General Public License for more details.
     12 
     13    You should have received a copy of the GNU Affero General Public License along with
     14    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 /**
     17  * @file taler-exchange-kyc-tester.c
     18  * @brief tool to test KYC integrations
     19  * @author Christian Grothoff
     20  * @defgroup request Request handling routines
     21  */
     22 #include "taler/platform.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <jansson.h>
     25 #include <microhttpd.h>
     26 #include <sched.h>
     27 #include <sys/resource.h>
     28 #include <limits.h>
     29 #include "taler/taler_mhd_lib.h"
     30 #include "taler/taler_json_lib.h"
     31 #include "taler/taler_templating_lib.h"
     32 #include "taler/taler_util.h"
     33 #include "taler/taler_kyclogic_lib.h"
     34 #include "taler/taler_kyclogic_plugin.h"
     35 #include <gnunet/gnunet_mhd_compat.h>
     36 
     37 
     38 /**
     39  * @brief Context in which the exchange is processing
     40  *        all requests
     41  */
     42 struct TEKT_RequestContext
     43 {
     44 
     45   /**
     46    * Opaque parsing context.
     47    */
     48   void *opaque_post_parsing_context;
     49 
     50   /**
     51    * Request handler responsible for this request.
     52    */
     53   const struct TEKT_RequestHandler *rh;
     54 
     55   /**
     56    * Request URL (for logging).
     57    */
     58   const char *url;
     59 
     60   /**
     61    * Connection we are processing.
     62    */
     63   struct MHD_Connection *connection;
     64 
     65   /**
     66    * HTTP response to return (or NULL).
     67    */
     68   struct MHD_Response *response;
     69 
     70   /**
     71    * @e rh-specific cleanup routine. Function called
     72    * upon completion of the request that should
     73    * clean up @a rh_ctx. Can be NULL.
     74    */
     75   void
     76   (*rh_cleaner)(struct TEKT_RequestContext *rc);
     77 
     78   /**
     79    * @e rh-specific context. Place where the request
     80    * handler can associate state with this request.
     81    * Can be NULL.
     82    */
     83   void *rh_ctx;
     84 
     85   /**
     86    * Uploaded JSON body, if any.
     87    */
     88   json_t *root;
     89 
     90   /**
     91    * HTTP status to return upon resume if @e response
     92    * is non-NULL.
     93    */
     94   unsigned int http_status;
     95 
     96 };
     97 
     98 
     99 /**
    100  * @brief Struct describing an URL and the handler for it.
    101  */
    102 struct TEKT_RequestHandler
    103 {
    104 
    105   /**
    106    * URL the handler is for (first part only).
    107    */
    108   const char *url;
    109 
    110   /**
    111    * Method the handler is for.
    112    */
    113   const char *method;
    114 
    115   /**
    116    * Callbacks for handling of the request. Which one is used
    117    * depends on @e method.
    118    */
    119   union
    120   {
    121     /**
    122      * Function to call to handle a GET requests (and those
    123      * with @e method NULL).
    124      *
    125      * @param rc context for the request
    126      * @param mime_type the @e mime_type for the reply (hint, can be NULL)
    127      * @param args array of arguments, needs to be of length @e args_expected
    128      * @return MHD result code
    129      */
    130     MHD_RESULT
    131     (*get)(struct TEKT_RequestContext *rc,
    132            const char *const args[]);
    133 
    134 
    135     /**
    136      * Function to call to handle a POST request.
    137      *
    138      * @param rc context for the request
    139      * @param json uploaded JSON data
    140      * @param args array of arguments, needs to be of length @e args_expected
    141      * @return MHD result code
    142      */
    143     MHD_RESULT
    144     (*post)(struct TEKT_RequestContext *rc,
    145             const json_t *root,
    146             const char *const args[]);
    147 
    148   } handler;
    149 
    150   /**
    151    * Number of arguments this handler expects in the @a args array.
    152    */
    153   unsigned int nargs;
    154 
    155   /**
    156    * Is the number of arguments given in @e nargs only an upper bound,
    157    * and calling with fewer arguments could be OK?
    158    */
    159   bool nargs_is_upper_bound;
    160 
    161   /**
    162    * Mime type to use in reply (hint, can be NULL).
    163    */
    164   const char *mime_type;
    165 
    166   /**
    167    * Raw data for the @e handler, can be NULL for none provided.
    168    */
    169   const void *data;
    170 
    171   /**
    172    * Number of bytes in @e data, 0 for data is 0-terminated (!).
    173    */
    174   size_t data_size;
    175 
    176   /**
    177    * Default response code. 0 for none provided.
    178    */
    179   unsigned int response_code;
    180 };
    181 
    182 
    183 /**
    184  * Information we track per ongoing kyc-proof request.
    185  */
    186 struct ProofRequestState
    187 {
    188   /**
    189    * Kept in a DLL.
    190    */
    191   struct ProofRequestState *next;
    192 
    193   /**
    194    * Kept in a DLL.
    195    */
    196   struct ProofRequestState *prev;
    197 
    198   /**
    199    * Handle for operation with the plugin.
    200    */
    201   struct TALER_KYCLOGIC_ProofHandle *ph;
    202 
    203   /**
    204    * Logic plugin we are using.
    205    */
    206   struct TALER_KYCLOGIC_Plugin *logic;
    207 
    208   /**
    209    * HTTP request details.
    210    */
    211   struct TEKT_RequestContext *rc;
    212 
    213 };
    214 
    215 /**
    216  * Head of DLL.
    217  */
    218 static struct ProofRequestState *rs_head;
    219 
    220 /**
    221  * Tail of DLL.
    222  */
    223 static struct ProofRequestState *rs_tail;
    224 
    225 /**
    226  * The exchange's configuration (global)
    227  */
    228 static const struct GNUNET_CONFIGURATION_Handle *TEKT_cfg;
    229 
    230 /**
    231  * Our base URL.
    232  */
    233 static char *TEKT_base_url;
    234 
    235 /**
    236  * Payto set via command-line (or otherwise random).
    237  */
    238 static struct TALER_NormalizedPaytoHashP cmd_line_h_payto;
    239 
    240 /**
    241  * Provider user ID to use.
    242  */
    243 static char *cmd_provider_user_id;
    244 
    245 /**
    246  * Provider legitimization ID to use.
    247  */
    248 static char *cmd_provider_legitimization_id;
    249 
    250 /**
    251  * Custom legitimization rule in JSON given as
    252  * a string.
    253  */
    254 static char *lrs_s;
    255 
    256 /**
    257  * Type of the operation that triggers legitimization.
    258  */
    259 static char *operation_s;
    260 
    261 /**
    262  * Amount threshold crossed that triggers some rule.
    263  */
    264 static struct TALER_Amount trigger_amount;
    265 
    266 /**
    267  * Row ID to use, override with '-r'
    268  */
    269 static unsigned int kyc_row_id = 42;
    270 
    271 /**
    272  * -P command-line option.
    273  */
    274 static int print_h_payto;
    275 
    276 /**
    277  * -W command-line option.
    278  */
    279 static int cmd_line_is_wallet;
    280 
    281 /**
    282  * -w command-line option.
    283  */
    284 static int run_webservice;
    285 
    286 /**
    287  * -M command-line option.
    288  */
    289 static int list_measures;
    290 
    291 /**
    292  * Value to return from main()
    293  */
    294 static int global_ret;
    295 
    296 /**
    297  * -m command-line flag.
    298  */
    299 static char *measure;
    300 
    301 /**
    302  * Handle for ongoing initiation operation.
    303  */
    304 static struct TALER_KYCLOGIC_InitiateHandle *ih;
    305 
    306 /**
    307  * KYC logic running for @e ih.
    308  */
    309 static struct TALER_KYCLOGIC_Plugin *ih_logic;
    310 
    311 /**
    312  * True if we started any daemon.
    313  */
    314 static bool have_daemons; uint16_t serve_port;
    315 
    316 /**
    317  * Context for all CURL operations (useful to the event loop)
    318  */
    319 static struct GNUNET_CURL_Context *TEKT_curl_ctx;
    320 
    321 /**
    322  * Context for integrating #TEKT_curl_ctx with the
    323  * GNUnet event loop.
    324  */
    325 static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc;
    326 
    327 
    328 /**
    329  * Context for the webhook.
    330  */
    331 struct KycWebhookContext
    332 {
    333 
    334   /**
    335    * Kept in a DLL while suspended.
    336    */
    337   struct KycWebhookContext *next;
    338 
    339   /**
    340    * Kept in a DLL while suspended.
    341    */
    342   struct KycWebhookContext *prev;
    343 
    344   /**
    345    * Details about the connection we are processing.
    346    */
    347   struct TEKT_RequestContext *rc;
    348 
    349   /**
    350    * Plugin responsible for the webhook.
    351    */
    352   struct TALER_KYCLOGIC_Plugin *plugin;
    353 
    354   /**
    355    * Configuration for the specific action.
    356    */
    357   struct TALER_KYCLOGIC_ProviderDetails *pd;
    358 
    359   /**
    360    * Webhook activity.
    361    */
    362   struct TALER_KYCLOGIC_WebhookHandle *wh;
    363 
    364   /**
    365    * HTTP response to return.
    366    */
    367   struct MHD_Response *response;
    368 
    369   /**
    370    * Name of the configuration
    371    * section defining the KYC logic.
    372    */
    373   const char *section_name;
    374 
    375   /**
    376    * HTTP response code to return.
    377    */
    378   unsigned int response_code;
    379 
    380   /**
    381    * #GNUNET_YES if we are suspended,
    382    * #GNUNET_NO if not.
    383    * #GNUNET_SYSERR if we had some error.
    384    */
    385   enum GNUNET_GenericReturnValue suspended;
    386 
    387 };
    388 
    389 
    390 /**
    391  * Contexts are kept in a DLL while suspended.
    392  */
    393 static struct KycWebhookContext *kwh_head;
    394 
    395 /**
    396  * Contexts are kept in a DLL while suspended.
    397  */
    398 static struct KycWebhookContext *kwh_tail;
    399 
    400 
    401 /**
    402  * Resume processing the @a kwh request.
    403  *
    404  * @param kwh request to resume
    405  */
    406 static void
    407 kwh_resume (struct KycWebhookContext *kwh)
    408 {
    409   GNUNET_assert (GNUNET_YES == kwh->suspended);
    410   kwh->suspended = GNUNET_NO;
    411   GNUNET_CONTAINER_DLL_remove (kwh_head,
    412                                kwh_tail,
    413                                kwh);
    414   MHD_resume_connection (kwh->rc->connection);
    415 }
    416 
    417 
    418 static void
    419 kyc_webhook_cleanup (void)
    420 {
    421   struct KycWebhookContext *kwh;
    422 
    423   while (NULL != (kwh = kwh_head))
    424   {
    425     if (NULL != kwh->wh)
    426     {
    427       kwh->plugin->webhook_cancel (kwh->wh);
    428       kwh->wh = NULL;
    429     }
    430     kwh_resume (kwh);
    431   }
    432 }
    433 
    434 
    435 /**
    436  * Function called with the result of a webhook
    437  * operation.
    438  *
    439  * Note that the "decref" for the @a response
    440  * will be done by the plugin.
    441  *
    442  * @param cls closure
    443  * @param process_row legitimization process request the webhook was about
    444  * @param account_id account the webhook was about
    445  * @param is_wallet true if @a account_id is for a wallet
    446  * @param provider_section configuration section of the logic
    447  * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
    448  * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
    449  * @param status KYC status
    450  * @param expiration until when is the KYC check valid
    451  * @param attributes user attributes returned by the provider
    452  * @param http_status HTTP status code of @a response
    453  * @param[in] response to return to the HTTP client
    454  */
    455 static void
    456 webhook_finished_cb (
    457   void *cls,
    458   uint64_t process_row,
    459   const struct TALER_NormalizedPaytoHashP *account_id,
    460   bool is_wallet,
    461   const char *provider_section,
    462   const char *provider_user_id,
    463   const char *provider_legitimization_id,
    464   enum TALER_KYCLOGIC_KycStatus status,
    465   struct GNUNET_TIME_Absolute expiration,
    466   const json_t *attributes,
    467   unsigned int http_status,
    468   struct MHD_Response *response)
    469 {
    470   struct KycWebhookContext *kwh = cls;
    471 
    472   (void) expiration;
    473   (void) provider_section;
    474   kwh->wh = NULL;
    475   GNUNET_break (is_wallet);
    476   if ( (NULL != account_id) &&
    477        (0 != GNUNET_memcmp (account_id,
    478                             &cmd_line_h_payto)) )
    479   {
    480     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    481                 "Received webhook for unexpected account\n");
    482   }
    483   if ( (NULL != provider_user_id) &&
    484        (NULL != cmd_provider_user_id) &&
    485        (0 != strcmp (provider_user_id,
    486                      cmd_provider_user_id)) )
    487   {
    488     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    489                 "Received webhook for unexpected provider user ID (%s)\n",
    490                 provider_user_id);
    491   }
    492   if ( (NULL != provider_legitimization_id) &&
    493        (NULL != cmd_provider_legitimization_id) &&
    494        (0 != strcmp (provider_legitimization_id,
    495                      cmd_provider_legitimization_id)) )
    496   {
    497     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    498                 "Received webhook for unexpected provider legitimization ID (%s)\n",
    499                 provider_legitimization_id);
    500   }
    501   switch (status)
    502   {
    503   case TALER_KYCLOGIC_STATUS_SUCCESS:
    504     /* _successfully_ resumed case */
    505     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    506                 "KYC successful for user `%s' (legi: %s)\n",
    507                 provider_user_id,
    508                 provider_legitimization_id);
    509     GNUNET_break (NULL != attributes);
    510     fprintf (stderr,
    511              "Extracted attributes:\n");
    512     json_dumpf (attributes,
    513                 stderr,
    514                 JSON_INDENT (2));
    515     break;
    516   default:
    517     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    518                 "KYC status of %s/%s (process #%llu) is %d\n",
    519                 provider_user_id,
    520                 provider_legitimization_id,
    521                 (unsigned long long) process_row,
    522                 status);
    523     break;
    524   }
    525   kwh->response = response;
    526   kwh->response_code = http_status;
    527   kwh_resume (kwh);
    528   TALER_MHD_daemon_trigger ();
    529 }
    530 
    531 
    532 /**
    533  * Function called to clean up a context.
    534  *
    535  * @param rc request context
    536  */
    537 static void
    538 clean_kwh (struct TEKT_RequestContext *rc)
    539 {
    540   struct KycWebhookContext *kwh = rc->rh_ctx;
    541 
    542   if (NULL != kwh->wh)
    543   {
    544     kwh->plugin->webhook_cancel (kwh->wh);
    545     kwh->wh = NULL;
    546   }
    547   if (NULL != kwh->response)
    548   {
    549     MHD_destroy_response (kwh->response);
    550     kwh->response = NULL;
    551   }
    552   GNUNET_free (kwh);
    553 }
    554 
    555 
    556 /**
    557  * Function the plugin can use to lookup an
    558  * @a h_payto by @a provider_legitimization_id.
    559  *
    560  * @param cls closure, NULL
    561  * @param provider_section
    562  * @param provider_legitimization_id legi to look up
    563  * @param[out] h_payto where to write the result
    564  * @param[out] is_wallet set to true if @a h_payto is for a wallet
    565  * @param[out] legi_row where to write the row ID for the legitimization ID
    566  * @return database transaction status
    567  */
    568 static enum GNUNET_DB_QueryStatus
    569 kyc_provider_account_lookup (
    570   void *cls,
    571   const char *provider_section,
    572   const char *provider_legitimization_id,
    573   struct TALER_NormalizedPaytoHashP *h_payto,
    574   bool *is_wallet,
    575   uint64_t *legi_row)
    576 {
    577   (void) cls;
    578   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    579               "Simulated account lookup using `%s/%s'\n",
    580               provider_section,
    581               provider_legitimization_id);
    582   *h_payto = cmd_line_h_payto;
    583   *legi_row = kyc_row_id;
    584   *is_wallet = (0 != cmd_line_is_wallet);
    585   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    586 }
    587 
    588 
    589 /**
    590  * Handle a (GET or POST) "/kyc-webhook" request.
    591  *
    592  * @param rc request to handle
    593  * @param method HTTP request method used by the client
    594  * @param root uploaded JSON body (can be NULL)
    595  * @param args one argument with the legitimization_uuid
    596  * @return MHD result code
    597  */
    598 static MHD_RESULT
    599 handler_kyc_webhook_generic (
    600   struct TEKT_RequestContext *rc,
    601   const char *method,
    602   const json_t *root,
    603   const char *const args[])
    604 {
    605   struct KycWebhookContext *kwh = rc->rh_ctx;
    606 
    607   if (NULL == kwh)
    608   { /* first time */
    609     kwh = GNUNET_new (struct KycWebhookContext);
    610     kwh->rc = rc;
    611     rc->rh_ctx = kwh;
    612     rc->rh_cleaner = &clean_kwh;
    613 
    614     if ( (NULL == args[0]) ||
    615          (GNUNET_OK !=
    616           TALER_KYCLOGIC_lookup_logic (args[0],
    617                                        &kwh->plugin,
    618                                        &kwh->pd,
    619                                        &kwh->section_name)) )
    620     {
    621       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    622                   "KYC logic `%s' unknown (check KYC provider configuration)\n",
    623                   args[0]);
    624       return TALER_MHD_reply_with_error (rc->connection,
    625                                          MHD_HTTP_NOT_FOUND,
    626                                          TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
    627                                          args[0]);
    628     }
    629     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    630                 "Calling KYC provider specific webhook\n");
    631     kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
    632                                     kwh->pd,
    633                                     &kyc_provider_account_lookup,
    634                                     NULL,
    635                                     method,
    636                                     &args[1],
    637                                     rc->connection,
    638                                     root,
    639                                     &webhook_finished_cb,
    640                                     kwh);
    641     if (NULL == kwh->wh)
    642     {
    643       GNUNET_break_op (0);
    644       return TALER_MHD_reply_with_error (rc->connection,
    645                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    646                                          TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    647                                          "failed to run webhook logic");
    648     }
    649     kwh->suspended = GNUNET_YES;
    650     GNUNET_CONTAINER_DLL_insert (kwh_head,
    651                                  kwh_tail,
    652                                  kwh);
    653     MHD_suspend_connection (rc->connection);
    654     return MHD_YES;
    655   }
    656 
    657   if (NULL != kwh->response)
    658   {
    659     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    660                 "Returning queued reply for KWH\n");
    661     /* handle _failed_ resumed cases */
    662     return MHD_queue_response (rc->connection,
    663                                kwh->response_code,
    664                                kwh->response);
    665   }
    666 
    667   /* We resumed, but got no response? This should
    668      not happen. */
    669   GNUNET_assert (0);
    670   return TALER_MHD_reply_with_error (rc->connection,
    671                                      MHD_HTTP_INTERNAL_SERVER_ERROR,
    672                                      TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    673                                      "resumed without response");
    674 }
    675 
    676 
    677 /**
    678  * Handle a GET "/kyc-webhook" request.
    679  *
    680  * @param rc request to handle
    681  * @param args one argument with the legitimization_uuid
    682  * @return MHD result code
    683  */
    684 static MHD_RESULT
    685 handler_kyc_webhook_get (
    686   struct TEKT_RequestContext *rc,
    687   const char *const args[])
    688 {
    689   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    690               "Webhook GET triggered\n");
    691   return handler_kyc_webhook_generic (rc,
    692                                       MHD_HTTP_METHOD_GET,
    693                                       NULL,
    694                                       args);
    695 }
    696 
    697 
    698 /**
    699  * Handle a POST "/kyc-webhook" request.
    700  *
    701  * @param rc request to handle
    702  * @param root uploaded JSON body (can be NULL)
    703  * @param args one argument with the legitimization_uuid
    704  * @return MHD result code
    705  */
    706 static MHD_RESULT
    707 handler_kyc_webhook_post (
    708   struct TEKT_RequestContext *rc,
    709   const json_t *root,
    710   const char *const args[])
    711 {
    712   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    713               "Webhook POST triggered\n");
    714   return handler_kyc_webhook_generic (rc,
    715                                       MHD_HTTP_METHOD_POST,
    716                                       root,
    717                                       args);
    718 }
    719 
    720 
    721 /**
    722  * Function called with the result of a proof check operation.
    723  *
    724  * Note that the "decref" for the @a response
    725  * will be done by the callee and MUST NOT be done by the plugin.
    726  *
    727  * @param cls closure with the `struct ProofRequestState`
    728  * @param status KYC status
    729  * @param provider_name name of the KYC provider
    730  * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
    731  * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
    732  * @param expiration until when is the KYC check valid
    733  * @param attributes attributes about the user
    734  * @param http_status HTTP status code of @a response
    735  * @param[in] response to return to the HTTP client
    736  */
    737 static void
    738 proof_cb (
    739   void *cls,
    740   enum TALER_KYCLOGIC_KycStatus status,
    741   const char *provider_name,
    742   const char *provider_user_id,
    743   const char *provider_legitimization_id,
    744   struct GNUNET_TIME_Absolute expiration,
    745   const json_t *attributes,
    746   unsigned int http_status,
    747   struct MHD_Response *response)
    748 {
    749   struct ProofRequestState *rs = cls;
    750 
    751   (void) expiration;
    752   (void) provider_name;
    753   GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    754               "KYC legitimization %s completed with status %d (%u) for %s\n",
    755               provider_legitimization_id,
    756               status,
    757               http_status,
    758               provider_user_id);
    759   if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
    760   {
    761     GNUNET_break (NULL != attributes);
    762     fprintf (stderr,
    763              "Extracted attributes:\n");
    764     json_dumpf (attributes,
    765                 stderr,
    766                 JSON_INDENT (2));
    767   }
    768   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    769               "Returning response %p with status %u\n",
    770               response,
    771               http_status);
    772   rs->rc->response = response;
    773   rs->rc->http_status = http_status;
    774   GNUNET_CONTAINER_DLL_remove (rs_head,
    775                                rs_tail,
    776                                rs);
    777   MHD_resume_connection (rs->rc->connection);
    778   TALER_MHD_daemon_trigger ();
    779   GNUNET_free (rs);
    780 }
    781 
    782 
    783 /**
    784  * Function called when we receive a 'GET' to the
    785  * '/kyc-proof' endpoint.
    786  *
    787  * @param rc request context
    788  * @param args remaining URL arguments;
    789  *        args[0] should be the logic plugin name
    790  */
    791 static MHD_RESULT
    792 handler_kyc_proof_get (
    793   struct TEKT_RequestContext *rc,
    794   const char *const args[1])
    795 {
    796   struct TALER_NormalizedPaytoHashP h_payto;
    797   struct TALER_KYCLOGIC_ProviderDetails *pd;
    798   struct TALER_KYCLOGIC_Plugin *logic;
    799   struct ProofRequestState *rs;
    800   const char *section_name;
    801   const char *h_paytos;
    802 
    803   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    804               "GET /kyc-proof triggered\n");
    805   if (NULL == args[0])
    806   {
    807     GNUNET_break_op (0);
    808     return TALER_MHD_reply_with_error (rc->connection,
    809                                        MHD_HTTP_NOT_FOUND,
    810                                        TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    811                                        "'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required");
    812   }
    813   h_paytos = MHD_lookup_connection_value (rc->connection,
    814                                           MHD_GET_ARGUMENT_KIND,
    815                                           "state");
    816   if (NULL == h_paytos)
    817   {
    818     GNUNET_break_op (0);
    819     return TALER_MHD_reply_with_error (rc->connection,
    820                                        MHD_HTTP_BAD_REQUEST,
    821                                        TALER_EC_GENERIC_PARAMETER_MISSING,
    822                                        "h_payto");
    823   }
    824   if (GNUNET_OK !=
    825       GNUNET_STRINGS_string_to_data (h_paytos,
    826                                      strlen (h_paytos),
    827                                      &h_payto,
    828                                      sizeof (h_payto)))
    829   {
    830     GNUNET_break_op (0);
    831     return TALER_MHD_reply_with_error (rc->connection,
    832                                        MHD_HTTP_BAD_REQUEST,
    833                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    834                                        "h_payto");
    835   }
    836   if (0 !=
    837       GNUNET_memcmp (&h_payto,
    838                      &cmd_line_h_payto))
    839   {
    840     GNUNET_break_op (0);
    841     return TALER_MHD_reply_with_error (rc->connection,
    842                                        MHD_HTTP_NOT_FOUND,
    843                                        TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
    844                                        "h_payto");
    845   }
    846 
    847   if (GNUNET_OK !=
    848       TALER_KYCLOGIC_lookup_logic (args[0],
    849                                    &logic,
    850                                    &pd,
    851                                    &section_name))
    852   {
    853     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    854                 "Could not initiate KYC with provider `%s' (configuration error?)\n",
    855                 args[0]);
    856     return TALER_MHD_reply_with_error (rc->connection,
    857                                        MHD_HTTP_NOT_FOUND,
    858                                        TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
    859                                        args[0]);
    860   }
    861   rs = GNUNET_new (struct ProofRequestState);
    862   rs->rc = rc;
    863   rs->logic = logic;
    864   MHD_suspend_connection (rc->connection);
    865   GNUNET_CONTAINER_DLL_insert (rs_head,
    866                                rs_tail,
    867                                rs);
    868   rs->ph = logic->proof (logic->cls,
    869                          pd,
    870                          rc->connection,
    871                          &h_payto,
    872                          kyc_row_id,
    873                          cmd_provider_user_id,
    874                          cmd_provider_legitimization_id,
    875                          &proof_cb,
    876                          rs);
    877   GNUNET_assert (NULL != rs->ph);
    878   return MHD_YES;
    879 }
    880 
    881 
    882 /**
    883  * Function called whenever MHD is done with a request.  If the
    884  * request was a POST, we may have stored a `struct Buffer *` in the
    885  * @a con_cls that might still need to be cleaned up.  Call the
    886  * respective function to free the memory.
    887  *
    888  * @param cls client-defined closure
    889  * @param connection connection handle
    890  * @param con_cls value as set by the last call to
    891  *        the #MHD_AccessHandlerCallback
    892  * @param toe reason for request termination
    893  * @see #MHD_OPTION_NOTIFY_COMPLETED
    894  * @ingroup request
    895  */
    896 static void
    897 handle_mhd_completion_callback (void *cls,
    898                                 struct MHD_Connection *connection,
    899                                 void **con_cls,
    900                                 enum MHD_RequestTerminationCode toe)
    901 {
    902   struct TEKT_RequestContext *rc = *con_cls;
    903 
    904   (void) cls;
    905   if (NULL == rc)
    906     return;
    907   if (NULL != rc->rh_cleaner)
    908     rc->rh_cleaner (rc);
    909   {
    910 #if MHD_VERSION >= 0x00097304
    911     const union MHD_ConnectionInfo *ci;
    912     unsigned int http_status = 0;
    913 
    914     ci = MHD_get_connection_info (connection,
    915                                   MHD_CONNECTION_INFO_HTTP_STATUS);
    916     if (NULL != ci)
    917       http_status = ci->http_status;
    918     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    919                 "Request for `%s' completed with HTTP status %u (%d)\n",
    920                 rc->url,
    921                 http_status,
    922                 toe);
    923 #else
    924     (void) connection;
    925     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    926                 "Request for `%s' completed (%d)\n",
    927                 rc->url,
    928                 toe);
    929 #endif
    930   }
    931 
    932   TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context);
    933   /* Sanity-check that we didn't leave any transactions hanging */
    934   if (NULL != rc->root)
    935     json_decref (rc->root);
    936   GNUNET_free (rc);
    937   *con_cls = NULL;
    938 }
    939 
    940 
    941 /**
    942  * We found a request handler responsible for handling a request. Parse the
    943  * @a upload_data (if applicable) and the @a url and call the
    944  * handler.
    945  *
    946  * @param rc request context
    947  * @param url rest of the URL to parse
    948  * @param upload_data upload data to parse (if available)
    949  * @param[in,out] upload_data_size number of bytes in @a upload_data
    950  * @return MHD result code
    951  */
    952 static MHD_RESULT
    953 proceed_with_handler (struct TEKT_RequestContext *rc,
    954                       const char *url,
    955                       const char *upload_data,
    956                       size_t *upload_data_size)
    957 {
    958   const struct TEKT_RequestHandler *rh = rc->rh;
    959   const char *args[rh->nargs + 2];
    960   size_t ulen = strlen (url) + 1;
    961   MHD_RESULT ret;
    962 
    963   /* We do check for "ulen" here, because we'll later stack-allocate a buffer
    964      of that size and don't want to enable malicious clients to cause us
    965      huge stack allocations. */
    966   if (ulen > 512)
    967   {
    968     /* 512 is simply "big enough", as it is bigger than "6 * 54",
    969        which is the longest URL format we ever get (for
    970        /deposits/).  The value should be adjusted if we ever define protocol
    971        endpoints with plausibly longer inputs.  */
    972     GNUNET_break_op (0);
    973     return TALER_MHD_reply_with_error (
    974       rc->connection,
    975       MHD_HTTP_URI_TOO_LONG,
    976       TALER_EC_GENERIC_URI_TOO_LONG,
    977       url);
    978   }
    979 
    980   /* All POST endpoints come with a body in JSON format. So we parse
    981      the JSON here. */
    982   if ( (NULL == rc->root) &&
    983        (0 == strcasecmp (rh->method,
    984                          MHD_HTTP_METHOD_POST)) )
    985   {
    986     enum GNUNET_GenericReturnValue res;
    987 
    988     res = TALER_MHD_parse_post_json (
    989       rc->connection,
    990       &rc->opaque_post_parsing_context,
    991       upload_data,
    992       upload_data_size,
    993       &rc->root);
    994     if (GNUNET_SYSERR == res)
    995     {
    996       GNUNET_assert (NULL == rc->root);
    997       GNUNET_break (0);
    998       return MHD_NO; /* bad upload, could not even generate error */
    999     }
   1000     if ( (GNUNET_NO == res) ||
   1001          (NULL == rc->root) )
   1002     {
   1003       GNUNET_assert (NULL == rc->root);
   1004       return MHD_YES; /* so far incomplete upload or parser error */
   1005     }
   1006   }
   1007 
   1008   {
   1009     char d[ulen];
   1010     unsigned int i;
   1011     char *sp;
   1012 
   1013     /* Parse command-line arguments */
   1014     /* make a copy of 'url' because 'strtok_r()' will modify */
   1015     GNUNET_memcpy (d,
   1016                    url,
   1017                    ulen);
   1018     i = 0;
   1019     args[i++] = strtok_r (d, "/", &sp);
   1020     while ( (NULL != args[i - 1]) &&
   1021             (i <= rh->nargs + 1) )
   1022       args[i++] = strtok_r (NULL, "/", &sp);
   1023     /* make sure above loop ran nicely until completion, and also
   1024        that there is no excess data in 'd' afterwards */
   1025     if ( ( (rh->nargs_is_upper_bound) &&
   1026            (i - 1 > rh->nargs) ) ||
   1027          ( (! rh->nargs_is_upper_bound) &&
   1028            (i - 1 != rh->nargs) ) )
   1029     {
   1030       char emsg[128 + 512];
   1031 
   1032       GNUNET_snprintf (emsg,
   1033                        sizeof (emsg),
   1034                        "Got %u+/%u segments for `%s' request (`%s')",
   1035                        i - 1,
   1036                        rh->nargs,
   1037                        rh->url,
   1038                        url);
   1039       GNUNET_break_op (0);
   1040       return TALER_MHD_reply_with_error (
   1041         rc->connection,
   1042         MHD_HTTP_NOT_FOUND,
   1043         TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
   1044         emsg);
   1045     }
   1046     GNUNET_assert (NULL == args[i - 1]);
   1047 
   1048     /* Above logic ensures that 'root' is exactly non-NULL for POST operations,
   1049        so we test for 'root' to decide which handler to invoke. */
   1050     if (NULL != rc->root)
   1051       ret = rh->handler.post (rc,
   1052                               rc->root,
   1053                               args);
   1054     else /* We also only have "POST" or "GET" in the API for at this point
   1055       (OPTIONS/HEAD are taken care of earlier) */
   1056       ret = rh->handler.get (rc,
   1057                              args);
   1058   }
   1059   return ret;
   1060 }
   1061 
   1062 
   1063 static void
   1064 rh_cleaner_cb (struct TEKT_RequestContext *rc)
   1065 {
   1066   if (NULL != rc->response)
   1067   {
   1068     MHD_destroy_response (rc->response);
   1069     rc->response = NULL;
   1070   }
   1071   if (NULL != rc->root)
   1072   {
   1073     json_decref (rc->root);
   1074     rc->root = NULL;
   1075   }
   1076 }
   1077 
   1078 
   1079 /**
   1080  * Handle incoming HTTP request.
   1081  *
   1082  * @param cls closure for MHD daemon (unused)
   1083  * @param connection the connection
   1084  * @param url the requested url
   1085  * @param method the method (POST, GET, ...)
   1086  * @param version HTTP version (ignored)
   1087  * @param upload_data request data
   1088  * @param upload_data_size size of @a upload_data in bytes
   1089  * @param con_cls closure for request (a `struct TEKT_RequestContext *`)
   1090  * @return MHD result code
   1091  */
   1092 static MHD_RESULT
   1093 handle_mhd_request (void *cls,
   1094                     struct MHD_Connection *connection,
   1095                     const char *url,
   1096                     const char *method,
   1097                     const char *version,
   1098                     const char *upload_data,
   1099                     size_t *upload_data_size,
   1100                     void **con_cls)
   1101 {
   1102   static struct TEKT_RequestHandler handlers[] = {
   1103     /* simulated KYC endpoints */
   1104     {
   1105       .url = "kyc-proof",
   1106       .method = MHD_HTTP_METHOD_GET,
   1107       .handler.get = &handler_kyc_proof_get,
   1108       .nargs = 1
   1109     },
   1110     {
   1111       .url = "kyc-webhook",
   1112       .method = MHD_HTTP_METHOD_POST,
   1113       .handler.post = &handler_kyc_webhook_post,
   1114       .nargs = 128,
   1115       .nargs_is_upper_bound = true
   1116     },
   1117     {
   1118       .url = "kyc-webhook",
   1119       .method = MHD_HTTP_METHOD_GET,
   1120       .handler.get = &handler_kyc_webhook_get,
   1121       .nargs = 128,
   1122       .nargs_is_upper_bound = true
   1123     },
   1124     /* mark end of list */
   1125     {
   1126       .url = NULL
   1127     }
   1128   };
   1129   struct TEKT_RequestContext *rc = *con_cls;
   1130 
   1131   (void) cls;
   1132   (void) version;
   1133   if (NULL == rc)
   1134   {
   1135     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1136                 "Handling new request\n");
   1137     /* We're in a new async scope! */
   1138     rc = *con_cls = GNUNET_new (struct TEKT_RequestContext);
   1139     rc->url = url;
   1140     rc->connection = connection;
   1141     rc->rh_cleaner = &rh_cleaner_cb;
   1142   }
   1143   if (NULL != rc->response)
   1144   {
   1145     return MHD_queue_response (rc->connection,
   1146                                rc->http_status,
   1147                                rc->response);
   1148   }
   1149 
   1150   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1151               "Handling request (%s) for URL '%s'\n",
   1152               method,
   1153               url);
   1154   /* on repeated requests, check our cache first */
   1155   if (NULL != rc->rh)
   1156   {
   1157     const char *start;
   1158 
   1159     if ('\0' == url[0])
   1160       /* strange, should start with '/', treat as just "/" */
   1161       url = "/";
   1162     start = strchr (url + 1, '/');
   1163     if (NULL == start)
   1164       start = "";
   1165     return proceed_with_handler (rc,
   1166                                  start,
   1167                                  upload_data,
   1168                                  upload_data_size);
   1169   }
   1170   if (0 == strcasecmp (method,
   1171                        MHD_HTTP_METHOD_HEAD))
   1172     method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
   1173 
   1174   /* parse first part of URL */
   1175   {
   1176     bool found = false;
   1177     size_t tok_size;
   1178     const char *tok;
   1179     const char *rest;
   1180 
   1181     if ('\0' == url[0])
   1182       /* strange, should start with '/', treat as just "/" */
   1183       url = "/";
   1184     tok = url + 1;
   1185     rest = strchr (tok, '/');
   1186     if (NULL == rest)
   1187     {
   1188       tok_size = strlen (tok);
   1189     }
   1190     else
   1191     {
   1192       tok_size = rest - tok;
   1193       rest++; /* skip over '/' */
   1194     }
   1195     for (unsigned int i = 0; NULL != handlers[i].url; i++)
   1196     {
   1197       struct TEKT_RequestHandler *rh = &handlers[i];
   1198 
   1199       if ( (0 != strncmp (tok,
   1200                           rh->url,
   1201                           tok_size)) ||
   1202            (tok_size != strlen (rh->url) ) )
   1203         continue;
   1204       found = true;
   1205       /* The URL is a match!  What we now do depends on the method. */
   1206       if (0 == strcasecmp (method,
   1207                            MHD_HTTP_METHOD_OPTIONS))
   1208       {
   1209         return TALER_MHD_reply_cors_preflight (connection);
   1210       }
   1211       GNUNET_assert (NULL != rh->method);
   1212       if (0 != strcasecmp (method,
   1213                            rh->method))
   1214       {
   1215         found = true;
   1216         continue;
   1217       }
   1218       /* cache to avoid the loop next time */
   1219       rc->rh = rh;
   1220       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1221                   "Handler found for %s '%s'\n",
   1222                   method,
   1223                   url);
   1224       return MHD_YES;
   1225     }
   1226 
   1227     if (found)
   1228     {
   1229       /* we found a matching address, but the method is wrong */
   1230       struct MHD_Response *reply;
   1231       MHD_RESULT ret;
   1232       char *allowed = NULL;
   1233 
   1234       GNUNET_break_op (0);
   1235       for (unsigned int i = 0; NULL != handlers[i].url; i++)
   1236       {
   1237         struct TEKT_RequestHandler *rh = &handlers[i];
   1238 
   1239         if ( (0 != strncmp (tok,
   1240                             rh->url,
   1241                             tok_size)) ||
   1242              (tok_size != strlen (rh->url) ) )
   1243           continue;
   1244         if (NULL == allowed)
   1245         {
   1246           allowed = GNUNET_strdup (rh->method);
   1247         }
   1248         else
   1249         {
   1250           char *tmp;
   1251 
   1252           GNUNET_asprintf (&tmp,
   1253                            "%s, %s",
   1254                            allowed,
   1255                            rh->method);
   1256           GNUNET_free (allowed);
   1257           allowed = tmp;
   1258         }
   1259         if (0 == strcasecmp (rh->method,
   1260                              MHD_HTTP_METHOD_GET))
   1261         {
   1262           char *tmp;
   1263 
   1264           GNUNET_asprintf (&tmp,
   1265                            "%s, %s",
   1266                            allowed,
   1267                            MHD_HTTP_METHOD_HEAD);
   1268           GNUNET_free (allowed);
   1269           allowed = tmp;
   1270         }
   1271       }
   1272       reply = TALER_MHD_make_error (
   1273         TALER_EC_GENERIC_METHOD_INVALID,
   1274         method);
   1275       GNUNET_break (
   1276         MHD_YES ==
   1277         MHD_add_response_header (reply,
   1278                                  MHD_HTTP_HEADER_ALLOW,
   1279                                  allowed));
   1280       GNUNET_free (allowed);
   1281       ret = MHD_queue_response (connection,
   1282                                 MHD_HTTP_METHOD_NOT_ALLOWED,
   1283                                 reply);
   1284       MHD_destroy_response (reply);
   1285       return ret;
   1286     }
   1287   }
   1288 
   1289   /* No handler matches, generate not found */
   1290   return TALER_MHD_reply_with_error (connection,
   1291                                      MHD_HTTP_NOT_FOUND,
   1292                                      TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
   1293                                      url);
   1294 }
   1295 
   1296 
   1297 /**
   1298  * Load configuration parameters for the exchange
   1299  * server into the corresponding global variables.
   1300  *
   1301  * @return #GNUNET_OK on success
   1302  */
   1303 static enum GNUNET_GenericReturnValue
   1304 exchange_serve_process_config (void)
   1305 {
   1306   if (GNUNET_OK !=
   1307       GNUNET_CONFIGURATION_get_value_string (TEKT_cfg,
   1308                                              "exchange",
   1309                                              "BASE_URL",
   1310                                              &TEKT_base_url))
   1311   {
   1312     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1313                                "exchange",
   1314                                "BASE_URL");
   1315     return GNUNET_SYSERR;
   1316   }
   1317   if (! TALER_url_valid_charset (TEKT_base_url))
   1318   {
   1319     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1320                                "exchange",
   1321                                "BASE_URL",
   1322                                "invalid URL");
   1323     return GNUNET_SYSERR;
   1324   }
   1325 
   1326   return GNUNET_OK;
   1327 }
   1328 
   1329 
   1330 /**
   1331  * Function run on shutdown.
   1332  *
   1333  * @param cls NULL
   1334  */
   1335 static void
   1336 do_shutdown (void *cls)
   1337 {
   1338   struct ProofRequestState *rs;
   1339 
   1340   (void) cls;
   1341   while (NULL != (rs = rs_head))
   1342   {
   1343     GNUNET_CONTAINER_DLL_remove (rs_head,
   1344                                  rs_tail,
   1345                                  rs);
   1346     rs->logic->proof_cancel (rs->ph);
   1347     MHD_resume_connection (rs->rc->connection);
   1348     GNUNET_free (rs);
   1349   }
   1350   if (NULL != ih)
   1351   {
   1352     ih_logic->initiate_cancel (ih);
   1353     ih = NULL;
   1354   }
   1355   kyc_webhook_cleanup ();
   1356   TALER_KYCLOGIC_kyc_done ();
   1357   TALER_MHD_daemons_halt ();
   1358   TALER_MHD_daemons_destroy ();
   1359   if (NULL != TEKT_curl_ctx)
   1360   {
   1361     GNUNET_CURL_fini (TEKT_curl_ctx);
   1362     TEKT_curl_ctx = NULL;
   1363   }
   1364   if (NULL != exchange_curl_rc)
   1365   {
   1366     GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc);
   1367     exchange_curl_rc = NULL;
   1368   }
   1369   TALER_TEMPLATING_done ();
   1370 }
   1371 
   1372 
   1373 /**
   1374  * Function called with the result of a KYC initiation
   1375  * operation.
   1376  *
   1377  * @param cls closure
   1378  * @param ec #TALER_EC_NONE on success
   1379  * @param redirect_url set to where to redirect the user on success, NULL on failure
   1380  * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
   1381  * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
   1382  * @param error_msg_hint set to additional details to return to user, NULL on success
   1383  */
   1384 static void
   1385 initiate_cb (
   1386   void *cls,
   1387   enum TALER_ErrorCode ec,
   1388   const char *redirect_url,
   1389   const char *provider_user_id,
   1390   const char *provider_legitimization_id,
   1391   const char *error_msg_hint)
   1392 {
   1393   (void) cls;
   1394   ih = NULL;
   1395   if (TALER_EC_NONE != ec)
   1396   {
   1397     fprintf (stderr,
   1398              "Failed to start KYC process: %s (#%d)\n",
   1399              error_msg_hint,
   1400              ec);
   1401     global_ret = EXIT_FAILURE;
   1402     GNUNET_SCHEDULER_shutdown ();
   1403     return;
   1404   }
   1405   {
   1406     char *s;
   1407 
   1408     s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto,
   1409                                              sizeof (cmd_line_h_payto));
   1410     if (NULL != provider_user_id)
   1411     {
   1412       fprintf (stdout,
   1413                "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -u '%s' -U '%s' -p %s\n",
   1414                redirect_url,
   1415                provider_user_id,
   1416                provider_legitimization_id,
   1417                s);
   1418     }
   1419     else
   1420     {
   1421       fprintf (stdout,
   1422                "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -U '%s' -p %s\n",
   1423                redirect_url,
   1424                provider_legitimization_id,
   1425                s);
   1426     }
   1427     GNUNET_free (s);
   1428   }
   1429   GNUNET_free (cmd_provider_user_id);
   1430   GNUNET_free (cmd_provider_legitimization_id);
   1431   if (NULL != provider_user_id)
   1432     cmd_provider_user_id = GNUNET_strdup (provider_user_id);
   1433   if (NULL != provider_legitimization_id)
   1434     cmd_provider_legitimization_id = GNUNET_strdup (provider_legitimization_id);
   1435   if (! run_webservice)
   1436     GNUNET_SCHEDULER_shutdown ();
   1437 }
   1438 
   1439 
   1440 /**
   1441  * Function called to iterate over KYC-relevant
   1442  * transaction amounts for a particular time range.
   1443  * Called within a database transaction, so must
   1444  * not start a new one.
   1445  *
   1446  * @param cls closure, identifies the event type and
   1447  *        account to iterate over events for
   1448  * @param limit maximum time-range for which events
   1449  *        should be fetched (timestamp in the past)
   1450  * @param cb function to call on each event found,
   1451  *        events must be returned in reverse chronological
   1452  *        order
   1453  * @param cb_cls closure for @a cb
   1454  * @return transaction status
   1455  */
   1456 static enum GNUNET_DB_QueryStatus
   1457 amount_iterator (
   1458   void *cls,
   1459   struct GNUNET_TIME_Absolute limit,
   1460   TALER_EXCHANGEDB_KycAmountCallback cb,
   1461   void *cb_cls)
   1462 {
   1463   const struct TALER_Amount *amount = cls;
   1464   struct GNUNET_TIME_Absolute date;
   1465   enum GNUNET_GenericReturnValue ret;
   1466 
   1467   date = GNUNET_TIME_absolute_subtract (limit,
   1468                                         GNUNET_TIME_UNIT_SECONDS);
   1469 
   1470   ret = cb (cb_cls,
   1471             amount,
   1472             date);
   1473   GNUNET_break (GNUNET_SYSERR != ret);
   1474   if (GNUNET_OK != ret)
   1475     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
   1476   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1477 }
   1478 
   1479 
   1480 /**
   1481  * Callback invoked on every listen socket to start the
   1482  * respective MHD HTTP daemon.
   1483  *
   1484  * @param cls unused
   1485  * @param lsock the listen socket
   1486  */
   1487 static void
   1488 start_daemon (void *cls,
   1489               int lsock)
   1490 {
   1491   struct MHD_Daemon *mhd;
   1492 
   1493   (void) cls;
   1494   GNUNET_assert (-1 != lsock);
   1495   mhd = MHD_start_daemon (
   1496     MHD_USE_SUSPEND_RESUME
   1497     | MHD_USE_PIPE_FOR_SHUTDOWN
   1498     | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
   1499     | MHD_USE_TCP_FASTOPEN,
   1500     0,
   1501     NULL, NULL,
   1502     &handle_mhd_request, NULL,
   1503     MHD_OPTION_LISTEN_SOCKET,
   1504     lsock,
   1505     MHD_OPTION_EXTERNAL_LOGGER,
   1506     &TALER_MHD_handle_logs,
   1507     NULL,
   1508     MHD_OPTION_NOTIFY_COMPLETED,
   1509     &handle_mhd_completion_callback,
   1510     NULL,
   1511     MHD_OPTION_END);
   1512   if (NULL == mhd)
   1513   {
   1514     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1515                 "Failed to launch HTTP service. Is the port in use?\n");
   1516     GNUNET_SCHEDULER_shutdown ();
   1517     return;
   1518   }
   1519   have_daemons = true;
   1520   TALER_MHD_daemon_start (mhd);
   1521 }
   1522 
   1523 
   1524 /**
   1525  * Main function that will be run by the scheduler.
   1526  *
   1527  * @param cls closure
   1528  * @param args remaining command-line arguments
   1529  * @param cfgfile name of the configuration file used (for saving, can be
   1530  *        NULL!)
   1531  * @param config configuration
   1532  */
   1533 static void
   1534 run (void *cls,
   1535      char *const *args,
   1536      const char *cfgfile,
   1537      const struct GNUNET_CONFIGURATION_Handle *config)
   1538 {
   1539   enum TALER_KYCLOGIC_KycTriggerEvent event;
   1540   struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL;
   1541   const struct TALER_KYCLOGIC_KycRule *rule = NULL;
   1542 
   1543   (void) cls;
   1544   (void) args;
   1545   (void ) cfgfile;
   1546   if (GNUNET_OK !=
   1547       TALER_TEMPLATING_init (TALER_EXCHANGE_project_data ()))
   1548   {
   1549     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1550                 "Could not load templates. Installation broken.\n");
   1551     global_ret = EXIT_FAILURE;
   1552     return;
   1553   }
   1554   if (NULL != operation_s)
   1555   {
   1556     if (GNUNET_OK !=
   1557         TALER_KYCLOGIC_kyc_trigger_from_string (operation_s,
   1558                                                 &event))
   1559     {
   1560       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1561                   "Malformed operation type `%s'\n",
   1562                   operation_s);
   1563       global_ret = EXIT_FAILURE;
   1564       return;
   1565     }
   1566   }
   1567 
   1568   if (NULL != lrs_s)
   1569   {
   1570     json_t *jlrs;
   1571     json_error_t err;
   1572 
   1573     jlrs = json_loads (lrs_s,
   1574                        JSON_REJECT_DUPLICATES,
   1575                        &err);
   1576     if (NULL == jlrs)
   1577     {
   1578       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1579                   "Malformed JSON for legitimization rule set: %s at %d\n",
   1580                   err.text,
   1581                   err.position);
   1582       global_ret = EXIT_INVALIDARGUMENT;
   1583       return;
   1584     }
   1585     lrs = TALER_KYCLOGIC_rules_parse (jlrs);
   1586     json_decref (jlrs);
   1587     if (NULL == lrs)
   1588     {
   1589       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1590                   "Malformed legitimization rule set `%s'\n",
   1591                   lrs_s);
   1592       global_ret = EXIT_INVALIDARGUMENT;
   1593       return;
   1594     }
   1595   }
   1596 
   1597   if (print_h_payto)
   1598   {
   1599     char *s;
   1600 
   1601     s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto,
   1602                                              sizeof (cmd_line_h_payto));
   1603     fprintf (stdout,
   1604              "%s\n",
   1605              s);
   1606     GNUNET_free (s);
   1607   }
   1608   TALER_MHD_setup (TALER_MHD_GO_NONE);
   1609   TEKT_cfg = config;
   1610   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1611                                  NULL);
   1612   if (GNUNET_OK !=
   1613       TALER_KYCLOGIC_kyc_init (config,
   1614                                cfgfile))
   1615   {
   1616     global_ret = EXIT_NOTCONFIGURED;
   1617     GNUNET_SCHEDULER_shutdown ();
   1618     return;
   1619   }
   1620   if (GNUNET_OK !=
   1621       exchange_serve_process_config ())
   1622   {
   1623     global_ret = EXIT_NOTCONFIGURED;
   1624     GNUNET_SCHEDULER_shutdown ();
   1625     return;
   1626   }
   1627   global_ret = EXIT_SUCCESS;
   1628   if (NULL != operation_s)
   1629   {
   1630     enum GNUNET_DB_QueryStatus qs;
   1631 
   1632     if (GNUNET_OK ==
   1633         TALER_amount_is_valid (&trigger_amount))
   1634     {
   1635       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1636                   "Trigger amount command-line option (-t) required\n");
   1637       global_ret = EXIT_INVALIDARGUMENT;
   1638       GNUNET_SCHEDULER_shutdown ();
   1639       return;
   1640     }
   1641     {
   1642       struct TALER_Amount next_threshold;
   1643 
   1644       qs = TALER_KYCLOGIC_kyc_test_required (
   1645         event,
   1646         lrs,
   1647         &amount_iterator,
   1648         &trigger_amount,
   1649         &rule,
   1650         &next_threshold);
   1651     }
   1652     switch (qs)
   1653     {
   1654     case GNUNET_DB_STATUS_HARD_ERROR:
   1655       GNUNET_break (0);
   1656       global_ret = EXIT_NOTCONFIGURED;
   1657       GNUNET_SCHEDULER_shutdown ();
   1658       return;
   1659     case GNUNET_DB_STATUS_SOFT_ERROR:
   1660       GNUNET_break (0);
   1661       global_ret = EXIT_NOTCONFIGURED;
   1662       GNUNET_SCHEDULER_shutdown ();
   1663       return;
   1664     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
   1665       fprintf (stdout,
   1666                "KYC not required for the given operation type and amount\n");
   1667       global_ret = EXIT_SUCCESS;
   1668       GNUNET_SCHEDULER_shutdown ();
   1669       return;
   1670     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
   1671       break;
   1672     }
   1673   }
   1674 
   1675   if (NULL != rule)
   1676   {
   1677     struct TALER_KYCLOGIC_KycCheckContext kcc;
   1678 
   1679     if (0 != list_measures)
   1680     {
   1681       // FIXME: print rule with possible measures!
   1682       GNUNET_break (0);
   1683       global_ret = EXIT_SUCCESS;
   1684       GNUNET_SCHEDULER_shutdown ();
   1685       return;
   1686     }
   1687 
   1688     if (GNUNET_OK !=
   1689         TALER_KYCLOGIC_requirements_to_check (lrs,
   1690                                               rule,
   1691                                               measure,
   1692                                               &kcc))
   1693     {
   1694       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1695                   "Could not initiate KYC for measure `%s' (configuration error?)\n",
   1696                   measure);
   1697       global_ret = EXIT_NOTCONFIGURED;
   1698       GNUNET_SCHEDULER_shutdown ();
   1699       return;
   1700     }
   1701     if (NULL == kcc.check)
   1702     {
   1703       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1704                   "SKIP check selected, nothing to do here\n");
   1705       global_ret = EXIT_SUCCESS;
   1706       GNUNET_SCHEDULER_shutdown ();
   1707       return;
   1708     }
   1709     switch (kcc.check->type)
   1710     {
   1711     case TALER_KYCLOGIC_CT_INFO:
   1712       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1713                   "KYC information is `%s'\n",
   1714                   kcc.check->description);
   1715       break;
   1716     case TALER_KYCLOGIC_CT_FORM:
   1717       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1718                   "Would initiate KYC check `%s' with form `%s'\n",
   1719                   kcc.check->check_name,
   1720                   kcc.check->details.form.name);
   1721       break;
   1722     case TALER_KYCLOGIC_CT_LINK:
   1723       {
   1724         struct TALER_KYCLOGIC_ProviderDetails *pd;
   1725         const char *provider_name;
   1726 
   1727         TALER_KYCLOGIC_provider_to_logic (
   1728           kcc.check->details.link.provider,
   1729           &ih_logic,
   1730           &pd,
   1731           &provider_name);
   1732         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1733                     "Initiating KYC check `%s' at provider `%s'\n",
   1734                     kcc.check->check_name,
   1735                     provider_name);
   1736         ih = ih_logic->initiate (ih_logic->cls,
   1737                                  pd,
   1738                                  &cmd_line_h_payto,
   1739                                  kyc_row_id,
   1740                                  NULL, /* FIXME: support passing context*/
   1741                                  &initiate_cb,
   1742                                  NULL);
   1743         GNUNET_break (NULL != ih);
   1744         break;
   1745       }
   1746     }
   1747   }
   1748   if (run_webservice)
   1749   {
   1750     enum GNUNET_GenericReturnValue ret;
   1751 
   1752     TEKT_curl_ctx
   1753       = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1754                           &exchange_curl_rc);
   1755     if (NULL == TEKT_curl_ctx)
   1756     {
   1757       GNUNET_break (0);
   1758       global_ret = EXIT_FAILURE;
   1759       GNUNET_SCHEDULER_shutdown ();
   1760       return;
   1761     }
   1762     exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEKT_curl_ctx);
   1763     ret = TALER_MHD_listen_bind (TEKT_cfg,
   1764                                  "exchange",
   1765                                  &start_daemon,
   1766                                  NULL);
   1767     switch (ret)
   1768     {
   1769     case GNUNET_SYSERR:
   1770       global_ret = EXIT_NOTCONFIGURED;
   1771       GNUNET_SCHEDULER_shutdown ();
   1772       return;
   1773     case GNUNET_NO:
   1774       if (! have_daemons)
   1775       {
   1776         global_ret = EXIT_NOTCONFIGURED;
   1777         GNUNET_SCHEDULER_shutdown ();
   1778         return;
   1779       }
   1780       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1781                   "Could not open all configured listen sockets\n");
   1782       break;
   1783     case GNUNET_OK:
   1784       break;
   1785     }
   1786   }
   1787 }
   1788 
   1789 
   1790 /**
   1791  * The main function of the taler-exchange-kyc-tester, a tool for
   1792  * testing KYC processes.
   1793  *
   1794  * @param argc number of arguments from the command line
   1795  * @param argv command line arguments
   1796  * @return 0 ok, non-zero on error
   1797  */
   1798 int
   1799 main (int argc,
   1800       char *const *argv)
   1801 {
   1802   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1803     GNUNET_GETOPT_option_help (
   1804       TALER_EXCHANGE_project_data (),
   1805       "tool to test KYC provider integrations"),
   1806     GNUNET_GETOPT_option_flag (
   1807       'M',
   1808       "list-measures",
   1809       "list available measures",
   1810       &list_measures),
   1811     GNUNET_GETOPT_option_string (
   1812       'm',
   1813       "measure",
   1814       "MEASURE_NAME",
   1815       "initiate KYC check for the selected measure",
   1816       &measure),
   1817     GNUNET_GETOPT_option_string (
   1818       'o',
   1819       "operation",
   1820       "OPERATION_TYPE",
   1821       "name of the operation that triggers legitimization (WITHDRAW, DEPOSIT, etc.)",
   1822       &operation_s),
   1823     GNUNET_GETOPT_option_flag (
   1824       'P',
   1825       "print-payto-hash",
   1826       "output the hash of the (normalized) payto://-URI",
   1827       &print_h_payto),
   1828     GNUNET_GETOPT_option_base32_fixed_size (
   1829       'p',
   1830       "payto-hash",
   1831       "HASH",
   1832       "base32 encoding of the hash of a payto://-URI to use for the account (otherwise a random value will be used)",
   1833       &cmd_line_h_payto,
   1834       sizeof (cmd_line_h_payto)),
   1835     GNUNET_GETOPT_option_string (
   1836       'R',
   1837       "ruleset",
   1838       "JSON",
   1839       "use the given legitimization rule set (otherwise defaults from configuration are used)",
   1840       &lrs_s),
   1841     GNUNET_GETOPT_option_uint (
   1842       'r',
   1843       "rowid",
   1844       "NUMBER",
   1845       "override row ID to use in simulation (default: 42)",
   1846       &kyc_row_id),
   1847     TALER_getopt_get_amount (
   1848       't',
   1849       "trigger",
   1850       "AMOUNT",
   1851       "threshold crossed that would trigger some legitimization rule",
   1852       &trigger_amount),
   1853     GNUNET_GETOPT_option_string (
   1854       'U',
   1855       "legitimization",
   1856       "ID",
   1857       "use the given provider legitimization ID (overridden if -i is also used)",
   1858       &cmd_provider_legitimization_id),
   1859     GNUNET_GETOPT_option_string (
   1860       'u',
   1861       "user",
   1862       "ID",
   1863       "use the given provider user ID (overridden if -i is also used)",
   1864       &cmd_provider_user_id),
   1865     GNUNET_GETOPT_option_flag (
   1866       'w',
   1867       "run-webservice",
   1868       "run the integrated HTTP service",
   1869       &run_webservice),
   1870     GNUNET_GETOPT_option_flag (
   1871       'W',
   1872       "wallet-payto",
   1873       "simulate that the address the KYC process is about is a wallet",
   1874       &cmd_line_is_wallet),
   1875     GNUNET_GETOPT_OPTION_END
   1876   };
   1877   enum GNUNET_GenericReturnValue ret;
   1878 
   1879   GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
   1880                               &cmd_line_h_payto,
   1881                               sizeof (cmd_line_h_payto));
   1882   ret = GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
   1883                             argc, argv,
   1884                             "taler-exchange-kyc-tester",
   1885                             "tool to test KYC provider integrations",
   1886                             options,
   1887                             &run, NULL);
   1888   if (GNUNET_SYSERR == ret)
   1889     return EXIT_INVALIDARGUMENT;
   1890   if (GNUNET_NO == ret)
   1891     return EXIT_SUCCESS;
   1892   return global_ret;
   1893 }
   1894 
   1895 
   1896 /* end of taler-exchange-kyc-tester.c */