challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

challenger-httpd_challenge.c (28252B)


      1 /*
      2   This file is part of Challenger
      3   Copyright (C) 2023, 2024 Taler Systems SA
      4 
      5   Challenger is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   Challenger is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file challenger-httpd_challenge.c
     18  * @brief functions to handle incoming /challenge requests
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "challenger-httpd.h"
     23 #include <regex.h>
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_pq_lib.h>
     26 #include "challenger-httpd_challenge.h"
     27 #include <taler/taler_json_lib.h>
     28 #include <taler/taler_templating_lib.h>
     29 #include <taler/taler_signatures.h>
     30 #include "challenger-database/challenge_set_address_and_pin.h"
     31 #include "challenger-database/address_get.h"
     32 #include "challenger-httpd_common.h"
     33 
     34 /**
     35  * Maximum number of retries for the database interaction.
     36  */
     37 #define MAX_RETRIES 3
     38 
     39 /**
     40  * Set to 1 to dump addresses into the log.
     41  */
     42 #define DEBUG 0
     43 
     44 /**
     45  * Context for a /challenge operation.
     46  */
     47 struct ChallengeContext
     48 {
     49 
     50   /**
     51    * Nonce of the operation.
     52    */
     53   struct CHALLENGER_ValidationNonceP nonce;
     54 
     55   /**
     56    * Kept in a DLL.
     57    */
     58   struct ChallengeContext *next;
     59 
     60   /**
     61    * Kept in a DLL.
     62    */
     63   struct ChallengeContext *prev;
     64 
     65   /**
     66    * Our handler context.
     67    */
     68   struct CH_HandlerContext *hc;
     69 
     70   /**
     71    * Handle to the helper process.
     72    */
     73   struct GNUNET_Process *child;
     74 
     75   /**
     76    * Handle to wait for @e child
     77    */
     78   struct GNUNET_ChildWaitHandle *cwh;
     79 
     80   /**
     81    * Handle for processing uploaded data.
     82    */
     83   struct MHD_PostProcessor *pp;
     84 
     85   /**
     86    * Where we store the collected address data.
     87    */
     88   json_t *address;
     89 
     90   /**
     91    * Last key during POST processing.
     92    */
     93   char *last_key;
     94 
     95   /**
     96    * Uploaded data during POST processing.
     97    */
     98   char *data;
     99 
    100   /**
    101    * Where to redirect the client on errors?
    102    */
    103   char *client_redirect_uri;
    104 
    105   /**
    106    * OAuth2 state.
    107    */
    108   char *state;
    109 
    110   /**
    111    * Buffer used by #TALER_MHD_parse_post_json(). Or NULL.
    112    */
    113   void *jbuffer;
    114 
    115   /**
    116    * When did we transmit last?
    117    */
    118   struct GNUNET_TIME_Absolute last_tx_time;
    119 
    120   /**
    121    * Exit code from helper.
    122    */
    123   unsigned long int exit_code;
    124 
    125   /**
    126    * Number of bytes in @a address, excluding 0-terminator.
    127    */
    128   size_t data_len;
    129 
    130   /**
    131    * Our tan.
    132    */
    133   uint32_t tan;
    134 
    135   /**
    136    * How many attempts does the user have left?
    137    */
    138   uint32_t pin_attempts_left;
    139 
    140   /**
    141    * How did the helper die?
    142    */
    143   enum GNUNET_OS_ProcessStatusType pst;
    144 
    145   /**
    146    * Connection status. #GNUNET_OK to continue
    147    * normally, #GNUNET_NO if an error was already
    148    * returned, #GNUNET_SYSERR if we failed to
    149    * return an error and should just return #MHD_NO.
    150    */
    151   enum GNUNET_GenericReturnValue status;
    152 
    153   /**
    154    * #GNUNET_YES if we are suspended in #bc_head,
    155    * #GNUNET_NO if operating normally,
    156    * #GNUNET_SYSERR if resumed by shutdown (end with #MHD_NO)
    157    */
    158   enum GNUNET_GenericReturnValue suspended;
    159 
    160   /**
    161    * True if the provided address was refused, usually because
    162    * the user tried too many different addresses already.
    163    */
    164   bool address_refused;
    165 
    166   /**
    167    * Should we retransmit the PIN?
    168    */
    169   bool retransmit;
    170 
    171   /**
    172    * Is the challenge already solved?
    173    */
    174   bool solved;
    175 
    176   /**
    177    * Did we do the DB interaction?
    178    */
    179   bool db_finished;
    180 
    181   /**
    182    * Is the upload in JSON?
    183    */
    184   bool is_json;
    185 };
    186 
    187 
    188 /**
    189  * Head of suspended challenger contexts.
    190  */
    191 struct ChallengeContext *bc_head;
    192 
    193 /**
    194  * Tail of suspended challenger contexts.
    195  */
    196 struct ChallengeContext *bc_tail;
    197 
    198 
    199 void
    200 CH_wakeup_challenge_on_shutdown ()
    201 {
    202   struct ChallengeContext *bc;
    203 
    204   while (NULL != (bc = bc_head))
    205   {
    206     GNUNET_CONTAINER_DLL_remove (bc_head,
    207                                  bc_tail,
    208                                  bc);
    209     MHD_resume_connection (bc->hc->connection);
    210     bc->suspended = GNUNET_SYSERR;
    211   }
    212 }
    213 
    214 
    215 /**
    216  * Function called to clean up a backup context.
    217  *
    218  * @param cls a `struct ChallengeContext`
    219  */
    220 static void
    221 cleanup_ctx (void *cls)
    222 {
    223   struct ChallengeContext *bc = cls;
    224 
    225   if (NULL != bc->pp)
    226   {
    227     GNUNET_break_op (MHD_YES ==
    228                      MHD_destroy_post_processor (bc->pp));
    229   }
    230   if (NULL != bc->cwh)
    231   {
    232     GNUNET_wait_child_cancel (bc->cwh);
    233     bc->cwh = NULL;
    234   }
    235   if (NULL != bc->child)
    236   {
    237     GNUNET_break (GNUNET_OK ==
    238                   GNUNET_process_kill (bc->child,
    239                                        SIGKILL));
    240     GNUNET_break (GNUNET_OK ==
    241                   GNUNET_process_wait (bc->child,
    242                                        true,
    243                                        NULL,
    244                                        NULL));
    245     GNUNET_process_destroy (bc->child);
    246     bc->child = NULL;
    247   }
    248   TALER_MHD_parse_post_cleanup_callback (bc->jbuffer);
    249   json_decref (bc->address);
    250   GNUNET_free (bc->data);
    251   GNUNET_free (bc->state);
    252   GNUNET_free (bc->last_key);
    253   GNUNET_free (bc->client_redirect_uri);
    254   GNUNET_free (bc);
    255 }
    256 
    257 
    258 /**
    259  * Generate error reply in the format requested by
    260  * the client.
    261  *
    262  * @param bc our context
    263  * @param template error template to use
    264  * @param http_status HTTP status to return
    265  * @param ec error code to return
    266  * @param hint human-readable hint to give
    267  */
    268 static enum MHD_Result
    269 reply_error (struct ChallengeContext *bc,
    270              const char *template,
    271              unsigned int http_status,
    272              enum TALER_ErrorCode ec,
    273              const char *hint)
    274 {
    275   struct CH_HandlerContext *hc = bc->hc;
    276 
    277   return TALER_MHD_reply_with_error (
    278     hc->connection,
    279     http_status,
    280     ec,
    281     hint);
    282 }
    283 
    284 
    285 /**
    286  * Function called when our PIN transmission helper has terminated.
    287  *
    288  * @param cls our `struct ChallengeContext *`
    289  * @param type type of the process
    290  * @param exit_code status code of the process
    291  */
    292 static void
    293 child_done_cb (void *cls,
    294                enum GNUNET_OS_ProcessStatusType type,
    295                long unsigned int exit_code)
    296 {
    297   struct ChallengeContext *bc = cls;
    298   struct GNUNET_AsyncScopeSave old_scope;
    299 
    300   GNUNET_async_scope_enter (&bc->hc->async_scope_id,
    301                             &old_scope);
    302   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    303               "Child done with exit code %d/%llu\n",
    304               (int) type,
    305               (unsigned long long) exit_code);
    306   GNUNET_process_destroy (bc->child);
    307   bc->child = NULL;
    308   bc->cwh = NULL;
    309   bc->pst = type;
    310   bc->exit_code = exit_code;
    311   bc->suspended = GNUNET_NO;
    312   MHD_resume_connection (bc->hc->connection);
    313   GNUNET_CONTAINER_DLL_remove (bc_head,
    314                                bc_tail,
    315                                bc);
    316   CH_trigger_daemon ();
    317   GNUNET_async_scope_restore (&old_scope);
    318 }
    319 
    320 
    321 /**
    322  * Transmit the TAN to the given address.
    323  *
    324  * @param[in,out] bc context to submit TAN for
    325  */
    326 static void
    327 send_tan (struct ChallengeContext *bc)
    328 {
    329   struct GNUNET_DISK_PipeHandle *p;
    330   struct GNUNET_DISK_FileHandle *pipe_stdin;
    331   void *msg;
    332   size_t msg_len;
    333 
    334   p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
    335   if (NULL == p)
    336   {
    337     enum MHD_Result mres;
    338 
    339     GNUNET_break (0);
    340     mres = TALER_MHD_reply_with_error (
    341       bc->hc->connection,
    342       MHD_HTTP_BAD_GATEWAY,
    343       TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
    344       "pipe");
    345     bc->status = (MHD_YES == mres)
    346       ? GNUNET_NO
    347       : GNUNET_SYSERR;
    348     return;
    349   }
    350   {
    351     char *address;
    352 
    353     address = json_dumps (bc->address,
    354                           JSON_COMPACT);
    355 #if DEBUG
    356     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    357                 "Running auth command `%s' on address `%s'\n",
    358                 CH_auth_command,
    359                 address);
    360 #endif
    361     bc->child = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    362     GNUNET_assert (GNUNET_OK ==
    363                    GNUNET_process_set_options (
    364                      bc->child,
    365                      GNUNET_process_option_inherit_rpipe (p,
    366                                                           STDIN_FILENO)));
    367     if (GNUNET_OK !=
    368         GNUNET_process_run_command_va (bc->child,
    369                                        CH_auth_command,
    370                                        CH_auth_command,
    371                                        address,
    372                                        NULL))
    373     {
    374       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    375                            "exec");
    376       GNUNET_process_destroy (bc->child);
    377       bc->child = NULL;
    378     }
    379     free (address);
    380   }
    381   if (NULL == bc->child)
    382   {
    383     enum MHD_Result mres;
    384 
    385     GNUNET_break (0);
    386     GNUNET_break (GNUNET_OK ==
    387                   GNUNET_DISK_pipe_close (p));
    388     mres = TALER_MHD_reply_with_error (
    389       bc->hc->connection,
    390       MHD_HTTP_BAD_GATEWAY,
    391       TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
    392       "exec");
    393     bc->status = (MHD_YES == mres)
    394       ? GNUNET_NO
    395       : GNUNET_SYSERR;
    396     return;
    397   }
    398   pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
    399                                             GNUNET_DISK_PIPE_END_WRITE);
    400   GNUNET_assert (NULL != pipe_stdin);
    401   GNUNET_break (GNUNET_OK ==
    402                 GNUNET_DISK_pipe_close (p));
    403   if (0 != CH_message_template_len)
    404   {
    405     json_t *root;
    406     int mret;
    407 
    408     root = GNUNET_JSON_PACK (
    409       GNUNET_JSON_pack_object_incref ("address",
    410                                       bc->address),
    411       GNUNET_JSON_pack_int64 ("pin",
    412                               bc->tan));
    413     mret = TALER_TEMPLATING_fill2 (CH_message_template,
    414                                    CH_message_template_len,
    415                                    root,
    416                                    &msg,
    417                                    &msg_len);
    418     json_decref (root);
    419     if (0 != mret)
    420     {
    421       enum MHD_Result mres;
    422 
    423       GNUNET_break (0);
    424       mres = TALER_MHD_reply_with_error (
    425         bc->hc->connection,
    426         MHD_HTTP_INTERNAL_SERVER_ERROR,
    427         TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
    428         NULL);
    429       GNUNET_DISK_file_close (pipe_stdin);
    430       bc->status = (MHD_YES == mres)
    431           ? GNUNET_NO
    432           : GNUNET_SYSERR;
    433       return;
    434     }
    435   }
    436   else
    437   {
    438     char *cmsg;
    439 
    440     GNUNET_asprintf (&cmsg,
    441                      "PIN: %u",
    442                      (unsigned int) bc->tan);
    443     msg_len = strlen (cmsg);
    444     msg = cmsg;
    445   }
    446   {
    447     const char *off = msg;
    448     size_t left = msg_len;
    449 
    450     while (0 != left)
    451     {
    452       ssize_t ret;
    453 
    454       ret = GNUNET_DISK_file_write (pipe_stdin,
    455                                     off,
    456                                     left);
    457       if (ret <= 0)
    458       {
    459         enum MHD_Result mres;
    460 
    461         GNUNET_break (0);
    462         mres = TALER_MHD_reply_with_error (
    463           bc->hc->connection,
    464           MHD_HTTP_BAD_GATEWAY,
    465           TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
    466           "write");
    467         GNUNET_DISK_file_close (pipe_stdin);
    468         GNUNET_free (msg);
    469         bc->status = (MHD_YES == mres)
    470           ? GNUNET_NO
    471           : GNUNET_SYSERR;
    472         return;
    473       }
    474       off += (size_t) ret;
    475       left -= (size_t) ret;
    476     }
    477     GNUNET_DISK_file_close (pipe_stdin);
    478   }
    479   GNUNET_free (msg);
    480   bc->cwh = GNUNET_wait_child (bc->child,
    481                                &child_done_cb,
    482                                bc);
    483   MHD_suspend_connection (bc->hc->connection);
    484   bc->suspended = GNUNET_YES;
    485   GNUNET_CONTAINER_DLL_insert (bc_head,
    486                                bc_tail,
    487                                bc);
    488 }
    489 
    490 
    491 /**
    492  * Iterator over key-value pairs where the value may be made available
    493  * in increments and/or may not be zero-terminated.  Used for
    494  * processing POST data.
    495  *
    496  * @param cls a `struct ChallengeContext *`
    497  * @param kind type of the value, always #MHD_POSTDATA_KIND when called from MHD
    498  * @param key 0-terminated key for the value
    499  * @param filename name of the uploaded file, NULL if not known
    500  * @param content_type mime-type of the data, NULL if not known
    501  * @param transfer_encoding encoding of the data, NULL if not known
    502  * @param data pointer to @a size bytes of data at the
    503  *              specified offset
    504  * @param off offset of data in the overall value
    505  * @param size number of bytes in @a data available
    506  * @return #MHD_YES to continue iterating,
    507  *         #MHD_NO to abort the iteration
    508  */
    509 static enum MHD_Result
    510 post_iter (void *cls,
    511            enum MHD_ValueKind kind,
    512            const char *key,
    513            const char *filename,
    514            const char *content_type,
    515            const char *transfer_encoding,
    516            const char *data,
    517            uint64_t off,
    518            size_t size)
    519 {
    520   struct ChallengeContext *bc = cls;
    521 
    522   (void) filename;
    523   (void) content_type;
    524   (void) transfer_encoding;
    525   (void) off;
    526   if (MHD_POSTDATA_KIND != kind)
    527     return MHD_YES;
    528   if ( (NULL != bc->last_key) &&
    529        (0 != strcmp (key,
    530                      bc->last_key)) )
    531   {
    532     GNUNET_assert (0 ==
    533                    json_object_set_new (bc->address,
    534                                         bc->last_key,
    535                                         json_string (bc->data)));
    536     GNUNET_free (bc->data);
    537     bc->data_len = 0;
    538     GNUNET_free (bc->last_key);
    539   }
    540   if (NULL == bc->last_key)
    541   {
    542     bc->last_key = GNUNET_strdup (key);
    543   }
    544   bc->data = GNUNET_realloc (bc->data,
    545                              bc->data_len + size + 1);
    546   memcpy (bc->data + bc->data_len,
    547           data,
    548           size);
    549   bc->data_len += size;
    550   bc->data[bc->data_len] = '\0';
    551   return MHD_YES;
    552 }
    553 
    554 
    555 /**
    556  * Check if the given address satisfies our restrictions.
    557  *
    558  * @param address address data provided by the client
    559  * @return NULL on success, otherwise the key that failed
    560  */
    561 static const char *
    562 check_restrictions (const json_t *address)
    563 {
    564   const char *key;
    565   const json_t *val;
    566 
    567   json_object_foreach ((json_t *) address, key, val)
    568   {
    569     const char *str = json_string_value (val);
    570     const char *regex = json_string_value (
    571       json_object_get (
    572         json_object_get (CH_restrictions,
    573                          key),
    574         "regex"));
    575     regex_t re;
    576 
    577     if (0 == strcasecmp (key,
    578                          "read_only"))
    579       continue;
    580     if (NULL == str)
    581       return key;
    582     if (NULL == regex)
    583       continue;
    584     if (0 != regcomp (&re,
    585                       regex,
    586                       REG_EXTENDED))
    587     {
    588       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    589                   "Invalid regex `%s' address restriction specified for `%s'\n",
    590                   regex,
    591                   key);
    592       continue;
    593     }
    594     if (0 != regexec (&re,
    595                       str,
    596                       0,
    597                       NULL,
    598                       0))
    599     {
    600       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    601                   "Client input `%s' rejected as it does not match address restriction `%s' specified for `%s'\n",
    602                   str,
    603                   regex,
    604                   key);
    605       return key;
    606     }
    607     regfree (&re);
    608   }
    609   return NULL;
    610 }
    611 
    612 
    613 enum MHD_Result
    614 CH_handler_challenge (struct CH_HandlerContext *hc,
    615                       const char *upload_data,
    616                       size_t *upload_data_size)
    617 {
    618   struct ChallengeContext *bc = hc->ctx;
    619 
    620   if (NULL == bc)
    621   {
    622     /* first call, setup internals */
    623     bc = GNUNET_new (struct ChallengeContext);
    624     bc->status = GNUNET_OK;
    625     bc->hc = hc;
    626     hc->cc = &cleanup_ctx;
    627     hc->ctx = bc;
    628     bc->pst = GNUNET_OS_PROCESS_UNKNOWN;
    629     bc->tan
    630       = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
    631                                   100000000);
    632     bc->pp = MHD_create_post_processor (hc->connection,
    633                                         1024,
    634                                         &post_iter,
    635                                         bc);
    636     if (GNUNET_OK !=
    637         GNUNET_STRINGS_string_to_data (hc->path,
    638                                        strlen (hc->path),
    639                                        &bc->nonce,
    640                                        sizeof (bc->nonce)))
    641     {
    642       GNUNET_break_op (0);
    643       return TALER_MHD_reply_with_error (
    644         hc->connection,
    645         MHD_HTTP_NOT_FOUND,
    646         TALER_EC_GENERIC_PARAMETER_MISSING,
    647         hc->path);
    648     }
    649     {
    650       const char *ct;
    651 
    652       ct = MHD_lookup_connection_value (hc->connection,
    653                                         MHD_HEADER_KIND,
    654                                         MHD_HTTP_HEADER_CONTENT_TYPE);
    655       bc->is_json = ( (NULL != ct) &&
    656                       (0 == strcasecmp (ct,
    657                                         "application/json")) );
    658       if (! bc->is_json)
    659         bc->address = json_object ();
    660       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    661                   "Processing /challenge upload with %s encoding...\n",
    662                   ct);
    663     }
    664     TALER_MHD_check_content_length (hc->connection,
    665                                     1024);
    666     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    667                 "Awaiting /challenge upload (%u)...\n",
    668                 (unsigned int) *upload_data_size);
    669     return MHD_YES;
    670   }
    671   GNUNET_assert (GNUNET_YES != bc->suspended);
    672   if (GNUNET_SYSERR == bc->suspended)
    673   {
    674     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    675                 "/challenge ends in shutdown\n");
    676     return MHD_NO;
    677   }
    678   /* Handle case where helper process failed */
    679   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    680               "Checking child process status: %d / %d (%s)\n",
    681               (int) bc->pst,
    682               (int) bc->exit_code,
    683               NULL == bc->child ? "dead" : "running");
    684   if ( (GNUNET_OS_PROCESS_UNKNOWN != bc->pst) &&
    685        (NULL == bc->child) &&
    686        ( (GNUNET_OS_PROCESS_EXITED != bc->pst) ||
    687          (0 != bc->exit_code) ) )
    688   {
    689     char es[32];
    690 
    691     GNUNET_break (0);
    692     GNUNET_snprintf (es,
    693                      sizeof (es),
    694                      "%u/%d",
    695                      (unsigned int) bc->exit_code,
    696                      bc->pst);
    697     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    698                 "Helper failed with status %d/%d\n",
    699                 (int) bc->pst,
    700                 (int) bc->exit_code);
    701     return TALER_MHD_reply_with_error (
    702       hc->connection,
    703       MHD_HTTP_BAD_GATEWAY,
    704       TALER_EC_CHALLENGER_HELPER_EXEC_FAILED,
    705       es);
    706   }
    707   /* handle upload */
    708   if (bc->is_json)
    709   {
    710     if (NULL == bc->address)
    711     {
    712       enum GNUNET_GenericReturnValue res;
    713 
    714       res = TALER_MHD_parse_post_json (hc->connection,
    715                                        &bc->jbuffer,
    716                                        upload_data,
    717                                        upload_data_size,
    718                                        &bc->address);
    719       if (GNUNET_SYSERR != res)
    720         return MHD_YES;
    721       GNUNET_break (0);
    722       return MHD_NO;
    723     }
    724     else
    725     {
    726       GNUNET_break (0 == *upload_data_size);
    727     }
    728   }
    729   else if (0 != *upload_data_size)
    730   {
    731     enum MHD_Result res;
    732 
    733     res = MHD_post_process (bc->pp,
    734                             upload_data,
    735                             *upload_data_size);
    736     *upload_data_size = 0;
    737     if (MHD_YES == res)
    738       return MHD_YES;
    739     GNUNET_break (0);
    740     return MHD_NO;
    741   }
    742   if (NULL != bc->last_key)
    743   {
    744     GNUNET_assert (0 ==
    745                    json_object_set_new (bc->address,
    746                                         bc->last_key,
    747                                         json_string (bc->data)));
    748     GNUNET_free (bc->data);
    749     bc->data_len = 0;
    750     GNUNET_free (bc->last_key);
    751   }
    752 #if DEBUG
    753   {
    754     char *address;
    755 
    756     address = json_dumps (bc->address,
    757                           JSON_COMPACT);
    758     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    759                 "Submitted address is `%s'\n",
    760                 address);
    761     free (address);
    762   }
    763 #endif
    764   {
    765     const char *bad_field;
    766 
    767     bad_field = check_restrictions (bc->address);
    768     if (NULL != bad_field)
    769     {
    770       GNUNET_break_op (0);
    771       return reply_error (bc,
    772                           "invalid-request",
    773                           MHD_HTTP_BAD_REQUEST,
    774                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    775                           bad_field);
    776     }
    777   }
    778   if (! bc->db_finished)
    779   {
    780     for (unsigned int r = 0; r < MAX_RETRIES; r++)
    781     {
    782       enum GNUNET_DB_QueryStatus qs;
    783       json_t *old_address;
    784       const json_t *ro;
    785 
    786       GNUNET_assert (NULL == bc->client_redirect_uri);
    787       qs = CHALLENGERDB_address_get (CH_context,
    788                                      &bc->nonce,
    789                                      &old_address);
    790       switch (qs)
    791       {
    792       case GNUNET_DB_STATUS_HARD_ERROR:
    793         GNUNET_break (0);
    794         return reply_error (bc,
    795                             "internal-error",
    796                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    797                             TALER_EC_GENERIC_DB_FETCH_FAILED,
    798                             "validation-get");
    799       case GNUNET_DB_STATUS_SOFT_ERROR:
    800         if (r < MAX_RETRIES - 1)
    801           continue;
    802         GNUNET_break (0);
    803         return reply_error (bc,
    804                             "internal-error",
    805                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    806                             TALER_EC_GENERIC_DB_FETCH_FAILED,
    807                             "validation-get");
    808       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    809         GNUNET_break_op (0);
    810         return reply_error (bc,
    811                             "validation-unknown",
    812                             MHD_HTTP_NOT_FOUND,
    813                             TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN,
    814                             NULL);
    815       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    816         break;
    817       }
    818       ro = json_object_get (old_address,
    819                             "read_only");
    820       if ( (NULL != ro) &&
    821            (json_boolean_value (ro)) &&
    822            (1 != json_equal (old_address,
    823                              bc->address)) )
    824       {
    825         GNUNET_break_op (0);
    826         json_decref (old_address);
    827         return reply_error (bc,
    828                             "address-read-only",
    829                             MHD_HTTP_FORBIDDEN,
    830                             TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_READ_ONLY,
    831                             NULL);
    832       }
    833       json_decref (old_address);
    834 
    835       qs = CHALLENGERDB_challenge_set_address_and_pin (CH_context,
    836                                                        &bc->nonce,
    837                                                        bc->address,
    838                                                        CH_validation_duration,
    839                                                        &bc->tan,
    840                                                        &bc->state,
    841                                                        &bc->last_tx_time,
    842                                                        &bc->pin_attempts_left,
    843                                                        &bc->retransmit,
    844                                                        &bc->client_redirect_uri,
    845                                                        &bc->address_refused,
    846                                                        &bc->solved);
    847       switch (qs)
    848       {
    849       case GNUNET_DB_STATUS_HARD_ERROR:
    850         GNUNET_break (0);
    851         return reply_error (bc,
    852                             "internal-error",
    853                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    854                             TALER_EC_GENERIC_DB_STORE_FAILED,
    855                             "set-address-and-pin");
    856       case GNUNET_DB_STATUS_SOFT_ERROR:
    857         if (r < MAX_RETRIES - 1)
    858           continue;
    859         GNUNET_break (0);
    860         return reply_error (bc,
    861                             "internal-error",
    862                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    863                             TALER_EC_GENERIC_DB_STORE_FAILED,
    864                             "set-address-and-pin");
    865       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    866         GNUNET_break_op (0);
    867         return reply_error (bc,
    868                             "validation-unknown",
    869                             MHD_HTTP_NOT_FOUND,
    870                             TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN,
    871                             NULL);
    872       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    873         break;
    874       }
    875       break;
    876     }
    877     bc->db_finished = true;
    878     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    879                 "Database interaction done (challenge %s)\n",
    880                 bc->solved ? "solved" : "unsolved");
    881     if (bc->solved)
    882     {
    883       enum GNUNET_GenericReturnValue ret;
    884       char *url;
    885       struct MHD_Response *response;
    886       json_t *args;
    887 
    888       ret = CH_build_full_redirect_url (&bc->nonce,
    889                                         hc->connection,
    890                                         &url);
    891       if (GNUNET_OK != ret)
    892         return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
    893       args = GNUNET_JSON_PACK (
    894         GNUNET_JSON_pack_string ("type",
    895                                  "completed"),
    896         GNUNET_JSON_pack_string ("redirect_url",
    897                                  url)
    898         );
    899       GNUNET_free (url);
    900       response = TALER_MHD_make_json (args);
    901       ret = MHD_queue_response (hc->connection,
    902                                 MHD_HTTP_OK,
    903                                 response);
    904       MHD_destroy_response (response);
    905       return ret;
    906     }
    907     if (bc->address_refused)
    908     {
    909       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    910                   "Address changes exhausted address change limit for this process\n");
    911       return reply_error (bc,
    912                           "unauthorized_client",
    913                           MHD_HTTP_TOO_MANY_REQUESTS,
    914                           TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS,
    915                           "client exceeded authorization attempts limit (too many addresses attempted)");
    916 
    917     }
    918     if (0 == bc->pin_attempts_left)
    919     {
    920       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    921                   "Address changes exhausted PIN limit for this address\n");
    922       return reply_error (bc,
    923                           "unauthorized_client",
    924                           MHD_HTTP_TOO_MANY_REQUESTS,
    925                           TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS,
    926                           "client exceeded authorization attempts limit (too many PINs)");
    927     }
    928 
    929     if (bc->retransmit)
    930     {
    931       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    932                   "Transmitting PIN\n");
    933       /* (Re)transmit PIN/TAN */
    934       send_tan (bc);
    935       if (GNUNET_YES == bc->suspended)
    936       {
    937         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    938                     "Suspending request after PIN transmission\n");
    939         return MHD_YES;
    940       }
    941       /* Did we already try to generate a response? */
    942       if (GNUNET_OK != bc->status)
    943         return (GNUNET_NO == bc->status)
    944           ? MHD_YES
    945           : MHD_NO;
    946     }
    947   }
    948 
    949   {
    950     json_t *args;
    951     struct MHD_Response *resp;
    952     unsigned int http_status;
    953     enum MHD_Result res;
    954 
    955     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    956                 "Returning success from challenge with %u attempts left\n",
    957                 (unsigned int) bc->pin_attempts_left);
    958     args = GNUNET_JSON_PACK (
    959       GNUNET_JSON_pack_uint64 ("attempts_left",
    960                                bc->pin_attempts_left),
    961       GNUNET_JSON_pack_string ("type",
    962                                "created"),
    963       GNUNET_JSON_pack_object_incref ("address",
    964                                       bc->address),
    965       GNUNET_JSON_pack_bool ("transmitted",
    966                              bc->retransmit),
    967       GNUNET_JSON_pack_timestamp ("retransmission_time",
    968                                   GNUNET_TIME_absolute_to_timestamp (
    969                                     GNUNET_TIME_absolute_add (
    970                                       bc->last_tx_time,
    971                                       CH_validation_duration)))
    972       );
    973     http_status = MHD_HTTP_OK;
    974     resp = TALER_MHD_make_json_steal (args);
    975     GNUNET_break (MHD_YES ==
    976                   MHD_add_response_header (resp,
    977                                            MHD_HTTP_HEADER_CACHE_CONTROL,
    978                                            "no-store,no-cache"));
    979     res = MHD_queue_response (hc->connection,
    980                               http_status,
    981                               resp);
    982     MHD_destroy_response (resp);
    983     return res;
    984   }
    985 }