exchange

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

plugin_kyclogic_kycaid.c (44189B)


      1 /*
      2   This file is part of GNU 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.GPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file plugin_kyclogic_kycaid.c
     18  * @brief kycaid for an authentication flow logic
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/taler_kyclogic_lib.h"
     22 #include "taler/taler_kyclogic_plugin.h"
     23 #include "taler/taler_mhd_lib.h"
     24 #include "taler/taler_curl_lib.h"
     25 #include "taler/taler_json_lib.h"
     26 #include "taler/taler_templating_lib.h"
     27 #include <regex.h>
     28 #include "taler/taler_util.h"
     29 
     30 
     31 /**
     32  * Saves the state of a plugin.
     33  */
     34 struct PluginState
     35 {
     36 
     37   /**
     38    * Our base URL.
     39    */
     40   char *exchange_base_url;
     41 
     42   /**
     43    * Our global configuration.
     44    */
     45   const struct GNUNET_CONFIGURATION_Handle *cfg;
     46 
     47   /**
     48    * Context for CURL operations (useful to the event loop)
     49    */
     50   struct GNUNET_CURL_Context *curl_ctx;
     51 
     52   /**
     53    * Context for integrating @e curl_ctx with the
     54    * GNUnet event loop.
     55    */
     56   struct GNUNET_CURL_RescheduleContext *curl_rc;
     57 
     58 };
     59 
     60 
     61 /**
     62  * Keeps the plugin-specific state for
     63  * a given configuration section.
     64  */
     65 struct TALER_KYCLOGIC_ProviderDetails
     66 {
     67 
     68   /**
     69    * Overall plugin state.
     70    */
     71   struct PluginState *ps;
     72 
     73   /**
     74    * Configuration section that configured us.
     75    */
     76   char *section;
     77 
     78   /**
     79    * Authorization token to use when talking
     80    * to the service.
     81    */
     82   char *auth_token;
     83 
     84   /**
     85    * Form ID for the KYC check to perform.
     86    */
     87   char *form_id;
     88 
     89   /**
     90    * Helper binary to convert attributes returned by
     91    * KYCAID into our internal format.
     92    */
     93   char *conversion_helper;
     94 
     95   /**
     96    * Validity time for a successful KYC process.
     97    */
     98   struct GNUNET_TIME_Relative validity;
     99 
    100   /**
    101    * Curl-ready authentication header to use.
    102    */
    103   struct curl_slist *slist;
    104 
    105 };
    106 
    107 
    108 /**
    109  * Handle for an initiation operation.
    110  */
    111 struct TALER_KYCLOGIC_InitiateHandle
    112 {
    113 
    114   /**
    115    * Hash of the payto:// URI we are initiating
    116    * the KYC for.
    117    */
    118   struct TALER_NormalizedPaytoHashP h_payto;
    119 
    120   /**
    121    * UUID being checked.
    122    */
    123   uint64_t legitimization_uuid;
    124 
    125   /**
    126    * Our configuration details.
    127    */
    128   const struct TALER_KYCLOGIC_ProviderDetails *pd;
    129 
    130   /**
    131    * Continuation to call.
    132    */
    133   TALER_KYCLOGIC_InitiateCallback cb;
    134 
    135   /**
    136    * Closure for @a cb.
    137    */
    138   void *cb_cls;
    139 
    140   /**
    141    * Context for #TEH_curl_easy_post(). Keeps the data that must
    142    * persist for Curl to make the upload.
    143    */
    144   struct TALER_CURL_PostContext ctx;
    145 
    146   /**
    147    * Handle for the request.
    148    */
    149   struct GNUNET_CURL_Job *job;
    150 
    151   /**
    152    * URL of the cURL request.
    153    */
    154   char *url;
    155 
    156 };
    157 
    158 
    159 /**
    160  * Handle for an KYC proof operation.
    161  */
    162 struct TALER_KYCLOGIC_ProofHandle
    163 {
    164 
    165   /**
    166    * Overall plugin state.
    167    */
    168   struct PluginState *ps;
    169 
    170   /**
    171    * Our configuration details.
    172    */
    173   const struct TALER_KYCLOGIC_ProviderDetails *pd;
    174 
    175   /**
    176    * Continuation to call.
    177    */
    178   TALER_KYCLOGIC_ProofCallback cb;
    179 
    180   /**
    181    * Closure for @e cb.
    182    */
    183   void *cb_cls;
    184 
    185   /**
    186    * Connection we are handling.
    187    */
    188   struct MHD_Connection *connection;
    189 
    190   /**
    191    * Task for asynchronous execution.
    192    */
    193   struct GNUNET_SCHEDULER_Task *task;
    194 };
    195 
    196 
    197 /**
    198  * Handle for an KYC Web hook operation.
    199  */
    200 struct TALER_KYCLOGIC_WebhookHandle
    201 {
    202 
    203   /**
    204    * Continuation to call when done.
    205    */
    206   TALER_KYCLOGIC_WebhookCallback cb;
    207 
    208   /**
    209    * Closure for @a cb.
    210    */
    211   void *cb_cls;
    212 
    213   /**
    214    * Task for asynchronous execution.
    215    */
    216   struct GNUNET_SCHEDULER_Task *task;
    217 
    218   /**
    219    * Overall plugin state.
    220    */
    221   struct PluginState *ps;
    222 
    223   /**
    224    * Handle to helper process to extract attributes
    225    * we care about.
    226    */
    227   struct TALER_JSON_ExternalConversion *econ;
    228 
    229   /**
    230    * Our configuration details.
    231    */
    232   const struct TALER_KYCLOGIC_ProviderDetails *pd;
    233 
    234   /**
    235    * Connection we are handling.
    236    */
    237   struct MHD_Connection *connection;
    238 
    239   /**
    240    * JSON response we got back, or NULL for none.
    241    */
    242   json_t *json_response;
    243 
    244   /**
    245    * Verification ID from the service.
    246    */
    247   char *verification_id;
    248 
    249   /**
    250    * Applicant ID from the service.
    251    */
    252   char *applicant_id;
    253 
    254   /**
    255    * URL of the cURL request.
    256    */
    257   char *url;
    258 
    259   /**
    260    * Handle for the request.
    261    */
    262   struct GNUNET_CURL_Job *job;
    263 
    264   /**
    265    * Response to return asynchronously.
    266    */
    267   struct MHD_Response *resp;
    268 
    269   /**
    270    * Our account ID.
    271    */
    272   struct TALER_NormalizedPaytoHashP h_payto;
    273 
    274   /**
    275    * Row in legitimizations for the given
    276    * @e verification_id.
    277    */
    278   uint64_t process_row;
    279 
    280   /**
    281    * HTTP response code we got from KYCAID.
    282    */
    283   unsigned int kycaid_response_code;
    284 
    285   /**
    286    * HTTP response code to return asynchronously.
    287    */
    288   unsigned int response_code;
    289 
    290   /**
    291    * True if @e h_payto is for a wallet.
    292    */
    293   bool is_wallet;
    294 };
    295 
    296 
    297 /**
    298  * Release configuration resources previously loaded
    299  *
    300  * @param[in] pd configuration to release
    301  */
    302 static void
    303 kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
    304 {
    305   curl_slist_free_all (pd->slist);
    306   GNUNET_free (pd->conversion_helper);
    307   GNUNET_free (pd->auth_token);
    308   GNUNET_free (pd->form_id);
    309   GNUNET_free (pd->section);
    310   GNUNET_free (pd);
    311 }
    312 
    313 
    314 /**
    315  * Load the configuration of the KYC provider.
    316  *
    317  * @param cls closure
    318  * @param provider_section_name configuration section to parse
    319  * @return NULL if configuration is invalid
    320  */
    321 static struct TALER_KYCLOGIC_ProviderDetails *
    322 kycaid_load_configuration (void *cls,
    323                            const char *provider_section_name)
    324 {
    325   struct PluginState *ps = cls;
    326   struct TALER_KYCLOGIC_ProviderDetails *pd;
    327 
    328   pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
    329   pd->ps = ps;
    330   pd->section = GNUNET_strdup (provider_section_name);
    331   if (GNUNET_OK !=
    332       GNUNET_CONFIGURATION_get_value_time (ps->cfg,
    333                                            provider_section_name,
    334                                            "KYC_KYCAID_VALIDITY",
    335                                            &pd->validity))
    336   {
    337     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    338                                provider_section_name,
    339                                "KYC_KYCAID_VALIDITY");
    340     kycaid_unload_configuration (pd);
    341     return NULL;
    342   }
    343   if (GNUNET_OK !=
    344       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    345                                              provider_section_name,
    346                                              "KYC_KYCAID_AUTH_TOKEN",
    347                                              &pd->auth_token))
    348   {
    349     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    350                                provider_section_name,
    351                                "KYC_KYCAID_AUTH_TOKEN");
    352     kycaid_unload_configuration (pd);
    353     return NULL;
    354   }
    355   if (GNUNET_OK !=
    356       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    357                                              provider_section_name,
    358                                              "KYC_KYCAID_FORM_ID",
    359                                              &pd->form_id))
    360   {
    361     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    362                                provider_section_name,
    363                                "KYC_KYCAID_FORM_ID");
    364     kycaid_unload_configuration (pd);
    365     return NULL;
    366   }
    367   if (GNUNET_OK !=
    368       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    369                                              provider_section_name,
    370                                              "KYC_KYCAID_CONVERTER_HELPER",
    371                                              &pd->conversion_helper))
    372   {
    373     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    374                                provider_section_name,
    375                                "KYC_KYCAID_CONVERTER_HELPER");
    376     kycaid_unload_configuration (pd);
    377     return NULL;
    378   }
    379   {
    380     char *auth;
    381 
    382     GNUNET_asprintf (&auth,
    383                      "%s: Token %s",
    384                      MHD_HTTP_HEADER_AUTHORIZATION,
    385                      pd->auth_token);
    386     pd->slist = curl_slist_append (NULL,
    387                                    auth);
    388     GNUNET_free (auth);
    389   }
    390   return pd;
    391 }
    392 
    393 
    394 /**
    395  * Cancel KYC check initiation.
    396  *
    397  * @param[in] ih handle of operation to cancel
    398  */
    399 static void
    400 kycaid_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
    401 {
    402   if (NULL != ih->job)
    403   {
    404     GNUNET_CURL_job_cancel (ih->job);
    405     ih->job = NULL;
    406   }
    407   GNUNET_free (ih->url);
    408   TALER_curl_easy_post_finished (&ih->ctx);
    409   GNUNET_free (ih);
    410 }
    411 
    412 
    413 /**
    414  * Function called when we're done processing the
    415  * HTTP "/forms/{form_id}/urls" request.
    416  *
    417  * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
    418  * @param response_code HTTP response code, 0 on error
    419  * @param response parsed JSON result, NULL on error
    420  */
    421 static void
    422 handle_initiate_finished (void *cls,
    423                           long response_code,
    424                           const void *response)
    425 {
    426   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
    427   const json_t *j = response;
    428 
    429   ih->job = NULL;
    430   switch (response_code)
    431   {
    432   case MHD_HTTP_OK:
    433     {
    434       const char *verification_id;
    435       const char *form_url;
    436       const char *form_id;
    437       struct GNUNET_JSON_Specification spec[] = {
    438         GNUNET_JSON_spec_string ("verification_id",
    439                                  &verification_id),
    440         GNUNET_JSON_spec_string ("form_url",
    441                                  &form_url),
    442         GNUNET_JSON_spec_string ("form_id",
    443                                  &form_id),
    444         GNUNET_JSON_spec_end ()
    445       };
    446 
    447       if (GNUNET_OK !=
    448           GNUNET_JSON_parse (j,
    449                              spec,
    450                              NULL, NULL))
    451       {
    452         GNUNET_break_op (0);
    453         json_dumpf (j,
    454                     stderr,
    455                     JSON_INDENT (2));
    456         ih->cb (ih->cb_cls,
    457                 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
    458                 NULL,
    459                 NULL,
    460                 NULL,
    461                 json_string_value (json_object_get (j,
    462                                                     "type")));
    463         break;
    464       }
    465       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    466                   "Started new verification `%s' using form %s\n",
    467                   verification_id,
    468                   form_id);
    469       ih->cb (ih->cb_cls,
    470               TALER_EC_NONE,
    471               form_url,
    472               NULL, /* no provider_user_id */
    473               verification_id,
    474               NULL /* no error */);
    475       GNUNET_JSON_parse_free (spec);
    476     }
    477     break;
    478   case MHD_HTTP_BAD_REQUEST:
    479   case MHD_HTTP_NOT_FOUND:
    480   case MHD_HTTP_CONFLICT:
    481     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    482                 "KYCAID failed with response %u:\n",
    483                 (unsigned int) response_code);
    484     json_dumpf (j,
    485                 stderr,
    486                 JSON_INDENT (2));
    487     ih->cb (ih->cb_cls,
    488             TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
    489             NULL,
    490             NULL,
    491             NULL,
    492             json_string_value (json_object_get (j,
    493                                                 "type")));
    494     break;
    495   case MHD_HTTP_UNAUTHORIZED:
    496   case MHD_HTTP_PAYMENT_REQUIRED:
    497     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    498                 "Refused access with HTTP status code %u\n",
    499                 (unsigned int) response_code);
    500     ih->cb (ih->cb_cls,
    501             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED,
    502             NULL,
    503             NULL,
    504             NULL,
    505             json_string_value (json_object_get (j,
    506                                                 "type")));
    507     break;
    508   case MHD_HTTP_REQUEST_TIMEOUT:
    509     ih->cb (ih->cb_cls,
    510             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT,
    511             NULL,
    512             NULL,
    513             NULL,
    514             json_string_value (json_object_get (j,
    515                                                 "type")));
    516     break;
    517   case MHD_HTTP_UNPROCESSABLE_CONTENT: /* validation */
    518     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    519                 "KYCAID failed with response %u:\n",
    520                 (unsigned int) response_code);
    521     json_dumpf (j,
    522                 stderr,
    523                 JSON_INDENT (2));
    524     ih->cb (ih->cb_cls,
    525             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
    526             NULL,
    527             NULL,
    528             NULL,
    529             json_string_value (json_object_get (j,
    530                                                 "type")));
    531     break;
    532   case MHD_HTTP_TOO_MANY_REQUESTS:
    533     ih->cb (ih->cb_cls,
    534             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
    535             NULL,
    536             NULL,
    537             NULL,
    538             json_string_value (json_object_get (j,
    539                                                 "type")));
    540     break;
    541   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    542     ih->cb (ih->cb_cls,
    543             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
    544             NULL,
    545             NULL,
    546             NULL,
    547             json_string_value (json_object_get (j,
    548                                                 "type")));
    549     break;
    550   default:
    551     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    552                 "Unexpected KYCAID response %u:\n",
    553                 (unsigned int) response_code);
    554     json_dumpf (j,
    555                 stderr,
    556                 JSON_INDENT (2));
    557     ih->cb (ih->cb_cls,
    558             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
    559             NULL,
    560             NULL,
    561             NULL,
    562             json_string_value (json_object_get (j,
    563                                                 "type")));
    564     break;
    565   }
    566   kycaid_initiate_cancel (ih);
    567 }
    568 
    569 
    570 /**
    571  * Initiate KYC check.
    572  *
    573  * @param cls the @e cls of this struct with the plugin-specific state
    574  * @param pd provider configuration details
    575  * @param account_id which account to trigger process for
    576  * @param legitimization_uuid unique ID for the legitimization process
    577  * @param context additional contextual information for the legi process
    578  * @param cb function to call with the result
    579  * @param cb_cls closure for @a cb
    580  * @return handle to cancel operation early
    581  */
    582 static struct TALER_KYCLOGIC_InitiateHandle *
    583 kycaid_initiate (void *cls,
    584                  const struct TALER_KYCLOGIC_ProviderDetails *pd,
    585                  const struct TALER_NormalizedPaytoHashP *account_id,
    586                  uint64_t legitimization_uuid,
    587                  const json_t *context,
    588                  TALER_KYCLOGIC_InitiateCallback cb,
    589                  void *cb_cls)
    590 {
    591   struct PluginState *ps = cls;
    592   struct TALER_KYCLOGIC_InitiateHandle *ih;
    593   json_t *body;
    594   CURL *eh;
    595 
    596   (void) context;
    597   eh = curl_easy_init ();
    598   if (NULL == eh)
    599   {
    600     GNUNET_break (0);
    601     return NULL;
    602   }
    603   ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
    604   ih->legitimization_uuid = legitimization_uuid;
    605   ih->cb = cb;
    606   ih->cb_cls = cb_cls;
    607   ih->h_payto = *account_id;
    608   ih->pd = pd;
    609   GNUNET_asprintf (&ih->url,
    610                    "https://api.kycaid.com/forms/%s/urls",
    611                    pd->form_id);
    612   body = GNUNET_JSON_PACK (
    613     GNUNET_JSON_pack_data64_auto ("external_applicant_id",
    614                                   account_id)
    615     );
    616   GNUNET_break (CURLE_OK ==
    617                 curl_easy_setopt (eh,
    618                                   CURLOPT_VERBOSE,
    619                                   0));
    620   GNUNET_assert (CURLE_OK ==
    621                  curl_easy_setopt (eh,
    622                                    CURLOPT_MAXREDIRS,
    623                                    1L));
    624   GNUNET_break (CURLE_OK ==
    625                 curl_easy_setopt (eh,
    626                                   CURLOPT_URL,
    627                                   ih->url));
    628   if (GNUNET_OK !=
    629       TALER_curl_easy_post (&ih->ctx,
    630                             eh,
    631                             body))
    632   {
    633     GNUNET_break (0);
    634     GNUNET_free (ih->url);
    635     GNUNET_free (ih);
    636     curl_easy_cleanup (eh);
    637     json_decref (body);
    638     return NULL;
    639   }
    640   json_decref (body);
    641   ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    642                                   eh,
    643                                   ih->ctx.headers,
    644                                   &handle_initiate_finished,
    645                                   ih);
    646   GNUNET_CURL_extend_headers (ih->job,
    647                               pd->slist);
    648   return ih;
    649 }
    650 
    651 
    652 /**
    653  * Cancel KYC proof.
    654  *
    655  * @param[in] ph handle of operation to cancel
    656  */
    657 static void
    658 kycaid_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
    659 {
    660   if (NULL != ph->task)
    661   {
    662     GNUNET_SCHEDULER_cancel (ph->task);
    663     ph->task = NULL;
    664   }
    665   GNUNET_free (ph);
    666 }
    667 
    668 
    669 /**
    670  * Call @a ph callback with HTTP error response.
    671  *
    672  * @param cls proof handle to generate reply for
    673  */
    674 static void
    675 proof_reply (void *cls)
    676 {
    677   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
    678   struct MHD_Response *resp;
    679   enum GNUNET_GenericReturnValue ret;
    680   json_t *body;
    681   unsigned int http_status;
    682 
    683   http_status = MHD_HTTP_BAD_REQUEST;
    684   body = GNUNET_JSON_PACK (
    685     TALER_JSON_pack_ec (TALER_EC_GENERIC_ENDPOINT_UNKNOWN));
    686   GNUNET_assert (NULL != body);
    687   ret = TALER_TEMPLATING_build (ph->connection,
    688                                 &http_status,
    689                                 "kycaid-invalid-request",
    690                                 NULL,
    691                                 NULL,
    692                                 body,
    693                                 &resp);
    694   json_decref (body);
    695   if (GNUNET_SYSERR == ret)
    696   {
    697     resp = NULL;
    698     GNUNET_break (0);
    699   }
    700   else
    701   {
    702     GNUNET_break (MHD_NO !=
    703                   MHD_add_response_header (resp,
    704                                            MHD_HTTP_HEADER_CONTENT_TYPE,
    705                                            "text/html"));
    706   }
    707   ph->cb (ph->cb_cls,
    708           TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    709           ph->pd->section,
    710           NULL, /* user id */
    711           NULL, /* provider legi ID */
    712           GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    713           NULL, /* attributes */
    714           http_status,
    715           resp);
    716 }
    717 
    718 
    719 /**
    720  * Check KYC status and return status to human. Not
    721  * used by KYC AID!
    722  *
    723  * @param cls the @e cls of this struct with the plugin-specific state
    724  * @param pd provider configuration details
    725  * @param connection MHD connection object (for HTTP headers)
    726  * @param account_id which account to trigger process for
    727  * @param process_row row in the legitimization processes table the legitimization is for
    728  * @param provider_user_id user ID (or NULL) the proof is for
    729  * @param provider_legitimization_id legitimization ID the proof is for
    730  * @param cb function to call with the result
    731  * @param cb_cls closure for @a cb
    732  * @return handle to cancel operation early
    733  */
    734 static struct TALER_KYCLOGIC_ProofHandle *
    735 kycaid_proof (void *cls,
    736               const struct TALER_KYCLOGIC_ProviderDetails *pd,
    737               struct MHD_Connection *connection,
    738               const struct TALER_NormalizedPaytoHashP *account_id,
    739               uint64_t process_row,
    740               const char *provider_user_id,
    741               const char *provider_legitimization_id,
    742               TALER_KYCLOGIC_ProofCallback cb,
    743               void *cb_cls)
    744 {
    745   struct PluginState *ps = cls;
    746   struct TALER_KYCLOGIC_ProofHandle *ph;
    747 
    748   ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
    749   ph->ps = ps;
    750   ph->pd = pd;
    751   ph->cb = cb;
    752   ph->cb_cls = cb_cls;
    753   ph->connection = connection;
    754   ph->task = GNUNET_SCHEDULER_add_now (&proof_reply,
    755                                        ph);
    756   return ph;
    757 }
    758 
    759 
    760 /**
    761  * Cancel KYC webhook execution.
    762  *
    763  * @param[in] wh handle of operation to cancel
    764  */
    765 static void
    766 kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
    767 {
    768   if (NULL != wh->task)
    769   {
    770     GNUNET_SCHEDULER_cancel (wh->task);
    771     wh->task = NULL;
    772   }
    773   if (NULL != wh->econ)
    774   {
    775     TALER_JSON_external_conversion_stop (wh->econ);
    776     wh->econ = NULL;
    777   }
    778   if (NULL != wh->job)
    779   {
    780     GNUNET_CURL_job_cancel (wh->job);
    781     wh->job = NULL;
    782   }
    783   if (NULL != wh->json_response)
    784   {
    785     json_decref (wh->json_response);
    786     wh->json_response = NULL;
    787   }
    788   GNUNET_free (wh->verification_id);
    789   GNUNET_free (wh->applicant_id);
    790   GNUNET_free (wh->url);
    791   GNUNET_free (wh);
    792 }
    793 
    794 
    795 /**
    796  * Extract KYC failure reasons and log those
    797  *
    798  * @param verifications JSON object with failure details
    799  */
    800 static void
    801 log_failure (const json_t *verifications)
    802 {
    803   const json_t *member;
    804   const char *name;
    805 
    806   json_object_foreach ((json_t *) verifications, name, member)
    807   {
    808     bool iverified;
    809     const char *comment;
    810     struct GNUNET_JSON_Specification spec[] = {
    811       GNUNET_JSON_spec_bool ("verified",
    812                              &iverified),
    813       GNUNET_JSON_spec_string ("comment",
    814                                &comment),
    815       GNUNET_JSON_spec_end ()
    816     };
    817 
    818     if (GNUNET_OK !=
    819         GNUNET_JSON_parse (member,
    820                            spec,
    821                            NULL, NULL))
    822     {
    823       GNUNET_break_op (0);
    824       json_dumpf (member,
    825                   stderr,
    826                   JSON_INDENT (2));
    827       continue;
    828     }
    829     if (iverified)
    830       continue;
    831     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    832                 "KYC verification of attribute `%s' failed: %s\n",
    833                 name,
    834                 comment);
    835   }
    836 }
    837 
    838 
    839 /**
    840  * Type of a callback that receives a JSON @a result.
    841  *
    842  * @param cls closure our `struct TALER_KYCLOGIC_WebhookHandle *`
    843  * @param status_type how did the process die
    844  * @param code termination status code from the process
    845  * @param result converted attribute data, NULL on failure
    846  */
    847 static void
    848 webhook_conversion_cb (void *cls,
    849                        enum GNUNET_OS_ProcessStatusType status_type,
    850                        unsigned long code,
    851                        const json_t *result)
    852 {
    853   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    854   struct GNUNET_TIME_Absolute expiration;
    855   struct MHD_Response *resp;
    856 
    857   wh->econ = NULL;
    858   if ( (0 == code) &&
    859        (NULL == result) )
    860   {
    861     /* No result, but *our helper* was OK => bad input */
    862     GNUNET_break_op (0);
    863     json_dumpf (wh->json_response,
    864                 stderr,
    865                 JSON_INDENT (2));
    866     resp = TALER_MHD_MAKE_JSON_PACK (
    867       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    868                                wh->kycaid_response_code),
    869       GNUNET_JSON_pack_object_incref ("kycaid_body",
    870                                       (json_t *) wh->json_response));
    871     wh->cb (wh->cb_cls,
    872             wh->process_row,
    873             &wh->h_payto,
    874             wh->is_wallet,
    875             wh->pd->section,
    876             wh->applicant_id,
    877             wh->verification_id,
    878             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    879             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    880             NULL,
    881             MHD_HTTP_BAD_GATEWAY,
    882             resp);
    883     kycaid_webhook_cancel (wh);
    884     return;
    885   }
    886   if (NULL == result)
    887   {
    888     /* Failure in our helper */
    889     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    890                 "Helper exited with status code %d\n",
    891                 (int) code);
    892     json_dumpf (wh->json_response,
    893                 stderr,
    894                 JSON_INDENT (2));
    895     resp = TALER_MHD_MAKE_JSON_PACK (
    896       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    897                                wh->kycaid_response_code),
    898       GNUNET_JSON_pack_object_incref ("kycaid_body",
    899                                       (json_t *) wh->json_response));
    900     wh->cb (wh->cb_cls,
    901             wh->process_row,
    902             &wh->h_payto,
    903             wh->is_wallet,
    904             wh->pd->section,
    905             wh->applicant_id,
    906             wh->verification_id,
    907             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    908             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    909             NULL,
    910             MHD_HTTP_BAD_GATEWAY,
    911             resp);
    912     kycaid_webhook_cancel (wh);
    913     return;
    914   }
    915   if (! json_is_string (json_object_get (result,
    916                                          "FORM_ID")))
    917   {
    918     /* Failure in our helper */
    919     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    920                 "Mandatory FORM_ID not set in result\n");
    921     json_dumpf (result,
    922                 stderr,
    923                 JSON_INDENT (2));
    924     resp = TALER_MHD_MAKE_JSON_PACK (
    925       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    926                                wh->kycaid_response_code),
    927       GNUNET_JSON_pack_object_incref ("kycaid_body",
    928                                       (json_t *) wh->json_response));
    929     wh->cb (wh->cb_cls,
    930             wh->process_row,
    931             &wh->h_payto,
    932             wh->is_wallet,
    933             wh->pd->section,
    934             wh->applicant_id,
    935             wh->verification_id,
    936             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    937             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    938             NULL,
    939             MHD_HTTP_BAD_GATEWAY,
    940             resp);
    941     kycaid_webhook_cancel (wh);
    942     return;
    943   }
    944 
    945   expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
    946   resp = MHD_create_response_from_buffer_static (0,
    947                                                  "");
    948   wh->cb (wh->cb_cls,
    949           wh->process_row,
    950           &wh->h_payto,
    951           wh->is_wallet,
    952           wh->pd->section,
    953           wh->applicant_id,
    954           wh->verification_id,
    955           TALER_KYCLOGIC_STATUS_SUCCESS,
    956           expiration,
    957           result,
    958           MHD_HTTP_NO_CONTENT,
    959           resp);
    960   kycaid_webhook_cancel (wh);
    961 }
    962 
    963 
    964 /**
    965  * Function called when we're done processing the
    966  * HTTP "/applicants/{verification_id}" request.
    967  *
    968  * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
    969  * @param response_code HTTP response code, 0 on error
    970  * @param response parsed JSON result, NULL on error
    971  */
    972 static void
    973 handle_webhook_finished (void *cls,
    974                          long response_code,
    975                          const void *response)
    976 {
    977   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    978   const json_t *j = response;
    979   struct MHD_Response *resp;
    980 
    981   wh->job = NULL;
    982   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    983               "Webhook returned with HTTP status %u\n",
    984               (unsigned int) response_code);
    985   wh->kycaid_response_code = response_code;
    986   wh->json_response = json_incref ((json_t *) j);
    987   switch (response_code)
    988   {
    989   case MHD_HTTP_OK:
    990     {
    991       const char *profile_status;
    992 
    993       profile_status = json_string_value (
    994         json_object_get (
    995           j,
    996           "profile_status"));
    997       if (0 != strcasecmp ("valid",
    998                            profile_status))
    999       {
   1000         enum TALER_KYCLOGIC_KycStatus ks;
   1001 
   1002         ks = (0 == strcasecmp ("pending",
   1003                                profile_status))
   1004           ? TALER_KYCLOGIC_STATUS_PENDING
   1005           : TALER_KYCLOGIC_STATUS_USER_ABORTED;
   1006         resp = MHD_create_response_from_buffer_static (0,
   1007                                                        "");
   1008         wh->cb (wh->cb_cls,
   1009                 wh->process_row,
   1010                 &wh->h_payto,
   1011                 wh->is_wallet,
   1012                 wh->pd->section,
   1013                 wh->applicant_id,
   1014                 wh->verification_id,
   1015                 ks,
   1016                 GNUNET_TIME_UNIT_ZERO_ABS,
   1017                 NULL,
   1018                 MHD_HTTP_NO_CONTENT,
   1019                 resp);
   1020         break;
   1021       }
   1022       {
   1023         const char *argv[] = {
   1024           wh->pd->conversion_helper,
   1025           "-a",
   1026           wh->pd->auth_token,
   1027           NULL,
   1028         };
   1029 
   1030         wh->econ
   1031           = TALER_JSON_external_conversion_start (
   1032               j,
   1033               &webhook_conversion_cb,
   1034               wh,
   1035               wh->pd->conversion_helper,
   1036               argv);
   1037       }
   1038       if (NULL == wh->econ)
   1039       {
   1040         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1041                     "Failed to start KYCAID conversion helper `%s'\n",
   1042                     wh->pd->conversion_helper);
   1043         resp = TALER_MHD_make_error (
   1044           TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED,
   1045           NULL);
   1046         wh->cb (wh->cb_cls,
   1047                 wh->process_row,
   1048                 &wh->h_payto,
   1049                 wh->is_wallet,
   1050                 wh->pd->section,
   1051                 wh->applicant_id,
   1052                 wh->verification_id,
   1053                 TALER_KYCLOGIC_STATUS_INTERNAL_ERROR,
   1054                 GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1055                 NULL,
   1056                 MHD_HTTP_INTERNAL_SERVER_ERROR,
   1057                 resp);
   1058         break;
   1059       }
   1060       return;
   1061     }
   1062     break;
   1063   case MHD_HTTP_BAD_REQUEST:
   1064   case MHD_HTTP_NOT_FOUND:
   1065   case MHD_HTTP_CONFLICT:
   1066     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1067                 "KYCAID failed with response %u:\n",
   1068                 (unsigned int) response_code);
   1069     json_dumpf (j,
   1070                 stderr,
   1071                 JSON_INDENT (2));
   1072     resp = TALER_MHD_MAKE_JSON_PACK (
   1073       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
   1074                                response_code));
   1075     wh->cb (wh->cb_cls,
   1076             wh->process_row,
   1077             &wh->h_payto,
   1078             wh->is_wallet,
   1079             wh->pd->section,
   1080             wh->applicant_id,
   1081             wh->verification_id,
   1082             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
   1083             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1084             NULL,
   1085             MHD_HTTP_INTERNAL_SERVER_ERROR,
   1086             resp);
   1087     break;
   1088   case MHD_HTTP_UNAUTHORIZED:
   1089   case MHD_HTTP_PAYMENT_REQUIRED:
   1090     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1091                 "Refused access with HTTP status code %u\n",
   1092                 (unsigned int) response_code);
   1093     resp = TALER_MHD_MAKE_JSON_PACK (
   1094       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
   1095                                response_code),
   1096       GNUNET_JSON_pack_object_incref ("kycaid_body",
   1097                                       (json_t *) j));
   1098     wh->cb (wh->cb_cls,
   1099             wh->process_row,
   1100             &wh->h_payto,
   1101             wh->is_wallet,
   1102             wh->pd->section,
   1103             wh->applicant_id,
   1104             wh->verification_id,
   1105             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
   1106             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1107             NULL,
   1108             MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED,
   1109             resp);
   1110     break;
   1111   case MHD_HTTP_REQUEST_TIMEOUT:
   1112     resp = TALER_MHD_MAKE_JSON_PACK (
   1113       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
   1114                                response_code),
   1115       GNUNET_JSON_pack_object_incref ("kycaid_body",
   1116                                       (json_t *) j));
   1117     wh->cb (wh->cb_cls,
   1118             wh->process_row,
   1119             &wh->h_payto,
   1120             wh->is_wallet,
   1121             wh->pd->section,
   1122             wh->applicant_id,
   1123             wh->verification_id,
   1124             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
   1125             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1126             NULL,
   1127             MHD_HTTP_GATEWAY_TIMEOUT,
   1128             resp);
   1129     break;
   1130   case MHD_HTTP_UNPROCESSABLE_CONTENT: /* validation */
   1131     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1132                 "KYCAID failed with response %u:\n",
   1133                 (unsigned int) response_code);
   1134     json_dumpf (j,
   1135                 stderr,
   1136                 JSON_INDENT (2));
   1137     resp = TALER_MHD_MAKE_JSON_PACK (
   1138       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
   1139                                response_code),
   1140       GNUNET_JSON_pack_object_incref ("kycaid_body",
   1141                                       (json_t *) j));
   1142     wh->cb (wh->cb_cls,
   1143             wh->process_row,
   1144             &wh->h_payto,
   1145             wh->is_wallet,
   1146             wh->pd->section,
   1147             wh->applicant_id,
   1148             wh->verification_id,
   1149             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
   1150             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1151             NULL,
   1152             MHD_HTTP_BAD_GATEWAY,
   1153             resp);
   1154     break;
   1155   case MHD_HTTP_TOO_MANY_REQUESTS:
   1156     resp = TALER_MHD_MAKE_JSON_PACK (
   1157       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
   1158                                response_code),
   1159       GNUNET_JSON_pack_object_incref ("kycaid_body",
   1160                                       (json_t *) j));
   1161     wh->cb (wh->cb_cls,
   1162             wh->process_row,
   1163             &wh->h_payto,
   1164             wh->is_wallet,
   1165             wh->pd->section,
   1166             wh->applicant_id,
   1167             wh->verification_id,
   1168             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
   1169             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1170             NULL,
   1171             MHD_HTTP_SERVICE_UNAVAILABLE,
   1172             resp);
   1173     break;
   1174   case MHD_HTTP_INTERNAL_SERVER_ERROR:
   1175     resp = TALER_MHD_MAKE_JSON_PACK (
   1176       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
   1177                                response_code),
   1178       GNUNET_JSON_pack_object_incref ("kycaid_body",
   1179                                       (json_t *) j));
   1180     wh->cb (wh->cb_cls,
   1181             wh->process_row,
   1182             &wh->h_payto,
   1183             wh->is_wallet,
   1184             wh->pd->section,
   1185             wh->applicant_id,
   1186             wh->verification_id,
   1187             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
   1188             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1189             NULL,
   1190             MHD_HTTP_BAD_GATEWAY,
   1191             resp);
   1192     break;
   1193   default:
   1194     resp = TALER_MHD_MAKE_JSON_PACK (
   1195       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
   1196                                response_code),
   1197       GNUNET_JSON_pack_object_incref ("kycaid_body",
   1198                                       (json_t *) j));
   1199     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1200                 "Unexpected KYCAID response %u:\n",
   1201                 (unsigned int) response_code);
   1202     json_dumpf (j,
   1203                 stderr,
   1204                 JSON_INDENT (2));
   1205     wh->cb (wh->cb_cls,
   1206             wh->process_row,
   1207             &wh->h_payto,
   1208             wh->is_wallet,
   1209             wh->pd->section,
   1210             wh->applicant_id,
   1211             wh->verification_id,
   1212             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
   1213             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1214             NULL,
   1215             MHD_HTTP_BAD_GATEWAY,
   1216             resp);
   1217     break;
   1218   }
   1219   kycaid_webhook_cancel (wh);
   1220 }
   1221 
   1222 
   1223 /**
   1224  * Asynchronously return a reply for the webhook.
   1225  *
   1226  * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
   1227  */
   1228 static void
   1229 async_webhook_reply (void *cls)
   1230 {
   1231   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
   1232 
   1233   wh->task = NULL;
   1234   wh->cb (wh->cb_cls,
   1235           wh->process_row,
   1236           (0 == wh->process_row)
   1237           ? NULL
   1238           : &wh->h_payto,
   1239           wh->is_wallet,
   1240           wh->pd->section,
   1241           wh->applicant_id, /* provider user ID */
   1242           wh->verification_id, /* provider legi ID */
   1243           TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
   1244           GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
   1245           NULL,
   1246           wh->response_code,
   1247           wh->resp);
   1248   kycaid_webhook_cancel (wh);
   1249 }
   1250 
   1251 
   1252 /**
   1253  * Check KYC status and return result for Webhook.  We do NOT implement the
   1254  * authentication check proposed by the KYCAID documentation, as it would
   1255  * allow an attacker who learns the access token to easily bypass the KYC
   1256  * checks. Instead, we insist on explicitly requesting the KYC status from the
   1257  * provider (at least on success).
   1258  *
   1259  * @param cls the @e cls of this struct with the plugin-specific state
   1260  * @param pd provider configuration details
   1261  * @param plc callback to lookup accounts with
   1262  * @param plc_cls closure for @a plc
   1263  * @param http_method HTTP method used for the webhook
   1264  * @param url_path rest of the URL after `/kyc-webhook/`
   1265  * @param connection MHD connection object (for HTTP headers)
   1266  * @param body HTTP request body
   1267  * @param cb function to call with the result
   1268  * @param cb_cls closure for @a cb
   1269  * @return handle to cancel operation early
   1270  */
   1271 static struct TALER_KYCLOGIC_WebhookHandle *
   1272 kycaid_webhook (void *cls,
   1273                 const struct TALER_KYCLOGIC_ProviderDetails *pd,
   1274                 TALER_KYCLOGIC_ProviderLookupCallback plc,
   1275                 void *plc_cls,
   1276                 const char *http_method,
   1277                 const char *const url_path[],
   1278                 struct MHD_Connection *connection,
   1279                 const json_t *body,
   1280                 TALER_KYCLOGIC_WebhookCallback cb,
   1281                 void *cb_cls)
   1282 {
   1283   struct PluginState *ps = cls;
   1284   struct TALER_KYCLOGIC_WebhookHandle *wh;
   1285   CURL *eh;
   1286   const char *request_id;
   1287   const char *type;
   1288   const char *verification_id; /* = provider_legitimization_id */
   1289   const char *applicant_id;
   1290   const char *form_id;
   1291   const char *status = NULL;
   1292   bool verified = false;
   1293   bool no_verified = true;
   1294   const json_t *verifications = NULL;
   1295   struct GNUNET_JSON_Specification spec[] = {
   1296     GNUNET_JSON_spec_string ("request_id",
   1297                              &request_id),
   1298     GNUNET_JSON_spec_string ("type",
   1299                              &type),
   1300     GNUNET_JSON_spec_string ("verification_id",
   1301                              &verification_id),
   1302     GNUNET_JSON_spec_string ("applicant_id",
   1303                              &applicant_id),
   1304     GNUNET_JSON_spec_string ("form_id",
   1305                              &form_id),
   1306     GNUNET_JSON_spec_mark_optional (
   1307       GNUNET_JSON_spec_string ("status",
   1308                                &status),
   1309       NULL),
   1310     GNUNET_JSON_spec_mark_optional (
   1311       GNUNET_JSON_spec_bool ("verified",
   1312                              &verified),
   1313       &no_verified),
   1314     GNUNET_JSON_spec_mark_optional (
   1315       GNUNET_JSON_spec_object_const ("verifications",
   1316                                      &verifications),
   1317       NULL),
   1318     GNUNET_JSON_spec_end ()
   1319   };
   1320   enum GNUNET_DB_QueryStatus qs;
   1321 
   1322   wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
   1323   wh->cb = cb;
   1324   wh->cb_cls = cb_cls;
   1325   wh->ps = ps;
   1326   wh->pd = pd;
   1327   wh->connection = connection;
   1328   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1329               "KYCAID webhook of `%s' triggered with %s\n",
   1330               pd->section,
   1331               http_method);
   1332 #if 1
   1333   if (NULL != body)
   1334     json_dumpf (body,
   1335                 stderr,
   1336                 JSON_INDENT (2));
   1337 #endif
   1338   if (NULL == pd)
   1339   {
   1340     GNUNET_break_op (0);
   1341     json_dumpf (body,
   1342                 stderr,
   1343                 JSON_INDENT (2));
   1344     wh->resp = TALER_MHD_make_error (
   1345       TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
   1346       "kycaid");
   1347     wh->response_code = MHD_HTTP_NOT_FOUND;
   1348     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
   1349                                          wh);
   1350     return wh;
   1351   }
   1352 
   1353   if (GNUNET_OK !=
   1354       GNUNET_JSON_parse (body,
   1355                          spec,
   1356                          NULL, NULL))
   1357   {
   1358     GNUNET_break_op (0);
   1359     json_dumpf (body,
   1360                 stderr,
   1361                 JSON_INDENT (2));
   1362     wh->resp = TALER_MHD_MAKE_JSON_PACK (
   1363       GNUNET_JSON_pack_object_incref ("webhook_body",
   1364                                       (json_t *) body));
   1365     wh->response_code = MHD_HTTP_BAD_REQUEST;
   1366     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
   1367                                          wh);
   1368     return wh;
   1369   }
   1370   qs = plc (plc_cls,
   1371             pd->section,
   1372             verification_id,
   1373             &wh->h_payto,
   1374             &wh->is_wallet,
   1375             &wh->process_row);
   1376   if (qs < 0)
   1377   {
   1378     wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
   1379                                      "provider-legitimization-lookup");
   1380     wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1381     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
   1382                                          wh);
   1383     return wh;
   1384   }
   1385   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   1386   {
   1387     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1388                 "Received webhook for unknown verification ID `%s' and section `%s'\n",
   1389                 verification_id,
   1390                 pd->section);
   1391     wh->resp = TALER_MHD_make_error (
   1392       TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
   1393       verification_id);
   1394     wh->response_code = MHD_HTTP_NOT_FOUND;
   1395     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
   1396                                          wh);
   1397     return wh;
   1398   }
   1399   wh->verification_id = GNUNET_strdup (verification_id);
   1400   wh->applicant_id = GNUNET_strdup (applicant_id);
   1401   if ( (0 != strcasecmp (type,
   1402                          "VERIFICATION_COMPLETED")) ||
   1403        (no_verified) ||
   1404        (! verified) )
   1405   {
   1406     /* We don't need to re-confirm the failure by
   1407        asking the API again. */
   1408     log_failure (verifications);
   1409     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1410                 "Webhook called with non-completion status: %s\n",
   1411                 type);
   1412     wh->response_code = MHD_HTTP_NO_CONTENT;
   1413     wh->resp = MHD_create_response_from_buffer_static (0,
   1414                                                        "");
   1415     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
   1416                                          wh);
   1417     return wh;
   1418   }
   1419 
   1420   eh = curl_easy_init ();
   1421   if (NULL == eh)
   1422   {
   1423     GNUNET_break (0);
   1424     wh->resp = TALER_MHD_make_error (
   1425       TALER_EC_GENERIC_ALLOCATION_FAILURE,
   1426       NULL);
   1427     wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1428     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
   1429                                          wh);
   1430     return wh;
   1431   }
   1432 
   1433   GNUNET_asprintf (&wh->url,
   1434                    "https://api.kycaid.com/applicants/%s",
   1435                    applicant_id);
   1436   GNUNET_break (CURLE_OK ==
   1437                 curl_easy_setopt (eh,
   1438                                   CURLOPT_VERBOSE,
   1439                                   0));
   1440   GNUNET_assert (CURLE_OK ==
   1441                  curl_easy_setopt (eh,
   1442                                    CURLOPT_MAXREDIRS,
   1443                                    1L));
   1444   GNUNET_break (CURLE_OK ==
   1445                 curl_easy_setopt (eh,
   1446                                   CURLOPT_URL,
   1447                                   wh->url));
   1448   wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
   1449                                   eh,
   1450                                   pd->slist,
   1451                                   &handle_webhook_finished,
   1452                                   wh);
   1453   return wh;
   1454 }
   1455 
   1456 
   1457 /**
   1458  * Initialize kycaid logic plugin
   1459  *
   1460  * @param cls a configuration instance
   1461  * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
   1462  */
   1463 void *
   1464 libtaler_plugin_kyclogic_kycaid_init (void *cls);
   1465 
   1466 /* declaration to avoid compiler warning */
   1467 void *
   1468 libtaler_plugin_kyclogic_kycaid_init (void *cls)
   1469 {
   1470   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
   1471   struct TALER_KYCLOGIC_Plugin *plugin;
   1472   struct PluginState *ps;
   1473 
   1474   ps = GNUNET_new (struct PluginState);
   1475   ps->cfg = cfg;
   1476   if (GNUNET_OK !=
   1477       GNUNET_CONFIGURATION_get_value_string (cfg,
   1478                                              "exchange",
   1479                                              "BASE_URL",
   1480                                              &ps->exchange_base_url))
   1481   {
   1482     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1483                                "exchange",
   1484                                "BASE_URL");
   1485     GNUNET_free (ps);
   1486     return NULL;
   1487   }
   1488 
   1489   ps->curl_ctx
   1490     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1491                         &ps->curl_rc);
   1492   if (NULL == ps->curl_ctx)
   1493   {
   1494     GNUNET_break (0);
   1495     GNUNET_free (ps->exchange_base_url);
   1496     GNUNET_free (ps);
   1497     return NULL;
   1498   }
   1499   ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
   1500 
   1501   plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
   1502   plugin->cls = ps;
   1503   plugin->load_configuration
   1504     = &kycaid_load_configuration;
   1505   plugin->unload_configuration
   1506     = &kycaid_unload_configuration;
   1507   plugin->initiate
   1508     = &kycaid_initiate;
   1509   plugin->initiate_cancel
   1510     = &kycaid_initiate_cancel;
   1511   plugin->proof
   1512     = &kycaid_proof;
   1513   plugin->proof_cancel
   1514     = &kycaid_proof_cancel;
   1515   plugin->webhook
   1516     = &kycaid_webhook;
   1517   plugin->webhook_cancel
   1518     = &kycaid_webhook_cancel;
   1519   return plugin;
   1520 }
   1521 
   1522 
   1523 /**
   1524  * Unload authorization plugin
   1525  *
   1526  * @param cls a `struct TALER_KYCLOGIC_Plugin`
   1527  * @return NULL (always)
   1528  */
   1529 void *
   1530 libtaler_plugin_kyclogic_kycaid_done (void *cls);
   1531 
   1532 /* declaration to avoid compiler warning */
   1533 void *
   1534 libtaler_plugin_kyclogic_kycaid_done (void *cls)
   1535 {
   1536   struct TALER_KYCLOGIC_Plugin *plugin = cls;
   1537   struct PluginState *ps = plugin->cls;
   1538 
   1539   if (NULL != ps->curl_ctx)
   1540   {
   1541     GNUNET_CURL_fini (ps->curl_ctx);
   1542     ps->curl_ctx = NULL;
   1543   }
   1544   if (NULL != ps->curl_rc)
   1545   {
   1546     GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
   1547     ps->curl_rc = NULL;
   1548   }
   1549   GNUNET_free (ps->exchange_base_url);
   1550   GNUNET_free (ps);
   1551   GNUNET_free (plugin);
   1552   return NULL;
   1553 }