/*
  This file is part of Anastasis
  (C) 2020-2025 Anastasis SARL

  Anastasis 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.

  Anastasis 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
  Anastasis; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file backend/anastasis-httpd.c
 * @brief HTTP serving layer intended to provide basic backup operations
 * @author Christian Grothoff
 * @author Dennis Neufeld
 * @author Dominik Meister
 */
#include "platform.h"
#include "anastasis-httpd.h"
#include "anastasis_util_lib.h"
#include "anastasis-httpd_mhd.h"
#include "anastasis_database_lib.h"
#include "anastasis-httpd_policy.h"
#include "anastasis-httpd_policy-meta.h"
#include "anastasis-httpd_truth.h"
#include "anastasis-httpd_terms.h"
#include "anastasis-httpd_config.h"
#include <taler/taler_json_lib.h>


/**
 * Upload limit to the service, in megabytes.
 */
unsigned long long int AH_upload_limit_mb;

/**
 * Currencies this provider prices its service in, primary currency first.
 */
char **AH_currencies;

/**
 * Length of the #AH_currencies array.
 */
unsigned int AH_currencies_len;

/**
 * Annual fee for the backup account, per currency.
 */
struct TALER_AmountList AH_annual_fees;

/**
 * Fee for a truth upload, per currency.
 */
struct TALER_AmountList AH_truth_upload_fees;

/**
 * Amount of insurance, per currency.
 */
struct TALER_AmountList AH_insurance;

/**
 * Cost for secure question truth download, per currency.
 */
struct TALER_AmountList AH_question_costs;

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

/**
 * Our Taler backend to process payments.
 */
char *AH_backend_url;

/**
 * Our fulfillment URL.
 */
char *AH_fulfillment_url;

/**
 * Our business name.
 */
char *AH_business_name;

/**
 * Our provider salt.
 */
struct ANASTASIS_CRYPTO_ProviderSaltP AH_provider_salt;

/**
 * Number of policy uploads permitted per annual fee payment.
 */
unsigned long long AH_post_counter = 64LLU;

/**
 * Our context for making HTTP requests.
 */
struct GNUNET_CURL_Context *AH_ctx;

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

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

/**
 * Heap for processing timeouts of requests.
 */
struct GNUNET_CONTAINER_Heap *AH_to_heap;

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

/**
 * Reschedule context for #AH_ctx.
 */
static struct GNUNET_CURL_RescheduleContext *rc;

/**
 * Username and password to use for client authentication
 * (optional).
 */
static char *userpass;

/**
 * Type of the client's TLS certificate (optional).
 */
static char *certtype;

/**
 * File with the client's TLS certificate (optional).
 */
static char *certfile;

/**
 * File with the client's TLS private key (optional).
 */
static char *keyfile;

/**
 * This value goes in the Authorization:-header.
 */
static char *apikey;

/**
 * Passphrase to decrypt client's TLS private key file (optional).
 */
static char *keypass;


/**
 * Kick MHD to run now, to be called after MHD_resume_connection().
 * Basically, we need to explicitly resume MHD's event loop whenever
 * we made progress serving a request.  This function re-schedules
 * the task processing MHD's activities to run immediately.
 *
 * @param cls NULL -- FIXME: why cls?
 *
 * FIXME: maybe call directly?
 */
void
AH_trigger_daemon (void *cls)
{
  TALER_MHD_daemon_trigger ();
}


/**
 * Kick GNUnet Curl scheduler to begin curl interactions.
 */
void
AH_trigger_curl (void)
{
  GNUNET_CURL_gnunet_scheduler_reschedule (&rc);
}


/**
 * 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 MHD connection handle with further request details
 * @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 NULL.
 * @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 enum 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)
{
  static struct AH_RequestHandler handlers[] = {
    /* Landing page, tell humans to go away. */
    { "/", MHD_HTTP_METHOD_GET, "text/plain",
      "Hello, I'm Anastasis. This HTTP server is not for humans.\n", 0,
      &TMH_MHD_handler_static_response, MHD_HTTP_OK },
    { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
      NULL, 0,
      &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
    { "/terms", MHD_HTTP_METHOD_GET, NULL,
      NULL, 0,
      &AH_handler_terms, MHD_HTTP_OK },
    { "/privacy", MHD_HTTP_METHOD_GET, NULL,
      NULL, 0,
      &AH_handler_privacy, MHD_HTTP_OK },
    { "/config", MHD_HTTP_METHOD_GET, "text/json",
      NULL, 0,
      &AH_handler_config, MHD_HTTP_OK },
    {NULL, NULL, NULL, NULL, 0, 0 }
  };
  static struct AH_RequestHandler h404 = {
    "", NULL, "text/html",
    "<html><title>404: not found</title></html>", 0,
    &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND
  };
  static struct AH_RequestHandler h405 = {
    "", NULL, "text/html",
    "<html><title>405: method not allowed</title></html>", 0,
    &TMH_MHD_handler_static_response, MHD_HTTP_METHOD_NOT_ALLOWED
  };
  struct TM_HandlerContext *hc = *con_cls;
  const char *correlation_id = NULL;
  bool path_matched;

  if (NULL == hc)
  {
    struct GNUNET_AsyncScopeId aid;

    GNUNET_async_scope_fresh (&aid);
    /* We only read the correlation ID on the first callback for every client */
    correlation_id = MHD_lookup_connection_value (connection,
                                                  MHD_HEADER_KIND,
                                                  "Anastasis-Correlation-Id");
    if ((NULL != correlation_id) &&
        (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Invalid incoming correlation ID\n");
      correlation_id = NULL;
    }
    hc = GNUNET_new (struct TM_HandlerContext);
    *con_cls = hc;
    hc->async_scope_id = aid;
    hc->url = url;
  }
  if (0 == strcasecmp (method,
                       MHD_HTTP_METHOD_HEAD))
    method = MHD_HTTP_METHOD_GET; /* MHD will throw away the body */

  GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
  if (NULL != correlation_id)
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Handling request for (%s) 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);
  if (0 == strncmp (url,
                    "/policy/",
                    strlen ("/policy/")))
  {
    const char *account = url + strlen ("/policy/");
    const char *end = strchr (account, '/');
    struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub;

    if (GNUNET_OK !=
        GNUNET_STRINGS_string_to_data (
          account,
          (NULL == end)
          ? strlen (account)
          : end - account,
          &account_pub,
          sizeof (struct ANASTASIS_CRYPTO_AccountPublicKeyP)))
    {
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                         "account public key");
    }
    if ( (NULL != end) &&
         (0 != strcmp (end,
                       "/meta")) )
      return TMH_MHD_handler_static_response (&h404,
                                              connection);
    if (0 == strcmp (method,
                     MHD_HTTP_METHOD_GET))
    {
      if (NULL == end)
        return AH_policy_get (connection,
                              &account_pub);
      return AH_policy_meta_get (connection,
                                 &account_pub);
    }
    if ( (0 == strcmp (method,
                       MHD_HTTP_METHOD_POST)) &&
         (NULL == end) )
    {
      return AH_handler_policy_post (connection,
                                     hc,
                                     &account_pub,
                                     upload_data,
                                     upload_data_size);
    }
    if (0 == strcmp (method,
                     MHD_HTTP_METHOD_OPTIONS))
    {
      return TALER_MHD_reply_cors_preflight (connection);
    }
    return TMH_MHD_handler_static_response (&h405,
                                            connection);
  }
  if (0 == strncmp (url,
                    "/truth/",
                    strlen ("/truth/")))
  {
    struct ANASTASIS_CRYPTO_TruthUUIDP tu;
    const char *pub_key_str;
    const char *end;
    size_t len;

    pub_key_str = &url[strlen ("/truth/")];
    end = strchr (pub_key_str,
                  '/');
    if (NULL == end)
      len = strlen (pub_key_str);
    else
      len = end - pub_key_str;
    if (GNUNET_OK !=
        GNUNET_STRINGS_string_to_data (
          pub_key_str,
          len,
          &tu,
          sizeof(tu)))
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                         "truth UUID");
    }
    if ( (NULL != end) &&
         (0 != strcmp (end, "/solve")) &&
         (0 != strcmp (end, "/challenge")) )
      return TMH_MHD_handler_static_response (&h404,
                                              connection);
    if (0 == strcmp (method,
                     MHD_HTTP_METHOD_OPTIONS))
      return TALER_MHD_reply_cors_preflight (connection);
    if (0 != strcmp (method,
                     MHD_HTTP_METHOD_POST))
      return TMH_MHD_handler_static_response (&h405,
                                              connection);
    if (NULL == end)
    {
      return AH_handler_truth_post (connection,
                                    hc,
                                    &tu,
                                    upload_data,
                                    upload_data_size);
    }
    if (0 == strcmp (end,
                     "/solve"))
    {
      return AH_handler_truth_solve (connection,
                                     hc,
                                     &tu,
                                     upload_data,
                                     upload_data_size);
    }
    if (0 == strcmp (end,
                     "/challenge"))
    {
      return AH_handler_truth_challenge (connection,
                                         hc,
                                         &tu,
                                         upload_data,
                                         upload_data_size);
    }
    /* should be impossible to get here */
    GNUNET_assert (0);
  } /* end of "/truth/" prefix */
  path_matched = false;
  for (unsigned int i = 0; NULL != handlers[i].url; i++)
  {
    struct AH_RequestHandler *rh = &handlers[i];

    if (0 == strcmp (url,
                     rh->url))
    {
      path_matched = true;
      if (0 == strcasecmp (method,
                           MHD_HTTP_METHOD_OPTIONS))
      {
        return TALER_MHD_reply_cors_preflight (connection);
      }
      if ( (NULL == rh->method) ||
           (0 == strcasecmp (method,
                             rh->method)) )
      {
        return rh->handler (rh,
                            connection);
      }
    }
  }
  if (path_matched)
    return TMH_MHD_handler_static_response (&h405,
                                            connection);
  return TMH_MHD_handler_static_response (&h404,
                                          connection);
}


/**
 * Shutdown task (magically invoked when the application is being
 * quit)
 *
 * @param cls NULL
 */
static void
do_shutdown (void *cls)
{
  (void) cls;
  TALER_MHD_daemons_halt ();
  AH_resume_all_bc ();
  AH_truth_challenge_shutdown ();
  AH_truth_solve_shutdown ();
  AH_truth_upload_shutdown ();
  TALER_MHD_daemons_destroy ();
  if (NULL != AH_ctx)
  {
    GNUNET_CURL_fini (AH_ctx);
    AH_ctx = NULL;
  }
  if (NULL != rc)
  {
    GNUNET_CURL_gnunet_rc_destroy (rc);
    rc = NULL;
  }
  ANASTASIS_DB_fini ();
  if (NULL != AH_to_heap)
  {
    GNUNET_CONTAINER_heap_destroy (AH_to_heap);
    AH_to_heap = NULL;
  }
  TALER_amount_list_free (&AH_annual_fees);
  TALER_amount_list_free (&AH_truth_upload_fees);
  TALER_amount_list_free (&AH_insurance);
  TALER_amount_list_free (&AH_question_costs);
  for (unsigned int i = 0; i<AH_currencies_len; i++)
    GNUNET_free (AH_currencies[i]);
  GNUNET_array_grow (AH_currencies,
                     AH_currencies_len,
                     0);
}


json_t *
AH_make_order (const char *order_id,
               const char *summary,
               const struct TALER_AmountList *prices)
{
  json_t *choices;
  json_t *order;

  GNUNET_assert (0 != prices->tal_len);
  choices = json_array ();
  GNUNET_assert (NULL != choices);
  for (unsigned int i = 0; i<prices->tal_len; i++)
  {
    json_t *choice;

    GNUNET_assert (! TALER_amount_is_zero (&prices->tal[i]));
    choice = GNUNET_JSON_PACK (
      TALER_JSON_pack_amount ("amount",
                              &prices->tal[i]),
      GNUNET_JSON_pack_string ("description",
                               summary));
    GNUNET_assert (0 ==
                   json_array_append_new (choices,
                                          choice));
  }
  order = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_uint64 ("version",
                             1),
    GNUNET_JSON_pack_string ("order_id",
                             order_id),
    GNUNET_JSON_pack_string ("summary",
                             summary),
    GNUNET_JSON_pack_array_steal ("choices",
                                  choices));
  return order;
}


enum GNUNET_GenericReturnValue
AH_paid_amount (const struct TALER_MERCHANT_GetPrivateOrderResponse *osr,
                struct TALER_Amount *amount)
{
  const json_t *contract = osr->details.ok.details.paid.contract_terms;
  int ci = osr->details.ok.details.paid.choice_index;
  const json_t *amount_obj;
  struct GNUNET_JSON_Specification cspec[] = {
    TALER_JSON_spec_amount_any ("amount",
                                amount),
    GNUNET_JSON_spec_end ()
  };

  amount_obj = (0 > ci)
    ? contract    /* legacy "v0" order, made before the upgrade */
    : json_array_get (json_object_get (contract,
                                       "choices"),
                      ci);
  if (NULL == amount_obj)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_JSON_parse (amount_obj,
                            cspec,
                            NULL, NULL);
}


const struct TALER_Amount *
AH_primary_price (const struct TALER_AmountList *al)
{
  static struct TALER_Amount zero;
  const struct TALER_Amount *a;

  GNUNET_assert (0 < AH_currencies_len);
  a = TALER_amount_list_find (al,
                              AH_currencies[0]);
  if (NULL != a)
    return a;
  /* The option is free.  Report zero in the primary currency, which is
     exactly what a single-currency provider running for free emitted
     before there were price lists. */
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (AH_currencies[0],
                                        &zero));
  return &zero;
}


enum GNUNET_GenericReturnValue
AH_check_price (const struct TALER_AmountList *al,
                const char *section,
                const char *option)
{
  switch (TALER_amount_list_check_uniform (al))
  {
  case GNUNET_SYSERR:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Price list error: option `%s' in section `%s' is `%s',"
                " which prices some currencies at zero and others above"
                " zero.  Either all of them are zero (the option is free)"
                " or none is.\n",
                option,
                section,
                TALER_amount_list2s (al));
    return GNUNET_SYSERR;
  case GNUNET_NO:
    /* Free is a first-class answer, and exempt from the coverage check
       below: an operator must be able to say "free" without having to
       enumerate every currency to do it. */
    return GNUNET_NO;
  case GNUNET_OK:
    break;
  }
  if (! TALER_amount_list_covers (al,
                                  (const char *const *) AH_currencies,
                                  AH_currencies_len))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Price list error: option `%s' in section `%s' is `%s',"
                " which does not price in exactly the currencies given in"
                " [anastasis] CURRENCIES.\n",
                option,
                section,
                TALER_amount_list2s (al));
    /* Naming the missing currency is what makes this actionable: the
       failure mode it guards against is an operator adding a currency
       and overlooking one of the many COST options. */
    for (unsigned int i = 0; i<AH_currencies_len; i++)
      if (NULL ==
          TALER_amount_list_find (al,
                                  AH_currencies[i]))
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "No price given for currency `%s' in option `%s' of"
                    " section `%s'\n",
                    AH_currencies[i],
                    option,
                    section);
    for (unsigned int i = 0; i<al->tal_len; i++)
    {
      bool found = false;

      for (unsigned int j = 0; j<AH_currencies_len; j++)
        if (0 == strcasecmp (al->tal[i].currency,
                             AH_currencies[j]))
          found = true;
      if (! found)
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Currency `%s' is priced in option `%s' of section"
                    " `%s' but is not in [anastasis] CURRENCIES\n",
                    al->tal[i].currency,
                    option,
                    section);
    }
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Closure for #check_method_cost_cb().
 */
struct MethodCostContext
{
  /**
   * Set to true if some enabled authorization method is misconfigured.
   */
  bool failure;
};


/**
 * Validate the COST of one enabled authorization method.
 *
 * The plugins themselves are loaded lazily, on the first ``/config``
 * request, so waiting for the loader to notice a bad COST would mean the
 * provider starts and only fails once a client asks --- by which time the
 * operator is no longer watching.  The configuration file can be checked
 * without loading anything, so it is checked here.
 *
 * @param cls a `struct MethodCostContext *`
 * @param section configuration section to inspect
 */
static void
check_method_cost_cb (void *cls,
                      const char *section)
{
  struct MethodCostContext *mcc = cls;
  struct TALER_AmountList costs;

  if (0 != strncasecmp (section,
                        "authorization-",
                        strlen ("authorization-")))
    return;
  if (GNUNET_YES !=
      GNUNET_CONFIGURATION_get_value_yesno (AH_cfg,
                                            section,
                                            "ENABLED"))
    return;
  /* An absent COST is an error, not "free": a typo in the option name
     must not silently give the method away.  Free is spelled with an
     empty value, or with a zero in every currency. */
  if (GNUNET_OK !=
      TALER_config_get_amount_list (AH_cfg,
                                    section,
                                    "COST",
                                    &costs))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               section,
                               "COST");
    mcc->failure = true;
    return;
  }
  if (GNUNET_SYSERR ==
      AH_check_price (&costs,
                      section,
                      "COST"))
    mcc->failure = true;
  TALER_amount_list_free (&costs);
}


/**
 * Determine the currencies this provider prices its service in.
 *
 * Normally these are given explicitly in [anastasis] CURRENCIES, primary
 * currency first.  A configuration written before multi-currency support
 * has no such option; for those, the single currency the provider was
 * already using is derived from the first priced option, which keeps every
 * existing configuration file working unchanged.  Any disagreement between
 * the options is then caught by the per-option coverage check, which can
 * name the offending option.
 *
 * @param cfg configuration to use
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_currencies (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  char *str;

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (cfg,
                                             "anastasis",
                                             "CURRENCIES",
                                             &str))
  {
    const struct TALER_AmountList *als[] = {
      &AH_annual_fees,
      &AH_truth_upload_fees,
      &AH_insurance,
      &AH_question_costs
    };

    /* A priced option names the currency authoritatively; failing that,
       an option that is free but written out as e.g. "EUR:0" still names
       one, which is how a provider running entirely for free is
       configured today. */
    for (unsigned int pass = 0; pass < 2; pass++)
      for (unsigned int i = 0; i<sizeof (als) / sizeof (als[0]); i++)
      {
        if ( (0 == pass) &&
             (GNUNET_OK !=
              TALER_amount_list_check_uniform (als[i])) )
          continue;
        if (0 == als[i]->tal_len)
          continue;
        if (1 != als[i]->tal_len)
        {
          GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                     "anastasis",
                                     "CURRENCIES");
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "A provider pricing in more than one currency must say so explicitly.\n");
          return GNUNET_SYSERR;
        }
        GNUNET_array_append (AH_currencies,
                             AH_currencies_len,
                             GNUNET_strdup (als[i]->tal[0].currency));
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "[anastasis] CURRENCIES not configured, assuming `%s'\n",
                    AH_currencies[0]);
        return GNUNET_OK;
      }
    /* Not one option names a currency, so there is nothing to derive
       from and the legacy scalar fields of /config would have no
       currency to report either. */
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "anastasis",
                               "CURRENCIES");
    return GNUNET_SYSERR;
  }
  for (const char *tok = strtok (str, ";");
       NULL != tok;
       tok = strtok (NULL, ";"))
  {
    if (GNUNET_OK !=
        TALER_check_currency (tok))
    {
      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                 "anastasis",
                                 "CURRENCIES",
                                 tok);
      GNUNET_free (str);
      return GNUNET_SYSERR;
    }
    for (unsigned int i = 0; i<AH_currencies_len; i++)
      if (0 == strcasecmp (tok,
                           AH_currencies[i]))
      {
        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                   "anastasis",
                                   "CURRENCIES",
                                   "currency given more than once");
        GNUNET_free (str);
        return GNUNET_SYSERR;
      }
    GNUNET_array_append (AH_currencies,
                         AH_currencies_len,
                         GNUNET_strdup (tok));
  }
  GNUNET_free (str);
  if (0 == AH_currencies_len)
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               "anastasis",
                               "CURRENCIES",
                               "no currency given");
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Parse and validate everything this provider charges for.
 *
 * @param cfg configuration to use
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_prices (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  static const struct
  {
    const char *section;
    const char *option;
    struct TALER_AmountList *al;
  } prices[] = {
    { "anastasis", "INSURANCE", &AH_insurance },
    { "authorization-question", "COST", &AH_question_costs },
    { "anastasis", "ANNUAL_FEE", &AH_annual_fees },
    { "anastasis", "TRUTH_UPLOAD_FEE", &AH_truth_upload_fees }
  };
  struct MethodCostContext mcc = {
    .failure = false
  };

  for (unsigned int i = 0; i<sizeof (prices) / sizeof (prices[0]); i++)
  {
    if (GNUNET_OK !=
        TALER_config_get_amount_list (cfg,
                                      prices[i].section,
                                      prices[i].option,
                                      prices[i].al))
    {
      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                 prices[i].section,
                                 prices[i].option);
      return GNUNET_SYSERR;
    }
  }
  /* The currency list has to exist before anything can be checked
     against it, but deriving it from the prices needs them parsed. */
  if (GNUNET_OK !=
      parse_currencies (cfg))
    return GNUNET_SYSERR;
  for (unsigned int i = 0; i<sizeof (prices) / sizeof (prices[0]); i++)
    if (GNUNET_SYSERR ==
        AH_check_price (prices[i].al,
                        prices[i].section,
                        prices[i].option))
      return GNUNET_SYSERR;
  GNUNET_CONFIGURATION_iterate_sections (cfg,
                                         &check_method_cost_cb,
                                         &mcc);
  if (mcc.failure)
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


/**
 * 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 TM_HandlerContext *hc = *con_cls;
  struct GNUNET_AsyncScopeSave old_scope;

  (void) cls;
  (void) connection;
  if (NULL == hc)
    return;
  GNUNET_async_scope_enter (&hc->async_scope_id,
                            &old_scope);
  {
#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,
                "Request for `%s' completed (%d)\n",
                hc->url,
                toe);
#endif
  }
  if (NULL != hc->cc)
    hc->cc (hc);
  GNUNET_free (hc);
  *con_cls = 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_DUAL_STACK,
                          0 /* port */,
                          NULL, NULL,
                          &url_handler, NULL,
                          MHD_OPTION_LISTEN_SOCKET, lsock,
                          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;

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Starting anastasis-httpd\n");
  go = TALER_MHD_GO_NONE;
  if (AH_connection_close)
    go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
  AH_load_terms (config);
  TALER_MHD_setup (go);
  AH_cfg = config;
  global_result = EXIT_NO_RESTART;
  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
                                 NULL);
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_number (config,
                                             "anastasis",
                                             "UPLOAD_LIMIT_MB",
                                             &AH_upload_limit_mb))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "anastasis",
                               "UPLOAD_LIMIT_MB");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      parse_prices (config))
  {
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (config,
                                             "anastasis-merchant-backend",
                                             "PAYMENT_BACKEND_URL",
                                             &AH_backend_url))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "anastasis-merchant-backend",
                               "PAYMENT_BACKEND_URL");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if ( (0 != strncasecmp ("https://",
                          AH_backend_url,
                          strlen ("https://"))) &&
       (0 != strncasecmp ("http://",
                          AH_backend_url,
                          strlen ("http://"))) )
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               "anastasis-merchant-backend",
                               "PAYMENT_BACKEND_URL",
                               "Must be HTTP(S) URL");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  if ( (0 == strcasecmp ("https://",
                         AH_backend_url)) ||
       (0 == strcasecmp ("http://",
                         AH_backend_url)) )
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               "anastasis-merchant-backend",
                               "PAYMENT_BACKEND_URL",
                               "Must have domain name");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (config,
                                             "anastasis",
                                             "FULFILLMENT_URL",
                                             &AH_fulfillment_url))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "anastasis",
                               "FULFILLMENT_URL");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_number (config,
                                             "anastasis",
                                             "ANNUAL_POLICY_UPLOAD_LIMIT",
                                             &AH_post_counter))
  {
    /* only warn, we will use the default */
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
                               "anastasis",
                               "ANNUAL_POLICY_UPLOAD_LIMIT");
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (config,
                                             "anastasis",
                                             "BUSINESS_NAME",
                                             &AH_business_name))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "anastasis",
                               "BUSINESS_NAME");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  {
    char *provider_salt;

    if (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_string (config,
                                               "anastasis",
                                               "PROVIDER_SALT",
                                               &provider_salt))
    {
      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                 "anastasis",
                                 "PROVIDER_SALT");
      GNUNET_SCHEDULER_shutdown ();
      return;
    }
    GNUNET_assert (GNUNET_YES ==
                   GNUNET_CRYPTO_hkdf_gnunet (&AH_provider_salt,
                                              sizeof (AH_provider_salt),
                                              "anastasis-provider-salt",
                                              strlen ("anastasis-provider-salt")
                                              ,
                                              provider_salt,
                                              strlen (provider_salt)));
    GNUNET_free (provider_salt);
  }

  /* setup HTTP client event loop */
  AH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
                             &rc);
  rc = GNUNET_CURL_gnunet_rc_create (AH_ctx);
  if (NULL != userpass)
    GNUNET_CURL_set_userpass (AH_ctx,
                              userpass);
  if (NULL != keyfile)
    GNUNET_CURL_set_tlscert (AH_ctx,
                             certtype,
                             certfile,
                             keyfile,
                             keypass);
  if (NULL == apikey)
  {
    (void) GNUNET_CONFIGURATION_get_value_string (config,
                                                  "anastasis-merchant-backend",
                                                  "API_KEY",
                                                  &apikey);
  }
  if (NULL != apikey)
  {
    char *auth_header;

    GNUNET_asprintf (&auth_header,
                     "%s: %s",
                     MHD_HTTP_HEADER_AUTHORIZATION,
                     apikey);
    if (GNUNET_OK !=
        GNUNET_CURL_append_header (AH_ctx,
                                   auth_header))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed so set %s header, trying without\n",
                  MHD_HTTP_HEADER_AUTHORIZATION);
    }
    GNUNET_free (auth_header);
  }

  if (GNUNET_OK !=
      ANASTASIS_DB_init (config))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Database not setup. Did you run anastasis-dbinit?\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  {
    enum GNUNET_GenericReturnValue ret;

    ret = TALER_MHD_listen_bind (config,
                                 "anastasis",
                                 &start_daemon,
                                 NULL);
    switch (ret)
    {
    case GNUNET_SYSERR:
      global_result = EXIT_NOTCONFIGURED;
      GNUNET_SCHEDULER_shutdown ();
      return;
    case GNUNET_NO:
      if (! have_daemons)
      {
        global_result = 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_result = 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, 1 on error
 */
int
main (int argc,
      char *const *argv)
{
  enum GNUNET_GenericReturnValue res;
  struct GNUNET_GETOPT_CommandLineOption options[] = {
    GNUNET_GETOPT_option_string ('A',
                                 "auth",
                                 "USERNAME:PASSWORD",
                                 "use the given USERNAME and PASSWORD for client authentication",
                                 &userpass),
    GNUNET_GETOPT_option_flag ('C',
                               "connection-close",
                               "force HTTP connections to be closed after each request",
                               &AH_connection_close),
    GNUNET_GETOPT_option_string ('k',
                                 "key",
                                 "KEYFILE",
                                 "file with the private TLS key for TLS client authentication",
                                 &keyfile),
    GNUNET_GETOPT_option_string ('p',
                                 "pass",
                                 "KEYFILEPASSPHRASE",
                                 "passphrase needed to decrypt the TLS client private key file",
                                 &keypass),
    GNUNET_GETOPT_option_string ('K',
                                 "apikey",
                                 "APIKEY",
                                 "API key to use in the HTTP request to the merchant backend",
                                 &apikey),
    GNUNET_GETOPT_option_string ('t',
                                 "type",
                                 "CERTTYPE",
                                 "type of the TLS client certificate, defaults to PEM if not specified",
                                 &certtype),
    GNUNET_GETOPT_OPTION_END
  };

  res = GNUNET_PROGRAM_run (ANASTASIS_project_data (),
                            argc, argv,
                            "anastasis-httpd",
                            "Anastasis HTTP interface",
                            options, &run, NULL);
  if (GNUNET_SYSERR == res)
    return 3;
  if (GNUNET_NO == res)
    return 0;
  return global_result;
}
