merchant

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

taler-merchant-httpd_mfa.c (22067B)


      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 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   const char *dash = strchr (challenge_id,
     48                              '-');
     49   unsigned long long ser;
     50   char min;
     51 
     52   if (NULL == dash)
     53   {
     54     GNUNET_break_op (0);
     55     return (MHD_NO ==
     56             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       : GNUNET_NO;
     62   }
     63   if ( (2 !=
     64         sscanf (challenge_id,
     65                 "%llu%c%*s",
     66                 &ser,
     67                 &min)) ||
     68        ('-' != min) )
     69   {
     70     GNUNET_break_op (0);
     71     return (MHD_NO ==
     72             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       : GNUNET_NO;
     78   }
     79   if (GNUNET_OK !=
     80       GNUNET_STRINGS_string_to_data (dash + 1,
     81                                      strlen (dash + 1),
     82                                      h_body,
     83                                      sizeof (*h_body)))
     84   {
     85     GNUNET_break_op (0);
     86     return (MHD_NO ==
     87             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       : GNUNET_NO;
     93   }
     94   *challenge_serial = (uint64_t) ser;
     95   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 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   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    138               "Checking status of challenge %s\n",
    139               challenge_id);
    140   ret = TMH_mfa_parse_challenge_id (hc,
    141                                     challenge_id,
    142                                     &challenge_serial,
    143                                     &x_h_body);
    144   if (GNUNET_OK != ret)
    145     return ret;
    146   *target_address = NULL;
    147   *solved = false;
    148   *channel = TALER_MERCHANT_MFA_CHANNEL_NONE;
    149   *retry_counter = UINT_MAX;
    150   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   switch (qs)
    161   {
    162   case GNUNET_DB_STATUS_HARD_ERROR:
    163     GNUNET_break (0);
    164     return (MHD_NO ==
    165             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       : GNUNET_NO;
    171   case GNUNET_DB_STATUS_SOFT_ERROR:
    172     GNUNET_break (0);
    173     return (MHD_NO ==
    174             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       : GNUNET_NO;
    180   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    181     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    182                 "Challenge %s not found\n",
    183                 challenge_id);
    184     return GNUNET_OK;
    185   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    186     break;
    187   }
    188 
    189   if (xop != op)
    190   {
    191     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    192                 "Challenge was for a different operation (%d!=%d)!\n",
    193                 (int) op,
    194                 (int) xop);
    195     *solved = false;
    196     return GNUNET_OK;
    197   }
    198   TALER_MERCHANT_mfa_body_hash (hc->request_body,
    199                                 &salt,
    200                                 &h_body);
    201   if (0 !=
    202       GNUNET_memcmp (&h_body,
    203                      &x_h_body))
    204   {
    205     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    206                 "Challenge was for a different request body!\n");
    207     *solved = false;
    208     return GNUNET_OK;
    209   }
    210   *solved = (! GNUNET_TIME_absolute_is_future (confirmation_date));
    211   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 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   GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
    253                               &salt,
    254                               sizeof (salt));
    255   TALER_MERCHANT_mfa_body_hash (hc->request_body,
    256                                 &salt,
    257                                 &h_body);
    258   GNUNET_asprintf (&code,
    259                    "%llu",
    260                    (unsigned long long)
    261                    GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
    262                                              1000 * 1000 * 100));
    263   qs = TMH_db->create_mfa_challenge (TMH_db->cls,
    264                                      op,
    265                                      &h_body,
    266                                      &salt,
    267                                      code,
    268                                      expiration_date,
    269                                      GNUNET_TIME_UNIT_ZERO_ABS,
    270                                      channel,
    271                                      required_address,
    272                                      &challenge_serial);
    273   GNUNET_free (code);
    274   switch (qs)
    275   {
    276   case GNUNET_DB_STATUS_HARD_ERROR:
    277     GNUNET_break (0);
    278     return (MHD_NO ==
    279             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       : GNUNET_NO;
    285   case GNUNET_DB_STATUS_SOFT_ERROR:
    286     GNUNET_break (0);
    287     return (MHD_NO ==
    288             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       : GNUNET_NO;
    294   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    295     GNUNET_assert (0);
    296     break;
    297   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    298     break;
    299   }
    300   {
    301     char *h_body_s;
    302 
    303     h_body_s = GNUNET_STRINGS_data_to_string_alloc (&h_body,
    304                                                     sizeof (h_body));
    305     GNUNET_asprintf (challenge_id,
    306                      "%llu-%s",
    307                      (unsigned long long) challenge_serial,
    308                      h_body_s);
    309     GNUNET_free (h_body_s);
    310   }
    311   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 get_hint (enum TALER_MERCHANT_MFA_Channel channel,
    358           const char *target_address)
    359 {
    360   switch (channel)
    361   {
    362   case TALER_MERCHANT_MFA_CHANNEL_NONE:
    363     GNUNET_assert (0);
    364     return NULL;
    365   case TALER_MERCHANT_MFA_CHANNEL_SMS:
    366     {
    367       size_t slen = strlen (target_address);
    368       const char *end;
    369 
    370       if (slen > 4)
    371         end = &target_address[slen - 4];
    372       else
    373         end = &target_address[slen / 2];
    374       return GNUNET_strdup (end);
    375     }
    376   case TALER_MERCHANT_MFA_CHANNEL_EMAIL:
    377     {
    378       const char *at;
    379       size_t len;
    380 
    381       at = strchr (target_address,
    382                    '@');
    383       if (NULL == at)
    384         len = 0;
    385       else
    386         len = at - target_address;
    387       return GNUNET_strndup (target_address,
    388                              len);
    389     }
    390   case TALER_MERCHANT_MFA_CHANNEL_TOTP:
    391     GNUNET_break (0);
    392     return GNUNET_strdup ("TOTP is not implemented: #10327");
    393   }
    394   GNUNET_break (0);
    395   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 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   char *challenge_ids_copy = NULL;
    424   size_t num_provided_challenges;
    425   enum GNUNET_GenericReturnValue ret;
    426 
    427   {
    428     va_list ap;
    429 
    430     va_start (ap,
    431               combi_and);
    432     for (num_challenges = 0;
    433          num_challenges < MAX_CHALLENGES;
    434          num_challenges++)
    435     {
    436       enum TALER_MERCHANT_MFA_Channel channel;
    437       const char *address;
    438 
    439       channel = va_arg (ap,
    440                         enum TALER_MERCHANT_MFA_Channel);
    441       if (TALER_MERCHANT_MFA_CHANNEL_NONE == channel)
    442         break;
    443       address = va_arg (ap,
    444                         const char *);
    445       GNUNET_assert (NULL != address);
    446       challenges[num_challenges].channel = channel;
    447       challenges[num_challenges].required_address = address;
    448       challenges[num_challenges].challenge_id = NULL;
    449       challenges[num_challenges].solved = false;
    450       challenges[num_challenges].solvable = true;
    451     }
    452     va_end (ap);
    453   }
    454 
    455   if (0 == num_challenges)
    456   {
    457     /* No challenges required. Strange... */
    458     return GNUNET_OK;
    459   }
    460 
    461   {
    462     const char *challenge_ids_header;
    463 
    464     challenge_ids_header
    465       = MHD_lookup_connection_value (hc->connection,
    466                                      MHD_HEADER_KIND,
    467                                      "Taler-Challenge-Ids");
    468     num_provided_challenges = 0;
    469     if (NULL != challenge_ids_header)
    470     {
    471       challenge_ids_copy = GNUNET_strdup (challenge_ids_header);
    472 
    473       for (char *token = strtok (challenge_ids_copy,
    474                                  ",");
    475            NULL != token;
    476            token = strtok (NULL,
    477                            ","))
    478       {
    479         if (num_provided_challenges >= MAX_CHALLENGES)
    480         {
    481           GNUNET_break_op (0);
    482           GNUNET_free (challenge_ids_copy);
    483           return (MHD_NO ==
    484                   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           : GNUNET_NO;
    491         }
    492         challenge_ids[num_provided_challenges] = token;
    493         num_provided_challenges++;
    494       }
    495     }
    496   }
    497 
    498   /* Check provided challenges against requirements */
    499   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     ret = mfa_challenge_check (hc,
    507                                op,
    508                                challenge_ids[i],
    509                                &solved,
    510                                &channel,
    511                                &target_address,
    512                                &retry_counter);
    513     if (GNUNET_OK != ret)
    514       goto cleanup;
    515     for (size_t j = 0; j < num_challenges; j++)
    516     {
    517       if ( (challenges[j].channel == channel) &&
    518            (NULL == challenges[j].challenge_id) &&
    519            (NULL != target_address /* just to be sure */) &&
    520            (0 == strcmp (target_address,
    521                          challenges[j].required_address) ) )
    522       {
    523         challenges[j].solved
    524           = solved;
    525         challenges[j].challenge_id
    526           = GNUNET_strdup (challenge_ids[i]);
    527         if ( (! solved) &&
    528              (0 == retry_counter) )
    529         {
    530           /* can't be solved anymore! */
    531           challenges[i].solvable = false;
    532         }
    533         break;
    534       }
    535     }
    536     GNUNET_free (target_address);
    537   }
    538 
    539   {
    540     struct GNUNET_TIME_Absolute expiration_date
    541       = GNUNET_TIME_relative_to_absolute (CHALLENGE_LIFETIME);
    542 
    543     /* Start new challenges for unsolved requirements */
    544     for (size_t i = 0; i < num_challenges; i++)
    545     {
    546       if (NULL == challenges[i].challenge_id)
    547       {
    548         GNUNET_assert (! challenges[i].solved);
    549         GNUNET_assert (challenges[i].solvable);
    550         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         if (GNUNET_OK != ret)
    557           goto cleanup;
    558       }
    559     }
    560   }
    561 
    562   {
    563     bool all_solved = true;
    564     bool any_solved = false;
    565     bool solvable = true;
    566 
    567     for (size_t i = 0; i < num_challenges; i++)
    568     {
    569       if (challenges[i].solved)
    570       {
    571         any_solved = true;
    572       }
    573       else
    574       {
    575         all_solved = false;
    576         if (combi_and &&
    577             (! challenges[i].solvable) )
    578           solvable = false;
    579       }
    580     }
    581 
    582     if ( (combi_and && all_solved) ||
    583          (! combi_and && any_solved) )
    584     {
    585       /* Authorization successful */
    586       ret = GNUNET_OK;
    587       goto cleanup;
    588     }
    589     if (! solvable)
    590     {
    591       ret = (MHD_NO ==
    592              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         : GNUNET_NO;
    600       goto cleanup;
    601     }
    602   }
    603 
    604   /* Return challenges to client */
    605   {
    606     json_t *jchallenges;
    607 
    608     jchallenges = json_array ();
    609     GNUNET_assert (NULL != jchallenges);
    610     for (size_t i = 0; i<num_challenges; i++)
    611     {
    612       const struct Challenge *c = &challenges[i];
    613       json_t *jc;
    614       char *hint;
    615 
    616       hint = get_hint (c->channel,
    617                        c->required_address);
    618 
    619       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       GNUNET_free (hint);
    628       GNUNET_assert (0 ==
    629                      json_array_append_new (
    630                        jchallenges,
    631                        jc));
    632     }
    633     ret = (MHD_NO ==
    634            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       : GNUNET_NO;
    643   }
    644 
    645 cleanup:
    646   for (size_t i = 0; i < num_challenges; i++)
    647     GNUNET_free (challenges[i].challenge_id);
    648   GNUNET_free (challenge_ids_copy);
    649   return ret;
    650 }
    651 
    652 
    653 enum GNUNET_GenericReturnValue
    654 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   bool have_sms = (NULL != mi->settings.phone) &&
    661                   (NULL != TMH_helper_sms) &&
    662                   (mi->settings.phone_validated);
    663   bool have_email = (NULL != mi->settings.email) &&
    664                     (NULL != TMH_helper_email) &&
    665                     (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   if (have_email)
    673   {
    674     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   else if (have_sms)
    686   {
    687     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     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    697                 "No MFA possible, skipping 2-FA\n");
    698     ret = GNUNET_OK;
    699   }
    700   return ret;
    701 }