/*
  This file is part of Challenger
  Copyright (C) 2023 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_token.c
 * @brief functions to handle incoming /token requests
 * @author Christian Grothoff
 */
#include "platform.h"
#include "challenger-httpd.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include "challenger-httpd_token.h"
#include "challenger-httpd_common.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>
#include "challenger_cm_enums.h"
#include "challenger-database/get_validation_pkce.h"
#include "challenger-database/get_client.h"
#include "challenger-database/do_insert_token.h"

#define MAX_RETRIES 3


/**
 * Context for a /token operation.
 */
struct TokenContext
{

  /**
   * Nonce of the validation process the request is about.
   */
  struct CHALLENGER_ValidationNonceP nonce;

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

  /**
   * Uploaded 'client_id' field from POST data.
   */
  char *client_id;

  /**
   * Uploaded 'client_id' field from POST data.
   */
  char *redirect_uri;

  /**
   * Uploaded 'client_secret' field from POST data.
   */
  char *client_secret;

  /**
   * Uploaded 'code' field from POST data.
   */
  char *code;

  /**
   * Uploaded 'grant_type' field from POST data.
   */
  char *grant_type;

  /**
   * Uploaded 'code_verifier' field from POST data.
   */
  char *code_verifier;

  /**
   * Set to true if the upload was too big.
   */
  bool too_big;
};


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

  if (NULL != bc->pp)
  {
    GNUNET_break_op (MHD_YES ==
                     MHD_destroy_post_processor (bc->pp));
  }
  GNUNET_free (bc->client_id);
  GNUNET_free (bc->redirect_uri);
  GNUNET_free (bc->client_secret);
  GNUNET_free (bc->code);
  GNUNET_free (bc->grant_type);
  GNUNET_free (bc->code_verifier);
  GNUNET_free (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 TokenContext *`
 * @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 TokenContext *bc = cls;
  struct Map
  {
    const char *name;
    char **ptr;
  } map[] = {
    {
      .name = "client_id",
      .ptr = &bc->client_id
    },
    {
      .name = "redirect_uri",
      .ptr = &bc->redirect_uri
    },
    {
      .name = "client_secret",
      .ptr = &bc->client_secret
    },
    {
      .name = "code",
      .ptr = &bc->code
    },
    {
      .name = "grant_type",
      .ptr = &bc->grant_type
    },
    {
      .name = "code_verifier",
      .ptr = &bc->code_verifier
    },
    {
      .name = NULL,
      .ptr = NULL
    },
  };
  char **ptr = NULL;
  size_t slen;

  (void) kind;
  (void) filename;
  (void) content_type;
  (void) transfer_encoding;
  (void) off;
  if (bc->too_big)
    return MHD_YES; /* discard the rest, we already have our answer */
  for (unsigned int i = 0; NULL != map[i].name; i++)
    if (0 == strcmp (key,
                     map[i].name))
      ptr = map[i].ptr;
  if (NULL == ptr)
    return MHD_YES; /* ignore */
  if (NULL == *ptr)
    slen = 0;
  else
    slen = strlen (*ptr);
  /* Hard cap independent of Content-Length: TALER_MHD_check_content_length()
     does not reject requests that omit Content-Length (e.g. chunked uploads),
     so without this guard an attacker could stream an unbounded field value. */
  if (slen + size > 2 * 1024)
  {
    GNUNET_break_op (0);
    bc->too_big = true;
    /* Returning MHD_NO here would put the post processor into an error
       state and make MHD tear the connection down without a response;
       keep going (discarding the data) so that the handler can reply
       with a proper 413. */
    return MHD_YES;
  }
  if (NULL == *ptr)
    *ptr = GNUNET_malloc (size + 1);
  else
    *ptr = GNUNET_realloc (*ptr,
                           slen + size + 1);
  memcpy (&(*ptr)[slen],
          data,
          size);
  (*ptr)[slen + size] = '\0';
  return MHD_YES;
}


/**
 * Check the "Content-Length" header (if any) of @a connection against
 * @a max_len.  Same contract as TALER_MHD_check_content_length(), except
 * that the error responses keep the "error" member required by RFC 6749
 * section 5.2, which every other error of this endpoint carries.
 *
 * @param connection connection to check
 * @param max_len largest permitted body length
 * @param[out] mret set to the result to return if we answered the request
 * @return true if a response was queued and the caller must return @a mret
 */
static bool
check_content_length (struct MHD_Connection *connection,
                      unsigned long long max_len,
                      enum MHD_Result *mret)
{
  const char *cl;
  unsigned long long cv;
  char dummy;

  cl = MHD_lookup_connection_value (connection,
                                    MHD_HEADER_KIND,
                                    MHD_HTTP_HEADER_CONTENT_LENGTH);
  if (NULL == cl)
    return false; /* chunked upload; the per-field cap catches oversize */
  if (1 != sscanf (cl,
                   "%llu%c",
                   &cv,
                   &dummy))
  {
    GNUNET_break_op (0);
    *mret = CH_reply_with_oauth_error (
      connection,
      MHD_HTTP_BAD_REQUEST,
      "invalid_request",
      TALER_EC_GENERIC_PARAMETER_MALFORMED,
      MHD_HTTP_HEADER_CONTENT_LENGTH);
    return true;
  }
  if (cv > max_len)
  {
    GNUNET_break_op (0);
    *mret = CH_reply_with_oauth_error (
      connection,
      MHD_HTTP_CONTENT_TOO_LARGE,
      "invalid_request",
      TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
      NULL);
    return true;
  }
  return false;
}


enum MHD_Result
CH_handler_token (struct CH_HandlerContext *hc,
                  const char *upload_data,
                  size_t *upload_data_size)
{
  struct TokenContext *bc = hc->ctx;

  if (NULL == bc)
  {
    /* first call, setup internals */
    bc = GNUNET_new (struct TokenContext);
    hc->cc = &cleanup_ctx;
    hc->ctx = bc;
    bc->pp = MHD_create_post_processor (hc->connection,
                                        2 * 1024,
                                        &post_iter,
                                        bc);
    if (NULL == bc->pp)
    {
      GNUNET_break_op (0);
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
        "invalid_request",
        TALER_EC_GENERIC_PARAMETER_MALFORMED,
        MHD_HTTP_HEADER_CONTENT_TYPE);
    }
    {
      enum MHD_Result mret;

      if (check_content_length (hc->connection,
                                2 * 1024,
                                &mret))
        return mret;
    }
    return MHD_YES;
  }
  /* handle upload */
  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;
    return MHD_NO;
  }
  if (bc->too_big)
  {
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_CONTENT_TOO_LARGE,
      "invalid_request",
      TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
      NULL);
  }
  if (NULL == bc->grant_type)
  {
    /* RFC 6749 5.2: a missing required parameter is 'invalid_request', not
       'unsupported_grant_type' (the latter is for a present, wrong value). */
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_BAD_REQUEST,
      "invalid_request",
      TALER_EC_GENERIC_PARAMETER_MISSING,
      "grant_type");
  }
  if (0 != strcmp (bc->grant_type,
                   "authorization_code"))
  {
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_BAD_REQUEST,
      "unsupported_grant_type",
      TALER_EC_GENERIC_PARAMETER_MALFORMED,
      "authorization_code");
  }

  if (NULL == bc->code)
  {
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_BAD_REQUEST,
      "invalid_request",
      TALER_EC_GENERIC_PARAMETER_MISSING,
      "code");
  }
  if (NULL == bc->client_secret)
  {
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_BAD_REQUEST,
      "invalid_request",
      TALER_EC_GENERIC_PARAMETER_MISSING,
      "client_secret");
  }
  if (NULL == bc->client_id)
  {
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_BAD_REQUEST,
      "invalid_request",
      TALER_EC_GENERIC_PARAMETER_MISSING,
      "client_id");
  }
  if (NULL == bc->redirect_uri)
  {
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_BAD_REQUEST,
      "invalid_request",
      TALER_EC_GENERIC_PARAMETER_MISSING,
      "redirect_uri");
  }

  /* Check this client is authorized to access the service */
  {
    enum GNUNET_DB_QueryStatus qs;
    char *client_url = NULL;
    unsigned long long client_id;
    char dummy;

    if (1 != sscanf (bc->client_id,
                     "%llu%c",
                     &client_id,
                     &dummy))
    {
      GNUNET_break_op (0);
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_BAD_REQUEST,
        "invalid_request",
        TALER_EC_GENERIC_PARAMETER_MALFORMED,
        "client_id");
    }

    for (unsigned int r = 0; r < MAX_RETRIES; r++)
    {
      qs = CHALLENGERDB_get_client (CH_context,
                                    client_id,
                                    bc->client_secret,
                                    &client_url);
      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
        GNUNET_break (0);
        return CH_reply_with_oauth_error (
          hc->connection,
          MHD_HTTP_INTERNAL_SERVER_ERROR,
          "server_error",
          TALER_EC_GENERIC_DB_FETCH_FAILED,
          "get_client");
      case GNUNET_DB_STATUS_SOFT_ERROR:
        continue;
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        GNUNET_break_op (0);
        return CH_reply_with_oauth_error (
          hc->connection,
          MHD_HTTP_UNAUTHORIZED,
          "invalid_client",
          TALER_EC_CHALLENGER_CLIENT_AUTHENTICATION_FAILED,
          NULL);
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        break;
      }
      break;
    } /* for MAX_RETRIES */
    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    {
      GNUNET_break (0);
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        "server_error",
        TALER_EC_GENERIC_DB_SOFT_FAILURE,
        "get_client");
    }
    if ( (NULL != client_url) &&
         (0 != strcmp (client_url,
                       bc->redirect_uri)) )
    {
      GNUNET_break_op (0);
      GNUNET_free (client_url);
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_UNAUTHORIZED,
        "invalid_client",
        TALER_EC_CHALLENGER_GENERIC_CLIENT_FORBIDDEN_BAD_REDIRECT_URI,
        NULL);
    }
    GNUNET_free (client_url);
  }

  if (GNUNET_OK !=
      CH_code_to_nonce (bc->code,
                        &bc->nonce))
  {
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_BAD_REQUEST,
      "invalid_grant",
      TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE,
      NULL);
  }

  /* Check code is valid */
  {
    char *client_secret;
    json_t *address;
    char *client_scope;
    char *client_state;
    char *client_redirect_uri;
    char *code_challenge;
    uint32_t code_challenge_method;
    enum GNUNET_DB_QueryStatus qs;
    char *code;
    enum CHALLENGER_CM code_challenge_method_enum;
    unsigned long long code_client_id;
    char code_dummy;

    /* bc->client_id was already validated as a number in the client-auth
       block above; re-parse it here (it is out of scope) so we can bind the
       authorization code to the authenticated client. */
    GNUNET_assert (1 == sscanf (bc->client_id,
                                "%llu%c",
                                &code_client_id,
                                &code_dummy));
    for (unsigned int r = 0; r < MAX_RETRIES; r++)
    {
      qs = CHALLENGERDB_get_validation_pkce (CH_context,
                                             &bc->nonce,
                                             (uint64_t) code_client_id,
                                             &client_secret,
                                             &address,
                                             &client_scope,
                                             &client_state,
                                             &client_redirect_uri,
                                             &code_challenge,
                                             &code_challenge_method);
      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
        GNUNET_break (0);
        return CH_reply_with_oauth_error (
          hc->connection,
          MHD_HTTP_INTERNAL_SERVER_ERROR,
          "server_error",
          TALER_EC_GENERIC_DB_FETCH_FAILED,
          "get_validation_pkce");
      case GNUNET_DB_STATUS_SOFT_ERROR:
        continue;
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        /* Unknown, expired, foreign or unsolved validation.  RFC 6749 5.2
           has exactly one error for all of these, and we must not say which
           one it was: the nonce is recoverable from any code, so a
           distinguishable answer would let anybody track the progress of a
           user's validation. */
        GNUNET_break_op (0);
        return CH_reply_with_oauth_error (
          hc->connection,
          MHD_HTTP_BAD_REQUEST,
          "invalid_grant",
          TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE,
          NULL);
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        break;
      }
      break;
    } /* for MAX_RETRIES */
    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    {
      GNUNET_break (0);
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        "server_error",
        TALER_EC_GENERIC_DB_SOFT_FAILURE,
        "get_validation_pkce");
    }

    code_challenge_method_enum = CHALLENGER_cm_from_int (
      code_challenge_method);
    if (CHALLENGER_CM_UNKNOWN == code_challenge_method_enum)
    {
      GNUNET_break (0);
      json_decref (address);
      GNUNET_free (client_scope);
      GNUNET_free (client_secret);
      GNUNET_free (client_redirect_uri);
      GNUNET_free (client_state);
      GNUNET_free (code_challenge);
      /* The value comes out of our own database, so a bad one is corrupt
         server state, not a malformed request parameter. */
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        "server_error",
        TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
        "Invalid code_challenge_method");
    }

    /* Authenticate the authorization code *before* looking at anything else
       about the validation.  CH_code_to_nonce() only parses the nonce out of
       the code, so every check that runs before the MAC comparison answers
       questions about a validation the caller may not hold a code for.  All
       failures below therefore collapse into one indistinguishable
       'invalid_grant' (RFC 6749 5.2). */
    if (NULL == address)
    {
      /* Cannot happen: only solved validations are returned, and those
         always carry an address.  Answered like a bad code regardless. */
      GNUNET_break_op (0);
      GNUNET_free (client_scope);
      GNUNET_free (client_secret);
      GNUNET_free (client_redirect_uri);
      GNUNET_free (client_state);
      GNUNET_free (code_challenge);
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_BAD_REQUEST,
        "invalid_grant",
        TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE,
        NULL);
    }
    code = CH_compute_code (&bc->nonce,
                            client_secret,
                            client_scope,
                            address,
                            client_redirect_uri);
    json_decref (address);
    GNUNET_free (client_scope);
    GNUNET_free (client_secret);
    GNUNET_free (client_redirect_uri);
    GNUNET_free (client_state);
    if (0 !=
        TALER_strcmp_ct (code,
                         bc->code))
    {
      GNUNET_break_op (0);
      GNUNET_free (code);
      GNUNET_free (code_challenge);
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_BAD_REQUEST,
        "invalid_grant",
        TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE,
        NULL);
    }
    GNUNET_free (code);

    /* Verify the code_challenge if present*/
    if (NULL != code_challenge)
    {
      if (NULL == bc->code_verifier)
      {
        GNUNET_break_op (0);
        GNUNET_free (code_challenge);
        return CH_reply_with_oauth_error (
          hc->connection,
          MHD_HTTP_BAD_REQUEST,
          "invalid_grant",
          TALER_EC_GENERIC_PARAMETER_MISSING,
          "code_verifier is missing");
      }

      /* RFC 7636 §4.1: code_verifier must be 43-128 unreserved chars */
      {
        size_t cv_len = strlen (bc->code_verifier);

        if ( (cv_len < 43) ||
             (cv_len > 128) )
        {
          GNUNET_break_op (0);
          GNUNET_free (code_challenge);
          return CH_reply_with_oauth_error (
            hc->connection,
            MHD_HTTP_BAD_REQUEST,
            "invalid_request",
            TALER_EC_GENERIC_PARAMETER_MALFORMED,
            "code_verifier length must be between 43 and 128 characters");
        }
        for (size_t i = 0; i < cv_len; i++)
        {
          char c = bc->code_verifier[i];

          if (! TALER_url_is_reserved (c))
            continue;
          GNUNET_break_op (0);
          GNUNET_free (code_challenge);
          return CH_reply_with_oauth_error (
            hc->connection,
            MHD_HTTP_BAD_REQUEST,
            "invalid_request",
            TALER_EC_GENERIC_PARAMETER_MALFORMED,
            "code_verifier contains invalid character");
        }
      }

      switch (code_challenge_method_enum)
      {
      case CHALLENGER_CM_S256:
        {
          gcry_md_hd_t hd;
          unsigned char hash[32];
          char *encoded_hash = NULL;
          size_t encoded_len;
          const void *md;

          if (GPG_ERR_NO_ERROR !=
              gcry_md_open (&hd,
                            GCRY_MD_SHA256,
                            0))
          {
            GNUNET_break (0);
            GNUNET_free (code_challenge);
            return CH_reply_with_oauth_error (
              hc->connection,
              MHD_HTTP_INTERNAL_SERVER_ERROR,
              "server_error",
              TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
              "Failed to initialize SHA256 hash function");
          }
          gcry_md_write (hd,
                         bc->code_verifier,
                         strlen (bc->code_verifier));
          md = gcry_md_read (hd,
                             0);
          GNUNET_assert (NULL != md);
          memcpy (hash,
                  md,
                  sizeof (hash));
          gcry_md_close (hd);

          encoded_len
            = GNUNET_STRINGS_base64url_encode (hash,
                                               sizeof (hash),
                                               &encoded_hash);

          if ( (0 == encoded_len) ||
               (NULL == encoded_hash) )
          {
            GNUNET_break (0);
            GNUNET_free (code_challenge);
            GNUNET_free (encoded_hash);
            return CH_reply_with_oauth_error (
              hc->connection,
              MHD_HTTP_INTERNAL_SERVER_ERROR,
              "server_error",
              TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
              "Failed to encode hash to Base64 URL");
          }

          if (0 !=
              TALER_strcmp_ct (encoded_hash,
                               code_challenge))
          {
            GNUNET_break_op (0);
            GNUNET_free (code_challenge);
            GNUNET_free (encoded_hash);
            return CH_reply_with_oauth_error (
              hc->connection,
              MHD_HTTP_BAD_REQUEST,
              "invalid_grant",
              TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE,
              "code_verifier does not match code_challenge (SHA256)");
          }
          GNUNET_free (encoded_hash);
        }
        break;
      case CHALLENGER_CM_PLAIN:
        if (0 !=
            TALER_strcmp_ct (bc->code_verifier,
                             code_challenge))
        {
          GNUNET_break_op (0);
          GNUNET_free (code_challenge);
          return CH_reply_with_oauth_error (
            hc->connection,
            MHD_HTTP_BAD_REQUEST,
            "invalid_grant",
            TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE,
            "code_verifier does not match code_challenge (PLAIN)");
        }
        break;
      case CHALLENGER_CM_UNKNOWN:
      case CHALLENGER_CM_EMPTY:
        GNUNET_break (0);
        GNUNET_free (code_challenge);
        return CH_reply_with_oauth_error (
          hc->connection,
          MHD_HTTP_INTERNAL_SERVER_ERROR,
          "server_error",
          TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
          "Database has empty or unknown challenge mode but with code_challenge");
      }
    }
    GNUNET_free (code_challenge);
  }

  {
    struct CHALLENGER_AccessTokenP token;
    enum GNUNET_DB_QueryStatus qs;
    unsigned long long token_client_id;
    char token_dummy;

    /* bc->client_id was already validated as a number in the client-auth
       block above; re-parse it here (it is out of scope) so the redemption
       is restricted to validations of the authenticated client. */
    GNUNET_assert (1 == sscanf (bc->client_id,
                                "%llu%c",
                                &token_client_id,
                                &token_dummy));
    GNUNET_CRYPTO_random_block (&token,
                                sizeof (token));
    for (unsigned int r = 0; r < MAX_RETRIES; r++)
    {
      qs = CHALLENGERDB_do_insert_token (CH_context,
                                         &bc->nonce,
                                         (uint64_t) token_client_id,
                                         &token,
                                         CH_token_expiration,
                                         CH_validation_expiration);
      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
        GNUNET_break (0);
        return CH_reply_with_oauth_error (
          hc->connection,
          MHD_HTTP_INTERNAL_SERVER_ERROR,
          "server_error",
          TALER_EC_GENERIC_DB_STORE_FAILED,
          "do_insert_token");
      case GNUNET_DB_STATUS_SOFT_ERROR:
        continue;
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        GNUNET_break_op (0);
        return CH_reply_with_oauth_error (
          hc->connection,
          MHD_HTTP_BAD_REQUEST,
          "invalid_grant",
          TALER_EC_CHALLENGER_GRANT_UNKNOWN,
          "do_insert_token");
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        break;
      }
      break;
    } /* for MAX_RETRIES */
    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    {
      GNUNET_break (0);
      return CH_reply_with_oauth_error (
        hc->connection,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        "server_error",
        TALER_EC_GENERIC_DB_SOFT_FAILURE,
        "do_insert_token");
    }


    return TALER_MHD_REPLY_JSON_PACK (
      hc->connection,
      MHD_HTTP_OK,
      GNUNET_JSON_pack_data_auto ("access_token",
                                  &token),
      GNUNET_JSON_pack_string ("token_type",
                               "Bearer"),
      GNUNET_JSON_pack_uint64 ("expires_in",
                               CH_token_expiration.rel_value_us
                               / GNUNET_TIME_UNIT_SECONDS.rel_value_us));
  }
}
