/* This file is part of TALER Copyright (C) 2014-2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see */ /** * @file taler-auditor-httpd.c * @brief Serve the HTTP interface of the auditor * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include #include "taler_mhd_lib.h" #include "taler_auditordb_lib.h" #include "taler_exchangedb_lib.h" #include "taler-auditor-httpd_spa.h" #include "taler-auditor-httpd_deposit-confirmation.h" #include "taler-auditor-httpd_deposit-confirmation-del.h" #include "taler-auditor-httpd_deposit-confirmation-get.h" #include "taler-auditor-httpd_amount-arithmetic-inconsistency-get.h" #include "taler-auditor-httpd_amount-arithmetic-inconsistency-del.h" #include "taler-auditor-httpd_amount-arithmetic-inconsistency-upd.h" #include "taler-auditor-httpd_coin-inconsistency-get.h" #include "taler-auditor-httpd_coin-inconsistency-del.h" #include "taler-auditor-httpd_row-inconsistency-get.h" #include "taler-auditor-httpd_row-inconsistency-del.h" #include "taler-auditor-httpd_emergency-get.h" #include "taler-auditor-httpd_emergency-del.h" #include "taler-auditor-httpd_emergency-by-count-get.h" #include "taler-auditor-httpd_emergency-by-count-del.h" #include \ "taler-auditor-httpd_denomination-key-validity-withdraw-inconsistency-get.h" #include \ "taler-auditor-httpd_denomination-key-validity-withdraw-inconsistency-del.h" #include "taler-auditor-httpd_purse-not-closed-inconsistencies-get.h" #include "taler-auditor-httpd_purse-not-closed-inconsistencies-del.h" #include "taler-auditor-httpd_reserve-balance-insufficient-inconsistency-get.h" #include "taler-auditor-httpd_reserve-balance-insufficient-inconsistency-del.h" #include "taler-auditor-httpd_bad-sig-losses-get.h" #include "taler-auditor-httpd_bad-sig-losses-del.h" #include "taler-auditor-httpd_bad-sig-losses-upd.h" #include "taler-auditor-httpd_closure-lags-get.h" #include "taler-auditor-httpd_closure-lags-del.h" #include "taler-auditor-httpd_progress-get.h" #include "taler-auditor-httpd_refreshes-hanging-get.h" #include "taler-auditor-httpd_refreshes-hanging-del.h" #include "taler-auditor-httpd_mhd.h" #include "taler-auditor-httpd.h" #include "taler-auditor-httpd_patch_generic_suppressed.h" #include "taler-auditor-httpd_emergency-by-count-upd.h" #include "taler-auditor-httpd_row-inconsistency-upd.h" #include "taler-auditor-httpd_purse-not-closed-inconsistencies-upd.h" #include "taler-auditor-httpd_reserve-balance-insufficient-inconsistency-upd.h" #include "taler-auditor-httpd_coin-inconsistency-upd.h" #include \ "taler-auditor-httpd_denomination-key-validity-withdraw-inconsistency-upd.h" #include "taler-auditor-httpd_refreshes-hanging-upd.h" #include "taler-auditor-httpd_emergency-upd.h" #include "taler-auditor-httpd_closure-lags-upd.h" #include "taler-auditor-httpd_row-minor-inconsistencies-upd.h" #include "taler-auditor-httpd_reserve-in-inconsistency-del.h" #include "taler-auditor-httpd_reserve-in-inconsistency-get.h" #include "taler-auditor-httpd_reserve-in-inconsistency-upd.h" #include "taler-auditor-httpd_reserve-not-closed-inconsistency-del.h" #include "taler-auditor-httpd_reserve-not-closed-inconsistency-get.h" #include "taler-auditor-httpd_reserve-not-closed-inconsistency-upd.h" #include "taler-auditor-httpd_denominations-without-sigs-del.h" #include "taler-auditor-httpd_denominations-without-sigs-get.h" #include "taler-auditor-httpd_denominations-without-sigs-upd.h" #include "taler-auditor-httpd_misattribution-in-inconsistency-del.h" #include "taler-auditor-httpd_misattribution-in-inconsistency-get.h" #include "taler-auditor-httpd_misattribution-in-inconsistency-upd.h" #include "taler-auditor-httpd_reserves-get.h" #include "taler-auditor-httpd_purses-get.h" #include "taler-auditor-httpd_historic-denomination-revenue-get.h" #include "taler-auditor-httpd_historic-reserve-summary-get.h" #include "taler-auditor-httpd_denomination-pending-del.h" #include "taler-auditor-httpd_denomination-pending-get.h" #include "taler-auditor-httpd_denomination-pending-upd.h" #include "taler-auditor-httpd_wire-format-inconsistency-del.h" #include "taler-auditor-httpd_wire-format-inconsistency-get.h" #include "taler-auditor-httpd_wire-format-inconsistency-upd.h" #include "taler-auditor-httpd_wire-out-inconsistency-del.h" #include "taler-auditor-httpd_wire-out-inconsistency-get.h" #include "taler-auditor-httpd_wire-out-inconsistency-upd.h" #include "taler-auditor-httpd_reserve-balance-summary-wrong-inconsistency-del.h" #include "taler-auditor-httpd_reserve-balance-summary-wrong-inconsistency-get.h" #include "taler-auditor-httpd_reserve-balance-summary-wrong-inconsistency-upd.h" #include "taler-auditor-httpd_row-minor-inconsistencies-del.h" #include "taler-auditor-httpd_row-minor-inconsistencies-get.h" #include "taler-auditor-httpd_row-minor-inconsistencies-upd.h" #include "taler-auditor-httpd_fee-time-inconsistency-del.h" #include "taler-auditor-httpd_fee-time-inconsistency-get.h" #include "taler-auditor-httpd_fee-time-inconsistency-upd.h" #include "taler-auditor-httpd_balances-get.h" /** * Auditor protocol version string. * * Taler protocol version in the format CURRENT:REVISION:AGE * as used by GNU libtool. See * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html * * Please be very careful when updating and follow * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info * precisely. Note that this version has NOTHING to do with the * release version, and the format is NOT the same that semantic * versioning uses either. */ #define AUDITOR_PROTOCOL_VERSION "1:0:1" /** * Salt we use when doing the KDF for access. */ #define KDF_SALT "auditor-standard-auth" /** * Backlog for listen operation on unix domain sockets. */ #define UNIX_BACKLOG 500 /** * Should we return "Connection: close" in each response? */ static int auditor_connection_close; /** * The auditor's configuration. */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Our DB plugin. */ struct TALER_AUDITORDB_Plugin *TAH_plugin; /** * Our DB plugin to talk to the *exchange* database. */ struct TALER_EXCHANGEDB_Plugin *TAH_eplugin; /** * Public key of this auditor. */ static struct TALER_AuditorPublicKeyP auditor_pub; /** * Exchange master public key (according to the * configuration). (global) */ struct TALER_MasterPublicKeyP TAH_master_public_key; /** * Default timeout in seconds for HTTP requests. */ static unsigned int connection_timeout = 30; /** * Return value from main() */ static int global_ret; /** * Disables authentication checks. */ static int disable_auth; /** * Port to run the daemon on. */ static uint16_t serve_port; /** * Our currency. */ char *TAH_currency; /** * Authorization code to use. */ static struct GNUNET_HashCode TAH_auth; /** * Prefix required for the access token. */ #define RFC_8959_PREFIX "secret-token:" /** * Function called whenever MHD is done with a request. If the * request was a POST, we may have stored a `struct Buffer *` in the * @a con_cls that might still need to be cleaned up. Call the * respective function to free the memory. * * @param cls client-defined closure * @param connection connection handle * @param con_cls value as set by the last call to * the #MHD_AccessHandlerCallback * @param toe reason for request termination * @see #MHD_OPTION_NOTIFY_COMPLETED * @ingroup request */ static void handle_mhd_completion_callback (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { (void) cls; (void) connection; (void) toe; if (NULL == *con_cls) return; TALER_MHD_parse_post_cleanup_callback (*con_cls); *con_cls = NULL; } /** * Handle a "/config" request. * * @param rh context of the handler * @param connection the MHD connection to handle * @param[in,out] connection_cls the connection's closure (can be updated) * @param upload_data upload data * @param[in,out] upload_data_size number of bytes (left) in @a upload_data * @param args NULL-terminated array of remaining parts of the URI broken up at '/' * @return MHD result code */ static MHD_RESULT handle_config (struct TAH_RequestHandler *rh, struct MHD_Connection *connection, void **connection_cls, const char *upload_data, size_t *upload_data_size, const char *const args[]) { static json_t *ver; /* we build the response only once, keep around for next query! */ (void) rh; (void) upload_data; (void) upload_data_size; (void) connection_cls; if (NULL == ver) { ver = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("name", "taler-auditor"), GNUNET_JSON_pack_string ("version", AUDITOR_PROTOCOL_VERSION), GNUNET_JSON_pack_string ("implementation", "urn:net:taler:specs:taler-auditor:c-reference"), GNUNET_JSON_pack_string ("currency", TAH_currency), GNUNET_JSON_pack_data_auto ("auditor_public_key", &auditor_pub), GNUNET_JSON_pack_data_auto ("exchange_master_public_key", &TAH_master_public_key)); } if (NULL == ver) { GNUNET_break (0); return MHD_NO; } return TALER_MHD_reply_json (connection, ver, MHD_HTTP_OK); } /** * Extract the token from authorization header value @a auth. * * @param auth pointer to authorization header value, * will be updated to point to the start of the token * or set to NULL if header value is invalid */ static void extract_token (const char **auth) { const char *bearer = "Bearer "; const char *tok = *auth; if (0 != strncmp (tok, bearer, strlen (bearer))) { *auth = NULL; return; } tok += strlen (bearer); while (' ' == *tok) tok++; if (0 != strncasecmp (tok, RFC_8959_PREFIX, strlen (RFC_8959_PREFIX))) { *auth = NULL; return; } *auth = tok; } enum GNUNET_GenericReturnValue TMH_check_auth (const char *token) { struct GNUNET_HashCode val; if (NULL == token) return GNUNET_SYSERR; token += strlen (RFC_8959_PREFIX); GNUNET_assert (GNUNET_YES == GNUNET_CRYPTO_kdf (&val, sizeof (val), KDF_SALT, strlen (KDF_SALT), token, strlen (token), NULL, 0)); /* We compare hashes instead of directly comparing tokens to minimize side-channel attacks on token length */ return (0 == GNUNET_memcmp_priv (&val, &TAH_auth)) ? GNUNET_OK : GNUNET_SYSERR; } /** * Handle incoming HTTP request. * * @param cls closure for MHD daemon (unused) * @param connection the connection * @param url the requested url * @param method the method (POST, GET, ...) * @param version HTTP version (ignored) * @param upload_data request data * @param upload_data_size size of @a upload_data in bytes * @param con_cls closure for request (a `struct Buffer *`) * @return MHD result code */ static MHD_RESULT handle_mhd_request (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { static struct TAH_RequestHandler handlers[] = { /* Our most popular handler (thus first!), used by merchants to probabilistically report us their deposit confirmations. */ { .url = "/deposit-confirmation", .method = MHD_HTTP_METHOD_PUT, .mime_type = "application/json", .handler = &TAH_DEPOSIT_CONFIRMATION_handler, .response_code = MHD_HTTP_OK }, { .url = "/spa", .method = MHD_HTTP_METHOD_GET, .handler = &TAH_spa_handler }, { "/monitoring/deposit-confirmation", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_DEPOSIT_CONFIRMATION_handler_get, MHD_HTTP_OK, true }, { "/monitoring/deposit-confirmation", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_DEPOSIT_CONFIRMATION_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/amount-arithmetic-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_AMOUNT_ARITHMETIC_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/amount-arithmetic-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_AMOUNT_ARITHMETIC_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/amount-arithmetic-inconsistency", MHD_HTTP_METHOD_PATCH, .mime_type = "application/json", .data = NULL, .data_size = 0, &TAH_patch_handler_generic_suppressed, MHD_HTTP_OK, true, .table = TALER_AUDITORDB_AMOUNT_ARITHMETIC_INCONSISTENCY }, { "/monitoring/coin-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_COIN_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/coin-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_COIN_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/coin-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_COIN_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/row-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_ROW_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/row-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_ROW_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/row-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_ROW_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/bad-sig-losses", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_BAD_SIG_LOSSES_handler_get, MHD_HTTP_OK, true }, { "/monitoring/bad-sig-losses", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_BAD_SIG_LOSSES_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/bad-sig-losses", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_BAD_SIG_LOSSES_handler_update, MHD_HTTP_OK, true }, { "/monitoring/closure-lags", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_CLOSURE_LAGS_handler_get, MHD_HTTP_OK, true }, { "/monitoring/closure-lags", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_CLOSURE_LAGS_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/closure-lags", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_CLOSURE_LAGS_handler_update, MHD_HTTP_OK, true }, { "/monitoring/emergency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_EMERGENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/emergency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_EMERGENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/emergency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_EMERGENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/refreshes-hanging", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_REFRESHES_HANGING_handler_get, MHD_HTTP_OK, true }, { "/monitoring/refreshes-hanging", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_REFRESHES_HANGING_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/refreshes-hanging", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_REFRESHES_HANGING_handler_update, MHD_HTTP_OK, true }, { "/monitoring/denomination-key-validity-withdraw-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_DENOMINATION_KEY_VALIDITY_WITHDRAW_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/denomination-key-validity-withdraw-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_DENOMINATION_KEY_VALIDITY_WITHDRAW_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/denomination-key-validity-withdraw-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_DENOMINATION_KEY_VALIDITY_WITHDRAW_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/progress", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_PROGRESS_handler_get, MHD_HTTP_OK, true }, { "/monitoring/reserve-balance-insufficient-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_RESERVE_BALANCE_INSUFFICIENT_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/reserve-balance-insufficient-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_RESERVE_BALANCE_INSUFFICIENT_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/reserve-balance-insufficient-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_RESERVE_BALANCE_INSUFFICIENT_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/purse-not-closed-inconsistencies", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_PURSE_NOT_CLOSED_INCONSISTENCIES_handler_get, MHD_HTTP_OK, true }, { "/monitoring/purse-not-closed-inconsistencies", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_PURSE_NOT_CLOSED_INCONSISTENCIES_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/purse-not-closed-inconsistencies", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_PURSE_NOT_CLOSED_INCONSISTENCIES_handler_update, MHD_HTTP_OK, true }, { "/monitoring/emergency-by-count", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_EMERGENCY_BY_COUNT_handler_get, MHD_HTTP_OK, true }, { "/monitoring/emergency-by-count", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_EMERGENCY_BY_COUNT_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/emergency-by-count", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_EMERGENCY_BY_COUNT_handler_update, MHD_HTTP_OK, true }, { "/monitoring/reserve-in-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_RESERVE_IN_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/reserve-in-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_RESERVE_IN_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/reserve-in-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_RESERVE_IN_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/reserve-not-closed-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_RESERVE_NOT_CLOSED_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/reserve-not-closed-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_RESERVE_NOT_CLOSED_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/reserve-not-closed-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_RESERVE_NOT_CLOSED_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/denominations-without-sigs", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_DENOMINATIONS_WITHOUT_SIGS_handler_get, MHD_HTTP_OK, true }, { "/monitoring/denominations-without-sigs", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_DENOMINATIONS_WITHOUT_SIGS_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/denominations-without-sigs", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_DENOMINATIONS_WITHOUT_SIGS_handler_update, MHD_HTTP_OK, true }, { "/monitoring/misattribution-in-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_MISATTRIBUTION_IN_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/misattribution-in-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_MISATTRIBUTION_IN_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/misattribution-in-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_MISATTRIBUTION_IN_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/reserves", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_RESERVES_handler_get, MHD_HTTP_OK, true }, { "/monitoring/purses", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_PURSES_handler_get, MHD_HTTP_OK, true }, { "/monitoring/historic-denomination-revenue", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_HISTORIC_DENOMINATION_REVENUE_handler_get, MHD_HTTP_OK, true }, { "/monitoring/denomination-pending", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_DENOMINATION_PENDING_handler_get, MHD_HTTP_OK, true }, { "/monitoring/denomination-pending", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_DENOMINATION_PENDING_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/historic-reserve-summary", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_HISTORIC_RESERVE_SUMMARY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/wire-format-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_WIRE_FORMAT_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/wire-format-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_WIRE_FORMAT_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/wire-format-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_WIRE_FORMAT_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/wire-out-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_WIRE_OUT_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/wire-out-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_WIRE_OUT_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/wire-out-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_WIRE_OUT_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/reserve-balance-summary-wrong-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_RESERVE_BALANCE_SUMMARY_WRONG_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/reserve-balance-summary-wrong-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_RESERVE_BALANCE_SUMMARY_WRONG_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/reserve-balance-summary-wrong-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_RESERVE_BALANCE_SUMMARY_WRONG_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/row-minor-inconsistencies", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_ROW_MINOR_INCONSISTENCIES_handler_get, MHD_HTTP_OK, true }, { "/monitoring/row-minor-inconsistencies", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_ROW_MINOR_INCONSISTENCIES_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/row-minor-inconsistencies", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_ROW_MINOR_INCONSISTENCIES_handler_update, MHD_HTTP_OK, true }, { "/monitoring/fee-time-inconsistency", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_FEE_TIME_INCONSISTENCY_handler_get, MHD_HTTP_OK, true }, { "/monitoring/fee-time-inconsistency", MHD_HTTP_METHOD_DELETE, "application/json", NULL, 0, &TAH_FEE_TIME_INCONSISTENCY_handler_delete, MHD_HTTP_OK, true }, { "/monitoring/fee-time-inconsistency", MHD_HTTP_METHOD_PATCH, "application/json", NULL, 0, &TAH_FEE_TIME_INCONSISTENCY_handler_update, MHD_HTTP_OK, true }, { "/monitoring/balances", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &TAH_BALANCES_handler_get, MHD_HTTP_OK, true }, { "/config", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, &handle_config, MHD_HTTP_OK, false }, /* /robots.txt: disallow everything */ { "/robots.txt", MHD_HTTP_METHOD_GET, "text/plain", "User-agent: *\nDisallow: /\n", 0, &TAH_MHD_handler_static_response, MHD_HTTP_OK, false }, /* AGPL licensing page, redirect to source. As per the AGPL-license, every deployment is required to offer the user a download of the source. We make this easy by including a redirect t the source here. */ { "/agpl", MHD_HTTP_METHOD_GET, "text/plain", NULL, 0, &TAH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND, false }, /* Landing page, for now tells humans to go away * (NOTE: ideally, the reverse proxy will respond with a nicer page) */ { "/", MHD_HTTP_METHOD_GET, "text/plain", "Hello, I'm the Taler auditor. This HTTP server is not for humans.\n", 0, &TAH_MHD_handler_static_response, MHD_HTTP_OK, false }, { NULL, NULL, NULL, NULL, 0, NULL, 0, 0 } }; unsigned int args_max = 3; const char *args[args_max + 1]; size_t ulen = strlen (url) + 1; char d[ulen]; /* const */ struct TAH_RequestHandler *match = NULL; bool url_match = false; (void) cls; (void) version; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling request for URL '%s'\n", url); if (0 == strcasecmp (method, MHD_HTTP_METHOD_HEAD)) method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS) ) return TALER_MHD_reply_cors_preflight (connection); memset (&args, 0, sizeof (args)); GNUNET_memcpy (d, url, ulen); { unsigned int i = 0; for (args[i] = strtok (d, "/"); NULL != args[i]; args[i] = strtok (NULL, "/")) { i++; if (i >= args_max) { GNUNET_break_op (0); goto not_found; } } } for (unsigned int i = 0; NULL != handlers[i].url; i++) { /* const */ struct TAH_RequestHandler *rh = &handlers[i]; if ( (0 == strcmp (url, rh->url)) || ( (0 == strncmp (url, rh->url, strlen (rh->url))) && ('/' == url[strlen (rh->url)]) ) ) { url_match = true; if ( (NULL == rh->method) || (0 == strcasecmp (method, rh->method)) ) { match = rh; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Matched %s\n", rh->url); break; } } } if (NULL == match) { GNUNET_break_op (0); goto not_found; } if (match->requires_auth && (0 == disable_auth) ) { const char *auth; auth = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_AUTHORIZATION); if (NULL == auth) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_UNAUTHORIZED, TALER_EC_AUDITOR_GENERIC_UNAUTHORIZED, "Check 'Authorization' header"); } extract_token (&auth); if (NULL == auth) return TALER_MHD_reply_with_error ( connection, MHD_HTTP_UNAUTHORIZED, TALER_EC_GENERIC_PARAMETER_MALFORMED, "'" RFC_8959_PREFIX "' prefix or 'Bearer' missing in 'Authorization' header"); if (GNUNET_OK != TMH_check_auth (auth)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_UNAUTHORIZED, TALER_EC_AUDITOR_GENERIC_UNAUTHORIZED, "Check 'Authorization' header"); } } return match->handler (match, connection, con_cls, upload_data, upload_data_size, args); not_found: if (url_match) { /* FIXME: return list of allowed methods... */ GNUNET_break (0); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_METHOD_NOT_ALLOWED, TALER_EC_AUDITOR_GENERIC_METHOD_NOT_ALLOWED, "This method is currently disabled."); } #define NOT_FOUND \ "404: not foundauditor endpoints have been moved to /monitoring/..." return TALER_MHD_reply_static (connection, MHD_HTTP_NOT_FOUND, "text/html", NOT_FOUND, strlen (NOT_FOUND)); #undef NOT_FOUND } /** * Load configuration parameters for the auditor * server into the corresponding global variables. * * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue auditor_serve_process_config (void) { if (NULL == (TAH_plugin = TALER_AUDITORDB_plugin_load (cfg))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to initialize DB subsystem to interact with auditor database\n"); return GNUNET_SYSERR; } if (NULL == (TAH_eplugin = TALER_EXCHANGEDB_plugin_load (cfg))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to initialize DB subsystem to query exchange database\n"); return GNUNET_SYSERR; } if (GNUNET_SYSERR == TAH_eplugin->preflight (TAH_eplugin->cls)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to initialize DB subsystem to query exchange database\n"); return GNUNET_SYSERR; } if (GNUNET_OK != TALER_config_get_currency (cfg, &TAH_currency)) { return GNUNET_SYSERR; } { char *master_public_key_str; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "exchange", "MASTER_PUBLIC_KEY", &master_public_key_str)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchange", "MASTER_PUBLIC_KEY"); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string ( master_public_key_str, strlen (master_public_key_str), &TAH_master_public_key.eddsa_pub)) { 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 auditor for exchange `%s'...\n", master_public_key_str); GNUNET_free (master_public_key_str); } { char *pub; if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, "AUDITOR", "PUBLIC_KEY", &pub)) { if (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (pub, strlen (pub), &auditor_pub.eddsa_pub)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid public key given in auditor configuration."); GNUNET_free (pub); return GNUNET_SYSERR; } GNUNET_free (pub); return GNUNET_OK; } } { /* Fall back to trying to read private key */ char *auditor_key_file; struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "auditor", "AUDITOR_PRIV_FILE", &auditor_key_file)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "AUDITOR", "PUBLIC_KEY"); GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "AUDITOR", "AUDITOR_PRIV_FILE"); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_CRYPTO_eddsa_key_from_file (auditor_key_file, GNUNET_NO, &eddsa_priv)) { /* Both failed, complain! */ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "AUDITOR", "PUBLIC_KEY"); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to initialize auditor key from file `%s'\n", auditor_key_file); GNUNET_free (auditor_key_file); return 1; } GNUNET_free (auditor_key_file); GNUNET_CRYPTO_eddsa_key_get_public (&eddsa_priv, &auditor_pub.eddsa_pub); } return GNUNET_OK; } /** * Function run on shutdown. * * @param cls NULL */ static void do_shutdown (void *cls) { struct MHD_Daemon *mhd; (void) cls; mhd = TALER_MHD_daemon_stop (); TEAH_DEPOSIT_CONFIRMATION_done (); if (NULL != mhd) MHD_stop_daemon (mhd); if (NULL != TAH_plugin) { TALER_AUDITORDB_plugin_unload (TAH_plugin); TAH_plugin = NULL; } if (NULL != TAH_eplugin) { TALER_EXCHANGEDB_plugin_unload (TAH_eplugin); TAH_eplugin = NULL; } } /** * Main function that will be run by the scheduler. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be * NULL!) * @param config configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *config) { enum TALER_MHD_GlobalOptions go; int fh; (void) cls; (void) args; (void) cfgfile; if (0 == disable_auth) { const char *tok; tok = getenv ("TALER_AUDITOR_ACCESS_TOKEN"); if (NULL == tok) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "TALER_AUDITOR_ACCESS_TOKEN environment variable not set. Disabling authentication\n"); disable_auth = 1; } else { GNUNET_assert (GNUNET_YES == GNUNET_CRYPTO_kdf (&TAH_auth, sizeof (TAH_auth), KDF_SALT, strlen (KDF_SALT), tok, strlen (tok), NULL, 0)); } } go = TALER_MHD_GO_NONE; if (auditor_connection_close) go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; TALER_MHD_setup (go); cfg = config; GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); if (GNUNET_OK != auditor_serve_process_config ()) { global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; } if (GNUNET_OK != TAH_spa_init ()) { global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; } TEAH_DEPOSIT_CONFIRMATION_init (); fh = TALER_MHD_bind (cfg, "auditor", &serve_port); if ( (0 == serve_port) && (-1 == fh) ) { GNUNET_SCHEDULER_shutdown (); return; } { 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_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); } } /** * The main function of the taler-auditor-httpd server ("the auditor"). * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { const struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_flag ('C', "connection-close", "force HTTP connections to be closed after each request", &auditor_connection_close), GNUNET_GETOPT_option_flag ('n', "no-authentication", "disable authentication checks", &disable_auth), GNUNET_GETOPT_option_uint ('t', "timeout", "SECONDS", "after how long do connections timeout by default (in seconds)", &connection_timeout), GNUNET_GETOPT_option_help ( "HTTP server providing a RESTful API to access a Taler auditor"), GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION), GNUNET_GETOPT_OPTION_END }; int ret; TALER_OS_init (); ret = GNUNET_PROGRAM_run (argc, argv, "taler-auditor-httpd", "Taler auditor HTTP service", options, &run, NULL); if (GNUNET_SYSERR == ret) return EXIT_INVALIDARGUMENT; if (GNUNET_NO == ret) return EXIT_SUCCESS; return global_ret; } /* end of taler-auditor-httpd.c */