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


      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                                      strlen (h_paytos),
    829                                      &h_payto,
    830                                      sizeof (h_payto)))
    831   {
    832     GNUNET_break_op (0);
    833     return TALER_MHD_reply_with_error (rc->connection,
    834                                        MHD_HTTP_BAD_REQUEST,
    835                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    836                                        "h_payto");
    837   }
    838   if (0 !=
    839       GNUNET_memcmp (&h_payto,
    840                      &cmd_line_h_payto))
    841   {
    842     GNUNET_break_op (0);
    843     return TALER_MHD_reply_with_error (rc->connection,
    844                                        MHD_HTTP_NOT_FOUND,
    845                                        TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
    846                                        "h_payto");
    847   }
    848 
    849   if (GNUNET_OK !=
    850       TALER_KYCLOGIC_lookup_logic (args[0],
    851                                    &logic,
    852                                    &pd,
    853                                    &section_name))
    854   {
    855     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    856                 "Could not initiate KYC with provider `%s' (configuration error?)\n",
    857                 args[0]);
    858     return TALER_MHD_reply_with_error (rc->connection,
    859                                        MHD_HTTP_NOT_FOUND,
    860                                        TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
    861                                        args[0]);
    862   }
    863   rs = GNUNET_new (struct ProofRequestState);
    864   rs->rc = rc;
    865   rs->logic = logic;
    866   MHD_suspend_connection (rc->connection);
    867   GNUNET_CONTAINER_DLL_insert (rs_head,
    868                                rs_tail,
    869                                rs);
    870   rs->ph = logic->proof (logic->cls,
    871                          pd,
    872                          rc->connection,
    873                          &h_payto,
    874                          kyc_row_id,
    875                          cmd_provider_user_id,
    876                          cmd_provider_legitimization_id,
    877                          &proof_cb,
    878                          rs);
    879   GNUNET_assert (NULL != rs->ph);
    880   return MHD_YES;
    881 }
    882 
    883 
    884 /**
    885  * Function called whenever MHD is done with a request.  If the
    886  * request was a POST, we may have stored a `struct Buffer *` in the
    887  * @a con_cls that might still need to be cleaned up.  Call the
    888  * respective function to free the memory.
    889  *
    890  * @param cls client-defined closure
    891  * @param connection connection handle
    892  * @param con_cls value as set by the last call to
    893  *        the #MHD_AccessHandlerCallback
    894  * @param toe reason for request termination
    895  * @see #MHD_OPTION_NOTIFY_COMPLETED
    896  * @ingroup request
    897  */
    898 static void
    899 handle_mhd_completion_callback (void *cls,
    900                                 struct MHD_Connection *connection,
    901                                 void **con_cls,
    902                                 enum MHD_RequestTerminationCode toe)
    903 {
    904   struct TEKT_RequestContext *rc = *con_cls;
    905 
    906   (void) cls;
    907   if (NULL == rc)
    908     return;
    909   if (NULL != rc->rh_cleaner)
    910     rc->rh_cleaner (rc);
    911   {
    912 #if MHD_VERSION >= 0x00097304
    913     const union MHD_ConnectionInfo *ci;
    914     unsigned int http_status = 0;
    915 
    916     ci = MHD_get_connection_info (connection,
    917                                   MHD_CONNECTION_INFO_HTTP_STATUS);
    918     if (NULL != ci)
    919       http_status = ci->http_status;
    920     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    921                 "Request for `%s' completed with HTTP status %u (%d)\n",
    922                 rc->url,
    923                 http_status,
    924                 toe);
    925 #else
    926     (void) connection;
    927     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    928                 "Request for `%s' completed (%d)\n",
    929                 rc->url,
    930                 toe);
    931 #endif
    932   }
    933 
    934   TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context);
    935   /* Sanity-check that we didn't leave any transactions hanging */
    936   if (NULL != rc->root)
    937     json_decref (rc->root);
    938   GNUNET_free (rc);
    939   *con_cls = NULL;
    940 }
    941 
    942 
    943 /**
    944  * We found a request handler responsible for handling a request. Parse the
    945  * @a upload_data (if applicable) and the @a url and call the
    946  * handler.
    947  *
    948  * @param rc request context
    949  * @param url rest of the URL to parse
    950  * @param upload_data upload data to parse (if available)
    951  * @param[in,out] upload_data_size number of bytes in @a upload_data
    952  * @return MHD result code
    953  */
    954 static enum MHD_Result
    955 proceed_with_handler (struct TEKT_RequestContext *rc,
    956                       const char *url,
    957                       const char *upload_data,
    958                       size_t *upload_data_size)
    959 {
    960   const struct TEKT_RequestHandler *rh = rc->rh;
    961   const char *args[rh->nargs + 2];
    962   size_t ulen = strlen (url) + 1;
    963   enum MHD_Result ret;
    964 
    965   /* We do check for "ulen" here, because we'll later stack-allocate a buffer
    966      of that size and don't want to enable malicious clients to cause us
    967      huge stack allocations. */
    968   if (ulen > 512)
    969   {
    970     /* 512 is simply "big enough", as it is bigger than "6 * 54",
    971        which is the longest URL format we ever get (for
    972        /deposits/).  The value should be adjusted if we ever define protocol
    973        endpoints with plausibly longer inputs.  */
    974     GNUNET_break_op (0);
    975     return TALER_MHD_reply_with_error (
    976       rc->connection,
    977       MHD_HTTP_URI_TOO_LONG,
    978       TALER_EC_GENERIC_URI_TOO_LONG,
    979       url);
    980   }
    981 
    982   /* All POST endpoints come with a body in JSON format. So we parse
    983      the JSON here. */
    984   if ( (NULL == rc->root) &&
    985        (0 == strcasecmp (rh->method,
    986                          MHD_HTTP_METHOD_POST)) )
    987   {
    988     enum GNUNET_GenericReturnValue res;
    989 
    990     res = TALER_MHD_parse_post_json (
    991       rc->connection,
    992       &rc->opaque_post_parsing_context,
    993       upload_data,
    994       upload_data_size,
    995       &rc->root);
    996     if (GNUNET_SYSERR == res)
    997     {
    998       GNUNET_assert (NULL == rc->root);
    999       GNUNET_break (0);
   1000       return MHD_NO; /* bad upload, could not even generate error */
   1001     }
   1002     if ( (GNUNET_NO == res) ||
   1003          (NULL == rc->root) )
   1004     {
   1005       GNUNET_assert (NULL == rc->root);
   1006       return MHD_YES; /* so far incomplete upload or parser error */
   1007     }
   1008   }
   1009 
   1010   {
   1011     char d[ulen];
   1012     unsigned int i;
   1013     char *sp;
   1014 
   1015     /* Parse command-line arguments */
   1016     /* make a copy of 'url' because 'strtok_r()' will modify */
   1017     GNUNET_memcpy (d,
   1018                    url,
   1019                    ulen);
   1020     i = 0;
   1021     args[i++] = strtok_r (d, "/", &sp);
   1022     while ( (NULL != args[i - 1]) &&
   1023             (i <= rh->nargs + 1) )
   1024       args[i++] = strtok_r (NULL, "/", &sp);
   1025     /* make sure above loop ran nicely until completion, and also
   1026        that there is no excess data in 'd' afterwards */
   1027     if ( ( (rh->nargs_is_upper_bound) &&
   1028            (i - 1 > rh->nargs) ) ||
   1029          ( (! rh->nargs_is_upper_bound) &&
   1030            (i - 1 != rh->nargs) ) )
   1031     {
   1032       char emsg[128 + 512];
   1033 
   1034       GNUNET_snprintf (emsg,
   1035                        sizeof (emsg),
   1036                        "Got %u+/%u segments for `%s' request (`%s')",
   1037                        i - 1,
   1038                        rh->nargs,
   1039                        rh->url,
   1040                        url);
   1041       GNUNET_break_op (0);
   1042       return TALER_MHD_reply_with_error (
   1043         rc->connection,
   1044         MHD_HTTP_NOT_FOUND,
   1045         TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
   1046         emsg);
   1047     }
   1048     GNUNET_assert (NULL == args[i - 1]);
   1049 
   1050     /* Above logic ensures that 'root' is exactly non-NULL for POST operations,
   1051        so we test for 'root' to decide which handler to invoke. */
   1052     if (NULL != rc->root)
   1053       ret = rh->handler.post (rc,
   1054                               rc->root,
   1055                               args);
   1056     else /* We also only have "POST" or "GET" in the API for at this point
   1057       (OPTIONS/HEAD are taken care of earlier) */
   1058       ret = rh->handler.get (rc,
   1059                              args);
   1060   }
   1061   return ret;
   1062 }
   1063 
   1064 
   1065 static void
   1066 rh_cleaner_cb (struct TEKT_RequestContext *rc)
   1067 {
   1068   if (NULL != rc->response)
   1069   {
   1070     MHD_destroy_response (rc->response);
   1071     rc->response = NULL;
   1072   }
   1073   if (NULL != rc->root)
   1074   {
   1075     json_decref (rc->root);
   1076     rc->root = NULL;
   1077   }
   1078 }
   1079 
   1080 
   1081 /**
   1082  * Handle incoming HTTP request.
   1083  *
   1084  * @param cls closure for MHD daemon (unused)
   1085  * @param connection the connection
   1086  * @param url the requested url
   1087  * @param method the method (POST, GET, ...)
   1088  * @param version HTTP version (ignored)
   1089  * @param upload_data request data
   1090  * @param upload_data_size size of @a upload_data in bytes
   1091  * @param con_cls closure for request (a `struct TEKT_RequestContext *`)
   1092  * @return MHD result code
   1093  */
   1094 static enum MHD_Result
   1095 handle_mhd_request (void *cls,
   1096                     struct MHD_Connection *connection,
   1097                     const char *url,
   1098                     const char *method,
   1099                     const char *version,
   1100                     const char *upload_data,
   1101                     size_t *upload_data_size,
   1102                     void **con_cls)
   1103 {
   1104   static struct TEKT_RequestHandler handlers[] = {
   1105     /* simulated KYC endpoints */
   1106     {
   1107       .url = "kyc-proof",
   1108       .method = MHD_HTTP_METHOD_GET,
   1109       .handler.get = &handler_kyc_proof_get,
   1110       .nargs = 1
   1111     },
   1112     {
   1113       .url = "kyc-webhook",
   1114       .method = MHD_HTTP_METHOD_POST,
   1115       .handler.post = &handler_kyc_webhook_post,
   1116       .nargs = 128,
   1117       .nargs_is_upper_bound = true
   1118     },
   1119     {
   1120       .url = "kyc-webhook",
   1121       .method = MHD_HTTP_METHOD_GET,
   1122       .handler.get = &handler_kyc_webhook_get,
   1123       .nargs = 128,
   1124       .nargs_is_upper_bound = true
   1125     },
   1126     /* mark end of list */
   1127     {
   1128       .url = NULL
   1129     }
   1130   };
   1131   struct TEKT_RequestContext *rc = *con_cls;
   1132 
   1133   (void) cls;
   1134   (void) version;
   1135   if (NULL == rc)
   1136   {
   1137     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1138                 "Handling new request\n");
   1139     /* We're in a new async scope! */
   1140     rc = *con_cls = GNUNET_new (struct TEKT_RequestContext);
   1141     rc->url = url;
   1142     rc->connection = connection;
   1143     rc->rh_cleaner = &rh_cleaner_cb;
   1144   }
   1145   if (NULL != rc->response)
   1146   {
   1147     return MHD_queue_response (rc->connection,
   1148                                rc->http_status,
   1149                                rc->response);
   1150   }
   1151 
   1152   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1153               "Handling request (%s) for URL '%s'\n",
   1154               method,
   1155               url);
   1156   /* on repeated requests, check our cache first */
   1157   if (NULL != rc->rh)
   1158   {
   1159     const char *start;
   1160 
   1161     if ('\0' == url[0])
   1162       /* strange, should start with '/', treat as just "/" */
   1163       url = "/";
   1164     start = strchr (url + 1, '/');
   1165     if (NULL == start)
   1166       start = "";
   1167     return proceed_with_handler (rc,
   1168                                  start,
   1169                                  upload_data,
   1170                                  upload_data_size);
   1171   }
   1172   if (0 == strcasecmp (method,
   1173                        MHD_HTTP_METHOD_HEAD))
   1174     method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
   1175 
   1176   /* parse first part of URL */
   1177   {
   1178     bool found = false;
   1179     size_t tok_size;
   1180     const char *tok;
   1181     const char *rest;
   1182 
   1183     if ('\0' == url[0])
   1184       /* strange, should start with '/', treat as just "/" */
   1185       url = "/";
   1186     tok = url + 1;
   1187     rest = strchr (tok, '/');
   1188     if (NULL == rest)
   1189     {
   1190       tok_size = strlen (tok);
   1191     }
   1192     else
   1193     {
   1194       tok_size = rest - tok;
   1195       rest++; /* skip over '/' */
   1196     }
   1197     for (unsigned int i = 0; NULL != handlers[i].url; i++)
   1198     {
   1199       struct TEKT_RequestHandler *rh = &handlers[i];
   1200 
   1201       if ( (0 != strncmp (tok,
   1202                           rh->url,
   1203                           tok_size)) ||
   1204            (tok_size != strlen (rh->url) ) )
   1205         continue;
   1206       found = true;
   1207       /* The URL is a match!  What we now do depends on the method. */
   1208       if (0 == strcasecmp (method,
   1209                            MHD_HTTP_METHOD_OPTIONS))
   1210       {
   1211         return TALER_MHD_reply_cors_preflight (connection);
   1212       }
   1213       GNUNET_assert (NULL != rh->method);
   1214       if (0 != strcasecmp (method,
   1215                            rh->method))
   1216       {
   1217         found = true;
   1218         continue;
   1219       }
   1220       /* cache to avoid the loop next time */
   1221       rc->rh = rh;
   1222       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1223                   "Handler found for %s '%s'\n",
   1224                   method,
   1225                   url);
   1226       return MHD_YES;
   1227     }
   1228 
   1229     if (found)
   1230     {
   1231       /* we found a matching address, but the method is wrong */
   1232       struct MHD_Response *reply;
   1233       enum MHD_Result ret;
   1234       char *allowed = NULL;
   1235 
   1236       GNUNET_break_op (0);
   1237       for (unsigned int i = 0; NULL != handlers[i].url; i++)
   1238       {
   1239         struct TEKT_RequestHandler *rh = &handlers[i];
   1240 
   1241         if ( (0 != strncmp (tok,
   1242                             rh->url,
   1243                             tok_size)) ||
   1244              (tok_size != strlen (rh->url) ) )
   1245           continue;
   1246         if (NULL == allowed)
   1247         {
   1248           allowed = GNUNET_strdup (rh->method);
   1249         }
   1250         else
   1251         {
   1252           char *tmp;
   1253 
   1254           GNUNET_asprintf (&tmp,
   1255                            "%s, %s",
   1256                            allowed,
   1257                            rh->method);
   1258           GNUNET_free (allowed);
   1259           allowed = tmp;
   1260         }
   1261         if (0 == strcasecmp (rh->method,
   1262                              MHD_HTTP_METHOD_GET))
   1263         {
   1264           char *tmp;
   1265 
   1266           GNUNET_asprintf (&tmp,
   1267                            "%s, %s",
   1268                            allowed,
   1269                            MHD_HTTP_METHOD_HEAD);
   1270           GNUNET_free (allowed);
   1271           allowed = tmp;
   1272         }
   1273       }
   1274       reply = TALER_MHD_make_error (
   1275         TALER_EC_GENERIC_METHOD_INVALID,
   1276         method);
   1277       GNUNET_break (
   1278         MHD_YES ==
   1279         MHD_add_response_header (reply,
   1280                                  MHD_HTTP_HEADER_ALLOW,
   1281                                  allowed));
   1282       GNUNET_free (allowed);
   1283       ret = MHD_queue_response (connection,
   1284                                 MHD_HTTP_METHOD_NOT_ALLOWED,
   1285                                 reply);
   1286       MHD_destroy_response (reply);
   1287       return ret;
   1288     }
   1289   }
   1290 
   1291   /* No handler matches, generate not found */
   1292   return TALER_MHD_reply_with_error (connection,
   1293                                      MHD_HTTP_NOT_FOUND,
   1294                                      TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
   1295                                      url);
   1296 }
   1297 
   1298 
   1299 /**
   1300  * Load configuration parameters for the exchange
   1301  * server into the corresponding global variables.
   1302  *
   1303  * @return #GNUNET_OK on success
   1304  */
   1305 static enum GNUNET_GenericReturnValue
   1306 exchange_serve_process_config (void)
   1307 {
   1308   if (GNUNET_OK !=
   1309       GNUNET_CONFIGURATION_get_value_string (TEKT_cfg,
   1310                                              "exchange",
   1311                                              "BASE_URL",
   1312                                              &TEKT_base_url))
   1313   {
   1314     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1315                                "exchange",
   1316                                "BASE_URL");
   1317     return GNUNET_SYSERR;
   1318   }
   1319   if (! TALER_url_valid_charset (TEKT_base_url))
   1320   {
   1321     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1322                                "exchange",
   1323                                "BASE_URL",
   1324                                "invalid URL");
   1325     return GNUNET_SYSERR;
   1326   }
   1327 
   1328   return GNUNET_OK;
   1329 }
   1330 
   1331 
   1332 /**
   1333  * Function run on shutdown.
   1334  *
   1335  * @param cls NULL
   1336  */
   1337 static void
   1338 do_shutdown (void *cls)
   1339 {
   1340   struct ProofRequestState *rs;
   1341 
   1342   (void) cls;
   1343   while (NULL != (rs = rs_head))
   1344   {
   1345     GNUNET_CONTAINER_DLL_remove (rs_head,
   1346                                  rs_tail,
   1347                                  rs);
   1348     rs->logic->proof_cancel (rs->ph);
   1349     MHD_resume_connection (rs->rc->connection);
   1350     GNUNET_free (rs);
   1351   }
   1352   if (NULL != ih)
   1353   {
   1354     ih_logic->initiate_cancel (ih);
   1355     ih = NULL;
   1356   }
   1357   kyc_webhook_cleanup ();
   1358   TALER_KYCLOGIC_rules_free (lrs);
   1359   lrs = NULL;
   1360   TALER_KYCLOGIC_kyc_done ();
   1361   TALER_MHD_daemons_halt ();
   1362   TALER_MHD_daemons_destroy ();
   1363   if (NULL != TEKT_curl_ctx)
   1364   {
   1365     GNUNET_CURL_fini (TEKT_curl_ctx);
   1366     TEKT_curl_ctx = NULL;
   1367   }
   1368   if (NULL != exchange_curl_rc)
   1369   {
   1370     GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc);
   1371     exchange_curl_rc = NULL;
   1372   }
   1373   TALER_TEMPLATING_done ();
   1374 }
   1375 
   1376 
   1377 /**
   1378  * Function called with the result of a KYC initiation
   1379  * operation.
   1380  *
   1381  * @param cls closure
   1382  * @param ec #TALER_EC_NONE on success
   1383  * @param redirect_url set to where to redirect the user on success, NULL on failure
   1384  * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
   1385  * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
   1386  * @param error_msg_hint set to additional details to return to user, NULL on success
   1387  */
   1388 static void
   1389 initiate_cb (
   1390   void *cls,
   1391   enum TALER_ErrorCode ec,
   1392   const char *redirect_url,
   1393   const char *provider_user_id,
   1394   const char *provider_legitimization_id,
   1395   const char *error_msg_hint)
   1396 {
   1397   (void) cls;
   1398   ih = NULL;
   1399   if (TALER_EC_NONE != ec)
   1400   {
   1401     fprintf (stderr,
   1402              "Failed to start KYC process: %s (#%d)\n",
   1403              error_msg_hint,
   1404              ec);
   1405     global_ret = EXIT_FAILURE;
   1406     GNUNET_SCHEDULER_shutdown ();
   1407     return;
   1408   }
   1409   {
   1410     char *s;
   1411 
   1412     s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto,
   1413                                              sizeof (cmd_line_h_payto));
   1414     if (NULL != provider_user_id)
   1415     {
   1416       fprintf (stdout,
   1417                "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -u '%s' -U '%s' -p %s\n",
   1418                redirect_url,
   1419                provider_user_id,
   1420                provider_legitimization_id,
   1421                s);
   1422     }
   1423     else
   1424     {
   1425       fprintf (stdout,
   1426                "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -U '%s' -p %s\n",
   1427                redirect_url,
   1428                provider_legitimization_id,
   1429                s);
   1430     }
   1431     GNUNET_free (s);
   1432   }
   1433   GNUNET_free (cmd_provider_user_id);
   1434   GNUNET_free (cmd_provider_legitimization_id);
   1435   if (NULL != provider_user_id)
   1436     cmd_provider_user_id = GNUNET_strdup (provider_user_id);
   1437   if (NULL != provider_legitimization_id)
   1438     cmd_provider_legitimization_id = GNUNET_strdup (provider_legitimization_id);
   1439   if (! run_webservice)
   1440     GNUNET_SCHEDULER_shutdown ();
   1441 }
   1442 
   1443 
   1444 /**
   1445  * Function called to iterate over KYC-relevant
   1446  * transaction amounts for a particular time range.
   1447  * Called within a database transaction, so must
   1448  * not start a new one.
   1449  *
   1450  * @param cls closure, identifies the event type and
   1451  *        account to iterate over events for
   1452  * @param limit maximum time-range for which events
   1453  *        should be fetched (timestamp in the past)
   1454  * @param cb function to call on each event found,
   1455  *        events must be returned in reverse chronological
   1456  *        order
   1457  * @param cb_cls closure for @a cb
   1458  * @return transaction status
   1459  */
   1460 static enum GNUNET_DB_QueryStatus
   1461 amount_iterator (
   1462   void *cls,
   1463   struct GNUNET_TIME_Absolute limit,
   1464   TALER_KYCLOGIC_KycAmountCallback cb,
   1465   void *cb_cls)
   1466 {
   1467   const struct TALER_Amount *amount = cls;
   1468   struct GNUNET_TIME_Absolute date;
   1469   enum GNUNET_GenericReturnValue ret;
   1470 
   1471   date = GNUNET_TIME_absolute_subtract (limit,
   1472                                         GNUNET_TIME_UNIT_SECONDS);
   1473 
   1474   ret = cb (cb_cls,
   1475             amount,
   1476             date);
   1477   GNUNET_break (GNUNET_SYSERR != ret);
   1478   if (GNUNET_OK != ret)
   1479     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
   1480   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1481 }
   1482 
   1483 
   1484 /**
   1485  * Callback invoked on every listen socket to start the
   1486  * respective MHD HTTP daemon.
   1487  *
   1488  * @param cls unused
   1489  * @param lsock the listen socket
   1490  */
   1491 static void
   1492 start_daemon (void *cls,
   1493               int lsock)
   1494 {
   1495   struct MHD_Daemon *mhd;
   1496 
   1497   (void) cls;
   1498   GNUNET_assert (-1 != lsock);
   1499   mhd = MHD_start_daemon (
   1500     MHD_USE_SUSPEND_RESUME
   1501     | MHD_USE_PIPE_FOR_SHUTDOWN
   1502     | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
   1503     | MHD_USE_TCP_FASTOPEN,
   1504     0,
   1505     NULL, NULL,
   1506     &handle_mhd_request, NULL,
   1507     MHD_OPTION_LISTEN_SOCKET,
   1508     lsock,
   1509     MHD_OPTION_EXTERNAL_LOGGER,
   1510     &TALER_MHD_handle_logs,
   1511     NULL,
   1512     MHD_OPTION_NOTIFY_COMPLETED,
   1513     &handle_mhd_completion_callback,
   1514     NULL,
   1515     MHD_OPTION_END);
   1516   if (NULL == mhd)
   1517   {
   1518     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1519                 "Failed to launch HTTP service. Is the port in use?\n");
   1520     GNUNET_SCHEDULER_shutdown ();
   1521     return;
   1522   }
   1523   have_daemons = true;
   1524   TALER_MHD_daemon_start (mhd);
   1525 }
   1526 
   1527 
   1528 /**
   1529  * Main function that will be run by the scheduler.
   1530  *
   1531  * @param cls closure
   1532  * @param args remaining command-line arguments
   1533  * @param cfgfile name of the configuration file used (for saving, can be
   1534  *        NULL!)
   1535  * @param config configuration
   1536  */
   1537 static void
   1538 run (void *cls,
   1539      char *const *args,
   1540      const char *cfgfile,
   1541      const struct GNUNET_CONFIGURATION_Handle *config)
   1542 {
   1543   enum TALER_KYCLOGIC_KycTriggerEvent event;
   1544   const struct TALER_KYCLOGIC_KycRule *rule = NULL;
   1545 
   1546   (void) cls;
   1547   (void) args;
   1548   (void ) cfgfile;
   1549   if (GNUNET_OK !=
   1550       TALER_TEMPLATING_init (TALER_EXCHANGE_project_data ()))
   1551   {
   1552     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1553                 "Could not load templates. Installation broken.\n");
   1554     global_ret = EXIT_FAILURE;
   1555     return;
   1556   }
   1557   if (NULL != operation_s)
   1558   {
   1559     if (GNUNET_OK !=
   1560         TALER_KYCLOGIC_kyc_trigger_from_string (operation_s,
   1561                                                 &event))
   1562     {
   1563       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1564                   "Malformed operation type `%s'\n",
   1565                   operation_s);
   1566       global_ret = EXIT_FAILURE;
   1567       return;
   1568     }
   1569   }
   1570 
   1571   if (NULL != lrs_s)
   1572   {
   1573     json_t *jlrs;
   1574     json_error_t err;
   1575 
   1576     jlrs = json_loads (lrs_s,
   1577                        JSON_REJECT_DUPLICATES,
   1578                        &err);
   1579     if (NULL == jlrs)
   1580     {
   1581       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1582                   "Malformed JSON for legitimization rule set: %s at %d\n",
   1583                   err.text,
   1584                   err.position);
   1585       global_ret = EXIT_INVALIDARGUMENT;
   1586       return;
   1587     }
   1588     lrs = TALER_KYCLOGIC_rules_parse (jlrs);
   1589     json_decref (jlrs);
   1590     if (NULL == lrs)
   1591     {
   1592       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1593                   "Malformed legitimization rule set `%s'\n",
   1594                   lrs_s);
   1595       global_ret = EXIT_INVALIDARGUMENT;
   1596       return;
   1597     }
   1598   }
   1599 
   1600   if (print_h_payto)
   1601   {
   1602     char *s;
   1603 
   1604     s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto,
   1605                                              sizeof (cmd_line_h_payto));
   1606     fprintf (stdout,
   1607              "%s\n",
   1608              s);
   1609     GNUNET_free (s);
   1610   }
   1611   TALER_MHD_setup (TALER_MHD_GO_NONE);
   1612   TEKT_cfg = config;
   1613   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1614                                  NULL);
   1615   if (GNUNET_OK !=
   1616       TALER_KYCLOGIC_kyc_init (config,
   1617                                cfgfile))
   1618   {
   1619     global_ret = EXIT_NOTCONFIGURED;
   1620     GNUNET_SCHEDULER_shutdown ();
   1621     return;
   1622   }
   1623   if (GNUNET_OK !=
   1624       exchange_serve_process_config ())
   1625   {
   1626     global_ret = EXIT_NOTCONFIGURED;
   1627     GNUNET_SCHEDULER_shutdown ();
   1628     return;
   1629   }
   1630   global_ret = EXIT_SUCCESS;
   1631   if (NULL != operation_s)
   1632   {
   1633     enum GNUNET_DB_QueryStatus qs;
   1634 
   1635     if (GNUNET_OK !=
   1636         TALER_amount_is_valid (&trigger_amount))
   1637     {
   1638       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1639                   "Trigger amount command-line option (-t) required\n");
   1640       global_ret = EXIT_INVALIDARGUMENT;
   1641       GNUNET_SCHEDULER_shutdown ();
   1642       return;
   1643     }
   1644     {
   1645       struct TALER_Amount next_threshold;
   1646 
   1647       qs = TALER_KYCLOGIC_kyc_test_required (
   1648         event,
   1649         lrs,
   1650         &amount_iterator,
   1651         &trigger_amount,
   1652         &rule,
   1653         &next_threshold);
   1654     }
   1655     switch (qs)
   1656     {
   1657     case GNUNET_DB_STATUS_HARD_ERROR:
   1658       GNUNET_break (0);
   1659       global_ret = EXIT_NOTCONFIGURED;
   1660       GNUNET_SCHEDULER_shutdown ();
   1661       return;
   1662     case GNUNET_DB_STATUS_SOFT_ERROR:
   1663       GNUNET_break (0);
   1664       global_ret = EXIT_NOTCONFIGURED;
   1665       GNUNET_SCHEDULER_shutdown ();
   1666       return;
   1667     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
   1668       fprintf (stdout,
   1669                "KYC not required for the given operation type and amount\n");
   1670       global_ret = EXIT_SUCCESS;
   1671       GNUNET_SCHEDULER_shutdown ();
   1672       return;
   1673     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
   1674       break;
   1675     }
   1676   }
   1677 
   1678   if (NULL != rule)
   1679   {
   1680     struct TALER_KYCLOGIC_KycCheckContext kcc;
   1681 
   1682     if (0 != list_measures)
   1683     {
   1684       // FIXME: print rule with possible measures!
   1685       GNUNET_break (0);
   1686       global_ret = EXIT_SUCCESS;
   1687       GNUNET_SCHEDULER_shutdown ();
   1688       return;
   1689     }
   1690 
   1691     if (GNUNET_OK !=
   1692         TALER_KYCLOGIC_requirements_to_check (lrs,
   1693                                               rule,
   1694                                               measure,
   1695                                               &kcc))
   1696     {
   1697       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1698                   "Could not initiate KYC for measure `%s' (configuration error?)\n",
   1699                   measure);
   1700       global_ret = EXIT_NOTCONFIGURED;
   1701       GNUNET_SCHEDULER_shutdown ();
   1702       return;
   1703     }
   1704     if (NULL == kcc.check)
   1705     {
   1706       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1707                   "SKIP check selected, nothing to do here\n");
   1708       global_ret = EXIT_SUCCESS;
   1709       GNUNET_SCHEDULER_shutdown ();
   1710       return;
   1711     }
   1712     switch (kcc.check->type)
   1713     {
   1714     case TALER_KYCLOGIC_CT_INFO:
   1715       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1716                   "KYC information is `%s'\n",
   1717                   kcc.check->description);
   1718       break;
   1719     case TALER_KYCLOGIC_CT_FORM:
   1720       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
   1721                   "Would initiate KYC check `%s' with form `%s'\n",
   1722                   kcc.check->check_name,
   1723                   kcc.check->details.form.name);
   1724       break;
   1725     case TALER_KYCLOGIC_CT_LINK:
   1726       {
   1727         struct TALER_KYCLOGIC_ProviderDetails *pd;
   1728         const char *provider_name;
   1729 
   1730         TALER_KYCLOGIC_provider_to_logic (
   1731           kcc.check->details.link.provider,
   1732           &ih_logic,
   1733           &pd,
   1734           &provider_name);
   1735         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1736                     "Initiating KYC check `%s' at provider `%s'\n",
   1737                     kcc.check->check_name,
   1738                     provider_name);
   1739         ih = ih_logic->initiate (ih_logic->cls,
   1740                                  pd,
   1741                                  &cmd_line_h_payto,
   1742                                  kyc_row_id,
   1743                                  NULL, /* FIXME: support passing context*/
   1744                                  &initiate_cb,
   1745                                  NULL);
   1746         GNUNET_break (NULL != ih);
   1747         break;
   1748       }
   1749     }
   1750   }
   1751   if (run_webservice)
   1752   {
   1753     enum GNUNET_GenericReturnValue ret;
   1754 
   1755     TEKT_curl_ctx
   1756       = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1757                           &exchange_curl_rc);
   1758     if (NULL == TEKT_curl_ctx)
   1759     {
   1760       GNUNET_break (0);
   1761       global_ret = EXIT_FAILURE;
   1762       GNUNET_SCHEDULER_shutdown ();
   1763       return;
   1764     }
   1765     exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEKT_curl_ctx);
   1766     ret = TALER_MHD_listen_bind (TEKT_cfg,
   1767                                  "exchange",
   1768                                  &start_daemon,
   1769                                  NULL);
   1770     switch (ret)
   1771     {
   1772     case GNUNET_SYSERR:
   1773       global_ret = EXIT_NOTCONFIGURED;
   1774       GNUNET_SCHEDULER_shutdown ();
   1775       return;
   1776     case GNUNET_NO:
   1777       if (! have_daemons)
   1778       {
   1779         global_ret = EXIT_NOTCONFIGURED;
   1780         GNUNET_SCHEDULER_shutdown ();
   1781         return;
   1782       }
   1783       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1784                   "Could not open all configured listen sockets\n");
   1785       break;
   1786     case GNUNET_OK:
   1787       break;
   1788     }
   1789   }
   1790 }
   1791 
   1792 
   1793 /**
   1794  * The main function of the taler-exchange-kyc-tester, a tool for
   1795  * testing KYC processes.
   1796  *
   1797  * @param argc number of arguments from the command line
   1798  * @param argv command line arguments
   1799  * @return 0 ok, non-zero on error
   1800  */
   1801 int
   1802 main (int argc,
   1803       char *const *argv)
   1804 {
   1805   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1806     GNUNET_GETOPT_option_help (
   1807       TALER_EXCHANGE_project_data (),
   1808       "tool to test KYC provider integrations"),
   1809     GNUNET_GETOPT_option_flag (
   1810       'M',
   1811       "list-measures",
   1812       "list available measures",
   1813       &list_measures),
   1814     GNUNET_GETOPT_option_string (
   1815       'm',
   1816       "measure",
   1817       "MEASURE_NAME",
   1818       "initiate KYC check for the selected measure",
   1819       &measure),
   1820     GNUNET_GETOPT_option_string (
   1821       'o',
   1822       "operation",
   1823       "OPERATION_TYPE",
   1824       "name of the operation that triggers legitimization (WITHDRAW, DEPOSIT, etc.)",
   1825       &operation_s),
   1826     GNUNET_GETOPT_option_flag (
   1827       'P',
   1828       "print-payto-hash",
   1829       "output the hash of the (normalized) payto://-URI",
   1830       &print_h_payto),
   1831     GNUNET_GETOPT_option_base32_fixed_size (
   1832       'p',
   1833       "payto-hash",
   1834       "HASH",
   1835       "base32 encoding of the hash of a payto://-URI to use for the account (otherwise a random value will be used)",
   1836       &cmd_line_h_payto,
   1837       sizeof (cmd_line_h_payto)),
   1838     GNUNET_GETOPT_option_string (
   1839       'R',
   1840       "ruleset",
   1841       "JSON",
   1842       "use the given legitimization rule set (otherwise defaults from configuration are used)",
   1843       &lrs_s),
   1844     GNUNET_GETOPT_option_uint (
   1845       'r',
   1846       "rowid",
   1847       "NUMBER",
   1848       "override row ID to use in simulation (default: 42)",
   1849       &kyc_row_id),
   1850     TALER_getopt_get_amount (
   1851       't',
   1852       "trigger",
   1853       "AMOUNT",
   1854       "threshold crossed that would trigger some legitimization rule",
   1855       &trigger_amount),
   1856     GNUNET_GETOPT_option_string (
   1857       'U',
   1858       "legitimization",
   1859       "ID",
   1860       "use the given provider legitimization ID (overridden if -i is also used)",
   1861       &cmd_provider_legitimization_id),
   1862     GNUNET_GETOPT_option_string (
   1863       'u',
   1864       "user",
   1865       "ID",
   1866       "use the given provider user ID (overridden if -i is also used)",
   1867       &cmd_provider_user_id),
   1868     GNUNET_GETOPT_option_flag (
   1869       'w',
   1870       "run-webservice",
   1871       "run the integrated HTTP service",
   1872       &run_webservice),
   1873     GNUNET_GETOPT_option_flag (
   1874       'W',
   1875       "wallet-payto",
   1876       "simulate that the address the KYC process is about is a wallet",
   1877       &cmd_line_is_wallet),
   1878     GNUNET_GETOPT_OPTION_END
   1879   };
   1880   enum GNUNET_GenericReturnValue ret;
   1881 
   1882   GNUNET_CRYPTO_random_block (&cmd_line_h_payto,
   1883                               sizeof (cmd_line_h_payto));
   1884   ret = GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
   1885                             argc, argv,
   1886                             "taler-exchange-kyc-tester",
   1887                             "tool to test KYC provider integrations",
   1888                             options,
   1889                             &run, NULL);
   1890   if (GNUNET_SYSERR == ret)
   1891     return EXIT_INVALIDARGUMENT;
   1892   if (GNUNET_NO == ret)
   1893     return EXIT_SUCCESS;
   1894   return global_ret;
   1895 }
   1896 
   1897 
   1898 /* end of taler-exchange-kyc-tester.c */