gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-challenger] branch master updated: -more basic work on challenger


From: gnunet
Subject: [taler-challenger] branch master updated: -more basic work on challenger
Date: Sat, 06 May 2023 15:33:38 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository challenger.

The following commit(s) were added to refs/heads/master by this push:
     new f3550bf  -more basic work on challenger
f3550bf is described below

commit f3550bf44f67735195a09c1a18500300d7a252d8
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sat May 6 15:33:33 2023 +0200

    -more basic work on challenger
---
 contrib/enter-address-form.must                    |  23 ++
 contrib/enter-tan-form.must                        |  29 ++
 src/challenger/challenger-httpd.c                  |  51 +++
 src/challenger/challenger-httpd.h                  |  16 +
 src/challenger/challenger-httpd_auth.c             | 297 ++++++++++++++-
 src/challenger/challenger-httpd_challenge.c        | 399 ++++++++++++++++++---
 src/challenger/challenger-httpd_challenge.h        |   7 +
 src/challenger/challenger-httpd_common.c           |  33 ++
 src/challenger/challenger-httpd_common.h           |  32 ++
 src/challenger/challenger-httpd_info.c             |  91 ++++-
 src/challenger/challenger-httpd_login.c            |   5 +-
 src/challenger/challenger-httpd_solve.c            | 279 +++++++++++++-
 src/challengerdb/challenger-0001.sql               |   9 +-
 .../pg_challenge_set_address_and_pin.c             |  94 ++---
 .../pg_challenge_set_address_and_pin.h             |  12 +-
 src/include/challenger_database_plugin.h           |  60 +++-
 16 files changed, 1296 insertions(+), 141 deletions(-)

diff --git a/contrib/enter-address-form.must b/contrib/enter-address-form.must
new file mode 100644
index 0000000..62a5f89
--- /dev/null
+++ b/contrib/enter-address-form.must
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Enter your address</title>
+</head>
+<body>
+<form action="/challenge/{{nonce}}" method="POST">
+  <div>
+    <label for="say">What is your address?</label>
+    <input
+       name="address"
+       id="address"
+       maxlength="512"
+       value="{{last_address}}"
+       {{#fixed_address}}readonly{{/fixed_address}}
+    />
+  </div>
+  (You can change address another {{changes_left}} times.)
+  <div>
+    <button>Submit</button>
+  </div>
+</form>
+</body>
+</html>
\ No newline at end of file
diff --git a/contrib/enter-tan-form.must b/contrib/enter-tan-form.must
new file mode 100644
index 0000000..e40aa10
--- /dev/null
+++ b/contrib/enter-tan-form.must
@@ -0,0 +1,29 @@
+<html>
+<head>
+<title>Enter your TAN</title>
+</head>
+<body>
+  {{#transmitted}}
+  A TAN was sent to your address &quot;{{address}}&quot;.
+  {{transmitted}}
+  {{^transmitted}}
+  We recently already sent a TAN to your address &quot;{{address}}&quot;.
+  A new TAN will not be transmitted again before {{next_tx_time}}.
+  {{/transmitted}}
+
+  <form action="/solve/{{nonce}}" method="POST">
+  <div>
+    <label for="say">Please enter the TAN your received to 
authenticate.</label>
+    <input
+       name="pin"
+       id="pin"
+       maxlength="64"
+    />
+  </div>
+  (You have {{attempts_left}} attempts left.)
+  <div>
+    <button>Submit</button>
+  </div>
+</form>
+</body>
+</html>
diff --git a/src/challenger/challenger-httpd.c 
b/src/challenger/challenger-httpd.c
index b147e39..14fae3e 100644
--- a/src/challenger/challenger-httpd.c
+++ b/src/challenger/challenger-httpd.c
@@ -80,11 +80,28 @@ struct CHALLENGER_DatabasePlugin *db;
  */
 struct GNUNET_TIME_Relative CH_validation_duration;
 
+/**
+ * How long validated data considered to be valid?
+ */
+struct GNUNET_TIME_Relative CH_validation_expiration;
+
 /**
  * How often do we retransmit the challenge.
  */
 struct GNUNET_TIME_Relative CH_pin_retransmission_frequency;
 
+/**
+ * Type of addresses this challenger validates.
+ */
+char *CH_address_type;
+
+/**
+ * Helper command to run for transmission of
+ * challenge values.
+ */
+char *CH_auth_command;
+
+
 /**
  * A client has requested the given url using the given method
  * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
@@ -293,6 +310,7 @@ static void
 do_shutdown (void *cls)
 {
   (void) cls;
+  CH_wakeup_challenge_on_shutdown ();
   if (NULL != mhd_task)
   {
     GNUNET_SCHEDULER_cancel (mhd_task);
@@ -511,6 +529,39 @@ run (void *cls,
                                "VALIDATION_DURATION");
     return;
   }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (config,
+                                           "CHALLENGER",
+                                           "VALIDATION_EXPIRATION",
+                                           &CH_validation_expiration))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "CHALLENGER",
+                               "VALIDATION_EXPIRATION");
+    return;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (config,
+                                               "CHALLENGER",
+                                               "AUTH_COMMAND",
+                                               &CH_auth_command))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "CHALLENGER",
+                               "AUTH_COMMAND");
+    return;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (config,
+                                               "CHALLENGER",
+                                               "ADDRESS_TYPE",
+                                               &CH_address_type))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "CHALLENGER",
+                               "ADDRESS_TYPE");
+    return;
+  }
 
   TALER_MHD_setup (go);
   result = EXIT_NOTCONFIGURED;
diff --git a/src/challenger/challenger-httpd.h 
b/src/challenger/challenger-httpd.h
index d3e129a..478310e 100644
--- a/src/challenger/challenger-httpd.h
+++ b/src/challenger/challenger-httpd.h
@@ -129,11 +129,27 @@ extern struct CHALLENGER_DatabasePlugin *db;
  */
 extern struct GNUNET_CURL_Context *CH_ctx;
 
+/**
+ * Helper command to run for transmission of
+ * challenge values.
+ */
+extern char *CH_auth_command;
+
+/**
+ * Type of addresses this challenger validates.
+ */
+extern char *CH_address_type;
+
 /**
  * How long is an individual validation request valid?
  */
 extern struct GNUNET_TIME_Relative CH_validation_duration;
 
+/**
+ * How long validated data considered to be valid?
+ */
+extern struct GNUNET_TIME_Relative CH_validation_expiration;
+
 /**
  * How often do we retransmit the challenge.
  */
diff --git a/src/challenger/challenger-httpd_auth.c 
b/src/challenger/challenger-httpd_auth.c
index fcc6463..22e7120 100644
--- a/src/challenger/challenger-httpd_auth.c
+++ b/src/challenger/challenger-httpd_auth.c
@@ -33,10 +33,40 @@
 struct AuthContext
 {
 
+  /**
+   * 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;
 };
 
 
@@ -55,6 +85,11 @@ cleanup_ctx (void *cls)
     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);
 }
 
@@ -89,10 +124,58 @@ post_iter (void *cls,
            size_t size)
 {
   struct AuthContext *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 = NULL,
+      .ptr = NULL
+    },
+  };
+  char **ptr = NULL;
+  size_t slen;
 
-  (void) bc;
-  GNUNET_break (0);
-  return MHD_NO;
+  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);
+  if (NULL == *ptr)
+    *ptr = GNUNET_malloc (size + 1);
+  else
+    *ptr = GNUNET_realloc (*ptr,
+                           slen + size + 1);
+  memcpy ((*ptr)[slen],
+          data,
+          size);
+  return MHD_YES;
 }
 
 
@@ -102,6 +185,7 @@ CH_handler_auth (struct CH_HandlerContext *hc,
                  size_t *upload_data_size)
 {
   struct AuthContext *bc = hc->ctx;
+  char *access_token;
 
   if (NULL == bc)
   {
@@ -127,10 +211,211 @@ CH_handler_auth (struct CH_HandlerContext *hc,
     *upload_data_size = 0;
     if (MHD_YES == res)
       return MHD_YES;
-    /* FIXME: return more specific error if possible... */
     return MHD_NO;
   }
+  if ( (NULL == bc->grant_type) ||
+       (0 != strcmp (bc->grant_type,
+                     "authorization_code")) )
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_INVALID,
+                                       "authorization_code");
+  }
+
+  if (NULL == bc->code)
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MISSING,
+                                       "code");
+  }
+  if (NULL == bc->client_secret)
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MISSING,
+                                       "client_secret");
+  }
+  if (NULL == bc->client_id)
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MISSING,
+                                       "client_id");
+  }
+  if (NULL == bc->redirect_uri)
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_BAD_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;
+
+    qs = CH_db->client_check (CH_db->cls,
+                              bc->client_id,
+                              bc->client_secret,
+                              0, /* do not increment */
+                              &client_url);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_XX,
+                                         "client_check");
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return GNUNET_NO;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_CHALLENGER_XXX,
+                                         NULL);
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+    if ( (NULL != client_url) &&
+         (0 != strcmp (client_url,
+                       bc->redirect_uri)) )
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_FORBIDDEN,
+                                         TALER_EC_CHALLENGER_XXX,
+                                         NULL);
+    }
+    GNUNET_free (client_url);
+  }
 
-  /* FIXME: generate proper response */
-  return MHD_NO;
+  if (GNUNET_OK !=
+      CH_code_to_nonce (bc->code,
+                        &bc->nonce))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       TALER_EC_CHALLENGER_XXX,
+                                       NULL);
+  }
+
+  /* Check code is valid */
+  {
+    char *client_secret;
+    char *address;
+    char *client_scope;
+    char *client_state;
+    char *client_redirect_url;
+    enum GNUNET_DB_QueryStatus qs;
+    char *code;
+
+    qs = CH_db->validation_get (CH_db->cls,
+                                &bc->nonce,
+                                &client_secret,
+                                &address,
+                                &client_scope,
+                                &client_state,
+                                &client_redirect_url);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_XX,
+                                         "validation_get");
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return GNUNET_NO;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_CHALLENGER_XXX,
+                                         "validation_get");
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+    code = CH_compute_code (&bc->nonce,
+                            client_secret,
+                            client_scope,
+                            address,
+                            client_redirect_url);
+    GNUNET_free (address);
+    GNUNET_free (client_scope);
+    GNUNET_free (client_secret);
+    GNUNET_free (client_redirect_url);
+    GNUNET_free (client_state);
+    if (0 != strcmp (code,
+                     bc->code))
+    {
+      GNUNET_break_op (0);
+      GNUNET_free (code);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_FORBIDDEN,
+                                         TALER_EC_CHALLENGER_XXX,
+                                         "code");
+    }
+    GNUNET_free (code);
+  }
+
+  {
+    struct CHALLENGER_AccessTokenP grant;
+    enum GNUNET_DB_QueryStatus qs;
+    /* FIXME: do not hard-code 1h? */
+    struct GNUNET_TIME_Relative expiration
+      = GNUNET_TIME_UNIT_HOURS;
+
+    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                                &grant,
+                                sizeof (grant));
+    qs = CH_db->auth_add_grant (CH_db->cls,
+                                &bc->nonce,
+                                &grant,
+                                expiration);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_XX,
+                                         "add_grant");
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return GNUNET_NO;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_CHALLENGER_XXX,
+                                         "add_grant");
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+
+
+    return TALER_MHD_REPLY_JSON_PACK (
+      hc->connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_data_auto ("access_token",
+                                  &grant),
+      GNUNET_JSON_pack_string ("token_type",
+                               "Bearer"),
+      GNUNET_JSON_pack_uint64 ("expires_in",
+                               expiration.rel_time_us
+                               / GNUNET_TIME_UNIT_SECONDS.rel_time_us));
+  }
 }
diff --git a/src/challenger/challenger-httpd_challenge.c 
b/src/challenger/challenger-httpd_challenge.c
index cf45cb5..1cfaa44 100644
--- a/src/challenger/challenger-httpd_challenge.c
+++ b/src/challenger/challenger-httpd_challenge.c
@@ -34,6 +34,36 @@
 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.
    */
@@ -44,13 +74,90 @@ struct ChallengeContext
    */
   char *address;
 
+  /**
+   * 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 address_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;
+
+  /**
+   * Should we retransmit the PIN?
+   */
+  bool retransmit;
+
+  /**
+   * Did we do the DB interaction?
+   */
+  bool db_finished;
 };
 
 
+/**
+ * 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 (bs->connection);
+    bc->suspended = GNUNET_SYSERR;
+  }
+}
+
+
 /**
  * Function called to clean up a backup context.
  *
@@ -66,11 +173,153 @@ cleanup_ctx (void *cls)
     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;
+  }
   GNUNET_free (bc->address);
   GNUNET_free (bc);
 }
 
 
+/**
+ * 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;
+
+  bc->child = NULL;
+  bc->cwh = NULL;
+  bc->pst = type;
+  bc->exit_code = exit_code;
+  MHD_resume_connection (bs->connection);
+  GNUNET_CONTAINER_DLL_remove (bc_head,
+                               bc_tail,
+                               bc);
+  CH_trigger_daemon ();
+}
+
+
+/**
+ * Transmit the TAN to the given address.
+ *
+ * @param[in,out] bc context to submit TAN for
+ * @param tan TAN value to submit
+ */
+static void
+send_tan (struct ChallengeContext *bc,
+          uint32_t tan)
+{
+  struct GNUNET_DISK_PipeHandle *p;
+  struct GNUNET_DISK_FileHandle *pipe_stdin;
+  char *msg;
+
+  p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+  if (NULL == p)
+  {
+    MHD_STATUS mres;
+
+    // FIXME: generate HTML error instead...
+    mres = TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
+                                       "pipe");
+    bc->status = (MHD_YES == mres)
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+    return;
+  }
+  bc->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+                                       p,
+                                       NULL,
+                                       NULL,
+                                       CH_auth_command,
+                                       CH_auth_command,
+                                       bc->address,
+                                       NULL);
+  if (NULL == child)
+  {
+    MHD_RESULT mres;
+
+    GNUNET_DISK_pipe_close (p);
+    // FIXME: generate HTML error instead...
+    mres = TALER_MHD_reply_with_error (bc->hc->connection,
+                                       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_DISK_pipe_close (p);
+
+  GNUNET_asprintf (&msg,
+                   "PIN: %u",
+                   (unsigned int) pin);
+  {
+    const char *off = msg;
+    size_t left = strlen (off);
+
+    while (0 != left)
+    {
+      ssize_t ret;
+
+      if (0 == left)
+        break;
+      ret = GNUNET_DISK_file_write (pipe_stdin,
+                                    off,
+                                    left);
+      if (ret <= 0)
+      {
+        MHD_RESULT mres;
+
+        // FIXME: generate HTML error instead...
+        mres = TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
+                                           "write");
+        bc->status = (MHD_YES == mres)
+          ? GNUNET_NO
+          : GNUNET_SYSERR;
+        return;
+      }
+      msg_off += ret;
+      off += ret;
+      left -= ret;
+    }
+    GNUNET_DISK_file_close (pipe_stdin);
+  }
+  bc->cwh = GNUNET_wait_child (bc->child,
+                               &child_done_cb,
+                               bc);
+  MHD_suspend_connection (connection);
+  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
@@ -128,33 +377,60 @@ CH_handler_challenge (struct CH_HandlerContext *hc,
                       size_t *upload_data_size)
 {
   struct ChallengeContext *bc = hc->ctx;
-  struct CHALLENGER_ValidationNonceP nonce;
 
-  if (GNUNET_OK !=
-      GNUNET_STRINGS_string_to_data (hc->path,
-                                     strlen (hc->path),
-                                     &nonce,
-                                     sizeof (nonce)))
-  {
-    GNUNET_break_op (0);
-    return TALER_MHD_reply_with_error (hc->connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_GENERIC_PARAMETER_MISSING,
-                                       hc->path);
-  }
   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);
+      // FIXME: generate HTML error instead...
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_GENERIC_PARAMETER_MISSING,
+                                         hc->path);
+    }
     /* FIXME: check content-length is low-enough */
     return MHD_YES;
   }
+  GNUNET_assert (GNUNET_YES != bc->suspended);
+  if (GNUNET_SYSERR == bc->suspended)
+    return MHD_NO;
+  /* Handle case where helper process failed */
+  if ( ( (GNUNET_OS_PROCESS_UNKNOWN != bc->pst) &&
+         (GNUNET_OS_PROCESS_EXITED != bc->pst) ) ||
+       (0 != bc->exit_code) )
+  {
+    char es[32];
+
+    GNUNET_snprintf (es,
+                     sizeof (es),
+                     "%u/%d",
+                     (unsigned int) bc->exit_code,
+                     bc->pst);
+    // FIXME: generate HTML error instead...
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
+                                       es);
+  }
   /* handle upload */
   if (0 != *upload_data_size)
   {
@@ -166,30 +442,23 @@ CH_handler_challenge (struct CH_HandlerContext *hc,
     *upload_data_size = 0;
     if (MHD_YES == res)
       return MHD_YES;
-    /* FIXME: return more specific error if possible... */
     return MHD_NO;
   }
-
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Submitted address is `%s'\n",
+              bc->address);
+  if (! bc->db_finished)
   {
-    struct GNUNET_TIME_Absolute last_tx_time
-      = GNUNET_TIME_absolute_get ();
-    uint32_t last_pin
-      = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
-                                  100000000);
-    uint32_t pin_attempts_left = 3; /* if addr is new */
     enum GNUNET_DB_QueryStatus qs;
-    struct GNUNET_TIME_Absolute next_tx_time;
-    bool retransmit;
 
-    next_tx_time = GNUNET_TIME_absolute_subtract (last_tx_time,
-                                                  CH_validation_duration);
     qs = db->challenge_set_address_and_pin (db->cls,
                                             &nonce,
                                             bc->address,
-                                            next_tx_time,
-                                            &last_tx_time,
-                                            &last_pin,
-                                            &retransmit);
+                                            CH_validation_duration,
+                                            &bc->tan,
+                                            &bc->last_tx_time,
+                                            &bc->pin_attempts_left,
+                                            &bc->retransmit);
     switch (qs)
     {
     case GNUNET_DB_STATUS_HARD_ERROR:
@@ -241,7 +510,8 @@ CH_handler_challenge (struct CH_HandlerContext *hc,
     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
       break;
     }
-    if (0 == pin_attempts_left)
+    bc->db_finished = true;
+    if (0 == bc->pin_attempts_left)
     {
       enum GNUNET_GenericReturnValue ret;
       json_t *root = json_object ();
@@ -263,35 +533,52 @@ CH_handler_challenge (struct CH_HandlerContext *hc,
       return MHD_YES;
     }
 
-    if (retransmit)
+    if (bc->retransmit)
     {
-      /* Retransmit PIN */
+      /* (Re)transmit PIN/TAN */
+      send_tan (bc,
+                tan);
+      if (GNUNET_YES == bc->suspended)
+        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;
-      enum GNUNET_GenericReturnValue ret;
+  }
 
-      args = GNUNET_JSON_PACK (
-        GNUNET_JSON_pack_uint64 ("attempts_left",
-                                 pin_attempts_left),
-        GNUNET_JSON_pack_timestamp ("next_tx_time",
-                                    GNUNET_TIME_absolute_to_timestamp (
-                                      next_tx_time))
-        );
-      ret = TALER_TEMPLATING_reply (hc->connection,
-                                    MHD_HTTP_OK,
-                                    "enter-pin-form.must",
-                                    NULL,
-                                    NULL,
-                                    args);
-      json_decref (args);
-      if (GNUNET_SYSERR == ret)
-      {
-        GNUNET_break (0);
-        return MHD_NO;
-      }
-      GNUNET_break (GNUNET_OK == ret);
-      return MHD_YES;
+  {
+    json_t *args;
+    enum GNUNET_GenericReturnValue ret;
+
+    args = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_uint64 ("attempts_left",
+                               bc->pin_attempts_left),
+      GNUNET_JSON_pack_string ("nonce",
+                               hc->path),
+      GNUNET_JSON_pack_string ("address",
+                               bc->address),
+      GNUNET_JSON_pack_bool ("transmitted",
+                             bc->retransmit),
+      GNUNET_JSON_pack_string ("next_tx_time",
+                               GNUNET_TIME_absolute2s (
+                                 GNUNET_TIME_absolute_to_timestamp (
+                                   bc->next_tx_time)))
+      );
+    ret = TALER_TEMPLATING_reply (hc->connection,
+                                  MHD_HTTP_OK,
+                                  "enter-tan-form.must",
+                                  NULL,
+                                  NULL,
+                                  args);
+    json_decref (args);
+    if (GNUNET_SYSERR == ret)
+    {
+      GNUNET_break (0);
+      return MHD_NO;
     }
+    GNUNET_break (GNUNET_OK == ret);
+    return MHD_YES;
   }
 }
diff --git a/src/challenger/challenger-httpd_challenge.h 
b/src/challenger/challenger-httpd_challenge.h
index f3f0d7b..c30f03c 100644
--- a/src/challenger/challenger-httpd_challenge.h
+++ b/src/challenger/challenger-httpd_challenge.h
@@ -24,6 +24,13 @@
 #include <microhttpd.h>
 
 
+/**
+ * Wake up suspended connections during shutdown.
+ */
+void
+CH_wakeup_challenge_on_shutdown (void);
+
+
 /**
  * Handle a client POSTing a /challenge request
  *
diff --git a/src/challenger/challenger-httpd_common.c 
b/src/challenger/challenger-httpd_common.c
index e79964e..56df4f3 100644
--- a/src/challenger/challenger-httpd_common.c
+++ b/src/challenger/challenger-httpd_common.c
@@ -53,3 +53,36 @@ CH_get_client_secret (struct MHD_Connection *connection)
   }
   return tok;
 }
+
+
+char *
+CH_compute_code (const struct CHALLENGER_ValidationNonceP *nonce,
+                 const char *client_secret,
+                 const char *client_scope,
+                 const char *address,
+                 const char *client_redirect_url)
+{
+  // FIXME: compute HKDF over inputs here!!!
+  GNUNET_break (0); // FIXME: insecure!
+  return "access-granted";
+}
+
+
+enum GNUNET_GenericReturnValue
+CH_code_to_nonce (const char *code,
+                  struct CHALLENGER_ValidationNonceP *nonce)
+{
+  GNUNET_break (0); // FIXME: not implemented
+  return GNUNET_SYSERR;
+}
+
+
+char *
+CH_compute_token (const struct CHALLENGER_ValidationNonceP *nonce,
+                  const char *client_secret,
+                  const char *client_redirect_url)
+{
+  // FIXME: compute HKDF over inputs here!!!
+  GNUNET_break (0); // FIXME: insecure!
+  return "grant-token";
+}
diff --git a/src/challenger/challenger-httpd_common.h 
b/src/challenger/challenger-httpd_common.h
index ca213fc..433e070 100644
--- a/src/challenger/challenger-httpd_common.h
+++ b/src/challenger/challenger-httpd_common.h
@@ -34,4 +34,36 @@ const char *
 CH_get_client_secret (struct MHD_Connection *connection);
 
 
+/**
+ * Compute code that would authorize access to the
+ * given challenge address. NOTE: We may not want
+ * to include all of these when hashing...
+ *
+ * @param nonce nonce of the challenge process
+ * @param client_secret secret of the client that should receive access
+ * @param client_scope scope of the grant
+ * @param address address that access is being granted to
+ * @param client_redirect_url redirect URL of the client
+ * @return code that grants access
+ */
+char *
+CH_compute_code (const struct CHALLENGER_ValidationNonceP *nonce,
+                 const char *client_secret,
+                 const char *client_scope,
+                 const char *address,
+                 const char *client_redirect_url);
+
+
+/**
+ * Extracts a @a nonce from the given @a code.
+ *
+ * @param code access code computed via #CH_compute_code()
+ * @param[out] nonce set to nonce used in #CH_compute_code()
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+CH_code_to_nonce (const char *code,
+                  struct CHALLENGER_ValidationNonceP *nonce);
+
+
 #endif
diff --git a/src/challenger/challenger-httpd_info.c 
b/src/challenger/challenger-httpd_info.c
index 04a8b47..7d48094 100644
--- a/src/challenger/challenger-httpd_info.c
+++ b/src/challenger/challenger-httpd_info.c
@@ -23,14 +23,97 @@
 #include <gnunet/gnunet_util_lib.h>
 #include "challenger-httpd_info.h"
 
+/**
+ * Prefix of a 'Bearer' token in an 'Authorization' HTTP header.
+ */
+#define BEARER_PREFIX "Bearer "
+
 
 MHD_RESULT
 CH_handler_info (struct CH_HandlerContext *hc,
                  const char *upload_data,
                  size_t *upload_data_size)
 {
-  return TALER_MHD_reply_with_error (hc->connection,
-                                     MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                     
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                                     NULL);
+  const char *auth;
+  const char *token;
+  struct CHALLENGER_AccessTokenP grant;
+
+  auth = MHD_lookup_connection_value (hc->connection,
+                                      MHD_HEADER_KIND,
+                                      MHD_HTTP_HEADER_AUTHORIZATION);
+  if (NULL == auth)
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       TALER_EC_GENERIC_PARAMETER_MISSING,
+                                       MHD_HTTP_HEADER_AUTHORIZATION);
+  }
+  if (0 != strncmp (auth,
+                    BEARER_PREFIX,
+                    strlen (BEARER_PREFIX)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       MHD_HTTP_HEADER_AUTHORIZATION);
+  }
+  token = auth + strlen (BEARER_PREFIX);
+
+  if (GNUNET_OK !=
+      CH_token_to_grant (token,
+                         &grant))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (hc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       MHD_HTTP_HEADER_AUTHORIZATION);
+  }
+
+  /* Check token is valid */
+  {
+    char *address;
+    enum GNUNET_DB_QueryStatus qs;
+    struct GNUNET_TIME_Timestamp address_expiration;
+    MHD_RESULT mret;
+
+    qs = CH_db->info_get_grant (CH_db->cls,
+                                &bc->grant,
+                                &address,
+                                &address_expiration);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_XX,
+                                         "info_get_grant");
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return GNUNET_NO;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_FORBIDDEN,
+                                         TALER_EC_CHALLENGER_XXX,
+                                         "info_get_grant");
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+
+    mret = TALER_MHD_REPLY_JSON_PACK (
+      hc->connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_string ("address",
+                               address),
+      GNUNET_JSON_pack_string ("address_type",
+                               CH_address_type),
+      GNUNET_JSON_pack_timestamp ("expires",
+                                  address_expiration));
+    GNUNET_free (address);
+    return mret;
+  }
 }
diff --git a/src/challenger/challenger-httpd_login.c 
b/src/challenger/challenger-httpd_login.c
index d7273c9..ff8ead9 100644
--- a/src/challenger/challenger-httpd_login.c
+++ b/src/challenger/challenger-httpd_login.c
@@ -68,7 +68,7 @@ CH_handler_login (struct CH_HandlerContext *hc,
     return TALER_MHD_reply_with_error (hc->connection,
                                        MHD_HTTP_BAD_REQUEST,
                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                       "response_type (mus be 'code')");
+                                       "response_type (must be 'code')");
   }
 
   {
@@ -127,7 +127,6 @@ CH_handler_login (struct CH_HandlerContext *hc,
     = MHD_lookup_connection_value (hc->connection,
                                    MHD_GET_ARGUMENT_KIND,
                                    "scope");
-  (void) scope; /* ignored */
   {
     char *last_address;
     uint32_t address_attempts_left;
@@ -199,6 +198,8 @@ CH_handler_login (struct CH_HandlerContext *hc,
       args = GNUNET_JSON_PACK (
         GNUNET_JSON_pack_bool ("fix_address",
                                0 == address_attempts_left),
+        GNUNET_JSON_pack_string ("nonce",
+                                 hc->path),
         GNUNET_JSON_pack_string ("last_address",
                                  last_address),
         GNUNET_JSON_pack_uint64 ("changes_left",
diff --git a/src/challenger/challenger-httpd_solve.c 
b/src/challenger/challenger-httpd_solve.c
index 0d5c436..8c6631d 100644
--- a/src/challenger/challenger-httpd_solve.c
+++ b/src/challenger/challenger-httpd_solve.c
@@ -33,6 +33,11 @@
 struct SolveContext
 {
 
+  /**
+   * Nonce of the operation.
+   */
+  struct CHALLENGER_ValidationNonceP nonce;
+
   /**
    * Handle for processing uploaded data.
    */
@@ -127,20 +132,7 @@ CH_handler_solve (struct CH_HandlerContext *hc,
                   size_t *upload_data_size)
 {
   struct SolveContext *bc = hc->ctx;
-  struct CHALLENGER_ValidationNonceP nonce;
 
-  if (GNUNET_OK !=
-      GNUNET_STRINGS_string_to_data (hc->path,
-                                     strlen (hc->path),
-                                     &nonce,
-                                     sizeof (nonce)))
-  {
-    GNUNET_break_op (0);
-    return TALER_MHD_reply_with_error (hc->connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_GENERIC_PARAMETER_MISSING,
-                                       hc->path);
-  }
   if (NULL == bc)
   {
     /* first call, setup internals */
@@ -151,6 +143,18 @@ CH_handler_solve (struct CH_HandlerContext *hc,
                                         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 TALER_MHD_reply_with_error (hc->connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_GENERIC_PARAMETER_MISSING,
+                                         hc->path);
+    }
     /* FIXME: check content-length is low-enough */
     return MHD_YES;
   }
@@ -165,12 +169,251 @@ CH_handler_solve (struct CH_HandlerContext *hc,
     *upload_data_size = 0;
     if (MHD_YES == res)
       return MHD_YES;
-    /* FIXME: return more specific error if possible... */
     return MHD_NO;
   }
-  /* FIXME: convert pin string to number */
-  /* FIXME: check with DB ... */
+  {
+    unsigned int pin;
+    char dummy;
+    enum GNUNET_DB_QueryStatus qs;
+    bool solved;
+
+    if (1 != sscanf (bc->pin,
+                     "%u%c",
+                     &pin,
+                     &dummy))
+    {
+      enum GNUNET_GenericReturnValue ret;
+      json_t *root = json_object ();
+
+      GNUNET_assert (NULL != root);
+      GNUNET_break_op (0);
+      ret = TALER_TEMPLATING_reply (hc->connection,
+                                    MHD_HTTP_BAD_REQUEST,
+                                    "pin-must-be-number.must",
+                                    NULL,
+                                    NULL,
+                                    root);
+      json_decref (root);
+      if (GNUNET_SYSERR == ret)
+      {
+        GNUNET_break (0);
+        return MHD_NO;
+      }
+      GNUNET_break (GNUNET_OK == ret);
+      return MHD_YES;
+    }
+
+    qs = CH_db->validate_solve_pin (CH_db->cls,
+                                    &bc->nonce,
+                                    pin,
+                                    &solved);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      {
+        enum GNUNET_GenericReturnValue ret;
+        json_t *root = json_object ();
+
+        GNUNET_assert (NULL != root);
+        GNUNET_break (0);
+        ret = TALER_TEMPLATING_reply (hc->connection,
+                                      MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                      "internal-server-error.must",
+                                      NULL,
+                                      NULL,
+                                      root);
+        json_decref (root);
+        if (GNUNET_SYSERR == ret)
+        {
+          GNUNET_break (0);
+          return MHD_NO;
+        }
+        GNUNET_break (GNUNET_OK == ret);
+        return MHD_YES;
+      }
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return GNUNET_NO;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      {
+        enum GNUNET_GenericReturnValue ret;
+        json_t *root = json_object ();
+
+        GNUNET_assert (NULL != root);
+        ret = TALER_TEMPLATING_reply (hc->connection,
+                                      MHD_HTTP_NOT_FOUND,
+                                      "validation-unknown.must",
+                                      NULL,
+                                      NULL,
+                                      root);
+        json_decref (root);
+        if (GNUNET_SYSERR == ret)
+        {
+          GNUNET_break (0);
+          return MHD_NO;
+        }
+        GNUNET_break (GNUNET_OK == ret);
+        return MHD_YES;
+      }
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+    if (! solved)
+    {
+      enum GNUNET_GenericReturnValue ret;
+      json_t *root = json_object ();
+
+      GNUNET_assert (NULL != root);
+      ret = TALER_TEMPLATING_reply (hc->connection,
+                                    MHD_HTTP_FORBIDDEN,
+                                    "invalid-pin.must",
+                                    NULL,
+                                    NULL,
+                                    root);
+      json_decref (root);
+      if (GNUNET_SYSERR == ret)
+      {
+        GNUNET_break (0);
+        return MHD_NO;
+      }
+      GNUNET_break (GNUNET_OK == ret);
+      return MHD_YES;
+    }
+  }
+
+  {
+    struct MHD_Response *response;
+    char *url;
+
+    {
+      char *client_secret;
+      char *address;
+      char *client_scope;
+      char *client_state;
+      char *client_redirect_url;
+      enum GNUNET_DB_QueryStatus qs;
+
+      qs = CH_db->validation_get (CH_db->cls,
+                                  &bc->nonce,
+                                  &client_secret,
+                                  &address,
+                                  &client_scope,
+                                  &client_state,
+                                  &client_redirect_url);
+      switch (qs)
+      {
+      case GNUNET_DB_STATUS_HARD_ERROR:
+        {
+          enum GNUNET_GenericReturnValue ret;
+          json_t *root = json_object ();
 
-  /* FIXME: generate proper response */
-  return MHD_NO;
+          GNUNET_assert (NULL != root);
+          GNUNET_break (0);
+          ret = TALER_TEMPLATING_reply (hc->connection,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        "internal-server-error.must",
+                                        NULL,
+                                        NULL,
+                                        root);
+          json_decref (root);
+          if (GNUNET_SYSERR == ret)
+          {
+            GNUNET_break (0);
+            return MHD_NO;
+          }
+          GNUNET_break (GNUNET_OK == ret);
+          return MHD_YES;
+        }
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        GNUNET_break (0);
+        return GNUNET_NO;
+      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+        {
+          enum GNUNET_GenericReturnValue ret;
+          json_t *root = json_object ();
+
+          GNUNET_assert (NULL != root);
+          ret = TALER_TEMPLATING_reply (hc->connection,
+                                        MHD_HTTP_NOT_FOUND,
+                                        "validation-unknown.must",
+                                        NULL,
+                                        NULL,
+                                        root);
+          json_decref (root);
+          if (GNUNET_SYSERR == ret)
+          {
+            GNUNET_break (0);
+            return MHD_NO;
+          }
+          GNUNET_break (GNUNET_OK == ret);
+          return MHD_YES;
+        }
+      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+        break;
+      }
+      {
+        char *code;
+        char *ue;
+
+        code = CH_compute_code (&bc->nonce,
+                                client_secret,
+                                client_scope,
+                                address,
+                                client_redirect_url);
+        ue = TALER_urlencode (client_state);
+        GNUNET_asprintf (&url,
+                         "%s?code=%s&state=%s",
+                         client_redirect_url,
+                         code,
+                         ue);
+        GNUNET_free (ue);
+        GNUNET_free (code);
+      }
+      GNUNET_free (address);
+      GNUNET_free (client_scope);
+      GNUNET_free (client_secret);
+      GNUNET_free (client_redirect_url);
+      GNUNET_free (client_state);
+    }
+
+    {
+      const char *ok = "Ok!";
+
+      response = MHD_create_response_from_buffer (strlen (ok),
+                                                  (void *) ok,
+                                                  MHD_RESPMEM_PERSISTENT);
+    }
+    if (NULL == response)
+    {
+      GNUNET_break (0);
+      GNUNET_free (url);
+      return MHD_NO;
+    }
+    TALER_MHD_add_global_headers (response);
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (response,
+                                           MHD_HTTP_HEADER_CONTENT_TYPE,
+                                           "text/plain"));
+    if (MHD_NO ==
+        MHD_add_response_header (response,
+                                 MHD_HTTP_HEADER_LOCATION,
+                                 url))
+    {
+      GNUNET_break (0);
+      MHD_destroy_response (response);
+      GNUNET_free (url);
+      return MHD_NO;
+    }
+    GNUNET_free (url);
+
+    {
+      MHD_RESULT ret;
+
+      ret = MHD_queue_response (connection,
+                                MHD_HTTP_FOUND,
+                                response);
+      MHD_destroy_response (response);
+      return ret;
+    }
+  }
 }
diff --git a/src/challengerdb/challenger-0001.sql 
b/src/challengerdb/challenger-0001.sql
index 5f79f5f..9cf8c8a 100644
--- a/src/challengerdb/challenger-0001.sql
+++ b/src/challengerdb/challenger-0001.sql
@@ -98,7 +98,8 @@ CREATE TABLE IF NOT EXISTS grants
   (grant_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
   ,access_token BYTEA PRIMARY KEY CHECK (length(access_token)=32)
   ,address VARCHAR NOT NULL
-  ,expiration_time INT8 NOT NULL
+  ,address_expiration_time INT8 NOT NULL
+  ,grant_expiration_time INT8 NOT NULL
   );
 
 COMMENT ON TABLE grants
@@ -107,8 +108,10 @@ COMMENT ON COLUMN grants.access_token
   IS 'Token that grants access to the resource (the address)';
 COMMENT ON COLUMN grants.address
   IS 'Address of the user (the resource protected by the token)';
-COMMENT ON COLUMN grants.expiration_time
-  IS 'When will the grant expire';
+COMMENT ON COLUMN grants.address_expiration_time
+  IS 'Timestamp until when we consider the address to be valid';
+COMMENT ON COLUMN grants.grant_expiration_time
+  IS 'Time until when we consider the grnat to be valid';
 
 -- Complete transaction
 COMMIT;
diff --git a/src/challengerdb/pg_challenge_set_address_and_pin.c 
b/src/challengerdb/pg_challenge_set_address_and_pin.c
index 9ef952d..cf99ec3 100644
--- a/src/challengerdb/pg_challenge_set_address_and_pin.c
+++ b/src/challengerdb/pg_challenge_set_address_and_pin.c
@@ -31,70 +31,82 @@ CH_PG_challenge_set_address_and_pin (
   void *cls,
   const struct CHALLENGER_ValidationNonceP *nonce,
   const char *address,
-  struct GNUNET_TIME_Absolute next_tx_time,
+  struct GNUNET_TIME_Relative validation_duration,
+  uint32_t *tan,
   struct GNUNET_TIME_Absolute *last_tx_time,
-  uint32_t *last_pin,
+  uint32_t *pin_attempts_left,
   bool *pin_transmit)
 {
   struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute now
+    = GNUNET_TIME_absolute_get ();
+  struct GNUNET_TIME_Absolute next_tx_time
+    = GNUNET_TIME_absolute_subtract (last_tx_time,
+                                     validation_duration),
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (nonce),
     GNUNET_PQ_query_param_string (address),
     GNUNET_PQ_query_param_absolute_time (&next_tx_time),
-    GNUNET_PQ_query_param_absolute_time (last_tx_time),
-    GNUNET_PQ_query_param_uint32 (last_pin),
+    GNUNET_PQ_query_param_absolute_time (&now),
+    GNUNET_PQ_query_param_uint32 (tan),
     GNUNET_PQ_query_param_end
   };
   struct GNUNET_PQ_ResultSpec rs[] = {
     GNUNET_PQ_result_spec_absolute_time ("last_tx_time",
                                          last_tx_time),
     GNUNET_PQ_result_spec_uint32 ("last_pin",
-                                  last_pin),
+                                  tan),
     GNUNET_PQ_result_spec_bool ("pin_transmit",
                                 pin_transmit),
+    GNUNET_PQ_result_spec_uint32 ("pin_attempts_left",
+                                  pin_attempts_left),
     GNUNET_PQ_result_spec_end
   };
 
-  // FIXME: review!!!
   PREPARE (pg,
            "challenge_set_address_and_pin",
+           "WITH decisions AS ("
+           "  SELECT "
+           "   ,(address != $2) AND"
+           "    (address_attempts_left > 0)"
+           "      AS addr_changed"
+           "   ,(pin_transmissions_left > 0) AND"
+           "      ( (address != $2) OR"
+           "        (last_tx_time < $3) ) AS send_pin"
+           "    FROM validations"
+           "    WHERE nonce=$1"
+           ")"
            "UPDATE validations SET"
            "  address_attempts_left=CASE"
-           "    WHEN address != $2"
-           "    THEN address_attempts_left - 1"
-           "    ELSE address_attempts_left"
-           "  END"
-           " ,last_pin=CASE"
-           "    WHEN address != $2"
-           "    THEN $5"
-           "    ELSE last_pin"
-           " ,END"
-           " ,pin_transmissions_left=CASE"
-           "    WHEN address != $2"
-           "    THEN 3"
-           "    ELSE WHEN last_tx_time < 3"
-           "      THEN pin_transmissions_left - 1"
-           "      ELSE pin_transmissions_left"
-           "    END"
-           " ,END"
-           " ,last_tx_time=CASE"
-           "    WHEN last_tx_time < $3"
-           "    THEN $4"
-           "    ELSE last_tx_time"
-           "  END"
-           " ,address=$2"
-           " WHERE nonce=$1"
-           "   AND ( (address_attempts_left > 0)"
-           "      OR ( (address == $2) AND"
-           "           ( (pin_transmissions_left > 0) OR"
-           "             (last_tx_time >= $3) ) ) )"
-           " RETURNING"
-           "   last_tx_time"
-           "  ,(address != $2) OR"
-           "   ( (pin_transmissions_left > 0) AND"
-           "     (last_tx_time < $3) ) AS pin_transmit"
-           "  ,last_pin"
-           "  ,pin_attempts_left;");
+           "    WHEN decisions.addr_changed
+           "    THEN address_attempts_left - 1 "
+           "    ELSE address_attempts_left "
+           "  END "
+           ",last_pin = CASE "
+           "    WHEN decisions.addr_changed
+                        "    THEN $5"
+                        "    ELSE last_pin"
+                        " ,END"
+                        " ,pin_transmissions_left=CASE"
+                        "    WHEN decisions.addr_changed
+           "    THEN 3 "
+           "    ELSE WHEN decisions.send_pin
+                        "      THEN pin_transmissions_left - 1"
+                        "      ELSE pin_transmissions_left"
+                        "    END"
+                        " ,END"
+                        " ,last_tx_time=CASE"
+                        "    WHEN decisions.send_pin"
+                        "    THEN $4"
+                        "    ELSE last_tx_time"
+                        "  END"
+                        " ,address=$2"
+                        " WHERE nonce=$1"
+                        " RETURNING"
+                        "   last_tx_time"
+                        "  ,decisions.send_pin AS pin_transmit"
+                        "  ,last_pin"
+                        "  ,pin_attempts_left;");
   return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                    
"challenge_set_address_and_pin",
                                                    params,
diff --git a/src/challengerdb/pg_challenge_set_address_and_pin.h 
b/src/challengerdb/pg_challenge_set_address_and_pin.h
index d817b57..3936c5e 100644
--- a/src/challengerdb/pg_challenge_set_address_and_pin.h
+++ b/src/challengerdb/pg_challenge_set_address_and_pin.h
@@ -35,10 +35,11 @@
  * @param cls
  * @param nonce unique nonce to use to identify the validation
  * @param address the new address to validate
- * @param next_tx_time tx time we must have sent earlier before to retransmit 
now
- * @param[in,out] last_tx_time set to the last time when we (presumably) send 
a PIN to @a address, input should be current time to use if the existing value 
for tx_time is past @a next_tx_time
- * @param[in,out] last_pin set to the PIN last send to @a address, input 
should be random PIN to use if address did not change
+ * @param validation_duration minimum time between transmissions
+ * @param[in,out] tan set to the PIN/TAN last send to @a address, input should 
be random PIN/TAN to use if address did not change
+ * @param[out] last_tx_time set to the last time when we (presumably) send a 
PIN to @a address, input should be current time to use if the existing value 
for tx_time is past @a next_tx_time
  * @param[out] pin_transmit set to true if we should transmit the @a last_pin 
to the @a address
+ * @param[out] pin_attempts_left set to number of attempts the user has left 
on this pin
  * @return transaction status:
  *   #GNUNET_DB_SUCCESS_ONE_RESULT if the address was changed
  *   #GNUNET_DB_SUCCESS_NO_RESULTS if we do not permit further changes to the 
address (attempts exhausted)
@@ -49,9 +50,10 @@ CH_PG_challenge_set_address_and_pin (
   void *cls,
   const struct CHALLENGER_ValidationNonceP *nonce,
   const char *address,
-  struct GNUNET_TIME_Absolute next_tx_time,
+  struct GNUNET_TIME_Relative validation_duration,
+  uint32_t *tan,
   struct GNUNET_TIME_Absolute *last_tx_time,
-  uint32_t *last_pin,
+  uint32_t *pin_attempts_left,
   bool *pin_transmit);
 
 
diff --git a/src/include/challenger_database_plugin.h 
b/src/include/challenger_database_plugin.h
index b8c19eb..c3cf1ce 100644
--- a/src/include/challenger_database_plugin.h
+++ b/src/include/challenger_database_plugin.h
@@ -39,6 +39,18 @@ struct CHALLENGER_ValidationNonceP
 };
 
 
+/**
+ * Nonce to uniquely (and unpredictably) identify grants.
+ */
+struct CHALLENGER_AccessTokenP
+{
+  /**
+   * 256-bit nonce used to identify grants.
+   */
+  uint32_t value[256 / 32];
+};
+
+
 /**
  * Handle to interact with the database.
  *
@@ -244,10 +256,11 @@ struct CHALLENGER_DatabasePlugin
    * @param cls closure
    * @param nonce unique nonce to use to identify the validation
    * @param address the new address to validate
-   * @param next_tx_time tx time we must have sent earlier before to 
retransmit now
-   * @param[in,out] last_tx_time set to the last time when we (presumably) 
send a PIN to @a address, input should be current time to use if the existing 
value for tx_time is past @a next_tx_time
-   * @param[in,out] last_pin set to the PIN last send to @a address, input 
should be random PIN to use if address did not change
-   * @param[out] pin_transmit set to true if we should transmit @a last_pin to 
the @a address
+   * @param validation_duration minimum time between transmissions
+   * @param[in,out] tan set to the PIN/TAN last send to @a address, input 
should be random PIN/TAN to use if address did not change
+   * @param[out] last_tx_time set to the last time when we (presumably) send a 
PIN to @a address, input should be current time to use if the existing value 
for tx_time is past @a next_tx_time
+   * @param[out] pin_transmit set to true if we should transmit the @a 
last_pin to the @a address
+   * @param[out] pin_attempts_left set to number of attempts the user has left 
on this pin
    * @return transaction status:
    *   #GNUNET_DB_SUCCESS_ONE_RESULT if the address was changed
    *   #GNUNET_DB_SUCCESS_NO_RESULTS if we do not permit further changes to 
the address (attempts exhausted)
@@ -258,9 +271,10 @@ struct CHALLENGER_DatabasePlugin
     void *cls,
     const struct CHALLENGER_ValidationNonceP *nonce,
     const char *address,
-    struct GNUNET_TIME_Absolute next_tx_time,
+    struct GNUNET_TIME_Relative validation_duration,
+    uint32_t *tan,
     struct GNUNET_TIME_Absolute *last_tx_time,
-    uint32_t *last_pin,
+    uint32_t *pin_attempts_left,
     bool *pin_transmit);
 
 
@@ -310,5 +324,39 @@ struct CHALLENGER_DatabasePlugin
                     char **client_redirect_url);
 
 
+  /**
+   * Add access @a grant to address under @a nonce.
+   *
+   * @param cls closure
+   * @param nonce validation process to grant access to
+   * @param grant grant token that grants access
+   * @param grant_expiration for how long should the grant be valid
+   * @param address_expiration for how long after validation do we consider 
addresses to be valid
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*auth_add_grant)(void *cls,
+                    const struct CHALLENGER_ValidationNonceP *nonce,
+                    const struct CHALLENGER_AccessTokenP *grant,
+                    struct GNUNET_TIME_Relative grant_expiration,
+                    struct GNUNET_TIME_Relative address_expiration);
+
+
+  /**
+   * Return @a address which @a grant gives access to.
+   *
+   * @param cls closure
+   * @param grant grant token that grants access
+   * @param[out] address set to the address under @a grant
+   * @param[out] address_expiration set to how long we consider @a address to 
be valid
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*info_get_grant)(void *cls,
+                    const struct CHALLENGER_AccessTokenP *grant,
+                    char **address,
+                    struct GNUNET_TIME_Timestamp *address_expiration);
+
+
 };
 #endif

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]