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