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


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