commit 697f247a0b91fd61ba65bd39d4272b099f597877
parent 9d7d7a2570125bbd80da68fc92de850f9e98641c
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 1 Sep 2025 21:00:59 +0200
make MFA API async, complete it a bit more
Diffstat:
5 files changed, 542 insertions(+), 312 deletions(-)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
@@ -80,6 +80,8 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_helper.h \
taler-merchant-httpd_mhd.c \
taler-merchant-httpd_mhd.h \
+ taler-merchant-httpd_mfa.c \
+ taler-merchant-httpd_mfa.h \
taler-merchant-httpd_private-delete-account-ID.c \
taler-merchant-httpd_private-delete-account-ID.h \
taler-merchant-httpd_private-delete-categories-ID.c \
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -35,6 +35,7 @@
#include "taler-merchant-httpd_get-templates-ID.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_mfa.h"
#include "taler-merchant-httpd_private-delete-account-ID.h"
#include "taler-merchant-httpd_private-delete-categories-ID.h"
#include "taler-merchant-httpd_private-delete-instances-ID.h"
@@ -634,6 +635,7 @@ do_shutdown (void *cls)
TMH_force_gorc_resume ();
TMH_force_wallet_get_order_resume ();
TMH_force_wallet_refund_order_resume ();
+ TMH_mfa_done ();
TALER_MHD_daemons_destroy ();
if (NULL != instance_eh)
{
diff --git a/src/backend/taler-merchant-httpd_mfa.c b/src/backend/taler-merchant-httpd_mfa.c
@@ -40,392 +40,590 @@
/**
- * Setup challenge @a code for @a channel and send it to the
- * @a required_address; on success, set @a expiration_date
- * and @a retransmission_date based on the @a channel. Return
- * a @a hint for the user.
+ * Internal state for MFA processing.
+ */
+struct MfaState
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct MfaState *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct MfaState *prev;
+
+ /**
+ * HTTP request we are handling.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
+ * Challenge code.
+ */
+ char *code;
+
+ /**
+ * When does @e code expire?
+ */
+ struct GNUNET_TIME_Absolute expiration_date;
+
+ /**
+ * When may we transmit a new code?
+ */
+ struct GNUNET_TIME_Absolute retransmission_date;
+
+ /**
+ * Handle to the helper process.
+ */
+ struct GNUNET_OS_Process *child;
+
+ /**
+ * Handle to wait for @e child
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Function to call with the result.
+ */
+ TMH_MFA_ResultCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Address hint to return to the client.
+ */
+ char *address_hint;
+
+ /**
+ * Message to send.
+ */
+ char *msg;
+
+ /**
+ * Offset of transmission in msg.
+ */
+ size_t msg_off;
+
+ /**
+ * ID of our challenge.
+ */
+ uint64_t challenge_id;
+
+ /**
+ * Salt used when hashing the request body.
+ */
+ struct TALER_MERCHANT_MFA_BodySalt salt;
+
+ /**
+ * Salted hash over the request body.
+ */
+ struct TALER_MERCHANT_MFA_BodyHash h_body;
+
+ /**
+ * Type of the next challenge.
+ */
+ enum TALER_MERCHANT_MFA_Channel next_challenge;
+
+ /**
+ * Operation this is about.
+ */
+ enum TALER_MERCHANT_MFA_CriticalOperation op;
+};
+
+
+/**
+ * Kept in a DLL.
+ */
+static struct MfaState *mfa_head;
+
+/**
+ * Kept in a DLL.
+ */
+static struct MfaState *mfa_tail;
+
+
+/**
+ * Conclude @a mfa process with status @a ret and response
+ * @a response
*
- * @param channel how to send the challenge
- * @param required_address where to send the challenge
- * @param[out] code set to challenge code
- * @param[out] expiration_date when the challenge expires
- * @param[out] retransmission_date when we may send another challenge
- * to the same @a required_address
- * @param[out] hint set to a hint to return to the user
- * about where he will receive the challenge
- * @return #GNUNET_OK on success
+ * @param[in] mfa MFA process to conclude and release resources for
+ * @param ret final result of the MFA process
+ * @param http_status HTTP status to give to the client if @ret is #GNUNET_NO
+ * @param[in] response HTTP response to return if @a ret is #GNUNET_NO
*/
-static enum GNUNET_DB_QueryStatus
-setup_challenge (enum TALER_MERCHANT_MFA_Channel channel,
- const char *required_address,
- char **code,
- struct GNUNET_TIME_Absolute *expiration_date,
- struct GNUNET_TIME_Absolute *retransmission_date,
- char **hint)
+static void
+finish_mfa (struct MfaState *mfa,
+ enum GNUNET_GenericReturnValue ret,
+ unsigned int http_status,
+ struct MHD_Response *response)
{
- const char *prog;
+ GNUNET_CONTAINER_DLL_remove (mfa_head,
+ mfa_tail,
+ mfa);
+ mfa->cb (mfa->cb_cls,
+ ret,
+ http_status,
+ response);
+ if (NULL != mfa->cwh)
+ {
+ GNUNET_wait_child_cancel (mfa->cwh);
+ mfa->cwh = NULL;
+ }
+ if (NULL != mfa->child)
+ {
+ (void) GNUNET_OS_process_kill (mfa->child,
+ SIGKILL);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (mfa->child));
+ mfa->child = NULL;
+ }
+ GNUNET_free (mfa->msg);
+ GNUNET_free (mfa->code);
+ GNUNET_free (mfa->address_hint);
+ GNUNET_free (mfa);
+}
+
+
+void
+TMH_mfa_done ()
+{
+ struct MfaState *mfa;
+ while (NULL != (mfa = mfa_head))
+ {
+ finish_mfa (mfa,
+ GNUNET_SYSERR,
+ 0,
+ NULL);
+ }
+}
+
+
+/**
+ * Obtain hint about the @a target_address of type @a channel to
+ * return to the client.
+ *
+ * @param channel type of challenge
+ * @param target_address address we will sent the challenge to
+ * @return hint for the user about the address
+ */
+static char *
+get_hint (enum TALER_MERCHANT_MFA_Channel channel,
+ const char *target_address)
+{
switch (channel)
{
case TALER_MERCHANT_MFA_CHANNEL_NONE:
GNUNET_assert (0);
- break;
+ return NULL;
case TALER_MERCHANT_MFA_CHANNEL_SMS:
- *expiration_date
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
- *retransmission_date
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
- GNUNET_asprintf (code,
- "%llu",
- (unsigned long long)
- GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
- 100000000));
- prog = "taler-merchant-helper-send-sms";
- *hint = NULL; // FIXME: generate nice hint!
- break;
+ {
+ size_t slen = strlen (target_address);
+ const char *end;
+
+ if (slen > 4)
+ end = &target_address[slen - 4];
+ else
+ end = &target_address[slen / 2];
+ return GNUNET_strdup (end);
+ }
case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
- *expiration_date
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
- *retransmission_date
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
- GNUNET_asprintf (code,
- "%llu",
- (unsigned long long)
- GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
- 100000000));
- prog = "taler-merchant-helper-send-email";
- *hint = NULL; // FIXME: generate nice hint!
- break;
+ {
+ const char *at;
+ size_t len;
+
+ at = strchr (target_address,
+ '@');
+ if (NULL == at)
+ len = 0;
+ else
+ len = at - target_address;
+ return GNUNET_strndup (target_address,
+ len);
+ }
case TALER_MERCHANT_MFA_CHANNEL_TOTP:
- *expiration_date
- = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
- *retransmission_date
- = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
- GNUNET_asprintf (hint,
- "You have %s to submit the correct OTP code",
- GNUNET_TIME_relative_to_string (OTP_TIMEOUT,
- false));
- code = GNUNET_strdup ("not implemented");
- return GNUNET_OK;
+ GNUNET_break (0);
+ return GNUNET_strdup ("TOTP is not implemented: #10327");
}
+ GNUNET_break (0);
+ return NULL;
+}
+
- // run 'prog' with 'code'!
- GNUNET_break (0); // not implemented
- return GNUNET_SYSERR;
+/**
+ * Generate an error for @a mfa.
+ *
+ * @param[in] mfa process to generate an error response for
+ * @param http_status HTTP status of the response
+ * @param ec Taler error code to return
+ * @param hint hint to return, can be NULL
+ */
+static void
+respond_with_error (struct MfaState *mfa,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *hint)
+{
+ finish_mfa (mfa,
+ GNUNET_NO,
+ http_status,
+ TALER_MHD_make_error (ec,
+ hint));
}
/**
- * If the @a retransmit_date is in the past, send a fresh challenge.
+ * Generate a response to the client pointing them to the challenge
+ * that was sent.
*
- * @param[in,out] hc handler context of the connection to authorize
- * @param op operation for which we are performing
- * @param challenge_id challenge to update
- * @param channel how to send the challenge
- * @param required_address where to send the challenge
- * @param retransmit_date when to send the challenge at the earliest
- * @param[out] hint hint to return to the user
- * @return #GNUNET_OK on success (including if @a retransmit_date is in the
- * future)
- * #GNUNET_NO if we failed to send a challenge and queued an
- * error response
- * #GNUNET_SYSERR if the client connection must simply be closed
+ * @param[in] mfa process to generate response for
*/
-static enum GNUNET_DB_QueryStatus
-retransmit_challenge (
- struct TMH_HandlerContext *hc,
- uint64_t challenge_id,
- enum TALER_MERCHANT_MFA_Channel channel,
- const char *required_address,
- struct GNUNET_TIME_Absolute retransmit_date,
- char **hint)
+static void
+respond_with_challenge (struct MfaState *mfa)
{
- enum GNUNET_DB_QueryStatus res;
- char *code;
+ finish_mfa (
+ mfa,
+ GNUNET_NO,
+ MHD_HTTP_FORBIDDEN, /* FIXME: check HTTP status code! Maybe use ACCEPTED? */
+ TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_string ("address_hint",
+ mfa->address_hint),
+ GNUNET_JSON_pack_string ("challenge_type",
+ TALER_MERCHANT_MFA_channel_to_string (
+ mfa->next_challenge)),
+ GNUNET_JSON_pack_uint64 ("challenge_id",
+ mfa->challenge_id),
+ GNUNET_JSON_pack_data_auto ("h_body",
+ &mfa->h_body)));
+}
+
+
+/**
+ * Function called when our SMS helper has terminated.
+ *
+ * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+transmission_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct MfaState *mfa = cls;
+ enum GNUNET_DB_QueryStatus qs;
- *hint = NULL;
- if (GNUNET_TIME_absolute_is_future (retransmit_date))
+ mfa->cwh = NULL;
+ if (NULL != mfa->child)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Challenge already sent to `%s' recently, not sending again\n",
- required_address);
- GNUNET_asprintf (
- hint,
- "Challenge will not be retransmitted before %s",
- GNUNET_STRINGS_absolute_time_to_string (retransmit_date));
- return GNUNET_OK;
+ GNUNET_OS_process_destroy (mfa->child);
+ mfa->child = NULL;
}
- res = setup_challenge (channel,
- required_address,
- &code,
- &expiration_date,
- &retransmit_date,
- hint);
- if (GNUNET_OK != res)
+ if (0 == mfa->challenge_id)
{
- GNUNET_break (0);
- return res;
+ /* 0: fresh challenge, must create one */
+ qs = TMH_db->create_mfa_challenge (TMH_db->cls,
+ mfa->hc->instance->settings.id,
+ mfa->op,
+ &mfa->h_body,
+ &mfa->salt,
+ mfa->code,
+ mfa->expiration_date,
+ mfa->retransmission_date,
+ mfa->next_challenge,
+ mfa->address_hint,
+ &mfa->challenge_id);
+ }
+ else
+ {
+ qs = TMH_db->update_mfa_challenge (TMH_db->cls,
+ mfa->challenge_id,
+ mfa->code,
+ MAX_SOLUTIONS,
+ mfa->expiration_date,
+ mfa->retransmission_date);
}
-
- qs = TMH_db->update_mfa_challenge (TMH_db->cls,
- challenge_id,
- code,
- MAX_SOLUTIONS,
- expiration_date,
- retransmit_date);
- GNUNET_free (code);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL);
+ return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (
- hc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- ? TALER_EC_GENERIC_DB_SOFT_FAILURE
- : TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL);
+ return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (
- hc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "no results on INSERT, but success?");
- return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "no results on INSERT, but success?");
+ return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
- return GNUNET_OK;
-
+ respond_with_challenge (mfa);
}
/**
- * Create a challenge in the backend and transmit it to
- * the @a required_address.
+ * Setup challenge code for @a mfa and send it to the
+ * @a required_address; on success.
*
- * @param[in,out] hc handler context of the connection to authorize
- * @param op operation for which we are performing
- * @param h_body salted hash of the request body
- * @param salt salt used for @a h_body
- * @param channel how to send the challenge
+ * @param[in,out] mfa process to send the challenge for
* @param required_address where to send the challenge
- * @param[out] challenge_id set to the numeric ID of the
- * challenge
- * @param[out] hint set to a hint to return to the user
- * about where he will receive the challenge
*/
-static enum GNUNET_DB_QueryStatus
-create_challenge (
- struct TMH_HandlerContext *hc,
- enum TALER_MERCHANT_MFA_CriticalOperation op,
- const struct TALER_MERCHANT_MFA_BodyHash *h_body,
- const struct TALER_MERCHANT_MFA_BodySalt *salt,
- enum TALER_MERCHANT_MFA_Channel channel,
- const char *required_address,
- uint64_t *challenge_id,
- char **hint)
+static void
+send_challenge (struct MfaState *mfa,
+ const char *required_address)
{
- enum GNUNET_DB_QueryStatus qs;
- char *code;
- struct GNUNET_TIME_Absolute expiration_date;
- struct GNUNET_TIME_Absolute retransmission_date;
- enum GNUNET_DB_QueryStatus res;
-
- *hint = NULL;
- res = setup_challenge (channel,
- required_address,
- &code,
- &expiration_date,
- &retransmission_date,
- hint);
- if (GNUNET_OK != res)
+ const char *prog;
+
+ switch (mfa->next_challenge)
{
- GNUNET_break (0);
- return res;
+ case TALER_MERCHANT_MFA_CHANNEL_NONE:
+ GNUNET_assert (0);
+ break;
+ case TALER_MERCHANT_MFA_CHANNEL_SMS:
+ mfa->expiration_date
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
+ mfa->retransmission_date
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
+ GNUNET_asprintf (&mfa->code,
+ "%llu",
+ (unsigned long long)
+ GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
+ 100000000));
+ prog = "taler-merchant-helper-send-sms";
+ break;
+ case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
+ mfa->expiration_date
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
+ mfa->retransmission_date
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
+ GNUNET_asprintf (&mfa->code,
+ "%llu",
+ (unsigned long long)
+ GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
+ 100000000));
+ prog = "taler-merchant-helper-send-email";
+ break;
+ case TALER_MERCHANT_MFA_CHANNEL_TOTP:
+ mfa->expiration_date
+ = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
+ mfa->retransmission_date
+ = GNUNET_TIME_relative_to_absolute (OTP_TIMEOUT);
+ respond_with_error (mfa,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "#10327");
+ return;
}
- qs = TMH_db->create_mfa_challenge (TMH_db->cls,
- hc->instance->settings.id,
- op,
- h_body,
- salt,
- code,
- expiration_date,
- retransmission_date,
- channel,
- *hint,
- challenge_id);
- GNUNET_free (code);
- switch (qs)
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (
- hc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- ? TALER_EC_GENERIC_DB_SOFT_FAILURE
- : TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (
- hc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "no results on INSERT, but success?");
- return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
+ /* Start child process and feed pipe */
+ struct GNUNET_DISK_PipeHandle *p;
+ struct GNUNET_DISK_FileHandle *pipe_stdin;
+
+ p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ if (NULL == p)
+ {
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "pipe");
+ return;
+ }
+ mfa->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ p,
+ NULL,
+ NULL,
+ prog,
+ prog,
+ required_address,
+ NULL);
+ if (NULL == mfa->child)
+ {
+ GNUNET_DISK_pipe_close (p);
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
+ "exec");
+ return;
+ }
+
+ pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
+ GNUNET_DISK_PIPE_END_WRITE);
+ GNUNET_assert (NULL != pipe_stdin);
+ GNUNET_DISK_pipe_close (p);
+ GNUNET_asprintf (&mfa->msg,
+ "%s\nTaler-Merchant:\n%s",
+ mfa->code,
+ mfa->hc->instance->settings.id);
+ {
+ const char *off = mfa->msg;
+ size_t left = strlen (off);
+
+ while (0 != left)
+ {
+ ssize_t ret;
+
+ ret = GNUNET_DISK_file_write (pipe_stdin,
+ off,
+ left);
+ if (ret <= 0)
+ {
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
+ "write");
+ return;
+ }
+ mfa->msg_off += ret;
+ off += ret;
+ left -= ret;
+ }
+ GNUNET_DISK_file_close (pipe_stdin);
+ }
}
- return GNUNET_OK;
+ mfa->cwh = GNUNET_wait_child (mfa->child,
+ &transmission_done_cb,
+ mfa);
}
-enum GNUNET_DB_QueryStatus
+void
TMH_mfa_challenge_check (
struct TMH_HandlerContext *hc,
enum TALER_MERCHANT_MFA_CriticalOperation op,
enum TALER_MERCHANT_MFA_Channel required_tans[],
- const char *required_addresses[])
+ const char *required_addresses[],
+ TMH_MFA_ResultCallback cb,
+ void *cb_cls)
{
- enum TALER_MERCHANT_MFA_Channel last_ch;
+ struct MfaState *mfa;
+ enum TALER_MERCHANT_MFA_Channel last_channel;
+ uint32_t retry_counter;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_MERCHANT_MFA_BodyHash h_body;
- struct TALER_MERCHANT_MFA_BodySalt salt;
struct GNUNET_TIME_Absolute confirmation_date;
- struct GNUNET_TIME_Absolute retransmission_date;
- uint32_t retry_counter;
- uint64_t challenge_id;
- MHD_RESULT ret;
- enum GNUNET_DB_QueryStatus res;
- char *hint = NULL;
- if (TALER_MERCHANT_MFA_CHANNEL_NONE == required_trans[0])
- return GNUNET_OK;
+ mfa = GNUNET_new (struct MfaState);
+ mfa->hc = hc;
+ mfa->cb = cb;
+ mfa->cb_cls = cb_cls;
+ mfa->op = op;
+ GNUNET_CONTAINER_DLL_insert (mfa_head,
+ mfa_tail,
+ mfa);
+ if (TALER_MERCHANT_MFA_CHANNEL_NONE == required_tans[0])
+ {
+ finish_mfa (mfa,
+ GNUNET_OK,
+ 0,
+ NULL);
+ return;
+ }
qs = TMH_db->lookup_mfa_challenge (TMH_db->cls,
hc->instance->settings.id,
op,
hc->request_body,
- &h_body,
- &salt,
- &conf_date,
- &retransmit_date,
+ &mfa->h_body,
+ &mfa->salt,
+ &confirmation_date,
+ &mfa->retransmission_date,
&retry_counter,
&last_channel,
- &challenge_id);
+ &mfa->challenge_id);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL);
+ return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (
- hc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- ? TALER_EC_GENERIC_DB_SOFT_FAILURE
- : TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL);
+ return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- &salt,
- sizeof (salt));
+ &mfa->salt,
+ sizeof (mfa->salt));
TALER_MERCHANT_mfa_body_hash (hc->request_body,
- &salt,
- &h_body);
- next_challenge = required_trans[0];
- retransmission_date = GNUNET_TIME_UNIT_ZERO_ABS; /* now */
- retry_counter = MAX_SOLUTIONS;
- res = create_challenge (hc,
- op,
- &h_body,
- &salt,
- next_challenge,
- required_addresses[0],
- &challenge_id,
- &hint);
- if (GNUNET_OK != res)
- return res;
- break;
+ &mfa->salt,
+ &mfa->h_body);
+ mfa->next_challenge = required_tans[0];
+ mfa->address_hint = get_hint (mfa->next_challenge,
+ required_addresses[0]);
+ send_challenge (mfa,
+ required_addresses[0]);
+ return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ for (unsigned int i = 0;
+ TALER_MERCHANT_MFA_CHANNEL_NONE != required_tans[i];
+ i++)
+ {
+ if (last_channel == required_tans[i])
{
- bool found = false;
-
- for (unsigned int i=0;
- TALER_MERCHANT_MFA_CHANNEL_NONE != required_trans[i];
- i++)
+ if (GNUNET_TIME_absolute_is_past (confirmation_date))
{
- if (last_channel == required_trans[i])
+ /* Last challenge WAS solved, move on! */
+ mfa->next_challenge = required_tans[i + 1];
+ if (TALER_MERCHANT_MFA_CHANNEL_NONE == mfa->next_challenge)
{
- if (GNUNET_TIME_absolute_is_past (confirmation_date))
- {
- /* Last challenge WAS solved, move on! */
- next_challenge = required_trans[i + 1];
- retransmission_date = GNUNET_TIME_UNIT_ZERO_ABS; /* now */
- retry_counter = MAX_SOLUTIONS;
- if (TALER_MERCHANT_MFA_CHANNEL_NONE == next_challenge)
- return GNUNET_OK;
- res = create_challenge (hc,
- op,
- &h_body,
- &salt,
- next_challenge,
- required_addresses[i + 1],
- &challenge_id,
- &hint);
- if (GNUNET_OK != res)
- return res;
- }
- else
- {
- /* challenge was not yet solved, possibly re-transmit
- a new one */
- next_challenge = last_channel;
- res = retransmit_challenge (hc,
- challenge_id,
- last_channel,
- required_addresses[i],
- retransmit_date,
- &hint);
- if (GNUNET_OK != res)
- return res;
- }
- found = true;
- break;
+ finish_mfa (mfa,
+ GNUNET_OK,
+ 0,
+ NULL);
+ return;
}
+ mfa->address_hint = get_hint (mfa->next_challenge,
+ required_addresses[i + 1]);
+ send_challenge (mfa,
+ required_addresses[i + 1]);
+ return;
}
- if (! found)
+ else
{
- /* challenge was satisfied that is not in our list! */
- GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (
- hc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "unexpected challenge already satisfied");
- return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
+ /* challenge was not yet solved, possibly re-transmit
+ a new one */
+ mfa->next_challenge = last_channel;
+ mfa->address_hint = get_hint (mfa->next_challenge,
+ required_addresses[i]);
+ if (GNUNET_TIME_absolute_is_past (mfa->retransmission_date))
+ {
+ send_challenge (mfa,
+ required_addresses[i]);
+ return;
+ }
+ respond_with_challenge (mfa);
+ return;
}
}
- break;
}
-
- /* Return HTTP response that a challenge was sent */
- ret = TALER_MHD_REPLY_JSON_PACK (
- hc->connection,
- MHD_HTTP_FORBIDDEN, /* FIXME: check HTTP status code! Maybe use ACCEPTED? */
- // FIXME: add ec!?
- GNUNET_JSON_pack_string ("hint",
- hint),
- GNUNET_JSON_pack_uint64 ("challenge_id",
- challenge_id),
- GNUNET_JSON_pack_auto_from_type ("h_body",
- &h_body));
- GNUNET_free (hint);
- return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO;
+ /* challenge was satisfied that is not in our list! */
+ GNUNET_break (0);
+ respond_with_error (mfa,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "unexpected challenge already satisfied");
}
diff --git a/src/backend/taler-merchant-httpd_mfa.h b/src/backend/taler-merchant-httpd_mfa.h
@@ -30,6 +30,24 @@
/**
+ * Function called with the result of an MFA operation.
+ *
+ * @param cls closure
+ * @param res #GNUNET_OK if all challenges have been satisfied
+ * #GNUNET_NO if a challenge response was returned to the client
+ * #GNUNET_SYSERR to just close the connection without response
+ * @param http_status HTTP status code of @a response, non-zero only if
+ * @a res is #GNUNET_NO
+ * @param response HTTP response to queue, only if @a res is #GNUNET_NO
+ */
+typedef void
+(*TMH_MFA_ResultCallback)(void *cls,
+ enum GNUNET_GenericReturnValue res,
+ unsigned int http_status,
+ struct MHD_Response *response);
+
+
+/**
* Multi-factor authentication check to see if for the given @a instance_id
* and the @a op operation all the TAN channels given in @a required_tans have
* been satisfied. Note that we always satisfy @a required_tans in the order
@@ -46,15 +64,25 @@
* @param required_addresses array of addresses to use for
* the respective challenge, NULL-terminated, same length
* as @a required_tans
- * @return #GNUNET_OK if all challenges have been satisfied
- * #GNUNET_NO if a challenge response was returned to the client
- * #GNUNET_SYSERR to just close the connection without response
+ * @param cb continuation to call with the result
+ * @param cb_cls closure for @a cb
*/
-enum GNUNET_DB_QueryStatus
+void
TMH_mfa_challenge_check (
struct TMH_HandlerContext *hc,
enum TALER_MERCHANT_MFA_CriticalOperation op,
enum TALER_MERCHANT_MFA_Channel required_tans[],
- const char *required_addresses[]);
+ const char *required_addresses[],
+ TMH_MFA_ResultCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Function to call when the HTTP server is shutting down to
+ * clean up all ongoing MFA challenge processes.
+ */
+void
+TMH_mfa_done (void);
+
#endif
diff --git a/src/include/taler_merchant_util.h b/src/include/taler_merchant_util.h
@@ -56,7 +56,7 @@ enum TALER_MERCHANT_MFA_Channel
TALER_MERCHANT_MFA_CHANNEL_EMAIL,
/**
- * TOTP ("totp"). (Not yet implemented.)
+ * TOTP ("totp"). (Not yet implemented, #10327.)
*/
TALER_MERCHANT_MFA_CHANNEL_TOTP
};