anastasis

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

anastasis_authorization_plugin_email.c (25479B)


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