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 : }
|