exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-auditor-httpd.c (56791B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 /**
     18  * @file taler-auditor-httpd.c
     19  * @brief Serve the HTTP interface of the auditor
     20  * @defgroup request Request handling routines
     21  * @author Florian Dold
     22  * @author Benedikt Mueller
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <jansson.h>
     28 #include <microhttpd.h>
     29 #include <pthread.h>
     30 #include <sys/resource.h>
     31 #include "taler/taler_mhd_lib.h"
     32 #include "auditordb_lib.h"
     33 #include "exchangedb_lib.h"
     34 #include "taler-auditor-httpd_spa.h"
     35 #include "taler-auditor-httpd_put-deposit-confirmation.h"
     36 #include "taler-auditor-httpd_get-monitoring-deposit-confirmations.h"
     37 #include "taler-auditor-httpd_get-monitoring-amount-arithmetic-inconsistency.h"
     38 #include "taler-auditor-httpd_get-monitoring-coin-inconsistency.h"
     39 #include "taler-auditor-httpd_get-monitoring-row-inconsistency.h"
     40 #include "taler-auditor-httpd_get-monitoring-emergency.h"
     41 #include "taler-auditor-httpd_get-monitoring-emergency-by-count.h"
     42 #include "taler-auditor-httpd_get-monitoring-early-aggregation.h"
     43 #include                                                                \
     44   "taler-auditor-httpd_get-monitoring-denomination-key-validity-withdraw-inconsistency.h"
     45 #include "taler-auditor-httpd_get-monitoring-purse-not-closed-inconsistencies.h"
     46 #include \
     47   "taler-auditor-httpd_get-monitoring-reserve-balance-insufficient-inconsistency.h"
     48 #include "taler-auditor-httpd_get-monitoring-bad-sig-losses.h"
     49 #include "taler-auditor-httpd_get-monitoring-aml-holds.h"
     50 #include "taler-auditor-httpd_get-monitoring-closure-lags.h"
     51 #include "taler-auditor-httpd_mhd.h"
     52 #include "taler-auditor-httpd.h"
     53 #include "taler-auditor-httpd_delete-generic.h"
     54 #include "taler-auditor-httpd_patch-generic-suppressed.h"
     55 #include "taler-auditor-httpd_get-monitoring-kycauth-in-inconsistency.h"
     56 #include "taler-auditor-httpd_get-monitoring-reserve-in-inconsistency.h"
     57 #include "taler-auditor-httpd_get-monitoring-reserve-not-closed-inconsistency.h"
     58 #include "taler-auditor-httpd_get-monitoring-denominations-without-sigs.h"
     59 #include "taler-auditor-httpd_get-monitoring-misattribution-in-inconsistency.h"
     60 #include "taler-auditor-httpd_get-monitoring-reserves.h"
     61 #include "taler-auditor-httpd_get-monitoring-pending-deposits.h"
     62 #include "taler-auditor-httpd_get-monitoring-purses.h"
     63 #include "taler-auditor-httpd_get-monitoring-historic-denomination-revenue.h"
     64 #include "taler-auditor-httpd_get-monitoring-historic-reserve-summary.h"
     65 #include "taler-auditor-httpd_get-monitoring-wire-format-inconsistency.h"
     66 #include "taler-auditor-httpd_get-monitoring-wire-out-inconsistency.h"
     67 #include \
     68   "taler-auditor-httpd_get-monitoring-reserve-balance-summary-wrong-inconsistency.h"
     69 #include "taler-auditor-httpd_get-monitoring-row-minor-inconsistencies.h"
     70 #include "taler-auditor-httpd_get-monitoring-fee-time-inconsistency.h"
     71 #include "taler-auditor-httpd_get-monitoring-balances.h"
     72 #include "taler-auditor-httpd_get-monitoring-progress.h"
     73 #include "exchange-database/preflight.h"
     74 
     75 /**
     76  * Auditor protocol version string.
     77  *
     78  * Taler protocol version in the format CURRENT:REVISION:AGE
     79  * as used by GNU libtool.  See
     80  * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
     81  *
     82  * Please be very careful when updating and follow
     83  * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
     84  * precisely.  Note that this version has NOTHING to do with the
     85  * release version, and the format is NOT the same that semantic
     86  * versioning uses either.
     87  */
     88 #define AUDITOR_PROTOCOL_VERSION "2:0:0"
     89 
     90 /**
     91  * Salt we use when doing the KDF for access.
     92  */
     93 #define KDF_SALT "auditor-standard-auth"
     94 
     95 /**
     96  * Backlog for listen operation on unix domain sockets.
     97  */
     98 #define UNIX_BACKLOG 500
     99 
    100 /**
    101  * Maximum number of concurrent connections per HTTP daemon.
    102  * TALER_MHD_daemon_start() keeps our descriptors below FD_SETSIZE;
    103  * this leaves ample room for the database connection and our other
    104  * descriptors.  At the limit, MHD stops accepting until a connection
    105  * closes.
    106  */
    107 #define CONNECTION_LIMIT 300
    108 
    109 /**
    110  * Should we return "Connection: close" in each response?
    111  */
    112 static int auditor_connection_close;
    113 
    114 /**
    115  * The auditor's configuration.
    116  */
    117 static const struct GNUNET_CONFIGURATION_Handle *cfg;
    118 
    119 /**
    120  * Our auditor database context.
    121  */
    122 struct TALER_AUDITORDB_PostgresContext *TAH_apg;
    123 
    124 /**
    125  * Our exchange database context.
    126  */
    127 struct TALER_EXCHANGEDB_PostgresContext *TAH_epg;
    128 
    129 /**
    130  * Public key of this auditor.
    131  */
    132 static struct TALER_AuditorPublicKeyP auditor_pub;
    133 
    134 /**
    135  * Exchange master public key (according to the
    136  * configuration).  (global)
    137  */
    138 struct TALER_MasterPublicKeyP TAH_master_public_key;
    139 
    140 /**
    141  * Exchange master public key (according to the
    142  * configuration).  (global)
    143  */
    144 struct TALER_MasterPublicKeyP TAH_master_public_key;
    145 
    146 char *TAH_spa_dir;
    147 
    148 /**
    149  * Default timeout in seconds for HTTP requests.
    150  */
    151 static unsigned int connection_timeout = 30;
    152 
    153 /**
    154  * Return value from main()
    155  */
    156 static int global_ret;
    157 
    158 /**
    159  * Disables authentication checks.
    160  */
    161 static int disable_auth;
    162 
    163 /**
    164  * True if we started any HTTP daemon.
    165  */
    166 static bool have_daemons;
    167 
    168 /**
    169  * Our currency.
    170  */
    171 char *TAH_currency;
    172 
    173 /**
    174  * Authorization code to use.
    175  */
    176 static struct GNUNET_HashCode TAH_auth;
    177 
    178 /**
    179  * Prefix required for the access token.
    180  */
    181 #define RFC_8959_PREFIX "secret-token:"
    182 
    183 
    184 /**
    185  * Function called whenever MHD is done with a request.  If the
    186  * request was a POST, we may have stored a `struct Buffer *` in the
    187  * @a con_cls that might still need to be cleaned up.  Call the
    188  * respective function to free the memory.
    189  *
    190  * @param cls client-defined closure
    191  * @param connection connection handle
    192  * @param con_cls value as set by the last call to
    193  *        the #MHD_AccessHandlerCallback
    194  * @param toe reason for request termination
    195  * @see #MHD_OPTION_NOTIFY_COMPLETED
    196  * @ingroup request
    197  */
    198 static void
    199 handle_mhd_completion_callback (void *cls,
    200                                 struct MHD_Connection *connection,
    201                                 void **con_cls,
    202                                 enum MHD_RequestTerminationCode toe)
    203 {
    204   (void) cls;
    205   (void) connection;
    206   (void) toe;
    207   if (NULL == *con_cls)
    208     return;
    209   TALER_MHD_parse_post_cleanup_callback (*con_cls);
    210   *con_cls = NULL;
    211 }
    212 
    213 
    214 /**
    215  * Handle a "/config" request.
    216  *
    217  * @param rh context of the handler
    218  * @param connection the MHD connection to handle
    219  * @param[in,out] connection_cls the connection's closure (can be updated)
    220  * @param upload_data upload data
    221  * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
    222  * @param args NULL-terminated array of remaining parts of the URI broken up at '/'
    223  * @return MHD result code
    224  */
    225 static enum MHD_Result
    226 handle_config (struct TAH_RequestHandler *rh,
    227                struct MHD_Connection *connection,
    228                void **connection_cls,
    229                const char *upload_data,
    230                size_t *upload_data_size,
    231                const char *const args[])
    232 {
    233   static json_t *ver; /* we build the response only once, keep around for next query! */
    234 
    235   (void) rh;
    236   (void) upload_data;
    237   (void) upload_data_size;
    238   (void) connection_cls;
    239   if (NULL == ver)
    240   {
    241     ver = GNUNET_JSON_PACK (
    242       GNUNET_JSON_pack_string ("name",
    243                                "taler-auditor"),
    244       GNUNET_JSON_pack_string ("version",
    245                                AUDITOR_PROTOCOL_VERSION),
    246       GNUNET_JSON_pack_string ("build_version",
    247                                PACKAGE_VERSION),
    248       GNUNET_JSON_pack_string ("implementation",
    249                                "urn:net:taler:specs:taler-auditor:c-reference"),
    250       GNUNET_JSON_pack_string ("currency",
    251                                TAH_currency),
    252       GNUNET_JSON_pack_data_auto ("auditor_public_key",
    253                                   &auditor_pub),
    254       GNUNET_JSON_pack_data_auto ("exchange_master_public_key",
    255                                   &TAH_master_public_key));
    256   }
    257   if (NULL == ver)
    258   {
    259     GNUNET_break (0);
    260     return MHD_NO;
    261   }
    262   return TALER_MHD_reply_json (connection,
    263                                ver,
    264                                MHD_HTTP_OK);
    265 }
    266 
    267 
    268 /**
    269  * Extract the token from authorization header value @a auth.
    270  *
    271  * @param auth pointer to authorization header value,
    272  *        will be updated to point to the start of the token
    273  *        or set to NULL if header value is invalid
    274  */
    275 static void
    276 extract_token (const char **auth)
    277 {
    278   const char *bearer = "Bearer ";
    279   const char *tok = *auth;
    280 
    281   if (0 != strncmp (tok,
    282                     bearer,
    283                     strlen (bearer)))
    284   {
    285     *auth = NULL;
    286     return;
    287   }
    288   tok += strlen (bearer);
    289   while (' ' == *tok)
    290     tok++;
    291   if (0 != strncasecmp (tok,
    292                         RFC_8959_PREFIX,
    293                         strlen (RFC_8959_PREFIX)))
    294   {
    295     *auth = NULL;
    296     return;
    297   }
    298   *auth = tok;
    299 }
    300 
    301 
    302 static enum GNUNET_GenericReturnValue
    303 check_auth (const char *token)
    304 {
    305   struct GNUNET_HashCode val;
    306 
    307   if (NULL == token)
    308     return GNUNET_SYSERR;
    309   token += strlen (RFC_8959_PREFIX);
    310   GNUNET_assert (GNUNET_YES ==
    311                  GNUNET_CRYPTO_hkdf_gnunet (
    312                    &val,
    313                    sizeof (val),
    314                    KDF_SALT,
    315                    strlen (KDF_SALT),
    316                    token,
    317                    strlen (token)));
    318   /* We compare hashes instead of directly comparing
    319      tokens to minimize side-channel attacks on token length */
    320   return (0 ==
    321           GNUNET_memcmp_priv (&val,
    322                               &TAH_auth))
    323            ? GNUNET_OK
    324            : GNUNET_SYSERR;
    325 }
    326 
    327 
    328 /**
    329  * Handle incoming HTTP request.
    330  *
    331  * @param cls closure for MHD daemon (unused)
    332  * @param connection the connection
    333  * @param url the requested url
    334  * @param method the method (POST, GET, ...)
    335  * @param version HTTP version (ignored)
    336  * @param upload_data request data
    337  * @param upload_data_size size of @a upload_data in bytes
    338  * @param con_cls closure for request (a `struct Buffer *`)
    339  * @return MHD result code
    340  */
    341 static enum MHD_Result
    342 handle_mhd_request (void *cls,
    343                     struct MHD_Connection *connection,
    344                     const char *url,
    345                     const char *method,
    346                     const char *version,
    347                     const char *upload_data,
    348                     size_t *upload_data_size,
    349                     void **con_cls)
    350 {
    351   static struct TAH_RequestHandler handlers[] = {
    352     /* Our most popular handler (thus first!), used by merchants to
    353        probabilistically report us their deposit confirmations. */
    354     { .url = "/deposit-confirmation",
    355       .method = MHD_HTTP_METHOD_PUT,
    356       .mime_type = "application/json",
    357       .handler = &TAH_put_deposit_confirmation,
    358       .response_code = MHD_HTTP_NO_CONTENT},
    359     { .url = "/webui",
    360       .method = MHD_HTTP_METHOD_GET,
    361       .handler = &TAH_spa_handler},
    362     { .url = "/monitoring/deposit-confirmations",
    363       .method = MHD_HTTP_METHOD_GET,
    364       .mime_type = "application/json",
    365       .data = NULL,
    366       .data_size = 0,
    367       .handler = &TAH_get_monitoring_deposit_confirmations,
    368       .response_code = MHD_HTTP_OK,
    369       .requires_auth = true },
    370     { .url = "/monitoring/deposit-confirmations",
    371       .method = MHD_HTTP_METHOD_PATCH,
    372       .mime_type = "application/json",
    373       .handler = &TAH_patch_generic_suppressed,
    374       .response_code = MHD_HTTP_NO_CONTENT,
    375       .requires_auth = true,
    376       .table = TALER_AUDITORDB_DEPOSIT_CONFIRMATION },
    377     { .url = "/monitoring/pending-deposits",
    378       .method = MHD_HTTP_METHOD_GET,
    379       .mime_type = "application/json",
    380       .data = NULL,
    381       .data_size = 0,
    382       .handler = &TAH_get_monitoring_pending_deposits,
    383       .response_code = MHD_HTTP_OK,
    384       .requires_auth = true },
    385     { .url = "/monitoring/pending-deposits",
    386       .method = MHD_HTTP_METHOD_DELETE,
    387       .mime_type = "application/json",
    388       .handler = &TAH_delete_generic,
    389       .response_code = MHD_HTTP_NO_CONTENT,
    390       .requires_auth = true,
    391       .table = TALER_AUDITORDB_PENDING_DEPOSITS },
    392     { .url = "/monitoring/pending-deposits",
    393       .method = MHD_HTTP_METHOD_PATCH,
    394       .mime_type = "application/json",
    395       .handler = &TAH_patch_generic_suppressed,
    396       .response_code = MHD_HTTP_NO_CONTENT,
    397       .requires_auth = true,
    398       .table = TALER_AUDITORDB_PENDING_DEPOSITS },
    399     { .url = "/monitoring/early-aggregations",
    400       .method = MHD_HTTP_METHOD_GET,
    401       .mime_type = "application/json",
    402       .data = NULL,
    403       .data_size = 0,
    404       .handler = &TAH_get_monitoring_early_aggregation,
    405       .response_code = MHD_HTTP_OK,
    406       .requires_auth = true },
    407     { .url = "/monitoring/early-aggregations",
    408       .method = MHD_HTTP_METHOD_DELETE,
    409       .mime_type = "application/json",
    410       .handler = &TAH_delete_generic,
    411       .response_code = MHD_HTTP_NO_CONTENT,
    412       .requires_auth = true,
    413       .table = TALER_AUDITORDB_EARLY_AGGREGATIONS },
    414     { .url = "/monitoring/early-aggregations",
    415       .method = MHD_HTTP_METHOD_PATCH,
    416       .mime_type = "application/json",
    417       .handler = &TAH_patch_generic_suppressed,
    418       .response_code = MHD_HTTP_NO_CONTENT,
    419       .requires_auth = true,
    420       .table = TALER_AUDITORDB_EARLY_AGGREGATIONS },
    421     { .url = "/monitoring/deposit-confirmations",
    422       .method = MHD_HTTP_METHOD_DELETE,
    423       .mime_type = "application/json",
    424       .data = NULL,
    425       .data_size = 0,
    426       .handler = &TAH_delete_generic,
    427       .response_code = MHD_HTTP_OK,
    428       .requires_auth = true,
    429       .table = TALER_AUDITORDB_DEPOSIT_CONFIRMATION },
    430     { .url = "/monitoring/amount-arithmetic-inconsistencies",
    431       .method = MHD_HTTP_METHOD_GET,
    432       .mime_type = "application/json",
    433       .data = NULL,
    434       .data_size = 0,
    435       .handler = &TAH_get_monitoring_amount_arithmetic_inconsistency,
    436       .response_code = MHD_HTTP_OK,
    437       .requires_auth = true },
    438     { .url = "/monitoring/amount-arithmetic-inconsistencies",
    439       .method = MHD_HTTP_METHOD_DELETE,
    440       .mime_type = "application/json",
    441       .data = NULL,
    442       .data_size = 0,
    443       .handler = &TAH_delete_generic,
    444       .response_code = MHD_HTTP_OK,
    445       .requires_auth = true,
    446       .table = TALER_AUDITORDB_AMOUNT_ARITHMETIC_INCONSISTENCY },
    447     { .url = "/monitoring/amount-arithmetic-inconsistencies",
    448       .method = MHD_HTTP_METHOD_PATCH,
    449       .mime_type = "application/json",
    450       .data = NULL,
    451       .data_size = 0,
    452       .handler = &TAH_patch_generic_suppressed,
    453       .response_code = MHD_HTTP_OK,
    454       .requires_auth = true,
    455       .table = TALER_AUDITORDB_AMOUNT_ARITHMETIC_INCONSISTENCY },
    456     { .url = "/monitoring/coin-inconsistencies",
    457       .method = MHD_HTTP_METHOD_GET,
    458       .mime_type = "application/json",
    459       .data = NULL,
    460       .data_size = 0,
    461       .handler = &TAH_get_monitoring_coin_inconsistency,
    462       .response_code = MHD_HTTP_OK,
    463       .requires_auth = true },
    464     { .url = "/monitoring/coin-inconsistencies",
    465       .method = MHD_HTTP_METHOD_DELETE,
    466       .mime_type = "application/json",
    467       .data = NULL,
    468       .data_size = 0,
    469       .handler = &TAH_delete_generic,
    470       .response_code = MHD_HTTP_OK,
    471       .requires_auth = true,
    472       .table = TALER_AUDITORDB_COIN_INCONSISTENCY },
    473     { .url = "/monitoring/coin-inconsistencies",
    474       .method = MHD_HTTP_METHOD_PATCH,
    475       .mime_type = "application/json",
    476       .data = NULL,
    477       .data_size = 0,
    478       .handler = &TAH_patch_generic_suppressed,
    479       .response_code = MHD_HTTP_OK,
    480       .requires_auth = true,
    481       .table = TALER_AUDITORDB_COIN_INCONSISTENCY },
    482     { .url = "/monitoring/row-inconsistencies",
    483       .method = MHD_HTTP_METHOD_GET,
    484       .mime_type = "application/json",
    485       .data = NULL,
    486       .data_size = 0,
    487       .handler = &TAH_get_monitoring_row_inconsistency,
    488       .response_code = MHD_HTTP_OK,
    489       .requires_auth = true },
    490     { .url = "/monitoring/row-inconsistencies",
    491       .method = MHD_HTTP_METHOD_DELETE,
    492       .mime_type = "application/json",
    493       .data = NULL,
    494       .data_size = 0,
    495       .handler = &TAH_delete_generic,
    496       .response_code = MHD_HTTP_OK,
    497       .requires_auth = true,
    498       .table = TALER_AUDITORDB_ROW_INCONSISTENCY},
    499     { .url = "/monitoring/row-inconsistencies",
    500       .method = MHD_HTTP_METHOD_PATCH,
    501       .mime_type = "application/json",
    502       .data = NULL,
    503       .data_size = 0,
    504       .handler = &TAH_patch_generic_suppressed,
    505       .response_code = MHD_HTTP_OK,
    506       .requires_auth = true,
    507       .table = TALER_AUDITORDB_ROW_INCONSISTENCY },
    508     { .url = "/monitoring/bad-sig-losses",
    509       .method = MHD_HTTP_METHOD_GET,
    510       .mime_type = "application/json",
    511       .data = NULL,
    512       .data_size = 0,
    513       .handler = &TAH_get_monitoring_bad_sig_losses,
    514       .response_code = MHD_HTTP_OK,
    515       .requires_auth = true },
    516     { .url = "/monitoring/bad-sig-losses",
    517       .method = MHD_HTTP_METHOD_DELETE,
    518       .mime_type = "application/json",
    519       .data = NULL,
    520       .data_size = 0,
    521       .handler = &TAH_delete_generic,
    522       .response_code = MHD_HTTP_OK,
    523       .requires_auth = true,
    524       .table = TALER_AUDITORDB_BAD_SIG_LOSSES},
    525     { .url = "/monitoring/bad-sig-losses",
    526       .method = MHD_HTTP_METHOD_PATCH,
    527       .mime_type = "application/json",
    528       .data = NULL,
    529       .data_size = 0,
    530       .handler = &TAH_patch_generic_suppressed,
    531       .response_code = MHD_HTTP_OK,
    532       .requires_auth = true,
    533       .table = TALER_AUDITORDB_BAD_SIG_LOSSES },
    534     { .url = "/monitoring/aml-holds",
    535       .method = MHD_HTTP_METHOD_GET,
    536       .mime_type = "application/json",
    537       .data = NULL,
    538       .data_size = 0,
    539       .handler = &TAH_get_monitoring_aml_holds,
    540       .response_code = MHD_HTTP_OK,
    541       .requires_auth = true },
    542     { .url = "/monitoring/aml-holds",
    543       .method = MHD_HTTP_METHOD_DELETE,
    544       .mime_type = "application/json",
    545       .data = NULL,
    546       .data_size = 0,
    547       .handler = &TAH_delete_generic,
    548       .response_code = MHD_HTTP_OK,
    549       .requires_auth = true,
    550       .table = TALER_AUDITORDB_AML_HOLDS },
    551     { .url = "/monitoring/aml-holds",
    552       .method = MHD_HTTP_METHOD_PATCH,
    553       .mime_type = "application/json",
    554       .data = NULL,
    555       .data_size = 0,
    556       .handler = &TAH_patch_generic_suppressed,
    557       .response_code = MHD_HTTP_OK,
    558       .requires_auth = true,
    559       .table = TALER_AUDITORDB_AML_HOLDS },
    560     { .url = "/monitoring/closure-lags",
    561       .method = MHD_HTTP_METHOD_GET,
    562       .mime_type = "application/json",
    563       .data = NULL,
    564       .data_size = 0,
    565       .handler = &TAH_get_monitoring_closure_lags,
    566       .response_code = MHD_HTTP_OK,
    567       .requires_auth = true },
    568     { .url = "/monitoring/closure-lags",
    569       .method = MHD_HTTP_METHOD_DELETE,
    570       .mime_type = "application/json",
    571       .data = NULL,
    572       .data_size = 0,
    573       .handler = &TAH_delete_generic,
    574       .response_code = MHD_HTTP_OK,
    575       .requires_auth = true,
    576       .table = TALER_AUDITORDB_CLOSURE_LAGS },
    577     { .url = "/monitoring/closure-lags",
    578       .method = MHD_HTTP_METHOD_PATCH,
    579       .mime_type = "application/json",
    580       .data = NULL,
    581       .data_size = 0,
    582       .handler = &TAH_patch_generic_suppressed,
    583       .response_code = MHD_HTTP_OK,
    584       .requires_auth = true,
    585       .table = TALER_AUDITORDB_CLOSURE_LAGS },
    586     { .url = "/monitoring/emergencies",
    587       .method = MHD_HTTP_METHOD_GET,
    588       .mime_type = "application/json",
    589       .data = NULL,
    590       .data_size = 0,
    591       .handler = &TAH_get_monitoring_emergency,
    592       .response_code = MHD_HTTP_OK,
    593       .requires_auth = true },
    594     { .url = "/monitoring/emergencies",
    595       .method = MHD_HTTP_METHOD_DELETE,
    596       .mime_type = "application/json",
    597       .data = NULL,
    598       .data_size = 0,
    599       .handler = &TAH_delete_generic,
    600       .response_code = MHD_HTTP_OK,
    601       .requires_auth = true,
    602       .table = TALER_AUDITORDB_EMERGENCY },
    603     { .url = "/monitoring/emergencies",
    604       .method = MHD_HTTP_METHOD_PATCH,
    605       .mime_type = "application/json",
    606       .data = NULL,
    607       .data_size = 0,
    608       .handler = &TAH_patch_generic_suppressed,
    609       .response_code = MHD_HTTP_OK,
    610       .requires_auth = true,
    611       .table = TALER_AUDITORDB_EMERGENCY  },
    612     { .url = "/monitoring/denomination-key-validity-withdraw-inconsistencies",
    613       .method = MHD_HTTP_METHOD_GET,
    614       .mime_type = "application/json",
    615       .data = NULL,
    616       .data_size = 0,
    617       .handler =
    618         &TAH_get_monitoring_denomination_key_validity_withdraw_inconsistency,
    619       .response_code = MHD_HTTP_OK,
    620       .requires_auth = true },
    621     { .url = "/monitoring/denomination-key-validity-withdraw-inconsistencies",
    622       .method = MHD_HTTP_METHOD_DELETE,
    623       .mime_type = "application/json",
    624       .data = NULL,
    625       .data_size = 0,
    626       .handler = &TAH_delete_generic,
    627       .response_code = MHD_HTTP_OK,
    628       .requires_auth = true,
    629       .table = TALER_AUDITORDB_DENOMINATION_KEY_VALIDITY_WITHDRAW_INCONSISTENCY}
    630     ,
    631     { .url = "/monitoring/denomination-key-validity-withdraw-inconsistencies",
    632       .method = MHD_HTTP_METHOD_PATCH,
    633       .mime_type = "application/json",
    634       .data = NULL,
    635       .data_size = 0,
    636       .handler = &TAH_patch_generic_suppressed,
    637       .response_code = MHD_HTTP_OK,
    638       .requires_auth = true,
    639       .table = TALER_AUDITORDB_DENOMINATION_KEY_VALIDITY_WITHDRAW_INCONSISTENCY}
    640     ,
    641     { .url = "/monitoring/reserve-balance-insufficient-inconsistencies",
    642       .method = MHD_HTTP_METHOD_GET,
    643       .mime_type = "application/json",
    644       .data = NULL,
    645       .data_size = 0,
    646       .handler = &TAH_get_monitoring_reserve_balance_insufficient_inconsistency,
    647       .response_code = MHD_HTTP_OK,
    648       .requires_auth = true },
    649     { .url = "/monitoring/reserve-balance-insufficient-inconsistencies",
    650       .method = MHD_HTTP_METHOD_DELETE,
    651       .mime_type = "application/json",
    652       .data = NULL,
    653       .data_size = 0,
    654       .handler = &TAH_delete_generic,
    655       .response_code = MHD_HTTP_OK,
    656       .requires_auth = true,
    657       .table = TALER_AUDITORDB_RESERVE_BALANCE_INSUFFICIENT_INCONSISTENCY },
    658     { .url = "/monitoring/reserve-balance-insufficient-inconsistencies",
    659       .method = MHD_HTTP_METHOD_PATCH,
    660       .mime_type = "application/json",
    661       .data = NULL,
    662       .data_size = 0,
    663       .handler = &TAH_patch_generic_suppressed,
    664       .response_code = MHD_HTTP_OK,
    665       .requires_auth = true,
    666       .table = TALER_AUDITORDB_RESERVE_BALANCE_INSUFFICIENT_INCONSISTENCY },
    667     { .url = "/monitoring/purse-not-closed-inconsistencies",
    668       .method = MHD_HTTP_METHOD_GET,
    669       .mime_type = "application/json",
    670       .data = NULL,
    671       .data_size = 0,
    672       .handler = &TAH_get_monitoring_purse_not_closed_inconsistencies,
    673       .response_code = MHD_HTTP_OK,
    674       .requires_auth = true },
    675     { .url = "/monitoring/purse-not-closed-inconsistencies",
    676       .method = MHD_HTTP_METHOD_DELETE,
    677       .mime_type = "application/json",
    678       .data = NULL,
    679       .data_size = 0,
    680       .handler = &TAH_delete_generic,
    681       .response_code = MHD_HTTP_OK,
    682       .requires_auth = true,
    683       .table = TALER_AUDITORDB_PURSE_NOT_CLOSED_INCONSISTENCY },
    684     { .url = "/monitoring/purse-not-closed-inconsistencies",
    685       .method = MHD_HTTP_METHOD_PATCH,
    686       .mime_type = "application/json",
    687       .data = NULL,
    688       .data_size = 0,
    689       .handler = &TAH_patch_generic_suppressed,
    690       .response_code = MHD_HTTP_OK,
    691       .requires_auth = true,
    692       .table = TALER_AUDITORDB_PURSE_NOT_CLOSED_INCONSISTENCY  },
    693     { .url = "/monitoring/emergencies-by-count",
    694       .method = MHD_HTTP_METHOD_GET,
    695       .mime_type = "application/json",
    696       .data = NULL,
    697       .data_size = 0,
    698       .handler = &TAH_get_monitoring_emergency_by_count,
    699       .response_code = MHD_HTTP_OK,
    700       .requires_auth = true },
    701     { .url = "/monitoring/emergencies-by-count",
    702       .method = MHD_HTTP_METHOD_DELETE,
    703       .mime_type = "application/json",
    704       .data = NULL,
    705       .data_size = 0,
    706       .handler = &TAH_delete_generic,
    707       .response_code = MHD_HTTP_OK,
    708       .requires_auth = true,
    709       .table = TALER_AUDITORDB_EMERGENCY_BY_COUNT },
    710     { .url = "/monitoring/emergencies-by-count",
    711       .method = MHD_HTTP_METHOD_PATCH,
    712       .mime_type = "application/json",
    713       .data = NULL,
    714       .data_size = 0,
    715       .handler = &TAH_patch_generic_suppressed,
    716       .response_code = MHD_HTTP_OK,
    717       .requires_auth = true,
    718       .table = TALER_AUDITORDB_EMERGENCY_BY_COUNT },
    719     { .url = "/monitoring/reserve-in-inconsistencies",
    720       .method = MHD_HTTP_METHOD_GET,
    721       .mime_type = "application/json",
    722       .data = NULL,
    723       .data_size = 0,
    724       .handler = &TAH_get_monitoring_reserve_in_inconsistency,
    725       .response_code = MHD_HTTP_OK,
    726       .requires_auth = true },
    727     { .url = "/monitoring/reserve-in-inconsistencies",
    728       .method = MHD_HTTP_METHOD_DELETE,
    729       .mime_type = "application/json",
    730       .data = NULL,
    731       .data_size = 0,
    732       .handler = &TAH_delete_generic,
    733       .response_code = MHD_HTTP_OK,
    734       .requires_auth = true,
    735       .table = TALER_AUDITORDB_RESERVE_IN_INCONSISTENCY },
    736     { .url = "/monitoring/reserve-in-inconsistencies",
    737       .method = MHD_HTTP_METHOD_PATCH,
    738       .mime_type = "application/json",
    739       .data = NULL,
    740       .data_size = 0,
    741       .handler = &TAH_patch_generic_suppressed,
    742       .response_code = MHD_HTTP_OK,
    743       .requires_auth = true,
    744       .table = TALER_AUDITORDB_RESERVE_IN_INCONSISTENCY  },
    745     { .url = "/monitoring/kycauth-in-inconsistencies",
    746       .method = MHD_HTTP_METHOD_GET,
    747       .mime_type = "application/json",
    748       .data = NULL,
    749       .data_size = 0,
    750       .handler = &TAH_get_monitoring_kycauth_in_inconsistency,
    751       .response_code = MHD_HTTP_OK,
    752       .requires_auth = true },
    753     { .url = "/monitoring/kycauth-in-inconsistencies",
    754       .method = MHD_HTTP_METHOD_DELETE,
    755       .mime_type = "application/json",
    756       .data = NULL,
    757       .data_size = 0,
    758       .handler = &TAH_delete_generic,
    759       .response_code = MHD_HTTP_OK,
    760       .requires_auth = true,
    761       .table = TALER_AUDITORDB_KYCAUTH_IN_INCONSISTENCY },
    762     { .url = "/monitoring/kycauth-in-inconsistencies",
    763       .method = MHD_HTTP_METHOD_PATCH,
    764       .mime_type = "application/json",
    765       .data = NULL,
    766       .data_size = 0,
    767       .handler = &TAH_patch_generic_suppressed,
    768       .response_code = MHD_HTTP_OK,
    769       .requires_auth = true,
    770       .table = TALER_AUDITORDB_KYCAUTH_IN_INCONSISTENCY  },
    771     { .url = "/monitoring/reserve-not-closed-inconsistencies",
    772       .method = MHD_HTTP_METHOD_GET,
    773       .mime_type = "application/json",
    774       .data = NULL,
    775       .data_size = 0,
    776       .handler = &TAH_get_monitoring_reserve_not_closed_inconsistency,
    777       .response_code = MHD_HTTP_OK,
    778       .requires_auth = true },
    779     { .url = "/monitoring/reserve-not-closed-inconsistencies",
    780       .method = MHD_HTTP_METHOD_DELETE,
    781       .mime_type = "application/json",
    782       .data = NULL,
    783       .data_size = 0,
    784       .handler = &TAH_delete_generic,
    785       .response_code = MHD_HTTP_OK,
    786       .requires_auth = true,
    787       .table = TALER_AUDITORDB_RESERVE_NOT_CLOSED_INCONSISTENCY },
    788     { .url = "/monitoring/reserve-not-closed-inconsistencies",
    789       .method = MHD_HTTP_METHOD_PATCH,
    790       .mime_type = "application/json",
    791       .data = NULL,
    792       .data_size = 0,
    793       .handler = &TAH_patch_generic_suppressed,
    794       .response_code = MHD_HTTP_OK,
    795       .requires_auth = true,
    796       .table = TALER_AUDITORDB_RESERVE_NOT_CLOSED_INCONSISTENCY },
    797     { .url = "/monitoring/denominations-without-sigs",
    798       .method = MHD_HTTP_METHOD_GET,
    799       .mime_type = "application/json",
    800       .data = NULL,
    801       .data_size = 0,
    802       .handler = &TAH_get_monitoring_denominations_without_sigs,
    803       .response_code = MHD_HTTP_OK,
    804       .requires_auth = true },
    805     { .url = "/monitoring/denominations-without-sigs",
    806       .method = MHD_HTTP_METHOD_DELETE,
    807       .mime_type = "application/json",
    808       .data = NULL,
    809       .data_size = 0,
    810       .handler = &TAH_delete_generic,
    811       .response_code = MHD_HTTP_OK,
    812       .requires_auth = true,
    813       .table = TALER_AUDITORDB_DENOMINATIONS_WITHOUT_SIG },
    814     { .url = "/monitoring/denominations-without-sigs",
    815       .method = MHD_HTTP_METHOD_PATCH,
    816       .mime_type = "application/json",
    817       .data = NULL,
    818       .data_size = 0,
    819       .handler = &TAH_patch_generic_suppressed,
    820       .response_code = MHD_HTTP_OK,
    821       .requires_auth = true,
    822       .table = TALER_AUDITORDB_DENOMINATIONS_WITHOUT_SIG },
    823     { .url = "/monitoring/misattribution-in-inconsistencies",
    824       .method = MHD_HTTP_METHOD_GET,
    825       .mime_type = "application/json",
    826       .data = NULL,
    827       .data_size = 0,
    828       .handler = &TAH_get_monitoring_misattribution_in_inconsistency,
    829       .response_code = MHD_HTTP_OK,
    830       .requires_auth = true },
    831     { .url = "/monitoring/misattribution-in-inconsistencies",
    832       .method = MHD_HTTP_METHOD_DELETE,
    833       .mime_type = "application/json",
    834       .data = NULL,
    835       .data_size = 0,
    836       .handler = &TAH_delete_generic,
    837       .response_code = MHD_HTTP_OK,
    838       .requires_auth = true,
    839       .table = TALER_AUDITORDB_MISATTRIBUTION_IN_INCONSISTENCY },
    840     { .url = "/monitoring/misattribution-in-inconsistencies",
    841       .method = MHD_HTTP_METHOD_PATCH,
    842       .mime_type = "application/json",
    843       .data = NULL,
    844       .data_size = 0,
    845       .handler = &TAH_patch_generic_suppressed,
    846       .response_code = MHD_HTTP_OK,
    847       .requires_auth = true,
    848       .table = TALER_AUDITORDB_MISATTRIBUTION_IN_INCONSISTENCY },
    849     { .url = "/monitoring/reserves",
    850       .method = MHD_HTTP_METHOD_GET,
    851       .mime_type = "application/json",
    852       .data = NULL,
    853       .data_size = 0,
    854       .handler = &TAH_get_monitoring_reserves,
    855       .response_code = MHD_HTTP_OK,
    856       .requires_auth = true },
    857     { .url = "/monitoring/purses",
    858       .method = MHD_HTTP_METHOD_GET,
    859       .mime_type = "application/json",
    860       .data = NULL,
    861       .data_size = 0,
    862       .handler = &TAH_get_monitoring_purses,
    863       .response_code = MHD_HTTP_OK,
    864       .requires_auth = true },
    865     { .url = "/monitoring/historic-denomination-revenues",
    866       .method = MHD_HTTP_METHOD_GET,
    867       .mime_type = "application/json",
    868       .data = NULL,
    869       .data_size = 0,
    870       .handler = &TAH_get_monitoring_historic_denomination_revenue,
    871       .response_code = MHD_HTTP_OK,
    872       .requires_auth = true },
    873     { .url = "/monitoring/historic-reserve-summaries",
    874       .method = MHD_HTTP_METHOD_GET,
    875       .mime_type = "application/json",
    876       .data = NULL,
    877       .data_size = 0,
    878       .handler = &TAH_get_monitoring_historic_reserve_summary,
    879       .response_code = MHD_HTTP_OK,
    880       .requires_auth = true },
    881     { .url = "/monitoring/wire-format-inconsistencies",
    882       .method = MHD_HTTP_METHOD_GET,
    883       .mime_type = "application/json",
    884       .data = NULL,
    885       .data_size = 0,
    886       .handler = &TAH_get_monitoring_wire_format_inconsistency,
    887       .response_code = MHD_HTTP_OK,
    888       .requires_auth = true },
    889     { .url = "/monitoring/wire-format-inconsistencies",
    890       .method = MHD_HTTP_METHOD_DELETE,
    891       .mime_type = "application/json",
    892       .data = NULL,
    893       .data_size = 0,
    894       .handler = &TAH_delete_generic,
    895       .response_code = MHD_HTTP_OK,
    896       .requires_auth = true,
    897       .table = TALER_AUDITORDB_WIRE_FORMAT_INCONSISTENCY },
    898     { .url = "/monitoring/wire-format-inconsistencies",
    899       .method = MHD_HTTP_METHOD_PATCH,
    900       .mime_type = "application/json",
    901       .data = NULL,
    902       .data_size = 0,
    903       .handler = &TAH_patch_generic_suppressed,
    904       .response_code = MHD_HTTP_OK,
    905       .requires_auth = true,
    906       .table = TALER_AUDITORDB_WIRE_FORMAT_INCONSISTENCY },
    907     { .url = "/monitoring/wire-out-inconsistencies",
    908       .method = MHD_HTTP_METHOD_GET,
    909       .mime_type = "application/json",
    910       .data = NULL,
    911       .data_size = 0,
    912       .handler = &TAH_get_monitoring_wire_out_inconsistency,
    913       .response_code = MHD_HTTP_OK,
    914       .requires_auth = true },
    915     { .url = "/monitoring/wire-out-inconsistencies",
    916       .method = MHD_HTTP_METHOD_DELETE,
    917       .mime_type = "application/json",
    918       .data = NULL,
    919       .data_size = 0,
    920       .handler = &TAH_delete_generic,
    921       .response_code = MHD_HTTP_OK,
    922       .requires_auth = true,
    923       .table = TALER_AUDITORDB_WIRE_OUT_INCONSISTENCY },
    924     { .url = "/monitoring/wire-out-inconsistencies",
    925       .method = MHD_HTTP_METHOD_PATCH,
    926       .mime_type = "application/json",
    927       .data = NULL,
    928       .data_size = 0,
    929       .handler = &TAH_patch_generic_suppressed,
    930       .response_code = MHD_HTTP_OK,
    931       .requires_auth = true,
    932       .table = TALER_AUDITORDB_WIRE_OUT_INCONSISTENCY },
    933     { .url = "/monitoring/reserve-balance-summary-wrong-inconsistencies",
    934       .method = MHD_HTTP_METHOD_GET,
    935       .mime_type = "application/json",
    936       .data = NULL,
    937       .data_size = 0,
    938       .handler = &TAH_get_monitoring_reserve_balance_summary_wrong_inconsistency
    939       ,
    940       .response_code = MHD_HTTP_OK,
    941       .requires_auth = true },
    942     { .url = "/monitoring/reserve-balance-summary-wrong-inconsistencies",
    943       .method = MHD_HTTP_METHOD_DELETE,
    944       .mime_type = "application/json",
    945       .data = NULL,
    946       .data_size = 0,
    947       .handler = &TAH_delete_generic,
    948       .response_code = MHD_HTTP_OK,
    949       .requires_auth = true,
    950       .table = TALER_AUDITORDB_RESERVE_BALANCE_SUMMARY_WRONG_INCONSISTENCY },
    951     { .url = "/monitoring/reserve-balance-summary-wrong-inconsistencies",
    952       .method = MHD_HTTP_METHOD_PATCH,
    953       .mime_type = "application/json",
    954       .data = NULL,
    955       .data_size = 0,
    956       .handler = &TAH_patch_generic_suppressed,
    957       .response_code = MHD_HTTP_OK,
    958       .requires_auth = true,
    959       .table = TALER_AUDITORDB_RESERVE_BALANCE_SUMMARY_WRONG_INCONSISTENCY },
    960     { .url = "/monitoring/row-minor-inconsistencies",
    961       .method = MHD_HTTP_METHOD_GET,
    962       .mime_type = "application/json",
    963       .data = NULL,
    964       .data_size = 0,
    965       .handler = &TAH_get_monitoring_row_minor_inconsistencies,
    966       .response_code = MHD_HTTP_OK,
    967       .requires_auth = true },
    968     { .url = "/monitoring/row-minor-inconsistencies",
    969       .method = MHD_HTTP_METHOD_DELETE,
    970       .mime_type = "application/json",
    971       .data = NULL,
    972       .data_size = 0,
    973       .handler = &TAH_delete_generic,
    974       .response_code = MHD_HTTP_OK,
    975       .requires_auth = true,
    976       .table = TALER_AUDITORDB_ROW_MINOR_INCONSISTENCY },
    977     { .url = "/monitoring/row-minor-inconsistencies",
    978       .method = MHD_HTTP_METHOD_PATCH,
    979       .mime_type = "application/json",
    980       .data = NULL,
    981       .data_size = 0,
    982       .handler = &TAH_patch_generic_suppressed,
    983       .response_code = MHD_HTTP_OK,
    984       .requires_auth = true,
    985       .table = TALER_AUDITORDB_ROW_MINOR_INCONSISTENCY },
    986     { .url = "/monitoring/fee-time-inconsistencies",
    987       .method = MHD_HTTP_METHOD_GET,
    988       .mime_type = "application/json",
    989       .data = NULL,
    990       .data_size = 0,
    991       .handler = &TAH_get_monitoring_fee_time_inconsistency,
    992       .response_code = MHD_HTTP_OK,
    993       .requires_auth = true },
    994     { .url = "/monitoring/fee-time-inconsistencies",
    995       .method = MHD_HTTP_METHOD_DELETE,
    996       .mime_type = "application/json",
    997       .data = NULL,
    998       .data_size = 0,
    999       .handler = &TAH_delete_generic,
   1000       .response_code = MHD_HTTP_OK,
   1001       .requires_auth = true,
   1002       .table =  TALER_AUDITORDB_FEE_TIME_INCONSISTENCY },
   1003     { .url = "/monitoring/fee-time-inconsistencies",
   1004       .method = MHD_HTTP_METHOD_PATCH,
   1005       .mime_type = "application/json",
   1006       .data = NULL,
   1007       .data_size = 0,
   1008       .handler = &TAH_patch_generic_suppressed,
   1009       .response_code = MHD_HTTP_OK,
   1010       .requires_auth = true,
   1011       .table =  TALER_AUDITORDB_FEE_TIME_INCONSISTENCY  },
   1012     { .url = "/monitoring/balances",
   1013       .method = MHD_HTTP_METHOD_GET,
   1014       .mime_type = "application/json",
   1015       .data = NULL,
   1016       .data_size = 0,
   1017       .handler = &TAH_get_monitoring_balances,
   1018       .response_code = MHD_HTTP_OK,
   1019       .requires_auth = true },
   1020     { .url = "/monitoring/progress",
   1021       .method = MHD_HTTP_METHOD_GET,
   1022       .mime_type = "application/json",
   1023       .data = NULL,
   1024       .data_size = 0,
   1025       .handler = &TAH_get_monitoring_progress,
   1026       .response_code = MHD_HTTP_OK,
   1027       .requires_auth = true },
   1028     { .url = "/config",
   1029       .method = MHD_HTTP_METHOD_GET,
   1030       .mime_type = "application/json",
   1031       .data = NULL,
   1032       .data_size = 0,
   1033       .handler = &handle_config,
   1034       .response_code = MHD_HTTP_OK,
   1035       .requires_auth = false },
   1036     /* /robots.txt: disallow everything */
   1037     { .url = "/robots.txt",
   1038       .method = MHD_HTTP_METHOD_GET,
   1039       .mime_type = "text/plain",
   1040       .data = "User-agent: *\nDisallow: /\n",
   1041       .data_size = 0,
   1042       .handler = &TAH_MHD_handler_static_response,
   1043       .response_code = MHD_HTTP_OK,
   1044       .requires_auth = false },
   1045     /* AGPL licensing page, redirect to source. As per the AGPL-license,
   1046        every deployment is required to offer the user a download of the
   1047        source. We make this easy by including a redirect t the source
   1048        here. */
   1049     { .url = "/agpl",
   1050       .method = MHD_HTTP_METHOD_GET,
   1051       .mime_type = "text/plain",
   1052       .data = NULL,
   1053       .data_size = 0,
   1054       .handler = &TAH_MHD_handler_agpl_redirect,
   1055       .response_code = MHD_HTTP_FOUND,
   1056       .requires_auth = false },
   1057     /* Landing page, for now tells humans to go away
   1058      * (NOTE: ideally, the reverse proxy will respond with a nicer page) */
   1059     { .url = "/",
   1060       .method = MHD_HTTP_METHOD_GET,
   1061       .mime_type = "text/plain",
   1062       .data =
   1063         "Hello, I'm the Taler auditor. This HTTP server is not for humans.\n",
   1064       .data_size = 0,
   1065       .handler = &TAH_MHD_handler_static_response,
   1066       .response_code = MHD_HTTP_OK,
   1067       .requires_auth = false },
   1068     { NULL, NULL, NULL, NULL, 0, NULL, 0, 0 }
   1069   };
   1070   unsigned int args_max = 3;
   1071   const char *args[args_max + 1];
   1072   size_t ulen = strlen (url) + 1;
   1073   char d[ulen];
   1074   /* const */ struct TAH_RequestHandler *match = NULL;
   1075   bool url_match = false;
   1076 
   1077   (void) cls;
   1078   (void) version;
   1079   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1080               "Handling request for URL '%s'\n",
   1081               url);
   1082   if (0 == strcasecmp (method,
   1083                        MHD_HTTP_METHOD_HEAD))
   1084     method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
   1085   if (0 == strcasecmp (method,
   1086                        MHD_HTTP_METHOD_OPTIONS) )
   1087     return TALER_MHD_reply_cors_preflight (connection);
   1088 
   1089   memset (&args,
   1090           0,
   1091           sizeof (args));
   1092   GNUNET_memcpy (d,
   1093                  url,
   1094                  ulen);
   1095   {
   1096     unsigned int i = 0;
   1097 
   1098     for (args[i] = strtok (d,
   1099                            "/");
   1100          NULL != args[i];
   1101          args[i] = strtok (NULL,
   1102                            "/"))
   1103     {
   1104       i++;
   1105       if (i > args_max)
   1106       {
   1107         /* more than @e args_max segments in the URL */
   1108         GNUNET_break_op (0);
   1109         goto not_found;
   1110       }
   1111     }
   1112   }
   1113 
   1114   for (unsigned int i = 0; NULL != handlers[i].url; i++)
   1115   {
   1116     /* const */ struct TAH_RequestHandler *rh = &handlers[i];
   1117 
   1118     if ( (0 == strcmp (url,
   1119                        rh->url)) ||
   1120          ( (0 == strncmp (url,
   1121                           rh->url,
   1122                           strlen (rh->url))) &&
   1123            ('/' == url[strlen (rh->url)]) ) )
   1124     {
   1125       url_match = true;
   1126       if ( (NULL == rh->method) ||
   1127            (0 == strcasecmp (method,
   1128                              rh->method)) )
   1129       {
   1130         match = rh;
   1131         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1132                     "Matched %s\n",
   1133                     rh->url);
   1134         break;
   1135       }
   1136     }
   1137   }
   1138   if (NULL == match)
   1139   {
   1140     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1141                 "Could not find handler for `%s'\n",
   1142                 url);
   1143     goto not_found;
   1144   }
   1145   if (match->requires_auth &&
   1146       (0 == disable_auth) )
   1147   {
   1148     const char *auth;
   1149 
   1150     auth = MHD_lookup_connection_value (connection,
   1151                                         MHD_HEADER_KIND,
   1152                                         MHD_HTTP_HEADER_AUTHORIZATION);
   1153     if (NULL == auth)
   1154     {
   1155       GNUNET_break_op (0);
   1156       return TALER_MHD_reply_with_error (
   1157         connection,
   1158         MHD_HTTP_UNAUTHORIZED,
   1159         TALER_EC_AUDITOR_GENERIC_UNAUTHORIZED,
   1160         "Check 'Authorization' header");
   1161     }
   1162     extract_token (&auth);
   1163     if (NULL == auth)
   1164       return TALER_MHD_reply_with_error (
   1165         connection,
   1166         MHD_HTTP_BAD_REQUEST,
   1167         TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1168         "'" RFC_8959_PREFIX
   1169         "' prefix or 'Bearer' missing in 'Authorization' header");
   1170 
   1171     if (GNUNET_OK !=
   1172         check_auth (auth))
   1173     {
   1174       GNUNET_break_op (0);
   1175       return TALER_MHD_reply_with_error (
   1176         connection,
   1177         MHD_HTTP_UNAUTHORIZED,
   1178         TALER_EC_AUDITOR_GENERIC_UNAUTHORIZED,
   1179         "Check 'Authorization' header");
   1180     }
   1181   }
   1182 
   1183   return match->handler (match,
   1184                          connection,
   1185                          con_cls,
   1186                          upload_data,
   1187                          upload_data_size,
   1188                          args);
   1189 not_found:
   1190   if (url_match)
   1191   {
   1192     /* The URL exists, but not for the requested HTTP method: respond with
   1193        405 Method Not Allowed and an 'Allow' header listing the methods that
   1194        are supported for this URL (#9424). */
   1195     char allow[128] = "OPTIONS";
   1196     size_t aoff = strlen ("OPTIONS");
   1197     struct MHD_Response *resp;
   1198     enum MHD_Result ret;
   1199 
   1200     GNUNET_break_op (0);
   1201     /* OPTIONS is always supported (handled above); additionally list every
   1202        method registered for this URL. */
   1203     for (unsigned int i = 0; NULL != handlers[i].url; i++)
   1204     {
   1205       const struct TAH_RequestHandler *rh = &handlers[i];
   1206 
   1207       if (NULL == rh->method)
   1208         continue;
   1209       if ( (0 != strcmp (url,
   1210                          rh->url)) &&
   1211            ! ( (0 == strncmp (url,
   1212                               rh->url,
   1213                               strlen (rh->url))) &&
   1214                ('/' == url[strlen (rh->url)]) ) )
   1215         continue;
   1216       GNUNET_assert (aoff + strlen (rh->method) + 3 < sizeof (allow));
   1217       memcpy (&allow[aoff],
   1218               ", ",
   1219               2);
   1220       aoff += 2;
   1221       memcpy (&allow[aoff],
   1222               rh->method,
   1223               strlen (rh->method));
   1224       aoff += strlen (rh->method);
   1225       allow[aoff] = '\0';
   1226     }
   1227     resp = MHD_create_response_from_buffer (0,
   1228                                             NULL,
   1229                                             MHD_RESPMEM_PERSISTENT);
   1230     TALER_MHD_add_global_headers (resp,
   1231                                   false);
   1232     GNUNET_break (MHD_YES ==
   1233                   MHD_add_response_header (resp,
   1234                                            MHD_HTTP_HEADER_ALLOW,
   1235                                            allow));
   1236     ret = MHD_queue_response (connection,
   1237                               MHD_HTTP_METHOD_NOT_ALLOWED,
   1238                               resp);
   1239     MHD_destroy_response (resp);
   1240     return ret;
   1241   }
   1242 
   1243 #define NOT_FOUND \
   1244         "<html><title>404: not found</title><body>auditor endpoints have been moved to /monitoring/...</body></html>"
   1245   return TALER_MHD_reply_static (connection,
   1246                                  MHD_HTTP_NOT_FOUND,
   1247                                  "text/html",
   1248                                  NOT_FOUND,
   1249                                  strlen (NOT_FOUND));
   1250 #undef NOT_FOUND
   1251 }
   1252 
   1253 
   1254 /**
   1255  * Load configuration parameters for the auditor
   1256  * server into the corresponding global variables.
   1257  *
   1258  * @return EXIT_SUCCESS on success, EXIT_NOTCONFIGURED for invalid settings,
   1259  *         EXIT_FAILURE for an unavailable dependency
   1260  */
   1261 static int
   1262 auditor_serve_process_config (void)
   1263 {
   1264   bool have_auditor_public_key = false;
   1265 
   1266   if (NULL ==
   1267       (TAH_apg = TALER_AUDITORDB_connect (cfg)))
   1268   {
   1269     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1270                 "Failed to initialize DB subsystem to interact with auditor database\n");
   1271     return EXIT_FAILURE;
   1272   }
   1273   if (NULL ==
   1274       (TAH_epg = TALER_EXCHANGEDB_connect (cfg)))
   1275   {
   1276     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1277                 "Failed to initialize DB subsystem to query exchange database\n");
   1278     return EXIT_FAILURE;
   1279   }
   1280   if (GNUNET_SYSERR ==
   1281       TALER_EXCHANGEDB_preflight (TAH_epg))
   1282   {
   1283     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1284                 "Failed to initialize DB subsystem to query exchange database\n");
   1285     return EXIT_FAILURE;
   1286   }
   1287   if (GNUNET_OK !=
   1288       TALER_config_get_currency (cfg,
   1289                                  "exchange",
   1290                                  &TAH_currency))
   1291   {
   1292     return EXIT_NOTCONFIGURED;
   1293   }
   1294 
   1295   {
   1296     char *master_public_key_str;
   1297 
   1298     if (GNUNET_OK !=
   1299         GNUNET_CONFIGURATION_get_value_string (cfg,
   1300                                                "exchange",
   1301                                                "MASTER_PUBLIC_KEY",
   1302                                                &master_public_key_str))
   1303     {
   1304       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1305                                  "exchange",
   1306                                  "MASTER_PUBLIC_KEY");
   1307       return EXIT_NOTCONFIGURED;
   1308     }
   1309     if (GNUNET_OK !=
   1310         GNUNET_CRYPTO_eddsa_public_key_from_string (
   1311           master_public_key_str,
   1312           strlen (master_public_key_str),
   1313           &TAH_master_public_key.eddsa_pub))
   1314     {
   1315       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1316                                  "exchange",
   1317                                  "MASTER_PUBLIC_KEY",
   1318                                  "invalid base32 encoding for a master public key");
   1319       GNUNET_free (master_public_key_str);
   1320       return EXIT_NOTCONFIGURED;
   1321     }
   1322     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1323                 "Launching auditor for exchange `%s'...\n",
   1324                 master_public_key_str);
   1325     GNUNET_free (master_public_key_str);
   1326   }
   1327 
   1328   {
   1329     char *pub;
   1330 
   1331     if (GNUNET_OK ==
   1332         GNUNET_CONFIGURATION_get_value_string (cfg,
   1333                                                "AUDITOR",
   1334                                                "PUBLIC_KEY",
   1335                                                &pub))
   1336     {
   1337       if (GNUNET_OK !=
   1338           GNUNET_CRYPTO_eddsa_public_key_from_string (pub,
   1339                                                       strlen (pub),
   1340                                                       &auditor_pub.eddsa_pub))
   1341       {
   1342         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1343                     "Invalid public key given in auditor configuration.");
   1344         GNUNET_free (pub);
   1345         return EXIT_NOTCONFIGURED;
   1346       }
   1347       GNUNET_free (pub);
   1348       have_auditor_public_key = true;
   1349     }
   1350   }
   1351 
   1352   if (! have_auditor_public_key)
   1353   {
   1354     /* Fall back to trying to read private key */
   1355     char *auditor_key_file;
   1356     struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
   1357 
   1358     if (GNUNET_OK !=
   1359         GNUNET_CONFIGURATION_get_value_filename (cfg,
   1360                                                  "auditor",
   1361                                                  "AUDITOR_PRIV_FILE",
   1362                                                  &auditor_key_file))
   1363     {
   1364       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1365                                  "AUDITOR",
   1366                                  "PUBLIC_KEY");
   1367       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1368                                  "AUDITOR",
   1369                                  "AUDITOR_PRIV_FILE");
   1370       return EXIT_NOTCONFIGURED;
   1371     }
   1372     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1373                 "Loading auditor private key from %s\n",
   1374                 auditor_key_file);
   1375     if (GNUNET_OK !=
   1376         GNUNET_CRYPTO_eddsa_key_from_file (auditor_key_file,
   1377                                            GNUNET_NO,
   1378                                            &eddsa_priv))
   1379     {
   1380       /* Both failed, complain! */
   1381       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1382                                  "AUDITOR",
   1383                                  "PUBLIC_KEY");
   1384       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1385                   "Failed to initialize auditor key from file `%s'\n",
   1386                   auditor_key_file);
   1387       GNUNET_free (auditor_key_file);
   1388       return 1;
   1389     }
   1390     GNUNET_free (auditor_key_file);
   1391     GNUNET_CRYPTO_eddsa_key_get_public (&eddsa_priv,
   1392                                         &auditor_pub.eddsa_pub);
   1393   }
   1394   if (GNUNET_OK ==
   1395       GNUNET_CONFIGURATION_get_value_filename (cfg,
   1396                                                "auditor",
   1397                                                "SPA_DIR",
   1398                                                &TAH_spa_dir))
   1399   {
   1400     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1401                 "Loading auditor SPA from %s\n",
   1402                 TAH_spa_dir);
   1403   }
   1404   else
   1405   {
   1406     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1407                 "Loading auditor SPA from default location\n");
   1408   }
   1409   return EXIT_SUCCESS;
   1410 }
   1411 
   1412 
   1413 /**
   1414  * Load the access token clients must present to use the endpoints that
   1415  * require authentication, and derive #TAH_auth from it.  The
   1416  * TALER_AUDITOR_ACCESS_TOKEN environment variable takes precedence over
   1417  * the ACCESS_TOKEN option in the [auditor] section.  Clients send the
   1418  * token with the RFC 8959 "secret-token:" prefix; it MUST be stored with
   1419  * it.
   1420  *
   1421  * @return #GNUNET_OK on success, #GNUNET_SYSERR if no usable token is
   1422  *         configured
   1423  */
   1424 static enum GNUNET_GenericReturnValue
   1425 load_access_token (void)
   1426 {
   1427   const char *tok;
   1428   char *cfg_tok = NULL;
   1429 
   1430   tok = getenv ("TALER_AUDITOR_ACCESS_TOKEN");
   1431   if ( (NULL == tok) &&
   1432        (GNUNET_OK ==
   1433         GNUNET_CONFIGURATION_get_value_string (cfg,
   1434                                                "auditor",
   1435                                                "ACCESS_TOKEN",
   1436                                                &cfg_tok)) )
   1437     tok = cfg_tok;
   1438   if (NULL == tok)
   1439   {
   1440     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1441                 "No access token configured: set ACCESS_TOKEN in section [auditor] (or the TALER_AUDITOR_ACCESS_TOKEN environment variable), or pass -n to run without authentication\n");
   1442     return GNUNET_SYSERR;
   1443   }
   1444   if (0 != strncasecmp (tok,
   1445                         RFC_8959_PREFIX,
   1446                         strlen (RFC_8959_PREFIX)))
   1447   {
   1448     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1449                 "The configured access token lacks the `%s' prefix\n",
   1450                 RFC_8959_PREFIX);
   1451     GNUNET_free (cfg_tok);
   1452     return GNUNET_SYSERR;
   1453   }
   1454   tok += strlen (RFC_8959_PREFIX);
   1455   if ('\0' == *tok)
   1456   {
   1457     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1458                 "The configured access token is empty\n");
   1459     GNUNET_free (cfg_tok);
   1460     return GNUNET_SYSERR;
   1461   }
   1462   GNUNET_assert (GNUNET_YES ==
   1463                  GNUNET_CRYPTO_hkdf_gnunet (
   1464                    &TAH_auth,
   1465                    sizeof (TAH_auth),
   1466                    KDF_SALT,
   1467                    strlen (KDF_SALT),
   1468                    tok,
   1469                    strlen (tok)));
   1470   GNUNET_free (cfg_tok);
   1471   return GNUNET_OK;
   1472 }
   1473 
   1474 
   1475 /**
   1476  * Function run on shutdown.
   1477  *
   1478  * @param cls NULL
   1479  */
   1480 static void
   1481 do_shutdown (void *cls)
   1482 {
   1483   (void) cls;
   1484   TALER_MHD_daemons_halt ();
   1485   TEAH_put_deposit_confirmation_done ();
   1486   TALER_MHD_daemons_destroy ();
   1487   if (NULL != TAH_apg)
   1488   {
   1489     TALER_AUDITORDB_disconnect (TAH_apg);
   1490     TAH_apg = NULL;
   1491   }
   1492   if (NULL != TAH_epg)
   1493   {
   1494     TALER_EXCHANGEDB_disconnect (TAH_epg);
   1495     TAH_epg = NULL;
   1496   }
   1497 }
   1498 
   1499 
   1500 /**
   1501  * Callback invoked on every listen socket to start the
   1502  * respective MHD HTTP daemon.
   1503  *
   1504  * @param cls unused
   1505  * @param lsock the listen socket
   1506  */
   1507 static void
   1508 start_daemon (void *cls,
   1509               int lsock)
   1510 {
   1511   struct MHD_Daemon *mhd;
   1512 
   1513   (void) cls;
   1514   GNUNET_assert (-1 != lsock);
   1515   mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
   1516                           | MHD_USE_PIPE_FOR_SHUTDOWN
   1517                           | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
   1518                           | MHD_USE_TCP_FASTOPEN,
   1519                           0,
   1520                           NULL, NULL,
   1521                           &handle_mhd_request, NULL,
   1522                           MHD_OPTION_LISTEN_SOCKET,
   1523                           lsock,
   1524                           MHD_OPTION_EXTERNAL_LOGGER,
   1525                           &TALER_MHD_handle_logs,
   1526                           NULL,
   1527                           MHD_OPTION_NOTIFY_COMPLETED,
   1528                           &handle_mhd_completion_callback,
   1529                           NULL,
   1530                           MHD_OPTION_CONNECTION_TIMEOUT,
   1531                           connection_timeout,
   1532                           MHD_OPTION_CONNECTION_LIMIT,
   1533                           (unsigned int) CONNECTION_LIMIT,
   1534                           MHD_OPTION_END);
   1535   if (NULL == mhd)
   1536   {
   1537     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1538                 "Failed to launch HTTP daemon.\n");
   1539     GNUNET_SCHEDULER_shutdown ();
   1540     return;
   1541   }
   1542   have_daemons = true;
   1543   TALER_MHD_daemon_start (mhd);
   1544 }
   1545 
   1546 
   1547 /**
   1548  * Main function that will be run by the scheduler.
   1549  *
   1550  * @param cls closure
   1551  * @param args remaining command-line arguments
   1552  * @param cfgfile name of the configuration file used (for saving, can be
   1553  *        NULL!)
   1554  * @param config configuration
   1555  */
   1556 static void
   1557 run (void *cls,
   1558      char *const *args,
   1559      const char *cfgfile,
   1560      const struct GNUNET_CONFIGURATION_Handle *config)
   1561 {
   1562   enum TALER_MHD_GlobalOptions go;
   1563   enum GNUNET_GenericReturnValue ret;
   1564 
   1565   (void) cls;
   1566   (void) args;
   1567   (void) cfgfile;
   1568   go = TALER_MHD_GO_NONE;
   1569   if (auditor_connection_close)
   1570     go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
   1571   TALER_MHD_setup (go);
   1572   cfg = config;
   1573   if ( (0 == disable_auth) &&
   1574        (GNUNET_OK != load_access_token ()) )
   1575   {
   1576     /* Failing open here would publish the entire audit findings
   1577        database, including the DELETE and PATCH handlers, to anyone
   1578        able to reach the port.  -n disables authentication on
   1579        purpose. */
   1580     global_ret = EXIT_NOTCONFIGURED;
   1581     GNUNET_SCHEDULER_shutdown ();
   1582     return;
   1583   }
   1584 
   1585   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1586                                  NULL);
   1587   if (EXIT_SUCCESS !=
   1588       (global_ret = auditor_serve_process_config ()))
   1589   {
   1590     GNUNET_SCHEDULER_shutdown ();
   1591     return;
   1592   }
   1593   if (GNUNET_OK !=
   1594       TAH_spa_init ())
   1595   {
   1596     global_ret = EXIT_NOTCONFIGURED;
   1597     GNUNET_SCHEDULER_shutdown ();
   1598     return;
   1599   }
   1600   TEAH_put_deposit_confirmation_init ();
   1601   ret = TALER_MHD_listen_bind (cfg,
   1602                                "auditor",
   1603                                &start_daemon,
   1604                                NULL);
   1605   switch (ret)
   1606   {
   1607   case GNUNET_SYSERR:
   1608     global_ret = EXIT_NOTCONFIGURED;
   1609     GNUNET_SCHEDULER_shutdown ();
   1610     return;
   1611   case GNUNET_NO:
   1612     if (! have_daemons)
   1613     {
   1614       global_ret = EXIT_FAILURE;
   1615       GNUNET_SCHEDULER_shutdown ();
   1616       return;
   1617     }
   1618     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1619                 "Could not open all configured listen sockets\n");
   1620     break;
   1621   case GNUNET_OK:
   1622     break;
   1623   }
   1624   global_ret = EXIT_SUCCESS;
   1625 }
   1626 
   1627 
   1628 /**
   1629  * The main function of the taler-auditor-httpd server ("the auditor").
   1630  *
   1631  * @param argc number of arguments from the command line
   1632  * @param argv command line arguments
   1633  * @return 0 ok, 1 on error
   1634  */
   1635 int
   1636 main (int argc,
   1637       char *const *argv)
   1638 {
   1639   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1640     GNUNET_GETOPT_option_flag ('C',
   1641                                "connection-close",
   1642                                "force HTTP connections to be closed after each request",
   1643                                &auditor_connection_close),
   1644     GNUNET_GETOPT_option_flag ('n',
   1645                                "no-authentication",
   1646                                "disable authentication checks",
   1647                                &disable_auth),
   1648     GNUNET_GETOPT_option_uint ('t',
   1649                                "timeout",
   1650                                "SECONDS",
   1651                                "after how long do connections timeout by default (in seconds)",
   1652                                &connection_timeout),
   1653     GNUNET_GETOPT_option_help (
   1654       TALER_AUDITOR_project_data (),
   1655       "HTTP server providing a RESTful API to access a Taler auditor"),
   1656     GNUNET_GETOPT_option_version (VERSION),
   1657     GNUNET_GETOPT_OPTION_END
   1658   };
   1659   int ret;
   1660 
   1661   ret = GNUNET_PROGRAM_run (
   1662     TALER_AUDITOR_project_data (),
   1663     argc, argv,
   1664     "taler-auditor-httpd",
   1665     "Taler auditor HTTP service",
   1666     options,
   1667     &run, NULL);
   1668   if (GNUNET_SYSERR == ret)
   1669     return EXIT_NOTCONFIGURED;
   1670   if (GNUNET_NO == ret)
   1671     return EXIT_SUCCESS;
   1672   return global_ret;
   1673 }
   1674 
   1675 
   1676 /* end of taler-auditor-httpd.c */