LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-challenge-ID.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 57.5 % 252 145
Test Date: 2026-09-11 17:45:24 Functions: 84.6 % 13 11

            Line data    Source code
       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           11 : mfa_context_cleanup (void *cls)
     192              : {
     193           11 :   struct MfaState *mfa = cls;
     194              : 
     195           11 :   GNUNET_CONTAINER_DLL_remove (mfa_head,
     196              :                                mfa_tail,
     197              :                                mfa);
     198           11 :   if (NULL != mfa->cwh)
     199              :   {
     200            0 :     GNUNET_wait_child_cancel (mfa->cwh);
     201            0 :     mfa->cwh = NULL;
     202              :   }
     203           11 :   if (NULL != mfa->child)
     204              :   {
     205            0 :     GNUNET_break (GNUNET_OK ==
     206              :                   GNUNET_process_kill (mfa->child,
     207              :                                        SIGKILL));
     208            0 :     GNUNET_break (GNUNET_OK ==
     209              :                   GNUNET_process_wait (mfa->child,
     210              :                                        true,
     211              :                                        NULL,
     212              :                                        NULL));
     213            0 :     GNUNET_process_destroy (mfa->child);
     214            0 :     mfa->child = NULL;
     215              :   }
     216           11 :   GNUNET_free (mfa->required_address);
     217           11 :   GNUNET_free (mfa->msg);
     218           11 :   GNUNET_free (mfa->instance_id);
     219           11 :   GNUNET_free (mfa->code);
     220           11 :   GNUNET_free (mfa);
     221           11 : }
     222              : 
     223              : 
     224              : void
     225           20 : TMH_challenge_done ()
     226              : {
     227           20 :   for (struct MfaState *mfa = mfa_head;
     228           20 :        NULL != mfa;
     229            0 :        mfa = mfa->next)
     230              :   {
     231            0 :     if (GNUNET_YES == mfa->suspended)
     232              :     {
     233              :       /* Make sure transmission_done_cb() cannot fire after
     234              :          we resumed the connection here. */
     235            0 :       if (NULL != mfa->cwh)
     236              :       {
     237            0 :         GNUNET_wait_child_cancel (mfa->cwh);
     238            0 :         mfa->cwh = NULL;
     239              :       }
     240            0 :       mfa->suspended = GNUNET_SYSERR;
     241            0 :       MHD_resume_connection (mfa->hc->connection);
     242              :     }
     243              :   }
     244           20 : }
     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           11 : 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           11 :   res = MHD_queue_response (mfa->hc->connection,
     262              :                             response_code,
     263              :                             response);
     264           11 :   MHD_destroy_response (response);
     265           11 :   mfa->phase = (MHD_NO == res)
     266              :     ? MFA_PHASE_RETURN_NO
     267           11 :     : MFA_PHASE_RETURN_YES;
     268           11 : }
     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            0 : respond_with_error (struct MfaState *mfa,
     281              :                     unsigned int http_status,
     282              :                     enum TALER_ErrorCode ec,
     283              :                     const char *hint)
     284              : {
     285            0 :   respond_to_challenge_with_response (
     286              :     mfa,
     287              :     http_status,
     288              :     TALER_MHD_make_error (ec,
     289              :                           hint));
     290            0 : }
     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           11 : helper_reported_success (unsigned long int exit_code)
     311              : {
     312           11 :   return (0 == exit_code) ||
     313            0 :          ( (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            0 : classify_helper_status (unsigned long int exit_code,
     338              :                         unsigned int *http_status)
     339              : {
     340            0 :   if ( (exit_code >= 10) &&
     341              :        (exit_code < 20) )
     342              :   {
     343            0 :     *http_status = MHD_HTTP_BAD_REQUEST;
     344            0 :     return TALER_EC_MERCHANT_TAN_ADDRESS_UNUSABLE;
     345              :   }
     346            0 :   if ( (exit_code >= 20) &&
     347              :        (exit_code < 30) )
     348              :   {
     349            0 :     *http_status = MHD_HTTP_SERVICE_UNAVAILABLE;
     350            0 :     return TALER_EC_MERCHANT_TAN_ADDRESS_UNREACHABLE;
     351              :   }
     352            0 :   if ( (exit_code >= 40) &&
     353              :        (exit_code < 50) )
     354              :   {
     355            0 :     *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
     356            0 :     return TALER_EC_MERCHANT_TAN_HELPER_MISCONFIGURED;
     357              :   }
     358            0 :   *http_status = MHD_HTTP_BAD_GATEWAY;
     359            0 :   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           11 : phase_sent (struct MfaState *mfa)
     370              : {
     371              :   enum GNUNET_DB_QueryStatus qs;
     372              : 
     373           11 :   if (! mfa->send_ok)
     374              :   {
     375              :     char es[32];
     376              :     unsigned int http_status;
     377              :     enum TALER_ErrorCode ec;
     378              : 
     379            0 :     if (! mfa->exited)
     380              :     {
     381              :       /* Killed by a signal or otherwise abnormal: there is no exit code to
     382              :          classify. */
     383            0 :       http_status = MHD_HTTP_BAD_GATEWAY;
     384            0 :       ec = TALER_EC_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED;
     385              :     }
     386              :     else
     387              :     {
     388            0 :       ec = classify_helper_status (mfa->exit_code,
     389              :                                    &http_status);
     390              :     }
     391            0 :     GNUNET_snprintf (es,
     392              :                      sizeof (es),
     393              :                      "process exited with %u",
     394            0 :                      (unsigned int) mfa->exit_code);
     395            0 :     respond_with_error (mfa,
     396              :                         http_status,
     397              :                         ec,
     398              :                         es);
     399            0 :     return;
     400              :   }
     401           11 :   qs = TALER_MERCHANTDB_update_mfa_challenge (TMH_db,
     402              :                                               mfa->challenge_id,
     403           11 :                                               mfa->code,
     404              :                                               MAX_SOLUTIONS,
     405              :                                               mfa->expiration_date,
     406              :                                               mfa->retransmission_date);
     407           11 :   switch (qs)
     408              :   {
     409            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     410            0 :     GNUNET_break (0);
     411            0 :     respond_with_error (mfa,
     412              :                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     413              :                         TALER_EC_GENERIC_DB_COMMIT_FAILED,
     414              :                         "update_mfa_challenge");
     415            0 :     return;
     416            0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     417            0 :     GNUNET_break (0);
     418            0 :     respond_with_error (mfa,
     419              :                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     420              :                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
     421              :                         "update_mfa_challenge");
     422            0 :     return;
     423            0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     424            0 :     GNUNET_break (0);
     425            0 :     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            0 :     return;
     430           11 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     431           11 :     break;
     432              :   }
     433              :   {
     434              :     struct MHD_Response *response;
     435              : 
     436              :     response =
     437           11 :       TALER_MHD_make_json_steal (
     438           11 :         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           11 :     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           11 : transmission_done_cb (void *cls,
     464              :                       enum GNUNET_OS_ProcessStatusType type,
     465              :                       long unsigned int exit_code)
     466              : {
     467           11 :   struct MfaState *mfa = cls;
     468              : 
     469           11 :   mfa->cwh = NULL;
     470           11 :   if (NULL != mfa->child)
     471              :   {
     472           11 :     GNUNET_process_destroy (mfa->child);
     473           11 :     mfa->child = NULL;
     474              :   }
     475           11 :   mfa->exited = (GNUNET_OS_PROCESS_EXITED == type);
     476           11 :   mfa->exit_code = exit_code;
     477           22 :   mfa->send_ok = (mfa->exited &&
     478           11 :                   helper_reported_success (exit_code));
     479           11 :   if (! mfa->send_ok)
     480            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     481              :                 "MFA helper failed with status %d/%u\n",
     482              :                 (int) type,
     483              :                 (unsigned int) exit_code);
     484           11 :   mfa->phase = MFA_PHASE_SENT;
     485           11 :   GNUNET_assert (GNUNET_YES == mfa->suspended);
     486           11 :   mfa->suspended = GNUNET_NO;
     487           11 :   MHD_resume_connection (mfa->hc->connection);
     488           11 :   TALER_MHD_daemon_trigger ();
     489           11 : }
     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           11 : 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           11 :   if (NULL != strchr (binary_name,
     510              :                       '/'))
     511              :   {
     512              :     /* Already a full path, do not search. */
     513           11 :     return GNUNET_strdup (binary_name);
     514              :   }
     515            0 :   path_env = getenv ("PATH");
     516            0 :   if (path_env == NULL)
     517            0 :     return NULL;
     518              :   /* Duplicate PATH because strtok modifies the string it parses */
     519            0 :   path_copy = GNUNET_strdup (path_env);
     520            0 :   dir = strtok (path_copy, ":");
     521            0 :   while (dir != NULL)
     522              :   {
     523            0 :     snprintf (full_path,
     524              :               sizeof(full_path),
     525              :               "%s/%s",
     526              :               dir,
     527              :               binary_name);
     528            0 :     if (0 == access (full_path,
     529              :                      X_OK))
     530              :     {
     531            0 :       GNUNET_free (path_copy);
     532            0 :       return GNUNET_strdup (full_path);
     533              :     }
     534            0 :     dir = strtok (NULL, ":");
     535              :   }
     536            0 :   GNUNET_free (path_copy);
     537            0 :   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           11 : phase_send_challenge (struct MfaState *mfa)
     549              : {
     550           11 :   const char *prog = NULL;
     551           11 :   char *binary_path = NULL;
     552              :   unsigned long long challenge_num;
     553           11 :   char **cmd_argv = NULL;
     554              : 
     555              :   challenge_num = (unsigned long long)
     556           11 :                   GNUNET_CRYPTO_random_u64 (1000 * 1000 * 100);
     557           11 :   GNUNET_asprintf (&mfa->code,
     558              :                    "%04llu-%04llu",
     559              :                    challenge_num / 10000,
     560              :                    challenge_num % 10000);
     561           11 :   switch (mfa->channel)
     562              :   {
     563            0 :   case TALER_MERCHANT_MFA_CHANNEL_NONE:
     564            0 :     GNUNET_assert (0);
     565              :     break;
     566            4 :   case TALER_MERCHANT_MFA_CHANNEL_SMS:
     567              :     mfa->expiration_date
     568            4 :       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
     569              :     mfa->retransmission_date
     570            4 :       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
     571            4 :     prog = TMH_helper_sms;
     572            4 :     break;
     573            7 :   case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
     574              :     mfa->expiration_date
     575            7 :       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
     576              :     mfa->retransmission_date
     577            7 :       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
     578            7 :     prog = TMH_helper_email;
     579            7 :     break;
     580            0 :   case TALER_MERCHANT_MFA_CHANNEL_TOTP:
     581              :     mfa->expiration_date
     582            0 :       = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
     583              :     mfa->retransmission_date
     584            0 :       = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
     585            0 :     respond_with_error (mfa,
     586              :                         MHD_HTTP_NOT_IMPLEMENTED,
     587              :                         TALER_EC_GENERIC_FEATURE_NOT_IMPLEMENTED,
     588              :                         "#10327");
     589            0 :     goto done;
     590              :   }
     591           11 :   if (NULL == prog)
     592              :   {
     593            0 :     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            0 :     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           11 :     const char *extra_args[] = {
     605           11 :       mfa->required_address,
     606              :       NULL,
     607              :     };
     608              : 
     609           11 :     cmd_argv = TALER_words_split (prog,
     610              :                                   extra_args);
     611              : 
     612           11 :     GNUNET_assert (NULL != cmd_argv[0]);
     613              : 
     614           11 :     p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
     615           11 :     if (NULL == p)
     616              :     {
     617            0 :       respond_with_error (mfa,
     618              :                           MHD_HTTP_INTERNAL_SERVER_ERROR,
     619              :                           TALER_EC_GENERIC_ALLOCATION_FAILURE,
     620              :                           "pipe");
     621            0 :       goto done;
     622              :     }
     623           11 :     mfa->child = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
     624           11 :     GNUNET_assert (GNUNET_OK ==
     625              :                    GNUNET_process_set_options (
     626              :                      mfa->child,
     627              :                      GNUNET_process_option_inherit_rpipe (p,
     628              :                                                           STDIN_FILENO)));
     629           11 :     binary_path = resolve_path (cmd_argv[0]);
     630           22 :     if ( (NULL == binary_path) ||
     631              :          (GNUNET_OK !=
     632           11 :           GNUNET_process_run_command_argv (mfa->child,
     633              :                                            binary_path,
     634              :                                            (const char **) cmd_argv)) )
     635              :     {
     636            0 :       GNUNET_process_destroy (mfa->child);
     637            0 :       mfa->child = NULL;
     638            0 :       GNUNET_break (GNUNET_OK ==
     639              :                     GNUNET_DISK_pipe_close (p));
     640            0 :       respond_with_error (mfa,
     641              :                           MHD_HTTP_BAD_GATEWAY,
     642              :                           TALER_EC_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED,
     643              :                           "exec");
     644            0 :       goto done;
     645              :     }
     646              : 
     647           11 :     pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
     648              :                                               GNUNET_DISK_PIPE_END_WRITE);
     649           11 :     GNUNET_assert (NULL != pipe_stdin);
     650           11 :     GNUNET_break (GNUNET_OK ==
     651              :                   GNUNET_DISK_pipe_close (p));
     652           11 :     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           11 :       const char *off = mfa->msg;
     669           11 :       size_t left = strlen (off);
     670              : 
     671           22 :       while (0 != left)
     672              :       {
     673              :         ssize_t ret;
     674              : 
     675           11 :         ret = GNUNET_DISK_file_write (pipe_stdin,
     676              :                                       off,
     677              :                                       left);
     678           11 :         if (ret <= 0)
     679              :         {
     680            0 :           GNUNET_DISK_file_close (pipe_stdin);
     681            0 :           respond_with_error (mfa,
     682              :                               MHD_HTTP_BAD_GATEWAY,
     683              :                               TALER_EC_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED,
     684              :                               "write");
     685            0 :           goto done;
     686              :         }
     687           11 :         mfa->msg_off += ret;
     688           11 :         off += ret;
     689           11 :         left -= ret;
     690              :       }
     691           11 :       GNUNET_DISK_file_close (pipe_stdin);
     692              :     }
     693              :   }
     694           11 :   mfa->phase = MFA_PHASE_SUSPENDING;
     695           11 : done:
     696           11 :   GNUNET_free (binary_path);
     697           11 :   TALER_words_destroy (cmd_argv);
     698           11 : }
     699              : 
     700              : 
     701              : /**
     702              :  * Lookup challenge in DB.
     703              :  *
     704              :  * @param[in,out] mfa process to parse data for
     705              :  */
     706              : static void
     707           11 : 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           11 :   qs = TALER_MERCHANTDB_get_mfa_challenge (TMH_db,
     716              :                                            mfa->challenge_id,
     717           11 :                                            &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           11 :   switch (qs)
     727              :   {
     728            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     729            0 :     GNUNET_break (0);
     730            0 :     respond_with_error (mfa,
     731              :                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     732              :                         TALER_EC_GENERIC_DB_COMMIT_FAILED,
     733              :                         "get_mfa_challenge");
     734            0 :     return;
     735            0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     736            0 :     GNUNET_break (0);
     737            0 :     respond_with_error (mfa,
     738              :                         MHD_HTTP_INTERNAL_SERVER_ERROR,
     739              :                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
     740              :                         "get_mfa_challenge");
     741            0 :     return;
     742            0 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     743            0 :     GNUNET_break_op (0);
     744            0 :     respond_with_error (mfa,
     745              :                         MHD_HTTP_NOT_FOUND,
     746              :                         TALER_EC_MERCHANT_TAN_CHALLENGE_UNKNOWN,
     747            0 :                         mfa->hc->infix);
     748            0 :     return;
     749           11 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     750           11 :     break;
     751              :   }
     752           11 :   if (! GNUNET_TIME_absolute_is_future (confirmation_date))
     753              :   {
     754              :     /* was already solved */
     755            0 :     respond_with_error (mfa,
     756              :                         MHD_HTTP_GONE,
     757              :                         TALER_EC_MERCHANT_TAN_CHALLENGE_SOLVED,
     758              :                         NULL);
     759            0 :     return;
     760              :   }
     761           11 :   if (GNUNET_TIME_absolute_is_future (retransmission_date))
     762              :   {
     763              :     /* too early to try again */
     764            0 :     respond_with_error (mfa,
     765              :                         MHD_HTTP_TOO_MANY_REQUESTS,
     766              :                         TALER_EC_MERCHANT_TAN_TOO_EARLY,
     767              :                         GNUNET_TIME_absolute2s (retransmission_date));
     768            0 :     return;
     769              :   }
     770           11 :   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           11 : phase_parse (struct MfaState *mfa)
     781              : {
     782           11 :   struct TMH_HandlerContext *hc = mfa->hc;
     783              :   enum GNUNET_GenericReturnValue ret;
     784              : 
     785           11 :   ret = TMH_mfa_parse_challenge_id (hc,
     786           11 :                                     hc->infix,
     787              :                                     &mfa->challenge_id,
     788              :                                     &mfa->h_body);
     789           11 :   if (GNUNET_OK != ret)
     790              :   {
     791            0 :     mfa->phase = (GNUNET_NO == ret)
     792              :       ? MFA_PHASE_RETURN_YES
     793            0 :       : MFA_PHASE_RETURN_NO;
     794            0 :     return;
     795              :   }
     796           11 :   mfa->phase++;
     797              : }
     798              : 
     799              : 
     800              : enum MHD_Result
     801           22 : TMH_post_challenge_ID (const struct TMH_RequestHandler *rh,
     802              :                        struct MHD_Connection *connection,
     803              :                        struct TMH_HandlerContext *hc)
     804              : {
     805           22 :   struct MfaState *mfa = hc->ctx;
     806              : 
     807           22 :   if (NULL == mfa)
     808              :   {
     809           11 :     mfa = GNUNET_new (struct MfaState);
     810           11 :     mfa->hc = hc;
     811           11 :     hc->ctx = mfa;
     812           11 :     hc->cc = &mfa_context_cleanup;
     813           11 :     GNUNET_CONTAINER_DLL_insert (mfa_head,
     814              :                                  mfa_tail,
     815              :                                  mfa);
     816              :   }
     817              : 
     818              :   while (1)
     819              :   {
     820          110 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     821              :                 "Processing /challenge in phase %d\n",
     822              :                 (int) mfa->phase);
     823           66 :     switch (mfa->phase)
     824              :     {
     825           11 :     case MFA_PHASE_PARSE:
     826           11 :       phase_parse (mfa);
     827           11 :       break;
     828           11 :     case MFA_PHASE_LOOKUP:
     829           11 :       phase_lookup (mfa);
     830           11 :       break;
     831           11 :     case MFA_PHASE_SENDING:
     832           11 :       phase_send_challenge (mfa);
     833           11 :       break;
     834           11 :     case MFA_PHASE_SUSPENDING:
     835           11 :       mfa->cwh = GNUNET_wait_child (mfa->child,
     836              :                                     &transmission_done_cb,
     837              :                                     mfa);
     838           11 :       if (NULL == mfa->cwh)
     839              :       {
     840            0 :         respond_with_error (mfa,
     841              :                             MHD_HTTP_INTERNAL_SERVER_ERROR,
     842              :                             TALER_EC_GENERIC_ALLOCATION_FAILURE,
     843              :                             "GNUNET_wait_child");
     844            0 :         continue;
     845              :       }
     846           11 :       mfa->suspended = GNUNET_YES;
     847           11 :       MHD_suspend_connection (hc->connection);
     848           11 :       return MHD_YES;
     849           11 :     case MFA_PHASE_SENT:
     850           11 :       phase_sent (mfa);
     851           11 :       break;
     852           11 :     case MFA_PHASE_RETURN_YES:
     853           11 :       return MHD_YES;
     854            0 :     case MFA_PHASE_RETURN_NO:
     855            0 :       GNUNET_break (0);
     856            0 :       return MHD_NO;
     857              :     }
     858              :   }
     859              : }
        

Generated by: LCOV version 2.0-1