donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

donau-httpd.c (33590B)


      1 /*
      2    This file is part of TALER
      3    Copyright (C) 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  * @file donau-httpd.c
     18  * @brief Serve the HTTP interface of the donau
     19  * @author Johannes Casaburi
     20  */
     21 #include "donau_config.h"
     22 #include <gnunet/gnunet_util_lib.h>
     23 #include <jansson.h>
     24 #include <microhttpd.h>
     25 #include <sched.h>
     26 #include <sys/resource.h>
     27 #include <limits.h>
     28 #include <taler/taler_mhd_lib.h>
     29 #include "donaudb_lib.h"
     30 #include "donau_util.h"
     31 #include "donau_json_lib.h"
     32 #include "donau-httpd_get-config.h"
     33 #include "donau-httpd_get-keys.h"
     34 #include "donau-httpd_get-charities.h"
     35 #include "donau-httpd_get-charity-CHARITY_ID.h"
     36 #include "donau-httpd_post-charities.h"
     37 #include "donau-httpd_patch-charities-CHARITY_ID.h"
     38 #include "donau-httpd_delete-charities-CHARITY_ID.h"
     39 #include "donau-httpd_get-donation-statement-YEAR-HASH_DONOR_ID.h"
     40 #include "donau-httpd_post-batch-issue-CHARITY_ID.h"
     41 #include "donau-httpd_post-batch-submit.h"
     42 #include "donau-httpd_get-history.h"
     43 #include "donau-database/preflight.h"
     44 #include "donau-httpd_post-csr-issue.h"
     45 #include "donau-httpd_terms.h"
     46 #include <gnunet/gnunet_mhd_compat.h>
     47 
     48 /* LSB-style exit status codes */
     49 #ifndef EXIT_INVALIDARGUMENT
     50 /**
     51  * Command-line arguments are invalid.
     52  * Restarting useless.
     53  */
     54 #define EXIT_INVALIDARGUMENT 2
     55 #endif
     56 
     57 
     58 #ifndef EXIT_NOTCONFIGURED
     59 /**
     60  * Key configuration settings are missing or invalid.
     61  * Restarting useless.
     62  */
     63 #define EXIT_NOTCONFIGURED 6
     64 #endif
     65 
     66 
     67 /**
     68  * Backlog for listen operation on unix domain sockets.
     69  */
     70 #define UNIX_BACKLOG 50
     71 
     72 /**
     73  * How often will we try to connect to the database before giving up?
     74  */
     75 #define MAX_DB_RETRIES 5
     76 
     77 /**
     78  * Above what request latency do we start to log?
     79  */
     80  #define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
     81            GNUNET_TIME_UNIT_MILLISECONDS, 500)
     82 
     83 /**
     84  * Are clients allowed to request /keys for times other than the
     85  * current time? Allowing this could be abused in a DoS-attack
     86  * as building new /keys responses is expensive. Should only be
     87  * enabled for testcases, development and test systems.
     88  */
     89 int DH_allow_keys_timetravel;
     90 
     91 /**
     92  * Should we allow two HTTPDs to bind to the same port?
     93  */
     94 static int allow_address_reuse;
     95 
     96 /**
     97  * The donau's configuration (global)
     98  */
     99 const struct GNUNET_CONFIGURATION_Handle *DH_cfg;
    100 
    101 /**
    102  * Set to true if we started *any* HTTP daemons.
    103  */
    104 static bool have_daemons;
    105 
    106 /**
    107  * Our DB context.  (global)
    108  */
    109 struct DONAUDB_PostgresContext *DH_context;
    110 
    111 /**
    112  * Our currency.
    113  */
    114 char *DH_currency;
    115 
    116 /**
    117  * Our base URL.
    118  */
    119 char *DH_base_url;
    120 
    121 /**
    122  * Our administrative bearer token.
    123  */
    124 static char *admin_bearer;
    125 
    126 /**
    127  * Default timeout in seconds for HTTP requests.
    128  */
    129 static unsigned int connection_timeout = 30;
    130 
    131 /**
    132  * -C command-line flag given?
    133  */
    134 static int connection_close;
    135 
    136 /**
    137  * True if we should commit suicide once all active
    138  * connections are finished.
    139  */
    140 bool DH_suicide;
    141 
    142 /**
    143  * Legal domain for this donau, to be shown to the user.
    144  */
    145 char *DH_legal_domain;
    146 
    147 /**
    148  * Value to return from main()
    149  */
    150 int DH_global_ret;
    151 
    152 /**
    153  * Counter for the number of open connections.
    154  */
    155 static unsigned long long active_connections;
    156 
    157 /**
    158  * Limit for the number of requests this HTTP may process before restarting.
    159  * (This was added as one way of dealing with unavoidable memory fragmentation
    160  * happening slowly over time.)
    161  */
    162 static unsigned long long req_max;
    163 
    164 /**
    165  * Context for all CURL operations (useful to the event loop)
    166  */
    167 struct GNUNET_CURL_Context *DH_curl_ctx;
    168 
    169 /**
    170  * Context for integrating #DH_curl_ctx with the
    171  * GNUnet event loop.
    172  */
    173 static struct GNUNET_CURL_RescheduleContext *donau_curl_rc;
    174 
    175 /**
    176  * Signature of functions that handle operations on coins.
    177  *
    178  * @param connection the MHD connection to handle
    179  * @param coin_pub the public key of the coin
    180  * @param root uploaded JSON data
    181  * @return MHD result code
    182  */
    183 typedef enum MHD_Result
    184 (*CoinOpHandler)(struct MHD_Connection *connection,
    185                  const struct TALER_CoinSpendPublicKeyP *coin_pub,
    186                  const json_t *root);
    187 
    188 /**
    189  * Function called whenever MHD is done with a request.  If the
    190  * request was a POST, we may have stored a `struct Buffer *` in the
    191  * @a con_cls that might still need to be cleaned up.  Call the
    192  * respective function to free the memory.
    193  *
    194  * @param cls client-defined closure
    195  * @param connection connection handle
    196  * @param con_cls value as set by the last call to
    197  *        the #MHD_AccessHandlerCallback
    198  * @param toe reason for request termination
    199  * @see #MHD_OPTION_NOTIFY_COMPLETED
    200  * @ingroup request
    201  */
    202 static void
    203 handle_mhd_completion_callback (void *cls,
    204                                 struct MHD_Connection *connection,
    205                                 void **con_cls,
    206                                 enum MHD_RequestTerminationCode toe)
    207 {
    208   struct DH_RequestContext *rc = *con_cls;
    209   struct GNUNET_AsyncScopeSave old_scope;
    210 
    211   (void) cls;
    212   if (NULL == rc)
    213     return;
    214   GNUNET_async_scope_enter (&rc->async_scope_id,
    215                             &old_scope);
    216   if (NULL != rc->rh_cleaner)
    217     rc->rh_cleaner (rc);
    218   {
    219 #if MHD_VERSION >= 0x00097304
    220     const union MHD_ConnectionInfo *ci;
    221     unsigned int http_status = 0;
    222 
    223     ci = MHD_get_connection_info (connection,
    224                                   MHD_CONNECTION_INFO_HTTP_STATUS);
    225     if (NULL != ci)
    226       http_status = ci->http_status;
    227     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    228                 "Request for `%s' completed with HTTP status %u (%d)\n",
    229                 rc->url,
    230                 http_status,
    231                 toe);
    232 #else
    233     (void) connection;
    234     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    235                 "Request for `%s' completed (%d)\n",
    236                 rc->url,
    237                 toe);
    238 #endif
    239   }
    240 
    241   TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context);
    242   /* Sanity-check that we didn't leave any transactions hanging */
    243   GNUNET_break (GNUNET_OK ==
    244                 DONAUDB_preflight (DH_context));
    245   {
    246     struct GNUNET_TIME_Relative latency;
    247     latency = GNUNET_TIME_absolute_get_duration (rc->start_time);
    248     if (latency.rel_value_us >
    249         WARN_LATENCY.rel_value_us)
    250       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    251                   "Request for `%s' took %s\n",
    252                   rc->url,
    253                   GNUNET_STRINGS_relative_time_to_string (latency,
    254                                                           GNUNET_YES));
    255   }
    256   GNUNET_free (rc);
    257   *con_cls = NULL;
    258   GNUNET_async_scope_restore (&old_scope);
    259 }
    260 
    261 
    262 /**
    263  * We found a request handler responsible for handling a request. Parse the
    264  * @a upload_data (if applicable) and the @a url and call the
    265  * handler.
    266  *
    267  * @param rc request context
    268  * @param url rest of the URL to parse
    269  * @param upload_data upload data to parse (if available)
    270  * @param[in,out] upload_data_size number of bytes in @a upload_data
    271  * @return MHD result code
    272  */
    273 static enum MHD_Result
    274 proceed_with_handler (struct DH_RequestContext *rc,
    275                       const char *url,
    276                       const char *upload_data,
    277                       size_t *upload_data_size)
    278 {
    279   const struct DH_RequestHandler *rh = rc->rh;
    280   const char *args[rh->nargs + 2];
    281   size_t ulen = strlen (url) + 1;
    282   json_t *root = NULL;
    283   const bool is_post = (0 == strcasecmp (rh->method,
    284                                          MHD_HTTP_METHOD_POST));
    285   const bool is_patch = (0 == strcasecmp (rh->method,
    286                                           MHD_HTTP_METHOD_PATCH));
    287   enum MHD_Result ret;
    288 
    289   if (rh->needs_authorization)
    290   {
    291     const char *ah;
    292 
    293     ah = MHD_lookup_connection_value (rc->connection,
    294                                       MHD_HEADER_KIND,
    295                                       MHD_HTTP_HEADER_AUTHORIZATION);
    296     /* If 'admin_bearer' is not set, we do not require authorization;
    297        should basically only apply for test systems */
    298     if ( (NULL != admin_bearer) &&
    299          ( (NULL == ah) ||
    300            (0 != strncasecmp (ah,
    301                               "Bearer ",
    302                               strlen ("Bearer "))) ||
    303            (0 != strcmp (ah + strlen ("Bearer "),
    304                          admin_bearer)) ) )
    305     {
    306       GNUNET_break_op (0);
    307       return TALER_MHD_reply_with_error (rc->connection,
    308                                          MHD_HTTP_FORBIDDEN,
    309                                          TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT,
    310                                          "authorization required");
    311     }
    312   }
    313   /* We do check for "ulen" here, because we'll later stack-allocate a buffer
    314      of that size and don't want to enable malicious clients to cause us
    315      huge stack allocations. */
    316   if (ulen > 512)
    317   {
    318     /* 512 is simply "big enough", as it is bigger than "6 * 54",
    319        which is the longest URL format we ever get (for
    320        /deposits/).  The value should be adjusted if we ever define protocol
    321        endpoints with plausibly longer inputs.  */
    322     GNUNET_break_op (0);
    323     return TALER_MHD_reply_with_error (rc->connection,
    324                                        MHD_HTTP_URI_TOO_LONG,
    325                                        TALER_EC_GENERIC_URI_TOO_LONG,
    326                                        url);
    327   }
    328 
    329   /* All POST and PATCH endpoints come with a body in JSON format. So we parse
    330      the JSON here. */
    331   if (is_post || is_patch)
    332   {
    333     enum GNUNET_GenericReturnValue res;
    334 
    335     res = TALER_MHD_parse_post_json (rc->connection,
    336                                      &rc->opaque_post_parsing_context,
    337                                      upload_data,
    338                                      upload_data_size,
    339                                      &root);
    340     if (GNUNET_SYSERR == res)
    341     {
    342       GNUNET_assert (NULL == root);
    343       return MHD_NO; /* bad upload, could not even generate error */
    344     }
    345     if ( (GNUNET_NO == res) ||
    346          (NULL == root) )
    347     {
    348       GNUNET_assert (NULL == root);
    349       return MHD_YES; /* so far incomplete upload or parser error */
    350     }
    351   }
    352 
    353   {
    354     char d[ulen];
    355     unsigned int i;
    356     char *sp;
    357 
    358     /* Parse command-line arguments */
    359     /* make a copy of 'url' because 'strtok_r()' will modify */
    360     GNUNET_memcpy (d,
    361                    url,
    362                    ulen);
    363     i = 0;
    364     args[i++] = strtok_r (d, "/", &sp);
    365     while ( (NULL != args[i - 1]) &&
    366             (i <= rh->nargs + 1) )
    367       args[i++] = strtok_r (NULL, "/", &sp);
    368     /* make sure above loop ran nicely until completion, and also
    369        that there is no excess data in 'd' afterwards */
    370     if ( ( (rh->nargs_is_upper_bound) &&
    371            (i - 1 > rh->nargs) ) ||
    372          ( (! rh->nargs_is_upper_bound) &&
    373            (i - 1 != rh->nargs) ) )
    374     {
    375       char emsg[128 + 512];
    376 
    377       GNUNET_snprintf (emsg,
    378                        sizeof (emsg),
    379                        "Got %u+/%u segments for `%s' request (`%s')",
    380                        i - 1,
    381                        rh->nargs,
    382                        rh->url,
    383                        url);
    384       GNUNET_break_op (0);
    385       json_decref (root);
    386       return TALER_MHD_reply_with_error (rc->connection,
    387                                          MHD_HTTP_NOT_FOUND,
    388                                          TALER_EC_DONAU_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
    389                                          emsg);
    390     }
    391     GNUNET_assert (NULL == args[i - 1]);
    392 
    393     /* Above logic ensures that 'root' is exactly non-NULL for POST operations,
    394        so we test for 'root' to decide which handler to invoke. */
    395     if (is_post)
    396       ret = rh->handler.post (rc,
    397                               root,
    398                               args);
    399     else if (is_patch)
    400       ret = rh->handler.patch (rc,
    401                                root,
    402                                args);
    403     else if (0 == strcasecmp (rh->method,
    404                               MHD_HTTP_METHOD_DELETE))
    405       ret = rh->handler.delete (rc,
    406                                 args);
    407     else /* Only GET left */
    408       ret = rh->handler.get (rc,
    409                              args);
    410   }
    411   json_decref (root);
    412   return ret;
    413 }
    414 
    415 
    416 /**
    417  * Handle a "/charities" GET request.
    418  *
    419  * @param rc request context
    420  * @param args empty array
    421  * @return MHD result code
    422  */
    423 static enum MHD_Result
    424 handle_get_charities (struct DH_RequestContext *rc,
    425                       const char *const args[])
    426 {
    427   GNUNET_break (NULL == args[0]);
    428   return DH_handler_get_charities (rc);
    429 }
    430 
    431 
    432 /**
    433  * Handle a "/charities/$CHARITY_ID" GET request.
    434  *
    435  * @param rc request context
    436  * @param args charity id (in args[0])
    437  * @return MHD result code
    438  */
    439 static enum MHD_Result
    440 handle_get_charity_id (struct DH_RequestContext *rc,
    441                        const char *const args[1])
    442 {
    443   struct DONAU_CharitySignatureP charity_sig;
    444   bool sig_required = true;
    445 
    446   TALER_MHD_parse_request_header_auto (
    447     rc->connection,
    448     DONAU_HTTP_HEADER_CHARITY_SIGNATURE,
    449     &charity_sig,
    450     sig_required);
    451   return DH_handler_get_charity (rc,
    452                                  &charity_sig,
    453                                  args[0]);
    454 }
    455 
    456 
    457 /**
    458  * Handle incoming HTTP request.
    459  *
    460  * @param cls closure for MHD daemon (unused)
    461  * @param connection the connection
    462  * @param url the requested url
    463  * @param method the method (POST, GET, ...)
    464  * @param version HTTP version (ignored)
    465  * @param upload_data request data
    466  * @param upload_data_size size of @a upload_data in bytes
    467  * @param con_cls closure for request (a `struct DH_RequestContext *`)
    468  * @return MHD result code
    469  */
    470 static enum MHD_Result
    471 handle_mhd_request (void *cls,
    472                     struct MHD_Connection *connection,
    473                     const char *url,
    474                     const char *method,
    475                     const char *version,
    476                     const char *upload_data,
    477                     size_t *upload_data_size,
    478                     void **con_cls)
    479 {
    480   static struct DH_RequestHandler handlers[] = {
    481     /* Terms of service */
    482     {
    483       .url = "terms",
    484       .method = MHD_HTTP_METHOD_GET,
    485       .handler.get = &DH_handler_terms
    486     },
    487     /* Privacy policy */
    488     {
    489       .url = "privacy",
    490       .method = MHD_HTTP_METHOD_GET,
    491       .handler.get = &DH_handler_privacy
    492     },
    493     /* Configuration */
    494     {
    495       .url = "config",
    496       .method = MHD_HTTP_METHOD_GET,
    497       .handler.get = &DH_handler_get_config
    498     },
    499     /* GET keys endpoints (we only really have "/keys") */
    500     {
    501       .url = "keys",
    502       .method = MHD_HTTP_METHOD_GET,
    503       .handler.get = &DH_handler_get_keys
    504     },
    505     /* GET charities */
    506     {
    507       .url = "charities",
    508       .method = MHD_HTTP_METHOD_GET,
    509       .handler.get = &handle_get_charities,
    510       .needs_authorization = true
    511     },
    512     /* GET charity/$ID */
    513     {
    514       .url = "charity",
    515       .method = MHD_HTTP_METHOD_GET,
    516       .handler.get = &handle_get_charity_id,
    517       .nargs = 1,
    518     },
    519     /* POST charities */
    520     {
    521       .url = "charities",
    522       .method = MHD_HTTP_METHOD_POST,
    523       .handler.post = &DH_handler_post_charities,
    524       .needs_authorization = true
    525     },
    526     /* GET history */
    527     {
    528       .url = "history",
    529       .method = MHD_HTTP_METHOD_GET,
    530       .handler.get = &DH_handler_get_history,
    531       .needs_authorization = true
    532     },
    533     /* PATCH charities */
    534     {
    535       .url = "charities",
    536       .method = MHD_HTTP_METHOD_PATCH,
    537       .handler.patch = &DH_handler_patch_charities,
    538       .nargs = 1,
    539       .needs_authorization = true
    540     },
    541     /* DELETE charities */
    542     {
    543       .url = "charities",
    544       .method = MHD_HTTP_METHOD_DELETE,
    545       .handler.delete = &DH_handler_delete_charities,
    546       .nargs = 1,
    547       .needs_authorization = true
    548     },
    549     /* POST get csr values*/
    550     {
    551       .url = "csr-issue",
    552       .method = MHD_HTTP_METHOD_POST,
    553       .handler.post = &DH_handler_post_csr_issue,
    554       .nargs = 0
    555     },
    556     /* POST batch issue receipts */
    557     {
    558       .url = "batch-issue",
    559       .method = MHD_HTTP_METHOD_POST,
    560       .handler.post = &DH_handler_post_batch_issue,
    561       .nargs = 1
    562     },
    563     /* POST submitted receipts */
    564     {
    565       .url = "batch-submit",
    566       .method = MHD_HTTP_METHOD_POST,
    567       .handler.post = &DH_handler_post_batch_submit
    568     },
    569     /* GET donation statement */
    570     {
    571       .url = "donation-statement",
    572       .method = MHD_HTTP_METHOD_GET,
    573       .handler.get = &DH_handler_get_donation_statement,
    574       .nargs = 2
    575     },
    576     /* mark end of list */
    577     {
    578       .url = NULL
    579     }
    580   };
    581   struct DH_RequestContext *rc = *con_cls;
    582   struct GNUNET_AsyncScopeSave old_scope;
    583   const char *correlation_id = NULL;
    584 
    585   (void) cls;
    586   (void) version;
    587   if (NULL == rc)
    588   {
    589     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    590                 "Handling new request\n");
    591 
    592     /* We're in a new async scope! */
    593     rc = *con_cls = GNUNET_new (struct DH_RequestContext);
    594     rc->start_time = GNUNET_TIME_absolute_get ();
    595     GNUNET_async_scope_fresh (&rc->async_scope_id);
    596     rc->url = url;
    597     rc->connection = connection;
    598     /* We only read the correlation ID on the first callback for every client */
    599     correlation_id = MHD_lookup_connection_value (connection,
    600                                                   MHD_HEADER_KIND,
    601                                                   "Taler-Correlation-Id");
    602     if ( (NULL != correlation_id) &&
    603          (GNUNET_YES !=
    604           GNUNET_CURL_is_valid_scope_id (correlation_id)) )
    605     {
    606       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    607                   "illegal incoming correlation ID\n");
    608       correlation_id = NULL;
    609     }
    610 
    611     /* Check if upload is in bounds */
    612     if ( (0 == strcasecmp (method,
    613                            MHD_HTTP_METHOD_POST)) ||
    614          (0 == strcasecmp (method,
    615                            MHD_HTTP_METHOD_PATCH)) )
    616     {
    617       TALER_MHD_check_content_length (connection,
    618                                       TALER_MHD_REQUEST_BUFFER_MAX);
    619     }
    620   }
    621 
    622   GNUNET_async_scope_enter (&rc->async_scope_id,
    623                             &old_scope);
    624   if (NULL != correlation_id)
    625     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    626                 "Handling request (%s) for URL '%s', correlation_id=%s\n",
    627                 method,
    628                 url,
    629                 correlation_id);
    630   else
    631     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    632                 "Handling request (%s) for URL '%s'\n",
    633                 method,
    634                 url);
    635   /* on repeated requests, check our cache first */
    636   if (NULL != rc->rh)
    637   {
    638     enum MHD_Result ret;
    639     const char *start;
    640 
    641     if ('\0' == url[0])
    642       /* strange, should start with '/', treat as just "/" */
    643       url = "/";
    644     start = strchr (url + 1, '/');
    645     if (NULL == start)
    646       start = "";
    647     ret = proceed_with_handler (rc,
    648                                 start,
    649                                 upload_data,
    650                                 upload_data_size);
    651     GNUNET_async_scope_restore (&old_scope);
    652     return ret;
    653   }
    654 
    655   if ( (0 == strcasecmp (method,
    656                          MHD_HTTP_METHOD_OPTIONS)) &&
    657        (0 == strcmp ("*",
    658                      url)) )
    659   {
    660     GNUNET_async_scope_restore (&old_scope);
    661     return TALER_MHD_reply_cors_preflight (connection);
    662   }
    663 
    664   if (0 == strcasecmp (method,
    665                        MHD_HTTP_METHOD_HEAD))
    666     method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
    667 
    668   /* parse first part of URL */
    669   {
    670     bool found = false;
    671     size_t tok_size;
    672     const char *tok;
    673     const char *rest;
    674 
    675     if ('\0' == url[0])
    676       /* strange, should start with '/', treat as just "/" */
    677       url = "/";
    678     tok = url + 1;
    679     rest = strchr (tok, '/');
    680     if (NULL == rest)
    681     {
    682       tok_size = strlen (tok);
    683     }
    684     else
    685     {
    686       tok_size = rest - tok;
    687       rest++; /* skip over '/' */
    688     }
    689     for (unsigned int i = 0; NULL != handlers[i].url; i++)
    690     {
    691       struct DH_RequestHandler *rh = &handlers[i];
    692 
    693       if ( (0 != strncmp (tok,
    694                           rh->url,
    695                           tok_size)) ||
    696            (tok_size != strlen (rh->url) ) )
    697         continue;
    698       found = true;
    699       /* The URL is a match!  What we now do depends on the method. */
    700       if (0 == strcasecmp (method,
    701                            MHD_HTTP_METHOD_OPTIONS))
    702       {
    703         GNUNET_async_scope_restore (&old_scope);
    704         return TALER_MHD_reply_cors_preflight (connection);
    705       }
    706       GNUNET_assert (NULL != rh->method);
    707       if (0 == strcasecmp (method,
    708                            rh->method))
    709       {
    710         enum MHD_Result ret;
    711 
    712         /* cache to avoid the loop next time */
    713         rc->rh = rh;
    714         /* run handler */
    715         ret = proceed_with_handler (rc,
    716                                     url + tok_size + 1,
    717                                     upload_data,
    718                                     upload_data_size);
    719         GNUNET_async_scope_restore (&old_scope);
    720         return ret;
    721       }
    722     }
    723 
    724     if (found)
    725     {
    726       /* we found a matching address, but the method is wrong */
    727       struct MHD_Response *reply;
    728       enum MHD_Result ret;
    729       char *allowed = NULL;
    730 
    731       GNUNET_break_op (0);
    732       for (unsigned int i = 0; NULL != handlers[i].url; i++)
    733       {
    734         struct DH_RequestHandler *rh = &handlers[i];
    735 
    736         if ( (0 != strncmp (tok,
    737                             rh->url,
    738                             tok_size)) ||
    739              (tok_size != strlen (rh->url) ) )
    740           continue;
    741         if (NULL == allowed)
    742         {
    743           allowed = GNUNET_strdup (rh->method);
    744         }
    745         else
    746         {
    747           char *tmp;
    748 
    749           GNUNET_asprintf (&tmp,
    750                            "%s, %s",
    751                            allowed,
    752                            rh->method);
    753           GNUNET_free (allowed);
    754           allowed = tmp;
    755         }
    756         if (0 == strcasecmp (rh->method,
    757                              MHD_HTTP_METHOD_GET))
    758         {
    759           char *tmp;
    760 
    761           GNUNET_asprintf (&tmp,
    762                            "%s, %s",
    763                            allowed,
    764                            MHD_HTTP_METHOD_HEAD);
    765           GNUNET_free (allowed);
    766           allowed = tmp;
    767         }
    768       }
    769       reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
    770                                     method);
    771       GNUNET_break (MHD_YES ==
    772                     MHD_add_response_header (reply,
    773                                              MHD_HTTP_HEADER_ALLOW,
    774                                              allowed));
    775       GNUNET_free (allowed);
    776       ret = MHD_queue_response (connection,
    777                                 MHD_HTTP_METHOD_NOT_ALLOWED,
    778                                 reply);
    779       MHD_destroy_response (reply);
    780       GNUNET_async_scope_restore (&old_scope);
    781       return ret;
    782     }
    783   }
    784 
    785   /* No handler matches, generate not found */
    786   {
    787     enum MHD_Result ret;
    788 
    789     ret = TALER_MHD_reply_with_error (connection,
    790                                       MHD_HTTP_NOT_FOUND,
    791                                       TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    792                                       url);
    793     GNUNET_async_scope_restore (&old_scope);
    794     return ret;
    795   }
    796 }
    797 
    798 
    799 /**
    800  * Load configuration parameters for the donau
    801  * server into the corresponding global variables.
    802  *
    803  * @return #GNUNET_OK on success
    804  */
    805 static enum GNUNET_GenericReturnValue
    806 donau_serve_process_config (void)
    807 {
    808   if (GNUNET_OK !=
    809       GNUNET_CONFIGURATION_get_value_number (DH_cfg,
    810                                              "donau",
    811                                              "MAX_REQUESTS",
    812                                              &req_max))
    813   {
    814     req_max = ULLONG_MAX;
    815   }
    816 
    817   if (GNUNET_OK !=
    818       TALER_config_get_currency (DH_cfg,
    819                                  "donau",
    820                                  &DH_currency))
    821   {
    822     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    823                                "donau",
    824                                "CURRENCY");
    825     return GNUNET_SYSERR;
    826   }
    827 
    828   if (GNUNET_OK !=
    829       GNUNET_CONFIGURATION_get_value_string (DH_cfg,
    830                                              "donau",
    831                                              "LEGAL_DOMAIN",
    832                                              &DH_legal_domain))
    833   {
    834     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    835                                "donau",
    836                                "LEGAL_DOMAIN");
    837     return GNUNET_SYSERR;
    838   }
    839   if (GNUNET_OK !=
    840       GNUNET_CONFIGURATION_get_value_string (DH_cfg,
    841                                              "donau",
    842                                              "ADMIN_BEARER_TOKEN",
    843                                              &admin_bearer))
    844   {
    845     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    846                                "donau",
    847                                "ADMIN_BEARER_TOKEN");
    848   }
    849   if (GNUNET_OK !=
    850       GNUNET_CONFIGURATION_get_value_string (DH_cfg,
    851                                              "donau",
    852                                              "BASE_URL",
    853                                              &DH_base_url))
    854   {
    855     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    856                                "donau",
    857                                "BASE_URL");
    858     return GNUNET_SYSERR;
    859   }
    860   if (! TALER_url_valid_charset (DH_base_url))
    861   {
    862     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    863                                "donau",
    864                                "BASE_URL",
    865                                "invalid URL");
    866     return GNUNET_SYSERR;
    867   }
    868 
    869   for (unsigned int i = 0; i<MAX_DB_RETRIES; i++)
    870   {
    871     DH_context = DONAUDB_connect (DH_cfg);
    872     if (NULL != DH_context)
    873       break;
    874     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    875                 "Failed to connect to DB, will try again %u times\n",
    876                 MAX_DB_RETRIES - i);
    877     sleep (1);
    878   }
    879   if (NULL == DH_context)
    880   {
    881     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    882                 "Failed to initialize DB subsystem. Giving up.\n");
    883     return GNUNET_SYSERR;
    884   }
    885 
    886   return GNUNET_OK;
    887 }
    888 
    889 
    890 /**
    891  * Signature of the callback used by MHD to notify the application
    892  * about completed connections.  If we are running in test-mode with
    893  * an input_filename, this function is used to terminate the HTTPD
    894  * after the first request has been processed.
    895  *
    896  * @param cls client-defined closure, NULL
    897  * @param connection connection handle (ignored)
    898  * @param socket_context socket-specific pointer (ignored)
    899  * @param toe reason for connection notification
    900  */
    901 static void
    902 connection_done (void *cls,
    903                  struct MHD_Connection *connection,
    904                  void **socket_context,
    905                  enum MHD_ConnectionNotificationCode toe)
    906 {
    907   (void) cls;
    908   (void) connection;
    909   (void) socket_context;
    910 
    911   switch (toe)
    912   {
    913   case MHD_CONNECTION_NOTIFY_STARTED:
    914     active_connections++;
    915     break;
    916   case MHD_CONNECTION_NOTIFY_CLOSED:
    917     active_connections--;
    918     if (DH_suicide &&
    919         (0 == active_connections) )
    920       GNUNET_SCHEDULER_shutdown ();
    921     break;
    922   }
    923 }
    924 
    925 
    926 /**
    927  * Function run on shutdown.
    928  *
    929  * @param cls NULL
    930  */
    931 static void
    932 do_shutdown (void *cls)
    933 {
    934   (void) cls;
    935   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    936               "Shutdown initiated\n");
    937   TALER_MHD_daemons_halt ();
    938   TALER_MHD_daemons_destroy ();
    939   DH_keys_finished ();
    940   if (NULL != DH_context)
    941   {
    942     DONAUDB_disconnect (DH_context);
    943     DH_context = NULL;
    944   }
    945   if (NULL != DH_curl_ctx)
    946   {
    947     GNUNET_CURL_fini (DH_curl_ctx);
    948     DH_curl_ctx = NULL;
    949   }
    950   if (NULL != donau_curl_rc)
    951   {
    952     GNUNET_CURL_gnunet_rc_destroy (donau_curl_rc);
    953     donau_curl_rc = NULL;
    954   }
    955 }
    956 
    957 
    958 /**
    959  * Callback invoked on every listen socket to start the
    960  * respective MHD HTTP daemon.
    961  *
    962  * @param cls unused
    963  * @param lsock the listen socket
    964  */
    965 static void
    966 start_daemon (void *cls,
    967               int lsock)
    968 {
    969   struct MHD_Daemon *mhd;
    970 
    971   (void) cls;
    972   GNUNET_assert (-1 != lsock);
    973   mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
    974                           | MHD_USE_PIPE_FOR_SHUTDOWN
    975                           | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
    976                           | MHD_USE_TCP_FASTOPEN,
    977                           0 /* port */,
    978                           NULL, NULL,
    979                           &handle_mhd_request, NULL,
    980                           MHD_OPTION_LISTEN_SOCKET,
    981                           lsock,
    982                           MHD_OPTION_EXTERNAL_LOGGER,
    983                           &TALER_MHD_handle_logs,
    984                           NULL,
    985                           MHD_OPTION_NOTIFY_COMPLETED,
    986                           &handle_mhd_completion_callback,
    987                           NULL,
    988                           MHD_OPTION_NOTIFY_CONNECTION,
    989                           &connection_done,
    990                           NULL,
    991                           MHD_OPTION_CONNECTION_TIMEOUT,
    992                           connection_timeout,
    993                           (0 == allow_address_reuse)
    994                           ? MHD_OPTION_END
    995                           : MHD_OPTION_LISTENING_ADDRESS_REUSE,
    996                           (unsigned int) allow_address_reuse,
    997                           MHD_OPTION_END);
    998   if (NULL == mhd)
    999   {
   1000     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1001                 "Failed to launch HTTP service. Is the port in use?\n");
   1002     GNUNET_SCHEDULER_shutdown ();
   1003     return;
   1004   }
   1005   have_daemons = true;
   1006   TALER_MHD_daemon_start (mhd);
   1007 }
   1008 
   1009 
   1010 /**
   1011  * Main function that will be run by the scheduler.
   1012  *
   1013  * @param cls closure
   1014  * @param args remaining command-line arguments
   1015  * @param cfgfile name of the configuration file used (for saving, can be
   1016  *        NULL!)
   1017  * @param config configuration
   1018  */
   1019 static void
   1020 run (void *cls,
   1021      char *const *args,
   1022      const char *cfgfile,
   1023      const struct GNUNET_CONFIGURATION_Handle *config)
   1024 {
   1025   enum TALER_MHD_GlobalOptions go;
   1026 
   1027   (void) cls;
   1028   (void) args;
   1029   (void ) cfgfile;
   1030   go = TALER_MHD_GO_NONE;
   1031   if (connection_close)
   1032     go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
   1033   TALER_MHD_setup (go);
   1034   DH_cfg = config;
   1035   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1036                                  NULL);
   1037 
   1038   if (GNUNET_OK !=
   1039       donau_serve_process_config ())
   1040   {
   1041     DH_global_ret = EXIT_NOTCONFIGURED;
   1042     GNUNET_SCHEDULER_shutdown ();
   1043     return;
   1044   }
   1045 
   1046   if (GNUNET_OK !=
   1047       DH_keys_init ())
   1048   {
   1049     DH_global_ret = EXIT_FAILURE;
   1050     GNUNET_SCHEDULER_shutdown ();
   1051     return;
   1052   }
   1053 
   1054   if (GNUNET_SYSERR ==
   1055       DONAUDB_preflight (DH_context))
   1056   {
   1057     DH_global_ret = EXIT_FAILURE;
   1058     GNUNET_SCHEDULER_shutdown ();
   1059     return;
   1060   }
   1061 
   1062   DH_load_terms (DH_cfg);
   1063   DH_curl_ctx
   1064     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1065                         &donau_curl_rc);
   1066   if (NULL == DH_curl_ctx)
   1067   {
   1068     GNUNET_break (0);
   1069     DH_global_ret = EXIT_FAILURE;
   1070     GNUNET_SCHEDULER_shutdown ();
   1071     return;
   1072   }
   1073   donau_curl_rc = GNUNET_CURL_gnunet_rc_create (DH_curl_ctx);
   1074   {
   1075     enum GNUNET_GenericReturnValue ret;
   1076 
   1077     ret = TALER_MHD_listen_bind (DH_cfg,
   1078                                  "donau",
   1079                                  &start_daemon,
   1080                                  NULL);
   1081     switch (ret)
   1082     {
   1083     case GNUNET_SYSERR:
   1084       DH_global_ret = EXIT_NOTCONFIGURED;
   1085       GNUNET_SCHEDULER_shutdown ();
   1086       return;
   1087     case GNUNET_NO:
   1088       if (! have_daemons)
   1089       {
   1090         DH_global_ret = EXIT_NOTCONFIGURED;
   1091         GNUNET_SCHEDULER_shutdown ();
   1092         return;
   1093       }
   1094       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1095                   "Could not open all configured listen sockets\n");
   1096       break;
   1097     case GNUNET_OK:
   1098       break;
   1099     }
   1100   }
   1101   DH_global_ret = EXIT_SUCCESS;
   1102 }
   1103 
   1104 
   1105 /**
   1106  * The main function of the donau-httpd server ("the donau").
   1107  *
   1108  * @param argc number of arguments from the command line
   1109  * @param argv command line arguments
   1110  * @return 0 ok, 1 on error
   1111  */
   1112 int
   1113 main (int argc,
   1114       char *const *argv)
   1115 {
   1116   const struct GNUNET_GETOPT_CommandLineOption options[] = {
   1117     GNUNET_GETOPT_option_flag ('C',
   1118                                "connection-close",
   1119                                "force HTTP connections to be closed after each request",
   1120                                &connection_close),
   1121     GNUNET_GETOPT_option_flag ('r',
   1122                                "allow-reuse-address",
   1123                                "allow multiple HTTPDs to listen to the same port",
   1124                                &allow_address_reuse),
   1125     GNUNET_GETOPT_option_uint ('t',
   1126                                "timeout",
   1127                                "SECONDS",
   1128                                "after how long do connections timeout by default (in seconds)",
   1129                                &connection_timeout),
   1130     GNUNET_GETOPT_option_timetravel ('T',
   1131                                      "timetravel"),
   1132     GNUNET_GETOPT_option_help (
   1133       DONAU_project_data (),
   1134       "HTTP server providing a RESTful API to access a Taler donau"),
   1135     GNUNET_GETOPT_OPTION_END
   1136   };
   1137   enum GNUNET_GenericReturnValue ret;
   1138 
   1139   ret = GNUNET_PROGRAM_run (DONAU_project_data (),
   1140                             argc, argv,
   1141                             "donau-httpd",
   1142                             "Taler donau HTTP service",
   1143                             options,
   1144                             &run, NULL);
   1145   if (GNUNET_SYSERR == ret)
   1146     return EXIT_INVALIDARGUMENT;
   1147   if (GNUNET_NO == ret)
   1148     return EXIT_SUCCESS;
   1149   return DH_global_ret;
   1150 }
   1151 
   1152 
   1153 /* end of donau-httpd.c */