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


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