/*
  This file is part of Challenger
  Copyright (C) 2023, 2024 Taler Systems SA

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

  Challenger 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
  Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file challenger-httpd_challenge.c
 * @brief functions to handle incoming /challenge requests
 * @author Christian Grothoff
 */
#include "platform.h"
#include "challenger-httpd.h"
#include <regex.h>
#include <gnunet/gnunet_util_lib.h>
#include "challenger-httpd_challenge.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_templating_lib.h>
#include <taler/taler_signatures.h>
#include "challenger-httpd_common.h"

/**
 * Maximum number of retries for the database interaction.
 */
#define MAX_RETRIES 3

/**
 * Set to 1 to dump addresses into the log.
 */
#define DEBUG 0

/**
 * Context for a /challenge operation.
 */
struct ChallengeContext
{

  /**
   * Nonce of the operation.
   */
  struct CHALLENGER_ValidationNonceP nonce;

  /**
   * Kept in a DLL.
   */
  struct ChallengeContext *next;

  /**
   * Kept in a DLL.
   */
  struct ChallengeContext *prev;

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

  /**
   * Handle to the helper process.
   */
  struct GNUNET_OS_Process *child;

  /**
   * Handle to wait for @e child
   */
  struct GNUNET_ChildWaitHandle *cwh;

  /**
   * Handle for processing uploaded data.
   */
  struct MHD_PostProcessor *pp;

  /**
   * Where we store the collected address data.
   */
  json_t *address;

  /**
   * Last key during POST processing.
   */
  char *last_key;

  /**
   * Uploaded data during POST processing.
   */
  char *data;

  /**
   * Where to redirect the client on errors?
   */
  char *client_redirect_uri;

  /**
   * OAuth2 state.
   */
  char *state;

  /**
   * Buffer used by #TALER_MHD_parse_post_json(). Or NULL.
   */
  void *jbuffer;

  /**
   * When did we transmit last?
   */
  struct GNUNET_TIME_Absolute last_tx_time;

  /**
   * Exit code from helper.
   */
  unsigned long int exit_code;

  /**
   * Number of bytes in @a address, excluding 0-terminator.
   */
  size_t data_len;

  /**
   * Our tan.
   */
  uint32_t tan;

  /**
   * How many attempts does the user have left?
   */
  uint32_t pin_attempts_left;

  /**
   * How did the helper die?
   */
  enum GNUNET_OS_ProcessStatusType pst;

  /**
   * Connection status. #GNUNET_OK to continue
   * normally, #GNUNET_NO if an error was already
   * returned, #GNUNET_SYSERR if we failed to
   * return an error and should just return #MHD_NO.
   */
  enum GNUNET_GenericReturnValue status;

  /**
   * #GNUNET_YES if we are suspended in #bc_head,
   * #GNUNET_NO if operating normally,
   * #GNUNET_SYSERR if resumed by shutdown (end with #MHD_NO)
   */
  enum GNUNET_GenericReturnValue suspended;

  /**
   * True if the provided address was refused, usually because
   * the user tried too many different addresses already.
   */
  bool address_refused;

  /**
   * Should we retransmit the PIN?
   */
  bool retransmit;

  /**
   * Is the challenge already solved?
   */
  bool solved;

  /**
   * Did we do the DB interaction?
   */
  bool db_finished;

  /**
   * Is the upload in JSON?
   */
  bool is_json;
};


/**
 * Head of suspended challenger contexts.
 */
struct ChallengeContext *bc_head;

/**
 * Tail of suspended challenger contexts.
 */
struct ChallengeContext *bc_tail;


void
CH_wakeup_challenge_on_shutdown ()
{
  struct ChallengeContext *bc;

  while (NULL != (bc = bc_head))
  {
    GNUNET_CONTAINER_DLL_remove (bc_head,
                                 bc_tail,
                                 bc);
    MHD_resume_connection (bc->hc->connection);
    bc->suspended = GNUNET_SYSERR;
  }
}


/**
 * Function called to clean up a backup context.
 *
 * @param cls a `struct ChallengeContext`
 */
static void
cleanup_ctx (void *cls)
{
  struct ChallengeContext *bc = cls;

  if (NULL != bc->pp)
  {
    GNUNET_break_op (MHD_YES ==
                     MHD_destroy_post_processor (bc->pp));
  }
  if (NULL != bc->cwh)
  {
    GNUNET_wait_child_cancel (bc->cwh);
    bc->cwh = NULL;
  }
  if (NULL != bc->child)
  {
    (void) GNUNET_OS_process_kill (bc->child,
                                   SIGKILL);
    GNUNET_break (GNUNET_OK ==
                  GNUNET_OS_process_wait (bc->child));
    bc->child = NULL;
  }
  TALER_MHD_parse_post_cleanup_callback (bc->jbuffer);
  json_decref (bc->address);
  GNUNET_free (bc->data);
  GNUNET_free (bc->state);
  GNUNET_free (bc->last_key);
  GNUNET_free (bc->client_redirect_uri);
  GNUNET_free (bc);
}


/**
 * Generate error reply in the format requested by
 * the client.
 *
 * @param bc our context
 * @param template error template to use
 * @param http_status HTTP status to return
 * @param ec error code to return
 * @param hint human-readable hint to give
 */
static MHD_RESULT
reply_error (struct ChallengeContext *bc,
             const char *template,
             unsigned int http_status,
             enum TALER_ErrorCode ec,
             const char *hint)
{
  struct CH_HandlerContext *hc = bc->hc;

  return TALER_MHD_reply_with_error (
    hc->connection,
    http_status,
    ec,
    hint);
}


/**
 * Function called when our PIN transmission helper has terminated.
 *
 * @param cls our `struct ChallengeContext *`
 * @param type type of the process
 * @param exit_code status code of the process
 */
static void
child_done_cb (void *cls,
               enum GNUNET_OS_ProcessStatusType type,
               long unsigned int exit_code)
{
  struct ChallengeContext *bc = cls;
  struct GNUNET_AsyncScopeSave old_scope;

  GNUNET_async_scope_enter (&bc->hc->async_scope_id,
                            &old_scope);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Child done with exit code %d/%llu\n",
              (int) type,
              (unsigned long long) exit_code);
  GNUNET_OS_process_destroy (bc->child);
  bc->child = NULL;
  bc->cwh = NULL;
  bc->pst = type;
  bc->exit_code = exit_code;
  bc->suspended = GNUNET_NO;
  MHD_resume_connection (bc->hc->connection);
  GNUNET_CONTAINER_DLL_remove (bc_head,
                               bc_tail,
                               bc);
  CH_trigger_daemon ();
  GNUNET_async_scope_restore (&old_scope);
}


/**
 * Transmit the TAN to the given address.
 *
 * @param[in,out] bc context to submit TAN for
 */
static void
send_tan (struct ChallengeContext *bc)
{
  struct GNUNET_DISK_PipeHandle *p;
  struct GNUNET_DISK_FileHandle *pipe_stdin;
  void *msg;
  size_t msg_len;

  p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
  if (NULL == p)
  {
    MHD_RESULT mres;

    GNUNET_break (0);
    mres = reply_error (bc,
                        "internal-error",
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
                        "pipe");
    bc->status = (MHD_YES == mres)
      ? GNUNET_NO
      : GNUNET_SYSERR;
    return;
  }
  {
    char *address;

    address = json_dumps (bc->address,
                          JSON_COMPACT);
#if DEBUG
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Running auth command `%s' on address `%s'\n",
                CH_auth_command,
                address);
#endif
    bc->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
                                         p,
                                         NULL,
                                         NULL,
                                         CH_auth_command,
                                         CH_auth_command,
                                         address,
                                         NULL);
    free (address);
  }
  if (NULL == bc->child)
  {
    MHD_RESULT mres;

    GNUNET_break (0);
    GNUNET_break (GNUNET_OK ==
                  GNUNET_DISK_pipe_close (p));
    mres = reply_error (bc,
                        "internal-error",
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
                        "exec");
    bc->status = (MHD_YES == mres)
      ? GNUNET_NO
      : GNUNET_SYSERR;
    return;
  }
  pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
                                            GNUNET_DISK_PIPE_END_WRITE);
  GNUNET_assert (NULL != pipe_stdin);
  GNUNET_break (GNUNET_OK ==
                GNUNET_DISK_pipe_close (p));
  if (0 != CH_message_template_len)
  {
    json_t *root;
    int mret;

    root = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_object_incref ("address",
                                      bc->address),
      GNUNET_JSON_pack_int64 ("pin",
                              bc->tan));
    mret = TALER_TEMPLATING_fill2 (CH_message_template,
                                   CH_message_template_len,
                                   root,
                                   &msg,
                                   &msg_len);
    json_decref (root);
    if (0 != mret)
    {
      MHD_RESULT mres;

      GNUNET_break (0);
      mres = reply_error (bc,
                          "internal-error",
                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                          TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
                          NULL);
      GNUNET_DISK_file_close (pipe_stdin);
      bc->status = (MHD_YES == mres)
          ? GNUNET_NO
          : GNUNET_SYSERR;
      return;
    }
  }
  else
  {
    char *cmsg;

    GNUNET_asprintf (&cmsg,
                     "PIN: %u",
                     (unsigned int) bc->tan);
    msg_len = strlen (cmsg);
    msg = cmsg;
  }
  {
    const char *off = msg;
    size_t left = msg_len;

    while (0 != left)
    {
      ssize_t ret;

      ret = GNUNET_DISK_file_write (pipe_stdin,
                                    off,
                                    left);
      if (ret <= 0)
      {
        MHD_RESULT mres;

        GNUNET_break (0);
        mres = reply_error (bc,
                            "internal-error",
                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                            TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
                            "write");
        GNUNET_DISK_file_close (pipe_stdin);
        GNUNET_free (msg);
        bc->status = (MHD_YES == mres)
          ? GNUNET_NO
          : GNUNET_SYSERR;
        return;
      }
      off += (size_t) ret;
      left -= (size_t) ret;
    }
    GNUNET_DISK_file_close (pipe_stdin);
  }
  GNUNET_free (msg);
  bc->cwh = GNUNET_wait_child (bc->child,
                               &child_done_cb,
                               bc);
  MHD_suspend_connection (bc->hc->connection);
  bc->suspended = GNUNET_YES;
  GNUNET_CONTAINER_DLL_insert (bc_head,
                               bc_tail,
                               bc);
}


/**
 * Iterator over key-value pairs where the value may be made available
 * in increments and/or may not be zero-terminated.  Used for
 * processing POST data.
 *
 * @param cls a `struct ChallengeContext *`
 * @param kind type of the value, always #MHD_POSTDATA_KIND when called from MHD
 * @param key 0-terminated key for the value
 * @param filename name of the uploaded file, NULL if not known
 * @param content_type mime-type of the data, NULL if not known
 * @param transfer_encoding encoding of the data, NULL if not known
 * @param data pointer to @a size bytes of data at the
 *              specified offset
 * @param off offset of data in the overall value
 * @param size number of bytes in @a data available
 * @return #MHD_YES to continue iterating,
 *         #MHD_NO to abort the iteration
 */
static enum MHD_Result
post_iter (void *cls,
           enum MHD_ValueKind kind,
           const char *key,
           const char *filename,
           const char *content_type,
           const char *transfer_encoding,
           const char *data,
           uint64_t off,
           size_t size)
{
  struct ChallengeContext *bc = cls;

  (void) filename;
  (void) content_type;
  (void) transfer_encoding;
  (void) off;
  if (MHD_POSTDATA_KIND != kind)
    return MHD_YES;
  if ( (NULL != bc->last_key) &&
       (0 != strcmp (key,
                     bc->last_key)) )
  {
    GNUNET_assert (0 ==
                   json_object_set_new (bc->address,
                                        bc->last_key,
                                        json_string (bc->data)));
    GNUNET_free (bc->data);
    bc->data_len = 0;
    GNUNET_free (bc->last_key);
  }
  if (NULL == bc->last_key)
  {
    bc->last_key = GNUNET_strdup (key);
  }
  bc->data = GNUNET_realloc (bc->data,
                             bc->data_len + size + 1);
  memcpy (bc->data + bc->data_len,
          data,
          size);
  bc->data_len += size;
  bc->data[bc->data_len] = '\0';
  return MHD_YES;
}


/**
 * Check if the given address satisfies our restrictions.
 *
 * @param address address data provided by the client
 * @return NULL on success, otherwise the key that failed
 */
static const char *
check_restrictions (const json_t *address)
{
  const char *key;
  const json_t *val;

  json_object_foreach ((json_t *) address, key, val)
  {
    const char *str = json_string_value (val);
    const char *regex = json_string_value (
      json_object_get (
        json_object_get (CH_restrictions,
                         key),
        "regex"));
    regex_t re;

    if (0 == strcasecmp (key,
                         "read_only"))
      continue;
    if (NULL == str)
      return key;
    if (NULL == regex)
      continue;
    if (0 != regcomp (&re,
                      regex,
                      REG_EXTENDED))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Invalid regex `%s' address restriction specified for `%s'\n",
                  regex,
                  key);
      continue;
    }
    if (0 != regexec (&re,
                      str,
                      0,
                      NULL,
                      0))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Client input `%s' rejected as it does not match address restriction `%s' specified for `%s'\n",
                  str,
                  regex,
                  key);
      return key;
    }
    regfree (&re);
  }
  return NULL;
}


MHD_RESULT
CH_handler_challenge (struct CH_HandlerContext *hc,
                      const char *upload_data,
                      size_t *upload_data_size)
{
  struct ChallengeContext *bc = hc->ctx;

  if (NULL == bc)
  {
    /* first call, setup internals */
    bc = GNUNET_new (struct ChallengeContext);
    bc->status = GNUNET_OK;
    bc->hc = hc;
    hc->cc = &cleanup_ctx;
    hc->ctx = bc;
    bc->pst = GNUNET_OS_PROCESS_UNKNOWN;
    bc->tan
      = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
                                  100000000);
    bc->pp = MHD_create_post_processor (hc->connection,
                                        1024,
                                        &post_iter,
                                        bc);
    if (GNUNET_OK !=
        GNUNET_STRINGS_string_to_data (hc->path,
                                       strlen (hc->path),
                                       &bc->nonce,
                                       sizeof (bc->nonce)))
    {
      GNUNET_break_op (0);
      return reply_error (bc,
                          "invalid-request",
                          MHD_HTTP_NOT_FOUND,
                          TALER_EC_GENERIC_PARAMETER_MISSING,
                          hc->path);
    }
    {
      const char *ct;

      ct = MHD_lookup_connection_value (hc->connection,
                                        MHD_HEADER_KIND,
                                        MHD_HTTP_HEADER_CONTENT_TYPE);
      bc->is_json = ( (NULL != ct) &&
                      (0 == strcasecmp (ct,
                                        "application/json")) );
      if (! bc->is_json)
        bc->address = json_object ();
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Processing /challenge upload with %s encoding...\n",
                  ct);
    }
    TALER_MHD_check_content_length (hc->connection,
                                    1024);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Awaiting /challenge upload (%u)...\n",
                (unsigned int) *upload_data_size);
    return MHD_YES;
  }
  GNUNET_assert (GNUNET_YES != bc->suspended);
  if (GNUNET_SYSERR == bc->suspended)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "/challenge ends in shutdown\n");
    return MHD_NO;
  }
  /* Handle case where helper process failed */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Checking child process status: %d / %d (%s)\n",
              (int) bc->pst,
              (int) bc->exit_code,
              NULL == bc->child ? "dead" : "running");
  if ( (GNUNET_OS_PROCESS_UNKNOWN != bc->pst) &&
       (NULL == bc->child) &&
       ( (GNUNET_OS_PROCESS_EXITED != bc->pst) ||
         (0 != bc->exit_code) ) )
  {
    char es[32];

    GNUNET_break (0);
    GNUNET_snprintf (es,
                     sizeof (es),
                     "%u/%d",
                     (unsigned int) bc->exit_code,
                     bc->pst);
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Helper failed with status %d/%d\n",
                (int) bc->pst,
                (int) bc->exit_code);
    return reply_error (bc,
                        "internal-error",
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
                        es);
  }
  /* handle upload */
  if (bc->is_json)
  {
    if (NULL == bc->address)
    {
      enum GNUNET_GenericReturnValue res;

      res = TALER_MHD_parse_post_json (hc->connection,
                                       &bc->jbuffer,
                                       upload_data,
                                       upload_data_size,
                                       &bc->address);
      if (GNUNET_SYSERR != res)
        return MHD_YES;
      GNUNET_break (0);
      return MHD_NO;
    }
    else
    {
      GNUNET_break (0 == *upload_data_size);
    }
  }
  else if (0 != *upload_data_size)
  {
    enum MHD_Result res;

    res = MHD_post_process (bc->pp,
                            upload_data,
                            *upload_data_size);
    *upload_data_size = 0;
    if (MHD_YES == res)
      return MHD_YES;
    GNUNET_break (0);
    return MHD_NO;
  }
  if (NULL != bc->last_key)
  {
    GNUNET_assert (0 ==
                   json_object_set_new (bc->address,
                                        bc->last_key,
                                        json_string (bc->data)));
    GNUNET_free (bc->data);
    bc->data_len = 0;
    GNUNET_free (bc->last_key);
  }
#if DEBUG
  {
    char *address;

    address = json_dumps (bc->address,
                          JSON_COMPACT);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Submitted address is `%s'\n",
                address);
    free (address);
  }
#endif
  {
    const char *bad_field;

    bad_field = check_restrictions (bc->address);
    if (NULL != bad_field)
    {
      GNUNET_break_op (0);
      return reply_error (bc,
                          "invalid-request",
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                          bad_field);
    }
  }
  if (! bc->db_finished)
  {
    for (unsigned int r = 0; r < MAX_RETRIES; r++)
    {
      enum GNUNET_DB_QueryStatus qs;
      json_t *old_address;
      const json_t *ro;

      GNUNET_assert (NULL == bc->client_redirect_uri);
      qs = CH_db->address_get (
        CH_db->cls,
        &bc->nonce,
        &old_address);
      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
        GNUNET_break (0);
        return reply_error (bc,
                            "internal-error",
                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                            TALER_EC_GENERIC_DB_FETCH_FAILED,
                            "validation-get");
      case GNUNET_DB_STATUS_SOFT_ERROR:
        if (r < MAX_RETRIES - 1)
          continue;
        GNUNET_break (0);
        return reply_error (bc,
                            "internal-error",
                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                            TALER_EC_GENERIC_DB_FETCH_FAILED,
                            "validation-get");
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        GNUNET_break_op (0);
        return reply_error (bc,
                            "validation-unknown",
                            MHD_HTTP_NOT_FOUND,
                            TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN,
                            NULL);
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        break;
      }
      ro = json_object_get (old_address,
                            "read_only");
      if ( (NULL != ro) &&
           (json_boolean_value (ro)) &&
           (1 != json_equal (old_address,
                             bc->address)) )
      {
        GNUNET_break_op (0);
        json_decref (old_address);
        return reply_error (bc,
                            "address-read-only",
                            MHD_HTTP_FORBIDDEN,
                            TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_READ_ONLY,
                            NULL);
      }
      json_decref (old_address);

      qs = CH_db->challenge_set_address_and_pin (
        CH_db->cls,
        &bc->nonce,
        bc->address,
        CH_validation_duration,
        &bc->tan,
        &bc->state,
        &bc->last_tx_time,
        &bc->pin_attempts_left,
        &bc->retransmit,
        &bc->client_redirect_uri,
        &bc->address_refused,
        &bc->solved);
      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
        GNUNET_break (0);
        return reply_error (bc,
                            "internal-error",
                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                            TALER_EC_GENERIC_DB_STORE_FAILED,
                            "set-address-and-pin");
      case GNUNET_DB_STATUS_SOFT_ERROR:
        if (r < MAX_RETRIES - 1)
          continue;
        GNUNET_break (0);
        return reply_error (bc,
                            "internal-error",
                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                            TALER_EC_GENERIC_DB_STORE_FAILED,
                            "set-address-and-pin");
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        GNUNET_break_op (0);
        return reply_error (bc,
                            "validation-unknown",
                            MHD_HTTP_NOT_FOUND,
                            TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN,
                            NULL);
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        break;
      }
      break;
    }
    bc->db_finished = true;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Database interaction done (challenge %s)\n",
                bc->solved ? "solved" : "unsolved");
    if (bc->solved)
    {
      struct MHD_Response *response;
      MHD_RESULT ret;

      json_t *args = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string ("type",
                                 "completed"),
        GNUNET_JSON_pack_string ("redirect_url",
                                 bc->client_redirect_uri)
        );

      response = TALER_MHD_make_json (args);

      ret = MHD_queue_response (hc->connection,
                                MHD_HTTP_OK,
                                response);
      MHD_destroy_response (response);
      return ret;
    }
    if (bc->address_refused)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Address changes exhausted address change limit for this process\n");
      return reply_error (bc,
                          "unauthorized_client",
                          MHD_HTTP_TOO_MANY_REQUESTS,
                          TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS,
                          "client exceeded authorization attempts limit (too many addresses attempted)");

    }
    if (0 == bc->pin_attempts_left)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Address changes exhausted PIN limit for this address\n");
      return reply_error (bc,
                          "unauthorized_client",
                          MHD_HTTP_TOO_MANY_REQUESTS,
                          TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS,
                          "client exceeded authorization attempts limit (too many PINs)");
    }

    if (bc->retransmit)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Transmitting PIN\n");
      /* (Re)transmit PIN/TAN */
      send_tan (bc);
      if (GNUNET_YES == bc->suspended)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Suspending request after PIN transmission\n");
        return MHD_YES;
      }
      /* Did we already try to generate a response? */
      if (GNUNET_OK != bc->status)
        return (GNUNET_NO == bc->status)
          ? MHD_YES
          : MHD_NO;
    }
  }

  {
    json_t *args;
    struct MHD_Response *resp;
    unsigned int http_status;
    MHD_RESULT res;

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Returning success from challenge with %u attempts left\n",
                (unsigned int) bc->pin_attempts_left);
    args = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_uint64 ("attempts_left",
                               bc->pin_attempts_left),
      GNUNET_JSON_pack_string ("type",
                               "created"),
      GNUNET_JSON_pack_object_incref ("address",
                                      bc->address),
      GNUNET_JSON_pack_bool ("transmitted",
                             bc->retransmit),
      GNUNET_JSON_pack_timestamp ("retransmission_time",
                                  GNUNET_TIME_absolute_to_timestamp (
                                    GNUNET_TIME_absolute_add (
                                      bc->last_tx_time,
                                      CH_validation_duration)))
      );
    http_status = MHD_HTTP_OK;
    resp = TALER_MHD_make_json_steal (args);
    GNUNET_break (MHD_YES ==
                  MHD_add_response_header (resp,
                                           MHD_HTTP_HEADER_CACHE_CONTROL,
                                           "no-store,no-cache"));
    res = MHD_queue_response (hc->connection,
                              http_status,
                              resp);
    MHD_destroy_response (resp);
    return res;
  }
}
