/*
  This file is part of TALER
  (C) 2014-2025 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file taler-merchant-httpd.c
 * @brief HTTP serving layer intended to perform crypto-work and
 * communication with the exchange
 * @author Marcello Stanisci
 * @author Christian Grothoff
 * @author Florian Dold
 * @author Priscilla HUANG
 */
#include "taler/platform.h"
#include <taler/taler_dbevents.h>
#include <taler/taler_bank_service.h>
#include <taler/taler_mhd_lib.h>
#include <taler/taler_templating_lib.h>
#include <taler/taler_exchange_service.h>
#include "taler/taler_merchant_util.h"
#include "taler-merchant-httpd_auth.h"
#include "taler-merchant-httpd_dispatcher.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_mfa.h"
#include "taler-merchant-httpd_post-private-orders.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-abort.h"
#include "taler-merchant-httpd_post-challenge-ID.h"
#include "taler-merchant-httpd_get-orders-ORDER_ID.h"
#include "taler-merchant-httpd_get-sessions-SESSION_ID.h"
#include "taler-merchant-httpd_get-exchanges.h"
#include "taler-merchant-httpd_get-webui.h"
#include "taler-merchant-httpd_get-terms.h"
#include "taler-merchant-httpd_get-private-kyc.h"
#include "taler-merchant-httpd_get-private-statistics-report-transactions.h"
#include "taler-merchant-httpd_post-private-donau.h"
#include "taler-merchant-httpd_get-private-orders-ORDER_ID.h"
#include "taler-merchant-httpd_get-private-orders.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-pay.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h"


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

/**
 * Default maximum upload size permitted.  Can be overridden
 * per handler.
 */
#define DEFAULT_MAX_UPLOAD_SIZE (16 * 1024)

char *TMH_currency;

char *TMH_base_url;

char *TMH_spa_dir;

char *TMH_helper_email;

char *TMH_helper_sms;

char *TMH_phone_regex;

regex_t TMH_phone_rx;

char *TMH_allowed_payment_targets;

char *TMH_default_persona;

char *TMH_payment_target_regex;

regex_t TMH_payment_target_re;

int TMH_force_audit;

struct TALER_MERCHANTDB_Plugin *TMH_db;

struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map;

struct GNUNET_TIME_Relative TMH_default_pay_delay;

struct GNUNET_TIME_Relative TMH_default_refund_delay;

struct GNUNET_TIME_Relative TMH_default_wire_transfer_delay;

enum GNUNET_TIME_RounderInterval TMH_default_wire_transfer_rounding_interval;

int TMH_strict_v19;

int TMH_auth_disabled;

int TMH_have_self_provisioning;

enum TEH_TanChannelSet TEH_mandatory_tan_channels;

struct GNUNET_TIME_Relative TMH_legal_expiration;

unsigned int TMH_num_cspecs;

json_t *TMH_global_spa_config_data;

struct TALER_CurrencySpecification *TMH_cspecs;

struct GNUNET_CURL_Context *TMH_curl_ctx;

/**
 * Event handler for instance settings changes.
 */
static struct GNUNET_DB_EventHandler *instance_eh;

/**
 * True if we started any HTTP daemon.
 */
static bool have_daemons;

/**
 * Should a "Connection: close" header be added to each HTTP response?
 */
static int merchant_connection_close;

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

/**
 * Global return code
 */
static int global_ret;

/**
 * Our configuration.
 */
const struct GNUNET_CONFIGURATION_Handle *TMH_cfg;


void
TMH_wire_method_free (struct TMH_WireMethod *wm)
{
  GNUNET_free (wm->payto_uri.full_payto);
  GNUNET_free (wm->wire_method);
  GNUNET_free (wm->extra_wire_subject_metadata);
  GNUNET_free (wm->credit_facade_url);
  json_decref (wm->credit_facade_credentials);
  GNUNET_free (wm);
}


void
TMH_instance_decref (struct TMH_MerchantInstance *mi)
{
  struct TMH_WireMethod *wm;

  mi->rc--;
  if (0 != mi->rc)
    return;
  TMH_force_get_orders_resume (mi);
  while (NULL != (wm = (mi->wm_head)))
  {
    GNUNET_CONTAINER_DLL_remove (mi->wm_head,
                                 mi->wm_tail,
                                 wm);
    TMH_wire_method_free (wm);
  }

  GNUNET_free (mi->settings.id);
  GNUNET_free (mi->settings.name);
  GNUNET_free (mi->settings.email);
  GNUNET_free (mi->settings.phone);
  GNUNET_free (mi->settings.website);
  GNUNET_free (mi->settings.logo);
  json_decref (mi->settings.address);
  json_decref (mi->settings.jurisdiction);
  GNUNET_free (mi);
}


enum GNUNET_GenericReturnValue
TMH_instance_free_cb (void *cls,
                      const struct GNUNET_HashCode *key,
                      void *value)
{
  struct TMH_MerchantInstance *mi = value;

  (void) cls;
  (void) key;
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CONTAINER_multihashmap_remove (TMH_by_id_map,
                                                       &mi->h_instance,
                                                       mi));
  TMH_instance_decref (mi);
  return GNUNET_YES;
}


/**
 * Shutdown task (invoked when the application is being
 * terminated for any reason)
 *
 * @param cls NULL
 */
static void
do_shutdown (void *cls)
{
  (void) cls;
  TALER_MHD_daemons_halt ();
  TMH_handler_statistic_report_transactions_cleanup ();
  TMH_force_orders_resume ();
  TMH_force_get_sessions_ID_resume ();
  TMH_force_get_orders_resume_typst ();
  TMH_force_ac_resume ();
  TMH_force_pc_resume ();
  TMH_force_kyc_resume ();
  TMH_force_gorc_resume ();
  TMH_force_wallet_get_order_resume ();
  TMH_force_wallet_refund_order_resume ();
  TMH_challenge_done ();
  TALER_MHD_daemons_destroy ();
  if (NULL != instance_eh)
  {
    TMH_db->event_listen_cancel (instance_eh);
    instance_eh = NULL;
  }
  TMH_EXCHANGES_done ();
  if (NULL != TMH_db)
  {
    TALER_MERCHANTDB_plugin_unload (TMH_db);
    TMH_db = NULL;
  }
  if (NULL != TMH_by_id_map)
  {
    GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map,
                                           &TMH_instance_free_cb,
                                           NULL);
    GNUNET_CONTAINER_multihashmap_destroy (TMH_by_id_map);
    TMH_by_id_map = NULL;
  }
  TALER_TEMPLATING_done ();
  if (NULL != TMH_curl_ctx)
  {
    GNUNET_CURL_fini (TMH_curl_ctx);
    TMH_curl_ctx = NULL;
  }
  if (NULL != merchant_curl_rc)
  {
    GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc);
    merchant_curl_rc = NULL;
  }
  if (NULL != TMH_payment_target_regex)
  {
    regfree (&TMH_payment_target_re);
    GNUNET_free (TMH_payment_target_regex);
  }
}


/**
 * 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 TMH_HandlerContext *hc = *con_cls;

  (void) cls;
  if (NULL == hc)
    return;
  GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
  {
#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",
                hc->url,
                http_status,
                toe);
#else
    (void) connection;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Finished handling request for `%s' with MHD termination code %d\n",
                hc->url,
                (int) toe);
#endif
  }
  if (NULL != hc->cc)
    hc->cc (hc->ctx);
  TALER_MHD_parse_post_cleanup_callback (hc->json_parse_context);
  GNUNET_free (hc->infix);
  if (NULL != hc->request_body)
    json_decref (hc->request_body);
  if (NULL != hc->instance)
    TMH_instance_decref (hc->instance);
  TMH_db->preflight (TMH_db->cls);
  GNUNET_free (hc->full_url);
  GNUNET_free (hc);
  *con_cls = NULL;
}


struct TMH_MerchantInstance *
TMH_lookup_instance (const char *instance_id)
{
  struct GNUNET_HashCode h_instance;

  if (NULL == instance_id)
    instance_id = "admin";
  GNUNET_CRYPTO_hash (instance_id,
                      strlen (instance_id),
                      &h_instance);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Looking for by-id key %s of '%s' in hashmap\n",
              GNUNET_h2s (&h_instance),
              instance_id);
  /* We're fine if that returns NULL, the calling routine knows how
     to handle that */
  return GNUNET_CONTAINER_multihashmap_get (TMH_by_id_map,
                                            &h_instance);
}


/**
 * Add instance definition to our active set of instances.
 *
 * @param[in,out] mi merchant instance details to define
 * @return #GNUNET_OK on success, #GNUNET_NO if the same ID is in use already
 */
enum GNUNET_GenericReturnValue
TMH_add_instance (struct TMH_MerchantInstance *mi)
{
  const char *id;
  enum GNUNET_GenericReturnValue ret;

  id = mi->settings.id;
  if (NULL == id)
    id = "admin";
  GNUNET_CRYPTO_hash (id,
                      strlen (id),
                      &mi->h_instance);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Looking for by-id key %s of `%s' in hashmap\n",
              GNUNET_h2s (&mi->h_instance),
              id);
  ret = GNUNET_CONTAINER_multihashmap_put (TMH_by_id_map,
                                           &mi->h_instance,
                                           mi,
                                           GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
  if (GNUNET_OK == ret)
  {
    GNUNET_assert (mi->rc < UINT_MAX);
    mi->rc++;
  }
  return ret;
}


/**
 * Function called first by MHD with the full URL.
 *
 * @param cls NULL
 * @param full_url the full URL
 * @param con MHD connection object
 * @return our handler context
 */
static void *
full_url_track_callback (void *cls,
                         const char *full_url,
                         struct MHD_Connection *con)
{
  struct TMH_HandlerContext *hc;

  hc = GNUNET_new (struct TMH_HandlerContext);
  hc->connection = con;
  GNUNET_async_scope_fresh (&hc->async_scope_id);
  GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
  hc->full_url = GNUNET_strdup (full_url);
  return hc;
}


/**
 * The callback was called again by MHD, continue processing
 * the request with the already identified handler.
 *
 * @param hc the handler context
 * @param upload_data the data being uploaded (excluding HEADERS,
 *        for a POST that fits into memory and that is encoded
 *        with a supported encoding, the POST data will NOT be
 *        given in upload_data and is instead available as
 *        part of #MHD_get_connection_values; very large POST
 *        data *will* be made available incrementally in
 *        @a upload_data)
 * @param upload_data_size set initially to the size of the
 *        @a upload_data provided; the method must update this
 *        value to the number of bytes NOT processed;
 * @return #MHD_YES if the connection was handled successfully,
 *         #MHD_NO if the socket must be closed due to a serious
 *         error while handling the request
 */
static MHD_RESULT
process_upload_with_handler (struct TMH_HandlerContext *hc,
                             const char *upload_data,
                             size_t *upload_data_size)
{
  GNUNET_assert (NULL != hc->rh);
  GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
  if ( (hc->has_body) &&
       (NULL == hc->request_body) )
  {
    size_t mul = hc->rh->max_upload;
    enum GNUNET_GenericReturnValue res;

    if (0 == mul)
      mul = DEFAULT_MAX_UPLOAD_SIZE;
    if ( (hc->total_upload + *upload_data_size < hc->total_upload) ||
         (hc->total_upload + *upload_data_size > mul) )
    {
      /* Client exceeds upload limit. Should _usually_ be checked earlier
         when we look at the MHD_HTTP_HEADER_CONTENT_LENGTH, alas with
         chunked encoding an uploader MAY have omitted this, and thus
         not permitted us to check on time. In this case, we just close
         the connection once it exceeds our limit (instead of waiting
         for the upload to complete and then fail). This could theoretically
         cause some clients to retry, alas broken or malicious clients
         are likely to retry anyway, so little we can do about it, and
         failing earlier seems the best option here.  */
      GNUNET_break_op (0);
      return MHD_NO;
    }
    hc->total_upload += *upload_data_size;
    res = TALER_MHD_parse_post_json (hc->connection,
                                     &hc->json_parse_context,
                                     upload_data,
                                     upload_data_size,
                                     &hc->request_body);
    if (GNUNET_SYSERR == res)
      return MHD_NO;
    /* A error response was already generated */
    if ( (GNUNET_NO == res) ||
         /* or, need more data to accomplish parsing */
         (NULL == hc->request_body) )
      return MHD_YES;   /* let MHD call us *again* */
  }
  /* Upload complete (if any), call handler to generate reply */
  return hc->rh->handler (hc->rh,
                          hc->connection,
                          hc);
}


/**
 * Log information about the request being handled.
 *
 * @param hc handler context
 * @param method HTTP method of the request
 */
static void
log_request (const struct TMH_HandlerContext *hc,
             const char *method)
{
  const char *correlation_id;

  correlation_id = MHD_lookup_connection_value (hc->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;
  }
  if (NULL != correlation_id)
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Handling request for (%s) URL '%s', correlation_id=%s\n",
                method,
                hc->url,
                correlation_id);
  else
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Handling request (%s) for URL '%s'\n",
                method,
                hc->url);
}


/**
 * Identify the instance of the request from the URL.
 *
 * @param[in,out] hc handler context
 * @param[in,out] urlp URL path of the request, updated to point to the rest
 * @param[out] use_admin set to true if we are using the admin instance
 * @return #GNUNET_OK on success,
 *         #GNUNET_NO if an error was queued (return #MHD_YES)
 *         #GNUNET_SYSERR to close the connection (return #MHD_NO)
 */
static enum GNUNET_GenericReturnValue
identify_instance (struct TMH_HandlerContext *hc,
                   const char **urlp,
                   bool *use_admin)
{
  const char *url = *urlp;
  const char *instance_prefix = "/instances/";

  if (0 == strncmp (url,
                    instance_prefix,
                    strlen (instance_prefix)))
  {
    /* url starts with "/instances/" */
    const char *istart = url + strlen (instance_prefix);
    const char *slash = strchr (istart, '/');
    char *instance_id;

    if (NULL == slash)
      instance_id = GNUNET_strdup (istart);
    else
      instance_id = GNUNET_strndup (istart,
                                    slash - istart);
    if (0 == strcmp (instance_id,
                     "admin"))
    {
      MHD_RESULT ret;
      struct MHD_Response *response;
      const char *rstart = hc->full_url + strlen (instance_prefix);
      const char *rslash = strchr (rstart, '/');

      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Client used deprecated '/instances/default/' path. Redirecting to modern path\n");

      response
        = MHD_create_response_from_buffer (0,
                                           NULL,
                                           MHD_RESPMEM_PERSISTENT);
      TALER_MHD_add_global_headers (response,
                                    true);
      if (MHD_NO ==
          MHD_add_response_header (response,
                                   MHD_HTTP_HEADER_LOCATION,
                                   NULL == rslash
                                     ? "/"
                                     : rslash))
      {
        GNUNET_break (0);
        MHD_destroy_response (response);
        GNUNET_free (instance_id);
        return GNUNET_SYSERR;
      }
      ret = MHD_queue_response (hc->connection,
                                MHD_HTTP_PERMANENT_REDIRECT,
                                response);
      MHD_destroy_response (response);
      GNUNET_free (instance_id);
      return (MHD_YES == ret) ? GNUNET_NO : GNUNET_SYSERR;
    }
    hc->instance = TMH_lookup_instance (instance_id);
    if ( (NULL == hc->instance) &&
         (0 == strcmp ("admin",
                       instance_id)) )
      hc->instance = TMH_lookup_instance (NULL);
    GNUNET_free (instance_id);
    if (NULL == slash)
      *urlp = "";
    else
      *urlp = slash;
  }
  else
  {
    /* use 'default' */
    *use_admin = true;
    hc->instance = TMH_lookup_instance (NULL);
  }
  if (NULL != hc->instance)
  {
    GNUNET_assert (hc->instance->rc < UINT_MAX);
    hc->instance->rc++;
  }
  return GNUNET_OK;
}


/**
 * A client has requested the given url using the given method
 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).  The callback
 * must call MHD callbacks to provide content to give back to the
 * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
 * #MHD_HTTP_NOT_FOUND, etc.).
 *
 * @param cls argument given together with the function
 *        pointer when the handler was registered with MHD
 * @param connection the MHD connection to handle
 * @param url the requested url
 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
 *        #MHD_HTTP_METHOD_PUT, etc.)
 * @param version the HTTP version string (i.e.
 *        #MHD_HTTP_VERSION_1_1)
 * @param upload_data the data being uploaded (excluding HEADERS,
 *        for a POST that fits into memory and that is encoded
 *        with a supported encoding, the POST data will NOT be
 *        given in upload_data and is instead available as
 *        part of #MHD_get_connection_values; very large POST
 *        data *will* be made available incrementally in
 *        @a upload_data)
 * @param upload_data_size set initially to the size of the
 *        @a upload_data provided; the method must update this
 *        value to the number of bytes NOT processed;
 * @param con_cls pointer that the callback can set to some
 *        address and that will be preserved by MHD for future
 *        calls for this request; since the access handler may
 *        be called many times (i.e., for a PUT/POST operation
 *        with plenty of upload data) this allows the application
 *        to easily associate some request-specific state.
 *        If necessary, this state can be cleaned up in the
 *        global #MHD_RequestCompletedCallback (which
 *        can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
 *        Initially, `*con_cls` will be set up by the
 *        full_url_track_callback().
 * @return #MHD_YES if the connection was handled successfully,
 *         #MHD_NO if the socket must be closed due to a serious
 *         error while handling the request
 */
static MHD_RESULT
url_handler (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)
{
  struct TMH_HandlerContext *hc = *con_cls;
  bool use_admin = false;
  bool is_public = false;

  (void) cls;
  (void) version;
  if (NULL != hc->url)
  {
    /* MHD calls us again for a request, we already identified
       the handler, just continue processing with the handler */
    return process_upload_with_handler (hc,
                                        upload_data,
                                        upload_data_size);
  }
  hc->url = url;
  log_request (hc,
               method);

  /* Find out the merchant backend instance for the request.
   * If there is an instance, remove the instance specification
   * from the beginning of the request URL. */
  {
    enum GNUNET_GenericReturnValue ret;

    ret = identify_instance (hc,
                             &url,
                             &use_admin);
    if (GNUNET_OK != ret)
      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
  }

  {
    enum GNUNET_GenericReturnValue ret;

    ret = TMH_dispatch_request (hc,
                                url,
                                method,
                                use_admin,
                                &is_public);
    if (GNUNET_OK != ret)
      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
  }

  /* At this point, we must have found a handler */
  GNUNET_assert (NULL != hc->rh);

  /* If an instance must be there, check one exists */
  if ( (NULL == hc->instance) &&
       (! hc->rh->skip_instance) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Instance for `%s' not known\n",
                hc->url);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_NOT_FOUND,
                                       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
                                       hc->url);
  }

  /* Perform access control for non-public handlers */
  if (! is_public)
  {
    enum GNUNET_GenericReturnValue ret;

    ret = TMH_perform_access_control (hc);
    if (GNUNET_OK != ret)
      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
  }

  if ( (NULL != hc->instance) && /* make static analysis happy */
       (! hc->rh->skip_instance) &&
       (hc->instance->deleted) &&
       (! hc->rh->allow_deleted_instance) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Instance `%s' was deleted\n",
                hc->instance->settings.id);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_NOT_FOUND,
                                       TALER_EC_MERCHANT_GENERIC_INSTANCE_DELETED,
                                       hc->instance->settings.id);
  }

  /* Check upload constraints */
  hc->has_body = ( (0 == strcasecmp (method,
                                     MHD_HTTP_METHOD_POST)) ||
                   /* PUT is not yet used */
                   (0 == strcasecmp (method,
                                     MHD_HTTP_METHOD_PATCH)) );
  if (hc->has_body)
  {
    /* This is a macro: it will queue an error response and return
       from this function if the upload would be too large. */
    TALER_MHD_check_content_length (connection,
                                    0 == hc->rh->max_upload
                                    ? DEFAULT_MAX_UPLOAD_SIZE
                                    : hc->rh->max_upload);
    GNUNET_break (NULL == hc->request_body); /* can't have it already */
  }
  /* wait for MHD to call us again, this time hc->url will be non-NULL
     and we should jump straight into process_upload_with_handler(). */
  return MHD_YES;
}


/**
 * Callback invoked with information about a bank account.
 *
 * @param cls closure with a `struct TMH_MerchantInstance *`
 * @param merchant_priv private key of the merchant instance
 * @param acc details about the account
 */
static void
add_account_cb (void *cls,
                const struct TALER_MerchantPrivateKeyP *merchant_priv,
                const struct TALER_MERCHANTDB_AccountDetails *acc)
{
  struct TMH_MerchantInstance *mi = cls;
  struct TMH_WireMethod *wm;

  (void) merchant_priv;
  wm = GNUNET_new (struct TMH_WireMethod);
  wm->h_wire = acc->h_wire;
  wm->payto_uri.full_payto
    = GNUNET_strdup (acc->payto_uri.full_payto);
  if (NULL != acc->extra_wire_subject_metadata)
    wm->extra_wire_subject_metadata
      = GNUNET_strdup (acc->extra_wire_subject_metadata);
  wm->wire_salt = acc->salt;
  wm->wire_method
    = TALER_payto_get_method (acc->payto_uri.full_payto);
  wm->active = acc->active;
  GNUNET_CONTAINER_DLL_insert (mi->wm_head,
                               mi->wm_tail,
                               wm);
}


/**
 * Function called during startup to add all known instances to our
 * hash map in memory for faster lookups when we receive requests.
 *
 * @param cls closure, NULL, unused
 * @param merchant_pub public key of the instance
 * @param merchant_priv private key of the instance, NULL if not available
 * @param is detailed configuration settings for the instance
 * @param ias authentication settings for the instance
 */
static void
add_instance_cb (void *cls,
                 const struct TALER_MerchantPublicKeyP *merchant_pub,
                 const struct TALER_MerchantPrivateKeyP *merchant_priv,
                 const struct TALER_MERCHANTDB_InstanceSettings *is,
                 const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
  struct TMH_MerchantInstance *mi;
  enum GNUNET_DB_QueryStatus qs;

  (void) cls;
  mi = TMH_lookup_instance (is->id);
  if (NULL != mi)
  {
    /* (outdated) entry exists, remove old entry */
    (void) TMH_instance_free_cb (NULL,
                                 &mi->h_instance,
                                 mi);
  }
  mi = GNUNET_new (struct TMH_MerchantInstance);
  mi->settings = *is;
  mi->auth = *ias;
  mi->settings.id = GNUNET_strdup (mi->settings.id);
  mi->settings.name = GNUNET_strdup (mi->settings.name);
  if (NULL != mi->settings.email)
    mi->settings.email = GNUNET_strdup (mi->settings.email);
  if (NULL != mi->settings.phone)
    mi->settings.phone = GNUNET_strdup (mi->settings.phone);
  if (NULL != mi->settings.website)
    mi->settings.website = GNUNET_strdup (mi->settings.website);
  if (NULL != mi->settings.logo)
    mi->settings.logo = GNUNET_strdup (mi->settings.logo);
  mi->settings.address = json_incref (mi->settings.address);
  mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
  if (NULL != merchant_priv)
    mi->merchant_priv = *merchant_priv;
  else
    mi->deleted = true;
  mi->merchant_pub = *merchant_pub;
  qs = TMH_db->select_accounts (TMH_db->cls,
                                mi->settings.id,
                                &add_account_cb,
                                mi);
  if (0 > qs)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Error loading accounts of `%s' from database\n",
                mi->settings.id);
  }
  GNUNET_assert (GNUNET_OK ==
                 TMH_add_instance (mi));
}


/**
 * Trigger (re)loading of instance settings from DB.
 *
 * @param cls NULL
 * @param extra ID of the instance that changed, NULL
 *              to load all instances (will not handle purges!)
 * @param extra_len number of bytes in @a extra
 */
static void
load_instances (void *cls,
                const void *extra,
                size_t extra_len)
{
  enum GNUNET_DB_QueryStatus qs;
  const char *id = extra;

  (void) cls;
  if ( (NULL != extra) &&
       ( (0 == extra_len) ||
         ('\0' != id[extra_len - 1]) ) )
  {
    GNUNET_break (0 == extra_len);
    extra = NULL;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Received instance settings notification: reload `%s'\n",
              id);
  if (NULL == extra)
  {
    qs = TMH_db->lookup_instances (TMH_db->cls,
                                   false,
                                   &add_instance_cb,
                                   NULL);
  }
  else
  {
    struct TMH_MerchantInstance *mi;

    /* This must be done here to handle instance
       purging, as for purged instances, the DB
       lookup below will otherwise do nothing */
    mi = TMH_lookup_instance (id);
    if (NULL != mi)
    {
      (void) TMH_instance_free_cb (NULL,
                                   &mi->h_instance,
                                   mi);
    }
    qs = TMH_db->lookup_instance (TMH_db->cls,
                                  id,
                                  false,
                                  &add_instance_cb,
                                  NULL);
  }
  if (0 > qs)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed initialization. Check database setup.\n");
    global_ret = EXIT_NOPERMISSION;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
}


/**
 * A transaction modified an instance setting (or created/deleted/purged
 * one). Notify all backends about the change.
 *
 * @param id ID of the instance that changed
 */
void
TMH_reload_instances (const char *id)
{
  struct GNUNET_DB_EventHeaderP es = {
    .size = htons (sizeof (es)),
    .type = htons (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
  };

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Generating instance settings notification: reload `%s'\n",
              id);
  TMH_db->event_notify (TMH_db->cls,
                        &es,
                        id,
                        (NULL == id)
                        ? 0
                        : strlen (id) + 1);
}


/**
 * 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_DUAL_STACK
                          | MHD_USE_AUTO,
                          0 /* port */,
                          NULL, NULL,
                          &url_handler, NULL,
                          MHD_OPTION_LISTEN_SOCKET, lsock,
                          MHD_OPTION_URI_LOG_CALLBACK,
                          &full_url_track_callback, NULL,
                          MHD_OPTION_NOTIFY_COMPLETED,
                          &handle_mhd_completion_callback, NULL,
                          MHD_OPTION_CONNECTION_TIMEOUT,
                          (unsigned int) 10 /* 10s */,
                          MHD_OPTION_END);
  if (NULL == mhd)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to launch HTTP service.\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;
  int elen;

  (void) cls;
  (void) args;
  (void) cfgfile;
  TMH_cfg = config;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Starting taler-merchant-httpd\n");
  go = TALER_MHD_GO_NONE;
  if (merchant_connection_close)
    go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
  TALER_MHD_setup (go);

  global_ret = EXIT_SUCCESS;
  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
                                 NULL);

  TMH_curl_ctx
    = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
                        &merchant_curl_rc);
  if (NULL == TMH_curl_ctx)
  {
    GNUNET_break (0);
    global_ret = EXIT_NO_RESTART;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (TMH_curl_ctx);
  /* Disable 100 continue processing */
  GNUNET_break (GNUNET_OK ==
                GNUNET_CURL_append_header (TMH_curl_ctx,
                                           MHD_HTTP_HEADER_EXPECT ":"));
  GNUNET_CURL_enable_async_scope_header (TMH_curl_ctx,
                                         "Taler-Correlation-Id");

  if (GNUNET_SYSERR ==
      TALER_config_get_currency (TMH_cfg,
                                 "merchant",
                                 &TMH_currency))
  {
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      TALER_CONFIG_parse_currencies (TMH_cfg,
                                     TMH_currency,
                                     &TMH_num_cspecs,
                                     &TMH_cspecs))
  {
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  {
    char *spa_data;

    if (GNUNET_OK ==
        GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                               "merchant",
                                               "GLOBAL_SPA_CONFIG_DATA",
                                               &spa_data))
    {
      json_error_t err;

      TMH_global_spa_config_data = json_loads (spa_data,
                                               JSON_REJECT_DUPLICATES,
                                               &err);
      GNUNET_free (spa_data);
      if (NULL == TMH_global_spa_config_data)
      {
        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                   "merchant",
                                   "GLOBAL_SPA_CONFIG_DATA",
                                   err.text);
        global_ret = EXIT_NOTCONFIGURED;
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
    }
  }


  if (GNUNET_SYSERR ==
      (TMH_strict_v19
         = GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg,
                                                 "merchant",
                                                 "STRICT_PROTOCOL_V19")))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
                               "merchant",
                               "STRICT_PROTOCOL_V19");
    TMH_strict_v19 = GNUNET_NO;
  }
  if (GNUNET_SYSERR ==
      (TMH_auth_disabled = GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg,
                                                                 "merchant",
                                                                 "DISABLE_AUTHENTICATION")))
  {
    TMH_auth_disabled = GNUNET_NO;
  }
  if (GNUNET_YES == TMH_auth_disabled)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "DANGEROUS: Endpoint Authentication disabled!");
  }

  if (GNUNET_SYSERR ==
      (TMH_have_self_provisioning
         = GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg,
                                                 "merchant",
                                                 "ENABLE_SELF_PROVISIONING")))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
                               "merchant",
                               "ENABLE_SELF_PROVISIONING");
    TMH_have_self_provisioning = GNUNET_NO;
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (TMH_cfg,
                                           "merchant",
                                           "LEGAL_PRESERVATION",
                                           &TMH_legal_expiration))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "merchant",
                               "LEGAL_PRESERVATION");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (TMH_cfg,
                                           "merchant",
                                           "DEFAULT_PAY_DELAY",
                                           &TMH_default_pay_delay))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
                               "merchant",
                               "DEFAULT_PAY_DELAY");
    TMH_default_pay_delay = GNUNET_TIME_UNIT_DAYS;
  }
  if (GNUNET_TIME_relative_is_forever (TMH_default_pay_delay))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_INFO,
                               "merchant",
                               "DEFAULT_PAY_DELAY",
                               "forever is not allowed");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (TMH_cfg,
                                           "merchant",
                                           "DEFAULT_REFUND_DELAY",
                                           &TMH_default_refund_delay))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
                               "merchant",
                               "DEFAULT_REFUND_DELAY");
    TMH_default_refund_delay = GNUNET_TIME_relative_multiply (
      GNUNET_TIME_UNIT_DAYS,
      15);
  }
  if (GNUNET_TIME_relative_is_forever (TMH_default_refund_delay))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_INFO,
                               "merchant",
                               "DEFAULT_REFUND_DELAY",
                               "forever is not allowed");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (TMH_cfg,
                                           "merchant",
                                           "DEFAULT_WIRE_TRANSFER_DELAY",
                                           &TMH_default_wire_transfer_delay))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
                               "merchant",
                               "DEFAULT_WIRE_TRANSFER_DELAY");
    TMH_default_wire_transfer_delay = GNUNET_TIME_UNIT_MONTHS;
  }
  if (GNUNET_TIME_relative_is_forever (TMH_default_wire_transfer_delay))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_INFO,
                               "merchant",
                               "DEFAULT_WIRE_TRANSFER_DELAY",
                               "forever is not allowed");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  {
    char *dwtri;

    if (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_string (
          TMH_cfg,
          "merchant",
          "DEFAULT_WIRE_TRANSFER_ROUNDING_INTERVAL",
          &dwtri))
    {
      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
                                 "merchant",
                                 "DEFAULT_WIRE_TRANSFER_ROUNDING_INTERVAL");
      TMH_default_wire_transfer_rounding_interval = GNUNET_TIME_RI_NONE;
    }
    else
    {
      if (GNUNET_OK !=
          GNUNET_TIME_string_to_round_interval (
            dwtri,
            &TMH_default_wire_transfer_rounding_interval))
      {
        GNUNET_log_config_invalid (
          GNUNET_ERROR_TYPE_ERROR,
          "merchant",
          "DEFAULT_WIRE_TRANSFER_ROUNDING_INTERVAL",
          "invalid time rounding interval");
        global_ret = EXIT_NOTCONFIGURED;
        GNUNET_free (dwtri);
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
      GNUNET_free (dwtri);
    }
  }

  TMH_load_terms (TMH_cfg);

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                             "merchant",
                                             "PAYMENT_TARGET_TYPES",
                                             &TMH_allowed_payment_targets))
  {
    TMH_allowed_payment_targets = GNUNET_strdup ("*");
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                             "merchant",
                                             "DEFAULT_PERSONA",
                                             &TMH_default_persona))
  {
    TMH_default_persona = GNUNET_strdup ("expert");
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                             "merchant",
                                             "PAYMENT_TARGET_REGEX",
                                             &TMH_payment_target_regex))
  {
    TMH_payment_target_regex = NULL;
  }
  else
  {
    if (0 == strlen (TMH_payment_target_regex))
    {
      GNUNET_free (TMH_payment_target_regex);
    }
    else
    {
      if (0 != regcomp (&TMH_payment_target_re,
                        TMH_payment_target_regex,
                        REG_NOSUB | REG_EXTENDED))
      {
        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                   "merchant",
                                   "PAYMENT_TARGET_REGEX",
                                   "malformed regular expression");
        global_ret = EXIT_NOTCONFIGURED;
        GNUNET_free (TMH_payment_target_regex);
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
    }
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                             "merchant",
                                             "PHONE_REGEX",
                                             &TMH_phone_regex))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
                               "merchant",
                               "PHONE_REGEX",
                               "no restrictions on phone number specified");
  }
  else
  {
    if (0 != regcomp (&TMH_phone_rx,
                      TMH_phone_regex,
                      REG_EXTENDED))
    {
      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                 "merchant",
                                 "PHONE_REGEX",
                                 "Invalid regex specified");
      global_ret = EXIT_NOTCONFIGURED;
      GNUNET_SCHEDULER_shutdown ();
      return;
    }
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                             "merchant",
                                             "HELPER_SMS",
                                             &TMH_helper_sms))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
                               "merchant",
                               "HELPER_SMS",
                               "no helper specified");
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                             "merchant",
                                             "HELPER_EMAIL",
                                             &TMH_helper_email))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
                               "merchant",
                               "HELPER_EMAIL",
                               "no helper specified");
  }

  {
    char *tan_channels;

    if (GNUNET_OK ==
        GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                               "merchant",
                                               "MANDATORY_TAN_CHANNELS",
                                               &tan_channels))
    {
      for (char *tok = strtok (tan_channels,
                               " ");
           NULL != tok;
           tok = strtok (NULL,
                         " "))
      {
        if (0 == strcasecmp (tok,
                             "sms"))
        {
          if (NULL == TMH_helper_sms)
          {
            GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                       "merchant",
                                       "MANDATORY_TAN_CHANNELS",
                                       "SMS mandatory, but no HELPER_SMS configured");
            global_ret = EXIT_NOTCONFIGURED;
            GNUNET_SCHEDULER_shutdown ();
            GNUNET_free (tan_channels);
            return;
          }
          TEH_mandatory_tan_channels |= TMH_TCS_SMS;
        }
        else if (0 == strcasecmp (tok,
                                  "email"))
        {
          if (NULL == TMH_helper_email)
          {
            GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                       "merchant",
                                       "MANDATORY_TAN_CHANNELS",
                                       "EMAIL mandatory, but no HELPER_EMAIL configured");
            global_ret = EXIT_NOTCONFIGURED;
            GNUNET_SCHEDULER_shutdown ();
            GNUNET_free (tan_channels);
            return;
          }
          TEH_mandatory_tan_channels |= TMH_TCS_EMAIL;
        }
        else
        {
          GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                     "merchant",
                                     "MANDATORY_TAN_CHANNELS",
                                     tok);
          global_ret = EXIT_NOTCONFIGURED;
          GNUNET_SCHEDULER_shutdown ();
          GNUNET_free (tan_channels);
          return;
        }
      }
      GNUNET_free (tan_channels);
    }
  }

  if (GNUNET_OK ==
      GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                             "merchant",
                                             "BASE_URL",
                                             &TMH_base_url))
  {
    if (! TALER_is_web_url (TMH_base_url))
    {
      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                 "merchant",
                                 "BASE_URL",
                                 "Needs to start with 'http://' or 'https://'");
      global_ret = EXIT_NOTCONFIGURED;
      GNUNET_SCHEDULER_shutdown ();
      return;
    }
  }
  if (GNUNET_OK ==
      GNUNET_CONFIGURATION_get_value_string (TMH_cfg,
                                             "merchant",
                                             "BACKOFFICE_SPA_DIR",
                                             &TMH_spa_dir))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Loading merchant SPA from %s\n",
                TMH_spa_dir);
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Loading merchant SPA from default location\n");
  }

  if (GNUNET_YES ==
      GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg,
                                            "merchant",
                                            "FORCE_AUDIT"))
    TMH_force_audit = GNUNET_YES;
  if (GNUNET_OK !=
      TALER_TEMPLATING_init (TALER_MERCHANT_project_data ()))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to setup templates\n");
    global_ret = EXIT_NOTINSTALLED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      TMH_spa_init (TMH_spa_dir))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to load single page app\n");
    global_ret = EXIT_NOTINSTALLED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  /* /static/ is currently not used */
  /* (void) TMH_statics_init (); */
  if (NULL ==
      (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4,
                                                             GNUNET_YES)))
  {
    global_ret = EXIT_FAILURE;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (NULL ==
      (TMH_db = TALER_MERCHANTDB_plugin_load (TMH_cfg)))
  {
    global_ret = EXIT_NOTINSTALLED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      TMH_db->connect (TMH_db->cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
    global_ret = EXIT_FAILURE;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  elen = TMH_EXCHANGES_init (config);
  if (GNUNET_SYSERR == elen)
  {
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (0 == elen)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Fatal: no trusted exchanges configured. Exiting.\n");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  {
    struct GNUNET_DB_EventHeaderP es = {
      .size = htons (sizeof (es)),
      .type = htons (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
    };

    instance_eh = TMH_db->event_listen (TMH_db->cls,
                                        &es,
                                        GNUNET_TIME_UNIT_FOREVER_REL,
                                        &load_instances,
                                        NULL);
  }
  load_instances (NULL,
                  NULL,
                  0);
  {
    enum GNUNET_GenericReturnValue ret;

    ret = TALER_MHD_listen_bind (TMH_cfg,
                                 "merchant",
                                 &start_daemon,
                                 NULL);
    switch (ret)
    {
    case GNUNET_SYSERR:
      global_ret = EXIT_NOTCONFIGURED;
      GNUNET_SCHEDULER_shutdown ();
      return;
    case GNUNET_NO:
      if (! have_daemons)
      {
        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;
    }
  }
  global_ret = EXIT_SUCCESS;
}


/**
 * The main function of the serve tool
 *
 * @param argc number of arguments from the command line
 * @param argv command line arguments
 * @return 0 ok, non-zero on error
 */
int
main (int argc,
      char *const *argv)
{
  enum GNUNET_GenericReturnValue res;
  struct GNUNET_GETOPT_CommandLineOption options[] = {
    GNUNET_GETOPT_option_flag ('C',
                               "connection-close",
                               "force HTTP connections to be closed after each request",
                               &merchant_connection_close),
    GNUNET_GETOPT_option_timetravel ('T',
                                     "timetravel"),
    GNUNET_GETOPT_option_version (PACKAGE_VERSION "-" VCS_VERSION),
    GNUNET_GETOPT_OPTION_END
  };

  res = GNUNET_PROGRAM_run (
    TALER_MERCHANT_project_data (),
    argc, argv,
    "taler-merchant-httpd",
    "Taler merchant's HTTP backend interface",
    options,
    &run, NULL);
  if (GNUNET_SYSERR == res)
    return EXIT_INVALIDARGUMENT;
  if (GNUNET_NO == res)
    return EXIT_SUCCESS;
  return global_ret;
}
