anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

anastasis_authorization_plugin_sms.c (23299B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2019, 2021 Anastasis SARL
      4 
      5   Anastasis is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file anastasis_authorization_plugin_sms.c
     18  * @brief authorization plugin email based
     19  * @author Dominik Meister
     20  */
     21 #include "platform.h"
     22 #include "anastasis_authorization_plugin.h"
     23 #include <taler/taler_mhd_lib.h>
     24 #include <taler/taler_json_lib.h>
     25 #include <regex.h>
     26 #include "anastasis_util_lib.h"
     27 #include <gnunet/gnunet_db_lib.h>
     28 #include "anastasis_database_lib.h"
     29 
     30 /**
     31  * How many retries do we allow per code?
     32  */
     33 #define INITIAL_RETRY_COUNTER 3
     34 
     35 /**
     36  * Saves the State of a authorization plugin.
     37  */
     38 struct SMS_Context
     39 {
     40 
     41   /**
     42    * Command which is executed to run the plugin (some bash script or a
     43    * command line argument)
     44    */
     45   char *auth_command;
     46 
     47   /**
     48    * Regex for phone number validation.
     49    */
     50   regex_t regex;
     51 
     52   /**
     53    * Messages of the plugin, read from a resource file.
     54    */
     55   json_t *messages;
     56 
     57   /**
     58    * Context we operate in.
     59    */
     60   const struct ANASTASIS_AuthorizationContext *ac;
     61 };
     62 
     63 
     64 /**
     65  * Saves the State of a authorization process
     66  */
     67 struct ANASTASIS_AUTHORIZATION_State
     68 {
     69   /**
     70    * Public key of the challenge which is authorised
     71    */
     72   struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
     73 
     74   /**
     75    * Code which is sent to the user (here sent via SMS)
     76    */
     77   uint64_t code;
     78 
     79   /**
     80    * Our plugin context.
     81    */
     82   struct SMS_Context *ctx;
     83 
     84   /**
     85    * Function to call when we made progress.
     86    */
     87   GNUNET_SCHEDULER_TaskCallback trigger;
     88 
     89   /**
     90    * Closure for @e trigger.
     91    */
     92   void *trigger_cls;
     93 
     94   /**
     95    * holds the truth information
     96    */
     97   char *phone_number;
     98 
     99   /**
    100    * Handle to the helper process.
    101    */
    102   struct GNUNET_Process *child;
    103 
    104   /**
    105    * Handle to wait for @e child
    106    */
    107   struct GNUNET_ChildWaitHandle *cwh;
    108 
    109   /**
    110    * Our client connection, set if suspended.
    111    */
    112   struct MHD_Connection *connection;
    113 
    114   /**
    115    * Message to send.
    116    */
    117   char *msg;
    118 
    119   /**
    120    * Offset of transmission in msg.
    121    */
    122   size_t msg_off;
    123 
    124   /**
    125    * Exit code from helper.
    126    */
    127   long unsigned int exit_code;
    128 
    129   /**
    130    * How did the helper die?
    131    */
    132   enum GNUNET_OS_ProcessStatusType pst;
    133 
    134 };
    135 
    136 
    137 /**
    138  * Returned by #get_message() when the configured messages file has no entry
    139  * for the requested ID.  This is an installation error, but it must not be
    140  * allowed to reach GNUNET_asprintf() as a NULL format string.  The text
    141  * deliberately contains no printf conversions, so substituting it for any
    142  * template is safe whatever argument list the caller passes.
    143  */
    144 #define MISSING_MESSAGE \
    145         "The provider is misconfigured: a message template is missing."
    146 
    147 
    148 /**
    149  * Obtain internationalized message @a msg_id from @a ctx using
    150  * language preferences of @a conn.
    151  *
    152  * @param messages JSON object to lookup message from
    153  * @param conn connection to lookup message for
    154  * @param msg_id unique message ID
    155  * @return the requested message, or #MISSING_MESSAGE if it was not
    156  *         configured; never NULL
    157  */
    158 static const char *
    159 get_message (const json_t *messages,
    160              struct MHD_Connection *conn,
    161              const char *msg_id)
    162 {
    163   const char *accept_lang;
    164 
    165   accept_lang = MHD_lookup_connection_value (conn,
    166                                              MHD_HEADER_KIND,
    167                                              MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
    168   if (NULL == accept_lang)
    169     accept_lang = "en_US";
    170   {
    171     const char *ret;
    172     struct GNUNET_JSON_Specification spec[] = {
    173       TALER_JSON_spec_i18n_string (msg_id,
    174                                    accept_lang,
    175                                    &ret),
    176       GNUNET_JSON_spec_end ()
    177     };
    178 
    179     if (GNUNET_OK !=
    180         GNUNET_JSON_parse (messages,
    181                            spec,
    182                            NULL, NULL))
    183     {
    184       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    185                   "Message `%s' is missing from the configured messages file\n",
    186                   msg_id);
    187       GNUNET_JSON_parse_free (spec);
    188       return MISSING_MESSAGE;
    189     }
    190     GNUNET_JSON_parse_free (spec);
    191     if (NULL == ret)
    192     {
    193       /* The parser of TALER_JSON_spec_i18n_string returns #GNUNET_OK even
    194          when the field is absent or is not a string, in which case it stores
    195          NULL; the check above therefore never fires for a missing message and
    196          this one is what keeps NULL out of the format argument of the
    197          GNUNET_asprintf() calls below. */
    198       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    199                   "Message `%s' is missing or not a string in the configured messages file\n",
    200                   msg_id);
    201       return MISSING_MESSAGE;
    202     }
    203     return ret;
    204   }
    205 }
    206 
    207 
    208 /**
    209  * Validate @a data is a well-formed input into the challenge method,
    210  * i.e. @a data is a well-formed phone number for sending an SMS, or
    211  * a well-formed e-mail address for sending an e-mail. Not expected to
    212  * check that the phone number or e-mail account actually exists.
    213  *
    214  * To be possibly used before issuing a 402 payment required to the client.
    215  *
    216  * @param cls closure with a `struct SMS_Context`
    217  * @param connection HTTP client request (for queuing response)
    218  * @param truth_mime mime type of @e data
    219  * @param data input to validate (i.e. is it a valid phone number, etc.)
    220  * @param data_length number of bytes in @a data
    221  * @return #GNUNET_OK if @a data is valid,
    222  *         #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
    223  *         #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
    224  */
    225 static enum GNUNET_GenericReturnValue
    226 sms_validate (void *cls,
    227               struct MHD_Connection *connection,
    228               const char *truth_mime,
    229               const char *data,
    230               size_t data_length)
    231 {
    232   struct SMS_Context *ctx = cls;
    233   int regex_result;
    234   char *phone_number;
    235 
    236   phone_number = GNUNET_strndup (data,
    237                                  data_length);
    238   regex_result = regexec (&ctx->regex,
    239                           phone_number,
    240                           0,
    241                           NULL,
    242                           0);
    243   GNUNET_free (phone_number);
    244   if (0 != regex_result)
    245   {
    246     if (MHD_NO ==
    247         TALER_MHD_reply_with_error (connection,
    248                                     MHD_HTTP_CONFLICT,
    249                                     TALER_EC_ANASTASIS_SMS_PHONE_INVALID,
    250                                     NULL))
    251       return GNUNET_SYSERR;
    252     return GNUNET_NO;
    253   }
    254   return GNUNET_OK;
    255 }
    256 
    257 
    258 /**
    259  * Begin issuing authentication challenge to user based on @a data.
    260  * Sends SMS.
    261  *
    262  * @param cls closure with a `struct SMS_Context`
    263  * @param trigger function to call when we made progress
    264  * @param trigger_cls closure for @a trigger
    265  * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
    266  *             interaction with the user
    267  * @param code secret code that the user has to provide back to satisfy the challenge in
    268  *             the main anastasis protocol
    269  * @param data input to validate (i.e. is it a valid phone number, etc.)
    270  * @param data_length number of bytes in @a data
    271  * @return state to track progress on the authorization operation, NULL on failure
    272  */
    273 static struct ANASTASIS_AUTHORIZATION_State *
    274 sms_start (void *cls,
    275            GNUNET_SCHEDULER_TaskCallback trigger,
    276            void *trigger_cls,
    277            const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
    278            uint64_t code,
    279            const void *data,
    280            size_t data_length)
    281 {
    282   struct SMS_Context *ctx = cls;
    283   struct ANASTASIS_AUTHORIZATION_State *as;
    284   enum GNUNET_DB_QueryStatus qs;
    285 
    286   /* If the user can show this challenge code, this
    287      plugin is already happy (no additional
    288      requirements), so mark this challenge as
    289      already satisfied from the start. */
    290   qs = ANASTASIS_DB_update_to_challenge_code_satisfied (
    291     truth_uuid,
    292     code);
    293   if (qs <= 0)
    294   {
    295     GNUNET_break (0);
    296     return NULL;
    297   }
    298   as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
    299   as->trigger = trigger;
    300   as->trigger_cls = trigger_cls;
    301   as->ctx = ctx;
    302   as->truth_uuid = *truth_uuid;
    303   as->code = code;
    304   as->phone_number = GNUNET_strndup (data,
    305                                      data_length);
    306   return as;
    307 }
    308 
    309 
    310 /**
    311  * Did a transmission helper report that the challenge was transmitted?
    312  *
    313  * Exit code 0 means the challenge was confirmed to have reached the address.
    314  * The 200-210 band means a service accepted it for delivery without confirming
    315  * that it arrived: 201 accepted by the provider, 202 suppressed as a duplicate
    316  * of a message already in flight.
    317  *
    318  * The band deliberately does not start at 1: libgnunetutil reports a helper it
    319  * failed to exec() as exit code 1, and that must not be mistaken for a
    320  * delivery.
    321  *
    322  * @param exit_code exit status of the helper, which must have exited normally
    323  * @return true if the challenge was transmitted
    324  */
    325 static bool
    326 helper_reported_success (unsigned long int exit_code)
    327 {
    328   return (0 == exit_code) ||
    329          ( (exit_code >= 200) &&
    330            (exit_code <= 210) );
    331 }
    332 
    333 
    334 /**
    335  * Map the exit status of the SMS helper onto an HTTP status and error code.
    336  *
    337  * The helpers shipped with Challenger use a banded exit-code scheme documented
    338  * in challenger-send-sms(1): 0 and 200-210 mean the challenge was transmitted
    339  * (see #helper_reported_success()), 10-19 blames the address the user gave
    340  * us, 20-29 is a recipient that is temporarily unreachable, 30-39 is the
    341  * transmission provider and 40-49 is our own configuration.
    342  *
    343  * Codes we do not recognise are reported as an upstream failure rather than
    344  * blamed on the user, so that a helper predating this scheme -- which used
    345  * small ad-hoc exit codes -- never yields a client error.
    346  *
    347  * @param exit_code exit status of the helper, which must have exited normally
    348  * @param[out] http_status set to the HTTP status to return
    349  * @return error code to return, #TALER_EC_NONE if the challenge was transmitted
    350  */
    351 static enum TALER_ErrorCode
    352 classify_helper_status (unsigned long int exit_code,
    353                         unsigned int *http_status)
    354 {
    355   if (helper_reported_success (exit_code))
    356   {
    357     *http_status = MHD_HTTP_OK;
    358     return TALER_EC_NONE;
    359   }
    360   if ( (exit_code >= 10) &&
    361        (exit_code < 20) )
    362   {
    363     *http_status = MHD_HTTP_CONFLICT;
    364     return TALER_EC_ANASTASIS_SMS_PHONE_INVALID;
    365   }
    366   if ( (exit_code >= 20) &&
    367        (exit_code < 30) )
    368   {
    369     *http_status = MHD_HTTP_SERVICE_UNAVAILABLE;
    370     return TALER_EC_ANASTASIS_ADDRESS_UNREACHABLE;
    371   }
    372   if ( (exit_code >= 40) &&
    373        (exit_code < 50) )
    374   {
    375     *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    376     return TALER_EC_ANASTASIS_HELPER_MISCONFIGURED;
    377   }
    378   *http_status = MHD_HTTP_BAD_GATEWAY;
    379   return TALER_EC_ANASTASIS_SMS_HELPER_COMMAND_FAILED;
    380 }
    381 
    382 
    383 /**
    384  * Function called when our SMS helper has terminated.
    385  *
    386  * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
    387  * @param type type of the process
    388  * @param exit_code status code of the process
    389  */
    390 static void
    391 sms_done_cb (void *cls,
    392              enum GNUNET_OS_ProcessStatusType type,
    393              long unsigned int exit_code)
    394 {
    395   struct ANASTASIS_AUTHORIZATION_State *as = cls;
    396 
    397   as->cwh = NULL;
    398   if (NULL != as->child)
    399   {
    400     GNUNET_process_destroy (as->child);
    401     as->child = NULL;
    402   }
    403   as->pst = type;
    404   as->exit_code = exit_code;
    405   MHD_resume_connection (as->connection);
    406   as->trigger (as->trigger_cls);
    407 }
    408 
    409 
    410 /**
    411  * Begin issuing authentication challenge to user based on @a data.
    412  * I.e. start to send SMS or e-mail or launch video identification.
    413  *
    414  * @param as authorization state
    415  * @param connection HTTP client request (for queuing response, such as redirection to video portal)
    416  * @return state of the request
    417  */
    418 static enum ANASTASIS_AUTHORIZATION_ChallengeResult
    419 sms_challenge (struct ANASTASIS_AUTHORIZATION_State *as,
    420                struct MHD_Connection *connection)
    421 {
    422   enum MHD_Result mres;
    423   const char *mime;
    424   const char *lang;
    425 
    426   mime = MHD_lookup_connection_value (connection,
    427                                       MHD_HEADER_KIND,
    428                                       MHD_HTTP_HEADER_ACCEPT);
    429   if (NULL == mime)
    430     mime = "text/plain";
    431   lang = MHD_lookup_connection_value (connection,
    432                                       MHD_HEADER_KIND,
    433                                       MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
    434   if (NULL == lang)
    435     lang = "en";
    436   if (NULL == as->msg)
    437   {
    438     /* First time, start child process and feed pipe */
    439     struct GNUNET_DISK_PipeHandle *p;
    440     struct GNUNET_DISK_FileHandle *pipe_stdin;
    441 
    442     p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
    443     if (NULL == p)
    444     {
    445       mres = TALER_MHD_reply_with_error (connection,
    446                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    447                                          TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
    448                                          "pipe");
    449       if (MHD_YES != mres)
    450         return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED;
    451       return ANASTASIS_AUTHORIZATION_CRES_FAILED;
    452     }
    453     as->child = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    454     GNUNET_assert (GNUNET_OK ==
    455                    GNUNET_process_set_options (
    456                      as->child,
    457                      GNUNET_process_option_inherit_rpipe (p,
    458                                                           STDIN_FILENO)));
    459     if (GNUNET_OK !=
    460         GNUNET_process_run_command_va (as->child,
    461                                        as->ctx->auth_command,
    462                                        as->ctx->auth_command,
    463                                        as->phone_number,
    464                                        NULL))
    465     {
    466       GNUNET_process_destroy (as->child);
    467       as->child = NULL;
    468       GNUNET_DISK_pipe_close (p);
    469       mres = TALER_MHD_reply_with_error (connection,
    470                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    471                                          TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
    472                                          "exec");
    473       if (MHD_YES != mres)
    474         return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED;
    475       return ANASTASIS_AUTHORIZATION_CRES_FAILED;
    476     }
    477     pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
    478                                               GNUNET_DISK_PIPE_END_WRITE);
    479     GNUNET_assert (NULL != pipe_stdin);
    480     GNUNET_DISK_pipe_close (p);
    481     GNUNET_asprintf (&as->msg,
    482                      "%s\nAnastasis:\n%s",
    483                      ANASTASIS_pin2s (as->code),
    484                      ANASTASIS_CRYPTO_uuid2s (&as->truth_uuid));
    485     {
    486       const char *off = as->msg;
    487       size_t left = strlen (off);
    488 
    489       while (0 != left)
    490       {
    491         ssize_t ret;
    492 
    493         ret = GNUNET_DISK_file_write (pipe_stdin,
    494                                       off,
    495                                       left);
    496         if (ret <= 0)
    497         {
    498           mres = TALER_MHD_reply_with_error (connection,
    499                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    500                                              TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
    501                                              "write");
    502           if (MHD_YES != mres)
    503             return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED;
    504           return ANASTASIS_AUTHORIZATION_CRES_FAILED;
    505         }
    506         as->msg_off += ret;
    507         off += ret;
    508         left -= ret;
    509       }
    510       GNUNET_DISK_file_close (pipe_stdin);
    511     }
    512     as->cwh = GNUNET_wait_child (as->child,
    513                                  &sms_done_cb,
    514                                  as);
    515     as->connection = connection;
    516     MHD_suspend_connection (connection);
    517     return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED;
    518   }
    519   if (NULL != as->cwh)
    520   {
    521     /* Spurious call, why are we here? */
    522     GNUNET_break (0);
    523     MHD_suspend_connection (connection);
    524     return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED;
    525   }
    526   if ( (GNUNET_OS_PROCESS_EXITED != as->pst) ||
    527        (! helper_reported_success (as->exit_code)) )
    528   {
    529     char es[32];
    530     unsigned int http_status;
    531     enum TALER_ErrorCode ec;
    532 
    533     if (GNUNET_OS_PROCESS_EXITED != as->pst)
    534     {
    535       /* Killed by a signal or otherwise abnormal: there is no exit code to
    536          classify. */
    537       http_status = MHD_HTTP_BAD_GATEWAY;
    538       ec = TALER_EC_ANASTASIS_SMS_HELPER_COMMAND_FAILED;
    539     }
    540     else
    541     {
    542       ec = classify_helper_status (as->exit_code,
    543                                    &http_status);
    544     }
    545     GNUNET_snprintf (es,
    546                      sizeof (es),
    547                      "%u/%d",
    548                      (unsigned int) as->exit_code,
    549                      as->pst);
    550     mres = TALER_MHD_reply_with_error (connection,
    551                                        http_status,
    552                                        ec,
    553                                        es);
    554     if (MHD_YES != mres)
    555       return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED;
    556     return ANASTASIS_AUTHORIZATION_CRES_FAILED;
    557   }
    558 
    559   /* Build HTTP response */
    560   {
    561     struct MHD_Response *resp;
    562     const char *end;
    563     size_t slen;
    564 
    565     slen = strlen (as->phone_number);
    566     if (slen > 4)
    567       end = &as->phone_number[slen - 4];
    568     else
    569       end = &as->phone_number[slen / 2];
    570 
    571     if (0.0 < TALER_pattern_matches (mime,
    572                                      "application/json"))
    573     {
    574       resp = TALER_MHD_MAKE_JSON_PACK (
    575         GNUNET_JSON_pack_string ("challenge_type",
    576                                  "TAN_SENT"),
    577         GNUNET_JSON_pack_string ("tan_address_hint",
    578                                  end));
    579     }
    580     else
    581     {
    582       size_t reply_len;
    583       char *reply;
    584 
    585       reply_len = GNUNET_asprintf (&reply,
    586                                    get_message (as->ctx->messages,
    587                                                 connection,
    588                                                 "instructions"),
    589                                    end);
    590       resp = MHD_create_response_from_buffer (reply_len,
    591                                               reply,
    592                                               MHD_RESPMEM_MUST_COPY);
    593       GNUNET_free (reply);
    594       TALER_MHD_add_global_headers (resp,
    595                                     false);
    596       GNUNET_break (MHD_YES ==
    597                     MHD_add_response_header (resp,
    598                                              MHD_HTTP_HEADER_CONTENT_TYPE,
    599                                              "text/plain"));
    600     }
    601     mres = MHD_queue_response (connection,
    602                                MHD_HTTP_OK,
    603                                resp);
    604     MHD_destroy_response (resp);
    605     if (MHD_YES != mres)
    606       return ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED;
    607     return ANASTASIS_AUTHORIZATION_CRES_SUCCESS;
    608   }
    609 }
    610 
    611 
    612 /**
    613  * Free internal state associated with @a as.
    614  *
    615  * @param as state to clean up
    616  */
    617 static void
    618 sms_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
    619 {
    620   if (NULL != as->cwh)
    621   {
    622     GNUNET_wait_child_cancel (as->cwh);
    623     as->cwh = NULL;
    624   }
    625   if (NULL != as->child)
    626   {
    627     GNUNET_break (GNUNET_OK ==
    628                   GNUNET_process_kill (as->child,
    629                                        SIGKILL));
    630     GNUNET_break (GNUNET_OK ==
    631                   GNUNET_process_wait (as->child,
    632                                        true,
    633                                        NULL,
    634                                        NULL));
    635     GNUNET_process_destroy (as->child);
    636     as->child = NULL;
    637   }
    638   GNUNET_free (as->msg);
    639   GNUNET_free (as->phone_number);
    640   GNUNET_free (as);
    641 }
    642 
    643 
    644 /**
    645  * Initialize email based authorization plugin
    646  *
    647  * @param cls a configuration instance
    648  * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
    649  */
    650 void *
    651 libanastasis_plugin_authorization_sms_init (void *cls);
    652 
    653 /* declaration to fix compiler warning */
    654 void *
    655 libanastasis_plugin_authorization_sms_init (void *cls)
    656 {
    657   const struct ANASTASIS_AuthorizationContext *ac = cls;
    658   struct ANASTASIS_AuthorizationPlugin *plugin;
    659   const struct GNUNET_CONFIGURATION_Handle *cfg = ac->cfg;
    660   struct SMS_Context *ctx;
    661 
    662   ctx = GNUNET_new (struct SMS_Context);
    663   ctx->ac = ac;
    664   {
    665     char *fn;
    666     json_error_t err;
    667     char *tmp;
    668 
    669     tmp = GNUNET_OS_installation_get_path (ANASTASIS_project_data (),
    670                                            GNUNET_OS_IPK_DATADIR);
    671     GNUNET_asprintf (&fn,
    672                      "%sauthorization-sms-messages.json",
    673                      tmp);
    674     GNUNET_free (tmp);
    675     ctx->messages = json_load_file (fn,
    676                                     JSON_REJECT_DUPLICATES,
    677                                     &err);
    678     if (NULL == ctx->messages)
    679     {
    680       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    681                   "Failed to load messages from `%s': %s at %d:%d\n",
    682                   fn,
    683                   err.text,
    684                   err.line,
    685                   err.column);
    686       GNUNET_free (fn);
    687       GNUNET_free (ctx);
    688       return NULL;
    689     }
    690     GNUNET_free (fn);
    691   }
    692   {
    693     int regex_result;
    694     const char *regexp = "^\\+?[0-9]+$";
    695 
    696     regex_result = regcomp (&ctx->regex,
    697                             regexp,
    698                             REG_EXTENDED);
    699     if (0 != regex_result)
    700     {
    701       GNUNET_break (0);
    702       json_decref (ctx->messages);
    703       GNUNET_free (ctx);
    704       return NULL;
    705     }
    706   }
    707   plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
    708   plugin->retry_counter = INITIAL_RETRY_COUNTER;
    709   plugin->code_validity_period = GNUNET_TIME_UNIT_DAYS;
    710   plugin->code_rotation_period = GNUNET_TIME_UNIT_HOURS;
    711   plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES;
    712   plugin->cls = ctx;
    713   plugin->validate = &sms_validate;
    714   plugin->start = &sms_start;
    715   plugin->challenge = &sms_challenge;
    716   plugin->cleanup = &sms_cleanup;
    717 
    718   if (GNUNET_OK !=
    719       GNUNET_CONFIGURATION_get_value_string (cfg,
    720                                              "authorization-sms",
    721                                              "COMMAND",
    722                                              &ctx->auth_command))
    723   {
    724     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    725                                "authorization-sms",
    726                                "COMMAND");
    727     regfree (&ctx->regex);
    728     json_decref (ctx->messages);
    729     GNUNET_free (ctx);
    730     GNUNET_free (plugin);
    731     return NULL;
    732   }
    733   return plugin;
    734 }
    735 
    736 
    737 /**
    738  * Unload authorization plugin
    739  *
    740  * @param cls a `struct ANASTASIS_AuthorizationPlugin`
    741  * @return NULL (always)
    742  */
    743 void *
    744 libanastasis_plugin_authorization_sms_done (void *cls);
    745 
    746 /* declaration to fix compiler warning */
    747 void *
    748 libanastasis_plugin_authorization_sms_done (void *cls)
    749 {
    750   struct ANASTASIS_AuthorizationPlugin *plugin = cls;
    751   struct SMS_Context *ctx = plugin->cls;
    752 
    753   GNUNET_free (ctx->auth_command);
    754   regfree (&ctx->regex);
    755   json_decref (ctx->messages);
    756   GNUNET_free (ctx);
    757   GNUNET_free (plugin);
    758   return NULL;
    759 }