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


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