diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd.c | 1614 |
1 files changed, 1228 insertions, 386 deletions
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index b948c315c..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 @@ -77,14 +111,17 @@ static int allow_address_reuse; const struct GNUNET_CONFIGURATION_Handle *TEH_cfg; /** - * Handle to the HTTP server. + * Configuration of age restriction + * + * Set after loading the library, enabled in database event handler. */ -static struct MHD_Daemon *mhd; +bool TEH_age_restriction_enabled = false; +struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0}; /** - * Our KYC configuration. + * 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) @@ -103,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; @@ -139,6 +218,14 @@ int TEH_check_invariants_flag; 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; @@ -166,17 +253,32 @@ static unsigned long long active_connections; 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. * @@ -215,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 @@ -239,10 +340,6 @@ handle_post_coins (struct TEH_RequestContext *rc, } h[] = { { - .op = "deposit", - .handler = &TEH_handler_deposit - }, - { .op = "melt", .handler = &TEH_handler_melt }, @@ -251,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 }, @@ -284,6 +385,572 @@ 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. */ @@ -354,6 +1021,11 @@ handle_mhd_completion_callback (void *cls, 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 @@ -382,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); @@ -406,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 @@ -429,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; @@ -438,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; } @@ -557,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 @@ -569,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); @@ -609,7 +1347,7 @@ handle_post_management (struct TEH_RequestContext *rc, if (0 == strcmp (args[0], "denominations")) { - struct TALER_DenominationHash h_denom_pub; + struct TALER_DenominationHashP h_denom_pub; if ( (NULL == args[0]) || (NULL == args[1]) || @@ -664,46 +1402,22 @@ handle_post_management (struct TEH_RequestContext *rc, &exchange_pub, root); } - if (0 == strcmp (args[0], - "keys")) + 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/keys/*"); + 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_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")) - { - if (NULL != args[1]) - { - GNUNET_break_op (0); - return r404 (rc->connection, - "/management/wire-fee/*"); - } - return TEH_handler_management_post_wire_fees (rc->connection, - root); } GNUNET_break_op (0); return r404 (rc->connection, @@ -712,7 +1426,7 @@ 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 [0] == "keys") @@ -750,7 +1464,7 @@ handle_post_auditors (struct TEH_RequestContext *rc, const char *const args[]) { struct TALER_AuditorPublicKeyP auditor_pub; - struct TALER_DenominationHash h_denom_pub; + struct TALER_DenominationHashP h_denom_pub; if ( (NULL == args[0]) || (NULL == args[1]) || @@ -793,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) @@ -825,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 @@ -849,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", @@ -867,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", @@ -896,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 */ { @@ -920,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", @@ -939,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", @@ -962,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 @@ -980,6 +1849,7 @@ handle_mhd_request (void *cls, /* 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; @@ -996,6 +1866,14 @@ 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, @@ -1040,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 */ { @@ -1074,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); @@ -1172,248 +2051,178 @@ handle_mhd_request (void *cls, /** - * Load general KYC configuration parameters for the exchange server into the - * #TEH_kyc_config variable. + * Load configuration parameters for the exchange + * server into the corresponding global variables. * * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -parse_kyc_settings (void) +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 != - GNUNET_CONFIGURATION_get_value_time (TEH_cfg, - "exchange", - "KYC_WITHDRAW_PERIOD", - &TEH_kyc_config.withdraw_period)) + TALER_KYCLOGIC_kyc_init (TEH_cfg)) { - 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_CONFIGURATION_get_value_number (TEH_cfg, + "exchange", + "MAX_REQUESTS", + &req_max)) { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_WITHDRAW_LIMIT", - "currency mismatch"); - return GNUNET_SYSERR; + req_max = ULLONG_MAX; } - 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_CONFIGURATION_get_value_time (TEH_cfg, + "exchangedb", + "IDLE_RESERVE_EXPIRATION_TIME", + &TEH_reserve_closing_delay)) { 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; + "exchangedb", + "IDLE_RESERVE_EXPIRATION_TIME"); + /* use default */ + TEH_reserve_closing_delay + = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS, + 4); } - TEH_kyc_config.details.oauth2.url = s; if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_INFO_URL", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_INFO_URL"); - return GNUNET_SYSERR; - } - if ( (! TALER_url_valid_charset (s)) || - ( (0 != strncasecmp (s, - "http://", - strlen ("http://"))) && - (0 != strncasecmp (s, - "https://", - strlen ("https://"))) ) ) + GNUNET_CONFIGURATION_get_value_time (TEH_cfg, + "exchange", + "MAX_KEYS_CACHING", + &TEH_max_keys_caching)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_INFO_URL", - "not a valid URL"); - GNUNET_free (s); + "exchange", + "MAX_KEYS_CACHING", + "valid relative time expected"); return GNUNET_SYSERR; } - TEH_kyc_config.details.oauth2.info_url = s; - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_ID", - &s)) + "exchange", + "KYC_AML_TRIGGER", + &TEH_kyc_aml_trigger)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_ID"); + "exchange", + "KYC_AML_TRIGGER"); 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)) + "exchange", + "TOPLEVEL_REDIRECT_URL", + &toplevel_redirect_url)) { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_CLIENT_SECRET"); - return GNUNET_SYSERR; + toplevel_redirect_url = GNUNET_strdup ("/terms"); } - 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)) + TALER_config_get_currency (TEH_cfg, + &TEH_currency)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchange-kyc-oauth2", - "KYC_OAUTH2_POST_URL"); + "taler", + "CURRENCY"); 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. - * - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -exchange_serve_process_config (void) -{ + if (GNUNET_OK != + TALER_CONFIG_parse_currencies (TEH_cfg, + &num_cspecs, + &cspecs)) + return GNUNET_SYSERR; + for (unsigned int i = 0; i<num_cspecs; i++) { - char *kyc_mode; + struct TALER_CurrencySpecification *cspec; - 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 + cspec = &cspecs[i]; + if (0 == strcmp (TEH_currency, + cspec->currency)) { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "KYC_MODE", - "Must be 'NONE' or 'OAUTH2'"); - GNUNET_free (kyc_mode); - return GNUNET_SYSERR; + TEH_cspec = cspec; + break; } - GNUNET_free (kyc_mode); } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (TEH_cfg, - "exchange", - "MAX_REQUESTS", - &req_max)) + if (NULL == TEH_cspec) { - req_max = ULLONG_MAX; + 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 != - GNUNET_CONFIGURATION_get_value_time (TEH_cfg, - "exchangedb", - "IDLE_RESERVE_EXPIRATION_TIME", - &TEH_reserve_closing_delay)) + TALER_config_get_amount (TEH_cfg, + "exchange", + "AML_THRESHOLD", + &TEH_aml_threshold)) { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchangedb", - "IDLE_RESERVE_EXPIRATION_TIME"); - /* use default */ - TEH_reserve_closing_delay - = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS, - 4); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Need amount in section `exchange' under `AML_THRESHOLD'\n"); + return GNUNET_SYSERR; } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (TEH_cfg, - "exchange", - "MAX_KEYS_CACHING", - &TEH_max_keys_caching)) + TALER_config_get_amount (TEH_cfg, + "exchange", + "STEFAN_ABS", + &TEH_stefan_abs)) { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &TEH_stefan_abs)); + } + if (GNUNET_OK != + TALER_config_get_amount (TEH_cfg, "exchange", - "MAX_KEYS_CACHING", - "valid relative time expected"); - return GNUNET_SYSERR; + "STEFAN_LOG", + &TEH_stefan_log)) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &TEH_stefan_log)); } if (GNUNET_OK != - TALER_config_get_currency (TEH_cfg, - &TEH_currency)) + GNUNET_CONFIGURATION_get_value_float (TEH_cfg, + "exchange", + "STEFAN_LIN", + &TEH_stefan_lin)) { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler", - "CURRENCY"); + 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 != @@ -1436,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; @@ -1480,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; @@ -1652,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; } @@ -1739,12 +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) { @@ -1761,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; } @@ -1798,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; } @@ -1827,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; } @@ -1866,8 +2709,8 @@ run (void *cls, MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout, (0 == allow_address_reuse) - ? MHD_OPTION_END - : MHD_OPTION_LISTENING_ADDRESS_REUSE, + ? MHD_OPTION_END + : MHD_OPTION_LISTENING_ADDRESS_REUSE, (unsigned int) allow_address_reuse, MHD_OPTION_END); if (NULL == mhd) @@ -1880,7 +2723,6 @@ run (void *cls, global_ret = EXIT_SUCCESS; TALER_MHD_daemon_start (mhd); atexit (&write_stats); - #if HAVE_DEVELOPER if (NULL != input_filename) run_single_request (); |