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


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