/*
   This file is part of TALER
   Copyright (C) 2024 Taler Systems SA

   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
   Foundation; either version 3, or (at your option) any later version.

   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License along with
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * @file donau-httpd.c
 * @brief Serve the HTTP interface of the donau
 * @author Johannes Casaburi
 */
#include "donau_config.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <sched.h>
#include <sys/resource.h>
#include <limits.h>
#include <taler/taler_mhd_lib.h>
#include "donaudb_lib.h"
#include "donau_util.h"
#include "donau_json_lib.h"
#include "donau-httpd_config.h"
#include "donau-httpd_keys.h"
#include "donau-httpd_charity.h"
#include "donau-httpd_donation-statement.h"
#include "donau-httpd_batch-issue.h"
#include "donau-httpd_batch-submit.h"
#include "donau-httpd_history.h"
#include "donau-httpd_csr.h"
#include "donau-httpd_terms.h"
#include "donaudb_plugin.h"
#include <gnunet/gnunet_mhd_compat.h>

/* LSB-style exit status codes */
#ifndef EXIT_INVALIDARGUMENT
/**
 * Command-line arguments are invalid.
 * Restarting useless.
 */
#define EXIT_INVALIDARGUMENT 2
#endif


#ifndef EXIT_NOTCONFIGURED
/**
 * Key configuration settings are missing or invalid.
 * Restarting useless.
 */
#define EXIT_NOTCONFIGURED 6
#endif


/**
 * Backlog for listen operation on unix domain sockets.
 */
#define UNIX_BACKLOG 50

/**
 * How often will we try to connect to the database before giving up?
 */
#define MAX_DB_RETRIES 5

/**
 * Above what request latency do we start to log?
 */
 #define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
           GNUNET_TIME_UNIT_MILLISECONDS, 500)

/**
 * Are clients allowed to request /keys for times other than the
 * current time? Allowing this could be abused in a DoS-attack
 * as building new /keys responses is expensive. Should only be
 * enabled for testcases, development and test systems.
 */
int DH_allow_keys_timetravel;

/**
 * Should we allow two HTTPDs to bind to the same port?
 */
static int allow_address_reuse;

/**
 * The donau's configuration (global)
 */
const struct GNUNET_CONFIGURATION_Handle *DH_cfg;

/**
 * Set to true if we started *any* HTTP daemons.
 */
static bool have_daemons;

/**
 * Our DB plugin.  (global)
 */
struct DONAUDB_Plugin *DH_plugin;

/**
 * Our currency.
 */
char *DH_currency;

/**
 * Our base URL.
 */
char *DH_base_url;

/**
 * Our administrative bearer token.
 */
static char *admin_bearer;

/**
 * Default timeout in seconds for HTTP requests.
 */
static unsigned int connection_timeout = 30;

/**
 * -C command-line flag given?
 */
static int connection_close;

/**
 * True if we should commit suicide once all active
 * connections are finished.
 */
bool DH_suicide;

/**
 * Legal domain for this donau, to be shown to the user.
 */
char *DH_legal_domain;

/**
 * Value to return from main()
 */
int DH_global_ret;

/**
 * Counter for the number of open connections.
 */
static unsigned long long active_connections;

/**
 * Limit for the number of requests this HTTP may process before restarting.
 * (This was added as one way of dealing with unavoidable memory fragmentation
 * happening slowly over time.)
 */
static unsigned long long req_max;

/**
 * Context for all CURL operations (useful to the event loop)
 */
struct GNUNET_CURL_Context *DH_curl_ctx;

/**
 * Context for integrating #DH_curl_ctx with the
 * GNUnet event loop.
 */
static struct GNUNET_CURL_RescheduleContext *donau_curl_rc;

/**
 * Signature of functions that handle operations on coins.
 *
 * @param connection the MHD connection to handle
 * @param coin_pub the public key of the coin
 * @param root uploaded JSON data
 * @return MHD result code
 */
typedef MHD_RESULT
(*CoinOpHandler)(struct MHD_Connection *connection,
                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
                 const json_t *root);

/**
 * Function called whenever MHD is done with a request.  If the
 * request was a POST, we may have stored a `struct Buffer *` in the
 * @a con_cls that might still need to be cleaned up.  Call the
 * respective function to free the memory.
 *
 * @param cls client-defined closure
 * @param connection connection handle
 * @param con_cls value as set by the last call to
 *        the #MHD_AccessHandlerCallback
 * @param toe reason for request termination
 * @see #MHD_OPTION_NOTIFY_COMPLETED
 * @ingroup request
 */
static void
handle_mhd_completion_callback (void *cls,
                                struct MHD_Connection *connection,
                                void **con_cls,
                                enum MHD_RequestTerminationCode toe)
{
  struct DH_RequestContext *rc = *con_cls;
  struct GNUNET_AsyncScopeSave old_scope;

  (void) cls;
  if (NULL == rc)
    return;
  GNUNET_async_scope_enter (&rc->async_scope_id,
                            &old_scope);
  if (NULL != rc->rh_cleaner)
    rc->rh_cleaner (rc);
  {
#if MHD_VERSION >= 0x00097304
    const union MHD_ConnectionInfo *ci;
    unsigned int http_status = 0;

    ci = MHD_get_connection_info (connection,
                                  MHD_CONNECTION_INFO_HTTP_STATUS);
    if (NULL != ci)
      http_status = ci->http_status;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Request for `%s' completed with HTTP status %u (%d)\n",
                rc->url,
                http_status,
                toe);
#else
    (void) connection;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Request for `%s' completed (%d)\n",
                rc->url,
                toe);
#endif
  }

  TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context);
  /* Sanity-check that we didn't leave any transactions hanging */
  GNUNET_break (GNUNET_OK ==
                DH_plugin->preflight (DH_plugin->cls));
  {
    struct GNUNET_TIME_Relative latency;
    latency = GNUNET_TIME_absolute_get_duration (rc->start_time);
    if (latency.rel_value_us >
        WARN_LATENCY.rel_value_us)
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Request for `%s' took %s\n",
                  rc->url,
                  GNUNET_STRINGS_relative_time_to_string (latency,
                                                          GNUNET_YES));
  }
  GNUNET_free (rc);
  *con_cls = NULL;
  GNUNET_async_scope_restore (&old_scope);
}


/**
 * We found a request handler responsible for handling a request. Parse the
 * @a upload_data (if applicable) and the @a url and call the
 * handler.
 *
 * @param rc request context
 * @param url rest of the URL to parse
 * @param upload_data upload data to parse (if available)
 * @param[in,out] upload_data_size number of bytes in @a upload_data
 * @return MHD result code
 */
static MHD_RESULT
proceed_with_handler (struct DH_RequestContext *rc,
                      const char *url,
                      const char *upload_data,
                      size_t *upload_data_size)
{
  const struct DH_RequestHandler *rh = rc->rh;
  const char *args[rh->nargs + 2];
  size_t ulen = strlen (url) + 1;
  json_t *root = NULL;
  const bool is_post = (0 == strcasecmp (rh->method,
                                         MHD_HTTP_METHOD_POST));
  const bool is_patch = (0 == strcasecmp (rh->method,
                                          MHD_HTTP_METHOD_PATCH));
  MHD_RESULT ret;

  if ( (rh->needs_authorization) &&
       (NULL != admin_bearer) )
  {
    const char *ah;

    ah = MHD_lookup_connection_value (rc->connection,
                                      MHD_HEADER_KIND,
                                      MHD_HTTP_HEADER_AUTHORIZATION);
    if ( (NULL == ah) ||
         (0 != strncasecmp (ah,
                            "Bearer ",
                            strlen ("Bearer "))) ||
         (0 != strcmp (ah + strlen ("Bearer "),
                       admin_bearer)) )
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (rc->connection,
                                         MHD_HTTP_FORBIDDEN,
                                         TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT,
                                         "authorization required");
    }
  }
  /* We do check for "ulen" here, because we'll later stack-allocate a buffer
     of that size and don't want to enable malicious clients to cause us
     huge stack allocations. */
  if (ulen > 512)
  {
    /* 512 is simply "big enough", as it is bigger than "6 * 54",
       which is the longest URL format we ever get (for
       /deposits/).  The value should be adjusted if we ever define protocol
       endpoints with plausibly longer inputs.  */
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (rc->connection,
                                       MHD_HTTP_URI_TOO_LONG,
                                       TALER_EC_GENERIC_URI_TOO_LONG,
                                       url);
  }

  /* All POST and PATCH endpoints come with a body in JSON format. So we parse
     the JSON here. */
  if (is_post || is_patch)
  {
    enum GNUNET_GenericReturnValue res;

    res = TALER_MHD_parse_post_json (rc->connection,
                                     &rc->opaque_post_parsing_context,
                                     upload_data,
                                     upload_data_size,
                                     &root);
    if (GNUNET_SYSERR == res)
    {
      GNUNET_assert (NULL == root);
      return MHD_NO; /* bad upload, could not even generate error */
    }
    if ( (GNUNET_NO == res) ||
         (NULL == root) )
    {
      GNUNET_assert (NULL == root);
      return MHD_YES; /* so far incomplete upload or parser error */
    }
  }

  {
    char d[ulen];
    unsigned int i;
    char *sp;

    /* Parse command-line arguments */
    /* make a copy of 'url' because 'strtok_r()' will modify */
    GNUNET_memcpy (d,
                   url,
                   ulen);
    i = 0;
    args[i++] = strtok_r (d, "/", &sp);
    while ( (NULL != args[i - 1]) &&
            (i <= rh->nargs + 1) )
      args[i++] = strtok_r (NULL, "/", &sp);
    /* make sure above loop ran nicely until completion, and also
       that there is no excess data in 'd' afterwards */
    if ( ( (rh->nargs_is_upper_bound) &&
           (i - 1 > rh->nargs) ) ||
         ( (! rh->nargs_is_upper_bound) &&
           (i - 1 != rh->nargs) ) )
    {
      char emsg[128 + 512];

      GNUNET_snprintf (emsg,
                       sizeof (emsg),
                       "Got %u+/%u segments for `%s' request (`%s')",
                       i - 1,
                       rh->nargs,
                       rh->url,
                       url);
      GNUNET_break_op (0);
      json_decref (root);
      return TALER_MHD_reply_with_error (rc->connection,
                                         MHD_HTTP_NOT_FOUND,
                                         TALER_EC_DONAU_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
                                         emsg);
    }
    GNUNET_assert (NULL == args[i - 1]);

    /* Above logic ensures that 'root' is exactly non-NULL for POST operations,
       so we test for 'root' to decide which handler to invoke. */
    if (is_post)
      ret = rh->handler.post (rc,
                              root,
                              args);
    else if (is_patch)
      ret = rh->handler.patch (rc,
                               root,
                               args);
    else if (0 == strcasecmp (rh->method,
                              MHD_HTTP_METHOD_DELETE))
      ret = rh->handler.delete (rc,
                                args);
    else /* Only GET left */
      ret = rh->handler.get (rc,
                             args);
  }
  json_decref (root);
  return ret;
}


/**
 * Handle a "/charities" GET request.
 *
 * @param rc request context
 * @param args empty array
 * @return MHD result code
 */
static MHD_RESULT
handle_get_charities (struct DH_RequestContext *rc,
                      const char *const args[])
{
  GNUNET_break (NULL == args[0]);
  return DH_handler_charities_get (rc);
}


/**
 * Handle a "/charities/$CHARITY_ID" GET request.
 *
 * @param rc request context
 * @param args charity id (in args[0])
 * @return MHD result code
 */
static MHD_RESULT
handle_get_charity_id (struct DH_RequestContext *rc,
                       const char *const args[1])
{
  struct DONAU_CharitySignatureP charity_sig;
  bool sig_required = true;

  TALER_MHD_parse_request_header_auto (
    rc->connection,
    DONAU_HTTP_HEADER_CHARITY_SIGNATURE,
    &charity_sig,
    sig_required);
  return DH_handler_charity_get (rc,
                                 &charity_sig,
                                 args[0]);
}


/**
 * Handle incoming HTTP request.
 *
 * @param cls closure for MHD daemon (unused)
 * @param connection the connection
 * @param url the requested url
 * @param method the method (POST, GET, ...)
 * @param version HTTP version (ignored)
 * @param upload_data request data
 * @param upload_data_size size of @a upload_data in bytes
 * @param con_cls closure for request (a `struct DH_RequestContext *`)
 * @return MHD result code
 */
static MHD_RESULT
handle_mhd_request (void *cls,
                    struct MHD_Connection *connection,
                    const char *url,
                    const char *method,
                    const char *version,
                    const char *upload_data,
                    size_t *upload_data_size,
                    void **con_cls)
{
  static struct DH_RequestHandler handlers[] = {
    /* Terms of service */
    {
      .url = "terms",
      .method = MHD_HTTP_METHOD_GET,
      .handler.get = &DH_handler_terms
    },
    /* Privacy policy */
    {
      .url = "privacy",
      .method = MHD_HTTP_METHOD_GET,
      .handler.get = &DH_handler_privacy
    },
    /* Configuration */
    {
      .url = "config",
      .method = MHD_HTTP_METHOD_GET,
      .handler.get = &DH_handler_config
    },
    /* GET keys endpoints (we only really have "/keys") */
    {
      .url = "keys",
      .method = MHD_HTTP_METHOD_GET,
      .handler.get = &DH_handler_keys
    },
    /* GET charities */
    {
      .url = "charities",
      .method = MHD_HTTP_METHOD_GET,
      .handler.get = &handle_get_charities,
      .needs_authorization = true
    },
    /* GET charity/$ID */
    {
      .url = "charity",
      .method = MHD_HTTP_METHOD_GET,
      .handler.get = &handle_get_charity_id,
      .nargs = 1,
    },
    /* POST charities */
    {
      .url = "charities",
      .method = MHD_HTTP_METHOD_POST,
      .handler.post = &DH_handler_charity_post,
      .needs_authorization = true
    },
    /* PATCH charities */
    {
      .url = "charities",
      .method = MHD_HTTP_METHOD_PATCH,
      .handler.patch = &DH_handler_charity_patch,
      .nargs = 1,
      .needs_authorization = true
    },
    /* DELETE charities */
    {
      .url = "charities",
      .method = MHD_HTTP_METHOD_DELETE,
      .handler.delete = &DH_handler_charity_delete,
      .nargs = 1,
      .needs_authorization = true
    },
    /* POST get csr values*/
    {
      .url = "csr-issue",
      .method = MHD_HTTP_METHOD_POST,
      .handler.post = &DH_handler_csr_issue,
      .nargs = 0
    },
    /* POST batch issue receipts */
    {
      .url = "batch-issue",
      .method = MHD_HTTP_METHOD_POST,
      .handler.post = &DH_handler_issue_receipts_post,
      .nargs = 1
    },
    /* POST submitted receipts */
    {
      .url = "batch-submit",
      .method = MHD_HTTP_METHOD_POST,
      .handler.post = &DH_handler_submit_receipts_post
    },
    /* GET donation statement */
    {
      .url = "donation-statement",
      .method = MHD_HTTP_METHOD_GET,
      .handler.get = &DH_handler_donation_statement_get,
      .nargs = 2,
      .nargs_is_upper_bound = true
    },
    /* mark end of list */
    {
      .url = NULL
    }
  };
  struct DH_RequestContext *rc = *con_cls;
  struct GNUNET_AsyncScopeSave old_scope;
  const char *correlation_id = NULL;

  (void) cls;
  (void) version;
  if (NULL == rc)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Handling new request\n");

    /* We're in a new async scope! */
    rc = *con_cls = GNUNET_new (struct DH_RequestContext);
    rc->start_time = GNUNET_TIME_absolute_get ();
    GNUNET_async_scope_fresh (&rc->async_scope_id);
    rc->url = url;
    rc->connection = connection;
    /* We only read the correlation ID on the first callback for every client */
    correlation_id = MHD_lookup_connection_value (connection,
                                                  MHD_HEADER_KIND,
                                                  "Taler-Correlation-Id");
    if ( (NULL != correlation_id) &&
         (GNUNET_YES !=
          GNUNET_CURL_is_valid_scope_id (correlation_id)) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "illegal incoming correlation ID\n");
      correlation_id = NULL;
    }

    /* Check if upload is in bounds */
    if ( (0 == strcasecmp (method,
                           MHD_HTTP_METHOD_POST)) ||
         (0 == strcasecmp (method,
                           MHD_HTTP_METHOD_PATCH)) )
    {
      TALER_MHD_check_content_length (connection,
                                      TALER_MHD_REQUEST_BUFFER_MAX);
    }
  }

  GNUNET_async_scope_enter (&rc->async_scope_id,
                            &old_scope);
  if (NULL != correlation_id)
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Handling request (%s) for URL '%s', correlation_id=%s\n",
                method,
                url,
                correlation_id);
  else
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Handling request (%s) for URL '%s'\n",
                method,
                url);
  /* on repeated requests, check our cache first */
  if (NULL != rc->rh)
  {
    MHD_RESULT ret;
    const char *start;

    if ('\0' == url[0])
      /* strange, should start with '/', treat as just "/" */
      url = "/";
    start = strchr (url + 1, '/');
    if (NULL == start)
      start = "";
    ret = proceed_with_handler (rc,
                                start,
                                upload_data,
                                upload_data_size);
    GNUNET_async_scope_restore (&old_scope);
    return ret;
  }

  if ( (0 == strcasecmp (method,
                         MHD_HTTP_METHOD_OPTIONS)) &&
       (0 == strcmp ("*",
                     url)) )
    return TALER_MHD_reply_cors_preflight (connection);

  if (0 == strcasecmp (method,
                       MHD_HTTP_METHOD_HEAD))
    method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */

  /* parse first part of URL */
  {
    bool found = false;
    size_t tok_size;
    const char *tok;
    const char *rest;

    if ('\0' == url[0])
      /* strange, should start with '/', treat as just "/" */
      url = "/";
    tok = url + 1;
    rest = strchr (tok, '/');
    if (NULL == rest)
    {
      tok_size = strlen (tok);
    }
    else
    {
      tok_size = rest - tok;
      rest++; /* skip over '/' */
    }
    for (unsigned int i = 0; NULL != handlers[i].url; i++)
    {
      struct DH_RequestHandler *rh = &handlers[i];

      if ( (0 != strncmp (tok,
                          rh->url,
                          tok_size)) ||
           (tok_size != strlen (rh->url) ) )
        continue;
      found = true;
      /* The URL is a match!  What we now do depends on the method. */
      if (0 == strcasecmp (method,
                           MHD_HTTP_METHOD_OPTIONS))
      {
        GNUNET_async_scope_restore (&old_scope);
        return TALER_MHD_reply_cors_preflight (connection);
      }
      GNUNET_assert (NULL != rh->method);
      if (0 == strcasecmp (method,
                           rh->method))
      {
        MHD_RESULT ret;

        /* cache to avoid the loop next time */
        rc->rh = rh;
        /* run handler */
        ret = proceed_with_handler (rc,
                                    url + tok_size + 1,
                                    upload_data,
                                    upload_data_size);
        GNUNET_async_scope_restore (&old_scope);
        return ret;
      }
    }

    if (found)
    {
      /* we found a matching address, but the method is wrong */
      struct MHD_Response *reply;
      MHD_RESULT ret;
      char *allowed = NULL;

      GNUNET_break_op (0);
      for (unsigned int i = 0; NULL != handlers[i].url; i++)
      {
        struct DH_RequestHandler *rh = &handlers[i];

        if ( (0 != strncmp (tok,
                            rh->url,
                            tok_size)) ||
             (tok_size != strlen (rh->url) ) )
          continue;
        if (NULL == allowed)
        {
          allowed = GNUNET_strdup (rh->method);
        }
        else
        {
          char *tmp;

          GNUNET_asprintf (&tmp,
                           "%s, %s",
                           allowed,
                           rh->method);
          GNUNET_free (allowed);
          allowed = tmp;
        }
        if (0 == strcasecmp (rh->method,
                             MHD_HTTP_METHOD_GET))
        {
          char *tmp;

          GNUNET_asprintf (&tmp,
                           "%s, %s",
                           allowed,
                           MHD_HTTP_METHOD_HEAD);
          GNUNET_free (allowed);
          allowed = tmp;
        }
      }
      reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
                                    method);
      GNUNET_break (MHD_YES ==
                    MHD_add_response_header (reply,
                                             MHD_HTTP_HEADER_ALLOW,
                                             allowed));
      GNUNET_free (allowed);
      ret = MHD_queue_response (connection,
                                MHD_HTTP_METHOD_NOT_ALLOWED,
                                reply);
      MHD_destroy_response (reply);
      return ret;
    }
  }

  /* No handler matches, generate not found */
  {
    MHD_RESULT ret;

    ret = TALER_MHD_reply_with_error (connection,
                                      MHD_HTTP_NOT_FOUND,
                                      TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
                                      url);
    GNUNET_async_scope_restore (&old_scope);
    return ret;
  }
}


/**
 * Load configuration parameters for the donau
 * server into the corresponding global variables.
 *
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
donau_serve_process_config (void)
{
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_number (DH_cfg,
                                             "donau",
                                             "MAX_REQUESTS",
                                             &req_max))
  {
    req_max = ULLONG_MAX;
  }

  if (GNUNET_OK !=
      TALER_config_get_currency (DH_cfg,
                                 "donau",
                                 &DH_currency))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "donau",
                               "CURRENCY");
    return GNUNET_SYSERR;
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (DH_cfg,
                                             "donau",
                                             "LEGAL_DOMAIN",
                                             &DH_legal_domain))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "donau",
                               "LEGAL_DOMAIN");
    return GNUNET_SYSERR;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (DH_cfg,
                                             "donau",
                                             "ADMIN_BEARER_TOKEN",
                                             &admin_bearer))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
                               "donau",
                               "ADMIN_BEARER_TOKEN");
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (DH_cfg,
                                             "donau",
                                             "BASE_URL",
                                             &DH_base_url))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "donau",
                               "BASE_URL");
    return GNUNET_SYSERR;
  }
  if (! TALER_url_valid_charset (DH_base_url))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               "donau",
                               "BASE_URL",
                               "invalid URL");
    return GNUNET_SYSERR;
  }

  for (unsigned int i = 0; i<MAX_DB_RETRIES; i++)
  {
    DH_plugin = DONAUDB_plugin_load (DH_cfg);
    if (NULL != DH_plugin)
      break;
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to connect to DB, will try again %u times\n",
                MAX_DB_RETRIES - i);
    sleep (1);
  }
  if (NULL == DH_plugin)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to initialize DB subsystem. Giving up.\n");
    return GNUNET_SYSERR;
  }

  return GNUNET_OK;
}


/**
 * Signature of the callback used by MHD to notify the application
 * about completed connections.  If we are running in test-mode with
 * an input_filename, this function is used to terminate the HTTPD
 * after the first request has been processed.
 *
 * @param cls client-defined closure, NULL
 * @param connection connection handle (ignored)
 * @param socket_context socket-specific pointer (ignored)
 * @param toe reason for connection notification
 */
static void
connection_done (void *cls,
                 struct MHD_Connection *connection,
                 void **socket_context,
                 enum MHD_ConnectionNotificationCode toe)
{
  (void) cls;
  (void) connection;
  (void) socket_context;

  switch (toe)
  {
  case MHD_CONNECTION_NOTIFY_STARTED:
    active_connections++;
    break;
  case MHD_CONNECTION_NOTIFY_CLOSED:
    active_connections--;
    if (DH_suicide &&
        (0 == active_connections) )
      GNUNET_SCHEDULER_shutdown ();
    break;
  }
}


/**
 * Function run on shutdown.
 *
 * @param cls NULL
 */
static void
do_shutdown (void *cls)
{
  (void) cls;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Shutdown initiated\n");
  TALER_MHD_daemons_halt ();
  TALER_MHD_daemons_destroy ();
  DH_keys_finished ();
  if (NULL != DH_plugin)
  {
    DONAUDB_plugin_unload (DH_plugin);
    DH_plugin = NULL;
  }
  if (NULL != DH_curl_ctx)
  {
    GNUNET_CURL_fini (DH_curl_ctx);
    DH_curl_ctx = NULL;
  }
  if (NULL != donau_curl_rc)
  {
    GNUNET_CURL_gnunet_rc_destroy (donau_curl_rc);
    donau_curl_rc = NULL;
  }
}


/**
 * Callback invoked on every listen socket to start the
 * respective MHD HTTP daemon.
 *
 * @param cls unused
 * @param lsock the listen socket
 */
static void
start_daemon (void *cls,
              int lsock)
{
  struct MHD_Daemon *mhd;

  (void) cls;
  GNUNET_assert (-1 != lsock);
  mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
                          | MHD_USE_PIPE_FOR_SHUTDOWN
                          | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
                          | MHD_USE_TCP_FASTOPEN,
                          0 /* port */,
                          NULL, NULL,
                          &handle_mhd_request, NULL,
                          MHD_OPTION_LISTEN_SOCKET,
                          lsock,
                          MHD_OPTION_EXTERNAL_LOGGER,
                          &TALER_MHD_handle_logs,
                          NULL,
                          MHD_OPTION_NOTIFY_COMPLETED,
                          &handle_mhd_completion_callback,
                          NULL,
                          MHD_OPTION_NOTIFY_CONNECTION,
                          &connection_done,
                          NULL,
                          MHD_OPTION_CONNECTION_TIMEOUT,
                          connection_timeout,
                          (0 == allow_address_reuse)
                          ? MHD_OPTION_END
                          : MHD_OPTION_LISTENING_ADDRESS_REUSE,
                          (unsigned int) allow_address_reuse,
                          MHD_OPTION_END);
  if (NULL == mhd)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to launch HTTP service. Is the port in use?\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  have_daemons = true;
  TALER_MHD_daemon_start (mhd);
}


/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure
 * @param args remaining command-line arguments
 * @param cfgfile name of the configuration file used (for saving, can be
 *        NULL!)
 * @param config configuration
 */
static void
run (void *cls,
     char *const *args,
     const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *config)
{
  enum TALER_MHD_GlobalOptions go;

  (void) cls;
  (void) args;
  (void ) cfgfile;
  go = TALER_MHD_GO_NONE;
  if (connection_close)
    go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
  TALER_MHD_setup (go);
  DH_cfg = config;
  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
                                 NULL);

  if (GNUNET_OK !=
      donau_serve_process_config ())
  {
    DH_global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  if (GNUNET_OK !=
      DH_keys_init ())
  {
    DH_global_ret = EXIT_FAILURE;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  if (GNUNET_SYSERR ==
      DH_plugin->preflight (DH_plugin->cls))
  {
    DH_global_ret = EXIT_FAILURE;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  DH_load_terms (DH_cfg);
  DH_curl_ctx
    = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
                        &donau_curl_rc);
  if (NULL == DH_curl_ctx)
  {
    GNUNET_break (0);
    DH_global_ret = EXIT_FAILURE;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  donau_curl_rc = GNUNET_CURL_gnunet_rc_create (DH_curl_ctx);
  {
    enum GNUNET_GenericReturnValue ret;

    ret = TALER_MHD_listen_bind (DH_cfg,
                                 "donau",
                                 &start_daemon,
                                 NULL);
    switch (ret)
    {
    case GNUNET_SYSERR:
      DH_global_ret = EXIT_NOTCONFIGURED;
      GNUNET_SCHEDULER_shutdown ();
      return;
    case GNUNET_NO:
      if (! have_daemons)
      {
        DH_global_ret = EXIT_NOTCONFIGURED;
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Could not open all configured listen sockets\n");
      break;
    case GNUNET_OK:
      break;
    }
  }
  DH_global_ret = EXIT_SUCCESS;
}


/**
 * The main function of the donau-httpd server ("the donau").
 *
 * @param argc number of arguments from the command line
 * @param argv command line arguments
 * @return 0 ok, 1 on error
 */
int
main (int argc,
      char *const *argv)
{
  const struct GNUNET_GETOPT_CommandLineOption options[] = {
    GNUNET_GETOPT_option_flag ('C',
                               "connection-close",
                               "force HTTP connections to be closed after each request",
                               &connection_close),
    GNUNET_GETOPT_option_flag ('r',
                               "allow-reuse-address",
                               "allow multiple HTTPDs to listen to the same port",
                               &allow_address_reuse),
    GNUNET_GETOPT_option_uint ('t',
                               "timeout",
                               "SECONDS",
                               "after how long do connections timeout by default (in seconds)",
                               &connection_timeout),
    GNUNET_GETOPT_option_timetravel ('T',
                                     "timetravel"),
    GNUNET_GETOPT_option_help (
      DONAU_project_data (),
      "HTTP server providing a RESTful API to access a Taler donau"),
    GNUNET_GETOPT_OPTION_END
  };
  enum GNUNET_GenericReturnValue ret;

  ret = GNUNET_PROGRAM_run (DONAU_project_data (),
                            argc, argv,
                            "donau-httpd",
                            "Taler donau HTTP service",
                            options,
                            &run, NULL);
  if (GNUNET_SYSERR == ret)
    return EXIT_INVALIDARGUMENT;
  if (GNUNET_NO == ret)
    return EXIT_SUCCESS;
  return DH_global_ret;
}


/* end of donau-httpd.c */
