summaryrefslogtreecommitdiff
path: root/src/authorization
diff options
context:
space:
mode:
Diffstat (limited to 'src/authorization')
-rw-r--r--src/authorization/Makefile.am98
-rw-r--r--src/authorization/anastasis_authorization_plugin.c239
-rw-r--r--src/authorization/anastasis_authorization_plugin_email.c616
-rw-r--r--src/authorization/anastasis_authorization_plugin_file.c302
-rw-r--r--src/authorization/anastasis_authorization_plugin_post.c655
-rw-r--r--src/authorization/anastasis_authorization_plugin_sms.c607
-rw-r--r--src/authorization/authorization-email-messages.json10
-rw-r--r--src/authorization/authorization-post-messages.json7
-rw-r--r--src/authorization/authorization-sms-messages.json6
9 files changed, 2540 insertions, 0 deletions
diff --git a/src/authorization/Makefile.am b/src/authorization/Makefile.am
new file mode 100644
index 0000000..8ea7e86
--- /dev/null
+++ b/src/authorization/Makefile.am
@@ -0,0 +1,98 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+pkgcfgdir = $(prefix)/share/anastasis/config.d/
+plugindir = $(libdir)/anastasis
+pkgdatadir= $(prefix)/share/anastasis/
+
+pkgdata_DATA = \
+ authorization-email-messages.json \
+ authorization-post-messages.json \
+ authorization-sms-messages.json
+
+EXTRA_DIST = $(pkgdata_DATA)
+
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libanastasisauthorization.la
+
+libanastasisauthorization_la_SOURCES = \
+ anastasis_authorization_plugin.c
+libanastasisauthorization_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasisauthorization_la_LDFLAGS = \
+ -ltalerutil \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -lltdl \
+ $(XLIB)
+
+plugin_LTLIBRARIES = \
+ libanastasis_plugin_authorization_email.la \
+ libanastasis_plugin_authorization_file.la \
+ libanastasis_plugin_authorization_post.la \
+ libanastasis_plugin_authorization_sms.la
+libanastasis_plugin_authorization_file_la_SOURCES = \
+ anastasis_authorization_plugin_file.c
+libanastasis_plugin_authorization_file_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_authorization_file_la_LDFLAGS = \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -ltalerjson \
+ -ltalermhd \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ $(XLIB)
+
+libanastasis_plugin_authorization_email_la_SOURCES = \
+ anastasis_authorization_plugin_email.c
+libanastasis_plugin_authorization_email_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_authorization_email_la_LDFLAGS = \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -ltalerjson \
+ -ltalermhd \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ $(XLIB)
+
+libanastasis_plugin_authorization_post_la_SOURCES = \
+ anastasis_authorization_plugin_post.c
+libanastasis_plugin_authorization_post_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_authorization_post_la_LDFLAGS = \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -ltalerjson \
+ -ltalermhd \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ $(XLIB)
+
+libanastasis_plugin_authorization_sms_la_SOURCES = \
+ anastasis_authorization_plugin_sms.c
+libanastasis_plugin_authorization_sms_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_authorization_sms_la_LDFLAGS = \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -ltalerjson \
+ -ltalermhd \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -lmicrohttpd \
+ $(XLIB)
diff --git a/src/authorization/anastasis_authorization_plugin.c b/src/authorization/anastasis_authorization_plugin.c
new file mode 100644
index 0000000..7874594
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin.c
@@ -0,0 +1,239 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015, 2016, 2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis_authorization_plugin.c
+ * @brief Logic to load database plugin
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <ltdl.h>
+
+
+/**
+ * Head of linked list for all loaded plugins
+ */
+static struct AuthPlugin *ap_head;
+
+/**
+ * Tail ofinked list for all loaded plugins
+ */
+static struct AuthPlugin *ap_tail;
+
+
+/**
+ * Authentication plugin which is used to verify code based authentication
+ * like SMS, E-Mail.
+ */
+struct AuthPlugin
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct AuthPlugin *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AuthPlugin *prev;
+
+ /**
+ * Actual plugin handle.
+ */
+ struct ANASTASIS_AuthorizationPlugin *authorization;
+
+ /**
+ * I.e. "sms", "phone".
+ */
+ char *name;
+
+ /**
+ * Name of the shared object providing the plugin logic.
+ */
+ char *lib_name;
+
+ /**
+ * Cost of using this plugin.
+ */
+ struct TALER_Amount cost;
+};
+
+
+struct ANASTASIS_AuthorizationPlugin *
+ANASTASIS_authorization_plugin_load (
+ const char *method,
+ const struct GNUNET_CONFIGURATION_Handle *AH_cfg,
+ struct TALER_Amount *cost)
+{
+ struct ANASTASIS_AuthorizationPlugin *authorization;
+ char *lib_name;
+ char *sec_name;
+ struct AuthPlugin *ap;
+ char *currency;
+
+ for (ap = ap_head; NULL != ap; ap = ap->next)
+ if (0 == strcmp (method,
+ ap->name))
+ {
+ *cost = ap->cost;
+ return ap->authorization;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_currency (AH_cfg,
+ &currency))
+ return NULL;
+ ap = GNUNET_new (struct AuthPlugin);
+ GNUNET_asprintf (&sec_name,
+ "authorization-%s",
+ method);
+ if (GNUNET_OK !=
+ TALER_config_get_amount (AH_cfg,
+ sec_name,
+ "COST",
+ &ap->cost))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ sec_name,
+ "COST");
+ GNUNET_free (sec_name);
+ GNUNET_free (currency);
+ GNUNET_free (ap);
+ return NULL;
+ }
+
+ if (0 !=
+ strcasecmp (currency,
+ ap->cost.currency))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ sec_name,
+ "COST",
+ "currency mismatch");
+ GNUNET_free (currency);
+ GNUNET_free (sec_name);
+ GNUNET_free (ap);
+ return NULL;
+ }
+ GNUNET_free (currency);
+ GNUNET_free (sec_name);
+ GNUNET_asprintf (&lib_name,
+ "libanastasis_plugin_authorization_%s",
+ method);
+ authorization = GNUNET_PLUGIN_load (lib_name,
+ (void *) AH_cfg);
+ if (NULL == authorization)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Authentication method `%s' not supported\n",
+ method);
+ GNUNET_free (lib_name);
+ GNUNET_free (ap);
+ return NULL;
+ }
+ ap->name = GNUNET_strdup (method);
+ ap->lib_name = lib_name;
+ ap->authorization = authorization;
+ GNUNET_CONTAINER_DLL_insert (ap_head,
+ ap_tail,
+ ap);
+ *cost = ap->cost;
+ return authorization;
+}
+
+
+void
+ANASTASIS_authorization_plugin_shutdown (void)
+{
+ struct AuthPlugin *ap;
+
+ while (NULL != (ap = ap_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (ap_head,
+ ap_tail,
+ ap);
+ GNUNET_PLUGIN_unload (ap->lib_name,
+ ap->authorization);
+ GNUNET_free (ap->lib_name);
+ GNUNET_free (ap->name);
+ GNUNET_free (ap);
+ }
+}
+
+
+/**
+ * Libtool search path before we started.
+ */
+static char *old_dlsearchpath;
+
+
+/**
+ * Setup libtool paths.
+ */
+void __attribute__ ((constructor))
+anastasis_authorization_plugin_init (void)
+{
+ int err;
+ const char *opath;
+ char *path;
+ char *cpath;
+
+ err = lt_dlinit ();
+ if (err > 0)
+ {
+ fprintf (stderr,
+ _ ("Initialization of plugin mechanism failed: %s!\n"),
+ lt_dlerror ());
+ return;
+ }
+ opath = lt_dlgetsearchpath ();
+ if (NULL != opath)
+ old_dlsearchpath = GNUNET_strdup (opath);
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
+ if (NULL != path)
+ {
+ if (NULL != opath)
+ {
+ GNUNET_asprintf (&cpath, "%s:%s", opath, path);
+ lt_dlsetsearchpath (cpath);
+ GNUNET_free (path);
+ GNUNET_free (cpath);
+ }
+ else
+ {
+ lt_dlsetsearchpath (path);
+ GNUNET_free (path);
+ }
+ }
+}
+
+
+/**
+ * Shutdown libtool.
+ */
+void __attribute__ ((destructor))
+anastasis_authorization_plugin_fini (void)
+{
+ lt_dlsetsearchpath (old_dlsearchpath);
+ if (NULL != old_dlsearchpath)
+ {
+ GNUNET_free (old_dlsearchpath);
+ }
+ lt_dlexit ();
+}
+
+
+/* end of anastasis_authorization_plugin.c */
diff --git a/src/authorization/anastasis_authorization_plugin_email.c b/src/authorization/anastasis_authorization_plugin_email.c
new file mode 100644
index 0000000..33c400b
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_email.c
@@ -0,0 +1,616 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019-2021 Anastasis SARL
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin_email.c
+ * @brief authorization plugin email based
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <taler/taler_mhd_lib.h>
+#include <taler/taler_json_lib.h>
+#include <regex.h>
+#include "anastasis_util_lib.h"
+
+
+/**
+ * Saves the State of a authorization plugin.
+ */
+struct Email_Context
+{
+
+ /**
+ * Command which is executed to run the plugin (some bash script or a
+ * command line argument)
+ */
+ char *auth_command;
+
+ /**
+ * Regex for email address validation.
+ */
+ regex_t regex;
+
+ /**
+ * Messages of the plugin, read from a resource file.
+ */
+ json_t *messages;
+
+};
+
+
+/**
+ * Saves the state of a authorization process
+ */
+struct ANASTASIS_AUTHORIZATION_State
+{
+ /**
+ * Public key of the challenge which is authorised
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Code which is sent to the user.
+ */
+ uint64_t code;
+
+ /**
+ * Our plugin context.
+ */
+ struct Email_Context *ctx;
+
+ /**
+ * Function to call when we made progress.
+ */
+ GNUNET_SCHEDULER_TaskCallback trigger;
+
+ /**
+ * Closure for @e trigger.
+ */
+ void *trigger_cls;
+
+ /**
+ * holds the truth information
+ */
+ char *email;
+
+ /**
+ * Handle to the helper process.
+ */
+ struct GNUNET_OS_Process *child;
+
+ /**
+ * Handle to wait for @e child
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Our client connection, set if suspended.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Message to send.
+ */
+ char *msg;
+
+ /**
+ * Offset of transmission in msg.
+ */
+ size_t msg_off;
+
+ /**
+ * Exit code from helper.
+ */
+ long unsigned int exit_code;
+
+ /**
+ * How did the helper die?
+ */
+ enum GNUNET_OS_ProcessStatusType pst;
+
+};
+
+
+/**
+ * Obtain internationalized message @a msg_id from @a ctx using
+ * language preferences of @a conn.
+ *
+ * @param messages JSON object to lookup message from
+ * @param conn connection to lookup message for
+ * @param msg_id unique message ID
+ * @return NULL if message was not found
+ */
+static const char *
+get_message (const json_t *messages,
+ struct MHD_Connection *conn,
+ const char *msg_id)
+{
+ const char *accept_lang;
+
+ accept_lang = MHD_lookup_connection_value (conn,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == accept_lang)
+ accept_lang = "en_US";
+ {
+ const char *ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_i18n_string (msg_id,
+ accept_lang,
+ &ret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (messages,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+static enum GNUNET_GenericReturnValue
+email_validate (void *cls,
+ struct MHD_Connection *connection,
+ const char *mime_type,
+ const char *data,
+ size_t data_length)
+{
+ struct Email_Context *ctx = cls;
+ int regex_result;
+ char *phone_number;
+
+ phone_number = GNUNET_strndup (data,
+ data_length);
+ regex_result = regexec (&ctx->regex,
+ phone_number,
+ 0,
+ NULL,
+ 0);
+ GNUNET_free (phone_number);
+ if (0 != regex_result)
+ {
+ if (MHD_NO ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_EMAIL_INVALID,
+ NULL))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param cls closure
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+static struct ANASTASIS_AUTHORIZATION_State *
+email_start (void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code,
+ const void *data,
+ size_t data_length)
+{
+ struct Email_Context *ctx = cls;
+ struct ANASTASIS_AUTHORIZATION_State *as;
+
+ as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
+ as->trigger = trigger;
+ as->trigger_cls = trigger_cls;
+ as->ctx = ctx;
+ as->truth_uuid = *truth_uuid;
+ as->code = code;
+ as->email = GNUNET_strndup (data,
+ data_length);
+ return as;
+}
+
+
+/**
+ * Function called when our Email helper has terminated.
+ *
+ * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+email_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct ANASTASIS_AUTHORIZATION_State *as = cls;
+
+ as->child = NULL;
+ as->cwh = NULL;
+ as->pst = type;
+ as->exit_code = exit_code;
+ MHD_resume_connection (as->connection);
+ as->trigger (as->trigger_cls);
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+static enum ANASTASIS_AUTHORIZATION_Result
+email_process (struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection)
+{
+ MHD_RESULT mres;
+ const char *mime;
+ const char *lang;
+
+ mime = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "text/plain";
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ if (NULL == as->msg)
+ {
+ /* First time, start child process and feed pipe */
+ struct GNUNET_DISK_PipeHandle *p;
+ struct GNUNET_DISK_FileHandle *pipe_stdin;
+
+ p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ if (NULL == p)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED,
+ "pipe");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ p,
+ NULL,
+ NULL,
+ as->ctx->auth_command,
+ as->ctx->auth_command,
+ as->email,
+ NULL);
+ if (NULL == as->child)
+ {
+ GNUNET_DISK_pipe_close (p);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED,
+ "exec");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
+ GNUNET_DISK_PIPE_END_WRITE);
+ GNUNET_assert (NULL != pipe_stdin);
+ GNUNET_DISK_pipe_close (p);
+ {
+ char *tpk;
+
+ tpk = GNUNET_STRINGS_data_to_string_alloc (
+ &as->truth_uuid,
+ sizeof (as->truth_uuid));
+ GNUNET_asprintf (&as->msg,
+ get_message (as->ctx->messages,
+ connection,
+ "body"),
+ (unsigned long long) as->code,
+ tpk);
+ GNUNET_free (tpk);
+ }
+
+ {
+ const char *off = as->msg;
+ size_t left = strlen (off);
+
+ while (0 != left)
+ {
+ ssize_t ret;
+
+ if (0 == left)
+ break;
+ ret = GNUNET_DISK_file_write (pipe_stdin,
+ off,
+ left);
+ if (ret <= 0)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED,
+ "write");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->msg_off += ret;
+ off += ret;
+ left -= ret;
+ }
+ GNUNET_DISK_file_close (pipe_stdin);
+ }
+ as->cwh = GNUNET_wait_child (as->child,
+ &email_done_cb,
+ as);
+ as->connection = connection;
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if (NULL != as->cwh)
+ {
+ /* Spurious call, why are we here? */
+ GNUNET_break (0);
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if ( (GNUNET_OS_PROCESS_EXITED != as->pst) ||
+ (0 != as->exit_code) )
+ {
+ char es[32];
+
+ GNUNET_snprintf (es,
+ sizeof (es),
+ "%u/%d",
+ (unsigned int) as->exit_code,
+ as->pst);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_EMAIL_HELPER_COMMAND_FAILED,
+ es);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+
+ /* Build HTTP response */
+ {
+ struct MHD_Response *resp;
+ const char *at;
+ size_t len;
+
+ at = strchr (as->email, '@');
+ if (NULL == at)
+ len = 0;
+ else
+ len = at - as->email;
+
+ if (TALER_MHD_xmime_matches (mime,
+ "application/json"))
+ {
+ json_t *body;
+ char *user;
+
+ user = GNUNET_strndup (as->email,
+ len);
+ body = json_pack ("{s:I, s:s, s:s}",
+ "code",
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
+ "hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
+ "detail",
+ user);
+ GNUNET_free (user);
+ GNUNET_break (NULL != body);
+ resp = TALER_MHD_make_json (body);
+ }
+ else
+ {
+ size_t reply_len;
+ char *reply;
+
+ reply_len = GNUNET_asprintf (&reply,
+ get_message (as->ctx->messages,
+ connection,
+ "instructions"),
+ (unsigned int) len,
+ as->email);
+ resp = MHD_create_response_from_buffer (reply_len,
+ reply,
+ MHD_RESPMEM_MUST_COPY);
+ GNUNET_free (reply);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ }
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
+ }
+}
+
+
+/**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+static void
+email_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
+{
+ if (NULL != as->cwh)
+ {
+ GNUNET_wait_child_cancel (as->cwh);
+ as->cwh = NULL;
+ }
+ if (NULL != as->child)
+ {
+ (void) GNUNET_OS_process_kill (as->child,
+ SIGKILL);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (as->child));
+ as->child = NULL;
+ }
+ GNUNET_free (as->msg);
+ GNUNET_free (as->email);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Initialize email based authorization plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
+ */
+void *
+libanastasis_plugin_authorization_email_init (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct Email_Context *ctx;
+
+ ctx = GNUNET_new (struct Email_Context);
+ {
+ char *fn;
+ json_error_t err;
+
+ GNUNET_asprintf (&fn,
+ "%sauthorization-email-messages.json",
+ GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR));
+ ctx->messages = json_load_file (fn,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == ctx->messages)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load messages from `%s': %s at %d:%d\n",
+ fn,
+ err.text,
+ err.line,
+ err.column);
+ GNUNET_free (fn);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ GNUNET_free (fn);
+ }
+ {
+ int regex_result;
+ const char *regexp = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}";
+
+ regex_result = regcomp (&ctx->regex,
+ regexp,
+ REG_EXTENDED);
+ if (0 < regex_result)
+ {
+ GNUNET_break (0);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ }
+
+ plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
+ plugin->code_validity_period = GNUNET_TIME_UNIT_DAYS;
+ plugin->code_rotation_period = GNUNET_TIME_UNIT_HOURS;
+ plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES;
+ plugin->cls = ctx;
+ plugin->validate = &email_validate;
+ plugin->start = &email_start;
+ plugin->process = &email_process;
+ plugin->cleanup = &email_cleanup;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "authorization-email",
+ "COMMAND",
+ &ctx->auth_command))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "authorization-email",
+ "COMMAND");
+ regfree (&ctx->regex);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+ }
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct ANASTASIS_AuthorizationPlugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_authorization_email_done (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin = cls;
+ struct Email_Context *ctx = plugin->cls;
+
+ GNUNET_free (ctx->auth_command);
+ regfree (&ctx->regex);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/authorization/anastasis_authorization_plugin_file.c b/src/authorization/anastasis_authorization_plugin_file.c
new file mode 100644
index 0000000..210ade7
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_file.c
@@ -0,0 +1,302 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin_file.c
+ * @brief authorization plugin file based for testing
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <taler/taler_mhd_lib.h>
+
+
+/**
+ * Saves the state of a authorization process
+ */
+struct ANASTASIS_AUTHORIZATION_State
+{
+ /**
+ * UUID of the challenge which is authorised
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Code which is sent to the user (here saved into a file)
+ */
+ uint64_t code;
+
+ /**
+ * holds the truth information
+ */
+ char *filename;
+
+ /**
+ * closure
+ */
+ void *cls;
+};
+
+
+/**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+static enum GNUNET_GenericReturnValue
+file_validate (void *cls,
+ struct MHD_Connection *connection,
+ const char *truth_mime,
+ const char *data,
+ size_t data_length)
+{
+ char *filename;
+ bool flag;
+
+ if (NULL == data)
+ return GNUNET_NO;
+ filename = GNUNET_STRINGS_data_to_string_alloc (data,
+ data_length);
+ flag = false;
+ for (size_t i = 0; i<strlen (filename); i++)
+ {
+ if ( (filename[i] == ' ') ||
+ (filename[i] == '/') )
+ {
+ flag = true;
+ break;
+ }
+ }
+ if (flag)
+ return GNUNET_NO;
+ GNUNET_free (filename);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param cls closure
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+static struct ANASTASIS_AUTHORIZATION_State *
+file_start (void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code,
+ const void *data,
+ size_t data_length)
+{
+ struct ANASTASIS_AUTHORIZATION_State *as;
+
+ as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
+ as->cls = cls;
+ as->truth_uuid = *truth_uuid;
+ as->code = code;
+ as->filename = GNUNET_strndup (data,
+ data_length);
+ return as;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+static enum ANASTASIS_AUTHORIZATION_Result
+file_process (struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection)
+{
+ const char *mime;
+ const char *lang;
+
+ mime = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "text/plain";
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ {
+ FILE *f = fopen (as->filename, "w");
+
+ if (NULL == f)
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT mres;
+
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ as->filename);
+ resp = TALER_MHD_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "open");
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+
+ /* print challenge code to file */
+ if (0 >= fprintf (f,
+ "%lu",
+ as->code))
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT mres;
+
+ GNUNET_break (0 == fclose (f));
+ resp = TALER_MHD_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "write");
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ GNUNET_break (0 == fclose (f));
+ }
+
+ /* Build HTTP response */
+ {
+ struct MHD_Response *resp;
+
+ if (TALER_MHD_xmime_matches (mime,
+ "application/json"))
+ {
+ json_t *body;
+
+ body = json_pack ("{s:s}",
+ "filename",
+ as->filename);
+ GNUNET_break (NULL != body);
+ resp = TALER_MHD_make_json (body);
+ }
+ else
+ {
+ size_t response_size;
+ char *response;
+
+ response_size = GNUNET_asprintf (&response,
+ _ ("Challenge written to file"));
+ resp = MHD_create_response_from_buffer (response_size,
+ response,
+ MHD_RESPMEM_MUST_COPY);
+ GNUNET_free (response);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ }
+
+ {
+ MHD_RESULT mres;
+
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
+ }
+ }
+}
+
+
+/**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+static void
+file_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
+{
+ GNUNET_free (as->filename);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Initialize File based authorization plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
+ */
+void *
+libanastasis_plugin_authorization_file_init (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin;
+
+ plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
+ plugin->code_validity_period = GNUNET_TIME_UNIT_MINUTES;
+ plugin->code_rotation_period = GNUNET_TIME_UNIT_MINUTES;
+ plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES;
+ plugin->validate = &file_validate;
+ plugin->start = &file_start;
+ plugin->process = &file_process;
+ plugin->cleanup = &file_cleanup;
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct ANASTASIS_AuthorizationPlugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_authorization_file_done (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin = cls;
+
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/authorization/anastasis_authorization_plugin_post.c b/src/authorization/anastasis_authorization_plugin_post.c
new file mode 100644
index 0000000..1f20ff3
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_post.c
@@ -0,0 +1,655 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin_post.c
+ * @brief authorization plugin post based
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <taler/taler_mhd_lib.h>
+#include <taler/taler_json_lib.h>
+#include <jansson.h>
+#include "anastasis_util_lib.h"
+
+
+/**
+ * Saves the State of a authorization plugin.
+ */
+struct PostContext
+{
+
+ /**
+ * Command which is executed to run the plugin (some bash script or a
+ * command line argument)
+ */
+ char *auth_command;
+
+ /**
+ * Messages of the plugin, read from a resource file.
+ */
+ json_t *messages;
+};
+
+
+/**
+ * Saves the state of a authorization process
+ */
+struct ANASTASIS_AUTHORIZATION_State
+{
+ /**
+ * Public key of the challenge which is authorised
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Code which is sent to the user.
+ */
+ uint64_t code;
+
+ /**
+ * Our plugin context.
+ */
+ struct PostContext *ctx;
+
+ /**
+ * Function to call when we made progress.
+ */
+ GNUNET_SCHEDULER_TaskCallback trigger;
+
+ /**
+ * Closure for @e trigger.
+ */
+ void *trigger_cls;
+
+ /**
+ * holds the truth information
+ */
+ json_t *post;
+
+ /**
+ * Handle to the helper process.
+ */
+ struct GNUNET_OS_Process *child;
+
+ /**
+ * Handle to wait for @e child
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Our client connection, set if suspended.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Message to send.
+ */
+ char *msg;
+
+ /**
+ * Offset of transmission in msg.
+ */
+ size_t msg_off;
+
+ /**
+ * Exit code from helper.
+ */
+ long unsigned int exit_code;
+
+ /**
+ * How did the helper die?
+ */
+ enum GNUNET_OS_ProcessStatusType pst;
+
+
+};
+
+
+/**
+ * Obtain internationalized message @a msg_id from @a ctx using
+ * language preferences of @a conn.
+ *
+ * @param messages JSON object to lookup message from
+ * @param conn connection to lookup message for
+ * @param msg_id unique message ID
+ * @return NULL if message was not found
+ */
+static const char *
+get_message (const json_t *messages,
+ struct MHD_Connection *conn,
+ const char *msg_id)
+{
+ const char *accept_lang;
+
+ accept_lang = MHD_lookup_connection_value (conn,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == accept_lang)
+ accept_lang = "en_US";
+ {
+ const char *ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_i18n_string (msg_id,
+ accept_lang,
+ &ret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (messages,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+static enum GNUNET_GenericReturnValue
+post_validate (void *cls,
+ struct MHD_Connection *connection,
+ const char *mime_type,
+ const char *data,
+ size_t data_length)
+{
+ struct PostContext *ctx = cls;
+ json_t *j;
+ json_error_t error;
+ const char *name;
+ const char *street;
+ const char *city;
+ const char *zip;
+ const char *country;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("full_name",
+ &name),
+ GNUNET_JSON_spec_string ("street",
+ &street),
+ GNUNET_JSON_spec_string ("city",
+ &city),
+ GNUNET_JSON_spec_string ("postcode",
+ &zip),
+ GNUNET_JSON_spec_string ("country",
+ &country),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) ctx;
+ j = json_loadb (data,
+ data_length,
+ JSON_REJECT_DUPLICATES,
+ &error);
+ if (NULL == j)
+ {
+ if (MHD_NO ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_POST_INVALID,
+ "JSON malformed"))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (j);
+ if (MHD_NO ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_POST_INVALID,
+ "JSON lacked required address information"))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ json_decref (j);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send mail.
+ *
+ * @param cls closure
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+static struct ANASTASIS_AUTHORIZATION_State *
+post_start (void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code,
+ const void *data,
+ size_t data_length)
+{
+ struct PostContext *ctx = cls;
+ struct ANASTASIS_AUTHORIZATION_State *as;
+ json_error_t error;
+
+ as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
+ as->trigger = trigger;
+ as->trigger_cls = trigger_cls;
+ as->ctx = ctx;
+ as->truth_uuid = *truth_uuid;
+ as->code = code;
+ as->post = json_loadb (data,
+ data_length,
+ JSON_REJECT_DUPLICATES,
+ &error);
+ if (NULL == as->post)
+ {
+ GNUNET_break (0);
+ GNUNET_free (as);
+ return NULL;
+ }
+ return as;
+}
+
+
+/**
+ * Function called when our Post helper has terminated.
+ *
+ * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+post_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct ANASTASIS_AUTHORIZATION_State *as = cls;
+
+ as->child = NULL;
+ as->cwh = NULL;
+ as->pst = type;
+ as->exit_code = exit_code;
+ MHD_resume_connection (as->connection);
+ as->trigger (as->trigger_cls);
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+static enum ANASTASIS_AUTHORIZATION_Result
+post_process (struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection)
+{
+ const char *mime;
+ const char *lang;
+ MHD_RESULT mres;
+ const char *name;
+ const char *street;
+ const char *city;
+ const char *zip;
+ const char *country;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("full_name",
+ &name),
+ GNUNET_JSON_spec_string ("street",
+ &street),
+ GNUNET_JSON_spec_string ("city",
+ &city),
+ GNUNET_JSON_spec_string ("postcode",
+ &zip),
+ GNUNET_JSON_spec_string ("country",
+ &country),
+ GNUNET_JSON_spec_end ()
+ };
+
+ mime = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "text/plain";
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (as->post,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_INVALID,
+ "address information incomplete");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ if (NULL == as->msg)
+ {
+ /* First time, start child process and feed pipe */
+ struct GNUNET_DISK_PipeHandle *p;
+ struct GNUNET_DISK_FileHandle *pipe_stdin;
+
+ p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ if (NULL == p)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
+ "pipe");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ p,
+ NULL,
+ NULL,
+ as->ctx->auth_command,
+ as->ctx->auth_command,
+ name,
+ street,
+ city,
+ zip,
+ country,
+ NULL);
+ if (NULL == as->child)
+ {
+ GNUNET_DISK_pipe_close (p);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
+ "exec");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
+ GNUNET_DISK_PIPE_END_WRITE);
+ GNUNET_assert (NULL != pipe_stdin);
+ GNUNET_DISK_pipe_close (p);
+ {
+ char *tpk;
+
+ tpk = GNUNET_STRINGS_data_to_string_alloc (
+ &as->truth_uuid,
+ sizeof (as->truth_uuid));
+ GNUNET_asprintf (&as->msg,
+ get_message (as->ctx->messages,
+ connection,
+ "body"),
+ (unsigned long long) as->code,
+ tpk);
+ GNUNET_free (tpk);
+ }
+
+ {
+ const char *off = as->msg;
+ size_t left = strlen (off);
+
+ while (0 != left)
+ {
+ ssize_t ret;
+
+ if (0 == left)
+ break;
+ ret = GNUNET_DISK_file_write (pipe_stdin,
+ off,
+ left);
+ if (ret <= 0)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
+ "write");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->msg_off += ret;
+ off += ret;
+ left -= ret;
+ }
+ GNUNET_DISK_file_close (pipe_stdin);
+ }
+ as->cwh = GNUNET_wait_child (as->child,
+ &post_done_cb,
+ as);
+ as->connection = connection;
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if (NULL != as->cwh)
+ {
+ /* Spurious call, why are we here? */
+ GNUNET_break (0);
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if ( (GNUNET_OS_PROCESS_EXITED != as->pst) ||
+ (0 != as->exit_code) )
+ {
+ char es[32];
+
+ GNUNET_snprintf (es,
+ sizeof (es),
+ "%u/%d",
+ (unsigned int) as->exit_code,
+ as->pst);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_HELPER_COMMAND_FAILED,
+ es);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+
+ /* Build HTTP response */
+ {
+ struct MHD_Response *resp;
+
+ if (TALER_MHD_xmime_matches (mime,
+ "application/json"))
+ {
+ json_t *body;
+
+ body = json_pack ("{s:I, s:s, s:s}",
+ "code",
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
+ "hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
+ "detail",
+ zip);
+ GNUNET_break (NULL != body);
+ resp = TALER_MHD_make_json (body);
+ }
+ else
+ {
+ size_t reply_len;
+ char *reply;
+
+ reply_len = GNUNET_asprintf (&reply,
+ get_message (as->ctx->messages,
+ connection,
+ "instructions"),
+ zip);
+ resp = MHD_create_response_from_buffer (reply_len,
+ reply,
+ MHD_RESPMEM_MUST_COPY);
+ GNUNET_free (reply);
+ TALER_MHD_add_global_headers (resp);
+ }
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
+ }
+}
+
+
+/**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+static void
+post_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
+{
+ if (NULL != as->cwh)
+ {
+ GNUNET_wait_child_cancel (as->cwh);
+ as->cwh = NULL;
+ }
+ if (NULL != as->child)
+ {
+ (void) GNUNET_OS_process_kill (as->child,
+ SIGKILL);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (as->child));
+ as->child = NULL;
+ }
+ GNUNET_free (as->msg);
+ json_decref (as->post);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Initialize post based authorization plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
+ */
+void *
+libanastasis_plugin_authorization_post_init (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct PostContext *ctx;
+
+ ctx = GNUNET_new (struct PostContext);
+ {
+ char *fn;
+ json_error_t err;
+
+ GNUNET_asprintf (&fn,
+ "%sauthorization-post-messages.json",
+ GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR));
+ ctx->messages = json_load_file (fn,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == ctx->messages)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load messages from `%s': %s at %d:%d\n",
+ fn,
+ err.text,
+ err.line,
+ err.column);
+ GNUNET_free (fn);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ GNUNET_free (fn);
+ }
+ plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
+ plugin->code_validity_period = GNUNET_TIME_UNIT_MONTHS;
+ plugin->code_rotation_period = GNUNET_TIME_UNIT_WEEKS;
+ plugin->code_retransmission_frequency
+ = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_DAYS,
+ 2);
+ plugin->cls = ctx;
+ plugin->validate = &post_validate;
+ plugin->start = &post_start;
+ plugin->process = &post_process;
+ plugin->cleanup = &post_cleanup;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "authorization-post",
+ "COMMAND",
+ &ctx->auth_command))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "authorization-post",
+ "COMMAND");
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+ }
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct ANASTASIS_AuthorizationPlugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_authorization_post_done (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin = cls;
+ struct PostContext *ctx = plugin->cls;
+
+ GNUNET_free (ctx->auth_command);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/authorization/anastasis_authorization_plugin_sms.c b/src/authorization/anastasis_authorization_plugin_sms.c
new file mode 100644
index 0000000..01b5f73
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_sms.c
@@ -0,0 +1,607 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin_email.c
+ * @brief authorization plugin email based
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <taler/taler_mhd_lib.h>
+#include <taler/taler_json_lib.h>
+#include <regex.h>
+#include "anastasis_util_lib.h"
+
+
+/**
+ * Saves the State of a authorization plugin.
+ */
+struct SMS_Context
+{
+
+ /**
+ * Command which is executed to run the plugin (some bash script or a
+ * command line argument)
+ */
+ char *auth_command;
+
+ /**
+ * Regex for phone number validation.
+ */
+ regex_t regex;
+
+ /**
+ * Messages of the plugin, read from a resource file.
+ */
+ json_t *messages;
+};
+
+
+/**
+ * Saves the State of a authorization process
+ */
+struct ANASTASIS_AUTHORIZATION_State
+{
+ /**
+ * Public key of the challenge which is authorised
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Code which is sent to the user (here sent via SMS)
+ */
+ uint64_t code;
+
+ /**
+ * Our plugin context.
+ */
+ struct SMS_Context *ctx;
+
+ /**
+ * Function to call when we made progress.
+ */
+ GNUNET_SCHEDULER_TaskCallback trigger;
+
+ /**
+ * Closure for @e trigger.
+ */
+ void *trigger_cls;
+
+ /**
+ * holds the truth information
+ */
+ char *phone_number;
+
+ /**
+ * Handle to the helper process.
+ */
+ struct GNUNET_OS_Process *child;
+
+ /**
+ * Handle to wait for @e child
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Our client connection, set if suspended.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Message to send.
+ */
+ char *msg;
+
+ /**
+ * Offset of transmission in msg.
+ */
+ size_t msg_off;
+
+ /**
+ * Exit code from helper.
+ */
+ long unsigned int exit_code;
+
+ /**
+ * How did the helper die?
+ */
+ enum GNUNET_OS_ProcessStatusType pst;
+
+};
+
+
+/**
+ * Obtain internationalized message @a msg_id from @a ctx using
+ * language preferences of @a conn.
+ *
+ * @param messages JSON object to lookup message from
+ * @param conn connection to lookup message for
+ * @param msg_id unique message ID
+ * @return NULL if message was not found
+ */
+static const char *
+get_message (const json_t *messages,
+ struct MHD_Connection *conn,
+ const char *msg_id)
+{
+ const char *accept_lang;
+
+ accept_lang = MHD_lookup_connection_value (conn,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == accept_lang)
+ accept_lang = "en_US";
+ {
+ const char *ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_i18n_string (msg_id,
+ accept_lang,
+ &ret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (messages,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure with a `struct SMS_Context`
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+static enum GNUNET_GenericReturnValue
+sms_validate (void *cls,
+ struct MHD_Connection *connection,
+ const char *truth_mime,
+ const char *data,
+ size_t data_length)
+{
+ struct SMS_Context *ctx = cls;
+ int regex_result;
+ char *phone_number;
+
+ phone_number = GNUNET_strndup (data,
+ data_length);
+ regex_result = regexec (&ctx->regex,
+ phone_number,
+ 0,
+ NULL,
+ 0);
+ GNUNET_free (phone_number);
+ if (0 != regex_result)
+ {
+ if (MHD_NO ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_SMS_PHONE_INVALID,
+ NULL))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * Sends SMS.
+ *
+ * @param cls closure with a `struct SMS_Context`
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+static struct ANASTASIS_AUTHORIZATION_State *
+sms_start (void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code,
+ const void *data,
+ size_t data_length)
+{
+ struct SMS_Context *ctx = cls;
+ struct ANASTASIS_AUTHORIZATION_State *as;
+
+ as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
+ as->trigger = trigger;
+ as->trigger_cls = trigger_cls;
+ as->ctx = ctx;
+ as->truth_uuid = *truth_uuid;
+ as->code = code;
+ as->phone_number = GNUNET_strndup (data,
+ data_length);
+ return as;
+}
+
+
+/**
+ * Function called when our SMS helper has terminated.
+ *
+ * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+sms_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct ANASTASIS_AUTHORIZATION_State *as = cls;
+
+ as->child = NULL;
+ as->cwh = NULL;
+ as->pst = type;
+ as->exit_code = exit_code;
+ MHD_resume_connection (as->connection);
+ as->trigger (as->trigger_cls);
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+static enum ANASTASIS_AUTHORIZATION_Result
+sms_process (struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection)
+{
+ MHD_RESULT mres;
+ const char *mime;
+ const char *lang;
+
+ mime = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "text/plain";
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ if (NULL == as->msg)
+ {
+ /* First time, start child process and feed pipe */
+ struct GNUNET_DISK_PipeHandle *p;
+ struct GNUNET_DISK_FileHandle *pipe_stdin;
+
+ p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ if (NULL == p)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
+ "pipe");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ p,
+ NULL,
+ NULL,
+ as->ctx->auth_command,
+ as->ctx->auth_command,
+ as->phone_number,
+ NULL);
+ if (NULL == as->child)
+ {
+ GNUNET_DISK_pipe_close (p);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
+ "exec");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
+ GNUNET_DISK_PIPE_END_WRITE);
+ GNUNET_assert (NULL != pipe_stdin);
+ GNUNET_DISK_pipe_close (p);
+ {
+ char *tpk;
+
+ tpk = GNUNET_STRINGS_data_to_string_alloc (
+ &as->truth_uuid,
+ sizeof (as->truth_uuid));
+ GNUNET_asprintf (&as->msg,
+ "A-%llu\nAnastasis\n%s",
+ (unsigned long long) as->code,
+ tpk);
+ GNUNET_free (tpk);
+ }
+
+ {
+ const char *off = as->msg;
+ size_t left = strlen (off);
+
+ while (0 != left)
+ {
+ ssize_t ret;
+
+ if (0 == left)
+ break;
+ ret = GNUNET_DISK_file_write (pipe_stdin,
+ off,
+ left);
+ if (ret <= 0)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
+ "write");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->msg_off += ret;
+ off += ret;
+ left -= ret;
+ }
+ GNUNET_DISK_file_close (pipe_stdin);
+ }
+ as->cwh = GNUNET_wait_child (as->child,
+ &sms_done_cb,
+ as);
+ as->connection = connection;
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if (NULL != as->cwh)
+ {
+ /* Spurious call, why are we here? */
+ GNUNET_break (0);
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if ( (GNUNET_OS_PROCESS_EXITED != as->pst) ||
+ (0 != as->exit_code) )
+ {
+ char es[32];
+
+ GNUNET_snprintf (es,
+ sizeof (es),
+ "%u/%d",
+ (unsigned int) as->exit_code,
+ as->pst);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_COMMAND_FAILED,
+ es);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+
+ /* Build HTTP response */
+ {
+ struct MHD_Response *resp;
+ const char *end;
+ size_t slen;
+
+ slen = strlen (as->phone_number);
+ if (slen > 4)
+ end = &as->phone_number[slen - 4];
+ else
+ end = &as->phone_number[slen / 2];
+
+ if (TALER_MHD_xmime_matches (mime,
+ "application/json"))
+ {
+ json_t *body;
+
+ body = json_pack ("{s:I, s:s, s:s}",
+ "code",
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
+ "hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
+ "detail",
+ end);
+ GNUNET_break (NULL != body);
+ resp = TALER_MHD_make_json (body);
+ }
+ else
+ {
+ size_t reply_len;
+ char *reply;
+
+ reply_len = GNUNET_asprintf (&reply,
+ get_message (as->ctx->messages,
+ connection,
+ "instructions"),
+ end);
+ resp = MHD_create_response_from_buffer (reply_len,
+ reply,
+ MHD_RESPMEM_MUST_COPY);
+ GNUNET_free (reply);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ }
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
+ }
+}
+
+
+/**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+static void
+sms_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
+{
+ if (NULL != as->cwh)
+ {
+ GNUNET_wait_child_cancel (as->cwh);
+ as->cwh = NULL;
+ }
+ if (NULL != as->child)
+ {
+ (void) GNUNET_OS_process_kill (as->child,
+ SIGKILL);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (as->child));
+ as->child = NULL;
+ }
+ GNUNET_free (as->msg);
+ GNUNET_free (as->phone_number);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Initialize email based authorization plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
+ */
+void *
+libanastasis_plugin_authorization_sms_init (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct SMS_Context *ctx;
+
+ ctx = GNUNET_new (struct SMS_Context);
+ {
+ char *fn;
+ json_error_t err;
+
+ GNUNET_asprintf (&fn,
+ "%sauthorization-sms-messages.json",
+ GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR));
+ ctx->messages = json_load_file (fn,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == ctx->messages)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load messages from `%s': %s at %d:%d\n",
+ fn,
+ err.text,
+ err.line,
+ err.column);
+ GNUNET_free (fn);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ GNUNET_free (fn);
+ }
+ {
+ int regex_result;
+ const char *regexp = "^\\+?[0-9]+$";
+
+ regex_result = regcomp (&ctx->regex,
+ regexp,
+ REG_EXTENDED);
+ if (0 != regex_result)
+ {
+ GNUNET_break (0);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ }
+ plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
+ plugin->code_validity_period = GNUNET_TIME_UNIT_DAYS;
+ plugin->code_rotation_period = GNUNET_TIME_UNIT_HOURS;
+ plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES;
+ plugin->cls = ctx;
+ plugin->validate = &sms_validate;
+ plugin->start = &sms_start;
+ plugin->process = &sms_process;
+ plugin->cleanup = &sms_cleanup;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "authorization-sms",
+ "COMMAND",
+ &ctx->auth_command))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "authorization-sms",
+ "COMMAND");
+ regfree (&ctx->regex);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+ }
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct ANASTASIS_AuthorizationPlugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_authorization_sms_done (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin = cls;
+ struct SMS_Context *ctx = plugin->cls;
+
+ GNUNET_free (ctx->auth_command);
+ regfree (&ctx->regex);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/authorization/authorization-email-messages.json b/src/authorization/authorization-email-messages.json
new file mode 100644
index 0000000..56f648c
--- /dev/null
+++ b/src/authorization/authorization-email-messages.json
@@ -0,0 +1,10 @@
+{
+ "instructions" : "Recovery TAN was sent to email %.*s@DOMAIN",
+ "instructions_i18n" : {
+ "de_DE" : "Ein Authorisierungscode wurde an %.*s@DOMAIN geschickt"
+ },
+ "body" : "Subject: Anastasis recovery code: A-%llu\n\nThis is for challenge %s.\n",
+ "body_i18n" : {
+ "de_DE" : "Subject: Anastasis Autorisierungscode: A-%llu\n\nDies ist der Code für den Vorgang %s.\n"
+ }
+}
diff --git a/src/authorization/authorization-post-messages.json b/src/authorization/authorization-post-messages.json
new file mode 100644
index 0000000..d2ac83a
--- /dev/null
+++ b/src/authorization/authorization-post-messages.json
@@ -0,0 +1,7 @@
+{
+ "instructions" : "Recovery message send to an address with ZIP code %s",
+ "instructions_i18n" : {
+ "de_DE" : "Ein Authorisierungscode wurde an eine Addresse mit der Postleitzahl %s geschickt"
+ },
+ "body" : "Dear Customer\n\nThe Anastasis recovery code you need to\nrecover your data is A-%llu.\nThis is for challenge %s.\n\nBest regards\n\nYour Anastasis provider"
+}
diff --git a/src/authorization/authorization-sms-messages.json b/src/authorization/authorization-sms-messages.json
new file mode 100644
index 0000000..cb45ed8
--- /dev/null
+++ b/src/authorization/authorization-sms-messages.json
@@ -0,0 +1,6 @@
+{
+ "instructions" : "Recovery TAN send to phone number ending with %s",
+ "instructions_i18n" : {
+ "de_DE" : "Ein Authorisierungscode wurde an die Telefonnummer mit der Endung %s geschickt"
+ }
+}