diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd.c | 1762 |
1 files changed, 1357 insertions, 405 deletions
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 1feede1a8..36459fbd7 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -1,18 +1,18 @@ /* - This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + This file is part of TALER + Copyright (C) 2014-2023 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 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. + 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 <http://www.gnu.org/licenses/> -*/ + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ /** * @file taler-exchange-httpd.c * @brief Serve the HTTP interface of the exchange @@ -25,31 +25,55 @@ #include <jansson.h> #include <microhttpd.h> #include <sched.h> -#include <pthread.h> #include <sys/resource.h> #include <limits.h> +#include "taler_kyclogic_lib.h" +#include "taler_templating_lib.h" #include "taler_mhd_lib.h" +#include "taler-exchange-httpd_age-withdraw.h" +#include "taler-exchange-httpd_age-withdraw_reveal.h" +#include "taler-exchange-httpd_aml-decision.h" #include "taler-exchange-httpd_auditors.h" -#include "taler-exchange-httpd_deposit.h" +#include "taler-exchange-httpd_batch-deposit.h" +#include "taler-exchange-httpd_batch-withdraw.h" +#include "taler-exchange-httpd_coins_get.h" +#include "taler-exchange-httpd_config.h" +#include "taler-exchange-httpd_contract.h" +#include "taler-exchange-httpd_csr.h" #include "taler-exchange-httpd_deposits_get.h" +#include "taler-exchange-httpd_extensions.h" #include "taler-exchange-httpd_keys.h" #include "taler-exchange-httpd_kyc-check.h" #include "taler-exchange-httpd_kyc-proof.h" #include "taler-exchange-httpd_kyc-wallet.h" +#include "taler-exchange-httpd_kyc-webhook.h" #include "taler-exchange-httpd_link.h" #include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_melt.h" +#include "taler-exchange-httpd_metrics.h" #include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_purses_create.h" +#include "taler-exchange-httpd_purses_deposit.h" +#include "taler-exchange-httpd_purses_get.h" +#include "taler-exchange-httpd_purses_delete.h" +#include "taler-exchange-httpd_purses_merge.h" #include "taler-exchange-httpd_recoup.h" +#include "taler-exchange-httpd_recoup-refresh.h" #include "taler-exchange-httpd_refreshes_reveal.h" #include "taler-exchange-httpd_refund.h" +#include "taler-exchange-httpd_reserves_attest.h" +#include "taler-exchange-httpd_reserves_close.h" #include "taler-exchange-httpd_reserves_get.h" +#include "taler-exchange-httpd_reserves_get_attest.h" +#include "taler-exchange-httpd_reserves_history.h" +#include "taler-exchange-httpd_reserves_open.h" +#include "taler-exchange-httpd_reserves_purse.h" +#include "taler-exchange-httpd_spa.h" #include "taler-exchange-httpd_terms.h" #include "taler-exchange-httpd_transfers_get.h" -#include "taler-exchange-httpd_wire.h" -#include "taler-exchange-httpd_withdraw.h" #include "taler_exchangedb_lib.h" #include "taler_exchangedb_plugin.h" +#include "taler_extensions.h" #include <gnunet/gnunet_mhd_compat.h> /** @@ -57,6 +81,16 @@ */ #define UNIX_BACKLOG 50 +/** + * How often will we try to connect to the database before giving up? + */ +#define MAX_DB_RETRIES 5 + +/** + * Above what request latency do we start to log? + */ +#define WARN_LATENCY GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_MILLISECONDS, 500) /** * Are clients allowed to request /keys for times other than the @@ -67,14 +101,27 @@ int TEH_allow_keys_timetravel; /** + * Should we allow two HTTPDs to bind to the same port? + */ +static int allow_address_reuse; + +/** * The exchange's configuration (global) */ const struct GNUNET_CONFIGURATION_Handle *TEH_cfg; /** - * Our KYC configuration. + * Configuration of age restriction + * + * Set after loading the library, enabled in database event handler. + */ +bool TEH_age_restriction_enabled = false; +struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0}; + +/** + * Handle to the HTTP server. */ -struct TEH_KycOptions TEH_kyc_config; +static struct MHD_Daemon *mhd; /** * How long is caching /keys allowed at most? (global) @@ -93,16 +140,58 @@ struct GNUNET_TIME_Relative TEH_reserve_closing_delay; struct TALER_MasterPublicKeyP TEH_master_public_key; /** + * Key used to encrypt KYC attribute data in our database. + */ +struct TALER_AttributeEncryptionKeyP TEH_attribute_key; + +/** * Our DB plugin. (global) */ struct TALER_EXCHANGEDB_Plugin *TEH_plugin; /** + * Absolute STEFAN parameter. + */ +struct TALER_Amount TEH_stefan_abs; + +/** + * Logarithmic STEFAN parameter. + */ +struct TALER_Amount TEH_stefan_log; + +/** + * Linear STEFAN parameter. + */ +float TEH_stefan_lin; + +/** + * Where to redirect users from "/"? + */ +static char *toplevel_redirect_url; + +/** * Our currency. */ char *TEH_currency; /** + * Name of the KYC-AML-trigger evaluation binary. + */ +char *TEH_kyc_aml_trigger; + +/** + * Option set to #GNUNET_YES if rewards are enabled. + */ +int TEH_enable_rewards; + +/** + * What is the largest amount we allow a peer to + * merge into a reserve before always triggering + * an AML check? + */ +struct TALER_Amount TEH_aml_threshold; + +/** * Our base URL. */ char *TEH_base_url; @@ -118,6 +207,25 @@ static unsigned int connection_timeout = 30; static int connection_close; /** + * -I command-line flag given? + */ +int TEH_check_invariants_flag; + +/** + * True if we should commit suicide once all active + * connections are finished. + */ +bool TEH_suicide; + +/** + * Signature of the configuration of all enabled extensions, + * signed by the exchange's offline master key with purpose + * TALER_SIGNATURE_MASTER_EXTENSION. + */ +struct TALER_MasterSignatureP TEH_extensions_sig; +bool TEH_extensions_signed = false; + +/** * Value to return from main() */ static int global_ret; @@ -133,6 +241,11 @@ static uint16_t serve_port; static unsigned long long req_count; /** + * Counter for the number of open connections. + */ +static unsigned long long active_connections; + +/** * Limit for the number of requests this HTTP may process before restarting. * (This was added as one way of dealing with unavoidable memory fragmentation * happening slowly over time.) @@ -140,17 +253,32 @@ static unsigned long long req_count; static unsigned long long req_max; /** + * Length of the cspecs array. + */ +static unsigned int num_cspecs; + +/** + * Rendering specs for currencies. + */ +static struct TALER_CurrencySpecification *cspecs; + +/** + * Rendering spec for our currency. + */ +const struct TALER_CurrencySpecification *TEH_cspec; + + +/** * Context for all CURL operations (useful to the event loop) */ struct GNUNET_CURL_Context *TEH_curl_ctx; /** - * Context for integrating #exchange_curl_ctx with the + * Context for integrating #TEH_curl_ctx with the * GNUnet event loop. */ static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc; - /** * Signature of functions that handle operations on coins. * @@ -189,8 +317,7 @@ r404 (struct MHD_Connection *connection, * * @param rc request context * @param root uploaded JSON data - * @param args array of additional options (first must be the - * reserve public key, the second one should be "withdraw") + * @param args array of additional options * @return MHD result code */ static MHD_RESULT @@ -213,10 +340,6 @@ handle_post_coins (struct TEH_RequestContext *rc, } h[] = { { - .op = "deposit", - .handler = &TEH_handler_deposit - }, - { .op = "melt", .handler = &TEH_handler_melt }, @@ -225,6 +348,10 @@ handle_post_coins (struct TEH_RequestContext *rc, .handler = &TEH_handler_recoup }, { + .op = "recoup-refresh", + .handler = &TEH_handler_recoup_refresh + }, + { .op = "refund", .handler = &TEH_handler_refund }, @@ -258,6 +385,611 @@ handle_post_coins (struct TEH_RequestContext *rc, /** + * Handle a GET "/coins/$COIN_PUB[/$OP]" request. Parses the "coin_pub" + * EdDSA key of the coin and demultiplexes based on $OP. + * + * @param rc request context + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_get_coins (struct TEH_RequestContext *rc, + const char *const args[2]) +{ + struct TALER_CoinSpendPublicKeyP coin_pub; + + if (NULL == args[0]) + { + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + rc->url); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &coin_pub, + sizeof (coin_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB, + args[0]); + } + if (NULL != args[1]) + { + if (0 == strcmp (args[1], + "history")) + return TEH_handler_coins_get (rc, + &coin_pub); + if (0 == strcmp (args[1], + "link")) + return TEH_handler_link (rc, + &coin_pub); + } + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + rc->url); +} + + +/** + * Signature of functions that handle operations + * authorized by AML officers. + * + * @param rc request context + * @param officer_pub the public key of the AML officer + * @param root uploaded JSON data + * @return MHD result code + */ +typedef MHD_RESULT +(*AmlOpPostHandler)(struct TEH_RequestContext *rc, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const json_t *root); + + +/** + * Handle a "/aml/$OFFICER_PUB/$OP" POST request. Parses the "officer_pub" + * EdDSA key of the officer and demultiplexes based on $OP. + * + * @param rc request context + * @param root uploaded JSON data + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_post_aml (struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[2]) +{ + struct TALER_AmlOfficerPublicKeyP officer_pub; + static const struct + { + /** + * Name of the operation (args[1]) + */ + const char *op; + + /** + * Function to call to perform the operation. + */ + AmlOpPostHandler handler; + + } h[] = { + { + .op = "decision", + .handler = &TEH_handler_post_aml_decision + }, + { + .op = NULL, + .handler = NULL + }, + }; + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &officer_pub, + sizeof (officer_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED, + args[0]); + } + for (unsigned int i = 0; NULL != h[i].op; i++) + if (0 == strcmp (h[i].op, + args[1])) + return h[i].handler (rc, + &officer_pub, + root); + return r404 (rc->connection, + args[1]); +} + + +/** + * Signature of functions that handle operations + * authorized by AML officers. + * + * @param rc request context + * @param officer_pub the public key of the AML officer + * @param args remaining arguments + * @return MHD result code + */ +typedef MHD_RESULT +(*AmlOpGetHandler)(struct TEH_RequestContext *rc, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const char *const args[]); + + +/** + * Handle a "/aml/$OFFICER_PUB/$OP" GET request. Parses the "officer_pub" + * EdDSA key of the officer, checks the authentication signature, and + * demultiplexes based on $OP. + * + * @param rc request context + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_get_aml (struct TEH_RequestContext *rc, + const char *const args[]) +{ + struct TALER_AmlOfficerPublicKeyP officer_pub; + static const struct + { + /** + * Name of the operation (args[1]) + */ + const char *op; + + /** + * Function to call to perform the operation. + */ + AmlOpGetHandler handler; + + } h[] = { + { + .op = "decisions", + .handler = &TEH_handler_aml_decisions_get + }, + { + .op = "decision", + .handler = &TEH_handler_aml_decision_get + }, + { + .op = NULL, + .handler = NULL + }, + }; + + if (NULL == args[0]) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED, + "argument missing"); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &officer_pub, + sizeof (officer_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED, + args[0]); + } + if (NULL == args[1]) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS, + "AML GET operations must specify an operation identifier"); + } + { + const char *sig_hdr; + struct TALER_AmlOfficerSignatureP officer_sig; + + sig_hdr = MHD_lookup_connection_value (rc->connection, + MHD_HEADER_KIND, + TALER_AML_OFFICER_SIGNATURE_HEADER); + if ( (NULL == sig_hdr) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (sig_hdr, + strlen (sig_hdr), + &officer_sig, + sizeof (officer_sig))) || + (GNUNET_OK != + TALER_officer_aml_query_verify (&officer_pub, + &officer_sig)) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID, + sig_hdr); + } + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + } + + { + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->test_aml_officer (TEH_plugin->cls, + &officer_pub); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED, + NULL); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + } + for (unsigned int i = 0; NULL != h[i].op; i++) + if (0 == strcmp (h[i].op, + args[1])) + return h[i].handler (rc, + &officer_pub, + &args[2]); + return r404 (rc->connection, + args[1]); +} + + +/** + * Handle a "/age-withdraw/$ACH/reveal" POST request. Parses the "ACH" + * hash of the commitment from a previous call to + * /reserves/$reserve_pub/age-withdraw + * + * @param rc request context + * @param root uploaded JSON data + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_post_age_withdraw (struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[2]) +{ + struct TALER_AgeWithdrawCommitmentHashP ach; + + if (0 != strcmp ("reveal", args[1])) + return r404 (rc->connection, + args[1]); + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &ach, + sizeof (ach))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, + args[0]); + } + + return TEH_handler_age_withdraw_reveal (rc, + &ach, + root); +} + + +/** + * Signature of functions that handle operations on reserves. + * + * @param rc request context + * @param reserve_pub the public key of the reserve + * @param root uploaded JSON data + * @return MHD result code + */ +typedef MHD_RESULT +(*ReserveOpHandler)(struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root); + + +/** + * Handle a "/reserves/$RESERVE_PUB/$OP" POST request. Parses the "reserve_pub" + * EdDSA key of the reserve and demultiplexes based on $OP. + * + * @param rc request context + * @param root uploaded JSON data + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_post_reserves (struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[2]) +{ + struct TALER_ReservePublicKeyP reserve_pub; + static const struct + { + /** + * Name of the operation (args[1]) + */ + const char *op; + + /** + * Function to call to perform the operation. + */ + ReserveOpHandler handler; + + } h[] = { + { + .op = "batch-withdraw", + .handler = &TEH_handler_batch_withdraw + }, + { + .op = "age-withdraw", + .handler = &TEH_handler_age_withdraw + }, + { + .op = "purse", + .handler = &TEH_handler_reserves_purse + }, + { + .op = "open", + .handler = &TEH_handler_reserves_open + }, + { + .op = "close", + .handler = &TEH_handler_reserves_close + }, + { + .op = NULL, + .handler = NULL + }, + }; + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &reserve_pub, + sizeof (reserve_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, + args[0]); + } + for (unsigned int i = 0; NULL != h[i].op; i++) + if (0 == strcmp (h[i].op, + args[1])) + return h[i].handler (rc, + &reserve_pub, + root); + return r404 (rc->connection, + args[1]); +} + + +/** + * Signature of functions that handle GET operations on reserves. + * + * @param rc request context + * @param reserve_pub the public key of the reserve + * @return MHD result code + */ +typedef MHD_RESULT +(*ReserveGetOpHandler)(struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub); + + +/** + * Handle a "GET /reserves/$RESERVE_PUB[/$OP]" request. Parses the "reserve_pub" + * EdDSA key of the reserve and demultiplexes based on $OP. + * + * @param rc request context + * @param args NULL-terminated array of additional options, zero, one or two + * @return MHD result code + */ +static MHD_RESULT +handle_get_reserves (struct TEH_RequestContext *rc, + const char *const args[]) +{ + struct TALER_ReservePublicKeyP reserve_pub; + static const struct + { + /** + * Name of the operation (args[1]), optional + */ + const char *op; + + /** + * Function to call to perform the operation. + */ + ReserveGetOpHandler handler; + + } h[] = { + { + .op = NULL, + .handler = &TEH_handler_reserves_get + }, + { + .op = "history", + .handler = &TEH_handler_reserves_history + }, + { + .op = NULL, + .handler = NULL + }, + }; + + if ( (NULL == args[0]) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &reserve_pub, + sizeof (reserve_pub))) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, + args[0]); + } + for (unsigned int i = 0; NULL != h[i].handler; i++) + { + if ( ( (NULL == args[1]) && + (NULL == h[i].op) ) || + ( (NULL != args[1]) && + (NULL != h[i].op) && + (0 == strcmp (h[i].op, + args[1])) ) ) + return h[i].handler (rc, + &reserve_pub); + } + return r404 (rc->connection, + args[1]); +} + + +/** + * Signature of functions that handle operations on purses. + * + * @param connection HTTP request handle + * @param purse_pub the public key of the purse + * @param root uploaded JSON data + * @return MHD result code + */ +typedef MHD_RESULT +(*PurseOpHandler)(struct MHD_Connection *connection, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const json_t *root); + + +/** + * Handle a "/purses/$RESERVE_PUB/$OP" POST request. Parses the "purse_pub" + * EdDSA key of the purse and demultiplexes based on $OP. + * + * @param rc request context + * @param root uploaded JSON data + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_post_purses (struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[2]) +{ + struct TALER_PurseContractPublicKeyP purse_pub; + static const struct + { + /** + * Name of the operation (args[1]) + */ + const char *op; + + /** + * Function to call to perform the operation. + */ + PurseOpHandler handler; + + } h[] = { + { + .op = "create", + .handler = &TEH_handler_purses_create + }, + { + .op = "deposit", + .handler = &TEH_handler_purses_deposit + }, + { + .op = "merge", + .handler = &TEH_handler_purses_merge + }, + { + .op = NULL, + .handler = NULL + }, + }; + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &purse_pub, + sizeof (purse_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED, + args[0]); + } + for (unsigned int i = 0; NULL != h[i].op; i++) + if (0 == strcmp (h[i].op, + args[1])) + return h[i].handler (rc->connection, + &purse_pub, + root); + return r404 (rc->connection, + args[1]); +} + + +/** + * Increments our request counter and checks if this + * process should commit suicide. + */ +static void +check_suicide (void) +{ + int fd; + pid_t chld; + unsigned long long cnt; + + cnt = req_count++; + if (req_max != cnt) + return; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Restarting exchange service after %llu requests\n", + cnt); + /* Stop accepting new connections */ + fd = MHD_quiesce_daemon (mhd); + GNUNET_break (0 == close (fd)); + /* Continue handling existing connections in child, + so that this process can die and be replaced by + systemd with a fresh one */ + chld = fork (); + if (-1 == chld) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "fork"); + _exit (1); + } + if (0 != chld) + { + /* We are the parent, instant-suicide! */ + _exit (0); + } + TEH_suicide = true; +} + + +/** * 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 @@ -285,8 +1017,16 @@ handle_mhd_completion_callback (void *cls, return; GNUNET_async_scope_enter (&rc->async_scope_id, &old_scope); + check_suicide (); + TEH_check_invariants (); if (NULL != rc->rh_cleaner) rc->rh_cleaner (rc); + if (NULL != rc->root) + { + json_decref (rc->root); + rc->root = NULL; + } + TEH_check_invariants (); { #if MHD_VERSION >= 0x00097304 const union MHD_ConnectionInfo *ci; @@ -314,6 +1054,18 @@ handle_mhd_completion_callback (void *cls, /* Sanity-check that we didn't leave any transactions hanging */ GNUNET_break (GNUNET_OK == TEH_plugin->preflight (TEH_plugin->cls)); + { + struct GNUNET_TIME_Relative latency; + + latency = GNUNET_TIME_absolute_get_duration (rc->start_time); + if (latency.rel_value_us > + WARN_LATENCY.rel_value_us) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Request for `%s' took %s\n", + rc->url, + GNUNET_STRINGS_relative_time_to_string (latency, + GNUNET_YES)); + } GNUNET_free (rc); *con_cls = NULL; GNUNET_async_scope_restore (&old_scope); @@ -338,9 +1090,8 @@ proceed_with_handler (struct TEH_RequestContext *rc, size_t *upload_data_size) { const struct TEH_RequestHandler *rh = rc->rh; - const char *args[rh->nargs + 1]; + 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 @@ -361,8 +1112,9 @@ proceed_with_handler (struct TEH_RequestContext *rc, /* 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)) + if ( (0 == strcasecmp (rh->method, + MHD_HTTP_METHOD_POST)) && + (NULL == rc->root) ) { enum GNUNET_GenericReturnValue res; @@ -370,83 +1122,74 @@ proceed_with_handler (struct TEH_RequestContext *rc, &rc->opaque_post_parsing_context, upload_data, upload_data_size, - &root); + &rc->root); if (GNUNET_SYSERR == res) { - GNUNET_assert (NULL == root); - return MHD_NO; /* bad upload, could not even generate error */ + GNUNET_assert (NULL == rc->root); + return MHD_NO; /* bad upload, could not even generate error */ } if ( (GNUNET_NO == res) || - (NULL == root) ) + (NULL == rc->root) ) { - GNUNET_assert (NULL == root); + GNUNET_assert (NULL == rc->root); return MHD_YES; /* so far incomplete upload or parser error */ } } { char d[ulen]; - - /* Parse command-line arguments, if applicable */ - args[0] = NULL; - if (rh->nargs > 0) - { - unsigned int i; - const char *fin; - char *sp; - - /* 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) ) - 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 != rh->nargs) || - (NULL == args[i - 1]) || - (NULL != (fin = strtok_r (NULL, "/", &sp))) ) ) - { - char emsg[128 + 512]; - - GNUNET_snprintf (emsg, - sizeof (emsg), - "Got %u/%u segments for %s request ('%s')", - (NULL == args[i - 1]) - ? i - 1 - : i + ((NULL != fin) ? 1 : 0), - 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); - } - - /* just to be safe(r), we always terminate the array with a NULL - (even if handlers requested precise number of arguments) */ - args[i] = NULL; + unsigned int i; + char *sp; + + /* Parse command-line arguments */ + /* make a copy of 'url' because 'strtok_r()' will modify */ + GNUNET_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); + 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) + if (0 == strcasecmp (rh->method, + MHD_HTTP_METHOD_POST)) ret = rh->handler.post (rc, - root, + 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) */ + else if (0 == strcasecmp (rh->method, + MHD_HTTP_METHOD_DELETE)) + ret = rh->handler.delete (rc, + args); + else /* Only GET left */ ret = rh->handler.get (rc, args); } - json_decref (root); return ret; } @@ -467,6 +1210,7 @@ handler_seed (struct TEH_RequestContext *rc, MHD_RESULT ret; struct MHD_Response *resp; + (void) args; body = malloc (SEED_SIZE); /* must use malloc(), because MHD will use free() */ if (NULL == body) return MHD_NO; @@ -488,6 +1232,20 @@ handler_seed (struct TEH_RequestContext *rc, /** + * Signature of functions that handle simple + * POST operations for the management API. + * + * @param connection the MHD connection to handle + * @param root uploaded JSON data + * @return MHD result code + */ +typedef MHD_RESULT +(*ManagementPostHandler)( + struct MHD_Connection *connection, + const json_t *root); + + +/** * Handle POST "/management/..." requests. * * @param rc request context @@ -500,6 +1258,55 @@ handle_post_management (struct TEH_RequestContext *rc, const json_t *root, const char *const args[]) { + static const struct + { + const char *arg0; + const char *arg1; + ManagementPostHandler handler; + } plain_posts[] = { + { + .arg0 = "keys", + .handler = &TEH_handler_management_post_keys + }, + { + .arg0 = "wire", + .handler = &TEH_handler_management_post_wire + }, + { + .arg0 = "wire", + .arg1 = "disable", + .handler = &TEH_handler_management_post_wire_disable + }, + { + .arg0 = "wire-fee", + .handler = &TEH_handler_management_post_wire_fees + }, + { + .arg0 = "global-fee", + .handler = &TEH_handler_management_post_global_fees + }, + { + .arg0 = "extensions", + .handler = &TEH_handler_management_post_extensions + }, + { + .arg0 = "drain", + .handler = &TEH_handler_management_post_drain + }, + { + .arg0 = "aml-officers", + .handler = &TEH_handler_management_aml_officers + }, + { + .arg0 = "partners", + .handler = &TEH_handler_management_partners + }, + { + NULL, + NULL, + NULL + } + }; if (NULL == args[0]) { GNUNET_break_op (0); @@ -540,7 +1347,7 @@ handle_post_management (struct TEH_RequestContext *rc, if (0 == strcmp (args[0], "denominations")) { - struct GNUNET_HashCode h_denom_pub; + struct TALER_DenominationHashP h_denom_pub; if ( (NULL == args[0]) || (NULL == args[1]) || @@ -595,46 +1402,22 @@ handle_post_management (struct TEH_RequestContext *rc, &exchange_pub, root); } - if (0 == strcmp (args[0], - "keys")) - { - if (NULL != args[1]) - { - GNUNET_break_op (0); - return r404 (rc->connection, - "/management/keys/*"); - } - return TEH_handler_management_post_keys (rc->connection, - root); - } - if (0 == strcmp (args[0], - "wire")) - { - if (NULL == args[1]) - return TEH_handler_management_post_wire (rc->connection, - root); - if ( (0 != strcmp (args[1], - "disable")) || - (NULL != args[2]) ) - { - GNUNET_break_op (0); - return r404 (rc->connection, - "/management/wire/disable"); - } - return TEH_handler_management_post_wire_disable (rc->connection, - root); - } - if (0 == strcmp (args[0], - "wire-fee")) + for (unsigned int i = 0; + NULL != plain_posts[i].handler; + i++) { - if (NULL != args[1]) + if (0 == strcmp (args[0], + plain_posts[i].arg0)) { - GNUNET_break_op (0); - return r404 (rc->connection, - "/management/wire-fee/*"); + if ( ( (NULL == args[1]) && + (NULL == plain_posts[i].arg1) ) || + ( (NULL != args[1]) && + (NULL != plain_posts[i].arg1) && + (0 == strcmp (args[1], + plain_posts[i].arg1)) ) ) + return plain_posts[i].handler (rc->connection, + root); } - return TEH_handler_management_post_wire_fees (rc->connection, - root); } GNUNET_break_op (0); return r404 (rc->connection, @@ -643,15 +1426,15 @@ handle_post_management (struct TEH_RequestContext *rc, /** - * Handle a get "/management" request. + * Handle a GET "/management" request. * * @param rc request context - * @param args array of additional options (must be empty for this function) + * @param args array of additional options (must be [0] == "keys") * @return MHD result code */ static MHD_RESULT handle_get_management (struct TEH_RequestContext *rc, - const char *const args[1]) + const char *const args[2]) { if ( (NULL != args[0]) && (0 == strcmp (args[0], @@ -681,7 +1464,7 @@ handle_post_auditors (struct TEH_RequestContext *rc, const char *const args[]) { struct TALER_AuditorPublicKeyP auditor_pub; - struct GNUNET_HashCode h_denom_pub; + struct TALER_DenominationHashP h_denom_pub; if ( (NULL == args[0]) || (NULL == args[1]) || @@ -724,6 +1507,56 @@ handle_post_auditors (struct TEH_RequestContext *rc, /** + * Generates the response for "/", redirecting the + * client to the ``toplevel_redirect_url``. + * + * @param rc request context + * @param args remaining arguments (should be empty) + * @return MHD result code + */ +static MHD_RESULT +toplevel_redirect (struct TEH_RequestContext *rc, + const char *const args[]) +{ + const char *text = "Redirecting to /webui/"; + struct MHD_Response *response; + + response = MHD_create_response_from_buffer (strlen (text), + (void *) text, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD_add_global_headers (response); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + toplevel_redirect_url)) + { + GNUNET_break (0); + MHD_destroy_response (response); + return MHD_NO; + } + + { + MHD_RESULT ret; + + ret = MHD_queue_response (rc->connection, + MHD_HTTP_FOUND, + response); + MHD_destroy_response (response); + return ret; + } +} + + +/** * Handle incoming HTTP request. * * @param cls closure for MHD daemon (unused) @@ -756,15 +1589,11 @@ handle_mhd_request (void *cls, .data = "User-agent: *\nDisallow: /\n", .response_code = MHD_HTTP_OK }, - /* Landing page, tell humans to go away. */ + /* Landing page, redirect to toplevel_redirect_url */ { .url = "", .method = MHD_HTTP_METHOD_GET, - .handler.get = TEH_handler_static_response, - .mime_type = "text/plain", - .data = - "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n", - .response_code = MHD_HTTP_OK + .handler.get = &toplevel_redirect }, /* AGPL licensing page, redirect to source. As per the AGPL-license, every deployment is required to offer the user a download of the source of @@ -780,6 +1609,18 @@ handle_mhd_request (void *cls, .method = MHD_HTTP_METHOD_GET, .handler.get = &handler_seed }, + /* Configuration */ + { + .url = "config", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_config + }, + /* Performance metrics */ + { + .url = "metrics", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_metrics + }, /* Terms of service */ { .url = "terms", @@ -798,25 +1639,57 @@ handle_mhd_request (void *cls, .method = MHD_HTTP_METHOD_GET, .handler.get = &TEH_keys_get_handler, }, - /* Requests for wiring information */ { - .url = "wire", - .method = MHD_HTTP_METHOD_GET, - .handler.get = &TEH_handler_wire + .url = "batch-deposit", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &TEH_handler_batch_deposit, + .nargs = 0 + }, + /* request R, used in clause schnorr withdraw and refresh */ + { + .url = "csr-melt", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &TEH_handler_csr_melt, + .nargs = 0 + }, + { + .url = "csr-withdraw", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &TEH_handler_csr_withdraw, + .nargs = 0 }, /* Withdrawing coins / interaction with reserves */ { .url = "reserves", .method = MHD_HTTP_METHOD_GET, - .handler.get = &TEH_handler_reserves_get, - .nargs = 1 + .handler.get = &handle_get_reserves, + .nargs = 2, + .nargs_is_upper_bound = true }, { .url = "reserves", .method = MHD_HTTP_METHOD_POST, - .handler.post = &TEH_handler_withdraw, + .handler.post = &handle_post_reserves, .nargs = 2 }, + { + .url = "age-withdraw", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &handle_post_age_withdraw, + .nargs = 2 + }, + { + .url = "reserves-attest", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_reserves_get_attest, + .nargs = 1 + }, + { + .url = "reserves-attest", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &TEH_handler_reserves_attest, + .nargs = 1 + }, /* coins */ { .url = "coins", @@ -827,8 +1700,9 @@ handle_mhd_request (void *cls, { .url = "coins", .method = MHD_HTTP_METHOD_GET, - .handler.get = TEH_handler_link, + .handler.get = &handle_get_coins, .nargs = 2, + .nargs_is_upper_bound = true }, /* refreshes/$RCH/reveal */ { @@ -851,12 +1725,40 @@ handle_mhd_request (void *cls, .handler.get = &TEH_handler_deposits_get, .nargs = 4 }, + /* Operating on purses */ + { + .url = "purses", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &handle_post_purses, + .nargs = 2 + }, + /* Getting purse status */ + { + .url = "purses", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_purses_get, + .nargs = 2 + }, + /* Deleting purse */ + { + .url = "purses", + .method = MHD_HTTP_METHOD_DELETE, + .handler.delete = &TEH_handler_purses_delete, + .nargs = 1 + }, + /* Getting contracts */ + { + .url = "contracts", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_contracts_get, + .nargs = 1 + }, /* KYC endpoints */ { .url = "kyc-check", .method = MHD_HTTP_METHOD_GET, .handler.get = &TEH_handler_kyc_check, - .nargs = 1 + .nargs = 3 }, { .url = "kyc-proof", @@ -870,6 +1772,20 @@ handle_mhd_request (void *cls, .handler.post = &TEH_handler_kyc_wallet, .nargs = 0 }, + { + .url = "kyc-webhook", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_kyc_webhook_get, + .nargs = 16, /* more is not plausible */ + .nargs_is_upper_bound = true + }, + { + .url = "kyc-webhook", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &TEH_handler_kyc_webhook_post, + .nargs = 16, /* more is not plausible */ + .nargs_is_upper_bound = true + }, /* POST management endpoints */ { .url = "management", @@ -893,6 +1809,28 @@ handle_mhd_request (void *cls, .nargs = 4, .nargs_is_upper_bound = true }, + /* AML endpoints */ + { + .url = "aml", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &handle_get_aml, + .nargs = 4, + .nargs_is_upper_bound = true + }, + { + .url = "aml", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &handle_post_aml, + .nargs = 2 + }, + { + .url = "webui", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_spa, + .nargs = 1, + .nargs_is_upper_bound = true + }, + /* mark end of list */ { .url = NULL @@ -906,25 +1844,14 @@ handle_mhd_request (void *cls, (void) version; if (NULL == rc) { - unsigned long long cnt; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling new request\n"); - /* Atomic operation, no need for a lock ;-) */ - cnt = __sync_add_and_fetch (&req_count, - 1LLU); - if (req_max == cnt) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Restarting exchange service after %llu requests\n", - cnt); - (void) kill (getpid (), - SIGHUP); - } /* We're in a new async scope! */ rc = *con_cls = GNUNET_new (struct TEH_RequestContext); + rc->start_time = GNUNET_TIME_absolute_get (); GNUNET_async_scope_fresh (&rc->async_scope_id); + TEH_check_invariants (); rc->url = url; rc->connection = connection; /* We only read the correlation ID on the first callback for every client */ @@ -939,10 +1866,19 @@ handle_mhd_request (void *cls, "illegal incoming correlation ID\n"); correlation_id = NULL; } + + /* Check if upload is in bounds */ + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)) + { + TALER_MHD_check_content_length (connection, + TALER_MHD_REQUEST_BUFFER_MAX); + } } GNUNET_async_scope_enter (&rc->async_scope_id, &old_scope); + TEH_check_invariants (); if (NULL != correlation_id) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling request (%s) for URL '%s', correlation_id=%s\n", @@ -982,7 +1918,7 @@ handle_mhd_request (void *cls, if (0 == strcasecmp (method, MHD_HTTP_METHOD_HEAD)) - method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ + method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ /* parse first part of URL */ { @@ -1016,7 +1952,8 @@ handle_mhd_request (void *cls, continue; found = true; /* The URL is a match! What we now do depends on the method. */ - if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS)) + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_OPTIONS)) { GNUNET_async_scope_restore (&old_scope); return TALER_MHD_reply_cors_preflight (connection); @@ -1114,129 +2051,6 @@ handle_mhd_request (void *cls, /** - * Load general KYC configuration parameters for the exchange server into the - * #TEH_kyc_config variable. - * - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_kyc_settings (void) -{ - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (TEH_cfg, - "exchange", - "KYC_WITHDRAW_PERIOD", - &TEH_kyc_config.withdraw_period)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_WITHDRAW_PERIOD", - "valid relative time expected"); - return GNUNET_SYSERR; - } - if (GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) - return GNUNET_OK; - if (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - "exchange", - "KYC_WITHDRAW_LIMIT", - &TEH_kyc_config.withdraw_limit)) - return GNUNET_SYSERR; - if (0 != strcasecmp (TEH_kyc_config.withdraw_limit.currency, - TEH_currency)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_WITHDRAW_LIMIT", - "currency mismatch"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Load OAuth2.0 configuration parameters for the exchange server into the - * #TEH_kyc_config variable. - * - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_kyc_oauth_cfg (void) -{ - char *s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_URL", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_URL"); - return GNUNET_SYSERR; - } - if ( (! TALER_url_valid_charset (s)) || - ( (0 != strncasecmp (s, - "http://", - strlen ("http://"))) && - (0 != strncasecmp (s, - "https://", - strlen ("https://"))) ) ) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_URL", - "not a valid URL"); - GNUNET_free (s); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.url = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_ID", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_ID"); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.client_id = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_SECRET", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_SECRET"); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.client_secret = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_POST_URL", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_POST_URL"); - return GNUNET_SYSERR; - } - TEH_kyc_config.details.oauth2.post_kyc_redirect_url = s; - return GNUNET_OK; -} - - -/** * Load configuration parameters for the exchange * server into the corresponding global variables. * @@ -1245,46 +2059,15 @@ parse_kyc_oauth_cfg (void) static enum GNUNET_GenericReturnValue exchange_serve_process_config (void) { + static struct TALER_CurrencySpecification defspec = { + .num_fractional_input_digits = 2, + .num_fractional_normal_digits = 2, + .num_fractional_trailing_zero_digits = 2 + }; + if (GNUNET_OK != + TALER_KYCLOGIC_kyc_init (TEH_cfg)) { - char *kyc_mode; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange", - "KYC_MODE", - &kyc_mode)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_MODE"); - return GNUNET_SYSERR; - } - if (0 == strcasecmp (kyc_mode, - "NONE")) - { - TEH_kyc_config.mode = TEH_KYC_NONE; - } - else if (0 == strcasecmp (kyc_mode, - "OAUTH2")) - { - TEH_kyc_config.mode = TEH_KYC_OAUTH2; - if (GNUNET_OK != - parse_kyc_oauth_cfg ()) - { - GNUNET_free (kyc_mode); - return GNUNET_SYSERR; - } - } - else - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_MODE", - "Must be 'NONE' or 'OAUTH2'"); - GNUNET_free (kyc_mode); - return GNUNET_SYSERR; - } - GNUNET_free (kyc_mode); + return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (TEH_cfg, @@ -1322,6 +2105,25 @@ exchange_serve_process_config (void) return GNUNET_SYSERR; } if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + "exchange", + "KYC_AML_TRIGGER", + &TEH_kyc_aml_trigger)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "KYC_AML_TRIGGER"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + "exchange", + "TOPLEVEL_REDIRECT_URL", + &toplevel_redirect_url)) + { + toplevel_redirect_url = GNUNET_strdup ("/terms"); + } + if (GNUNET_OK != TALER_config_get_currency (TEH_cfg, &TEH_currency)) { @@ -1330,6 +2132,99 @@ exchange_serve_process_config (void) "CURRENCY"); return GNUNET_SYSERR; } + + if (GNUNET_OK != + TALER_CONFIG_parse_currencies (TEH_cfg, + &num_cspecs, + &cspecs)) + return GNUNET_SYSERR; + for (unsigned int i = 0; i<num_cspecs; i++) + { + struct TALER_CurrencySpecification *cspec; + + cspec = &cspecs[i]; + if (0 == strcmp (TEH_currency, + cspec->currency)) + { + TEH_cspec = cspec; + break; + } + } + if (NULL == TEH_cspec) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING, + "taler", + "CURRENCY", + "Lacking enabled currency specification for the given currency, using default"); + defspec.map_alt_unit_names + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("0", + TEH_currency) + ); + defspec.name = TEH_currency; + GNUNET_assert (strlen (TEH_currency) < + sizeof (defspec.currency)); + strcpy (defspec.currency, + TEH_currency); + TEH_cspec = &defspec; + } + if (GNUNET_OK != + TALER_config_get_amount (TEH_cfg, + "exchange", + "AML_THRESHOLD", + &TEH_aml_threshold)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Need amount in section `exchange' under `AML_THRESHOLD'\n"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_amount (TEH_cfg, + "exchange", + "STEFAN_ABS", + &TEH_stefan_abs)) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &TEH_stefan_abs)); + } + if (GNUNET_OK != + TALER_config_get_amount (TEH_cfg, + "exchange", + "STEFAN_LOG", + &TEH_stefan_log)) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &TEH_stefan_log)); + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_float (TEH_cfg, + "exchange", + "STEFAN_LIN", + &TEH_stefan_lin)) + { + TEH_stefan_lin = 0.0f; + } + + if (0 != strcmp (TEH_currency, + TEH_aml_threshold.currency)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Amount in section `exchange' under `AML_THRESHOLD' uses the wrong currency!\n"); + return GNUNET_SYSERR; + } + TEH_enable_rewards + = GNUNET_CONFIGURATION_get_value_yesno ( + TEH_cfg, + "exchange", + "ENABLE_REWARDS"); + if (GNUNET_SYSERR == TEH_enable_rewards) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Need YES or NO in section `exchange' under `ENABLE_REWARDS'\n"); + return GNUNET_SYSERR; + } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (TEH_cfg, "exchange", @@ -1350,35 +2245,6 @@ exchange_serve_process_config (void) return GNUNET_SYSERR; } - if (TEH_KYC_NONE != TEH_kyc_config.mode) - { - if (GNUNET_YES == - GNUNET_CONFIGURATION_have_value (TEH_cfg, - "exchange", - "KYC_WALLET_BALANCE_LIMIT")) - { - if ( (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - "exchange", - "KYC_WALLET_BALANCE_LIMIT", - &TEH_kyc_config.wallet_balance_limit)) || - (0 != strcasecmp (TEH_currency, - TEH_kyc_config.wallet_balance_limit.currency)) ) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_WALLET_BALANCE_LIMIT", - "valid amount expected"); - return GNUNET_SYSERR; - } - } - else - { - memset (&TEH_kyc_config.wallet_balance_limit, - 0, - sizeof (TEH_kyc_config.wallet_balance_limit)); - } - } { char *master_public_key_str; @@ -1394,34 +2260,58 @@ exchange_serve_process_config (void) return GNUNET_SYSERR; } if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str, - strlen ( - master_public_key_str), - &TEH_master_public_key. - eddsa_pub)) + GNUNET_CRYPTO_eddsa_public_key_from_string ( + master_public_key_str, + strlen (master_public_key_str), + &TEH_master_public_key.eddsa_pub)) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid master public key given in exchange configuration."); + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "MASTER_PUBLIC_KEY", + "invalid base32 encoding for a master public key"); GNUNET_free (master_public_key_str); return GNUNET_SYSERR; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Launching exchange with public key `%s'...\n", + master_public_key_str); GNUNET_free (master_public_key_str); } - if (TEH_KYC_NONE != TEH_kyc_config.mode) + { + char *attr_enc_key_str; + if (GNUNET_OK != - parse_kyc_settings ()) + GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + "exchange", + "ATTRIBUTE_ENCRYPTION_KEY", + &attr_enc_key_str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "ATTRIBUTE_ENCRYPTION_KEY"); return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash (attr_enc_key_str, + strlen (attr_enc_key_str), + &TEH_attribute_key.hash); + GNUNET_free (attr_enc_key_str); } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Launching exchange with public key `%s'...\n", - GNUNET_p2s (&TEH_master_public_key.eddsa_pub)); - if (NULL == - (TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg))) + for (unsigned int i = 0; i<MAX_DB_RETRIES; i++) + { + TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg); + if (NULL != TEH_plugin) + break; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to connect to DB, will try again %u times\n", + MAX_DB_RETRIES - i); + sleep (1); + } + if (NULL == TEH_plugin) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to initialize DB subsystem\n"); + "Failed to initialize DB subsystem. Giving up.\n"); return GNUNET_SYSERR; } return GNUNET_OK; @@ -1566,7 +2456,9 @@ run_single_request (void) xfork = fork (); if (-1 == xfork) { - global_ret = EXIT_FAILURE; + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "fork"); + global_ret = EXIT_NO_RESTART; GNUNET_SCHEDULER_shutdown (); return; } @@ -1618,6 +2510,18 @@ connection_done (void *cls, (void) connection; (void) socket_context; + switch (toe) + { + case MHD_CONNECTION_NOTIFY_STARTED: + active_connections++; + break; + case MHD_CONNECTION_NOTIFY_CLOSED: + active_connections--; + if (TEH_suicide && + (0 == active_connections) ) + GNUNET_SCHEDULER_shutdown (); + break; + } #if HAVE_DEVELOPER /* We only act if the connection is closed. */ if (MHD_CONNECTION_NOTIFY_CLOSED != toe) @@ -1641,11 +2545,19 @@ do_shutdown (void *cls) mhd = TALER_MHD_daemon_stop (); TEH_resume_keys_requests (true); + TEH_deposits_get_cleanup (); TEH_reserves_get_cleanup (); + TEH_purses_get_cleanup (); + TEH_kyc_check_cleanup (); TEH_kyc_proof_cleanup (); + TALER_KYCLOGIC_kyc_done (); if (NULL != mhd) + { MHD_stop_daemon (mhd); - TEH_WIRE_done (); + mhd = NULL; + } + TEH_wire_done (); + TEH_extensions_done (); TEH_keys_finished (); if (NULL != TEH_plugin) { @@ -1662,6 +2574,12 @@ do_shutdown (void *cls) GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc); exchange_curl_rc = NULL; } + TALER_TEMPLATING_done (); + TEH_cspec = NULL; + TALER_CONFIG_free_currencies (num_cspecs, + cspecs); + num_cspecs = 0; + cspecs = NULL; } @@ -1683,6 +2601,9 @@ run (void *cls, enum TALER_MHD_GlobalOptions go; int fh; + (void) cls; + (void) args; + (void ) cfgfile; go = TALER_MHD_GO_NONE; if (connection_close) go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; @@ -1696,24 +2617,48 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } + if (GNUNET_OK != + TEH_spa_init ()) + { + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + TALER_TEMPLATING_init ("exchange")) + { + global_ret = EXIT_NOTINSTALLED; + GNUNET_SCHEDULER_shutdown (); + return; + } if (GNUNET_SYSERR == TEH_plugin->preflight (TEH_plugin->cls)) { - global_ret = EXIT_FAILURE; + GNUNET_break (0); + global_ret = EXIT_NO_RESTART; + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + TEH_extensions_init ()) + { + global_ret = EXIT_NOTINSTALLED; GNUNET_SCHEDULER_shutdown (); return; } if (GNUNET_OK != TEH_keys_init ()) { - global_ret = EXIT_FAILURE; + GNUNET_break (0); + global_ret = EXIT_NO_RESTART; GNUNET_SCHEDULER_shutdown (); return; } if (GNUNET_OK != TEH_wire_init ()) { - global_ret = EXIT_FAILURE; + GNUNET_break (0); + global_ret = EXIT_NO_RESTART; GNUNET_SCHEDULER_shutdown (); return; } @@ -1725,7 +2670,7 @@ run (void *cls, if (NULL == TEH_curl_ctx) { GNUNET_break (0); - global_ret = EXIT_FAILURE; + global_ret = EXIT_NO_RESTART; GNUNET_SCHEDULER_shutdown (); return; } @@ -1741,44 +2686,43 @@ run (void *cls, 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_BACKLOG_SIZE, + (unsigned int) 1024, + 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_NOTIFY_CONNECTION, + &connection_done, + NULL, + MHD_OPTION_CONNECTION_TIMEOUT, + connection_timeout, + (0 == allow_address_reuse) + ? MHD_OPTION_END + : MHD_OPTION_LISTENING_ADDRESS_REUSE, + (unsigned int) allow_address_reuse, + MHD_OPTION_END); + if (NULL == mhd) { - struct MHD_Daemon *mhd; - - 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_BACKLOG_SIZE, - (unsigned int) 1024, - 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_NOTIFY_CONNECTION, - &connection_done, - NULL, - MHD_OPTION_CONNECTION_TIMEOUT, - connection_timeout, - 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); + 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); atexit (&write_stats); - #if HAVE_DEVELOPER if (NULL != input_filename) run_single_request (); @@ -1806,6 +2750,14 @@ main (int argc, "connection-close", "force HTTP connections to be closed after each request", &connection_close), + GNUNET_GETOPT_option_flag ('I', + "check-invariants", + "enable expensive invariant checks", + &TEH_check_invariants_flag), + GNUNET_GETOPT_option_flag ('r', + "allow-reuse-address", + "allow multiple HTTPDs to listen to the same port", + &allow_address_reuse), GNUNET_GETOPT_option_uint ('t', "timeout", "SECONDS", |