/* 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; /** * @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; }; /** * @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; }; /** * The exchange's configuration (global) */ static const struct GNUNET_CONFIGURATION_Handle *TEKT_cfg; /** * Handle to the HTTP server. */ static struct MHD_Daemon *mhd; /** * Our currency. */ static char *TEKT_currency; /** * Our base URL. */ static char *TEKT_base_url; /** * Value to return from main() */ static int global_ret; /** * 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; /** * Generate a 404 "not found" reply on @a connection with * the hint @a details. * * @param connection where to send the reply on * @param details details for the error message, can be NULL */ static MHD_RESULT r404 (struct MHD_Connection *connection, const char *details) { return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, details); } /** * 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) { // FIXME: pass value to use for h_payto via command-line? memset (h_payto, 42, sizeof (*h_payto)); return 1; // FIXME... } /** * 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_WEBHOOK_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"); } 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); } 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 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; } /** * 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[] = { #if FIXME /* KYC endpoints */ { .url = "kyc-check", .method = MHD_HTTP_METHOD_GET, .handler.get = &TEKT_handler_kyc_check, .nargs = 1 }, { .url = "kyc-proof", .method = MHD_HTTP_METHOD_GET, .handler.get = &TEKT_handler_kyc_proof, .nargs = 1 }, { .url = "kyc-wallet", .method = MHD_HTTP_METHOD_POST, .handler.post = &TEKT_handler_kyc_wallet, .nargs = 0 }, #endif { .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; } 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) { MHD_RESULT ret; const char *start; if ('\0' == url[0]) /* strange, should start with '/', treat as just "/" */ url = "/"; start = strchr (url + 1, '/'); if (NULL == start) start = ""; ret = proceed_with_handler (rc, start, upload_data, upload_data_size); return ret; } 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)) { MHD_RESULT ret; /* cache to avoid the loop next time */ rc->rh = rh; /* run handler */ ret = proceed_with_handler (rc, url + tok_size + 1, upload_data, upload_data_size); return ret; } } 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 */ { MHD_RESULT ret; ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_GENERIC_ENDPOINT_UNKNOWN, url); return ret; } } /** * 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; (void) cls; 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; } } /** * 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; 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; } if (GNUNET_OK != exchange_serve_process_config ()) { global_ret = EXIT_NOTCONFIGURED; TALER_KYCLOGIC_kyc_done (); GNUNET_SCHEDULER_shutdown (); return; } 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); GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); 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; } global_ret = EXIT_SUCCESS; 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_END }; enum GNUNET_GenericReturnValue ret; TALER_OS_init (); 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 */