/* This file is part of TALER Copyright (C) 2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see */ /** * @file taler-exchange-kyc-tester.c * @brief tool to test KYC integrations * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include #include #include "taler_mhd_lib.h" #include "taler_json_lib.h" #include "taler_crypto_lib.h" #include "taler_kyclogic_lib.h" #include "taler_kyclogic_plugin.h" #include /** * @brief Context in which the exchange is processing * all requests */ struct TEKT_RequestContext { /** * Opaque parsing context. */ void *opaque_post_parsing_context; /** * Request handler responsible for this request. */ const struct TEKT_RequestHandler *rh; /** * Request URL (for logging). */ const char *url; /** * Connection we are processing. */ struct MHD_Connection *connection; /** * HTTP response to return (or NULL). */ struct MHD_Response *response; /** * @e rh-specific cleanup routine. Function called * upon completion of the request that should * clean up @a rh_ctx. Can be NULL. */ void (*rh_cleaner)(struct TEKT_RequestContext *rc); /** * @e rh-specific context. Place where the request * handler can associate state with this request. * Can be NULL. */ void *rh_ctx; /** * HTTP status to return upon resume if @e response * is non-NULL. */ unsigned int http_status; }; /** * @brief Struct describing an URL and the handler for it. */ struct TEKT_RequestHandler { /** * URL the handler is for (first part only). */ const char *url; /** * Method the handler is for. */ const char *method; /** * Callbacks for handling of the request. Which one is used * depends on @e method. */ union { /** * Function to call to handle a GET requests (and those * with @e method NULL). * * @param rc context for the request * @param mime_type the @e mime_type for the reply (hint, can be NULL) * @param args array of arguments, needs to be of length @e args_expected * @return MHD result code */ MHD_RESULT (*get)(struct TEKT_RequestContext *rc, const char *const args[]); /** * Function to call to handle a POST request. * * @param rc context for the request * @param json uploaded JSON data * @param args array of arguments, needs to be of length @e args_expected * @return MHD result code */ MHD_RESULT (*post)(struct TEKT_RequestContext *rc, const json_t *root, const char *const args[]); } handler; /** * Number of arguments this handler expects in the @a args array. */ unsigned int nargs; /** * Is the number of arguments given in @e nargs only an upper bound, * and calling with fewer arguments could be OK? */ bool nargs_is_upper_bound; /** * Mime type to use in reply (hint, can be NULL). */ const char *mime_type; /** * Raw data for the @e handler, can be NULL for none provided. */ const void *data; /** * Number of bytes in @e data, 0 for data is 0-terminated (!). */ size_t data_size; /** * Default response code. 0 for none provided. */ unsigned int response_code; }; /** * Information we track per ongoing kyc-proof request. */ struct ProofRequestState { /** * Kept in a DLL. */ struct ProofRequestState *next; /** * Kept in a DLL. */ struct ProofRequestState *prev; /** * Handle for operation with the plugin. */ struct TALER_KYCLOGIC_ProofHandle *ph; /** * Logic plugin we are using. */ struct TALER_KYCLOGIC_Plugin *logic; /** * HTTP request details. */ struct TEKT_RequestContext *rc; }; /** * Head of DLL. */ static struct ProofRequestState *rs_head; /** * Tail of DLL. */ static struct ProofRequestState *rs_tail; /** * The exchange's configuration (global) */ static const struct GNUNET_CONFIGURATION_Handle *TEKT_cfg; /** * Handle to the HTTP server. */ static struct MHD_Daemon *mhd; /** * Our base URL. */ static char *TEKT_base_url; /** * Payto set via command-line (or otherwise random). */ static struct TALER_PaytoHashP cmd_line_h_payto; /** * Provider user ID to use. */ static char *cmd_provider_user_id; /** * Provider legitimization ID to use. */ static char *cmd_provider_legitimization_id; /** * Row ID to use, override with '-r' */ static unsigned int kyc_row_id = 42; /** * -P command-line option. */ static int print_h_payto; /** * -w command-line option. */ static int run_webservice; /** * Value to return from main() */ static int global_ret; /** * -i command-line flag. */ static char *initiate_section; /** * Handle for ongoing initiation operation. */ static struct TALER_KYCLOGIC_InitiateHandle *ih; /** * KYC logic running for @e ih. */ static struct TALER_KYCLOGIC_Plugin *ih_logic; /** * Port to run the daemon on. */ static uint16_t serve_port; /** * Context for all CURL operations (useful to the event loop) */ static struct GNUNET_CURL_Context *TEKT_curl_ctx; /** * Context for integrating #TEKT_curl_ctx with the * GNUnet event loop. */ static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc; /** * Context for the webhook. */ struct KycWebhookContext { /** * Kept in a DLL while suspended. */ struct KycWebhookContext *next; /** * Kept in a DLL while suspended. */ struct KycWebhookContext *prev; /** * Details about the connection we are processing. */ struct TEKT_RequestContext *rc; /** * Plugin responsible for the webhook. */ struct TALER_KYCLOGIC_Plugin *plugin; /** * Configuration for the specific action. */ struct TALER_KYCLOGIC_ProviderDetails *pd; /** * Webhook activity. */ struct TALER_KYCLOGIC_WebhookHandle *wh; /** * HTTP response to return. */ struct MHD_Response *response; /** * Logic the request is for. Name of the configuration * section defining the KYC logic. */ char *logic; /** * HTTP response code to return. */ unsigned int response_code; /** * #GNUNET_YES if we are suspended, * #GNUNET_NO if not. * #GNUNET_SYSERR if we had some error. */ enum GNUNET_GenericReturnValue suspended; }; /** * Contexts are kept in a DLL while suspended. */ static struct KycWebhookContext *kwh_head; /** * Contexts are kept in a DLL while suspended. */ static struct KycWebhookContext *kwh_tail; /** * Resume processing the @a kwh request. * * @param kwh request to resume */ static void kwh_resume (struct KycWebhookContext *kwh) { GNUNET_assert (GNUNET_YES == kwh->suspended); kwh->suspended = GNUNET_NO; GNUNET_CONTAINER_DLL_remove (kwh_head, kwh_tail, kwh); MHD_resume_connection (kwh->rc->connection); TALER_MHD_daemon_trigger (); } static void kyc_webhook_cleanup (void) { struct KycWebhookContext *kwh; while (NULL != (kwh = kwh_head)) { if (NULL != kwh->wh) { kwh->plugin->webhook_cancel (kwh->wh); kwh->wh = NULL; } kwh_resume (kwh); } } /** * Function called with the result of a webhook * operation. * * Note that the "decref" for the @a response * will be done by the plugin. * * @param cls closure * @param legi_row legitimization request the webhook was about * @param account_id account the webhook was about * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown * @param status KYC status * @param expiration until when is the KYC check valid * @param http_status HTTP status code of @a response * @param[in] response to return to the HTTP client */ static void webhook_finished_cb ( void *cls, uint64_t legi_row, const struct TALER_PaytoHashP *account_id, const char *provider_user_id, const char *provider_legitimization_id, enum TALER_KYCLOGIC_KycStatus status, struct GNUNET_TIME_Absolute expiration, unsigned int http_status, struct MHD_Response *response) { struct KycWebhookContext *kwh = cls; kwh->wh = NULL; switch (status) { case TALER_KYCLOGIC_STATUS_SUCCESS: /* _successfully_ resumed case */ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "KYC successful for user `%s' (legi: %s)\n", provider_user_id, provider_legitimization_id); break; default: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC status of %s/%s (Row #%llu) is %d\n", provider_user_id, provider_legitimization_id, (unsigned long long) legi_row, status); break; } kwh->response = response; kwh->response_code = http_status; kwh_resume (kwh); } /** * Function called to clean up a context. * * @param rc request context */ static void clean_kwh (struct TEKT_RequestContext *rc) { struct KycWebhookContext *kwh = rc->rh_ctx; if (NULL != kwh->wh) { kwh->plugin->webhook_cancel (kwh->wh); kwh->wh = NULL; } if (NULL != kwh->response) { MHD_destroy_response (kwh->response); kwh->response = NULL; } GNUNET_free (kwh->logic); GNUNET_free (kwh); } /** * Function the plugin can use to lookup an * @a h_payto by @a provider_legitimization_id. * * @param cls closure, NULL * @param provider_section * @param provider_legitimization_id legi to look up * @param[out] h_payto where to write the result * @return database transaction status */ static enum GNUNET_DB_QueryStatus kyc_provider_account_lookup ( void *cls, const char *provider_section, const char *provider_legitimization_id, struct TALER_PaytoHashP *h_payto) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Simulated account lookup using `%s/%s'\n", provider_section, provider_legitimization_id); *h_payto = cmd_line_h_payto; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } /** * Handle a (GET or POST) "/kyc-webhook" request. * * @param rc request to handle * @param method HTTP request method used by the client * @param root uploaded JSON body (can be NULL) * @param args one argument with the payment_target_uuid * @return MHD result code */ static MHD_RESULT handler_kyc_webhook_generic ( struct TEKT_RequestContext *rc, const char *method, const json_t *root, const char *const args[]) { struct KycWebhookContext *kwh = rc->rh_ctx; if (NULL == kwh) { /* first time */ kwh = GNUNET_new (struct KycWebhookContext); kwh->logic = GNUNET_strdup (args[0]); kwh->rc = rc; rc->rh_ctx = kwh; rc->rh_cleaner = &clean_kwh; if (GNUNET_OK != TALER_KYCLOGIC_kyc_get_logic (kwh->logic, &kwh->plugin, &kwh->pd)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "KYC logic `%s' unknown (check KYC provider configuration)\n", kwh->logic); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, "$LOGIC"); } kwh->wh = kwh->plugin->webhook (kwh->plugin->cls, kwh->pd, &kyc_provider_account_lookup, NULL, method, &args[1], rc->connection, root, &webhook_finished_cb, kwh); if (NULL == kwh->wh) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, "failed to run webhook logic"); } kwh->suspended = GNUNET_YES; GNUNET_CONTAINER_DLL_insert (kwh_head, kwh_tail, kwh); MHD_suspend_connection (rc->connection); return MHD_YES; } if (NULL != kwh->response) { /* handle _failed_ resumed cases */ return MHD_queue_response (rc->connection, kwh->response_code, kwh->response); } /* We resumed, but got no response? This should not happen. */ GNUNET_break (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, "resumed without response"); } /** * Handle a GET "/kyc-webhook" request. * * @param rc request to handle * @param args one argument with the payment_target_uuid * @return MHD result code */ static MHD_RESULT handler_kyc_webhook_get ( struct TEKT_RequestContext *rc, const char *const args[]) { return handler_kyc_webhook_generic (rc, MHD_HTTP_METHOD_GET, NULL, args); } /** * Handle a POST "/kyc-webhook" request. * * @param rc request to handle * @param root uploaded JSON body (can be NULL) * @param args one argument with the payment_target_uuid * @return MHD result code */ static MHD_RESULT handler_kyc_webhook_post ( struct TEKT_RequestContext *rc, const json_t *root, const char *const args[]) { return handler_kyc_webhook_generic (rc, MHD_HTTP_METHOD_POST, root, args); } /** * Function called with the result of a proof check operation. * * Note that the "decref" for the @a response * will be done by the callee and MUST NOT be done by the plugin. * * @param cls closure with the `struct ProofRequestState` * @param status KYC status * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown * @param expiration until when is the KYC check valid * @param http_status HTTP status code of @a response * @param[in] response to return to the HTTP client */ static void proof_cb ( void *cls, enum TALER_KYCLOGIC_KycStatus status, const char *provider_user_id, const char *provider_legitimization_id, struct GNUNET_TIME_Absolute expiration, unsigned int http_status, struct MHD_Response *response) { struct ProofRequestState *rs = cls; GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "KYC legitimization %s completed with status %d (%u) for %s\n", provider_legitimization_id, status, http_status, provider_user_id); MHD_resume_connection (rs->rc->connection); TALER_MHD_daemon_trigger (); rs->rc->response = response; rs->rc->http_status = http_status; GNUNET_CONTAINER_DLL_remove (rs_head, rs_tail, rs); GNUNET_free (rs); } /** * Function called when we receive a 'GET' to the * '/kyc-proof' endpoint. * * @param rc request context * @param args remaining URL arguments; * args[0] is the 'h_payto', * args[1] should be the logic plugin name */ static MHD_RESULT handler_kyc_proof_get ( struct TEKT_RequestContext *rc, const char *const args[]) { struct TALER_PaytoHashP h_payto; struct TALER_KYCLOGIC_ProviderDetails *pd; struct TALER_KYCLOGIC_Plugin *logic; struct ProofRequestState *rs; if ( (NULL == args[0]) || (NULL == args[1]) ) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_GENERIC_ENDPOINT_UNKNOWN, "'/$H_PAYTO/$LOGIC' required after '/kyc-proof'"); } if (GNUNET_OK != GNUNET_STRINGS_string_to_data (args[0], strlen (args[0]), &h_payto, sizeof (h_payto))) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "h_payto"); } if (0 != GNUNET_memcmp (&h_payto, &cmd_line_h_payto)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN, "h_payto"); } if (GNUNET_OK != TALER_KYCLOGIC_kyc_get_logic (args[1], &logic, &pd)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not initiate KYC with provider `%s' (configuration error?)\n", initiate_section); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, args[1]); } rs = GNUNET_new (struct ProofRequestState); rs->rc = rc; rs->logic = logic; MHD_suspend_connection (rc->connection); GNUNET_CONTAINER_DLL_insert (rs_head, rs_tail, rs); rs->ph = logic->proof (logic->cls, pd, &args[2], rc->connection, &h_payto, cmd_provider_user_id, cmd_provider_legitimization_id, &proof_cb, rs); GNUNET_assert (NULL != rs->ph); return MHD_YES; } /** * Function called whenever MHD is done with a request. If the * request was a POST, we may have stored a `struct Buffer *` in the * @a con_cls that might still need to be cleaned up. Call the * respective function to free the memory. * * @param cls client-defined closure * @param connection connection handle * @param con_cls value as set by the last call to * the #MHD_AccessHandlerCallback * @param toe reason for request termination * @see #MHD_OPTION_NOTIFY_COMPLETED * @ingroup request */ static void handle_mhd_completion_callback (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { struct TEKT_RequestContext *rc = *con_cls; (void) cls; if (NULL == rc) return; if (NULL != rc->rh_cleaner) rc->rh_cleaner (rc); { #if MHD_VERSION >= 0x00097304 const union MHD_ConnectionInfo *ci; unsigned int http_status = 0; ci = MHD_get_connection_info (connection, MHD_CONNECTION_INFO_HTTP_STATUS); if (NULL != ci) http_status = ci->http_status; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Request for `%s' completed with HTTP status %u (%d)\n", rc->url, http_status, toe); #else (void) connection; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Request for `%s' completed (%d)\n", rc->url, toe); #endif } TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context); /* Sanity-check that we didn't leave any transactions hanging */ GNUNET_free (rc); *con_cls = NULL; } /** * We found a request handler responsible for handling a request. Parse the * @a upload_data (if applicable) and the @a url and call the * handler. * * @param rc request context * @param url rest of the URL to parse * @param upload_data upload data to parse (if available) * @param[in,out] upload_data_size number of bytes in @a upload_data * @return MHD result code */ static MHD_RESULT proceed_with_handler (struct TEKT_RequestContext *rc, const char *url, const char *upload_data, size_t *upload_data_size) { const struct TEKT_RequestHandler *rh = rc->rh; const char *args[rh->nargs + 2]; size_t ulen = strlen (url) + 1; json_t *root = NULL; MHD_RESULT ret; /* We do check for "ulen" here, because we'll later stack-allocate a buffer of that size and don't want to enable malicious clients to cause us huge stack allocations. */ if (ulen > 512) { /* 512 is simply "big enough", as it is bigger than "6 * 54", which is the longest URL format we ever get (for /deposits/). The value should be adjusted if we ever define protocol endpoints with plausibly longer inputs. */ GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_URI_TOO_LONG, TALER_EC_GENERIC_URI_TOO_LONG, url); } /* All POST endpoints come with a body in JSON format. So we parse the JSON here. */ if (0 == strcasecmp (rh->method, MHD_HTTP_METHOD_POST)) { enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_post_json (rc->connection, &rc->opaque_post_parsing_context, upload_data, upload_data_size, &root); if (GNUNET_SYSERR == res) { GNUNET_assert (NULL == root); return MHD_NO; /* bad upload, could not even generate error */ } if ( (GNUNET_NO == res) || (NULL == root) ) { GNUNET_assert (NULL == root); return MHD_YES; /* so far incomplete upload or parser error */ } } { char d[ulen]; unsigned int i; char *sp; /* Parse command-line arguments */ /* make a copy of 'url' because 'strtok_r()' will modify */ memcpy (d, url, ulen); i = 0; args[i++] = strtok_r (d, "/", &sp); while ( (NULL != args[i - 1]) && (i <= rh->nargs + 1) ) args[i++] = strtok_r (NULL, "/", &sp); /* make sure above loop ran nicely until completion, and also that there is no excess data in 'd' afterwards */ if ( ( (rh->nargs_is_upper_bound) && (i - 1 > rh->nargs) ) || ( (! rh->nargs_is_upper_bound) && (i - 1 != rh->nargs) ) ) { char emsg[128 + 512]; GNUNET_snprintf (emsg, sizeof (emsg), "Got %u+/%u segments for `%s' request (`%s')", i - 1, rh->nargs, rh->url, url); GNUNET_break_op (0); json_decref (root); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS, emsg); } GNUNET_assert (NULL == args[i - 1]); /* Above logic ensures that 'root' is exactly non-NULL for POST operations, so we test for 'root' to decide which handler to invoke. */ if (NULL != root) ret = rh->handler.post (rc, root, args); else /* We also only have "POST" or "GET" in the API for at this point (OPTIONS/HEAD are taken care of earlier) */ ret = rh->handler.get (rc, args); } json_decref (root); return ret; } static void rh_cleaner_cb (struct TEKT_RequestContext *rc) { if (NULL != rc->response) MHD_destroy_response (rc->response); GNUNET_free (rc); } /** * Handle incoming HTTP request. * * @param cls closure for MHD daemon (unused) * @param connection the connection * @param url the requested url * @param method the method (POST, GET, ...) * @param version HTTP version (ignored) * @param upload_data request data * @param upload_data_size size of @a upload_data in bytes * @param con_cls closure for request (a `struct TEKT_RequestContext *`) * @return MHD result code */ static MHD_RESULT handle_mhd_request (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { static struct TEKT_RequestHandler handlers[] = { /* simulated KYC endpoints */ { .url = "kyc-proof", .method = MHD_HTTP_METHOD_GET, .handler.get = &handler_kyc_proof_get, .nargs = 128, .nargs_is_upper_bound = true }, { .url = "kyc-webhook", .method = MHD_HTTP_METHOD_POST, .handler.post = &handler_kyc_webhook_post, .nargs = 128, .nargs_is_upper_bound = true }, { .url = "kyc-webhook", .method = MHD_HTTP_METHOD_GET, .handler.get = &handler_kyc_webhook_get, .nargs = 128, .nargs_is_upper_bound = true }, /* mark end of list */ { .url = NULL } }; struct TEKT_RequestContext *rc = *con_cls; (void) cls; (void) version; if (NULL == rc) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling new request\n"); /* We're in a new async scope! */ rc = *con_cls = GNUNET_new (struct TEKT_RequestContext); rc->url = url; rc->connection = connection; rc->rh_cleaner = &rh_cleaner_cb; } if (NULL != rc->response) { return MHD_queue_response (rc->connection, rc->http_status, rc->response); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling request (%s) for URL '%s'\n", method, url); /* on repeated requests, check our cache first */ if (NULL != rc->rh) { const char *start; if ('\0' == url[0]) /* strange, should start with '/', treat as just "/" */ url = "/"; start = strchr (url + 1, '/'); if (NULL == start) start = ""; return proceed_with_handler (rc, start, upload_data, upload_data_size); } if (0 == strcasecmp (method, MHD_HTTP_METHOD_HEAD)) method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ /* parse first part of URL */ { bool found = false; size_t tok_size; const char *tok; const char *rest; if ('\0' == url[0]) /* strange, should start with '/', treat as just "/" */ url = "/"; tok = url + 1; rest = strchr (tok, '/'); if (NULL == rest) { tok_size = strlen (tok); } else { tok_size = rest - tok; rest++; /* skip over '/' */ } for (unsigned int i = 0; NULL != handlers[i].url; i++) { struct TEKT_RequestHandler *rh = &handlers[i]; if ( (0 != strncmp (tok, rh->url, tok_size)) || (tok_size != strlen (rh->url) ) ) continue; found = true; /* The URL is a match! What we now do depends on the method. */ if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS)) { return TALER_MHD_reply_cors_preflight (connection); } GNUNET_assert (NULL != rh->method); if (0 == strcasecmp (method, rh->method)) /* cache to avoid the loop next time */ rc->rh = rh; /* run handler */ return proceed_with_handler (rc, url + tok_size + 1, upload_data, upload_data_size); } if (found) { /* we found a matching address, but the method is wrong */ struct MHD_Response *reply; MHD_RESULT ret; char *allowed = NULL; GNUNET_break_op (0); for (unsigned int i = 0; NULL != handlers[i].url; i++) { struct TEKT_RequestHandler *rh = &handlers[i]; if ( (0 != strncmp (tok, rh->url, tok_size)) || (tok_size != strlen (rh->url) ) ) continue; if (NULL == allowed) { allowed = GNUNET_strdup (rh->method); } else { char *tmp; GNUNET_asprintf (&tmp, "%s, %s", allowed, rh->method); GNUNET_free (allowed); allowed = tmp; } if (0 == strcasecmp (rh->method, MHD_HTTP_METHOD_GET)) { char *tmp; GNUNET_asprintf (&tmp, "%s, %s", allowed, MHD_HTTP_METHOD_HEAD); GNUNET_free (allowed); allowed = tmp; } } reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID, method); GNUNET_break (MHD_YES == MHD_add_response_header (reply, MHD_HTTP_HEADER_ALLOW, allowed)); GNUNET_free (allowed); ret = MHD_queue_response (connection, MHD_HTTP_METHOD_NOT_ALLOWED, reply); MHD_destroy_response (reply); return ret; } } /* No handler matches, generate not found */ return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_GENERIC_ENDPOINT_UNKNOWN, url); } /** * Load configuration parameters for the exchange * server into the corresponding global variables. * * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue exchange_serve_process_config (void) { if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (TEKT_cfg, "exchange", "BASE_URL", &TEKT_base_url)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchange", "BASE_URL"); return GNUNET_SYSERR; } if (! TALER_url_valid_charset (TEKT_base_url)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "exchange", "BASE_URL", "invalid URL"); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Function run on shutdown. * * @param cls NULL */ static void do_shutdown (void *cls) { struct MHD_Daemon *mhd; struct ProofRequestState *rs; (void) cls; while (NULL != (rs = rs_head)) { GNUNET_CONTAINER_DLL_remove (rs_head, rs_tail, rs); rs->logic->proof_cancel (rs->ph); MHD_resume_connection (rs->rc->connection); GNUNET_free (rs); } if (NULL != ih) { ih_logic->initiate_cancel (ih); ih = NULL; } kyc_webhook_cleanup (); TALER_KYCLOGIC_kyc_done (); mhd = TALER_MHD_daemon_stop (); if (NULL != mhd) MHD_stop_daemon (mhd); if (NULL != TEKT_curl_ctx) { GNUNET_CURL_fini (TEKT_curl_ctx); TEKT_curl_ctx = NULL; } if (NULL != exchange_curl_rc) { GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc); exchange_curl_rc = NULL; } } /** * Function called with the result of a KYC initiation * operation. * * @param cls closure * @param ec #TALER_EC_NONE on success * @param redirect_url set to where to redirect the user on success, NULL on failure * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown * @param error_msg_hint set to additional details to return to user, NULL on success */ static void initiate_cb ( void *cls, enum TALER_ErrorCode ec, const char *redirect_url, const char *provider_user_id, const char *provider_legitimization_id, const char *error_msg_hint) { ih = NULL; if (TALER_EC_NONE != ec) { fprintf (stderr, "Failed to start KYC process: %s (#%d)\n", error_msg_hint, ec); global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); return; } fprintf (stdout, "Visit `%s' to begin KYC process (-u: '%s', -l: '%s')\n", redirect_url, provider_user_id, provider_legitimization_id); GNUNET_free (cmd_provider_user_id); GNUNET_free (cmd_provider_legitimization_id); if (NULL != provider_user_id) cmd_provider_user_id = GNUNET_strdup (provider_user_id); if (NULL != provider_legitimization_id) cmd_provider_legitimization_id = GNUNET_strdup (provider_legitimization_id); } /** * Main function that will be run by the scheduler. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be * NULL!) * @param config configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *config) { int fh; (void) cls; (void) args; (void ) cfgfile; if (print_h_payto) { char *s; s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto, sizeof (cmd_line_h_payto)); fprintf (stdout, "%s\n", s); GNUNET_free (s); } TALER_MHD_setup (TALER_MHD_GO_NONE); TEKT_cfg = config; if (GNUNET_OK != TALER_KYCLOGIC_kyc_init (config)) { global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; } GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); if (GNUNET_OK != exchange_serve_process_config ()) { global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; } global_ret = EXIT_SUCCESS; if (NULL != initiate_section) { struct TALER_KYCLOGIC_ProviderDetails *pd; if (GNUNET_OK != TALER_KYCLOGIC_kyc_get_logic (initiate_section, &ih_logic, &pd)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not initiate KYC with provider `%s' (configuration error?)\n", initiate_section); global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; } ih = ih_logic->initiate (ih_logic->cls, pd, &cmd_line_h_payto, kyc_row_id, &initiate_cb, NULL); GNUNET_break (NULL != ih); } if (run_webservice) { TEKT_curl_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &exchange_curl_rc); if (NULL == TEKT_curl_ctx) { GNUNET_break (0); global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); return; } exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEKT_curl_ctx); fh = TALER_MHD_bind (TEKT_cfg, "exchange", &serve_port); if ( (0 == serve_port) && (-1 == fh) ) { GNUNET_SCHEDULER_shutdown (); return; } mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_PIPE_FOR_SHUTDOWN | MHD_USE_DEBUG | MHD_USE_DUAL_STACK | MHD_USE_TCP_FASTOPEN, (-1 == fh) ? serve_port : 0, NULL, NULL, &handle_mhd_request, NULL, MHD_OPTION_LISTEN_SOCKET, fh, MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs, NULL, MHD_OPTION_NOTIFY_COMPLETED, &handle_mhd_completion_callback, NULL, MHD_OPTION_END); if (NULL == mhd) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to launch HTTP service. Is the port in use?\n"); GNUNET_SCHEDULER_shutdown (); return; } TALER_MHD_daemon_start (mhd); } } /** * The main function of the taler-exchange-httpd server ("the exchange"). * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { const struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_help ( "tool to test KYC provider integrations"), GNUNET_GETOPT_option_flag ( 'P', "print-payto-hash", "output the hash of the payto://-URI", &print_h_payto), GNUNET_GETOPT_option_uint ( 'r', "rowid", "NUMBER", "override row ID to use in simulation (default: 42)", &kyc_row_id), GNUNET_GETOPT_option_flag ( 'w', "run-webservice", "run the integrated HTTP service", &run_webservice), GNUNET_GETOPT_option_string ( 'i', "initiate", "SECTION_NAME", "initiate KYC check using provider configured in SECTION_NAME of the configuration", &initiate_section), GNUNET_GETOPT_option_string ( 'i', "initiate", "SECTION_NAME", "initiate KYC check using provider configured in SECTION_NAME of the configuration", &initiate_section), GNUNET_GETOPT_option_string ( 'u', "user", "ID", "use the given provider user ID (overridden if -i is also used)", &cmd_provider_user_id), GNUNET_GETOPT_option_string ( 'l', "legitimization", "ID", "use the given provider legitimization ID (overridden if -i is also used)", &cmd_provider_legitimization_id), GNUNET_GETOPT_option_base32_fixed_size ( 'p', "payto-hash", "URI", "base32 encoding of the hash of a payto://-URI to use for the account (otherwise a random value will be used)", &cmd_line_h_payto, sizeof (cmd_line_h_payto)), GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue ret; TALER_OS_init (); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &cmd_line_h_payto, sizeof (cmd_line_h_payto)); ret = GNUNET_PROGRAM_run (argc, argv, "taler-exchange-kyc-tester", "tool to test KYC provider integrations", options, &run, NULL); if (GNUNET_SYSERR == ret) return EXIT_INVALIDARGUMENT; if (GNUNET_NO == ret) return EXIT_SUCCESS; return global_ret; } /* end of taler-exchange-kyc-tester.c */