merchant

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

commit 1c71613d16918ab9d97e75f336a393d79892485e
parent ee187192164b1bd0a4f76f5416c5a2d9dd93567c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue,  2 Sep 2025 23:00:06 +0200

fix tutorial build, tutorial is no longer in prebuilt

Diffstat:
Asrc/backend/taler-merchant-httpd_post-challenge-ID-confirm.c | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_post-challenge-ID-confirm.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_post-challenge-ID.c | 701+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_post-challenge-ID.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 921 insertions(+), 0 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-challenge-ID-confirm.c b/src/backend/taler-merchant-httpd_post-challenge-ID-confirm.c @@ -0,0 +1,126 @@ +/* + This file is part of TALER + (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-merchant-httpd_post-challenge-ID-confirm.c + * @brief endpoint to solve MFA challenge + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_mfa.h" +#include "taler-merchant-httpd_post-challenge-ID-confirm.h" + + +MHD_RESULT +TMH_post_challenge_ID_confirm (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + uint64_t challenge_serial; + struct TALER_MERCHANT_MFA_BodyHash h_body; + const char *tan; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("tan", + &tan), + GNUNET_JSON_spec_end () + }; + enum GNUNET_DB_QueryStatus qs; + bool solved; + uint32_t retry_counter; + enum GNUNET_GenericReturnValue ret; + + ret = TMH_mfa_parse_challenge_id (hc, + hc->infix, + &challenge_serial, + &h_body); + if (GNUNET_OK != ret) + return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (hc->connection, + hc->request_body, + spec); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + return (GNUNET_NO == res) + ? MHD_YES + : MHD_NO; + } + } + qs = TMH_db->solve_mfa_challenge (TMH_db->cls, + challenge_serial, + hc->instance->settings.id, + &h_body, + tan, + &solved, + &retry_counter); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + hc->connection, + + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "solve_mfa_challenge"); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + "solve_mfa_challenge"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_TAN_CHALLENGE_UNKNOWN, + hc->infix); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + if (0 == retry_counter) + { + return TALER_MHD_reply_with_error ( + hc->connection, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_EC_MERCHANT_TAN_TOO_MANY_ATTEMPTS, + NULL); + } + if (! solved) + { + return TALER_MHD_REPLY_JSON_PACK ( + hc->connection, + MHD_HTTP_CONFLICT, + TALER_MHD_PACK_EC (TALER_EC_MERCHANT_TAN_CHALLENGE_FAILED), + GNUNET_JSON_pack_uint64 ("retry_counter", + retry_counter)); + } + return TALER_MHD_reply_static ( + hc->connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); +} diff --git a/src/backend/taler-merchant-httpd_post-challenge-ID-confirm.h b/src/backend/taler-merchant-httpd_post-challenge-ID-confirm.h @@ -0,0 +1,45 @@ +/* + This file is part of TALER + (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-merchant-httpd_post-challenge-ID-confirm.h + * @brief endpoint to solve MFA challenge + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_POST_CHALLENGE_ID_CONFIRM_H +#define TALER_EXCHANGE_HTTPD_POST_CHALLENGE_ID_CONFIRM_H + +#include <microhttpd.h> +#include "taler-merchant-httpd.h" + + +/** + * Client submits TAN code to solve multi-factor authentication challenge. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_post_challenge_ID_confirm (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + + +#endif diff --git a/src/backend/taler-merchant-httpd_post-challenge-ID.c b/src/backend/taler-merchant-httpd_post-challenge-ID.c @@ -0,0 +1,701 @@ +/* + This file is part of TALER + (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-merchant-httpd_post-challenge-ID.c + * @brief endpoint to trigger sending MFA challenge + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_mfa.h" +#include "taler-merchant-httpd_post-challenge-ID.h" + + +/** + * How many attempts do we allow per solution at most? Note that + * this is just for the API, the value must also match the + * database logic in create_mfa_challenge. + */ +#define MAX_SOLUTIONS 3 + + +/** + * How long is an OTP code valid? + */ +#define OTP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + + +/** + * 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; + + /** + * Address where to send the challenge. + */ + char *required_address; + + /** + * Message to send. + */ + char *msg; + + /** + * Offset of transmission in msg. + */ + size_t msg_off; + + /** + * ID of our challenge. + */ + uint64_t challenge_id; + + /** + * Salted hash over the request body. + */ + struct TALER_MERCHANT_MFA_BodyHash h_body; + + /** + * Channel to use for the challenge. + */ + enum TALER_MERCHANT_MFA_Channel channel; + + enum + { + MFA_PHASE_PARSE = 0, + MFA_PHASE_LOOKUP, + MFA_PHASE_SENDING, + MFA_PHASE_SUSPENDING, + MFA_PHASE_SENT, + MFA_PHASE_RETURN_YES, + MFA_PHASE_RETURN_NO, + + } phase; + + + /** + * #GNUNET_NO if the @e connection was not suspended, + * #GNUNET_YES if the @e connection was suspended, + * #GNUNET_SYSERR if @e connection was resumed to as + * part of #THM_mfa_done during shutdown. + */ + enum GNUNET_GenericReturnValue suspended; + + /** + * Set to true if sending worked. + */ + bool send_ok; +}; + + +/** + * Kept in a DLL. + */ +static struct MfaState *mfa_head; + +/** + * Kept in a DLL. + */ +static struct MfaState *mfa_tail; + + +/** + * Clean up @a mfa process. + * + * @param[in] cls the `struct MfaState` to clean up + */ +static void +mfa_context_cleanup (void *cls) +{ + struct MfaState *mfa = cls; + + GNUNET_CONTAINER_DLL_remove (mfa_head, + mfa_tail, + mfa); + 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->required_address); + GNUNET_free (mfa->msg); + GNUNET_free (mfa->code); + GNUNET_free (mfa); +} + + +void +TMH_challenge_done () +{ + for (struct MfaState *mfa = mfa_head; + NULL != mfa; + mfa = mfa->next) + { + if (GNUNET_YES == mfa->suspended) + { + mfa->suspended = GNUNET_SYSERR; + MHD_resume_connection (mfa->hc->connection); + } + } +} + + +/** + * 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); + return NULL; + case TALER_MERCHANT_MFA_CHANNEL_SMS: + { + 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: + { + 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: + GNUNET_break (0); + return GNUNET_strdup ("TOTP is not implemented: #10327"); + } + GNUNET_break (0); + return NULL; +} + + +/** + * Send the given @a response for the @a mfa request. + * + * @param[in,out] mfa process to generate an error response for + * @param response_code response code to use + * @param[in] response response data to send back + */ +static void +respond_to_challenge_with_response (struct MfaState *mfa, + unsigned int response_code, + struct MHD_Response *response) +{ + MHD_RESULT res; + + res = MHD_queue_response (mfa->hc->connection, + response_code, + response); + MHD_destroy_response (response); + mfa->phase = (MHD_NO == res) + ? MFA_PHASE_RETURN_NO + : MFA_PHASE_RETURN_YES; +} + + +/** + * Generate an error for @a mfa. + * + * @param[in,out] 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) +{ + respond_to_challenge_with_response ( + mfa, + http_status, + TALER_MHD_make_error (ec, + hint)); +} + + +/** + * Challenge code transmission complete. Continue based on the result. + * + * @param[in,out] mfa process to send the challenge for + */ +static void +phase_sent (struct MfaState *mfa) +{ + enum GNUNET_DB_QueryStatus qs; + char *address_hint; + + if (! mfa->send_ok) + { + respond_with_error (mfa, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED, + "process exited with error"); + return; + } + qs = TMH_db->update_mfa_challenge (TMH_db->cls, + mfa->challenge_id, + mfa->code, + MAX_SOLUTIONS, + mfa->expiration_date, + mfa->retransmission_date); + 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, + "update_mfa_challenge"); + return; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + respond_with_error (mfa, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + "update_mfa_challenge"); + return; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break (0); + 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; + } + address_hint = get_hint (mfa->channel, + mfa->required_address); + respond_to_challenge_with_response ( + mfa, + MHD_HTTP_ACCEPTED, + TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_string ("address_hint", + address_hint), + GNUNET_JSON_pack_string ("challenge_type", + TALER_MERCHANT_MFA_channel_to_string ( + mfa->channel)), + GNUNET_JSON_pack_uint64 ("challenge_id", + mfa->challenge_id), + GNUNET_JSON_pack_data_auto ("h_body", + &mfa->h_body))); + GNUNET_free (address_hint); +} + + +/** + * 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; + + mfa->cwh = NULL; + if (NULL != mfa->child) + { + GNUNET_OS_process_destroy (mfa->child); + mfa->child = NULL; + } + mfa->send_ok = ( (GNUNET_OS_PROCESS_EXITED == type) && + (0 == exit_code) ); + if (! mfa->send_ok) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "MFA helper failed with status %d/%u\n", + (int) type, + (unsigned int) exit_code); + mfa->phase = MFA_PHASE_SENT; + GNUNET_assert (GNUNET_YES == mfa->suspended); + mfa->suspended = GNUNET_NO; + MHD_resume_connection (mfa->hc->connection); + TALER_MHD_daemon_trigger (); +} + + +/** + * Setup challenge code for @a mfa and send it to the + * @a required_address; on success. + * + * @param[in,out] mfa process to send the challenge for + * @param required_address where to send the challenge + */ +static void +phase_send_challenge (struct MfaState *mfa) +{ + const char *prog; + + switch (mfa->channel) + { + 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 = TMH_helper_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 = TMH_helper_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_FEATURE_NOT_IMPLEMENTED, + "#10327"); + return; + } + if (NULL == prog) + { + respond_with_error ( + mfa, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + TALER_MERCHANT_MFA_channel_to_string (mfa->channel)); + return; + } + { + /* 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_ALLOCATION_FAILURE, + "pipe"); + return; + } + mfa->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, + p, + NULL, + NULL, + prog, + prog, + mfa->required_address, + NULL); + if (NULL == mfa->child) + { + GNUNET_DISK_pipe_close (p); + respond_with_error (mfa, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_TAN_MFA_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_MERCHANT_TAN_MFA_HELPER_EXEC_FAILED, + "write"); + return; + } + mfa->msg_off += ret; + off += ret; + left -= ret; + } + GNUNET_DISK_file_close (pipe_stdin); + } + } + mfa->phase = MFA_PHASE_SUSPENDING; +} + + +/** + * Lookup challenge in DB. + * + * @param[in,out] mfa process to parse data for + */ +static void +phase_lookup (struct MfaState *mfa) +{ + enum GNUNET_DB_QueryStatus qs; + uint32_t retry_counter; + struct GNUNET_TIME_Absolute confirmation_date; + struct GNUNET_TIME_Absolute retransmission_date; + struct TALER_MERCHANT_MFA_BodySalt salt; + enum TALER_MERCHANT_MFA_CriticalOperation op; + + qs = TMH_db->lookup_mfa_challenge (TMH_db->cls, + mfa->hc->instance->settings.id, + mfa->challenge_id, + &mfa->h_body, + &salt, + &mfa->required_address, + &op, + &confirmation_date, + &retransmission_date, + &retry_counter, + &mfa->channel); + 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, + "lookup_mfa_challenge"); + return; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + respond_with_error (mfa, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + "lookup_mfa_challenge"); + return; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break (0); + respond_with_error (mfa, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_TAN_CHALLENGE_UNKNOWN, + mfa->hc->infix); + return; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + if (! GNUNET_TIME_absolute_is_future (confirmation_date)) + { + /* was already solved */ + respond_with_error (mfa, + MHD_HTTP_GONE, + TALER_EC_MERCHANT_TAN_CHALLENGE_SOLVED, + NULL); + return; + } + if (! GNUNET_TIME_absolute_is_future (retransmission_date)) + { + /* too early to try again */ + respond_with_error (mfa, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_EC_MERCHANT_TAN_TOO_EARLY, + GNUNET_TIME_absolute2s (retransmission_date)); + return; + } + mfa->phase++; +} + + +/** + * Parse challenge request. + * + * @param[in,out] mfa process to parse data for + */ +static void +phase_parse (struct MfaState *mfa) +{ + struct TMH_HandlerContext *hc = mfa->hc; + enum GNUNET_GenericReturnValue ret; + + ret = TMH_mfa_parse_challenge_id (hc, + hc->infix, + &mfa->challenge_id, + &mfa->h_body); + if (GNUNET_OK != ret) + { + mfa->phase = (GNUNET_NO == ret) + ? MFA_PHASE_RETURN_YES + : MFA_PHASE_RETURN_NO; + return; + } + mfa->phase++; +} + + +MHD_RESULT +TMH_post_challenge_ID (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct MfaState *mfa = hc->ctx; + + if (NULL == mfa) + { + mfa = GNUNET_new (struct MfaState); + mfa->hc = hc; + hc->ctx = mfa; + hc->cc = &mfa_context_cleanup; + GNUNET_CONTAINER_DLL_insert (mfa_head, + mfa_tail, + mfa); + } + + while (1) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing /challenge in phase %d\n", + (int) mfa->phase); + switch (mfa->phase) + { + case MFA_PHASE_PARSE: + phase_parse (mfa); + break; + case MFA_PHASE_LOOKUP: + phase_lookup (mfa); + break; + case MFA_PHASE_SENDING: + phase_send_challenge (mfa); + break; + case MFA_PHASE_SUSPENDING: + mfa->cwh = GNUNET_wait_child (mfa->child, + &transmission_done_cb, + mfa); + if (NULL == mfa->cwh) + { + respond_with_error (mfa, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_ALLOCATION_FAILURE, + "GNUNET_wait_child"); + continue; + } + mfa->suspended = GNUNET_YES; + MHD_suspend_connection (hc->connection); + return MHD_YES; + case MFA_PHASE_SENT: + phase_sent (mfa); + break; + case MFA_PHASE_RETURN_YES: + return MHD_YES; + case MFA_PHASE_RETURN_NO: + GNUNET_break (0); + return MHD_NO; + } + } +} diff --git a/src/backend/taler-merchant-httpd_post-challenge-ID.h b/src/backend/taler-merchant-httpd_post-challenge-ID.h @@ -0,0 +1,49 @@ +/* + This file is part of TALER + (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-merchant-httpd_post-challenge-ID.h + * @brief headers for POST /challenge/$ID handler + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_POST_CHALLENGE_ID_H +#define TALER_EXCHANGE_HTTPD_POST_CHALLENGE_ID_H + +#include <microhttpd.h> +#include "taler-merchant-httpd.h" + + +/** + * Function to call when the HTTP server is shutting down to + * clean up all ongoing MFA challenge processes. + */ +void +TMH_challenge_done (void); + + +/** + * Transmit TAN code for multi-factor authentication to client. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_post_challenge_ID (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +#endif