merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_post-challenge-ID.c (23719B)


      1 /*
      2   This file is part of TALER
      3   (C) 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_post-challenge-ID.c
     22  * @brief endpoint to trigger sending MFA challenge
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd.h"
     27 #include "taler-merchant-httpd_mfa.h"
     28 #include "taler-merchant-httpd_post-challenge-ID.h"
     29 #include "merchant-database/get_mfa_challenge.h"
     30 #include "merchant-database/update_mfa_challenge.h"
     31 
     32 
     33 /**
     34  * How many attempts do we allow per solution at most? Note that
     35  * this is just for the API, the value must also match the
     36  * database logic in insert_mfa_challenge.
     37  */
     38 #define MAX_SOLUTIONS 3
     39 
     40 
     41 /**
     42  * How long is an OTP code valid?
     43  */
     44 #define OTP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
     45 
     46 
     47 /**
     48  * Internal state for MFA processing.
     49  */
     50 struct MfaState
     51 {
     52 
     53   /**
     54    * Kept in a DLL.
     55    */
     56   struct MfaState *next;
     57 
     58   /**
     59    * Kept in a DLL.
     60    */
     61   struct MfaState *prev;
     62 
     63   /**
     64    * HTTP request we are handling.
     65    */
     66   struct TMH_HandlerContext *hc;
     67 
     68   /**
     69    * Challenge code.
     70    */
     71   char *code;
     72 
     73   /**
     74    * When does @e code expire?
     75    */
     76   struct GNUNET_TIME_Absolute expiration_date;
     77 
     78   /**
     79    * When may we transmit a new code?
     80    */
     81   struct GNUNET_TIME_Absolute retransmission_date;
     82 
     83   /**
     84    * Handle to the helper process.
     85    */
     86   struct GNUNET_Process *child;
     87 
     88   /**
     89    * Handle to wait for @e child
     90    */
     91   struct GNUNET_ChildWaitHandle *cwh;
     92 
     93   /**
     94    * Address where to send the challenge.
     95    */
     96   char *required_address;
     97 
     98   /**
     99    * Message to send.
    100    */
    101   char *msg;
    102 
    103   /**
    104    * Instance the challenge is for.
    105    */
    106   char *instance_id;
    107 
    108   /**
    109    * Offset of transmission in msg.
    110    */
    111   size_t msg_off;
    112 
    113   /**
    114    * ID of our challenge.
    115    */
    116   uint64_t challenge_id;
    117 
    118   /**
    119    * Salted hash over the request body.
    120    */
    121   struct TALER_MERCHANT_MFA_BodyHash h_body;
    122 
    123   /**
    124    * Channel to use for the challenge.
    125    */
    126   enum TALER_MERCHANT_MFA_Channel channel;
    127 
    128   enum
    129   {
    130     MFA_PHASE_PARSE = 0,
    131     MFA_PHASE_LOOKUP,
    132     MFA_PHASE_SENDING,
    133     MFA_PHASE_SUSPENDING,
    134     MFA_PHASE_SENT,
    135     MFA_PHASE_RETURN_YES,
    136     MFA_PHASE_RETURN_NO,
    137 
    138   } phase;
    139 
    140 
    141   /**
    142    * #GNUNET_NO if the @e connection was not suspended,
    143    * #GNUNET_YES if the @e connection was suspended,
    144    * #GNUNET_SYSERR if @e connection was resumed to as
    145    * part of #THM_mfa_done during shutdown.
    146    */
    147   enum GNUNET_GenericReturnValue suspended;
    148 
    149   /**
    150    * Type of critical operation being authorized.
    151    */
    152   enum TALER_MERCHANT_MFA_CriticalOperation op;
    153 
    154   /**
    155    * Set to true if sending worked.
    156    */
    157   bool send_ok;
    158 
    159   /**
    160    * Exit status of the transmission helper, valid if it exited normally.
    161    * Retained (and not just collapsed into @e send_ok) because the helpers
    162    * classify *why* transmission failed; see classify_helper_status().
    163    */
    164   unsigned long int exit_code;
    165 
    166   /**
    167    * True if the transmission helper exited normally, so that @e exit_code
    168    * is meaningful.
    169    */
    170   bool exited;
    171 };
    172 
    173 
    174 /**
    175  * Kept in a DLL.
    176  */
    177 static struct MfaState *mfa_head;
    178 
    179 /**
    180  * Kept in a DLL.
    181  */
    182 static struct MfaState *mfa_tail;
    183 
    184 
    185 /**
    186  * Clean up @a mfa process.
    187  *
    188  * @param[in] cls the `struct MfaState` to clean up
    189  */
    190 static void
    191 mfa_context_cleanup (void *cls)
    192 {
    193   struct MfaState *mfa = cls;
    194 
    195   GNUNET_CONTAINER_DLL_remove (mfa_head,
    196                                mfa_tail,
    197                                mfa);
    198   if (NULL != mfa->cwh)
    199   {
    200     GNUNET_wait_child_cancel (mfa->cwh);
    201     mfa->cwh = NULL;
    202   }
    203   if (NULL != mfa->child)
    204   {
    205     GNUNET_break (GNUNET_OK ==
    206                   GNUNET_process_kill (mfa->child,
    207                                        SIGKILL));
    208     GNUNET_break (GNUNET_OK ==
    209                   GNUNET_process_wait (mfa->child,
    210                                        true,
    211                                        NULL,
    212                                        NULL));
    213     GNUNET_process_destroy (mfa->child);
    214     mfa->child = NULL;
    215   }
    216   GNUNET_free (mfa->required_address);
    217   GNUNET_free (mfa->msg);
    218   GNUNET_free (mfa->instance_id);
    219   GNUNET_free (mfa->code);
    220   GNUNET_free (mfa);
    221 }
    222 
    223 
    224 void
    225 TMH_challenge_done ()
    226 {
    227   for (struct MfaState *mfa = mfa_head;
    228        NULL != mfa;
    229        mfa = mfa->next)
    230   {
    231     if (GNUNET_YES == mfa->suspended)
    232     {
    233       /* Make sure transmission_done_cb() cannot fire after
    234          we resumed the connection here. */
    235       if (NULL != mfa->cwh)
    236       {
    237         GNUNET_wait_child_cancel (mfa->cwh);
    238         mfa->cwh = NULL;
    239       }
    240       mfa->suspended = GNUNET_SYSERR;
    241       MHD_resume_connection (mfa->hc->connection);
    242     }
    243   }
    244 }
    245 
    246 
    247 /**
    248  * Send the given @a response for the @a mfa request.
    249  *
    250  * @param[in,out] mfa process to generate an error response for
    251  * @param response_code response code to use
    252  * @param[in] response response data to send back
    253  */
    254 static void
    255 respond_to_challenge_with_response (struct MfaState *mfa,
    256                                     unsigned int response_code,
    257                                     struct MHD_Response *response)
    258 {
    259   enum MHD_Result res;
    260 
    261   res = MHD_queue_response (mfa->hc->connection,
    262                             response_code,
    263                             response);
    264   MHD_destroy_response (response);
    265   mfa->phase = (MHD_NO == res)
    266     ? MFA_PHASE_RETURN_NO
    267     : MFA_PHASE_RETURN_YES;
    268 }
    269 
    270 
    271 /**
    272  * Generate an error for @a mfa.
    273  *
    274  * @param[in,out] mfa process to generate an error response for
    275  * @param http_status HTTP status of the response
    276  * @param ec Taler error code to return
    277  * @param hint hint to return, can be NULL
    278  */
    279 static void
    280 respond_with_error (struct MfaState *mfa,
    281                     unsigned int http_status,
    282                     enum TALER_ErrorCode ec,
    283                     const char *hint)
    284 {
    285   respond_to_challenge_with_response (
    286     mfa,
    287     http_status,
    288     TALER_MHD_make_error (ec,
    289                           hint));
    290 }
    291 
    292 
    293 /**
    294  * Did a challenge transmission helper report that the challenge was
    295  * transmitted?
    296  *
    297  * Exit code 0 means the challenge was confirmed to have reached the address.
    298  * The 200-210 band means a service accepted it for delivery without confirming
    299  * that it arrived: 201 accepted by the provider, 202 suppressed as a duplicate
    300  * of a message already in flight.
    301  *
    302  * The band deliberately does not start at 1: libgnunetutil reports a helper it
    303  * failed to exec() as exit code 1, and that must not be mistaken for a
    304  * delivery.
    305  *
    306  * @param exit_code exit status of the helper, which must have exited normally
    307  * @return true if the challenge was transmitted
    308  */
    309 static bool
    310 helper_reported_success (unsigned long int exit_code)
    311 {
    312   return (0 == exit_code) ||
    313          ( (exit_code >= 200) &&
    314            (exit_code <= 210) );
    315 }
    316 
    317 
    318 /**
    319  * Map the exit status of a challenge transmission helper onto an HTTP status
    320  * and error code.
    321  *
    322  * The helpers shipped with Challenger use a banded exit-code scheme documented
    323  * in challenger-send-sms(1): 0 and 200-210 mean the challenge was transmitted
    324  * (see #helper_reported_success()), 10-19 blames the address configured for
    325  * this step, 20-29 is a recipient that is temporarily unreachable, 30-39 is
    326  * the transmission provider and 40-49 is our own configuration.
    327  *
    328  * Codes we do not recognise are reported as an upstream failure rather than
    329  * blamed on the client, so that a helper predating this scheme -- which used
    330  * small ad-hoc exit codes -- never yields a client error.
    331  *
    332  * @param exit_code exit status of the helper, which must have exited normally
    333  * @param[out] http_status set to the HTTP status to return
    334  * @return error code to return
    335  */
    336 static enum TALER_ErrorCode
    337 classify_helper_status (unsigned long int exit_code,
    338                         unsigned int *http_status)
    339 {
    340   if ( (exit_code >= 10) &&
    341        (exit_code < 20) )
    342   {
    343     *http_status = MHD_HTTP_BAD_REQUEST;
    344     return TALER_EC_MERCHANT_TAN_ADDRESS_UNUSABLE;
    345   }
    346   if ( (exit_code >= 20) &&
    347        (exit_code < 30) )
    348   {
    349     *http_status = MHD_HTTP_SERVICE_UNAVAILABLE;
    350     return TALER_EC_MERCHANT_TAN_ADDRESS_UNREACHABLE;
    351   }
    352   if ( (exit_code >= 40) &&
    353        (exit_code < 50) )
    354   {
    355     *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    356     return TALER_EC_MERCHANT_TAN_HELPER_MISCONFIGURED;
    357   }
    358   *http_status = MHD_HTTP_BAD_GATEWAY;
    359   return TALER_EC_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED;
    360 }
    361 
    362 
    363 /**
    364  * Challenge code transmission complete. Continue based on the result.
    365  *
    366  * @param[in,out] mfa process to send the challenge for
    367  */
    368 static void
    369 phase_sent (struct MfaState *mfa)
    370 {
    371   enum GNUNET_DB_QueryStatus qs;
    372 
    373   if (! mfa->send_ok)
    374   {
    375     char es[32];
    376     unsigned int http_status;
    377     enum TALER_ErrorCode ec;
    378 
    379     if (! mfa->exited)
    380     {
    381       /* Killed by a signal or otherwise abnormal: there is no exit code to
    382          classify. */
    383       http_status = MHD_HTTP_BAD_GATEWAY;
    384       ec = TALER_EC_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED;
    385     }
    386     else
    387     {
    388       ec = classify_helper_status (mfa->exit_code,
    389                                    &http_status);
    390     }
    391     GNUNET_snprintf (es,
    392                      sizeof (es),
    393                      "process exited with %u",
    394                      (unsigned int) mfa->exit_code);
    395     respond_with_error (mfa,
    396                         http_status,
    397                         ec,
    398                         es);
    399     return;
    400   }
    401   qs = TALER_MERCHANTDB_update_mfa_challenge (TMH_db,
    402                                               mfa->challenge_id,
    403                                               mfa->code,
    404                                               MAX_SOLUTIONS,
    405                                               mfa->expiration_date,
    406                                               mfa->retransmission_date);
    407   switch (qs)
    408   {
    409   case GNUNET_DB_STATUS_HARD_ERROR:
    410     GNUNET_break (0);
    411     respond_with_error (mfa,
    412                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    413                         TALER_EC_GENERIC_DB_COMMIT_FAILED,
    414                         "update_mfa_challenge");
    415     return;
    416   case GNUNET_DB_STATUS_SOFT_ERROR:
    417     GNUNET_break (0);
    418     respond_with_error (mfa,
    419                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    420                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
    421                         "update_mfa_challenge");
    422     return;
    423   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    424     GNUNET_break (0);
    425     respond_with_error (mfa,
    426                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    427                         TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    428                         "no results on INSERT, but success?");
    429     return;
    430   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    431     break;
    432   }
    433   {
    434     struct MHD_Response *response;
    435 
    436     response =
    437       TALER_MHD_make_json_steal (
    438         GNUNET_JSON_PACK (
    439           GNUNET_JSON_pack_timestamp (
    440             "solve_expiration",
    441             GNUNET_TIME_absolute_to_timestamp (
    442               mfa->expiration_date)),
    443           GNUNET_JSON_pack_timestamp (
    444             "earliest_retransmission",
    445             GNUNET_TIME_absolute_to_timestamp (
    446               mfa->retransmission_date))));
    447     respond_to_challenge_with_response (
    448       mfa,
    449       MHD_HTTP_OK,
    450       response);
    451   }
    452 }
    453 
    454 
    455 /**
    456  * Function called when our SMS helper has terminated.
    457  *
    458  * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
    459  * @param type type of the process
    460  * @param exit_code status code of the process
    461  */
    462 static void
    463 transmission_done_cb (void *cls,
    464                       enum GNUNET_OS_ProcessStatusType type,
    465                       long unsigned int exit_code)
    466 {
    467   struct MfaState *mfa = cls;
    468 
    469   mfa->cwh = NULL;
    470   if (NULL != mfa->child)
    471   {
    472     GNUNET_process_destroy (mfa->child);
    473     mfa->child = NULL;
    474   }
    475   mfa->exited = (GNUNET_OS_PROCESS_EXITED == type);
    476   mfa->exit_code = exit_code;
    477   mfa->send_ok = (mfa->exited &&
    478                   helper_reported_success (exit_code));
    479   if (! mfa->send_ok)
    480     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    481                 "MFA helper failed with status %d/%u\n",
    482                 (int) type,
    483                 (unsigned int) exit_code);
    484   mfa->phase = MFA_PHASE_SENT;
    485   GNUNET_assert (GNUNET_YES == mfa->suspended);
    486   mfa->suspended = GNUNET_NO;
    487   MHD_resume_connection (mfa->hc->connection);
    488   TALER_MHD_daemon_trigger ();
    489 }
    490 
    491 
    492 /**
    493  * Resolve a binary name via PATH.
    494  *
    495  * Needed because the GNUnet process helpers to not support
    496  * an execp equivalent at present.
    497  *
    498  * @param binary_name name to search for
    499  * @returns resolved path or NULL if not found
    500  */
    501 static char *
    502 resolve_path (const char *binary_name)
    503 {
    504   char *path_env;
    505   char full_path[2048];
    506   char *dir;
    507   char *path_copy;
    508 
    509   if (NULL != strchr (binary_name,
    510                       '/'))
    511   {
    512     /* Already a full path, do not search. */
    513     return GNUNET_strdup (binary_name);
    514   }
    515   path_env = getenv ("PATH");
    516   if (path_env == NULL)
    517     return NULL;
    518   /* Duplicate PATH because strtok modifies the string it parses */
    519   path_copy = GNUNET_strdup (path_env);
    520   dir = strtok (path_copy, ":");
    521   while (dir != NULL)
    522   {
    523     snprintf (full_path,
    524               sizeof(full_path),
    525               "%s/%s",
    526               dir,
    527               binary_name);
    528     if (0 == access (full_path,
    529                      X_OK))
    530     {
    531       GNUNET_free (path_copy);
    532       return GNUNET_strdup (full_path);
    533     }
    534     dir = strtok (NULL, ":");
    535   }
    536   GNUNET_free (path_copy);
    537   return NULL;
    538 }
    539 
    540 
    541 /**
    542  * Setup challenge code for @a mfa and send it to the
    543  * @a required_address; on success.
    544  *
    545  * @param[in,out] mfa process to send the challenge for
    546  */
    547 static void
    548 phase_send_challenge (struct MfaState *mfa)
    549 {
    550   const char *prog = NULL;
    551   char *binary_path = NULL;
    552   unsigned long long challenge_num;
    553   char **cmd_argv = NULL;
    554 
    555   challenge_num = (unsigned long long)
    556                   GNUNET_CRYPTO_random_u64 (1000 * 1000 * 100);
    557   GNUNET_asprintf (&mfa->code,
    558                    "%04llu-%04llu",
    559                    challenge_num / 10000,
    560                    challenge_num % 10000);
    561   switch (mfa->channel)
    562   {
    563   case TALER_MERCHANT_MFA_CHANNEL_NONE:
    564     GNUNET_assert (0);
    565     break;
    566   case TALER_MERCHANT_MFA_CHANNEL_SMS:
    567     mfa->expiration_date
    568       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
    569     mfa->retransmission_date
    570       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
    571     prog = TMH_helper_sms;
    572     break;
    573   case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
    574     mfa->expiration_date
    575       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
    576     mfa->retransmission_date
    577       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
    578     prog = TMH_helper_email;
    579     break;
    580   case TALER_MERCHANT_MFA_CHANNEL_TOTP:
    581     mfa->expiration_date
    582       = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
    583     mfa->retransmission_date
    584       = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
    585     respond_with_error (mfa,
    586                         MHD_HTTP_NOT_IMPLEMENTED,
    587                         TALER_EC_GENERIC_FEATURE_NOT_IMPLEMENTED,
    588                         "#10327");
    589     goto done;
    590   }
    591   if (NULL == prog)
    592   {
    593     respond_with_error (
    594       mfa,
    595       MHD_HTTP_INTERNAL_SERVER_ERROR,
    596       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    597       TALER_MERCHANT_MFA_channel_to_string (mfa->channel));
    598     goto done;
    599   }
    600   {
    601     /* Start child process and feed pipe */
    602     struct GNUNET_DISK_PipeHandle *p;
    603     struct GNUNET_DISK_FileHandle *pipe_stdin;
    604     const char *extra_args[] = {
    605       mfa->required_address,
    606       NULL,
    607     };
    608 
    609     cmd_argv = TALER_words_split (prog,
    610                                   extra_args);
    611 
    612     GNUNET_assert (NULL != cmd_argv[0]);
    613 
    614     p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
    615     if (NULL == p)
    616     {
    617       respond_with_error (mfa,
    618                           MHD_HTTP_INTERNAL_SERVER_ERROR,
    619                           TALER_EC_GENERIC_ALLOCATION_FAILURE,
    620                           "pipe");
    621       goto done;
    622     }
    623     mfa->child = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    624     GNUNET_assert (GNUNET_OK ==
    625                    GNUNET_process_set_options (
    626                      mfa->child,
    627                      GNUNET_process_option_inherit_rpipe (p,
    628                                                           STDIN_FILENO)));
    629     binary_path = resolve_path (cmd_argv[0]);
    630     if ( (NULL == binary_path) ||
    631          (GNUNET_OK !=
    632           GNUNET_process_run_command_argv (mfa->child,
    633                                            binary_path,
    634                                            (const char **) cmd_argv)) )
    635     {
    636       GNUNET_process_destroy (mfa->child);
    637       mfa->child = NULL;
    638       GNUNET_break (GNUNET_OK ==
    639                     GNUNET_DISK_pipe_close (p));
    640       respond_with_error (mfa,
    641                           MHD_HTTP_BAD_GATEWAY,
    642                           TALER_EC_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED,
    643                           "exec");
    644       goto done;
    645     }
    646 
    647     pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
    648                                               GNUNET_DISK_PIPE_END_WRITE);
    649     GNUNET_assert (NULL != pipe_stdin);
    650     GNUNET_break (GNUNET_OK ==
    651                   GNUNET_DISK_pipe_close (p));
    652     GNUNET_asprintf (&mfa->msg,
    653                      "%s is your security code.\n"
    654                      "Do not share your code with anyone.\n\n"
    655                      "Authorizes: %s\n"
    656                      "Login: %s\n\n"
    657                      "Expires: %s (%s).\n",
    658                      mfa->code,
    659                      TALER_MERCHANT_MFA_co2s (mfa->op),
    660                      mfa->instance_id,
    661                      GNUNET_TIME_absolute2s (
    662                        mfa->expiration_date),
    663                      GNUNET_TIME_relative2s (
    664                        GNUNET_TIME_absolute_get_remaining (
    665                          mfa->expiration_date),
    666                        true));
    667     {
    668       const char *off = mfa->msg;
    669       size_t left = strlen (off);
    670 
    671       while (0 != left)
    672       {
    673         ssize_t ret;
    674 
    675         ret = GNUNET_DISK_file_write (pipe_stdin,
    676                                       off,
    677                                       left);
    678         if (ret <= 0)
    679         {
    680           GNUNET_DISK_file_close (pipe_stdin);
    681           respond_with_error (mfa,
    682                               MHD_HTTP_BAD_GATEWAY,
    683                               TALER_EC_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED,
    684                               "write");
    685           goto done;
    686         }
    687         mfa->msg_off += ret;
    688         off += ret;
    689         left -= ret;
    690       }
    691       GNUNET_DISK_file_close (pipe_stdin);
    692     }
    693   }
    694   mfa->phase = MFA_PHASE_SUSPENDING;
    695 done:
    696   GNUNET_free (binary_path);
    697   TALER_words_destroy (cmd_argv);
    698 }
    699 
    700 
    701 /**
    702  * Lookup challenge in DB.
    703  *
    704  * @param[in,out] mfa process to parse data for
    705  */
    706 static void
    707 phase_lookup (struct MfaState *mfa)
    708 {
    709   enum GNUNET_DB_QueryStatus qs;
    710   uint32_t retry_counter;
    711   struct GNUNET_TIME_Absolute confirmation_date;
    712   struct GNUNET_TIME_Absolute retransmission_date;
    713   struct TALER_MERCHANT_MFA_BodySalt salt;
    714 
    715   qs = TALER_MERCHANTDB_get_mfa_challenge (TMH_db,
    716                                            mfa->challenge_id,
    717                                            &mfa->h_body,
    718                                            &salt,
    719                                            &mfa->required_address,
    720                                            &mfa->op,
    721                                            &confirmation_date,
    722                                            &retransmission_date,
    723                                            &retry_counter,
    724                                            &mfa->channel,
    725                                            &mfa->instance_id);
    726   switch (qs)
    727   {
    728   case GNUNET_DB_STATUS_HARD_ERROR:
    729     GNUNET_break (0);
    730     respond_with_error (mfa,
    731                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    732                         TALER_EC_GENERIC_DB_COMMIT_FAILED,
    733                         "get_mfa_challenge");
    734     return;
    735   case GNUNET_DB_STATUS_SOFT_ERROR:
    736     GNUNET_break (0);
    737     respond_with_error (mfa,
    738                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    739                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
    740                         "get_mfa_challenge");
    741     return;
    742   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    743     GNUNET_break_op (0);
    744     respond_with_error (mfa,
    745                         MHD_HTTP_NOT_FOUND,
    746                         TALER_EC_MERCHANT_TAN_CHALLENGE_UNKNOWN,
    747                         mfa->hc->infix);
    748     return;
    749   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    750     break;
    751   }
    752   if (! GNUNET_TIME_absolute_is_future (confirmation_date))
    753   {
    754     /* was already solved */
    755     respond_with_error (mfa,
    756                         MHD_HTTP_GONE,
    757                         TALER_EC_MERCHANT_TAN_CHALLENGE_SOLVED,
    758                         NULL);
    759     return;
    760   }
    761   if (GNUNET_TIME_absolute_is_future (retransmission_date))
    762   {
    763     /* too early to try again */
    764     respond_with_error (mfa,
    765                         MHD_HTTP_TOO_MANY_REQUESTS,
    766                         TALER_EC_MERCHANT_TAN_TOO_EARLY,
    767                         GNUNET_TIME_absolute2s (retransmission_date));
    768     return;
    769   }
    770   mfa->phase++;
    771 }
    772 
    773 
    774 /**
    775  * Parse challenge request.
    776  *
    777  * @param[in,out] mfa process to parse data for
    778  */
    779 static void
    780 phase_parse (struct MfaState *mfa)
    781 {
    782   struct TMH_HandlerContext *hc = mfa->hc;
    783   enum GNUNET_GenericReturnValue ret;
    784 
    785   ret = TMH_mfa_parse_challenge_id (hc,
    786                                     hc->infix,
    787                                     &mfa->challenge_id,
    788                                     &mfa->h_body);
    789   if (GNUNET_OK != ret)
    790   {
    791     mfa->phase = (GNUNET_NO == ret)
    792       ? MFA_PHASE_RETURN_YES
    793       : MFA_PHASE_RETURN_NO;
    794     return;
    795   }
    796   mfa->phase++;
    797 }
    798 
    799 
    800 enum MHD_Result
    801 TMH_post_challenge_ID (const struct TMH_RequestHandler *rh,
    802                        struct MHD_Connection *connection,
    803                        struct TMH_HandlerContext *hc)
    804 {
    805   struct MfaState *mfa = hc->ctx;
    806 
    807   if (NULL == mfa)
    808   {
    809     mfa = GNUNET_new (struct MfaState);
    810     mfa->hc = hc;
    811     hc->ctx = mfa;
    812     hc->cc = &mfa_context_cleanup;
    813     GNUNET_CONTAINER_DLL_insert (mfa_head,
    814                                  mfa_tail,
    815                                  mfa);
    816   }
    817 
    818   while (1)
    819   {
    820     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    821                 "Processing /challenge in phase %d\n",
    822                 (int) mfa->phase);
    823     switch (mfa->phase)
    824     {
    825     case MFA_PHASE_PARSE:
    826       phase_parse (mfa);
    827       break;
    828     case MFA_PHASE_LOOKUP:
    829       phase_lookup (mfa);
    830       break;
    831     case MFA_PHASE_SENDING:
    832       phase_send_challenge (mfa);
    833       break;
    834     case MFA_PHASE_SUSPENDING:
    835       mfa->cwh = GNUNET_wait_child (mfa->child,
    836                                     &transmission_done_cb,
    837                                     mfa);
    838       if (NULL == mfa->cwh)
    839       {
    840         respond_with_error (mfa,
    841                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    842                             TALER_EC_GENERIC_ALLOCATION_FAILURE,
    843                             "GNUNET_wait_child");
    844         continue;
    845       }
    846       mfa->suspended = GNUNET_YES;
    847       MHD_suspend_connection (hc->connection);
    848       return MHD_YES;
    849     case MFA_PHASE_SENT:
    850       phase_sent (mfa);
    851       break;
    852     case MFA_PHASE_RETURN_YES:
    853       return MHD_YES;
    854     case MFA_PHASE_RETURN_NO:
    855       GNUNET_break (0);
    856       return MHD_NO;
    857     }
    858   }
    859 }