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


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