/*
  This file is part of Anastasis
  Copyright (C) 2019-2022 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 anastasis-httpd_truth-solve.c
 * @brief functions to handle incoming requests on /truth/$TID/solve
 * @author Dennis Neufeld
 * @author Dominik Meister
 * @author Christian Grothoff
 */
#include "platform.h"
struct SolveContext;
#define TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE struct SolveContext
#define TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE struct SolveContext
#include "anastasis-httpd.h"
#include "anastasis_service.h"
#include "anastasis-httpd_truth.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_rest_lib.h>
#include "anastasis_authorization_lib.h"
#include <taler/taler_merchant_service.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_mhd_lib.h>
#include <taler/merchant/post-private-orders.h>
#include <taler/merchant/get-private-orders-ORDER_ID.h>

/**
 * What is the maximum frequency at which we allow
 * clients to attempt to answer security questions?
 */
#define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \
          GNUNET_TIME_UNIT_SECONDS, 30)

/**
 * How long should the wallet check for auto-refunds before giving up?
 */
#define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \
          GNUNET_TIME_UNIT_MINUTES, 2)


/**
 * How many retries do we allow per code?
 */
#define INITIAL_RETRY_COUNTER 3


struct SolveContext
{

  /**
   * Payment Identifier
   */
  struct ANASTASIS_PaymentSecretP payment_identifier;

  /**
   * Public key of the challenge which is solved.
   */
  struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;

  /**
   * Key to decrypt the truth.
   */
  struct ANASTASIS_CRYPTO_TruthKeyP truth_key;

  /**
   * Cost for paying the challenge, one entry per currency the wallet may
   * settle in.  Borrowed from the authorization plugin or from
   * #AH_question_costs, both of which outlive the request.
   */
  const struct TALER_AmountList *challenge_costs;

  /**
   * What the wallet actually paid, in the currency it chose.  Only
   * meaningful if @e have_payment is true.
   */
  struct TALER_Amount challenge_paid;

  /**
   * Our handler context.
   */
  struct TM_HandlerContext *hc;

  /**
   * Opaque parsing context.
   */
  void *opaque_post_parsing_context;

  /**
   * Uploaded JSON data, NULL if upload is not yet complete.
   */
  json_t *root;

  /**
   * Kept in DLL for shutdown handling while suspended.
   */
  struct SolveContext *next;

  /**
   * Kept in DLL for shutdown handling while suspended.
   */
  struct SolveContext *prev;

  /**
   * Connection handle for closing or resuming
   */
  struct MHD_Connection *connection;

  /**
   * Reference to the authorization plugin which was loaded
   */
  struct ANASTASIS_AuthorizationPlugin *authorization;

  /**
   * Status of the authorization
   */
  struct ANASTASIS_AUTHORIZATION_State *as;

  /**
   * Used while we are awaiting proposal creation.
   */
  struct TALER_MERCHANT_PostPrivateOrdersHandle *po;

  /**
   * Used while we are waiting payment.
   */
  struct TALER_MERCHANT_GetPrivateOrderHandle *cpo;

  /**
   * HTTP response code to use on resume, if non-NULL.
   */
  struct MHD_Response *resp;

  /**
   * Our entry in the #to_heap, or NULL.
   */
  struct GNUNET_CONTAINER_HeapNode *hn;

  /**
   * Challenge response we got from the request.
   */
  struct GNUNET_HashCode challenge_response;

  /**
   * How long do we wait at most for payment or
   * authorization?
   */
  struct GNUNET_TIME_Absolute timeout;

  /**
   * Random authorization code we are using.
   */
  uint64_t code;

  /**
   * HTTP response code to use on resume, if resp is set.
   */
  unsigned int response_code;

  /**
   * true if client did not provide a payment secret / order ID.
   */
  bool no_payment_identifier_provided;

  /**
   * True if @e challenge_paid holds what the wallet paid for this
   * challenge, as read back from the database.
   */
  bool have_payment;

  /**
   * True if this entry is in the #gc_head DLL.
   */
  bool in_list;

  /**
   * True if this entry is currently suspended.
   */
  bool suspended;

};


/**
 * Head of linked list over all authorization processes
 */
static struct SolveContext *gc_head;

/**
 * Tail of linked list over all authorization processes
 */
static struct SolveContext *gc_tail;

/**
 * Task running #do_timeout().
 */
static struct GNUNET_SCHEDULER_Task *to_task;


/**
 * Generate a response telling the client that answering this
 * challenge failed because the rate limit has been exceeded.
 *
 * @param gc request to answer for
 * @return MHD status code
 */
static enum MHD_Result
reply_rate_limited (const struct SolveContext *gc)
{
  if (NULL != gc->authorization)
    return TALER_MHD_REPLY_JSON_PACK (
      gc->connection,
      MHD_HTTP_TOO_MANY_REQUESTS,
      TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED),
      GNUNET_JSON_pack_uint64 ("request_limit",
                               gc->authorization->retry_counter),
      GNUNET_JSON_pack_time_rel ("request_frequency",
                                 gc->authorization->code_rotation_period));
  /* must be security question */
  return TALER_MHD_REPLY_JSON_PACK (
    gc->connection,
    MHD_HTTP_TOO_MANY_REQUESTS,
    TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED),
    GNUNET_JSON_pack_uint64 ("request_limit",
                             INITIAL_RETRY_COUNTER),
    GNUNET_JSON_pack_time_rel ("request_frequency",
                               MAX_QUESTION_FREQ));
}


/**
 * Timeout requests that are past their due date.
 *
 * @param cls NULL
 */
static void
do_timeout (void *cls)
{
  struct SolveContext *gc;

  (void) cls;
  to_task = NULL;
  while (NULL !=
         (gc = GNUNET_CONTAINER_heap_peek (AH_to_heap)))
  {
    if (GNUNET_TIME_absolute_is_future (gc->timeout))
      break;
    if (gc->suspended)
    {
      /* Test needed as we may have a "concurrent"
         wakeup from another task that did not clear
         this entry from the heap before the
         response process concluded. */
      gc->suspended = false;
      MHD_resume_connection (gc->connection);
    }
    GNUNET_assert (NULL != gc->hn);
    gc->hn = NULL;
    GNUNET_assert (gc ==
                   GNUNET_CONTAINER_heap_remove_root (AH_to_heap));
  }
  if (NULL == gc)
    return;
  to_task = GNUNET_SCHEDULER_add_at (gc->timeout,
                                     &do_timeout,
                                     NULL);
}


void
AH_truth_solve_shutdown (void)
{
  struct SolveContext *gc;

  while (NULL != (gc = gc_head))
  {
    GNUNET_CONTAINER_DLL_remove (gc_head,
                                 gc_tail,
                                 gc);
    gc->in_list = false;
    if (NULL != gc->cpo)
    {
      TALER_MERCHANT_get_private_order_cancel (gc->cpo);
      gc->cpo = NULL;
    }
    if (NULL != gc->po)
    {
      TALER_MERCHANT_post_private_orders_cancel (gc->po);
      gc->po = NULL;
    }
    if (gc->suspended)
    {
      gc->suspended = false;
      MHD_resume_connection (gc->connection);
    }
    if (NULL != gc->as)
    {
      gc->authorization->cleanup (gc->as);
      gc->as = NULL;
      gc->authorization = NULL;
    }
  }
  ANASTASIS_authorization_plugin_shutdown ();
  if (NULL != to_task)
  {
    GNUNET_SCHEDULER_cancel (to_task);
    to_task = NULL;
  }
}


/**
 * Callback used to notify the application about completed requests.
 * Cleans up the requests data structures.
 *
 * @param[in,out] hc
 */
static void
request_done (struct TM_HandlerContext *hc)
{
  struct SolveContext *gc = hc->ctx;

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Request completed\n");
  if (NULL == gc)
    return;
  hc->cc = NULL;
  GNUNET_assert (! gc->suspended);
  if (gc->in_list)
  {
    GNUNET_CONTAINER_DLL_remove (gc_head,
                                 gc_tail,
                                 gc);
    gc->in_list = false;
  }
  if (NULL != gc->hn)
  {
    GNUNET_assert (gc ==
                   GNUNET_CONTAINER_heap_remove_node (gc->hn));
    gc->hn = NULL;
  }
  if (NULL != gc->as)
  {
    gc->authorization->cleanup (gc->as);
    gc->authorization = NULL;
    gc->as = NULL;
  }
  if (NULL != gc->cpo)
  {
    TALER_MERCHANT_get_private_order_cancel (gc->cpo);
    gc->cpo = NULL;
  }
  if (NULL != gc->po)
  {
    TALER_MERCHANT_post_private_orders_cancel (gc->po);
    gc->po = NULL;
  }
  if (NULL != gc->root)
  {
    json_decref (gc->root);
    gc->root = NULL;
  }
  TALER_MHD_parse_post_cleanup_callback (gc->opaque_post_parsing_context);
  GNUNET_free (gc);
  hc->ctx = NULL;
}


/**
 * Transmit a payment request for @a order_id on @a connection
 *
 * @param gc context to make payment request for
 */
static void
make_payment_request (struct SolveContext *gc)
{
  struct MHD_Response *resp;

  resp = MHD_create_response_from_buffer (0,
                                          NULL,
                                          MHD_RESPMEM_PERSISTENT);
  GNUNET_assert (NULL != resp);
  TALER_MHD_add_global_headers (resp,
                                false);
  {
    char *hdr;
    char *order_id;
    const char *pfx;
    const char *hn;

    if (0 == strncasecmp ("https://",
                          AH_backend_url,
                          strlen ("https://")))
    {
      pfx = "taler://";
      hn = &AH_backend_url[strlen ("https://")];
    }
    else if (0 == strncasecmp ("http://",
                               AH_backend_url,
                               strlen ("http://")))
    {
      pfx = "taler+http://";
      hn = &AH_backend_url[strlen ("http://")];
    }
    else
    {
      /* This invariant holds as per check in anastasis-httpd.c */
      GNUNET_assert (0);
    }
    /* This invariant holds as per check in anastasis-httpd.c */
    GNUNET_assert (0 != strlen (hn));

    order_id = GNUNET_STRINGS_data_to_string_alloc (
      &gc->payment_identifier,
      sizeof (gc->payment_identifier));
    GNUNET_asprintf (&hdr,
                     "%spay/%s%s/",
                     pfx,
                     hn,
                     order_id);
    GNUNET_free (order_id);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Sending payment request `%s'\n",
                hdr);
    GNUNET_break (MHD_YES ==
                  MHD_add_response_header (resp,
                                           ANASTASIS_HTTP_HEADER_TALER,
                                           hdr));
    GNUNET_free (hdr);
  }
  gc->resp = resp;
  gc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
}


/**
 * Callbacks of this type are used to serve the result of submitting a
 * /contract request to a merchant.
 *
 * @param cls our `struct SolveContext`
 * @param por response details
 */
static void
proposal_cb (struct SolveContext *gc,
             const struct TALER_MERCHANT_PostPrivateOrdersResponse *por)
{
  enum GNUNET_DB_QueryStatus qs;

  gc->po = NULL;
  GNUNET_assert (gc->in_list);
  GNUNET_CONTAINER_DLL_remove (gc_head,
                               gc_tail,
                               gc);
  gc->in_list = false;
  GNUNET_assert (gc->suspended);
  gc->suspended = false;
  MHD_resume_connection (gc->connection);
  AH_trigger_daemon (NULL);
  if (MHD_HTTP_OK != por->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Backend returned status %u/%d\n",
                por->hr.http_status,
                (int) por->hr.ec);
    GNUNET_break (0);
    gc->resp = TALER_MHD_MAKE_JSON_PACK (
      GNUNET_JSON_pack_uint64 ("code",
                               TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR),
      GNUNET_JSON_pack_string ("hint",
                               "Failed to setup order with merchant backend"),
      GNUNET_JSON_pack_uint64 ("backend-ec",
                               por->hr.ec),
      GNUNET_JSON_pack_uint64 ("backend-http-status",
                               por->hr.http_status),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_object_steal ("backend-reply",
                                       (json_t *) por->hr.reply)));
    gc->response_code = MHD_HTTP_BAD_GATEWAY;
    return;
  }
  /* Records the asking price in the primary currency; which currency the
     wallet settles in is only known once it has paid, and is written by
     ANASTASIS_DB_update_to_challenge_payment_paid() then. */
  qs = ANASTASIS_DB_insert_challenge_payment (
    &gc->truth_uuid,
    &gc->payment_identifier,
    AH_primary_price (gc->challenge_costs));
  if (0 >= qs)
  {
    GNUNET_break (0);
    gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
                                     "insert challenge payment");
    gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Setup fresh order, creating payment request\n");
  make_payment_request (gc);
}


/**
 * Callback to process a GET /check-payment request
 *
 * @param cls our `struct SolveContext`
 * @param osr order status
 */
static void
check_payment_cb (struct SolveContext *gc,
                  const struct TALER_MERCHANT_GetPrivateOrderResponse *osr)

{
  const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;

  gc->cpo = NULL;
  GNUNET_assert (gc->in_list);
  GNUNET_CONTAINER_DLL_remove (gc_head,
                               gc_tail,
                               gc);
  gc->in_list = false;
  GNUNET_assert (gc->suspended);
  gc->suspended = false;
  MHD_resume_connection (gc->connection);
  AH_trigger_daemon (NULL);

  switch (hr->http_status)
  {
  case MHD_HTTP_OK:
    GNUNET_assert (NULL != osr);
    break;
  case MHD_HTTP_NOT_FOUND:
    /* We created this order before, how can it be not found now? */
    GNUNET_break (0);
    gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED,
                                     NULL);
    gc->response_code = MHD_HTTP_BAD_GATEWAY;
    return;
  case MHD_HTTP_BAD_GATEWAY:
    gc->resp = TALER_MHD_make_error (
      TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD,
      NULL);
    gc->response_code = MHD_HTTP_BAD_GATEWAY;
    return;
  case MHD_HTTP_GATEWAY_TIMEOUT:
    gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT,
                                     "Timeout check payment status");
    GNUNET_assert (NULL != gc->resp);
    gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
    return;
  default:
    {
      char status[14];

      GNUNET_snprintf (status,
                       sizeof (status),
                       "%u",
                       hr->http_status);
      gc->resp = TALER_MHD_make_error (
        TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS,
        status);
      GNUNET_assert (NULL != gc->resp);
      gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
      return;
    }
  }

  GNUNET_assert (MHD_HTTP_OK == hr->http_status);
  switch (osr->details.ok.status)
  {
  case TALER_MERCHANT_OSC_PAID:
    {
      enum GNUNET_DB_QueryStatus qs;

      if (GNUNET_OK !=
          AH_paid_amount (osr,
                          &gc->challenge_paid))
      {
        GNUNET_break (0);
        gc->resp = TALER_MHD_make_error (
          TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
          "no amount given");
        gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
        return;
      }
      gc->have_payment = true;
      qs = ANASTASIS_DB_update_to_challenge_payment_paid (
        &gc->truth_uuid,
        &gc->payment_identifier,
        &gc->challenge_paid);
      if (0 <= qs)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Order has been paid, continuing with request processing\n")
        ;
        return; /* continue as planned */
      }
      GNUNET_break (0);
      gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
                                       "update to challenge payment paid");
      gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
      return; /* continue as planned */
    }
  case TALER_MERCHANT_OSC_CLAIMED:
  case TALER_MERCHANT_OSC_UNPAID:
    /* repeat payment request */
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Order remains unpaid, sending payment request again\n");
    make_payment_request (gc);
    return;
  }
  /* should never get here */
  GNUNET_break (0);
}


/**
 * Helper function used to ask our backend to begin processing a
 * payment for the user's account.  May perform asynchronous
 * operations by suspending the connection if required.
 *
 * @param gc context to begin payment for.
 * @return MHD status code
 */
static enum MHD_Result
begin_payment (struct SolveContext *gc)
{
  enum GNUNET_DB_QueryStatus qs;
  char *order_id;

  qs = ANASTASIS_DB_get_pending_challenge_payment (
    &gc->truth_uuid,
    &gc->payment_identifier);
  if (qs < 0)
  {
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (gc->connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
                                       "get pending challenge payment");
  }
  GNUNET_assert (! gc->in_list);
  gc->in_list = true;
  GNUNET_CONTAINER_DLL_insert (gc_head,
                               gc_tail,
                               gc);
  GNUNET_assert (! gc->suspended);
  gc->suspended = true;
  MHD_suspend_connection (gc->connection);
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
  {
    /* We already created the order, check if it was paid */
    struct GNUNET_TIME_Relative timeout;

    order_id = GNUNET_STRINGS_data_to_string_alloc (
      &gc->payment_identifier,
      sizeof (gc->payment_identifier));
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Order exists, checking payment status for order `%s'\n",
                order_id);
    timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout);
    gc->cpo = TALER_MERCHANT_get_private_order_create (AH_ctx,
                                                       AH_backend_url,
                                                       order_id);
    GNUNET_assert (NULL != gc->cpo);
    GNUNET_assert (
      GNUNET_OK ==
      TALER_MERCHANT_get_private_order_set_options (
        gc->cpo,
        TALER_MERCHANT_get_private_order_option_timeout (timeout)));
    GNUNET_assert (TALER_EC_NONE ==
                   TALER_MERCHANT_get_private_order_start (gc->cpo,
                                                           &check_payment_cb,
                                                           gc));
  }
  else
  {
    /* Create a fresh order */
    struct GNUNET_TIME_Timestamp pay_deadline;

    GNUNET_CRYPTO_random_block (&gc->payment_identifier,
                                sizeof (struct ANASTASIS_PaymentSecretP));
    order_id = GNUNET_STRINGS_data_to_string_alloc (
      &gc->payment_identifier,
      sizeof (gc->payment_identifier));
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Creating fresh order `%s'\n",
                order_id);
    pay_deadline = GNUNET_TIME_relative_to_timestamp (
      ANASTASIS_CHALLENGE_OFFER_LIFETIME);
    {
      json_t *order;

      order = AH_make_order (order_id,
                             "challenge fee for anastasis service",
                             gc->challenge_costs);
      GNUNET_assert (0 ==
                     json_object_update_new (
                       order,
                       GNUNET_JSON_PACK (
                         GNUNET_JSON_pack_time_rel ("auto_refund",
                                                    AUTO_REFUND_TIMEOUT),
                         GNUNET_JSON_pack_timestamp ("pay_deadline",
                                                     pay_deadline))));
      gc->po = TALER_MERCHANT_post_private_orders_create (AH_ctx,
                                                          AH_backend_url,
                                                          order);
      json_decref (order);
    }
    GNUNET_assert (
      GNUNET_OK ==
      TALER_MERCHANT_post_private_orders_set_options (
        gc->po,
        TALER_MERCHANT_post_private_orders_option_create_token (false),
        TALER_MERCHANT_post_private_orders_option_refund_delay (
          AUTO_REFUND_TIMEOUT)));
    GNUNET_assert (TALER_EC_NONE ==
                   TALER_MERCHANT_post_private_orders_start (gc->po,
                                                             &proposal_cb,
                                                             gc));
  }
  GNUNET_free (order_id);
  AH_trigger_curl ();
  return MHD_YES;
}


/**
 * Load encrypted keyshare from db and return it to the client.
 *
 * @param truth_uuid UUID to the truth for the looup
 * @param connection the connection to respond upon
 * @return MHD status code
 */
static enum MHD_Result
return_key_share (
  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
  struct MHD_Connection *connection)
{
  struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare;

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Returning key share of %s\n",
              TALER_B2S (truth_uuid));
  {
    enum GNUNET_DB_QueryStatus qs;

    qs = ANASTASIS_DB_get_truth_key_share (
      truth_uuid,
      &encrypted_keyshare);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
                                         "get key share");
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      /* this should be "impossible", after all the
         client was able to solve the challenge!
         (Exception: we deleted the truth via GC
         just while the client was trying to recover.
         Alas, highly unlikely...) */
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_NOT_FOUND,
                                         TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE,
                                         NULL);
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    }
  }

  {
    struct MHD_Response *resp;
    enum MHD_Result ret;

    resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare),
                                            &encrypted_keyshare,
                                            MHD_RESPMEM_MUST_COPY);
    TALER_MHD_add_global_headers (resp,
                                  false);
    ret = MHD_queue_response (connection,
                              MHD_HTTP_OK,
                              resp);
    MHD_destroy_response (resp);
    return ret;
  }
}


/**
 * Mark @a gc as suspended and update the respective
 * data structures and jobs.
 *
 * @param[in,out] gc context of the suspended operation
 */
static void
gc_suspended (struct SolveContext *gc)
{
  GNUNET_assert (NULL == gc->hn);
  GNUNET_assert (! gc->suspended);
  gc->suspended = true;
  if (NULL == AH_to_heap)
    AH_to_heap = GNUNET_CONTAINER_heap_create (
      GNUNET_CONTAINER_HEAP_ORDER_MIN);
  gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap,
                                         gc,
                                         gc->timeout.abs_value_us);
  if (NULL != to_task)
  {
    GNUNET_SCHEDULER_cancel (to_task);
    to_task = NULL;
  }
  {
    struct SolveContext *rn;

    rn = GNUNET_CONTAINER_heap_peek (AH_to_heap);
    to_task = GNUNET_SCHEDULER_add_at (rn->timeout,
                                       &do_timeout,
                                       NULL);
  }
}


/**
 * Run the authorization method-specific 'process' function and continue
 * based on its result with generating an HTTP response.
 *
 * @param connection the connection we are handling
 * @param gc our overall handler context
 */
static enum MHD_Result
run_authorization_process (struct MHD_Connection *connection,
                           struct SolveContext *gc)
{
  enum ANASTASIS_AUTHORIZATION_SolveResult ret;

  GNUNET_assert (! gc->suspended);
  if (NULL == gc->authorization->solve)
  {
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (gc->connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
                                       "solve method not implemented for authorization method");
  }
  ret = gc->authorization->solve (gc->as,
                                  gc->timeout,
                                  &gc->challenge_response,
                                  connection);
  switch (ret)
  {
  case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED:
    /* connection was suspended */
    gc_suspended (gc);
    return MHD_YES;
  case ANASTASIS_AUTHORIZATION_SRES_FAILED:
    gc->authorization->cleanup (gc->as);
    gc->as = NULL;
    return MHD_YES;
  case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED:
    gc->authorization->cleanup (gc->as);
    gc->as = NULL;
    return MHD_NO;
  case ANASTASIS_AUTHORIZATION_SRES_FINISHED:
    GNUNET_assert (! gc->suspended);
    gc->authorization->cleanup (gc->as);
    gc->as = NULL;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Resuming with authorization successful!\n");
    if (gc->in_list)
    {
      GNUNET_CONTAINER_DLL_remove (gc_head,
                                   gc_tail,
                                   gc);
      gc->in_list = false;
    }
    return MHD_YES;
  }
  GNUNET_break (0);
  return MHD_NO;
}


/**
 * Use the database to rate-limit queries to the authentication
 * procedure, but without actually storing 'real' challenge codes.
 *
 * @param[in,out] gc context to rate limit requests for
 * @return #GNUNET_OK if rate-limiting passes,
 *         #GNUNET_NO if a reply was sent (rate limited)
 *         #GNUNET_SYSERR if we failed and no reply
 *                        was queued
 */
static enum GNUNET_GenericReturnValue
rate_limit (struct SolveContext *gc)
{
  enum GNUNET_DB_QueryStatus qs;
  struct GNUNET_TIME_Timestamp rt;
  uint64_t code;
  enum ANASTASIS_DB_CodeStatus cs;
  struct GNUNET_HashCode hc;
  bool satisfied;
  uint64_t dummy;

  rt = GNUNET_TIME_UNIT_FOREVER_TS;
  qs = ANASTASIS_DB_insert_challenge_code (
    &gc->truth_uuid,
    MAX_QUESTION_FREQ,
    GNUNET_TIME_UNIT_HOURS,
    INITIAL_RETRY_COUNTER,
    &rt,
    &code);
  if (0 > qs)
  {
    GNUNET_break (0 < qs);
    return (MHD_YES ==
            TALER_MHD_reply_with_error (gc->connection,
                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                                        "insert_challenge_code (for rate limiting)"))
           ? GNUNET_NO
           : GNUNET_SYSERR;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
  {
    return (MHD_YES ==
            reply_rate_limited (gc))
           ? GNUNET_NO
           : GNUNET_SYSERR;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Using intentionally wrong answer to produce rate-limiting\n");
  /* decrement trial counter */
  ANASTASIS_hash_answer (code + 1,      /* always use wrong answer */
                         &hc);
  cs = ANASTASIS_DB_do_verify_challenge_code (
    &gc->truth_uuid,
    &hc,
    &dummy,
    &satisfied);
  switch (cs)
  {
  case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
    /* good, what we wanted */
    return GNUNET_OK;
  case ANASTASIS_DB_CODE_STATUS_RATE_LIMITED:
    /* the code we just obtained was already out of attempts */
    return (MHD_YES ==
            reply_rate_limited (gc))
           ? GNUNET_NO
           : GNUNET_SYSERR;
  case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
  case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
    GNUNET_break (0);
    return (MHD_YES ==
            TALER_MHD_reply_with_error (gc->connection,
                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                                        "do_verify_challenge_code"))
           ? GNUNET_NO
           : GNUNET_SYSERR;
  case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
    return (MHD_YES ==
            reply_rate_limited (gc))
           ? GNUNET_NO
           : GNUNET_SYSERR;
  case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
    /* this should be impossible, we used code+1 */
    GNUNET_assert (0);
  }
  return GNUNET_SYSERR;
}


/**
 * Handle special case of a security question where we do not
 * generate a code. Rate limits answers against brute forcing.
 *
 * @param[in,out] gc request to handle
 * @param decrypted_truth hash to check against
 * @param decrypted_truth_size number of bytes in @a decrypted_truth
 * @return MHD status code
 */
static enum MHD_Result
handle_security_question (struct SolveContext *gc,
                          const void *decrypted_truth,
                          size_t decrypted_truth_size)
{
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Handling security question challenge\n");
  /* rate limit */
  {
    enum GNUNET_GenericReturnValue ret;

    ret = rate_limit (gc);
    if (GNUNET_OK != ret)
      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
  }
  /* check reply matches truth */
  if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) ||
       (0 != memcmp (&gc->challenge_response,
                     decrypted_truth,
                     decrypted_truth_size)) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Wrong answer provided to secure question had %u bytes, wanted %u\n",
                (unsigned int) decrypted_truth_size,
                (unsigned int) sizeof (struct GNUNET_HashCode));
    return TALER_MHD_reply_with_error (gc->connection,
                                       MHD_HTTP_FORBIDDEN,
                                       TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
                                       NULL);
  }
  /* good, return the key share */
  return return_key_share (&gc->truth_uuid,
                           gc->connection);
}


/**
 * Handle special case of an answer being directly checked by the
 * plugin and not by our database.  Also ensures that the
 * request is rate-limited.
 *
 * @param[in,out] gc request to handle
 * @param decrypted_truth hash to check against
 * @param decrypted_truth_size number of bytes in @a decrypted_truth
 * @return MHD status code
 */
static enum MHD_Result
direct_validation (struct SolveContext *gc,
                   const void *decrypted_truth,
                   size_t decrypted_truth_size)
{
  /* Non-random code, call plugin directly! */
  enum ANASTASIS_AUTHORIZATION_SolveResult aar;
  enum GNUNET_GenericReturnValue ret;

  ret = rate_limit (gc);
  if (GNUNET_OK != ret)
    return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
  gc->as = gc->authorization->start (gc->authorization->cls,
                                     &AH_trigger_daemon,
                                     NULL,
                                     &gc->truth_uuid,
                                     0LLU,
                                     decrypted_truth,
                                     decrypted_truth_size);
  if (NULL == gc->as)
  {
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (gc->connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
                                       NULL);
  }
  if (NULL == gc->authorization->solve)
  {
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (gc->connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
                                       "solve method not implemented for authorization method");
  }
  aar = gc->authorization->solve (gc->as,
                                  gc->timeout,
                                  &gc->challenge_response,
                                  gc->connection);
  switch (aar)
  {
  case ANASTASIS_AUTHORIZATION_SRES_FAILED:
    return MHD_YES;
  case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED:
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Suspending request handling\n");
    gc_suspended (gc);
    return MHD_YES;
  case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED:
    return MHD_NO;
  case ANASTASIS_AUTHORIZATION_SRES_FINISHED:
    return return_key_share (&gc->truth_uuid,
                             gc->connection);
  }
  GNUNET_break (0);
  return MHD_NO;
}


/**
 * Handle special case of an answer being checked
 * by the plugin asynchronously (IBAN) after we inverted
 * the hash using the database.
 *
 * @param[in,out] gc request to handle
 * @param code validation code provided by the client
 * @param decrypted_truth hash to check against
 * @param decrypted_truth_size number of bytes in @a decrypted_truth
 * @return MHD status code
 */
static enum MHD_Result
iban_validation (struct SolveContext *gc,
                 uint64_t code,
                 const void *decrypted_truth,
                 size_t decrypted_truth_size)
{
  enum ANASTASIS_AUTHORIZATION_SolveResult aar;

  gc->as = gc->authorization->start (gc->authorization->cls,
                                     &AH_trigger_daemon,
                                     NULL,
                                     &gc->truth_uuid,
                                     code,
                                     decrypted_truth,
                                     decrypted_truth_size);
  if (NULL == gc->as)
  {
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (gc->connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
                                       NULL);
  }
  if (NULL == gc->authorization->solve)
  {
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (gc->connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
                                       "solve method not implemented for authorization method");
  }
  aar = gc->authorization->solve (gc->as,
                                  gc->timeout,
                                  &gc->challenge_response,
                                  gc->connection);
  switch (aar)
  {
  case ANASTASIS_AUTHORIZATION_SRES_FAILED:
    return MHD_YES;
  case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED:
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Suspending request handling\n");
    gc_suspended (gc);
    return MHD_YES;
  case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED:
    return MHD_NO;
  case ANASTASIS_AUTHORIZATION_SRES_FINISHED:
    return return_key_share (&gc->truth_uuid,
                             gc->connection);
  }
  GNUNET_break (0);
  return MHD_NO;
}


enum MHD_Result
AH_handler_truth_solve (
  struct MHD_Connection *connection,
  struct TM_HandlerContext *hc,
  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
  const char *upload_data,
  size_t *upload_data_size)
{
  struct SolveContext *gc = hc->ctx;
  void *encrypted_truth;
  size_t encrypted_truth_size;
  void *decrypted_truth;
  size_t decrypted_truth_size;
  char *truth_mime = NULL;
  bool is_question;

  if (NULL == gc)
  {
    /* Fresh request, do initial setup */
    gc = GNUNET_new (struct SolveContext);
    gc->hc = hc;
    hc->ctx = gc;
    gc->connection = connection;
    gc->truth_uuid = *truth_uuid;
    gc->hc->cc = &request_done;
    gc->timeout = GNUNET_TIME_relative_to_absolute (
      GNUNET_TIME_UNIT_SECONDS);
    TALER_MHD_parse_request_timeout (connection,
                                     &gc->timeout);
  } /* end of first-time initialization (if NULL == gc) */
  else
  {
    /* might have been woken up by authorization plugin,
       so clear the flag. MDH called us, so we are
       clearly no longer suspended */
    gc->suspended = false;
    if (NULL != gc->resp)
    {
      enum MHD_Result ret;

      /* We generated a response asynchronously, queue that */
      ret = MHD_queue_response (connection,
                                gc->response_code,
                                gc->resp);
      GNUNET_break (MHD_YES == ret);
      MHD_destroy_response (gc->resp);
      gc->resp = NULL;
      return ret;
    }
    if (NULL != gc->as)
    {
      /* Authorization process is "running", check what is going on */
      GNUNET_assert (NULL != gc->authorization);
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Continuing with running the authorization process\n");
      GNUNET_assert (! gc->suspended);
      return run_authorization_process (connection,
                                        gc);
    }
    /* We get here if the async check for payment said this request
       was indeed paid! */
  }

  if (NULL == gc->root)
  {
    /* parse byte stream upload into JSON */
    enum GNUNET_GenericReturnValue res;

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

    /* 'root' is now initialized, parse JSON body */
    {
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_fixed_auto ("truth_decryption_key",
                                     &gc->truth_key),
        GNUNET_JSON_spec_fixed_auto ("h_response",
                                     &gc->challenge_response),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_fixed_auto ("payment_secret",
                                       &gc->payment_identifier),
          &gc->no_payment_identifier_provided),
        GNUNET_JSON_spec_end ()
      };
      enum GNUNET_GenericReturnValue pres;

      pres = TALER_MHD_parse_json_data (connection,
                                        gc->root,
                                        spec);
      if (GNUNET_SYSERR == pres)
      {
        GNUNET_break (0);
        return MHD_NO; /* hard failure */
      }
      if (GNUNET_NO == pres)
      {
        GNUNET_break_op (0);
        return MHD_YES; /* failure */
      }
      if (! gc->no_payment_identifier_provided)
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Client provided payment identifier `%s'\n",
                    TALER_B2S (&gc->payment_identifier));
    }
  }

  {
    /* load encrypted truth from DB; we may do this repeatedly
       while handling the same request, if payment was checked
       asynchronously! */
    enum GNUNET_DB_QueryStatus qs;
    char *method;

    qs = ANASTASIS_DB_get_truth (
      &gc->truth_uuid,
      &encrypted_truth,
      &encrypted_truth_size,
      &truth_mime,
      &method);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (gc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
                                         "get escrow challenge");
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_NOT_FOUND,
                                         TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
                                         NULL);
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    }
    is_question = (0 == strcmp ("question",
                                method));
    if (! is_question)
    {
      gc->authorization
        = ANASTASIS_authorization_plugin_load (method,
                                               AH_cfg);
      if (NULL == gc->authorization)
      {
        enum MHD_Result ret;

        ret = TALER_MHD_reply_with_error (
          connection,
          MHD_HTTP_INTERNAL_SERVER_ERROR,
          TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED,
          method);
        GNUNET_free (encrypted_truth);
        GNUNET_free (truth_mime);
        GNUNET_free (method);
        return ret;
      }
      gc->challenge_costs = &gc->authorization->costs;
    }
    else
    {
      gc->challenge_costs = &AH_question_costs;
    }
    GNUNET_free (method);
  }

  /* check for payment */
  if ( (is_question) ||
       (! gc->authorization->payment_plugin_managed) )
  {
    /* GNUNET_SYSERR is unreachable: anastasis-httpd refuses to start on
       a price list that mixes free and non-free currencies. */
    GNUNET_assert (GNUNET_SYSERR !=
                   TALER_amount_list_check_uniform (gc->challenge_costs));
    if (GNUNET_NO !=
        TALER_amount_list_check_uniform (gc->challenge_costs))
    {
      /* Check database to see if the transaction is paid for */
      enum GNUNET_DB_QueryStatus qs;
      bool paid;

      if (gc->no_payment_identifier_provided)
      {
        GNUNET_free (truth_mime);
        GNUNET_free (encrypted_truth);
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Beginning payment, client did not provide payment identifier\n");
        return begin_payment (gc);
      }
      qs = ANASTASIS_DB_get_challenge_payment (
        &gc->payment_identifier,
        &gc->truth_uuid,
        &paid,
        &gc->challenge_paid);
      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
      case GNUNET_DB_STATUS_SOFT_ERROR:
        GNUNET_break (0);
        GNUNET_free (truth_mime);
        GNUNET_free (encrypted_truth);
        return TALER_MHD_reply_with_error (gc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
                                           "get challenge payment");
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        /* Create fresh payment identifier (cannot trust client) */
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Client-provided payment identifier is unknown.\n");
        GNUNET_free (truth_mime);
        GNUNET_free (encrypted_truth);
        return begin_payment (gc);
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        if (! paid)
        {
          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                      "Payment identifier known. Checking payment with client's payment identifier\n");
          GNUNET_free (truth_mime);
          GNUNET_free (encrypted_truth);
          return begin_payment (gc);
        }
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Payment confirmed\n");
        gc->have_payment = true;
        break;
      }
    }
    else
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Request is free of charge\n");
    }
  }

  /* We've been paid, now validate the response */
  /* decrypt encrypted_truth */
  {
    enum GNUNET_GenericReturnValue res;

    res = ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
                                          encrypted_truth,
                                          encrypted_truth_size,
                                          &decrypted_truth,
                                          &decrypted_truth_size);
    GNUNET_free (encrypted_truth);
    if (GNUNET_OK != res)
    {
      /* most likely, the decryption key is simply wrong */
      GNUNET_break_op (0);
      GNUNET_free (truth_mime);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
                                         NULL);
    }
  }

  /* Special case for secure question: we do not generate a numeric challenge,
     but check that the hash matches */
  if (is_question)
  {
    enum MHD_Result ret;

    ret = handle_security_question (gc,
                                    decrypted_truth,
                                    decrypted_truth_size);
    GNUNET_free (truth_mime);
    GNUNET_free (decrypted_truth);
    return ret;
  }

  /* Not security question, check for answer in DB */
  {
    enum ANASTASIS_DB_CodeStatus cs;
    bool satisfied = false;
    uint64_t code;

    GNUNET_free (truth_mime);
    if (gc->authorization->user_provided_code)
    {
      enum MHD_Result res;

      if (GNUNET_TIME_absolute_is_past (gc->timeout))
      {
        GNUNET_free (decrypted_truth);
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Timeout with user provided code\n");
        return TALER_MHD_reply_with_error (connection,
                                           MHD_HTTP_FORBIDDEN,
                                           TALER_EC_ANASTASIS_IBAN_MISSING_TRANSFER,
                                           "timeout awaiting validation");
      }
      res = direct_validation (gc,
                               decrypted_truth,
                               decrypted_truth_size);
      GNUNET_free (decrypted_truth);
      return res;
    }

    /* random code, check against database */
    cs = ANASTASIS_DB_do_verify_challenge_code (
      &gc->truth_uuid,
      &gc->challenge_response,
      &code,
      &satisfied);
    switch (cs)
    {
    case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Provided response does not match our stored challenge\n");
      GNUNET_free (decrypted_truth);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_FORBIDDEN,
                                         TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
                                         NULL);
    case ANASTASIS_DB_CODE_STATUS_RATE_LIMITED:
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Challenge is out of attempts, client must await a new code\n");
      GNUNET_free (decrypted_truth);
      return reply_rate_limited (gc);
    case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
    case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      GNUNET_free (decrypted_truth);
      return TALER_MHD_reply_with_error (gc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
                                         "do_verify_challenge_code");
    case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
      GNUNET_free (decrypted_truth);
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Specified challenge code %s was not issued\n",
                  GNUNET_h2s (&gc->challenge_response));
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_FORBIDDEN,
                                         TALER_EC_ANASTASIS_TRUTH_CHALLENGE_UNKNOWN,
                                         "specific challenge code was not issued");
    case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
      if (! satisfied)
      {
        enum MHD_Result res;

        res = iban_validation (gc,
                               code,
                               decrypted_truth,
                               decrypted_truth_size);
        GNUNET_free (decrypted_truth);
        return res;
      }
      GNUNET_free (decrypted_truth);
      return return_key_share (&gc->truth_uuid,
                               connection);
    default:
      GNUNET_break (0);
      return MHD_NO;
    }
  }
}
