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 taler-merchant-httpd_mfa.c
22 : * @brief internal APIs for multi-factor authentication (MFA)
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include "taler-merchant-httpd.h"
27 : #include "taler-merchant-httpd_mfa.h"
28 :
29 :
30 : /**
31 : * How many challenges do we allow at most per request?
32 : */
33 : #define MAX_CHALLENGES 9
34 :
35 : /**
36 : * How long are challenges valid?
37 : */
38 : #define CHALLENGE_LIFETIME GNUNET_TIME_UNIT_DAYS
39 :
40 :
41 : enum GNUNET_GenericReturnValue
42 0 : TMH_mfa_parse_challenge_id (struct TMH_HandlerContext *hc,
43 : const char *challenge_id,
44 : uint64_t *challenge_serial,
45 : struct TALER_MERCHANT_MFA_BodyHash *h_body)
46 : {
47 0 : const char *dash = strchr (challenge_id,
48 : '-');
49 : unsigned long long ser;
50 : char min;
51 :
52 0 : if (NULL == dash)
53 : {
54 0 : GNUNET_break_op (0);
55 : return (MHD_NO ==
56 0 : TALER_MHD_reply_with_error (hc->connection,
57 : MHD_HTTP_BAD_REQUEST,
58 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
59 : "'-' missing in challenge ID"))
60 : ? GNUNET_SYSERR
61 0 : : GNUNET_NO;
62 : }
63 0 : if ( (2 !=
64 0 : sscanf (challenge_id,
65 : "%llu%c%*s",
66 : &ser,
67 0 : &min)) ||
68 0 : ('-' != min) )
69 : {
70 0 : GNUNET_break_op (0);
71 : return (MHD_NO ==
72 0 : TALER_MHD_reply_with_error (hc->connection,
73 : MHD_HTTP_BAD_REQUEST,
74 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
75 : "Invalid number for challenge ID"))
76 : ? GNUNET_SYSERR
77 0 : : GNUNET_NO;
78 : }
79 0 : if (GNUNET_OK !=
80 0 : GNUNET_STRINGS_string_to_data (dash + 1,
81 : strlen (dash + 1),
82 : h_body,
83 : sizeof (*h_body)))
84 : {
85 0 : GNUNET_break_op (0);
86 : return (MHD_NO ==
87 0 : TALER_MHD_reply_with_error (hc->connection,
88 : MHD_HTTP_BAD_REQUEST,
89 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
90 : "Malformed challenge ID"))
91 : ? GNUNET_SYSERR
92 0 : : GNUNET_NO;
93 : }
94 0 : *challenge_serial = (uint64_t) ser;
95 0 : return GNUNET_OK;
96 : }
97 :
98 :
99 : /**
100 : * Check if the given authentication check was already completed.
101 : *
102 : * @param[in,out] hc handler context of the connection to authorize
103 : * @param op operation for which we are requiring authorization
104 : * @param challenge_id ID of the challenge to check if it is done
105 : * @param[out] solved set to true if the challenge was solved,
106 : * set to false if @a challenge_id was not found
107 : * @param[out] channel TAN channel that was used,
108 : * set to #TALER_MERCHANT_MFA_CHANNEL_NONE if @a challenge_id
109 : * was not found
110 : * @param[out] target_address address which was validated,
111 : * set to NULL if @a challenge_id was not found
112 : * @param[out] retry_counter how many attempts are left on the challenge
113 : * @return #GNUNET_OK on success (challenge found)
114 : * #GNUNET_NO if an error message was returned to the client
115 : * #GNUNET_SYSERR to just close the connection
116 : */
117 : static enum GNUNET_GenericReturnValue
118 0 : mfa_challenge_check (
119 : struct TMH_HandlerContext *hc,
120 : enum TALER_MERCHANT_MFA_CriticalOperation op,
121 : const char *challenge_id,
122 : bool *solved,
123 : enum TALER_MERCHANT_MFA_Channel *channel,
124 : char **target_address,
125 : uint32_t *retry_counter)
126 : {
127 : uint64_t challenge_serial;
128 : struct TALER_MERCHANT_MFA_BodyHash h_body;
129 : struct TALER_MERCHANT_MFA_BodyHash x_h_body;
130 : struct TALER_MERCHANT_MFA_BodySalt salt;
131 : struct GNUNET_TIME_Absolute retransmission_date;
132 : enum TALER_MERCHANT_MFA_CriticalOperation xop;
133 : enum GNUNET_DB_QueryStatus qs;
134 : struct GNUNET_TIME_Absolute confirmation_date;
135 : enum GNUNET_GenericReturnValue ret;
136 :
137 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
138 : "Checking status of challenge %s\n",
139 : challenge_id);
140 0 : ret = TMH_mfa_parse_challenge_id (hc,
141 : challenge_id,
142 : &challenge_serial,
143 : &x_h_body);
144 0 : if (GNUNET_OK != ret)
145 0 : return ret;
146 0 : *target_address = NULL;
147 0 : *solved = false;
148 0 : *channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
149 0 : *retry_counter = UINT_MAX;
150 0 : qs = TMH_db->lookup_mfa_challenge (TMH_db->cls,
151 : challenge_serial,
152 : &x_h_body,
153 : &salt,
154 : target_address,
155 : &xop,
156 : &confirmation_date,
157 : &retransmission_date,
158 : retry_counter,
159 : channel);
160 0 : switch (qs)
161 : {
162 0 : case GNUNET_DB_STATUS_HARD_ERROR:
163 0 : GNUNET_break (0);
164 : return (MHD_NO ==
165 0 : TALER_MHD_reply_with_error (hc->connection,
166 : MHD_HTTP_INTERNAL_SERVER_ERROR,
167 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
168 : NULL))
169 : ? GNUNET_SYSERR
170 0 : : GNUNET_NO;
171 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
172 0 : GNUNET_break (0);
173 : return (MHD_NO ==
174 0 : TALER_MHD_reply_with_error (hc->connection,
175 : MHD_HTTP_INTERNAL_SERVER_ERROR,
176 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
177 : NULL))
178 : ? GNUNET_SYSERR
179 0 : : GNUNET_NO;
180 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
181 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
182 : "Challenge %s not found\n",
183 : challenge_id);
184 0 : return GNUNET_OK;
185 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
186 0 : break;
187 : }
188 :
189 0 : if (xop != op)
190 : {
191 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
192 : "Challenge was for a different operation (%d!=%d)!\n",
193 : (int) op,
194 : (int) xop);
195 0 : *solved = false;
196 0 : return GNUNET_OK;
197 : }
198 0 : TALER_MERCHANT_mfa_body_hash (hc->request_body,
199 : &salt,
200 : &h_body);
201 0 : if (0 !=
202 0 : GNUNET_memcmp (&h_body,
203 : &x_h_body))
204 : {
205 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
206 : "Challenge was for a different request body!\n");
207 0 : *solved = false;
208 0 : return GNUNET_OK;
209 : }
210 0 : *solved = (! GNUNET_TIME_absolute_is_future (confirmation_date));
211 0 : return GNUNET_OK;
212 : }
213 :
214 :
215 : /**
216 : * Multi-factor authentication check to see if for the given @a instance_id
217 : * and the @a op operation all the TAN channels given in @a required_tans have
218 : * been satisfied. Note that we always satisfy @a required_tans in the order
219 : * given in the array, so if the last one is satisfied, all previous ones must
220 : * have been satisfied before.
221 : *
222 : * If the challenges has not been satisfied, an appropriate response
223 : * is returned to the client of @a hc.
224 : *
225 : * @param[in,out] hc handler context of the connection to authorize
226 : * @param op operation for which we are performing
227 : * @param channel TAN channel to try
228 : * @param expiration_date when should the challenge expire
229 : * @param required_address addresses to use for
230 : * the respective challenge
231 : * @param[out] challenge_id set to the challenge ID, to be freed by
232 : * the caller
233 : * @return #GNUNET_OK on success,
234 : * #GNUNET_NO if an error message was returned to the client
235 : * #GNUNET_SYSERR to just close the connection
236 : */
237 : static enum GNUNET_GenericReturnValue
238 0 : mfa_challenge_start (
239 : struct TMH_HandlerContext *hc,
240 : enum TALER_MERCHANT_MFA_CriticalOperation op,
241 : enum TALER_MERCHANT_MFA_Channel channel,
242 : struct GNUNET_TIME_Absolute expiration_date,
243 : const char *required_address,
244 : char **challenge_id)
245 : {
246 : enum GNUNET_DB_QueryStatus qs;
247 : struct TALER_MERCHANT_MFA_BodySalt salt;
248 : struct TALER_MERCHANT_MFA_BodyHash h_body;
249 : uint64_t challenge_serial;
250 : char *code;
251 :
252 0 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
253 : &salt,
254 : sizeof (salt));
255 0 : TALER_MERCHANT_mfa_body_hash (hc->request_body,
256 : &salt,
257 : &h_body);
258 0 : GNUNET_asprintf (&code,
259 : "%llu",
260 : (unsigned long long)
261 0 : GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
262 : 1000 * 1000 * 100));
263 0 : qs = TMH_db->create_mfa_challenge (TMH_db->cls,
264 : op,
265 : &h_body,
266 : &salt,
267 : code,
268 : expiration_date,
269 0 : GNUNET_TIME_UNIT_ZERO_ABS,
270 : channel,
271 : required_address,
272 : &challenge_serial);
273 0 : GNUNET_free (code);
274 0 : switch (qs)
275 : {
276 0 : case GNUNET_DB_STATUS_HARD_ERROR:
277 0 : GNUNET_break (0);
278 : return (MHD_NO ==
279 0 : TALER_MHD_reply_with_error (hc->connection,
280 : MHD_HTTP_INTERNAL_SERVER_ERROR,
281 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
282 : NULL))
283 : ? GNUNET_SYSERR
284 0 : : GNUNET_NO;
285 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
286 0 : GNUNET_break (0);
287 : return (MHD_NO ==
288 0 : TALER_MHD_reply_with_error (hc->connection,
289 : MHD_HTTP_INTERNAL_SERVER_ERROR,
290 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
291 : NULL))
292 : ? GNUNET_SYSERR
293 0 : : GNUNET_NO;
294 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
295 0 : GNUNET_assert (0);
296 : break;
297 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
298 0 : break;
299 : }
300 : {
301 : char *h_body_s;
302 :
303 0 : h_body_s = GNUNET_STRINGS_data_to_string_alloc (&h_body,
304 : sizeof (h_body));
305 0 : GNUNET_asprintf (challenge_id,
306 : "%llu-%s",
307 : (unsigned long long) challenge_serial,
308 : h_body_s);
309 0 : GNUNET_free (h_body_s);
310 : }
311 0 : return GNUNET_OK;
312 : }
313 :
314 :
315 : /**
316 : * Internal book-keeping for #TMH_mfa_challenges_do().
317 : */
318 : struct Challenge
319 : {
320 : /**
321 : * Channel on which the challenge is transmitted.
322 : */
323 : enum TALER_MERCHANT_MFA_Channel channel;
324 :
325 : /**
326 : * Address to send the challenge to.
327 : */
328 : const char *required_address;
329 :
330 : /**
331 : * Internal challenge ID.
332 : */
333 : char *challenge_id;
334 :
335 : /**
336 : * True if the challenge was solved.
337 : */
338 : bool solved;
339 :
340 : /**
341 : * True if the challenge could still be solved.
342 : */
343 : bool solvable;
344 :
345 : };
346 :
347 :
348 : /**
349 : * Obtain hint about the @a target_address of type @a channel to
350 : * return to the client.
351 : *
352 : * @param channel type of challenge
353 : * @param target_address address we will sent the challenge to
354 : * @return hint for the user about the address
355 : */
356 : static char *
357 0 : get_hint (enum TALER_MERCHANT_MFA_Channel channel,
358 : const char *target_address)
359 : {
360 0 : switch (channel)
361 : {
362 0 : case TALER_MERCHANT_MFA_CHANNEL_NONE:
363 0 : GNUNET_assert (0);
364 : return NULL;
365 0 : case TALER_MERCHANT_MFA_CHANNEL_SMS:
366 : {
367 0 : size_t slen = strlen (target_address);
368 : const char *end;
369 :
370 0 : if (slen > 4)
371 0 : end = &target_address[slen - 4];
372 : else
373 0 : end = &target_address[slen / 2];
374 0 : return GNUNET_strdup (end);
375 : }
376 0 : case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
377 : {
378 : const char *at;
379 : size_t len;
380 :
381 0 : at = strchr (target_address,
382 : '@');
383 0 : if (NULL == at)
384 0 : len = 0;
385 : else
386 0 : len = at - target_address;
387 0 : return GNUNET_strndup (target_address,
388 : len);
389 : }
390 0 : case TALER_MERCHANT_MFA_CHANNEL_TOTP:
391 0 : GNUNET_break (0);
392 0 : return GNUNET_strdup ("TOTP is not implemented: #10327");
393 : }
394 0 : GNUNET_break (0);
395 0 : return NULL;
396 : }
397 :
398 :
399 : /**
400 : * Check that a set of MFA challenges has been satisfied by the
401 : * client for the request in @a hc.
402 : *
403 : * @param[in,out] hc handler context with the connection to the client
404 : * @param op operation for which we should check challenges for
405 : * @param combi_and true to tell the client to solve all challenges (AND),
406 : * false means that any of the challenges will do (OR)
407 : * @param ... pairs of channel and address, terminated by
408 : * #TALER_MERCHANT_MFA_CHANNEL_NONE
409 : * @return #GNUNET_OK on success (challenges satisfied)
410 : * #GNUNET_NO if an error message was returned to the client
411 : * #GNUNET_SYSERR to just close the connection
412 : */
413 : enum GNUNET_GenericReturnValue
414 0 : TMH_mfa_challenges_do (
415 : struct TMH_HandlerContext *hc,
416 : enum TALER_MERCHANT_MFA_CriticalOperation op,
417 : bool combi_and,
418 : ...)
419 : {
420 : struct Challenge challenges[MAX_CHALLENGES];
421 : const char *challenge_ids[MAX_CHALLENGES];
422 : size_t num_challenges;
423 0 : char *challenge_ids_copy = NULL;
424 : size_t num_provided_challenges;
425 : enum GNUNET_GenericReturnValue ret;
426 :
427 : {
428 : va_list ap;
429 :
430 0 : va_start (ap,
431 : combi_and);
432 0 : for (num_challenges = 0;
433 0 : num_challenges < MAX_CHALLENGES;
434 0 : num_challenges++)
435 : {
436 : enum TALER_MERCHANT_MFA_Channel channel;
437 : const char *address;
438 :
439 0 : channel = va_arg (ap,
440 : enum TALER_MERCHANT_MFA_Channel);
441 0 : if (TALER_MERCHANT_MFA_CHANNEL_NONE == channel)
442 0 : break;
443 0 : address = va_arg (ap,
444 : const char *);
445 0 : GNUNET_assert (NULL != address);
446 0 : challenges[num_challenges].channel = channel;
447 0 : challenges[num_challenges].required_address = address;
448 0 : challenges[num_challenges].challenge_id = NULL;
449 0 : challenges[num_challenges].solved = false;
450 0 : challenges[num_challenges].solvable = true;
451 : }
452 0 : va_end (ap);
453 : }
454 :
455 0 : if (0 == num_challenges)
456 : {
457 : /* No challenges required. Strange... */
458 0 : return GNUNET_OK;
459 : }
460 :
461 : {
462 : const char *challenge_ids_header;
463 :
464 : challenge_ids_header
465 0 : = MHD_lookup_connection_value (hc->connection,
466 : MHD_HEADER_KIND,
467 : "Taler-Challenge-Ids");
468 0 : num_provided_challenges = 0;
469 0 : if (NULL != challenge_ids_header)
470 : {
471 0 : challenge_ids_copy = GNUNET_strdup (challenge_ids_header);
472 :
473 0 : for (char *token = strtok (challenge_ids_copy,
474 : ",");
475 0 : NULL != token;
476 0 : token = strtok (NULL,
477 : ","))
478 : {
479 0 : if (num_provided_challenges >= MAX_CHALLENGES)
480 : {
481 0 : GNUNET_break_op (0);
482 0 : GNUNET_free (challenge_ids_copy);
483 : return (MHD_NO ==
484 0 : TALER_MHD_reply_with_error (
485 : hc->connection,
486 : MHD_HTTP_BAD_REQUEST,
487 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
488 : "Taler-Challenge-Ids"))
489 : ? GNUNET_SYSERR
490 0 : : GNUNET_NO;
491 : }
492 0 : challenge_ids[num_provided_challenges] = token;
493 0 : num_provided_challenges++;
494 : }
495 : }
496 : }
497 :
498 : /* Check provided challenges against requirements */
499 0 : for (size_t i = 0; i < num_provided_challenges; i++)
500 : {
501 : bool solved;
502 : enum TALER_MERCHANT_MFA_Channel channel;
503 : char *target_address;
504 : uint32_t retry_counter;
505 :
506 0 : ret = mfa_challenge_check (hc,
507 : op,
508 : challenge_ids[i],
509 : &solved,
510 : &channel,
511 : &target_address,
512 : &retry_counter);
513 0 : if (GNUNET_OK != ret)
514 0 : goto cleanup;
515 0 : for (size_t j = 0; j < num_challenges; j++)
516 : {
517 0 : if ( (challenges[j].channel == channel) &&
518 0 : (NULL == challenges[j].challenge_id) &&
519 0 : (NULL != target_address /* just to be sure */) &&
520 0 : (0 == strcmp (target_address,
521 : challenges[j].required_address) ) )
522 : {
523 : challenges[j].solved
524 0 : = solved;
525 : challenges[j].challenge_id
526 0 : = GNUNET_strdup (challenge_ids[i]);
527 0 : if ( (! solved) &&
528 0 : (0 == retry_counter) )
529 : {
530 : /* can't be solved anymore! */
531 0 : challenges[i].solvable = false;
532 : }
533 0 : break;
534 : }
535 : }
536 0 : GNUNET_free (target_address);
537 : }
538 :
539 : {
540 : struct GNUNET_TIME_Absolute expiration_date
541 0 : = GNUNET_TIME_relative_to_absolute (CHALLENGE_LIFETIME);
542 :
543 : /* Start new challenges for unsolved requirements */
544 0 : for (size_t i = 0; i < num_challenges; i++)
545 : {
546 0 : if (NULL == challenges[i].challenge_id)
547 : {
548 0 : GNUNET_assert (! challenges[i].solved);
549 0 : GNUNET_assert (challenges[i].solvable);
550 0 : ret = mfa_challenge_start (hc,
551 : op,
552 : challenges[i].channel,
553 : expiration_date,
554 : challenges[i].required_address,
555 : &challenges[i].challenge_id);
556 0 : if (GNUNET_OK != ret)
557 0 : goto cleanup;
558 : }
559 : }
560 : }
561 :
562 : {
563 0 : bool all_solved = true;
564 0 : bool any_solved = false;
565 0 : bool solvable = true;
566 :
567 0 : for (size_t i = 0; i < num_challenges; i++)
568 : {
569 0 : if (challenges[i].solved)
570 : {
571 0 : any_solved = true;
572 : }
573 : else
574 : {
575 0 : all_solved = false;
576 0 : if (combi_and &&
577 0 : (! challenges[i].solvable) )
578 0 : solvable = false;
579 : }
580 : }
581 :
582 0 : if ( (combi_and && all_solved) ||
583 0 : (! combi_and && any_solved) )
584 : {
585 : /* Authorization successful */
586 0 : ret = GNUNET_OK;
587 0 : goto cleanup;
588 : }
589 0 : if (! solvable)
590 : {
591 0 : ret = (MHD_NO ==
592 0 : TALER_MHD_reply_with_error (
593 : hc->connection,
594 : MHD_HTTP_FORBIDDEN,
595 : TALER_EC_MERCHANT_MFA_FORBIDDEN,
596 : GNUNET_TIME_relative2s (CHALLENGE_LIFETIME,
597 : false)))
598 : ? GNUNET_SYSERR
599 0 : : GNUNET_NO;
600 0 : goto cleanup;
601 : }
602 : }
603 :
604 : /* Return challenges to client */
605 : {
606 : json_t *jchallenges;
607 :
608 0 : jchallenges = json_array ();
609 0 : GNUNET_assert (NULL != jchallenges);
610 0 : for (size_t i = 0; i<num_challenges; i++)
611 : {
612 0 : const struct Challenge *c = &challenges[i];
613 : json_t *jc;
614 : char *hint;
615 :
616 0 : hint = get_hint (c->channel,
617 0 : c->required_address);
618 :
619 0 : jc = GNUNET_JSON_PACK (
620 : GNUNET_JSON_pack_string ("tan_info",
621 : hint),
622 : GNUNET_JSON_pack_string ("tan_channel",
623 : TALER_MERCHANT_MFA_channel_to_string (
624 : c->channel)),
625 : GNUNET_JSON_pack_string ("challenge_id",
626 : c->challenge_id));
627 0 : GNUNET_free (hint);
628 0 : GNUNET_assert (0 ==
629 : json_array_append_new (
630 : jchallenges,
631 : jc));
632 : }
633 0 : ret = (MHD_NO ==
634 0 : TALER_MHD_REPLY_JSON_PACK (
635 : hc->connection,
636 : MHD_HTTP_ACCEPTED,
637 : GNUNET_JSON_pack_bool ("combi_and",
638 : combi_and),
639 : GNUNET_JSON_pack_array_steal ("challenges",
640 : jchallenges)))
641 : ? GNUNET_SYSERR
642 0 : : GNUNET_NO;
643 : }
644 :
645 0 : cleanup:
646 0 : for (size_t i = 0; i < num_challenges; i++)
647 0 : GNUNET_free (challenges[i].challenge_id);
648 0 : GNUNET_free (challenge_ids_copy);
649 0 : return ret;
650 : }
651 :
652 :
653 : enum GNUNET_GenericReturnValue
654 34 : TMH_mfa_check_simple (
655 : struct TMH_HandlerContext *hc,
656 : enum TALER_MERCHANT_MFA_CriticalOperation op,
657 : struct TMH_MerchantInstance *mi)
658 : {
659 : enum GNUNET_GenericReturnValue ret;
660 68 : bool have_sms = (NULL != mi->settings.phone) &&
661 34 : (NULL != TMH_helper_sms) &&
662 0 : (mi->settings.phone_validated);
663 68 : bool have_email = (NULL != mi->settings.email) &&
664 34 : (NULL != TMH_helper_email) &&
665 0 : (mi->settings.email_validated);
666 :
667 : /* Note: we check for 'validated' above, but in theory
668 : we could also use unvalidated for this operation.
669 : That's a policy-decision we may want to revise,
670 : but probably need to look at the global threat model to
671 : make sure alternative configurations are still sane. */
672 34 : if (have_email)
673 : {
674 0 : ret = TMH_mfa_challenges_do (hc,
675 : op,
676 : false,
677 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
678 : mi->settings.email,
679 : have_sms
680 : ? TALER_MERCHANT_MFA_CHANNEL_SMS
681 : : TALER_MERCHANT_MFA_CHANNEL_NONE,
682 : mi->settings.phone,
683 : TALER_MERCHANT_MFA_CHANNEL_NONE);
684 : }
685 34 : else if (have_sms)
686 : {
687 0 : ret = TMH_mfa_challenges_do (hc,
688 : op,
689 : false,
690 : TALER_MERCHANT_MFA_CHANNEL_SMS,
691 : mi->settings.phone,
692 : TALER_MERCHANT_MFA_CHANNEL_NONE);
693 : }
694 : else
695 : {
696 34 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
697 : "No MFA possible, skipping 2-FA\n");
698 34 : ret = GNUNET_OK;
699 : }
700 34 : return ret;
701 : }
|