exchange

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

plugin_kyclogic_oauth2.c (65820B)


      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_oauth2.c
     18  * @brief oauth2.0 based authentication flow logic
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/taler_kyclogic_plugin.h"
     22 #include "taler/taler_mhd_lib.h"
     23 #include "taler/taler_templating_lib.h"
     24 #include "taler/taler_curl_lib.h"
     25 #include "taler/taler_json_lib.h"
     26 #include <regex.h>
     27 #include "taler/taler_util.h"
     28 
     29 /**
     30  * Set to 1 to get extra-verbose, possibly privacy-sensitive
     31  * data in the logs.
     32  */
     33 #define DEBUG 0
     34 
     35 /**
     36  * Saves the state of a plugin.
     37  */
     38 struct PluginState
     39 {
     40 
     41   /**
     42    * Our global configuration.
     43    */
     44   const struct GNUNET_CONFIGURATION_Handle *cfg;
     45 
     46   /**
     47    * Our base URL.
     48    */
     49   char *exchange_base_url;
     50 
     51   /**
     52    * Context for CURL operations (useful to the event loop)
     53    */
     54   struct GNUNET_CURL_Context *curl_ctx;
     55 
     56   /**
     57    * Context for integrating @e curl_ctx with the
     58    * GNUnet event loop.
     59    */
     60   struct GNUNET_CURL_RescheduleContext *curl_rc;
     61 
     62 };
     63 
     64 
     65 /**
     66  * Keeps the plugin-specific state for
     67  * a given configuration section.
     68  */
     69 struct TALER_KYCLOGIC_ProviderDetails
     70 {
     71 
     72   /**
     73    * Overall plugin state.
     74    */
     75   struct PluginState *ps;
     76 
     77   /**
     78    * Configuration section that configured us.
     79    */
     80   char *section;
     81 
     82   /**
     83    * URL of the Challenger ``/setup`` endpoint for
     84    * approving address validations. NULL if not used.
     85    */
     86   char *setup_url;
     87 
     88   /**
     89    * URL of the OAuth2.0 endpoint for KYC checks.
     90    */
     91   char *authorize_url;
     92 
     93   /**
     94    * URL of the OAuth2.0 endpoint for KYC checks.
     95    * (token/auth)
     96    */
     97   char *token_url;
     98 
     99   /**
    100    * URL of the user info access endpoint.
    101    */
    102   char *info_url;
    103 
    104   /**
    105    * Our client ID for OAuth2.0.
    106    */
    107   char *client_id;
    108 
    109   /**
    110    * Our client secret for OAuth2.0.
    111    */
    112   char *client_secret;
    113 
    114   /**
    115    * OAuth2 scope, NULL if not used
    116    */
    117   char *scope;
    118 
    119   /**
    120    * Where to redirect clients after the
    121    * Web-based KYC process is done?
    122    */
    123   char *post_kyc_redirect_url;
    124 
    125   /**
    126    * Name of the program we use to convert outputs
    127    * from OAuth2 outputs into our JSON inputs.
    128    */
    129   char *conversion_binary;
    130 
    131   /**
    132    * Validity time for a successful KYC process.
    133    */
    134   struct GNUNET_TIME_Relative validity;
    135 
    136   /**
    137    * Set to true if we are operating in DEBUG
    138    * mode and may return private details in HTML
    139    * responses to make diagnostics easier.
    140    */
    141   bool debug_mode;
    142 };
    143 
    144 
    145 /**
    146  * Handle for an initiation operation.
    147  */
    148 struct TALER_KYCLOGIC_InitiateHandle
    149 {
    150 
    151   /**
    152    * Hash of the payto:// URI we are initiating
    153    * the KYC for.
    154    */
    155   struct TALER_NormalizedPaytoHashP h_payto;
    156 
    157   /**
    158    * UUID being checked.
    159    */
    160   uint64_t legitimization_uuid;
    161 
    162   /**
    163    * Our configuration details.
    164    */
    165   const struct TALER_KYCLOGIC_ProviderDetails *pd;
    166 
    167   /**
    168    * The task for asynchronous response generation.
    169    */
    170   struct GNUNET_SCHEDULER_Task *task;
    171 
    172   /**
    173    * Handle for the OAuth 2.0 setup request.
    174    */
    175   struct GNUNET_CURL_Job *job;
    176 
    177   /**
    178    * Continuation to call.
    179    */
    180   TALER_KYCLOGIC_InitiateCallback cb;
    181 
    182   /**
    183    * Closure for @a cb.
    184    */
    185   void *cb_cls;
    186 
    187   /**
    188    * Initial address to pass to the KYC provider on ``/setup``.
    189    */
    190   json_t *initial_address;
    191 
    192   /**
    193    * Expiration reported by a dynamic /setup endpoint.
    194    */
    195   struct GNUNET_TIME_Timestamp process_expiration;
    196 
    197   /**
    198    * Context for #TEH_curl_easy_post(). Keeps the data that must
    199    * persist for Curl to make the upload.
    200    */
    201   struct TALER_CURL_PostContext ctx;
    202 
    203 };
    204 
    205 
    206 /**
    207  * Handle for an KYC proof operation.
    208  */
    209 struct TALER_KYCLOGIC_ProofHandle
    210 {
    211 
    212   /**
    213    * Our configuration details.
    214    */
    215   const struct TALER_KYCLOGIC_ProviderDetails *pd;
    216 
    217   /**
    218    * HTTP connection we are processing.
    219    */
    220   struct MHD_Connection *connection;
    221 
    222   /**
    223    * Handle to an external process that converts the
    224    * Persona response to our internal format.
    225    */
    226   struct TALER_JSON_ExternalConversion *ec;
    227 
    228   /**
    229    * Hash of the payto URI that this is about.
    230    */
    231   struct TALER_NormalizedPaytoHashP h_payto;
    232 
    233   /**
    234    * Continuation to call.
    235    */
    236   TALER_KYCLOGIC_ProofCallback cb;
    237 
    238   /**
    239    * Closure for @e cb.
    240    */
    241   void *cb_cls;
    242 
    243   /**
    244    * Curl request we are running to the OAuth 2.0 service.
    245    */
    246   CURL *eh;
    247 
    248   /**
    249    * Body for the @e eh POST request.
    250    */
    251   char *post_body;
    252 
    253   /**
    254    * OAuth2 ``state`` of the process, see compute_state().
    255    */
    256   char *state;
    257 
    258   /**
    259    * KYC attributes returned about the user by the OAuth 2.0 server.
    260    */
    261   json_t *attributes;
    262 
    263   /**
    264    * Response to return.
    265    */
    266   struct MHD_Response *response;
    267 
    268   /**
    269    * The task for asynchronous response generation.
    270    */
    271   struct GNUNET_SCHEDULER_Task *task;
    272 
    273   /**
    274    * Handle for the OAuth 2.0 CURL request.
    275    */
    276   struct GNUNET_CURL_Job *job;
    277 
    278   /**
    279    * User ID to return, the 'id' from OAuth.
    280    */
    281   char *provider_user_id;
    282 
    283   /**
    284    * Legitimization ID to return, the 64-bit row ID
    285    * as a string.
    286    */
    287   char provider_legitimization_id[32];
    288 
    289   /**
    290    * KYC status to return.
    291    */
    292   enum TALER_KYCLOGIC_KycStatus status;
    293 
    294   /**
    295    * HTTP status to return.
    296    */
    297   unsigned int http_status;
    298 
    299 
    300 };
    301 
    302 
    303 /**
    304  * Handle for an KYC Web hook operation.
    305  */
    306 struct TALER_KYCLOGIC_WebhookHandle
    307 {
    308 
    309   /**
    310    * Continuation to call when done.
    311    */
    312   TALER_KYCLOGIC_WebhookCallback cb;
    313 
    314   /**
    315    * Closure for @a cb.
    316    */
    317   void *cb_cls;
    318 
    319   /**
    320    * Task for asynchronous execution.
    321    */
    322   struct GNUNET_SCHEDULER_Task *task;
    323 
    324   /**
    325    * Overall plugin state.
    326    */
    327   struct PluginState *ps;
    328 };
    329 
    330 
    331 /**
    332  * Release configuration resources previously loaded
    333  *
    334  * @param[in] pd configuration to release
    335  */
    336 static void
    337 oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
    338 {
    339   GNUNET_free (pd->section);
    340   GNUNET_free (pd->token_url);
    341   GNUNET_free (pd->setup_url);
    342   GNUNET_free (pd->authorize_url);
    343   GNUNET_free (pd->info_url);
    344   GNUNET_free (pd->client_id);
    345   GNUNET_free (pd->client_secret);
    346   GNUNET_free (pd->scope);
    347   GNUNET_free (pd->post_kyc_redirect_url);
    348   GNUNET_free (pd->conversion_binary);
    349   GNUNET_free (pd);
    350 }
    351 
    352 
    353 /**
    354  * Check that @a url, given in option @a option of section @a section, is
    355  * an http(s) URL acceptable for an OAuth 2.0 endpoint.  RFC 6749 (Sections
    356  * 3.1, 3.2 and 10.9) requires TLS for these endpoints, as otherwise the
    357  * client secret, authorization codes and access tokens are transmitted in
    358  * the clear.  Hence a plaintext "http://" URL is refused unless
    359  * @a debug_mode is set, in which case it is accepted with a warning.
    360  *
    361  * @param section configuration section that configured @a url
    362  * @param option configuration option that configured @a url
    363  * @param url URL that was configured
    364  * @param debug_mode true if KYC_OAUTH2_DEBUG_MODE is set
    365  * @return true if @a url may be used
    366  */
    367 static bool
    368 check_endpoint_url (const char *section,
    369                     const char *option,
    370                     const char *url,
    371                     bool debug_mode)
    372 {
    373   if (! TALER_url_valid_charset (url))
    374   {
    375     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    376                                section,
    377                                option,
    378                                "not a valid URL");
    379     return false;
    380   }
    381   if (0 == strncasecmp (url,
    382                         "https://",
    383                         strlen ("https://")))
    384     return true;
    385   if (0 != strncasecmp (url,
    386                         "http://",
    387                         strlen ("http://")))
    388   {
    389     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    390                                section,
    391                                option,
    392                                "not a valid URL");
    393     return false;
    394   }
    395   if (! debug_mode)
    396   {
    397     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    398                                section,
    399                                option,
    400                                "OAuth 2.0 endpoints must use https:// (RFC 6749); plaintext http:// is only accepted with KYC_OAUTH2_DEBUG_MODE = YES");
    401     return false;
    402   }
    403   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    404               "KYC_OAUTH2_DEBUG_MODE is set: accepting plaintext http:// endpoint `%s' in option `%s' of section `%s'; the client secret, authorization codes and access tokens will be transmitted in the clear. Never use this in production!\n",
    405               url,
    406               option,
    407               section);
    408   return true;
    409 }
    410 
    411 
    412 /**
    413  * Load the configuration of the KYC provider.
    414  *
    415  * @param cls closure
    416  * @param provider_section_name configuration section to parse
    417  * @return NULL if configuration is invalid
    418  */
    419 static struct TALER_KYCLOGIC_ProviderDetails *
    420 oauth2_load_configuration (void *cls,
    421                            const char *provider_section_name)
    422 {
    423   struct PluginState *ps = cls;
    424   struct TALER_KYCLOGIC_ProviderDetails *pd;
    425   char *s;
    426 
    427   pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
    428   pd->ps = ps;
    429   pd->section = GNUNET_strdup (provider_section_name);
    430   /* parse debug mode first, it decides whether http:// URLs are allowed */
    431   if (GNUNET_OK ==
    432       GNUNET_CONFIGURATION_get_value_yesno (ps->cfg,
    433                                             provider_section_name,
    434                                             "KYC_OAUTH2_DEBUG_MODE"))
    435     pd->debug_mode = true;
    436   if (GNUNET_OK !=
    437       GNUNET_CONFIGURATION_get_value_time (ps->cfg,
    438                                            provider_section_name,
    439                                            "KYC_OAUTH2_VALIDITY",
    440                                            &pd->validity))
    441   {
    442     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    443                                provider_section_name,
    444                                "KYC_OAUTH2_VALIDITY");
    445     oauth2_unload_configuration (pd);
    446     return NULL;
    447   }
    448 
    449   if (GNUNET_OK !=
    450       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    451                                              provider_section_name,
    452                                              "KYC_OAUTH2_CLIENT_ID",
    453                                              &s))
    454   {
    455     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    456                                provider_section_name,
    457                                "KYC_OAUTH2_CLIENT_ID");
    458     oauth2_unload_configuration (pd);
    459     return NULL;
    460   }
    461   pd->client_id = s;
    462 
    463   if (GNUNET_OK ==
    464       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    465                                              provider_section_name,
    466                                              "KYC_OAUTH2_SCOPE",
    467                                              &s))
    468   {
    469     pd->scope = s;
    470   }
    471 
    472   if (GNUNET_OK !=
    473       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    474                                              provider_section_name,
    475                                              "KYC_OAUTH2_TOKEN_URL",
    476                                              &s))
    477   {
    478     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    479                                provider_section_name,
    480                                "KYC_OAUTH2_TOKEN_URL");
    481     oauth2_unload_configuration (pd);
    482     return NULL;
    483   }
    484   if (! check_endpoint_url (provider_section_name,
    485                             "KYC_OAUTH2_TOKEN_URL",
    486                             s,
    487                             pd->debug_mode))
    488   {
    489     GNUNET_free (s);
    490     oauth2_unload_configuration (pd);
    491     return NULL;
    492   }
    493   pd->token_url = s;
    494 
    495   if (GNUNET_OK !=
    496       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    497                                              provider_section_name,
    498                                              "KYC_OAUTH2_AUTHORIZE_URL",
    499                                              &s))
    500   {
    501     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    502                                provider_section_name,
    503                                "KYC_OAUTH2_AUTHORIZE_URL");
    504     oauth2_unload_configuration (pd);
    505     return NULL;
    506   }
    507   if (! check_endpoint_url (provider_section_name,
    508                             "KYC_OAUTH2_AUTHORIZE_URL",
    509                             s,
    510                             pd->debug_mode))
    511   {
    512     oauth2_unload_configuration (pd);
    513     GNUNET_free (s);
    514     return NULL;
    515   }
    516   if (NULL != strchr (s, '#'))
    517   {
    518     const char *extra = strchr (s, '#');
    519     const char *slash = strrchr (s, '/');
    520 
    521     if ( (0 != strcasecmp (extra,
    522                            "#setup")) ||
    523          (NULL == slash) )
    524     {
    525       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    526                                  provider_section_name,
    527                                  "KYC_OAUTH2_AUTHORIZE_URL",
    528                                  "not a valid authorze URL (bad fragment)");
    529       oauth2_unload_configuration (pd);
    530       GNUNET_free (s);
    531       return NULL;
    532     }
    533     pd->authorize_url = GNUNET_strndup (s,
    534                                         extra - s);
    535     GNUNET_asprintf (&pd->setup_url,
    536                      "%.*s/setup/%s",
    537                      (int) (slash - s),
    538                      s,
    539                      pd->client_id);
    540     GNUNET_free (s);
    541   }
    542   else
    543   {
    544     pd->authorize_url = s;
    545   }
    546 
    547   if (GNUNET_OK !=
    548       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    549                                              provider_section_name,
    550                                              "KYC_OAUTH2_INFO_URL",
    551                                              &s))
    552   {
    553     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    554                                provider_section_name,
    555                                "KYC_OAUTH2_INFO_URL");
    556     oauth2_unload_configuration (pd);
    557     return NULL;
    558   }
    559   if (! check_endpoint_url (provider_section_name,
    560                             "KYC_OAUTH2_INFO_URL",
    561                             s,
    562                             pd->debug_mode))
    563   {
    564     GNUNET_free (s);
    565     oauth2_unload_configuration (pd);
    566     return NULL;
    567   }
    568   pd->info_url = s;
    569 
    570   if (GNUNET_OK !=
    571       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    572                                              provider_section_name,
    573                                              "KYC_OAUTH2_CLIENT_SECRET",
    574                                              &s))
    575   {
    576     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    577                                provider_section_name,
    578                                "KYC_OAUTH2_CLIENT_SECRET");
    579     oauth2_unload_configuration (pd);
    580     return NULL;
    581   }
    582   pd->client_secret = s;
    583 
    584   if (GNUNET_OK !=
    585       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    586                                              provider_section_name,
    587                                              "KYC_OAUTH2_POST_URL",
    588                                              &s))
    589   {
    590     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    591                                provider_section_name,
    592                                "KYC_OAUTH2_POST_URL");
    593     oauth2_unload_configuration (pd);
    594     return NULL;
    595   }
    596   pd->post_kyc_redirect_url = s;
    597 
    598   if (GNUNET_OK !=
    599       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    600                                              provider_section_name,
    601                                              "KYC_OAUTH2_CONVERTER_HELPER",
    602                                              &pd->conversion_binary))
    603   {
    604     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    605                                provider_section_name,
    606                                "KYC_OAUTH2_CONVERTER_HELPER");
    607     oauth2_unload_configuration (pd);
    608     return NULL;
    609   }
    610 
    611   return pd;
    612 }
    613 
    614 
    615 /**
    616  * Cancel KYC check initiation.
    617  *
    618  * @param[in] ih handle of operation to cancel
    619  */
    620 static void
    621 oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
    622 {
    623   if (NULL != ih->task)
    624   {
    625     GNUNET_SCHEDULER_cancel (ih->task);
    626     ih->task = NULL;
    627   }
    628   if (NULL != ih->job)
    629   {
    630     GNUNET_CURL_job_cancel (ih->job);
    631     ih->job = NULL;
    632   }
    633   TALER_curl_easy_post_finished (&ih->ctx);
    634   json_decref (ih->initial_address);
    635   GNUNET_free (ih);
    636 }
    637 
    638 
    639 /**
    640  * Compute the OAuth2 ``state`` for a KYC process.  Next to the
    641  * account the ``/kyc-proof`` handler needs to find the process, it
    642  * carries a tag that only we and the provider can compute.  The state
    643  * is thus unguessable and bound to the process, as RFC 6749 (section
    644  * 10.12) requires: whoever did not see the authorization request can
    645  * neither fail the process with a forged error redirect nor complete
    646  * it with an authorization code obtained for another process.
    647  *
    648  * @param pd provider the process runs with, its client secret keys the tag
    649  * @param h_payto account the process is for
    650  * @param process_row row of the process in the legitimization processes table
    651  * @return the state, "$H_PAYTO-$TAG"
    652  */
    653 static char *
    654 compute_state (const struct TALER_KYCLOGIC_ProviderDetails *pd,
    655                const struct TALER_NormalizedPaytoHashP *h_payto,
    656                uint64_t process_row)
    657 {
    658   static const char context[] = "taler-kyc-oauth2-state";
    659   char msg[sizeof (context) + sizeof (*h_payto) + sizeof (uint64_t)];
    660   uint64_t row_nbo = GNUNET_htonll (process_row);
    661   struct GNUNET_HashCode hmac;
    662   struct GNUNET_ShortHashCode tag;
    663   char *hps;
    664   char *tags;
    665   char *state;
    666 
    667   GNUNET_static_assert (sizeof (tag) <= sizeof (hmac));
    668   memcpy (msg,
    669           context,
    670           sizeof (context));
    671   memcpy (&msg[sizeof (context)],
    672           h_payto,
    673           sizeof (*h_payto));
    674   memcpy (&msg[sizeof (context) + sizeof (*h_payto)],
    675           &row_nbo,
    676           sizeof (row_nbo));
    677   GNUNET_CRYPTO_hmac_raw ((const unsigned char *) pd->client_secret,
    678                           strlen (pd->client_secret),
    679                           msg,
    680                           sizeof (msg),
    681                           &hmac);
    682   memcpy (&tag,
    683           &hmac,
    684           sizeof (tag));
    685   hps = GNUNET_STRINGS_data_to_string_alloc (h_payto,
    686                                              sizeof (*h_payto));
    687   tags = GNUNET_STRINGS_data_to_string_alloc (&tag,
    688                                               sizeof (tag));
    689   GNUNET_asprintf (&state,
    690                    "%s-%s",
    691                    hps,
    692                    tags);
    693   GNUNET_free (tags);
    694   GNUNET_free (hps);
    695   return state;
    696 }
    697 
    698 
    699 /**
    700  * Logic to asynchronously return the response for
    701  * how to begin the OAuth2.0 checking process to
    702  * the client.
    703  *
    704  * @param ih process to redirect for
    705  * @param authorize_url authorization URL to use
    706  */
    707 static void
    708 initiate_with_url (struct TALER_KYCLOGIC_InitiateHandle *ih,
    709                    const char *authorize_url)
    710 {
    711 
    712   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
    713   struct PluginState *ps = pd->ps;
    714   char *state;
    715   char *url;
    716   char legi_s[42];
    717 
    718   GNUNET_snprintf (legi_s,
    719                    sizeof (legi_s),
    720                    "%llu",
    721                    (unsigned long long) ih->legitimization_uuid);
    722   state = compute_state (pd,
    723                          &ih->h_payto,
    724                          ih->legitimization_uuid);
    725   {
    726     char *redirect_uri_encoded;
    727     char *client_id_encoded;
    728     char *scope_encoded;
    729 
    730     {
    731       char *redirect_uri;
    732 
    733       GNUNET_asprintf (&redirect_uri,
    734                        "%skyc-proof/%s",
    735                        ps->exchange_base_url,
    736                        &pd->section[strlen ("kyc-provider-")]);
    737       redirect_uri_encoded = TALER_urlencode (redirect_uri);
    738       GNUNET_free (redirect_uri);
    739     }
    740     client_id_encoded = TALER_urlencode (pd->client_id);
    741     scope_encoded = TALER_urlencode (NULL != pd->scope
    742                                      ? pd->scope
    743                                      : "");
    744     GNUNET_asprintf (&url,
    745                      "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s&scope=%s",
    746                      authorize_url,
    747                      client_id_encoded,
    748                      redirect_uri_encoded,
    749                      state,
    750                      scope_encoded);
    751     GNUNET_free (scope_encoded);
    752     GNUNET_free (client_id_encoded);
    753     GNUNET_free (redirect_uri_encoded);
    754   }
    755   ih->cb (ih->cb_cls,
    756           TALER_EC_NONE,
    757           url,
    758           NULL /* unknown user_id here */,
    759           legi_s,
    760           NULL /* no error */);
    761   GNUNET_free (url);
    762   GNUNET_free (state);
    763   oauth2_initiate_cancel (ih);
    764 }
    765 
    766 
    767 /**
    768  * After we are done with the CURL interaction we
    769  * need to update our database state with the information
    770  * retrieved.
    771  *
    772  * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
    773  * @param response_code HTTP response code from server, 0 on hard error
    774  * @param response in JSON, NULL if response was not in JSON format
    775  */
    776 static void
    777 handle_curl_setup_finished (void *cls,
    778                             long response_code,
    779                             const void *response)
    780 {
    781   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
    782   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
    783   const json_t *j = response;
    784 
    785   ih->job = NULL;
    786   switch (response_code)
    787   {
    788   case 0:
    789     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    790                 "/setup URL failed to return HTTP response\n");
    791     ih->cb (ih->cb_cls,
    792             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    793             NULL,
    794             NULL,
    795             NULL,
    796             "/setup request to OAuth 2.0 backend returned no response");
    797     oauth2_initiate_cancel (ih);
    798     return;
    799   case MHD_HTTP_OK:
    800     {
    801       const char *nonce;
    802       bool no_expiration;
    803       struct GNUNET_JSON_Specification spec[] = {
    804         GNUNET_JSON_spec_string ("nonce",
    805                                  &nonce),
    806         GNUNET_JSON_spec_mark_optional (
    807           GNUNET_JSON_spec_timestamp ("expires",
    808                                       &ih->process_expiration),
    809           &no_expiration),
    810         GNUNET_JSON_spec_end ()
    811       };
    812       enum GNUNET_GenericReturnValue res;
    813       const char *emsg;
    814       unsigned int line;
    815       char *url;
    816 
    817       res = GNUNET_JSON_parse (j,
    818                                spec,
    819                                &emsg,
    820                                &line);
    821       if (GNUNET_OK != res)
    822       {
    823         GNUNET_break_op (0);
    824         json_dumpf (j,
    825                     stderr,
    826                     JSON_INDENT (2));
    827         ih->cb (ih->cb_cls,
    828                 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    829                 NULL,
    830                 NULL,
    831                 NULL,
    832                 "Unexpected response from KYC gateway: setup must return a nonce");
    833         oauth2_initiate_cancel (ih);
    834         return;
    835       }
    836       if ( (! no_expiration) &&
    837            GNUNET_TIME_timestamp_cmp (
    838              ih->process_expiration,
    839              <=,
    840              GNUNET_TIME_timestamp_get ()) )
    841       {
    842         GNUNET_break_op (0);
    843         ih->cb (ih->cb_cls,
    844                 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    845                 NULL,
    846                 NULL,
    847                 NULL,
    848                 "KYC gateway returned an expired setup process");
    849         GNUNET_JSON_parse_free (spec);
    850         oauth2_initiate_cancel (ih);
    851         return;
    852       }
    853       {
    854         char *nonce_encoded;
    855 
    856         nonce_encoded = TALER_urlencode (nonce);
    857         GNUNET_asprintf (&url,
    858                          "%s/%s",
    859                          pd->authorize_url,
    860                          nonce_encoded);
    861         GNUNET_free (nonce_encoded);
    862       }
    863       initiate_with_url (ih,
    864                          url);
    865       GNUNET_free (url);
    866       return;
    867     }
    868     break;
    869   default:
    870     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    871                 "/setup URL returned HTTP status %u\n",
    872                 (unsigned int) response_code);
    873     ih->cb (ih->cb_cls,
    874             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    875             NULL,
    876             NULL,
    877             NULL,
    878             "/setup request to OAuth 2.0 backend returned unexpected HTTP status code");
    879     oauth2_initiate_cancel (ih);
    880     return;
    881   }
    882 }
    883 
    884 
    885 static struct GNUNET_TIME_Timestamp
    886 oauth2_initiate_get_expiration (
    887   const struct TALER_KYCLOGIC_InitiateHandle *ih)
    888 {
    889   return ih->process_expiration;
    890 }
    891 
    892 
    893 /**
    894  * Logic to asynchronously return the response for how to begin the OAuth2.0
    895  * checking process to the client.  May first request a dynamic URL via
    896  * ``/setup`` if configured to use a client-authenticated setup process.
    897  *
    898  * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
    899  */
    900 static void
    901 initiate_task (void *cls)
    902 {
    903   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
    904   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
    905   struct PluginState *ps = pd->ps;
    906   CURL *eh;
    907 
    908   ih->task = NULL;
    909   if (NULL == pd->setup_url)
    910   {
    911     initiate_with_url (ih,
    912                        pd->authorize_url);
    913     return;
    914   }
    915   eh = curl_easy_init ();
    916   if (NULL == eh)
    917   {
    918     GNUNET_break (0);
    919     ih->cb (ih->cb_cls,
    920             TALER_EC_GENERIC_ALLOCATION_FAILURE,
    921             NULL,
    922             NULL,
    923             NULL,
    924             "curl_easy_init() failed");
    925     oauth2_initiate_cancel (ih);
    926     return;
    927   }
    928   GNUNET_assert (CURLE_OK ==
    929                  curl_easy_setopt (eh,
    930                                    CURLOPT_URL,
    931                                    pd->setup_url));
    932 #if DEBUG
    933   GNUNET_assert (CURLE_OK ==
    934                  curl_easy_setopt (eh,
    935                                    CURLOPT_VERBOSE,
    936                                    1));
    937 #endif
    938   if (NULL == ih->initial_address)
    939   {
    940     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    941                 "Staring OAuth 2.0 without initial address\n");
    942     GNUNET_assert (CURLE_OK ==
    943                    curl_easy_setopt (eh,
    944                                      CURLOPT_POST,
    945                                      1));
    946     GNUNET_assert (CURLE_OK ==
    947                    curl_easy_setopt (eh,
    948                                      CURLOPT_POSTFIELDS,
    949                                      ""));
    950     GNUNET_assert (CURLE_OK ==
    951                    curl_easy_setopt (eh,
    952                                      CURLOPT_POSTFIELDSIZE,
    953                                      (long) 0));
    954   }
    955   else
    956   {
    957     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    958                 "Staring OAuth 2.0 with initial address\n");
    959 #if DEBUG
    960     json_dumpf (ih->initial_address,
    961                 stderr,
    962                 JSON_INDENT (2));
    963     fprintf (stderr,
    964              "\n");
    965 #endif
    966     if (GNUNET_OK !=
    967         TALER_curl_easy_post (&ih->ctx,
    968                               eh,
    969                               ih->initial_address))
    970     {
    971       curl_easy_cleanup (eh);
    972       ih->cb (ih->cb_cls,
    973               TALER_EC_GENERIC_ALLOCATION_FAILURE,
    974               NULL,
    975               NULL,
    976               NULL,
    977               "TALER_curl_easy_post() failed");
    978       oauth2_initiate_cancel (ih);
    979       return;
    980     }
    981   }
    982   GNUNET_assert (CURLE_OK ==
    983                  curl_easy_setopt (eh,
    984                                    CURLOPT_FOLLOWLOCATION,
    985                                    1L));
    986   GNUNET_assert (CURLE_OK ==
    987                  curl_easy_setopt (eh,
    988                                    CURLOPT_MAXREDIRS,
    989                                    5L));
    990   ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    991                                   eh,
    992                                   ih->ctx.headers,
    993                                   &handle_curl_setup_finished,
    994                                   ih);
    995   {
    996     char *hdr;
    997     struct curl_slist *slist;
    998 
    999     GNUNET_asprintf (&hdr,
   1000                      "%s: Bearer %s",
   1001                      MHD_HTTP_HEADER_AUTHORIZATION,
   1002                      pd->client_secret);
   1003     slist = curl_slist_append (NULL,
   1004                                hdr);
   1005     GNUNET_CURL_extend_headers (ih->job,
   1006                                 slist);
   1007     curl_slist_free_all (slist);
   1008     GNUNET_free (hdr);
   1009   }
   1010 }
   1011 
   1012 
   1013 /**
   1014  * Initiate KYC check.
   1015  *
   1016  * @param cls the @e cls of this struct with the plugin-specific state
   1017  * @param pd provider configuration details
   1018  * @param account_id which account to trigger process for
   1019  * @param legitimization_uuid unique ID for the legitimization process
   1020  * @param context additional contextual information for the legi process
   1021  * @param cb function to call with the result
   1022  * @param cb_cls closure for @a cb
   1023  * @return handle to cancel operation early
   1024  */
   1025 static struct TALER_KYCLOGIC_InitiateHandle *
   1026 oauth2_initiate (void *cls,
   1027                  const struct TALER_KYCLOGIC_ProviderDetails *pd,
   1028                  const struct TALER_NormalizedPaytoHashP *account_id,
   1029                  uint64_t legitimization_uuid,
   1030                  const json_t *context,
   1031                  TALER_KYCLOGIC_InitiateCallback cb,
   1032                  void *cb_cls)
   1033 {
   1034   struct TALER_KYCLOGIC_InitiateHandle *ih;
   1035 
   1036   (void) cls;
   1037   ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
   1038   ih->legitimization_uuid = legitimization_uuid;
   1039   ih->cb = cb;
   1040   ih->cb_cls = cb_cls;
   1041   ih->h_payto = *account_id;
   1042   ih->pd = pd;
   1043   ih->task = GNUNET_SCHEDULER_add_now (&initiate_task,
   1044                                        ih);
   1045   if (NULL != context)
   1046   {
   1047     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1048                 "Initiating OAuth2 validation with context\n");
   1049 #if DEBUG
   1050     json_dumpf (context,
   1051                 stderr,
   1052                 JSON_INDENT (2));
   1053     fprintf (stderr,
   1054              "\n");
   1055 #endif
   1056     ih->initial_address = json_incref (json_object_get (context,
   1057                                                         "initial_address"));
   1058   }
   1059   return ih;
   1060 }
   1061 
   1062 
   1063 /**
   1064  * Cancel KYC proof.
   1065  *
   1066  * @param[in] ph handle of operation to cancel
   1067  */
   1068 static void
   1069 oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
   1070 {
   1071   if (NULL != ph->ec)
   1072   {
   1073     TALER_JSON_external_conversion_stop (ph->ec);
   1074     ph->ec = NULL;
   1075   }
   1076   if (NULL != ph->task)
   1077   {
   1078     GNUNET_SCHEDULER_cancel (ph->task);
   1079     ph->task = NULL;
   1080   }
   1081   if (NULL != ph->job)
   1082   {
   1083     GNUNET_CURL_job_cancel (ph->job);
   1084     ph->job = NULL;
   1085   }
   1086   if (NULL != ph->response)
   1087   {
   1088     MHD_destroy_response (ph->response);
   1089     ph->response = NULL;
   1090   }
   1091   GNUNET_free (ph->provider_user_id);
   1092   if (NULL != ph->attributes)
   1093     json_decref (ph->attributes);
   1094   GNUNET_free (ph->post_body);
   1095   GNUNET_free (ph->state);
   1096   GNUNET_free (ph);
   1097 }
   1098 
   1099 
   1100 /**
   1101  * Function called to asynchronously return the final
   1102  * result to the callback.
   1103  *
   1104  * @param cls a `struct TALER_KYCLOGIC_ProofHandle`
   1105  */
   1106 static void
   1107 return_proof_response (void *cls)
   1108 {
   1109   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1110   const char *provider_name;
   1111 
   1112   ph->task = NULL;
   1113   provider_name = ph->pd->section;
   1114   if (0 !=
   1115       strncasecmp (provider_name,
   1116                    "KYC-PROVIDER-",
   1117                    strlen ("KYC-PROVIDER-")))
   1118   {
   1119     GNUNET_break (0);
   1120   }
   1121   else
   1122   {
   1123     provider_name += strlen ("KYC-PROVIDER-");
   1124   }
   1125   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1126               "Returning KYC proof from `%s'\n",
   1127               provider_name);
   1128   ph->cb (ph->cb_cls,
   1129           ph->status,
   1130           provider_name,
   1131           ph->provider_user_id,
   1132           ph->provider_legitimization_id,
   1133           GNUNET_TIME_relative_to_absolute (ph->pd->validity),
   1134           ph->attributes,
   1135           ph->http_status,
   1136           ph->response);
   1137   ph->response = NULL; /*Ownership passed to 'ph->cb'!*/
   1138   oauth2_proof_cancel (ph);
   1139 }
   1140 
   1141 
   1142 /**
   1143  * Load a @a template and substitute using @a root, returning the result in a
   1144  * @a reply encoded suitable for the @a connection with the given @a
   1145  * http_status code.  On errors, the @a http_status code
   1146  * is updated to reflect the type of error encoded in the
   1147  * @a reply.
   1148  *
   1149  * @param connection the connection we act upon
   1150  * @param[in,out] http_status code to use on success,
   1151  *           set to alternative code on failure
   1152  * @param template basename of the template to load
   1153  * @param root JSON object to pass as the root context
   1154  * @param[out] reply where to write the response object
   1155  * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
   1156  *         #GNUNET_SYSERR on failure (to queue an error)
   1157  */
   1158 static enum GNUNET_GenericReturnValue
   1159 templating_build (struct MHD_Connection *connection,
   1160                   unsigned int *http_status,
   1161                   const char *template,
   1162                   const json_t *root,
   1163                   struct MHD_Response **reply)
   1164 {
   1165   enum GNUNET_GenericReturnValue ret;
   1166 
   1167   ret = TALER_TEMPLATING_build (connection,
   1168                                 http_status,
   1169                                 template,
   1170                                 NULL,
   1171                                 NULL,
   1172                                 root,
   1173                                 reply);
   1174   if (GNUNET_SYSERR != ret)
   1175   {
   1176     GNUNET_break (MHD_NO !=
   1177                   MHD_add_response_header (*reply,
   1178                                            MHD_HTTP_HEADER_CONTENT_TYPE,
   1179                                            "text/html"));
   1180   }
   1181   return ret;
   1182 }
   1183 
   1184 
   1185 /**
   1186  * The request for @a ph failed. We may have gotten a useful error
   1187  * message in @a j. Generate a failure response.
   1188  *
   1189  * @param[in,out] ph request that failed
   1190  * @param j reply from the server (or NULL)
   1191  */
   1192 static void
   1193 handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
   1194                     const json_t *j)
   1195 {
   1196   enum GNUNET_GenericReturnValue res;
   1197 
   1198   {
   1199     const char *msg;
   1200     const char *desc;
   1201     bool no_desc;
   1202     struct GNUNET_JSON_Specification spec[] = {
   1203       GNUNET_JSON_spec_string ("error",
   1204                                &msg),
   1205       /* RFC 6749, Section 5.2: only "error" is REQUIRED,
   1206          "error_description" is OPTIONAL */
   1207       GNUNET_JSON_spec_mark_optional (
   1208         GNUNET_JSON_spec_string ("error_description",
   1209                                  &desc),
   1210         &no_desc),
   1211       GNUNET_JSON_spec_end ()
   1212     };
   1213     const char *emsg;
   1214     unsigned int line;
   1215 
   1216     res = GNUNET_JSON_parse (j,
   1217                              spec,
   1218                              &emsg,
   1219                              &line);
   1220   }
   1221 
   1222   if (GNUNET_OK != res)
   1223   {
   1224     json_t *body;
   1225 
   1226     GNUNET_break_op (0);
   1227     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1228     ph->http_status
   1229       = MHD_HTTP_BAD_GATEWAY;
   1230     body = GNUNET_JSON_PACK (
   1231       GNUNET_JSON_pack_allow_null (
   1232         GNUNET_JSON_pack_object_incref ("server_response",
   1233                                         (json_t *) j)),
   1234       GNUNET_JSON_pack_bool ("debug",
   1235                              ph->pd->debug_mode),
   1236       TALER_JSON_pack_ec (
   1237         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1238     GNUNET_assert (NULL != body);
   1239     GNUNET_break (
   1240       GNUNET_SYSERR !=
   1241       templating_build (ph->connection,
   1242                         &ph->http_status,
   1243                         "oauth2-authorization-failure-malformed",
   1244                         body,
   1245                         &ph->response));
   1246     json_decref (body);
   1247     return;
   1248   }
   1249   ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
   1250   ph->http_status = MHD_HTTP_FORBIDDEN;
   1251   GNUNET_break (
   1252     GNUNET_SYSERR !=
   1253     templating_build (ph->connection,
   1254                       &ph->http_status,
   1255                       "oauth2-authorization-failure",
   1256                       j,
   1257                       &ph->response));
   1258 }
   1259 
   1260 
   1261 /**
   1262  * Type of a callback that receives a JSON @a result.
   1263  *
   1264  * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
   1265  * @param status_type how did the process die
   1266  * @param code termination status code from the process
   1267  * @param attr result some JSON result, NULL if we failed to get an JSON output
   1268  */
   1269 static void
   1270 converted_proof_cb (void *cls,
   1271                     enum GNUNET_OS_ProcessStatusType status_type,
   1272                     unsigned long code,
   1273                     const json_t *attr)
   1274 {
   1275   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1276   const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
   1277 
   1278   ph->ec = NULL;
   1279   if ( (NULL == attr) ||
   1280        (GNUNET_OS_PROCESS_EXITED != status_type) ||
   1281        (0 != code) )
   1282   {
   1283     json_t *body;
   1284     char *msg;
   1285 
   1286     GNUNET_break_op (0);
   1287     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1288     ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1289     if ( (GNUNET_OS_PROCESS_EXITED != status_type) ||
   1290          (0 != code) )
   1291       GNUNET_asprintf (&msg,
   1292                        "Attribute converter died with status %d/%ld",
   1293                        (int) status_type,
   1294                        code);
   1295     else
   1296       msg = GNUNET_strdup (
   1297         "Attribute converter response was not in JSON format");
   1298     body = GNUNET_JSON_PACK (
   1299       GNUNET_JSON_pack_string ("converter",
   1300                                pd->conversion_binary),
   1301       GNUNET_JSON_pack_allow_null (
   1302         GNUNET_JSON_pack_object_incref ("attributes",
   1303                                         (json_t *) attr)),
   1304       GNUNET_JSON_pack_bool ("debug",
   1305                              ph->pd->debug_mode),
   1306       GNUNET_JSON_pack_string ("message",
   1307                                msg),
   1308       TALER_JSON_pack_ec (
   1309         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1310     GNUNET_free (msg);
   1311     GNUNET_break (
   1312       GNUNET_SYSERR !=
   1313       templating_build (ph->connection,
   1314                         &ph->http_status,
   1315                         "oauth2-conversion-failure",
   1316                         body,
   1317                         &ph->response));
   1318     json_decref (body);
   1319     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1320                                          ph);
   1321     return;
   1322   }
   1323   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1324               "Attribute conversion output is:\n");
   1325 #if DEBUG
   1326   json_dumpf (attr,
   1327               stderr,
   1328               JSON_INDENT (2));
   1329   fprintf (stderr,
   1330            "\n");
   1331 #endif
   1332   {
   1333     const char *id;
   1334     struct GNUNET_JSON_Specification ispec[] = {
   1335       GNUNET_JSON_spec_string ("id",
   1336                                &id),
   1337       GNUNET_JSON_spec_end ()
   1338     };
   1339     enum GNUNET_GenericReturnValue res;
   1340     const char *emsg;
   1341     unsigned int line;
   1342 
   1343     res = GNUNET_JSON_parse (attr,
   1344                              ispec,
   1345                              &emsg,
   1346                              &line);
   1347     if (GNUNET_OK != res)
   1348     {
   1349       json_t *body;
   1350 
   1351       GNUNET_break_op (0);
   1352       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1353       ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1354       body = GNUNET_JSON_PACK (
   1355         GNUNET_JSON_pack_string ("converter",
   1356                                  pd->conversion_binary),
   1357         GNUNET_JSON_pack_string ("message",
   1358                                  "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"),
   1359         GNUNET_JSON_pack_bool ("debug",
   1360                                ph->pd->debug_mode),
   1361         GNUNET_JSON_pack_object_incref ("attributes",
   1362                                         (json_t *) attr),
   1363         TALER_JSON_pack_ec (
   1364           TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1365       GNUNET_break (
   1366         GNUNET_SYSERR !=
   1367         templating_build (ph->connection,
   1368                           &ph->http_status,
   1369                           "oauth2-conversion-failure",
   1370                           body,
   1371                           &ph->response));
   1372       json_decref (body);
   1373       ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1374                                            ph);
   1375       return;
   1376     }
   1377     ph->provider_user_id = GNUNET_strdup (id);
   1378   }
   1379   if (! json_is_string (json_object_get (attr,
   1380                                          "FORM_ID")))
   1381   {
   1382     json_t *body;
   1383 
   1384     GNUNET_break_op (0);
   1385     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1386     ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1387     body = GNUNET_JSON_PACK (
   1388       GNUNET_JSON_pack_string ("converter",
   1389                                pd->conversion_binary),
   1390       GNUNET_JSON_pack_string ("message",
   1391                                "Missing 'FORM_ID' field in attributes"),
   1392       GNUNET_JSON_pack_bool ("debug",
   1393                              ph->pd->debug_mode),
   1394       GNUNET_JSON_pack_object_incref ("attributes",
   1395                                       (json_t *) attr),
   1396       TALER_JSON_pack_ec (
   1397         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1398     GNUNET_break (
   1399       GNUNET_SYSERR !=
   1400       templating_build (ph->connection,
   1401                         &ph->http_status,
   1402                         "oauth2-conversion-failure",
   1403                         body,
   1404                         &ph->response));
   1405     json_decref (body);
   1406     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1407                                          ph);
   1408     return;
   1409   }
   1410   ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
   1411   ph->response = MHD_create_response_from_buffer_static (0,
   1412                                                          "");
   1413   GNUNET_assert (NULL != ph->response);
   1414   GNUNET_break (MHD_YES ==
   1415                 MHD_add_response_header (
   1416                   ph->response,
   1417                   MHD_HTTP_HEADER_LOCATION,
   1418                   ph->pd->post_kyc_redirect_url));
   1419   ph->http_status = MHD_HTTP_SEE_OTHER;
   1420   ph->attributes = json_incref ((json_t *) attr);
   1421   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1422                                        ph);
   1423 }
   1424 
   1425 
   1426 /**
   1427  * The request for @a ph succeeded (presumably).
   1428  * Call continuation with the result.
   1429  *
   1430  * @param[in,out] ph request that succeeded
   1431  * @param j reply from the server
   1432  */
   1433 static void
   1434 parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
   1435                            const json_t *j)
   1436 {
   1437   const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
   1438   const char *argv[] = {
   1439     pd->conversion_binary,
   1440     NULL,
   1441   };
   1442 
   1443   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1444               "Calling converter `%s' with JSON\n",
   1445               pd->conversion_binary);
   1446 #if DEBUG
   1447   json_dumpf (j,
   1448               stderr,
   1449               JSON_INDENT (2));
   1450 #endif
   1451   ph->ec = TALER_JSON_external_conversion_start (
   1452     j,
   1453     &converted_proof_cb,
   1454     ph,
   1455     pd->conversion_binary,
   1456     argv);
   1457   if (NULL != ph->ec)
   1458     return;
   1459   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1460               "Failed to start OAUTH2 conversion helper `%s'\n",
   1461               pd->conversion_binary);
   1462   ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
   1463   ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1464   {
   1465     json_t *body;
   1466 
   1467     body = GNUNET_JSON_PACK (
   1468       GNUNET_JSON_pack_string ("converter",
   1469                                pd->conversion_binary),
   1470       GNUNET_JSON_pack_bool ("debug",
   1471                              ph->pd->debug_mode),
   1472       GNUNET_JSON_pack_string ("message",
   1473                                "Failed to launch KYC conversion helper process."),
   1474       TALER_JSON_pack_ec (
   1475         TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED));
   1476     GNUNET_break (
   1477       GNUNET_SYSERR !=
   1478       templating_build (ph->connection,
   1479                         &ph->http_status,
   1480                         "oauth2-conversion-failure",
   1481                         body,
   1482                         &ph->response));
   1483     json_decref (body);
   1484   }
   1485   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1486                                        ph);
   1487 }
   1488 
   1489 
   1490 /**
   1491  * After we are done with the CURL interaction we
   1492  * need to update our database state with the information
   1493  * retrieved.
   1494  *
   1495  * @param cls our `struct TALER_KYCLOGIC_ProofHandle`
   1496  * @param response_code HTTP response code from server, 0 on hard error
   1497  * @param response in JSON, NULL if response was not in JSON format
   1498  */
   1499 static void
   1500 handle_curl_proof_finished (void *cls,
   1501                             long response_code,
   1502                             const void *response)
   1503 {
   1504   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1505   const json_t *j = response;
   1506 
   1507   ph->job = NULL;
   1508   switch (response_code)
   1509   {
   1510   case 0:
   1511     {
   1512       json_t *body;
   1513 
   1514       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1515       ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1516 
   1517       body = GNUNET_JSON_PACK (
   1518         GNUNET_JSON_pack_string ("message",
   1519                                  "No response from KYC gateway"),
   1520         TALER_JSON_pack_ec (
   1521           TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1522       GNUNET_break (
   1523         GNUNET_SYSERR !=
   1524         templating_build (ph->connection,
   1525                           &ph->http_status,
   1526                           "oauth2-provider-failure",
   1527                           body,
   1528                           &ph->response));
   1529       json_decref (body);
   1530     }
   1531     break;
   1532   case MHD_HTTP_OK:
   1533     parse_proof_success_reply (ph,
   1534                                j);
   1535     return;
   1536   default:
   1537     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1538                 "OAuth2.0 info URL returned HTTP status %u\n",
   1539                 (unsigned int) response_code);
   1540     handle_proof_error (ph,
   1541                         j);
   1542     break;
   1543   }
   1544   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1545                                        ph);
   1546 }
   1547 
   1548 
   1549 /**
   1550  * After we are done with the CURL interaction we
   1551  * need to fetch the user's account details.
   1552  *
   1553  * @param cls our `struct KycProofContext`
   1554  * @param response_code HTTP response code from server, 0 on hard error
   1555  * @param response in JSON, NULL if response was not in JSON format
   1556  */
   1557 static void
   1558 handle_curl_login_finished (void *cls,
   1559                             long response_code,
   1560                             const void *response)
   1561 {
   1562   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1563   const json_t *j = response;
   1564 
   1565   ph->job = NULL;
   1566   switch (response_code)
   1567   {
   1568   case MHD_HTTP_OK:
   1569     {
   1570       const char *access_token;
   1571       const char *token_type;
   1572       uint64_t expires_in_s;
   1573       const char *refresh_token;
   1574       bool no_expires;
   1575       bool no_refresh;
   1576       struct GNUNET_JSON_Specification spec[] = {
   1577         GNUNET_JSON_spec_string ("access_token",
   1578                                  &access_token),
   1579         GNUNET_JSON_spec_string ("token_type",
   1580                                  &token_type),
   1581         GNUNET_JSON_spec_mark_optional (
   1582           GNUNET_JSON_spec_uint64 ("expires_in",
   1583                                    &expires_in_s),
   1584           &no_expires),
   1585         GNUNET_JSON_spec_mark_optional (
   1586           GNUNET_JSON_spec_string ("refresh_token",
   1587                                    &refresh_token),
   1588           &no_refresh),
   1589         GNUNET_JSON_spec_end ()
   1590       };
   1591       CURL *eh;
   1592 
   1593       {
   1594         enum GNUNET_GenericReturnValue res;
   1595         const char *emsg;
   1596         unsigned int line;
   1597 
   1598         res = GNUNET_JSON_parse (j,
   1599                                  spec,
   1600                                  &emsg,
   1601                                  &line);
   1602         if (GNUNET_OK != res)
   1603         {
   1604           json_t *body;
   1605 
   1606           GNUNET_break_op (0);
   1607           ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1608           ph->http_status
   1609             = MHD_HTTP_BAD_GATEWAY;
   1610           body = GNUNET_JSON_PACK (
   1611             GNUNET_JSON_pack_object_incref ("server_response",
   1612                                             (json_t *) j),
   1613             GNUNET_JSON_pack_bool ("debug",
   1614                                    ph->pd->debug_mode),
   1615             GNUNET_JSON_pack_string ("message",
   1616                                      "Unexpected response from KYC gateway: required fields missing or malformed"),
   1617             TALER_JSON_pack_ec (
   1618               TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1619           GNUNET_break (
   1620             GNUNET_SYSERR !=
   1621             templating_build (ph->connection,
   1622                               &ph->http_status,
   1623                               "oauth2-provider-failure",
   1624                               body,
   1625                               &ph->response));
   1626           json_decref (body);
   1627           break;
   1628         }
   1629       }
   1630       if (0 != strcasecmp (token_type,
   1631                            "bearer"))
   1632       {
   1633         json_t *body;
   1634 
   1635         GNUNET_break_op (0);
   1636         ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1637         ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1638         body = GNUNET_JSON_PACK (
   1639           GNUNET_JSON_pack_object_incref ("server_response",
   1640                                           (json_t *) j),
   1641           GNUNET_JSON_pack_bool ("debug",
   1642                                  ph->pd->debug_mode),
   1643           GNUNET_JSON_pack_string ("message",
   1644                                    "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"),
   1645           TALER_JSON_pack_ec (
   1646             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1647         GNUNET_break (
   1648           GNUNET_SYSERR !=
   1649           templating_build (ph->connection,
   1650                             &ph->http_status,
   1651                             "oauth2-provider-failure",
   1652                             body,
   1653                             &ph->response));
   1654         json_decref (body);
   1655         break;
   1656       }
   1657 
   1658       /* We guard against a few characters that could
   1659          conceivably be abused to mess with the HTTP header */
   1660       if ( (NULL != strchr (access_token,
   1661                             '\n')) ||
   1662            (NULL != strchr (access_token,
   1663                             '\r')) ||
   1664            (NULL != strchr (access_token,
   1665                             ' ')) ||
   1666            (NULL != strchr (access_token,
   1667                             ';')) )
   1668       {
   1669         json_t *body;
   1670 
   1671         GNUNET_break_op (0);
   1672         ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1673         ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1674         body = GNUNET_JSON_PACK (
   1675           GNUNET_JSON_pack_object_incref ("server_response",
   1676                                           (json_t *) j),
   1677           GNUNET_JSON_pack_bool ("debug",
   1678                                  ph->pd->debug_mode),
   1679           GNUNET_JSON_pack_string ("message",
   1680                                    "Illegal character in access token"),
   1681           TALER_JSON_pack_ec (
   1682             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1683         GNUNET_break (
   1684           GNUNET_SYSERR !=
   1685           templating_build (ph->connection,
   1686                             &ph->http_status,
   1687                             "oauth2-provider-failure",
   1688                             body,
   1689                             &ph->response));
   1690         json_decref (body);
   1691         break;
   1692       }
   1693 
   1694       eh = curl_easy_init ();
   1695       GNUNET_assert (NULL != eh);
   1696       GNUNET_assert (CURLE_OK ==
   1697                      curl_easy_setopt (eh,
   1698                                        CURLOPT_URL,
   1699                                        ph->pd->info_url));
   1700       {
   1701         char *hdr;
   1702         struct curl_slist *slist;
   1703 
   1704         GNUNET_asprintf (&hdr,
   1705                          "%s: Bearer %s",
   1706                          MHD_HTTP_HEADER_AUTHORIZATION,
   1707                          access_token);
   1708         slist = curl_slist_append (NULL,
   1709                                    hdr);
   1710         ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
   1711                                         eh,
   1712                                         slist,
   1713                                         &handle_curl_proof_finished,
   1714                                         ph);
   1715         curl_slist_free_all (slist);
   1716         GNUNET_free (hdr);
   1717       }
   1718       return;
   1719     }
   1720   default:
   1721     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1722                 "OAuth2.0 login URL returned HTTP status %u\n",
   1723                 (unsigned int) response_code);
   1724     handle_proof_error (ph,
   1725                         j);
   1726     break;
   1727   }
   1728   return_proof_response (ph);
   1729 }
   1730 
   1731 
   1732 /**
   1733  * Check KYC status and return status to human.
   1734  *
   1735  * @param cls the @e cls of this struct with the plugin-specific state
   1736  * @param pd provider configuration details
   1737  * @param connection MHD connection object (for HTTP headers)
   1738  * @param account_id which account to trigger process for
   1739  * @param process_row row in the legitimization processes table the legitimization is for
   1740  * @param provider_user_id user ID (or NULL) the proof is for
   1741  * @param provider_legitimization_id legitimization ID the proof is for
   1742  * @param cb function to call with the result
   1743  * @param cb_cls closure for @a cb
   1744  * @return handle to cancel operation early
   1745  */
   1746 static struct TALER_KYCLOGIC_ProofHandle *
   1747 oauth2_proof (void *cls,
   1748               const struct TALER_KYCLOGIC_ProviderDetails *pd,
   1749               struct MHD_Connection *connection,
   1750               const struct TALER_NormalizedPaytoHashP *account_id,
   1751               uint64_t process_row,
   1752               const char *provider_user_id,
   1753               const char *provider_legitimization_id,
   1754               TALER_KYCLOGIC_ProofCallback cb,
   1755               void *cb_cls)
   1756 {
   1757   struct PluginState *ps = cls;
   1758   struct TALER_KYCLOGIC_ProofHandle *ph;
   1759   const char *code;
   1760 
   1761   GNUNET_break (NULL == provider_user_id);
   1762   ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
   1763   GNUNET_snprintf (ph->provider_legitimization_id,
   1764                    sizeof (ph->provider_legitimization_id),
   1765                    "%llu",
   1766                    (unsigned long long) process_row);
   1767   if ( (NULL != provider_legitimization_id) &&
   1768        (0 != strcmp (provider_legitimization_id,
   1769                      ph->provider_legitimization_id)))
   1770   {
   1771     GNUNET_break (0);
   1772     GNUNET_free (ph);
   1773     return NULL;
   1774   }
   1775 
   1776   ph->pd = pd;
   1777   ph->connection = connection;
   1778   ph->h_payto = *account_id;
   1779   ph->cb = cb;
   1780   ph->cb_cls = cb_cls;
   1781   ph->state = compute_state (pd,
   1782                              account_id,
   1783                              process_row);
   1784   {
   1785     const char *state;
   1786 
   1787     state = MHD_lookup_connection_value (connection,
   1788                                          MHD_GET_ARGUMENT_KIND,
   1789                                          "state");
   1790     GNUNET_assert (NULL != state); /* checked by the /kyc-proof handler */
   1791     if ( (strlen (state) != strlen (ph->state)) ||
   1792          (0 != GNUNET_memcmp_ct_ (state,
   1793                                   ph->state,
   1794                                   strlen (ph->state))) )
   1795     {
   1796       json_t *body;
   1797 
   1798       /* Not the state we issued for this process: a forged or stale
   1799          redirect, which must neither complete nor fail the process. */
   1800       GNUNET_break_op (0);
   1801       ph->status = TALER_KYCLOGIC_STATUS_KEEP;
   1802       ph->http_status = MHD_HTTP_FORBIDDEN;
   1803       body = GNUNET_JSON_PACK (
   1804         GNUNET_JSON_pack_string ("message",
   1805                                  "'state' does not match the KYC process"),
   1806         TALER_JSON_pack_ec (
   1807           TALER_EC_GENERIC_FORBIDDEN));
   1808       GNUNET_break (
   1809         GNUNET_SYSERR !=
   1810         templating_build (ph->connection,
   1811                           &ph->http_status,
   1812                           "oauth2-state-invalid",
   1813                           body,
   1814                           &ph->response));
   1815       json_decref (body);
   1816       ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1817                                            ph);
   1818       return ph;
   1819     }
   1820   }
   1821   code = MHD_lookup_connection_value (connection,
   1822                                       MHD_GET_ARGUMENT_KIND,
   1823                                       "code");
   1824   if (NULL == code)
   1825   {
   1826     const char *err;
   1827     const char *desc;
   1828     const char *euri;
   1829     json_t *body;
   1830 
   1831     err = MHD_lookup_connection_value (connection,
   1832                                        MHD_GET_ARGUMENT_KIND,
   1833                                        "error");
   1834     if (NULL == err)
   1835     {
   1836       GNUNET_break_op (0);
   1837       ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
   1838       ph->http_status = MHD_HTTP_BAD_REQUEST;
   1839       body = GNUNET_JSON_PACK (
   1840         GNUNET_JSON_pack_string ("message",
   1841                                  "'code' parameter malformed"),
   1842         TALER_JSON_pack_ec (
   1843           TALER_EC_GENERIC_PARAMETER_MALFORMED));
   1844       GNUNET_break (
   1845         GNUNET_SYSERR !=
   1846         templating_build (ph->connection,
   1847                           &ph->http_status,
   1848                           "oauth2-bad-request",
   1849                           body,
   1850                           &ph->response));
   1851       json_decref (body);
   1852       ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1853                                            ph);
   1854       return ph;
   1855     }
   1856     desc = MHD_lookup_connection_value (connection,
   1857                                         MHD_GET_ARGUMENT_KIND,
   1858                                         "error_description");
   1859     euri = MHD_lookup_connection_value (connection,
   1860                                         MHD_GET_ARGUMENT_KIND,
   1861                                         "error_uri");
   1862     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1863                 "OAuth2 process %llu failed with error `%s'\n",
   1864                 (unsigned long long) process_row,
   1865                 err);
   1866     if (0 == strcasecmp (err,
   1867                          "server_error"))
   1868       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1869     else if (0 == strcasecmp (err,
   1870                               "unauthorized_client"))
   1871       ph->status = TALER_KYCLOGIC_STATUS_FAILED;
   1872     else if (0 == strcasecmp (err,
   1873                               "access_denied"))
   1874       /* The provider refused authorization for good, e.g. challenger
   1875          after the user exhausted all attempts to prove their address. */
   1876       ph->status = TALER_KYCLOGIC_STATUS_FAILED;
   1877     else if (0 == strcasecmp (err,
   1878                               "temporarily_unavailable"))
   1879       ph->status = TALER_KYCLOGIC_STATUS_PENDING;
   1880     else
   1881       ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
   1882     ph->http_status = MHD_HTTP_FORBIDDEN;
   1883     body = GNUNET_JSON_PACK (
   1884       GNUNET_JSON_pack_string ("error",
   1885                                err),
   1886       GNUNET_JSON_pack_allow_null (
   1887         GNUNET_JSON_pack_string ("error_details",
   1888                                  desc)),
   1889       GNUNET_JSON_pack_allow_null (
   1890         GNUNET_JSON_pack_string ("error_uri",
   1891                                  euri)));
   1892     GNUNET_break (
   1893       GNUNET_SYSERR !=
   1894       templating_build (ph->connection,
   1895                         &ph->http_status,
   1896                         "oauth2-authentication-failure",
   1897                         body,
   1898                         &ph->response));
   1899     json_decref (body);
   1900     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1901                                          ph);
   1902     return ph;
   1903 
   1904   }
   1905 
   1906   ph->eh = curl_easy_init ();
   1907   GNUNET_assert (NULL != ph->eh);
   1908   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1909               "Requesting OAuth 2.0 data via HTTP POST `%s'\n",
   1910               pd->token_url);
   1911   GNUNET_assert (CURLE_OK ==
   1912                  curl_easy_setopt (ph->eh,
   1913                                    CURLOPT_URL,
   1914                                    pd->token_url));
   1915 #if DEBUG
   1916   GNUNET_assert (CURLE_OK ==
   1917                  curl_easy_setopt (ph->eh,
   1918                                    CURLOPT_VERBOSE,
   1919                                    1));
   1920 #endif
   1921   GNUNET_assert (CURLE_OK ==
   1922                  curl_easy_setopt (ph->eh,
   1923                                    CURLOPT_POST,
   1924                                    1));
   1925   {
   1926     char *client_id;
   1927     char *client_secret;
   1928     char *authorization_code;
   1929     char *redirect_uri_encoded;
   1930 
   1931     {
   1932       char *redirect_uri;
   1933 
   1934       GNUNET_asprintf (&redirect_uri,
   1935                        "%skyc-proof/%s",
   1936                        ps->exchange_base_url,
   1937                        &pd->section[strlen ("kyc-provider-")]);
   1938       redirect_uri_encoded = TALER_urlencode (redirect_uri);
   1939       GNUNET_free (redirect_uri);
   1940     }
   1941     GNUNET_assert (NULL != redirect_uri_encoded);
   1942     client_id = curl_easy_escape (ph->eh,
   1943                                   pd->client_id,
   1944                                   0);
   1945     GNUNET_assert (NULL != client_id);
   1946     client_secret = curl_easy_escape (ph->eh,
   1947                                       pd->client_secret,
   1948                                       0);
   1949     GNUNET_assert (NULL != client_secret);
   1950     authorization_code = curl_easy_escape (ph->eh,
   1951                                            code,
   1952                                            0);
   1953     GNUNET_assert (NULL != authorization_code);
   1954     GNUNET_asprintf (&ph->post_body,
   1955                      "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code",
   1956                      client_id,
   1957                      redirect_uri_encoded,
   1958                      ph->state,
   1959                      client_secret,
   1960                      authorization_code);
   1961     curl_free (authorization_code);
   1962     curl_free (client_secret);
   1963     GNUNET_free (redirect_uri_encoded);
   1964     curl_free (client_id);
   1965   }
   1966   GNUNET_assert (CURLE_OK ==
   1967                  curl_easy_setopt (ph->eh,
   1968                                    CURLOPT_POSTFIELDS,
   1969                                    ph->post_body));
   1970   GNUNET_assert (CURLE_OK ==
   1971                  curl_easy_setopt (ph->eh,
   1972                                    CURLOPT_FOLLOWLOCATION,
   1973                                    1L));
   1974   /* limit MAXREDIRS to 5 as a simple security measure against
   1975      a potential infinite loop caused by a malicious target */
   1976   GNUNET_assert (CURLE_OK ==
   1977                  curl_easy_setopt (ph->eh,
   1978                                    CURLOPT_MAXREDIRS,
   1979                                    5L));
   1980 
   1981   ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
   1982                                  ph->eh,
   1983                                  &handle_curl_login_finished,
   1984                                  ph);
   1985   return ph;
   1986 }
   1987 
   1988 
   1989 /**
   1990  * Function to asynchronously return the 404 not found
   1991  * page for the webhook.
   1992  *
   1993  * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *`
   1994  */
   1995 static void
   1996 wh_return_not_found (void *cls)
   1997 {
   1998   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
   1999   struct MHD_Response *response;
   2000 
   2001   wh->task = NULL;
   2002   response = MHD_create_response_from_buffer_static (0,
   2003                                                      "");
   2004   wh->cb (wh->cb_cls,
   2005           0LLU,
   2006           NULL,
   2007           false,
   2008           NULL,
   2009           NULL,
   2010           NULL,
   2011           TALER_KYCLOGIC_STATUS_KEEP,
   2012           GNUNET_TIME_UNIT_ZERO_ABS,
   2013           NULL,
   2014           MHD_HTTP_NOT_FOUND,
   2015           response);
   2016   GNUNET_free (wh);
   2017 }
   2018 
   2019 
   2020 /**
   2021  * Check KYC status and return result for Webhook.
   2022  *
   2023  * @param cls the @e cls of this struct with the plugin-specific state
   2024  * @param pd provider configuration details
   2025  * @param plc callback to lookup accounts with
   2026  * @param plc_cls closure for @a plc
   2027  * @param http_method HTTP method used for the webhook
   2028  * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array
   2029  * @param connection MHD connection object (for HTTP headers)
   2030  * @param body HTTP request body, or NULL if not available
   2031  * @param cb function to call with the result
   2032  * @param cb_cls closure for @a cb
   2033  * @return handle to cancel operation early
   2034  */
   2035 static struct TALER_KYCLOGIC_WebhookHandle *
   2036 oauth2_webhook (void *cls,
   2037                 const struct TALER_KYCLOGIC_ProviderDetails *pd,
   2038                 TALER_KYCLOGIC_ProviderLookupCallback plc,
   2039                 void *plc_cls,
   2040                 const char *http_method,
   2041                 const char *const url_path[],
   2042                 struct MHD_Connection *connection,
   2043                 const json_t *body,
   2044                 TALER_KYCLOGIC_WebhookCallback cb,
   2045                 void *cb_cls)
   2046 {
   2047   struct PluginState *ps = cls;
   2048   struct TALER_KYCLOGIC_WebhookHandle *wh;
   2049 
   2050   (void) pd;
   2051   (void) plc;
   2052   (void) plc_cls;
   2053   (void) http_method;
   2054   (void) url_path;
   2055   (void) connection;
   2056   (void) body;
   2057   GNUNET_break_op (0);
   2058   wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
   2059   wh->cb = cb;
   2060   wh->cb_cls = cb_cls;
   2061   wh->ps = ps;
   2062   wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found,
   2063                                        wh);
   2064   return wh;
   2065 }
   2066 
   2067 
   2068 /**
   2069  * Cancel KYC webhook execution.
   2070  *
   2071  * @param[in] wh handle of operation to cancel
   2072  */
   2073 static void
   2074 oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
   2075 {
   2076   GNUNET_SCHEDULER_cancel (wh->task);
   2077   GNUNET_free (wh);
   2078 }
   2079 
   2080 
   2081 /**
   2082  * Initialize OAuth2.0 KYC logic plugin
   2083  *
   2084  * @param cls a configuration instance
   2085  * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
   2086  */
   2087 void *
   2088 libtaler_plugin_kyclogic_oauth2_init (void *cls);
   2089 
   2090 /* declaration to avoid compiler warning */
   2091 void *
   2092 libtaler_plugin_kyclogic_oauth2_init (void *cls)
   2093 {
   2094   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
   2095   struct TALER_KYCLOGIC_Plugin *plugin;
   2096   struct PluginState *ps;
   2097 
   2098   ps = GNUNET_new (struct PluginState);
   2099   ps->cfg = cfg;
   2100   if (GNUNET_OK !=
   2101       GNUNET_CONFIGURATION_get_value_string (cfg,
   2102                                              "exchange",
   2103                                              "BASE_URL",
   2104                                              &ps->exchange_base_url))
   2105   {
   2106     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   2107                                "exchange",
   2108                                "BASE_URL");
   2109     GNUNET_free (ps);
   2110     return NULL;
   2111   }
   2112   ps->curl_ctx
   2113     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   2114                         &ps->curl_rc);
   2115   if (NULL == ps->curl_ctx)
   2116   {
   2117     GNUNET_break (0);
   2118     GNUNET_free (ps->exchange_base_url);
   2119     GNUNET_free (ps);
   2120     return NULL;
   2121   }
   2122   ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
   2123 
   2124   plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
   2125   plugin->cls = ps;
   2126   plugin->load_configuration
   2127     = &oauth2_load_configuration;
   2128   plugin->unload_configuration
   2129     = &oauth2_unload_configuration;
   2130   plugin->initiate
   2131     = &oauth2_initiate;
   2132   plugin->initiate_get_expiration
   2133     = &oauth2_initiate_get_expiration;
   2134   plugin->initiate_cancel
   2135     = &oauth2_initiate_cancel;
   2136   plugin->proof
   2137     = &oauth2_proof;
   2138   plugin->proof_cancel
   2139     = &oauth2_proof_cancel;
   2140   plugin->webhook
   2141     = &oauth2_webhook;
   2142   plugin->webhook_cancel
   2143     = &oauth2_webhook_cancel;
   2144   return plugin;
   2145 }
   2146 
   2147 
   2148 /**
   2149  * Unload authorization plugin
   2150  *
   2151  * @param cls a `struct TALER_KYCLOGIC_Plugin`
   2152  * @return NULL (always)
   2153  */
   2154 void *
   2155 libtaler_plugin_kyclogic_oauth2_done (void *cls);
   2156 
   2157 /* declaration to avoid compiler warning */
   2158 void *
   2159 libtaler_plugin_kyclogic_oauth2_done (void *cls)
   2160 {
   2161   struct TALER_KYCLOGIC_Plugin *plugin = cls;
   2162   struct PluginState *ps = plugin->cls;
   2163 
   2164   if (NULL != ps->curl_ctx)
   2165   {
   2166     GNUNET_CURL_fini (ps->curl_ctx);
   2167     ps->curl_ctx = NULL;
   2168   }
   2169   if (NULL != ps->curl_rc)
   2170   {
   2171     GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
   2172     ps->curl_rc = NULL;
   2173   }
   2174   GNUNET_free (ps->exchange_base_url);
   2175   GNUNET_free (ps);
   2176   GNUNET_free (plugin);
   2177   return NULL;
   2178 }