anastasis

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

anastasis_authorization_plugin_post.c (26985B)


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