summaryrefslogtreecommitdiff
path: root/src/reducer
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-07-30 10:38:27 +0200
committerChristian Grothoff <christian@grothoff.org>2021-07-30 10:38:27 +0200
commit7e669bcf6b6336ec429da949bcb4aa456971dba2 (patch)
treed19912f950d1cac1c38b857b7d5bdaba2289544e /src/reducer
downloadanastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.tar.gz
anastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.tar.bz2
anastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.zip
folding history in preparation of GNU Anastasis v0.0.0 release
Diffstat (limited to 'src/reducer')
-rw-r--r--src/reducer/Makefile.am45
-rw-r--r--src/reducer/anastasis_api_backup_redux.c4893
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c2558
-rw-r--r--src/reducer/anastasis_api_redux.c1730
-rw-r--r--src/reducer/anastasis_api_redux.h347
-rw-r--r--src/reducer/validation_CH_AHV.c57
-rw-r--r--src/reducer/validation_CZ_BN.c59
-rw-r--r--src/reducer/validation_DE_SVN.c98
-rw-r--r--src/reducer/validation_DE_TIN.c57
-rw-r--r--src/reducer/validation_IN_AADHAR.c113
-rw-r--r--src/reducer/validation_IT_CF.c198
-rw-r--r--src/reducer/validation_XX_SQUARE.c48
-rw-r--r--src/reducer/validation_XY_PRIME.c53
13 files changed, 10256 insertions, 0 deletions
diff --git a/src/reducer/Makefile.am b/src/reducer/Makefile.am
new file mode 100644
index 0000000..5cbe6f7
--- /dev/null
+++ b/src/reducer/Makefile.am
@@ -0,0 +1,45 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/backend -I$(top_srcdir)/src/lib
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+.NOTPARALLEL:
+
+lib_LTLIBRARIES = \
+ libanastasisredux.la
+
+libanastasisredux_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libanastasisredux_la_SOURCES = \
+ anastasis_api_redux.c anastasis_api_redux.h \
+ anastasis_api_recovery_redux.c \
+ anastasis_api_backup_redux.c \
+ validation_CH_AHV.c \
+ validation_CZ_BN.c \
+ validation_DE_SVN.c \
+ validation_DE_TIN.c \
+ validation_IN_AADHAR.c \
+ validation_IT_CF.c \
+ validation_XX_SQUARE.c \
+ validation_XY_PRIME.c
+libanastasisredux_la_LIBADD = \
+ $(top_builddir)/src/restclient/libanastasisrest.la \
+ $(top_builddir)/src/lib/libanastasis.la \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ltalermhd \
+ -ltalerutil \
+ -ltalerexchange \
+ -ltalermerchant \
+ -ltalerjson \
+ -ljansson \
+ -lgcrypt \
+ -ldl \
+ -lm \
+ $(XLIB)
diff --git a/src/reducer/anastasis_api_backup_redux.c b/src/reducer/anastasis_api_backup_redux.c
new file mode 100644
index 0000000..cea1360
--- /dev/null
+++ b/src/reducer/anastasis_api_backup_redux.c
@@ -0,0 +1,4893 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 lib/anastasis_api_backup_redux.c
+ * @brief anastasis reducer backup api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+
+#include "platform.h"
+#include "anastasis_redux.h"
+#include "anastasis_api_redux.h"
+#include <taler/taler_merchant_service.h>
+
+/**
+ * How long do Anastasis providers store data if the service
+ * is free? Must match #ANASTASIS_MAX_YEARS_STORAGE from
+ * anastasis-httpd.h.
+ */
+#define ANASTASIS_FREE_STORAGE GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_YEARS, 5)
+
+/**
+ * CPU limiter: do not evaluate more than 16k
+ * possible policy combinations to find the "best"
+ * policy.
+ */
+#define MAX_EVALUATIONS (1024 * 16)
+
+
+#define GENERATE_STRING(STRING) #STRING,
+static const char *backup_strings[] = {
+ ANASTASIS_BACKUP_STATES (GENERATE_STRING)
+};
+#undef GENERATE_STRING
+
+
+/**
+ * Linked list of costs.
+ */
+struct Costs
+{
+
+ /**
+ * Kept in a LL.
+ */
+ struct Costs *next;
+
+ /**
+ * Cost in one of the currencies.
+ */
+ struct TALER_Amount cost;
+};
+
+
+/**
+ * Add amount from @a cost to @a my_cost list.
+ *
+ * @param[in,out] my_cost pointer to list to modify
+ * @param cost amount to add
+ */
+static void
+add_cost (struct Costs **my_cost,
+ const struct TALER_Amount *cost)
+{
+ for (struct Costs *pos = *my_cost;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pos->cost,
+ cost))
+ continue;
+ GNUNET_assert (0 <=
+ TALER_amount_add (&pos->cost,
+ &pos->cost,
+ cost));
+ return;
+ }
+ {
+ struct Costs *nc;
+
+ nc = GNUNET_new (struct Costs);
+ nc->cost = *cost;
+ nc->next = *my_cost;
+ *my_cost = nc;
+ }
+}
+
+
+enum ANASTASIS_BackupState
+ANASTASIS_backup_state_from_string_ (const char *state_string)
+{
+ for (enum ANASTASIS_BackupState i = 0;
+ i < sizeof (backup_strings) / sizeof(*backup_strings);
+ i++)
+ if (0 == strcmp (state_string,
+ backup_strings[i]))
+ return i;
+ return ANASTASIS_BACKUP_STATE_ERROR;
+}
+
+
+const char *
+ANASTASIS_backup_state_to_string_ (enum ANASTASIS_BackupState bs)
+{
+ if ( (bs < 0) ||
+ (bs >= sizeof (backup_strings) / sizeof(*backup_strings)) )
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return backup_strings[bs];
+}
+
+
+/**
+ * Update the 'backup_state' field of @a state to @a new_backup_state.
+ *
+ * @param[in,out] state the state to transition
+ * @param new_backup_state the state to transition to
+ */
+static void
+set_state (json_t *state,
+ enum ANASTASIS_BackupState new_backup_state)
+{
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (
+ state,
+ "backup_state",
+ json_string (ANASTASIS_backup_state_to_string_ (new_backup_state))));
+}
+
+
+/**
+ * Returns an initial ANASTASIS backup state (CONTINENT_SELECTING).
+ *
+ * @param cfg handle for gnunet configuration
+ * @return NULL on failure
+ */
+json_t *
+ANASTASIS_backup_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ json_t *initial_state;
+
+ (void) cfg;
+ initial_state = ANASTASIS_REDUX_load_continents_ ();
+ if (NULL == initial_state)
+ return NULL;
+ set_state (initial_state,
+ ANASTASIS_BACKUP_STATE_CONTINENT_SELECTING);
+ return initial_state;
+}
+
+
+/**
+ * Test if @a challenge_size is small enough for the provider's
+ * @a size_limit_in_mb.
+ *
+ * We add 1024 to @a challenge_size here as a "safety margin" as
+ * the encrypted challenge has some additional headers around it
+ *
+ * @param size_limit_in_mb provider's upload limit
+ * @param challenge_size actual binary size of the challenge
+ * @return true if this fits
+ */
+static bool
+challenge_size_ok (uint32_t size_limit_in_mb,
+ size_t challenge_size)
+{
+ return (size_limit_in_mb * 1024LLU * 1024LLU >=
+ challenge_size + 1024LLU);
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_authentication" action.
+ * Returns an #ANASTASIS_ReduxAction if operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+add_authentication (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *auth_providers;
+ json_t *method;
+ const char *method_type;
+ void *challenge;
+ size_t challenge_size;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &method_type),
+ GNUNET_JSON_spec_varsize ("challenge",
+ &challenge,
+ &challenge_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ auth_providers = json_object_get (state,
+ "authentication_providers");
+ if (NULL == auth_providers)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' missing");
+ return NULL;
+ }
+
+ method = json_object_get (arguments,
+ "authentication_method");
+ if (NULL == method)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'authentication_method' required");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_dumpf (method,
+ stderr,
+ JSON_INDENT (2));
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'authentication_method' content malformed");
+ return NULL;
+ }
+ /* Check we know at least one provider that supports this method */
+ {
+ bool found = false;
+ bool too_big = false;
+ json_t *details;
+ const char *url;
+
+ json_object_foreach (auth_providers, url, details)
+ {
+ json_t *methods;
+ json_t *method;
+ size_t index;
+ uint32_t size_limit_in_mb;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &size_limit_in_mb),
+ GNUNET_JSON_spec_json ("methods",
+ &methods),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (details,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_foreach (methods, index, method)
+ {
+ const char *type;
+
+ type = json_string_value (json_object_get (method,
+ "type"));
+ GNUNET_break (NULL != type);
+ if ( (NULL != type) &&
+ (0 == strcmp (type,
+ method_type)) )
+ {
+ found = true;
+ break;
+ }
+ }
+ GNUNET_JSON_parse_free (ispec);
+ if (! challenge_size_ok (size_limit_in_mb,
+ challenge_size))
+ {
+ /* Challenge data too big for this provider. Try to find another one.
+ Note: we add 1024 to challenge-size here as a "safety margin" as
+ the encrypted challenge has some additional headers around it */
+ too_big = true;
+ found = false;
+ }
+ if (found)
+ break;
+ }
+ if (! found)
+ {
+ if (too_big)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_CHALLENGE_DATA_TOO_BIG,
+ method_type);
+ }
+ else
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_AUTHENTICATION_METHOD_NOT_SUPPORTED,
+ method_type);
+ }
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+
+ /* append provided method to our array */
+ {
+ json_t *auth_method_arr;
+
+ auth_method_arr = json_object_get (state,
+ "authentication_methods");
+ if (NULL == auth_method_arr)
+ {
+ auth_method_arr = json_array ();
+ GNUNET_assert (0 == json_object_set_new (state,
+ "authentication_methods",
+ auth_method_arr));
+ }
+ if (! json_is_array (auth_method_arr))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must be an array");
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_array_append (auth_method_arr,
+ method));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ }
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "delete_authentication" action.
+ * Returns an #ANASTASIS_ReduxAction if operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+del_authentication (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *idx;
+ json_t *auth_method_arr;
+
+ auth_method_arr = json_object_get (state,
+ "authentication_methods");
+ if (! json_is_array (auth_method_arr))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must be an array");
+ return NULL;
+ }
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ idx = json_object_get (arguments,
+ "authentication_method");
+ if ( (NULL == idx) ||
+ (! json_is_integer (idx)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'authentication_method' must be a number");
+ return NULL;
+ }
+
+ {
+ size_t index = (size_t) json_integer_value (idx);
+
+ if (0 != json_array_remove (auth_method_arr,
+ index))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "removal failed");
+ return NULL;
+ }
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ********************** done_authentication ******************** */
+
+/**
+ * Which provider would be used for the given challenge,
+ * and at what cost?
+ */
+struct PolicyEntry
+{
+ /**
+ * URL of the provider.
+ */
+ const char *provider_name;
+
+ /**
+ * Recovery fee.
+ */
+ struct TALER_Amount usage_fee;
+};
+
+
+/**
+ * Map from challenges to providers.
+ */
+struct PolicyMap
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct PolicyMap *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PolicyMap *prev;
+
+ /**
+ * Array of proividers selected for each challenge,
+ * with associated costs.
+ * Length of the array will be 'req_methods'.
+ */
+ struct PolicyEntry *providers;
+
+ /**
+ * Diversity score for this policy mapping.
+ */
+ unsigned int diversity;
+
+};
+
+
+/**
+ * Array of challenges for a policy, and DLL with
+ * possible mappings of challenges to providers.
+ */
+struct Policy
+{
+
+ /**
+ * Kept in DLL of all possible policies.
+ */
+ struct Policy *next;
+
+ /**
+ * Kept in DLL of all possible policies.
+ */
+ struct Policy *prev;
+
+ /**
+ * Head of DLL.
+ */
+ struct PolicyMap *pm_head;
+
+ /**
+ * Tail of DLL.
+ */
+ struct PolicyMap *pm_tail;
+
+ /**
+ * Challenges selected for this policy.
+ * Length of the array will be 'req_methods'.
+ */
+ unsigned int *challenges;
+
+};
+
+
+/**
+ * Information for running done_authentication() logic.
+ */
+struct PolicyBuilder
+{
+ /**
+ * Authentication providers available overall, from our state.
+ */
+ json_t *providers;
+
+ /**
+ * Authentication methods available overall, from our state.
+ */
+ const json_t *methods;
+
+ /**
+ * Head of DLL of all possible policies.
+ */
+ struct Policy *p_head;
+
+ /**
+ * Tail of DLL of all possible policies.
+ */
+ struct Policy *p_tail;
+
+ /**
+ * Array of authentication policies to be computed.
+ */
+ json_t *policies;
+
+ /**
+ * Array of length @e req_methods.
+ */
+ unsigned int *m_idx;
+
+ /**
+ * Array of length @e req_methods identifying a set of providers selected
+ * for each authentication method, while we are trying to compute the
+ * 'best' allocation of providers to authentication methods.
+ * Only valid during the go_with() function.
+ */
+ const char **best_sel;
+
+ /**
+ * Error hint to return on failure. Set if @e ec is not #TALER_EC_NONE.
+ */
+ const char *hint;
+
+ /**
+ * Policy we are currently building maps for.
+ */
+ struct Policy *current_policy;
+
+ /**
+ * LL of costs associated with the currently preferred
+ * policy.
+ */
+ struct Costs *best_cost;
+
+ /**
+ * Array of 'best' policy maps found so far,
+ * ordered by policy.
+ */
+ struct PolicyMap *best_map;
+
+ /**
+ * Array of the currency policy maps under evaluation
+ * by find_best_map().
+ */
+ struct PolicyMap *curr_map;
+
+ /**
+ * How many mappings have we evaluated so far?
+ * Used to limit the computation by aborting after
+ * #MAX_EVALUATIONS trials.
+ */
+ unsigned int evaluations;
+
+ /**
+ * Overall number of challenges provided by the user.
+ */
+ unsigned int num_methods;
+
+ /**
+ * Number of challenges that must be satisfied to recover the secret.
+ * Derived from the total number of challenges entered by the user.
+ */
+ unsigned int req_methods;
+
+ /**
+ * Number of different Anastasis providers selected in @e best_sel.
+ * Only valid during the go_with() function.
+ */
+ unsigned int best_diversity;
+
+ /**
+ * Number of identical challenges duplicated at
+ * various providers in the best case. Smaller is
+ * better.
+ */
+ unsigned int best_duplicates;
+
+ /**
+ * Error code to return, #TALER_EC_NONE on success.
+ */
+ enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Free @a costs LL.
+ *
+ * @param[in] costs linked list to free
+ */
+static void
+free_costs (struct Costs *costs)
+{
+ if (NULL == costs)
+ return;
+ free_costs (costs->next);
+ GNUNET_free (costs);
+}
+
+
+/**
+ * Check if providers @a p1 and @a p2 have equivalent
+ * methods and cost structures.
+ *
+ * @return true if the providers are fully equivalent
+ */
+static bool
+equiv_provider (struct PolicyBuilder *pb,
+ const char *p1,
+ const char *p2)
+{
+ json_t *j1;
+ json_t *j2;
+ json_t *m1;
+ json_t *m2;
+ struct TALER_Amount uc1;
+ struct TALER_Amount uc2;
+
+ j1 = json_object_get (pb->providers,
+ p1);
+ j2 = json_object_get (pb->providers,
+ p2);
+ if ( (NULL == j1) ||
+ (NULL == j2) )
+ {
+ GNUNET_break (0);
+ return false;
+ }
+
+ {
+ struct GNUNET_JSON_Specification s1[] = {
+ GNUNET_JSON_spec_json ("methods",
+ &m1),
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &uc1),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j1,
+ s1,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ }
+
+ {
+ struct GNUNET_JSON_Specification s2[] = {
+ GNUNET_JSON_spec_json ("methods",
+ &m2),
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &uc2),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j2,
+ s2,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ }
+
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&uc1,
+ &uc2)) ||
+ (0 !=
+ TALER_amount_cmp (&uc1,
+ &uc2)) )
+ return false;
+
+ if (json_array_size (m1) != json_array_size (m2))
+ return false;
+ {
+ size_t idx1;
+ json_t *e1;
+
+ json_array_foreach (m1, idx1, e1)
+ {
+ const char *type1;
+ struct TALER_Amount fee1;
+ struct GNUNET_JSON_Specification s1[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type1),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ &fee1),
+ GNUNET_JSON_spec_end ()
+ };
+ bool matched = false;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (e1,
+ s1,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ {
+ size_t idx2;
+ json_t *e2;
+
+ json_array_foreach (m2, idx2, e2)
+ {
+ const char *type2;
+ struct TALER_Amount fee2;
+ struct GNUNET_JSON_Specification s2[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type2),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ &fee2),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (e2,
+ s2,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ if ( (0 == strcmp (type1,
+ type2)) &&
+ (GNUNET_OK ==
+ TALER_amount_cmp_currency (&fee1,
+ &fee2)) &&
+ (0 == TALER_amount_cmp (&fee1,
+ &fee2)) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ if (! matched)
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Evaluate the cost/benefit of the provider selection in @a prov_sel
+ * and if it is better then the best known one in @a pb, update @a pb.
+ *
+ * @param[in,out] pb our operational context
+ * @param[in,out] prov_sel array of req_methods provider indices to complete
+ */
+static void
+eval_provider_selection (struct PolicyBuilder *pb,
+ const char *prov_sel[])
+{
+ unsigned int curr_diversity;
+ struct PolicyEntry policy_ent[pb->req_methods];
+
+ for (unsigned int i = 0; i < pb->req_methods; i++)
+ {
+ const json_t *method_obj = json_array_get (pb->methods,
+ pb->m_idx[i]);
+ const json_t *provider_cfg = json_object_get (pb->providers,
+ prov_sel[i]);
+ json_t *provider_methods;
+ const char *method_type;
+ json_t *md;
+ size_t index;
+ bool found = false;
+ uint32_t size_limit_in_mb;
+ struct TALER_Amount upload_cost;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &size_limit_in_mb),
+ GNUNET_JSON_spec_json ("methods",
+ &provider_methods),
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &upload_cost),
+ GNUNET_JSON_spec_end ()
+ };
+ void *challenge;
+ size_t challenge_size;
+ struct GNUNET_JSON_Specification mspec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &method_type),
+ GNUNET_JSON_spec_varsize ("challenge",
+ &challenge,
+ &challenge_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ policy_ent[i].provider_name = prov_sel[i];
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method_obj,
+ mspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ pb->ec = TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID;
+ pb->hint = "'authentication_method' content malformed";
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider_cfg,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Skipping provider %s: no suitable configuration\n",
+ prov_sel[i]);
+ GNUNET_JSON_parse_free (mspec);
+ return;
+ }
+ json_array_foreach (provider_methods, index, md)
+ {
+ const char *type;
+ struct TALER_Amount method_cost;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ &method_cost),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (md,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ pb->ec = TALER_EC_ANASTASIS_REDUCER_STATE_INVALID;
+ pb->hint = "'methods' of provider";
+ GNUNET_JSON_parse_free (pspec);
+ return;
+ }
+ if ( (0 == strcmp (type,
+ method_type)) &&
+ (challenge_size_ok (size_limit_in_mb,
+ challenge_size) ) )
+ {
+ found = true;
+ GNUNET_break (0 <=
+ TALER_amount_add (&policy_ent[i].usage_fee,
+ &method_cost,
+ &upload_cost));
+ }
+ }
+ if (! found)
+ {
+ /* Provider does not OFFER this method, combination not possible.
+ Cost is basically 'infinite', but we simply then skip this. */
+ GNUNET_JSON_parse_free (pspec);
+ GNUNET_JSON_parse_free (mspec);
+ return;
+ }
+ GNUNET_JSON_parse_free (mspec);
+ GNUNET_JSON_parse_free (pspec);
+ }
+
+ /* calculate provider diversity by counting number of different
+ providers selected */
+ curr_diversity = 0;
+ for (unsigned int i = 0; i < pb->req_methods; i++)
+ {
+ bool found = false;
+
+ for (unsigned int j = 0; j < i; j++)
+ {
+ if (prov_sel[i] == prov_sel[j])
+ {
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ curr_diversity++;
+ }
+#if DEBUG
+ fprintf (stderr,
+ "Diversity: %u (best: %u)\n",
+ curr_diversity,
+ pb->best_diversity);
+#endif
+ if (curr_diversity < pb->best_diversity)
+ return; /* do not allow combinations that are bad
+ for provider diversity */
+ if (curr_diversity > pb->best_diversity)
+ {
+ /* drop existing policies, they are all worse */
+ struct PolicyMap *m;
+
+ while (NULL != (m = pb->current_policy->pm_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (pb->current_policy->pm_head,
+ pb->current_policy->pm_tail,
+ m);
+ GNUNET_free (m->providers);
+ GNUNET_free (m);
+ }
+ pb->best_diversity = curr_diversity;
+ }
+ if (NULL == pb->p_head)
+ {
+ /* For the first policy, check for equivalent
+ policy mapping existing: we
+ do not want to do spend CPU time investigating
+ purely equivalent permutations */
+ for (struct PolicyMap *m = pb->current_policy->pm_head;
+ NULL != m;
+ m = m->next)
+ {
+ bool equiv = true;
+ for (unsigned int i = 0; i<pb->req_methods; i++)
+ {
+ if (! equiv_provider (pb,
+ m->providers[i].provider_name,
+ policy_ent[i].provider_name))
+ {
+ equiv = false;
+ break;
+ }
+ }
+ if (equiv)
+ return; /* equivalent to known allocation */
+ }
+ }
+
+ /* Add possible mapping to result list */
+ {
+ struct PolicyMap *m;
+
+ m = GNUNET_new (struct PolicyMap);
+ m->providers = GNUNET_new_array (pb->req_methods,
+ struct PolicyEntry);
+ memcpy (m->providers,
+ policy_ent,
+ sizeof (struct PolicyEntry) * pb->req_methods);
+ m->diversity = curr_diversity;
+ GNUNET_CONTAINER_DLL_insert (pb->current_policy->pm_head,
+ pb->current_policy->pm_tail,
+ m);
+ }
+}
+
+
+/**
+ * Recursively compute possible combination(s) of provider candidates
+ * in @e prov_sel. The selection is complete up to index @a i. Calls
+ * eval_provider_selection() upon a feasible provider selection for
+ * evaluation, resulting in "better" combinations being persisted in
+ * @a pb.
+ *
+ * @param[in,out] pb our operational context
+ * @param[in,out] prov_sel array of req_methods provider URLs to complete
+ * @param i index up to which @a prov_sel is already initialized
+ */
+static void
+provider_candidate (struct PolicyBuilder *pb,
+ const char *prov_sel[],
+ unsigned int i)
+{
+ const char *url;
+ json_t *pconfig;
+
+ json_object_foreach (pb->providers, url, pconfig)
+ {
+ prov_sel[i] = url;
+ if (i == pb->req_methods - 1)
+ {
+ eval_provider_selection (pb,
+ prov_sel);
+ if (TALER_EC_NONE != pb->ec)
+ break;
+ continue;
+ }
+ provider_candidate (pb,
+ prov_sel,
+ i + 1);
+ }
+}
+
+
+/**
+ * Using the selection of authentication methods from @a pb in
+ * "m_idx", compute the best choice of providers.
+ *
+ * @param[in,out] pb our operational context
+ */
+static void
+go_with (struct PolicyBuilder *pb)
+{
+ const char *prov_sel[pb->req_methods];
+ struct Policy *policy;
+
+ /* compute provider selection */
+ policy = GNUNET_new (struct Policy);
+ policy->challenges = GNUNET_new_array (pb->req_methods,
+ unsigned int);
+ memcpy (policy->challenges,
+ pb->m_idx,
+ pb->req_methods * sizeof (unsigned int));
+ pb->current_policy = policy;
+ pb->best_diversity = 0;
+ provider_candidate (pb,
+ prov_sel,
+ 0);
+ GNUNET_CONTAINER_DLL_insert (pb->p_head,
+ pb->p_tail,
+ policy);
+ pb->current_policy = NULL;
+}
+
+
+/**
+ * Recursively computes all possible subsets of length "req_methods"
+ * from an array of length "num_methods", calling "go_with" on each of
+ * those subsets (in "m_idx").
+ *
+ * @param[in,out] pb our operational context
+ * @param i offset up to which the "m_idx" has been computed
+ */
+static void
+method_candidate (struct PolicyBuilder *pb,
+ unsigned int i)
+{
+ unsigned int start;
+ unsigned int *m_idx = pb->m_idx;
+
+ start = (i > 0) ? m_idx[i - 1] + 1 : 0;
+ for (unsigned int j = start; j < pb->num_methods; j++)
+ {
+ m_idx[i] = j;
+ if (i == pb->req_methods - 1)
+ {
+#if DEBUG
+ fprintf (stderr,
+ "Suggesting: ");
+ for (unsigned int k = 0; k<pb->req_methods; k++)
+ {
+ fprintf (stderr,
+ "%u ",
+ m_idx[k]);
+ }
+ fprintf (stderr, "\n");
+#endif
+ go_with (pb);
+ continue;
+ }
+ method_candidate (pb,
+ i + 1);
+ }
+}
+
+
+/**
+ * Lookup @a salt of @a provider_url in @a state.
+ *
+ * @param state the state to inspect
+ * @param provider_url provider to look into
+ * @param[out] salt value to extract
+ * @return #GNUNET_OK on success
+ */
+static int
+lookup_salt (const json_t *state,
+ const char *provider_url,
+ struct ANASTASIS_CRYPTO_ProviderSaltP *salt)
+{
+ const json_t *aps;
+ const json_t *cfg;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ salt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ aps = json_object_get (state,
+ "authentication_providers");
+ if (NULL == aps)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ cfg = json_object_get (aps,
+ provider_url);
+ if (NULL == cfg)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (cfg,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Compare two cost lists.
+ *
+ * @param my cost to compare
+ * @param be cost to compare
+ * @return 0 if costs are estimated equal,
+ * 1 if @a my < @a be
+ * -1 if @a my > @a be
+ */
+static int
+compare_costs (const struct Costs *my,
+ const struct Costs *be)
+{
+ int ranking = 0;
+
+ for (const struct Costs *cmp = be;
+ NULL != cmp;
+ cmp = cmp->next)
+ {
+ bool found = false;
+
+ for (const struct Costs *pos = my;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&cmp->cost,
+ &pos->cost))
+ continue;
+ found = true;
+ }
+ if (! found)
+ ranking--; /* new policy has no cost in this currency */
+ }
+
+ for (const struct Costs *pos = my;
+ NULL != pos;
+ pos = pos->next)
+ {
+ bool found = false;
+
+ for (const struct Costs *cmp = be;
+ NULL != cmp;
+ cmp = cmp->next)
+ {
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&cmp->cost,
+ &pos->cost))
+ continue;
+ found = true;
+ switch (TALER_amount_cmp (&cmp->cost,
+ &pos->cost))
+ {
+ case -1: /* cmp < pos */
+ ranking--;
+ break;
+ case 0:
+ break;
+ case 1: /* cmp > pos */
+ ranking++;
+ break;
+ }
+ break;
+ }
+ if (! found)
+ ranking++; /* old policy has no cost in this currency */
+ }
+ if (0 == ranking)
+ return 0;
+ return (0 > ranking) ? -1 : 1;
+}
+
+
+/**
+ * Evaluate the combined policy map stack in the ``curr_map`` of @a pb
+ * and compare to the current best cost. If we are better, save the
+ * stack in the ``best_map``.
+ *
+ * @param[in,out] pb policy builder we evaluate for
+ * @param num_policies length of the ``curr_map`` array
+ */
+static void
+evaluate_map (struct PolicyBuilder *pb,
+ unsigned int num_policies)
+{
+ struct Costs *my_cost = NULL;
+ unsigned int i = 0;
+ unsigned int duplicates = 0;
+ int ccmp;
+
+#if DEBUG
+ fprintf (stderr,
+ "Checking...\n");
+#endif
+ /* calculate cost */
+ for (const struct Policy *p = pb->p_head;
+ NULL != p;
+ p = p->next)
+ {
+ const struct PolicyMap *pm = &pb->curr_map[i++];
+
+#if DEBUG
+ fprintf (stderr,
+ "Evaluating %p (%u): ",
+ p,
+ pm->diversity);
+ for (unsigned int k = 0; k<pb->req_methods; k++)
+ {
+ const struct PolicyEntry *pe = &pm->providers[k];
+
+ fprintf (stderr,
+ "%u->%s ",
+ p->challenges[k],
+ pe->provider_name);
+ }
+ fprintf (stderr, "\n");
+#endif
+ for (unsigned int j = 0; j<pb->req_methods; j++)
+ {
+ const struct PolicyEntry *pe = &pm->providers[j];
+ unsigned int cv = p->challenges[j];
+ bool found = false;
+ unsigned int i2 = 0;
+
+ /* check for duplicates */
+ for (const struct Policy *p2 = pb->p_head;
+ p2 != p;
+ p2 = p2->next)
+ {
+ const struct PolicyMap *pm2 = &pb->curr_map[i2++];
+
+ for (unsigned int j2 = 0; j2<pb->req_methods; j2++)
+ {
+ const struct PolicyEntry *pe2 = &pm2->providers[j2];
+ unsigned int cv2 = p2->challenges[j2];
+
+ if (cv != cv2)
+ continue; /* different challenge */
+ if (0 == strcmp (pe->provider_name,
+ pe2->provider_name))
+ found = true; /* same challenge&provider! */
+ else
+ duplicates++; /* penalty for same challenge at two providers */
+ }
+ }
+ if (found)
+ continue; /* cost already included, do not add */
+ add_cost (&my_cost,
+ &pe->usage_fee);
+ }
+ }
+
+ ccmp = -1; /* non-zero if 'best_duplicates' is UINT_MAX */
+ if ( (UINT_MAX != pb->best_duplicates) &&
+ (0 > (ccmp = compare_costs (my_cost,
+ pb->best_cost))) )
+ {
+ /* new method not clearly better, do not use it */
+ free_costs (my_cost);
+#if DEBUG
+ fprintf (stderr,
+ "... useless\n");
+#endif
+ return;
+ }
+ if ( (0 == ccmp) &&
+ (duplicates > pb->best_duplicates) )
+ {
+ /* new method is cost-equal, but looses on duplicates,
+ do not use it */
+ free_costs (my_cost);
+#if DEBUG
+ fprintf (stderr,
+ "... useless\n");
+#endif
+ return;
+ }
+ /* new method is better (or first), set as best */
+#if DEBUG
+ fprintf (stderr,
+ "New best: %u duplicates, %s cost\n",
+ duplicates,
+ TALER_amount2s (&my_cost->cost));
+#endif
+ free_costs (pb->best_cost);
+ pb->best_cost = my_cost;
+ pb->best_duplicates = duplicates;
+ memcpy (pb->best_map,
+ pb->curr_map,
+ sizeof (struct PolicyMap) * num_policies);
+}
+
+
+/**
+ * Try all policy maps for @a pos and evaluate the
+ * resulting total cost, saving the best result in
+ * @a pb.
+ *
+ * @param[in,out] pb policy builder context
+ * @param pos policy we are currently looking at maps for
+ * @param off index of @a pos for the policy map
+ */
+static void
+find_best_map (struct PolicyBuilder *pb,
+ struct Policy *pos,
+ unsigned int off)
+{
+ if (NULL == pos)
+ {
+ evaluate_map (pb,
+ off);
+ pb->evaluations++;
+ return;
+ }
+ for (struct PolicyMap *pm = pos->pm_head;
+ NULL != pm;
+ pm = pm->next)
+ {
+ pb->curr_map[off] = *pm;
+ find_best_map (pb,
+ pos->next,
+ off + 1);
+ if (pb->evaluations >= MAX_EVALUATIONS)
+ break;
+ }
+}
+
+
+/**
+ * Select cheapest policy combinations and add them to the JSON ``policies``
+ * array in @a pb
+ *
+ * @param[in,out] policy builder with our state
+ */
+static void
+select_policies (struct PolicyBuilder *pb)
+{
+ unsigned int cnt = 0;
+
+ for (struct Policy *p = pb->p_head;
+ NULL != p;
+ p = p->next)
+ cnt++;
+ {
+ struct PolicyMap best[cnt];
+ struct PolicyMap curr[cnt];
+ unsigned int i;
+
+ pb->best_map = best;
+ pb->curr_map = curr;
+ pb->best_duplicates = UINT_MAX; /* worst */
+ find_best_map (pb,
+ pb->p_head,
+ 0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Assessed %u/%u policies\n",
+ pb->evaluations,
+ (unsigned int) MAX_EVALUATIONS);
+ i = 0;
+ for (struct Policy *p = pb->p_head;
+ NULL != p;
+ p = p->next)
+ {
+ struct PolicyMap *pm = &best[i++];
+ json_t *method_arr;
+
+#if DEBUG
+ fprintf (stderr,
+ "Best map (%u): ",
+ pm->diversity);
+ for (unsigned int k = 0; k<pb->req_methods; k++)
+ {
+ fprintf (stderr,
+ "%u->%s ",
+ p->challenges[k],
+ pm->providers[k].provider_name);
+ }
+ fprintf (stderr, "\n");
+#endif
+ /* Convert "best" selection into 'policies' array */
+ method_arr = json_array ();
+ GNUNET_assert (NULL != method_arr);
+ for (unsigned int i = 0; i < pb->req_methods; i++)
+ {
+ json_t *policy_method = json_pack ("{s:I, s:s}",
+ "authentication_method",
+ (json_int_t) p->challenges[i],
+ "provider",
+ pm->providers[i].provider_name);
+ GNUNET_assert (0 ==
+ json_array_append_new (method_arr,
+ policy_method));
+ }
+ {
+ json_t *policy = json_pack ("{s:o}",
+ "methods",
+ method_arr);
+ GNUNET_assert (NULL != policy);
+ GNUNET_assert (0 ==
+ json_array_append_new (pb->policies,
+ policy));
+ }
+ }
+ }
+}
+
+
+/**
+ * Clean up @a pb, in particular the policies DLL.
+ *
+ * @param[in] pb builder to clean up
+ */
+static void
+clean_pb (struct PolicyBuilder *pb)
+{
+ struct Policy *p;
+
+ while (NULL != (p = pb->p_head))
+ {
+ struct PolicyMap *pm;
+
+ while (NULL != (pm = p->pm_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (p->pm_head,
+ p->pm_tail,
+ pm);
+ GNUNET_free (pm->providers);
+ GNUNET_free (pm);
+ }
+ GNUNET_CONTAINER_DLL_remove (pb->p_head,
+ pb->p_tail,
+ p);
+ GNUNET_free (p->challenges);
+ GNUNET_free (p);
+ }
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "done_authentication" action. Automaticially computes policies
+ * based on available Anastasis providers and challenges provided by
+ * the user.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+done_authentication (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct PolicyBuilder pb = {
+ .ec = TALER_EC_NONE
+ };
+ json_t *providers;
+ json_t *policy_providers;
+
+ pb.providers = json_object_get (state,
+ "authentication_providers");
+ if ( (NULL == pb.providers) ||
+ (! json_is_object (pb.providers) ) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' must be provided");
+ return NULL;
+ }
+ pb.methods = json_object_get (state,
+ "authentication_methods");
+ if ( (NULL == pb.methods) ||
+ (! json_is_array (pb.methods)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must be provided");
+ return NULL;
+ }
+ pb.num_methods = json_array_size (pb.methods);
+ switch (pb.num_methods)
+ {
+ case 0:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must not be empty");
+ return NULL;
+ case 1:
+ case 2:
+ pb.req_methods = pb.num_methods;
+ break;
+ case 3:
+ case 4:
+ pb.req_methods = pb.num_methods - 1;
+ break;
+ case 5:
+ case 6:
+ pb.req_methods = pb.num_methods - 2;
+ break;
+ case 7:
+ pb.req_methods = pb.num_methods - 3;
+ break;
+ default:
+ /* cap at 4 for auto-generation, algorithm
+ to compute mapping gets too expensive
+ otherwise. */
+ pb.req_methods = 4;
+ break;
+ }
+ {
+ unsigned int m_idx[pb.req_methods];
+
+ /* select req_methods from num_methods. */
+ pb.m_idx = m_idx;
+ method_candidate (&pb,
+ 0);
+ }
+ pb.policies = json_array ();
+ select_policies (&pb);
+ clean_pb (&pb);
+ if (TALER_EC_NONE != pb.ec)
+ {
+ json_decref (pb.policies);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ pb.ec,
+ pb.hint);
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "policies",
+ pb.policies));
+ providers = json_object_get (arguments,
+ "providers");
+ if (NULL == providers)
+ {
+ /* Setup a providers array from all working providers */
+ json_t *available = json_object_get (state,
+ "authentication_providers");
+ const char *url;
+ json_t *details;
+
+ policy_providers = json_array ();
+ json_object_foreach (available, url, details)
+ {
+ json_t *provider;
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+
+ if (GNUNET_OK !=
+ lookup_salt (state,
+ url,
+ &salt))
+ continue; /* skip providers that are down */
+ provider = json_pack ("{s:s}",
+ "provider_url", url);
+ GNUNET_assert (NULL != provider);
+ GNUNET_assert (0 ==
+ json_array_append_new (policy_providers,
+ provider));
+ }
+ }
+ else
+ {
+ /* Setup a providers array from all working providers */
+ size_t off;
+ json_t *url;
+
+ policy_providers = json_array ();
+ json_array_foreach (providers, off, url)
+ {
+ json_t *provider;
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+ const char *url_str;
+
+ url_str = json_string_value (url);
+ if ( (NULL == url_str) ||
+ (GNUNET_OK !=
+ lookup_salt (state,
+ url_str,
+ &salt)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "unworkable provider requested");
+ return NULL;
+ }
+ provider = json_pack ("{s:s}",
+ "provider_url", url);
+ GNUNET_assert (NULL != provider);
+ GNUNET_assert (0 ==
+ json_array_append_new (policy_providers,
+ provider));
+ }
+ }
+ if (0 == json_array_size (policy_providers))
+ {
+ json_decref (policy_providers);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "no workable providers in state");
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "policy_providers",
+ policy_providers));
+ set_state (state,
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ******************** add_provider ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_provider" action. Adds another Anastasis provider
+ * to the list of available providers for storing information.
+ *
+ * @param state state to operate on
+ * @param arguments arguments with a provider URL to add
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+add_provider (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ if (ANASTASIS_add_provider_ (state,
+ arguments,
+ cb,
+ cb_cls))
+ return NULL;
+ return ANASTASIS_REDUX_backup_begin_ (state,
+ NULL,
+ cb,
+ cb_cls);
+}
+
+
+/* ******************** add_policy ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_policy" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+add_policy (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *arg_array;
+ json_t *policies;
+ const json_t *auth_providers;
+ const json_t *auth_methods;
+ json_t *methods;
+
+ if (NULL == arguments)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ arg_array = json_object_get (arguments,
+ "policy");
+ if (! json_is_array (arg_array))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy' not an array");
+ return NULL;
+ }
+ policies = json_object_get (state,
+ "policies");
+ if (! json_is_array (policies))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' not an array");
+ return NULL;
+ }
+ auth_providers = json_object_get (state,
+ "authentication_providers");
+ if (! json_is_object (auth_providers))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'auth_providers' not an object");
+ return NULL;
+ }
+ auth_methods = json_object_get (state,
+ "authentication_methods");
+ if (! json_is_array (auth_methods))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'auth_methods' not an array");
+ return NULL;
+ }
+
+ methods = json_array ();
+ GNUNET_assert (NULL != methods);
+
+ /* Add all methods from 'arg_array' to 'methods' */
+ {
+ size_t index;
+ json_t *method;
+
+ json_array_foreach (arg_array, index, method)
+ {
+ const char *provider_url;
+ uint32_t method_idx;
+ json_t *prov_methods;
+ const char *method_type;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("provider",
+ &provider_url),
+ GNUNET_JSON_spec_uint32 ("authentication_method",
+ &method_idx),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'method' details malformed");
+ return NULL;
+ }
+
+ {
+ const json_t *prov_cfg;
+ uint32_t limit;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &limit),
+ GNUNET_JSON_spec_json ("methods",
+ &prov_methods),
+ GNUNET_JSON_spec_end ()
+ };
+
+ prov_cfg = json_object_get (auth_providers,
+ provider_url);
+ if (NULL == prov_cfg)
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider URL unknown");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (prov_cfg,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider lacks authentication methods");
+ return NULL;
+
+ }
+ if (! json_is_array (prov_methods))
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider lacks authentication methods");
+ return NULL;
+ }
+ }
+
+ {
+ const json_t *auth_method;
+
+ auth_method = json_array_get (auth_methods,
+ method_idx);
+ if (NULL == auth_method)
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "authentication method unknown");
+ return NULL;
+ }
+ method_type = json_string_value (json_object_get (auth_method,
+ "type"));
+ if (NULL == method_type)
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "authentication method must be a string");
+ return NULL;
+ }
+ }
+
+ {
+ bool found = false;
+ size_t index;
+ json_t *pm;
+ json_array_foreach (prov_methods, index, pm)
+ {
+ struct TALER_Amount method_cost;
+ const char *type;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ &method_cost),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (pm,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "provider authentication method specification invalid");
+ return NULL;
+ }
+ if (0 != strcmp (type,
+ method_type))
+ continue;
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "selected provider does not support authentication method");
+ return NULL;
+ }
+ }
+ GNUNET_assert (0 ==
+ json_array_append (methods,
+ method));
+ json_decref (prov_methods);
+ } /* end of json_array_foreach (arg_array, index, method) */
+ }
+
+ /* add new policy to array of existing policies */
+ {
+ json_t *policy;
+ json_t *idx;
+
+ policy = json_pack ("{s:o}",
+ "methods",
+ methods);
+ GNUNET_assert (NULL != policy);
+ idx = json_object_get (arguments,
+ "policy_index");
+ if ( (NULL == idx) ||
+ (! json_is_integer (idx)) )
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (policies,
+ policy));
+ }
+ else
+ {
+ GNUNET_assert (0 ==
+ json_array_insert_new (policies,
+ json_integer_value (idx),
+ policy));
+ }
+ }
+
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ******************** update_policy ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "update_policy" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+update_policy (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *idx;
+ size_t index;
+ json_t *policy_arr;
+
+ if (NULL == arguments)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ idx = json_object_get (arguments,
+ "policy_index");
+ if (! json_is_integer (idx))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy_index' must be an integer");
+ return NULL;
+ }
+ index = json_integer_value (idx);
+ policy_arr = json_object_get (state,
+ "policies");
+ if (! json_is_array (policy_arr))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be an array");
+ return NULL;
+ }
+ if (0 != json_array_remove (policy_arr,
+ index))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "removal failed");
+ return NULL;
+ }
+ return add_policy (state,
+ arguments,
+ cb,
+ cb_cls);
+}
+
+
+/* ******************** del_policy ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "delete_policy" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+del_policy (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *idx;
+ size_t index;
+ json_t *policy_arr;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ idx = json_object_get (arguments,
+ "policy_index");
+ if (! json_is_integer (idx))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy_index' must be an integer");
+ return NULL;
+ }
+ index = json_integer_value (idx);
+ policy_arr = json_object_get (state,
+ "policies");
+ if (! json_is_array (policy_arr))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be an array");
+ return NULL;
+ }
+ if (0 != json_array_remove (policy_arr,
+ index))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "removal failed");
+ return NULL;
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ******************** del_challenge ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "delete_challenge" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+del_challenge (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *pidx;
+ const json_t *cidx;
+ size_t index;
+ json_t *policy_arr;
+ json_t *policy;
+ json_t *method_arr;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ pidx = json_object_get (arguments,
+ "policy_index");
+ cidx = json_object_get (arguments,
+ "challenge_index");
+ if (! json_is_integer (pidx))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy_index' must be an integer");
+ return NULL;
+ }
+ if (! json_is_integer (cidx))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'challenge_index' must be an integer");
+ return NULL;
+ }
+ index = json_integer_value (pidx);
+ policy_arr = json_object_get (state,
+ "policies");
+ if (! json_is_array (policy_arr))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be an array");
+ return NULL;
+ }
+ policy = json_array_get (policy_arr,
+ index);
+ if (NULL == policy)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy_index' out of range");
+ return NULL;
+ }
+ method_arr = json_object_get (policy,
+ "methods");
+ if (NULL == method_arr)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "methods missing in policy");
+ return NULL;
+ }
+ index = json_integer_value (cidx);
+ if (0 != json_array_remove (method_arr,
+ index))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "removal failed");
+ return NULL;
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ********************** done_policy_review ***************** */
+
+
+/**
+ * Calculate how many years of service we need
+ * from the desired @a expiration time,
+ * rounding up.
+ *
+ * @param expiration desired expiration time
+ * @return number of years of service to pay for
+*/
+static unsigned int
+expiration_to_years (struct GNUNET_TIME_Absolute expiration)
+{
+ struct GNUNET_TIME_Relative rem;
+ unsigned int years;
+
+ rem = GNUNET_TIME_absolute_get_remaining (expiration);
+ years = rem.rel_value_us / GNUNET_TIME_UNIT_YEARS.rel_value_us;
+ if (0 != rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us)
+ years++;
+ return years;
+}
+
+
+/**
+ * Update @a state such that the earliest expiration for
+ * any truth or policy is @a expiration. Recalculate
+ * the ``upload_fees`` array with the associated costs.
+ *
+ * @param[in,out] state our state to update
+ * @param expiration new expiration to enforce
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR if the state is invalid
+ */
+static enum GNUNET_GenericReturnValue
+update_expiration_cost (json_t *state,
+ struct GNUNET_TIME_Absolute expiration)
+{
+ struct Costs *costs = NULL;
+ unsigned int years;
+ json_t *providers;
+ bool is_free = true;
+
+ providers = json_object_get (state,
+ "authentication_providers");
+ if (! json_is_object (providers))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ years = expiration_to_years (expiration);
+
+ /* go over all providers and add up cost */
+ {
+ const char *url;
+ json_t *provider;
+
+ json_object_foreach (providers, url, provider)
+ {
+ struct TALER_Amount annual_fee;
+ struct GNUNET_JSON_Specification pspec[] = {
+ TALER_JSON_spec_amount_any ("annual_fee",
+ &annual_fee),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_Amount fee;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_multiply (&fee,
+ &annual_fee,
+ years))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ add_cost (&costs,
+ &fee);
+ }
+ }
+
+ /* go over all truths and add up cost */
+ {
+ unsigned int off = 0;
+ unsigned int len = 0;
+ struct AlreadySeen
+ {
+ uint32_t method;
+ const char *provider_url;
+ } *seen = NULL;
+ json_t *policies;
+ size_t pidx;
+ json_t *policy;
+
+ policies = json_object_get (state,
+ "policies");
+ json_array_foreach (policies, pidx, policy)
+ {
+ json_t *methods;
+ json_t *method;
+ size_t midx;
+
+ methods = json_object_get (policy,
+ "methods");
+ json_array_foreach (methods, midx, method)
+ {
+ const char *provider_url;
+ uint32_t method_idx;
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("provider",
+ &provider_url),
+ GNUNET_JSON_spec_uint32 ("authentication_method",
+ &method_idx),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* check if we have seen this one before */
+ {
+ bool found = false;
+
+ for (unsigned int i = 0; i<off; i++)
+ if ( (seen[i].method == method_idx) &&
+ (0 == strcmp (seen[i].provider_url,
+ provider_url)) )
+ found = true;
+ if (found)
+ continue; /* skip */
+ }
+ if (off == len)
+ {
+ GNUNET_array_grow (seen,
+ len,
+ 4 + len * 2);
+ }
+ seen[off].method = method_idx;
+ seen[off].provider_url = provider_url;
+ off++;
+ {
+ struct TALER_Amount upload_cost;
+ struct GNUNET_JSON_Specification pspec[] = {
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &upload_cost),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_Amount fee;
+ const json_t *provider_cfg
+ = json_object_get (providers,
+ provider_url);
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider_cfg,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_multiply (&fee,
+ &upload_cost,
+ years))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ add_cost (&costs,
+ &fee);
+ }
+ }
+ }
+ GNUNET_array_grow (seen,
+ len,
+ 0);
+ }
+
+ /* convert 'costs' into state */
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ while (NULL != costs)
+ {
+ struct Costs *nxt = costs->next;
+
+ if ( (0 != costs->cost.value) ||
+ (0 != costs->cost.fraction) )
+ {
+ json_t *ao;
+
+ ao = json_pack ("{s:o}",
+ "fee",
+ TALER_JSON_from_amount (&costs->cost));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ ao));
+ is_free = false;
+ }
+ GNUNET_free (costs);
+ costs = nxt;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "upload_fees",
+ arr));
+ }
+
+ if (is_free)
+ expiration = GNUNET_TIME_relative_to_absolute (ANASTASIS_FREE_STORAGE);
+ /* update 'expiration' in state */
+ {
+ json_t *eo;
+
+ (void) GNUNET_TIME_round_abs (&expiration);
+ eo = GNUNET_JSON_from_time_abs (expiration);
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "expiration",
+ eo));
+ }
+
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "done_policy_review" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+done_policy_review (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *policy_arr;
+
+ policy_arr = json_object_get (state,
+ "policies");
+ if (0 == json_array_size (policy_arr))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "no policies specified");
+ return NULL;
+ }
+ {
+ struct GNUNET_TIME_Absolute exp = {0};
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_absolute_time ("expiration",
+ &exp)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "invalid expiration specified");
+ return NULL;
+ }
+ if (0 == exp.abs_value_us)
+ exp = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS);
+ if (GNUNET_OK !=
+ update_expiration_cost (state,
+ exp))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "could not calculate expiration cost");
+ return NULL;
+ }
+ }
+ set_state (state,
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Information we keep for an upload() operation.
+ */
+struct UploadContext;
+
+
+/**
+ * Maps a TruthUpload to a policy and recovery method where this
+ * truth is used.
+ */
+struct PolicyMethodReference
+{
+ /**
+ * Offset into the "policies" array.
+ */
+ unsigned int policy_index;
+
+ /**
+ * Offset into the "methods" array (of the policy selected
+ * by @e policy_index).
+ */
+ unsigned int method_index;
+
+};
+
+
+/**
+ * Entry we keep per truth upload.
+ */
+struct TruthUpload
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TruthUpload *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TruthUpload *prev;
+
+ /**
+ * Handle to the actual upload operation.
+ */
+ struct ANASTASIS_TruthUpload *tu;
+
+ /**
+ * Upload context this operation is part of.
+ */
+ struct UploadContext *uc;
+
+ /**
+ * Truth resulting from the upload, if any.
+ */
+ struct ANASTASIS_Truth *t;
+
+ /**
+ * A taler://pay/-URI with a request to pay the annual fee for
+ * the service. Set if payment is required.
+ */
+ char *payment_request;
+
+ /**
+ * Which policies and methods does this truth affect?
+ */
+ struct PolicyMethodReference *policies;
+
+ /**
+ * Where are we uploading to?
+ */
+ char *provider_url;
+
+ /**
+ * Which challenge object are we uploading?
+ */
+ uint32_t am_idx;
+
+ /**
+ * Length of the @e policies array.
+ */
+ unsigned int policies_length;
+
+ /**
+ * Status of the upload.
+ */
+ enum ANASTASIS_UploadStatus us;
+
+ /**
+ * Taler error code of the upload.
+ */
+ enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Information we keep for an upload() operation.
+ */
+struct UploadContext
+{
+ /**
+ * Recovery action returned to caller for aborting the operation.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Function to call upon completion.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Our state.
+ */
+ json_t *state;
+
+ /**
+ * Master secret sharing operation, NULL if not yet running.
+ */
+ struct ANASTASIS_SecretShare *ss;
+
+ /**
+ * Head of DLL of truth uploads.
+ */
+ struct TruthUpload *tues_head;
+
+ /**
+ * Tail of DLL of truth uploads.
+ */
+ struct TruthUpload *tues_tail;
+
+ /**
+ * Timeout to use for the operation, from the arguments.
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * For how many years should we pay?
+ */
+ unsigned int years;
+
+};
+
+
+/**
+ * Function called when the #upload transition is being aborted.
+ *
+ * @param cls a `struct UploadContext`
+ */
+static void
+upload_cancel_cb (void *cls)
+{
+ struct UploadContext *uc = cls;
+ struct TruthUpload *tue;
+
+ while (NULL != (tue = uc->tues_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (uc->tues_head,
+ uc->tues_tail,
+ tue);
+ if (NULL != tue->tu)
+ {
+ ANASTASIS_truth_upload_cancel (tue->tu);
+ tue->tu = NULL;
+ }
+ if (NULL != tue->t)
+ {
+ ANASTASIS_truth_free (tue->t);
+ tue->t = NULL;
+ }
+ GNUNET_free (tue->provider_url);
+ GNUNET_free (tue->payment_request);
+ GNUNET_free (tue->policies);
+ GNUNET_free (tue);
+ }
+ if (NULL != uc->ss)
+ {
+ ANASTASIS_secret_share_cancel (uc->ss);
+ uc->ss = NULL;
+ }
+ json_decref (uc->state);
+ GNUNET_free (uc);
+}
+
+
+/**
+ * Take all of the ongoing truth uploads and serialize them into the @a uc
+ * state.
+ *
+ * @param[in,out] uc context to take truth uploads from and to update state of
+ */
+static void
+serialize_truth (struct UploadContext *uc)
+{
+ json_t *policies;
+
+ policies = json_object_get (uc->state,
+ "policies");
+ GNUNET_assert (json_is_array (policies));
+ for (struct TruthUpload *tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if (NULL == tue->t)
+ continue;
+ for (unsigned int i = 0; i<tue->policies_length; i++)
+ {
+ const struct PolicyMethodReference *pmr = &tue->policies[i];
+ json_t *policy = json_array_get (policies,
+ pmr->policy_index);
+ json_t *methods = json_object_get (policy,
+ "methods");
+ json_t *auth_method = json_array_get (methods,
+ pmr->method_index);
+ json_t *truth = ANASTASIS_truth_to_json (tue->t);
+
+ GNUNET_assert (0 ==
+ json_object_set_new (truth,
+ "upload_status",
+ json_integer (tue->us)));
+ GNUNET_assert (NULL != policy);
+ GNUNET_assert (NULL != methods);
+ GNUNET_assert (NULL != auth_method);
+ GNUNET_assert (NULL != truth);
+ GNUNET_assert (0 ==
+ json_object_set_new (auth_method,
+ "truth",
+ truth));
+ }
+ }
+}
+
+
+/**
+ * Function called with the results of a #ANASTASIS_secret_share().
+ *
+ * @param cls closure with a `struct UploadContext *`
+ * @param sr share result
+ */
+static void
+secret_share_result_cb (void *cls,
+ const struct ANASTASIS_ShareResult *sr)
+{
+ struct UploadContext *uc = cls;
+
+ uc->ss = NULL;
+ switch (sr->ss)
+ {
+ case ANASTASIS_SHARE_STATUS_SUCCESS:
+ /* Just to be safe, delete the "core_secret" so that it is not
+ accidentally preserved anywhere */
+ (void) json_object_del (uc->state,
+ "core_secret");
+ {
+ json_t *sa = json_object ();
+
+ GNUNET_assert (NULL != sa);
+ for (unsigned int i = 0; i<sr->details.success.num_providers; i++)
+ {
+ const struct ANASTASIS_ProviderSuccessStatus *pssi
+ = &sr->details.success.pss[i];
+ json_t *d;
+
+ d = json_pack ("{s:I, s:o}",
+ "policy_version",
+ pssi->policy_version,
+ "policy_expiration",
+ GNUNET_JSON_from_time_abs (pssi->policy_expiration));
+ GNUNET_assert (NULL != d);
+ GNUNET_assert (0 ==
+ json_object_set_new (sa,
+ pssi->provider_url,
+ d));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (uc->state,
+ "success_details",
+ sa));
+ }
+ set_state (uc->state,
+ ANASTASIS_BACKUP_STATE_BACKUP_FINISHED);
+ uc->cb (uc->cb_cls,
+ TALER_EC_NONE,
+ uc->state);
+ break;
+ case ANASTASIS_SHARE_STATUS_PAYMENT_REQUIRED:
+ {
+ json_t *ra;
+ json_t *providers;
+
+ providers = json_object_get (uc->state,
+ "policy_providers");
+ set_state (uc->state,
+ ANASTASIS_BACKUP_STATE_POLICIES_PAYING);
+ serialize_truth (uc);
+ ra = json_array ();
+ GNUNET_assert (NULL != ra);
+ for (unsigned int i = 0; i<
+ sr->details.payment_required.payment_requests_length; i++)
+ {
+ const struct ANASTASIS_SharePaymentRequest *spr;
+ json_t *pr;
+ size_t off;
+ json_t *provider;
+
+ spr = &sr->details.payment_required.payment_requests[i];
+ pr = json_pack ("{s:s, s:s}",
+ "payto",
+ spr->payment_request_url,
+ "provider",
+ spr->provider_url);
+ GNUNET_assert (0 ==
+ json_array_append_new (ra,
+ pr));
+ json_array_foreach (providers, off, provider)
+ {
+ const char *purl = json_string_value (json_object_get (provider,
+ "provider_url"));
+
+ if (NULL == purl)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "policy_providers array contents are invalid");
+ json_decref (ra);
+ return;
+ }
+ if (0 == strcmp (purl,
+ spr->provider_url))
+ {
+ json_t *psj;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Remembering payment secret for provider `%s'\n",
+ spr->provider_url);
+ psj = GNUNET_JSON_from_data_auto (&spr->payment_secret);
+ GNUNET_assert (0 ==
+ json_object_set_new (provider,
+ "payment_secret",
+ psj));
+ }
+ }
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (uc->state,
+ "policy_payment_requests",
+ ra));
+ }
+ uc->cb (uc->cb_cls,
+ TALER_EC_NONE,
+ uc->state);
+ break;
+ case ANASTASIS_SHARE_STATUS_PROVIDER_FAILED:
+ {
+ json_t *details;
+
+ details = json_pack ("{s:s, s:I, s:I, s:s}",
+ "backup_state",
+ "ERROR",
+ "http_status",
+ (json_int_t) sr->details.provider_failure.http_status,
+ "upload_status",
+ (json_int_t) sr->details.provider_failure.ec,
+ "provider_url",
+ sr->details.provider_failure.provider_url);
+ uc->cb (uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED,
+ details);
+ json_decref (details);
+ }
+ break;
+ default:
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected share result");
+ break;
+ }
+ upload_cancel_cb (uc);
+}
+
+
+/**
+ * All truth uploads are done, begin with uploading the policy.
+ *
+ * @param[in,out] uc context for the operation
+ */
+static void
+share_secret (struct UploadContext *uc)
+{
+ json_t *user_id;
+ json_t *core_secret;
+ json_t *jpolicies;
+ json_t *providers = NULL;
+ size_t policies_len;
+ const char *secret_name = NULL;
+ unsigned int pds_len;
+ struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("identity_attributes",
+ &user_id),
+ GNUNET_JSON_spec_json ("policies",
+ &jpolicies),
+ GNUNET_JSON_spec_json ("policy_providers",
+ &providers),
+ GNUNET_JSON_spec_json ("core_secret",
+ &core_secret),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("secret_name",
+ &secret_name)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (uc->state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "State parsing failed when preparing to share secret");
+ upload_cancel_cb (uc);
+ return;
+ }
+
+ {
+ json_t *args;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &timeout)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ args = json_object_get (uc->state,
+ "pay-arguments");
+ if ( (NULL != args) &&
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (args,
+ pspec,
+ NULL, NULL)) )
+ {
+ json_dumpf (args,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ NULL);
+ upload_cancel_cb (uc);
+ return;
+ }
+ }
+
+ if ( (! json_is_object (user_id)) ||
+ (! json_is_array (jpolicies)) ||
+ (0 == json_array_size (jpolicies)) ||
+ ( (NULL != providers) &&
+ (! json_is_array (providers)) ) )
+ {
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "State parsing failed checks when preparing to share secret");
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+
+ policies_len = json_array_size (jpolicies);
+ pds_len = json_array_size (providers);
+
+ if (0 == pds_len)
+ {
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "no workable providers in state");
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+
+
+ {
+ struct ANASTASIS_Policy *vpolicies[policies_len];
+ const struct ANASTASIS_Policy *policies[policies_len];
+ struct ANASTASIS_ProviderDetails pds[GNUNET_NZL (pds_len)];
+
+ /* initialize policies/vpolicies arrays */
+ memset (pds,
+ 0,
+ sizeof (pds));
+ for (size_t i = 0; i<policies_len; i++)
+ {
+ const json_t *policy = json_array_get (jpolicies,
+ i);
+ const json_t *jmethods = json_object_get (policy,
+ "methods");
+ unsigned int methods_len;
+
+ if ( (! json_is_array (jmethods)) ||
+ (0 == json_array_size (jmethods)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'methods' must be an array");
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+ methods_len = json_array_size (jmethods);
+ {
+ struct ANASTASIS_Policy *p;
+ struct ANASTASIS_Truth *truths[methods_len];
+ const struct ANASTASIS_Truth *ctruths[methods_len];
+
+ for (unsigned int j = 0; j<methods_len; j++)
+ {
+ const json_t *jmethod = json_array_get (jmethods,
+ j);
+ json_t *jtruth = NULL;
+ uint32_t truth_index;
+ const char *provider_url;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("truth",
+ &jtruth)),
+ GNUNET_JSON_spec_string ("provider",
+ &provider_url),
+ GNUNET_JSON_spec_uint32 ("authentication_method",
+ &truth_index),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_break (NULL != jmethod);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jmethod,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ for (unsigned int k = 0; k<j; k++)
+ ANASTASIS_truth_free (truths[k]);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'truth' failed to decode");
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+ if (NULL != jtruth)
+ {
+ /* Get truth by deserializing from state */
+ truths[j] = ANASTASIS_truth_from_json (jtruth);
+ if (NULL == truths[j])
+ {
+ GNUNET_break (0);
+ for (unsigned int k = 0; k<j; k++)
+ ANASTASIS_truth_free (truths[k]);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'truth' failed to decode");
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+ }
+ else
+ {
+ bool found = false;
+ /* Maybe we never serialized the truth; find it in our DLL */
+ for (struct TruthUpload *tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ GNUNET_break (NULL != tue->t);
+ if ( (tue->am_idx == truth_index) &&
+ (0 == strcmp (provider_url,
+ tue->provider_url)) )
+ {
+ /* Duplicate truth object */
+ json_t *jt = ANASTASIS_truth_to_json (tue->t);
+
+ GNUNET_assert (NULL != jt);
+ truths[j] = ANASTASIS_truth_from_json (jt);
+ GNUNET_assert (NULL != truths[j]);
+ json_decref (jt);
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ GNUNET_break (0);
+ for (unsigned int k = 0; k<j; k++)
+ ANASTASIS_truth_free (truths[k]);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'truth' failed to decode");
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+ }
+ GNUNET_JSON_parse_free (ispec);
+ ctruths[j] = truths[j];
+ }
+ p = ANASTASIS_policy_create (ctruths,
+ methods_len);
+ vpolicies[i] = p;
+ policies[i] = p;
+ for (unsigned int k = 0; k<methods_len; k++)
+ ANASTASIS_truth_free (truths[k]);
+ }
+ }
+
+ /* initialize 'pds' array */
+ for (unsigned int i = 0; i<pds_len; i++)
+ {
+ json_t *pdj = json_array_get (providers,
+ i);
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("payment_secret",
+ &pds[i].payment_secret)),
+ GNUNET_JSON_spec_string ("provider_url",
+ &pds[i].provider_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if ( (GNUNET_OK !=
+ GNUNET_JSON_parse (pdj,
+ ispec,
+ NULL, NULL)) ||
+ (GNUNET_OK !=
+ lookup_salt (uc->state,
+ pds[i].provider_url,
+ &pds[i].provider_salt)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'providers' entry malformed");
+ for (unsigned int i = 0; i<policies_len; i++)
+ ANASTASIS_policy_destroy (vpolicies[i]);
+ upload_cancel_cb (uc);
+ GNUNET_JSON_parse_free (spec);
+ return;
+ }
+ }
+
+ {
+ char *secret;
+ size_t secret_size;
+
+ secret = json_dumps (core_secret,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != secret);
+ secret_size = strlen (secret);
+ uc->ss = ANASTASIS_secret_share (ANASTASIS_REDUX_ctx_,
+ user_id,
+ pds,
+ pds_len,
+ policies,
+ policies_len,
+ uc->years,
+ timeout,
+ &secret_share_result_cb,
+ uc,
+ secret_name,
+ secret,
+ secret_size);
+ GNUNET_free (secret);
+ }
+ for (unsigned int i = 0; i<policies_len; i++)
+ ANASTASIS_policy_destroy (vpolicies[i]);
+ }
+ GNUNET_JSON_parse_free (spec);
+ if (NULL == uc->ss)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin secret sharing");
+ upload_cancel_cb (uc);
+ return;
+ }
+}
+
+
+/**
+ * Some truth uploads require payment, serialize state and
+ * request payment to be executed by the application.
+ *
+ * @param[in,out] uc context for the operation
+ */
+static void
+request_truth_payment (struct UploadContext *uc)
+{
+ json_t *payments;
+
+ payments = json_array ();
+ GNUNET_assert (NULL != payments);
+ serialize_truth (uc);
+ for (struct TruthUpload *tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if (NULL == tue->payment_request)
+ continue;
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (payments,
+ json_string (
+ tue->payment_request)));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (uc->state,
+ "payments",
+ payments));
+ set_state (uc->state,
+ ANASTASIS_BACKUP_STATE_TRUTHS_PAYING);
+ uc->cb (uc->cb_cls,
+ TALER_EC_NONE,
+ uc->state);
+ upload_cancel_cb (uc);
+}
+
+
+/**
+ * We may be finished with all (active) asynchronous operations.
+ * Check if any are pending and continue accordingly.
+ *
+ * @param[in,out] uc context for the operation
+ */
+static void
+check_upload_finished (struct UploadContext *uc)
+{
+ bool pay = false;
+ bool active = false;
+
+ for (struct TruthUpload *tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if (TALER_EC_NONE != tue->ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Truth upload failed with error %d\n",
+ (int) tue->ec);
+ uc->cb (uc->cb_cls,
+ tue->ec,
+ NULL);
+ upload_cancel_cb (uc);
+ return;
+ }
+ if (NULL != tue->tu)
+ active = true;
+ if (NULL != tue->payment_request)
+ pay = true;
+ }
+ if (active)
+ return;
+ if (pay)
+ {
+ request_truth_payment (uc);
+ return;
+ }
+ share_secret (uc);
+}
+
+
+/**
+ * Upload result information. The resulting truth object can be used
+ * to create policies. If payment is required, the @a taler_pay_url
+ * is returned and the operation must be retried after payment.
+ * Callee MUST free @a t using ANASTASIS_truth_free().
+ *
+ * @param cls closure with a `struct TruthUpload`
+ * @param t truth object to create policies, NULL on failure
+ * @param ud upload details
+ */
+static void
+truth_upload_cb (void *cls,
+ struct ANASTASIS_Truth *t,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct TruthUpload *tue = cls;
+
+ tue->tu = NULL;
+ tue->t = t;
+ tue->ec = ud->ec;
+ tue->us = ud->us;
+ if (ANASTASIS_US_PAYMENT_REQUIRED == ud->us)
+ {
+ tue->payment_request = GNUNET_strdup (
+ ud->details.payment.payment_request);
+ }
+ check_upload_finished (tue->uc);
+}
+
+
+/**
+ * Check if we still need to create a new truth object for the truth
+ * identified by @a provider_url and @a am_idx. If so, create it from
+ * @a truth for policy reference @a pmr. If such a truth object
+ * already exists, append @a pmr to its list of reasons.
+ *
+ * @param[in,out] our upload context
+ * @param pmr policy method combination that requires the truth
+ * @param provider_url the URL of the Anastasis provider to upload
+ * the truth to, used to check for existing entries
+ * @param am_idx index of the authentication method, used to check for existing entries
+ * @param[in] truth object representing already uploaded truth, reference captured!
+ * @param[in,out] async_truth pointer to counter with the number of ongoing uploads,
+ * updated
+ * @param auth_method object with the challenge details, to generate the truth
+ * @return #GNUNET_SYSERR error requiring abort,
+ * #GNUNET_OK on success
+ */
+static int
+add_truth_object (struct UploadContext *uc,
+ const struct PolicyMethodReference *pmr,
+ const char *provider_url,
+ uint32_t am_idx,
+ json_t *truth,
+ unsigned int *async_truth,
+ json_t *auth_method)
+{
+ /* check if we are already uploading this truth */
+ struct TruthUpload *tue;
+ bool must_upload = true;
+
+ for (tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if ( (0 == strcmp (tue->provider_url,
+ provider_url)) &&
+ (am_idx == tue->am_idx) )
+ {
+ GNUNET_array_append (tue->policies,
+ tue->policies_length,
+ *pmr);
+ break;
+ }
+ }
+
+ if (NULL == tue)
+ {
+ /* Create new entry */
+ tue = GNUNET_new (struct TruthUpload);
+
+ GNUNET_CONTAINER_DLL_insert (uc->tues_head,
+ uc->tues_tail,
+ tue);
+ tue->uc = uc;
+ tue->policies = GNUNET_new (struct PolicyMethodReference);
+ *tue->policies = *pmr;
+ tue->provider_url = GNUNET_strdup (provider_url);
+ tue->am_idx = am_idx;
+ tue->policies_length = 1;
+ }
+
+ {
+ uint32_t status = UINT32_MAX;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("upload_status",
+ &status)),
+ GNUNET_JSON_spec_end ()
+ };
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (truth,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ must_upload = (ANASTASIS_US_SUCCESS != status);
+ }
+
+ if (NULL == tue->t)
+ {
+ tue->t = ANASTASIS_truth_from_json (truth);
+ if (NULL == tue->t)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ if ( (NULL != tue->tu) &&
+ (! must_upload) )
+ {
+ ANASTASIS_truth_upload_cancel (tue->tu);
+ (*async_truth)--;
+ tue->tu = NULL;
+ return GNUNET_OK;
+ }
+
+ if ( (NULL == tue->tu) &&
+ (must_upload) )
+ {
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+ void *truth_data;
+ size_t truth_data_size;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_varsize ("challenge",
+ &truth_data,
+ &truth_data_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ lookup_salt (uc->state,
+ provider_url,
+ &salt))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (auth_method,
+ spec,
+ NULL, NULL))
+ {
+ json_dumpf (auth_method,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ json_t *user_id;
+
+ user_id = json_object_get (uc->state,
+ "identity_attributes");
+ if (! json_is_object (user_id))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ ANASTASIS_CRYPTO_user_identifier_derive (user_id,
+ &salt,
+ &id);
+ }
+ tue->tu = ANASTASIS_truth_upload3 (ANASTASIS_REDUX_ctx_,
+ &id,
+ tue->t,
+ truth_data,
+ truth_data_size,
+ uc->years,
+ uc->timeout,
+ &truth_upload_cb,
+ tue);
+ GNUNET_JSON_parse_free (spec);
+ tue->t = NULL;
+ (*async_truth)++;
+ }
+
+ if ( (NULL != tue->tu) &&
+ (NULL != tue->t) )
+ {
+ /* no point in having both */
+ ANASTASIS_truth_free (tue->t);
+ tue->t = NULL;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if we still need to upload the truth identified by
+ * @a provider_url and @a am_idx. If so, upload it for
+ * policy reference @a pmr. If the upload is already queued,
+ * append @a pmr to its list of reasons.
+ *
+ * @param[in,out] our upload context
+ * @param pmr policy method combination that requires the truth
+ * @param provider_url the URL of the Anastasis provider to upload
+ * the truth to, used to check for existing entries
+ * @param am_idx index of the authentication method, used to check for existing entries
+ * @param auth_method object with the challenge details, to generate the truth
+ * @return #GNUNET_SYSERR on error requiring abort,
+ * #GNUNET_NO if no new truth upload was generated (@a pmr was appended)
+ * #GNUNET_OK if a new truth upload was initiated
+ */
+static int
+check_truth_upload (struct UploadContext *uc,
+ const struct PolicyMethodReference *pmr,
+ const char *provider_url,
+ uint32_t am_idx,
+ json_t *auth_method)
+{
+ json_t *user_id;
+ json_t *jtruth;
+ struct TruthUpload *tue;
+
+ user_id = json_object_get (uc->state,
+ "identity_attributes");
+ if (! json_is_object (user_id))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check if we are already uploading this truth */
+ for (tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if ( (0 == strcmp (tue->provider_url,
+ provider_url)) &&
+ (am_idx == tue->am_idx) )
+ {
+ GNUNET_array_append (tue->policies,
+ tue->policies_length,
+ *pmr);
+ return GNUNET_NO;
+ }
+ }
+
+ /* need new upload */
+ tue = GNUNET_new (struct TruthUpload);
+ {
+ json_t *policies = json_object_get (uc->state,
+ "policies");
+ json_t *policy = json_array_get (policies,
+ pmr->policy_index);
+ json_t *methods = json_object_get (policy,
+ "methods");
+ json_t *method = json_array_get (methods,
+ pmr->method_index);
+
+ jtruth = json_object_get (method,
+ "truth");
+ }
+
+ {
+ const char *type;
+ const char *mime_type = NULL;
+ const char *instructions = NULL;
+ void *truth_data;
+ size_t truth_data_size;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("mime_type",
+ &mime_type)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("instructions",
+ &instructions)),
+ GNUNET_JSON_spec_varsize ("challenge",
+ &truth_data,
+ &truth_data_size),
+ GNUNET_JSON_spec_end ()
+ };
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (auth_method,
+ spec,
+ NULL, NULL))
+ {
+ json_dumpf (auth_method,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ GNUNET_free (tue);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CONTAINER_DLL_insert (uc->tues_head,
+ uc->tues_tail,
+ tue);
+ tue->uc = uc;
+ tue->policies = GNUNET_new (struct PolicyMethodReference);
+ *tue->policies = *pmr;
+ tue->provider_url = GNUNET_strdup (provider_url);
+ tue->am_idx = am_idx;
+ tue->policies_length = 1;
+ if (GNUNET_OK !=
+ lookup_salt (uc->state,
+ provider_url,
+ &provider_salt))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return GNUNET_SYSERR;
+ }
+ ANASTASIS_CRYPTO_user_identifier_derive (user_id,
+ &provider_salt,
+ &id);
+ {
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+ struct ANASTASIS_CRYPTO_QuestionSaltP question_salt;
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ struct GNUNET_JSON_Specification jspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &question_salt),
+ GNUNET_JSON_spec_fixed_auto ("truth_key",
+ &truth_key),
+ GNUNET_JSON_spec_fixed_auto ("nonce",
+ &nonce),
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &uuid),
+ GNUNET_JSON_spec_fixed_auto ("key_share",
+ &key_share),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jtruth,
+ jspec,
+ NULL, NULL))
+ {
+ tue->tu = ANASTASIS_truth_upload (ANASTASIS_REDUX_ctx_,
+ &id,
+ provider_url,
+ type,
+ instructions,
+ mime_type,
+ &provider_salt,
+ truth_data,
+ truth_data_size,
+ uc->years,
+ uc->timeout,
+ &truth_upload_cb,
+ tue);
+ }
+ else
+ {
+ tue->tu = ANASTASIS_truth_upload2 (ANASTASIS_REDUX_ctx_,
+ &id,
+ provider_url,
+ type,
+ instructions,
+ mime_type,
+ &provider_salt,
+ truth_data,
+ truth_data_size,
+ uc->years,
+ uc->timeout,
+ &nonce,
+ &uuid,
+ &question_salt,
+ &truth_key,
+ &key_share,
+ &truth_upload_cb,
+ tue);
+ }
+ }
+ if (NULL == tue->tu)
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+ }
+}
+
+
+/**
+ * Function to upload truths and recovery document policies.
+ * Ultimately transitions to failed state (allowing user to go back
+ * and change providers/policies), or payment, or finished.
+ *
+ * @param state state to operate on
+ * @param truth_indices indices of truths to upload explicitly
+ * @param cb callback (#ANASTASIS_ActionCallback) to call after upload
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+upload (json_t *state,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct UploadContext *uc;
+ json_t *auth_methods;
+ json_t *policies;
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_absolute_time ("expiration",
+ &expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'expiration' missing");
+ return NULL;
+ }
+ auth_methods = json_object_get (state,
+ "authentication_methods");
+ if ( (! json_is_array (auth_methods)) ||
+ (0 == json_array_size (auth_methods)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must be non-empty array");
+ return NULL;
+ }
+ policies = json_object_get (state,
+ "policies");
+ if ( (! json_is_array (policies)) ||
+ (0 == json_array_size (policies)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be non-empty array");
+ return NULL;
+ }
+
+ uc = GNUNET_new (struct UploadContext);
+ uc->ra.cleanup = &upload_cancel_cb;
+ uc->ra.cleanup_cls = uc;
+ uc->cb = cb;
+ uc->cb_cls = cb_cls;
+ uc->state = json_incref (state);
+ uc->years = expiration_to_years (expiration);
+
+ {
+ json_t *args;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &uc->timeout)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ args = json_object_get (uc->state,
+ "pay-arguments");
+ if ( (NULL != args) &&
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (args,
+ pspec,
+ NULL, NULL)) )
+ {
+ json_dumpf (args,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'timeout' must be valid delay");
+
+ return NULL;
+ }
+ }
+
+ {
+ json_t *policy;
+ size_t pindex;
+ unsigned int async_truth = 0;
+
+ json_array_foreach (policies, pindex, policy)
+ {
+ json_t *methods = json_object_get (policy,
+ "methods");
+ json_t *auth_method;
+ size_t mindex;
+
+ if ( (! json_is_array (methods)) ||
+ (0 == json_array_size (policies)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be non-empty array");
+ upload_cancel_cb (uc);
+ return NULL;
+ }
+ json_array_foreach (methods, mindex, auth_method)
+ {
+ uint32_t am_idx;
+ const char *provider_url;
+ json_t *truth = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("provider",
+ &provider_url),
+ GNUNET_JSON_spec_uint32 ("authentication_method",
+ &am_idx),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("truth",
+ &truth)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (auth_method,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'method' data malformed");
+ upload_cancel_cb (uc);
+ return NULL;
+ }
+ {
+ struct PolicyMethodReference pmr = {
+ .policy_index = pindex,
+ .method_index = mindex
+ };
+ json_t *amj;
+
+ amj = json_array_get (auth_methods,
+ am_idx);
+ if (NULL == amj)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_method' refers to invalid authorization index malformed");
+ upload_cancel_cb (uc);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ }
+ if (NULL == truth)
+ {
+ int ret;
+
+ ret = check_truth_upload (uc,
+ &pmr,
+ provider_url,
+ am_idx,
+ amj);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_JSON_parse_free (spec);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ if (GNUNET_OK == ret)
+ async_truth++;
+ }
+ else
+ {
+ int ret;
+
+ ret = add_truth_object (uc,
+ &pmr,
+ provider_url,
+ am_idx,
+ truth,
+ &async_truth,
+ amj);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_JSON_parse_free (spec);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+ } /* end for all methods of policy */
+ } /* end for all policies */
+ if (async_truth > 0)
+ return &uc->ra;
+ }
+ share_secret (uc);
+ if (NULL == uc->ss)
+ return NULL;
+ return &uc->ra;
+}
+
+
+/**
+ * Test if the core secret @a secret_size is small enough to be stored
+ * at all providers, which have a minimum upload limit of @a min_limit_in_mb.
+ *
+ * For now, we do not precisely calculate the size of the recovery document,
+ * and simply assume that the instructions (i.e. security questions) are all
+ * relatively small (aka sane), and that the number of authentication methods
+ * and recovery policies is similarly small so that all of this meta data
+ * fits in 512 kb (which is VERY big).
+ *
+ * Even with the minimum permitted upload limit of 1 MB (which is likely,
+ * given that there is hardly a reason for providers to offer more), this
+ * leaves 512 kb for the @a secret_size, which should be plenty (given
+ * that this is supposed to be for a master key, and not the actual data).
+ *
+ * @param state our state, could be used in the future to calculate the
+ * size of the recovery document without the core secret
+ * @param secret_size size of the core secret
+ * @param min_limit_in_mb minimum upload size of all providers
+ */
+static bool
+core_secret_fits (const json_t *state,
+ size_t secret_size,
+ uint32_t min_limit_in_mb)
+{
+ return (min_limit_in_mb * 1024LL * 1024LL >
+ 512LLU * 1024LLU + secret_size);
+}
+
+
+/**
+ * Check if the upload size limit is satisfied.
+ *
+ * @param state our state
+ * @param jsecret the uploaded secret
+ * @return #GNUNET_OK if @a secret_size works for all providers,
+ * #GNUNET_NO if the @a secret_size is too big,
+ * #GNUNET_SYSERR if a provider has a limit of 0
+ */
+static enum GNUNET_GenericReturnValue
+check_upload_size_limit (json_t *state,
+ const json_t *jsecret)
+{
+ uint32_t min_limit = UINT32_MAX;
+ json_t *aps = json_object_get (state,
+ "authentication_providers");
+ const char *url;
+ json_t *ap;
+ size_t secret_size;
+
+ {
+ char *secret;
+
+ secret = json_dumps (jsecret,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != secret);
+ secret_size = strlen (secret);
+ GNUNET_free (secret);
+ }
+
+ /* We calculate the minimum upload limit of all possible providers;
+ this is under the (simplified) assumption that we store the
+ recovery document at all providers; this may be changed later,
+ see #6760. */
+ json_object_foreach (aps, url, ap)
+ {
+ uint32_t limit;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &limit),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (ap,
+ spec,
+ NULL, NULL))
+ {
+ /* skip malformed provider, likely /config failed */
+ continue;
+ }
+ if (0 == limit)
+ return GNUNET_SYSERR;
+ min_limit = GNUNET_MIN (min_limit,
+ limit);
+ }
+ if (! core_secret_fits (state,
+ secret_size,
+ min_limit))
+ return GNUNET_NO;
+ return GNUNET_OK;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_secret" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+enter_secret (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *jsecret;
+ struct GNUNET_TIME_Absolute expiration = {0};
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("secret",
+ &jsecret),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_absolute_time ("expiration",
+ &expiration)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'secret' argument required");
+ return NULL;
+ }
+
+ /* check upload size limit */
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = check_upload_size_limit (state,
+ jsecret);
+ switch (ret)
+ {
+ case GNUNET_SYSERR:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider has an upload limit of 0");
+ return NULL;
+ case GNUNET_NO:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_SECRET_TOO_BIG,
+ NULL);
+ return NULL;
+ default:
+ break;
+ }
+ }
+ if (0 != expiration.abs_value_us)
+ {
+ if (GNUNET_OK !=
+ update_expiration_cost (state,
+ expiration))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "could not calculate expiration cost");
+ return NULL;
+ }
+ }
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "core_secret",
+ jsecret));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "clear_secret" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+clear_secret (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ if (0 !=
+ json_object_del (state,
+ "core_secret"))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'core_secret' not set");
+ return NULL;
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for an
+ * "enter_secret_name" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+enter_secret_name (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const char *secret_name = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &secret_name),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'name' argument required");
+ return NULL;
+ }
+
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "secret_name",
+ json_string (secret_name)));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for the
+ * "update_expiration" action in the "secret editing" state.
+ * Updates how long we are to store the truth and policies
+ * and computes the new cost.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL (synchronous operation)
+ */
+static struct ANASTASIS_ReduxAction *
+update_expiration (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_absolute_time ("expiration",
+ &expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'expiration' argument required");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ update_expiration_cost (state,
+ expiration))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "could not calculate expiration cost");
+ return NULL;
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for the
+ * "next" action in the "secret editing" state.
+ * Returns an #ANASTASIS_ReduxAction as operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+finish_secret (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *core_secret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("core_secret",
+ &core_secret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "State parsing failed: 'core_secret' is missing");
+ return NULL;
+ }
+
+ /* check upload size limit */
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = check_upload_size_limit (state,
+ core_secret);
+ switch (ret)
+ {
+ case GNUNET_SYSERR:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider has an upload limit of 0");
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ case GNUNET_NO:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_SECRET_TOO_BIG,
+ NULL);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ default:
+ break;
+ }
+ }
+
+ GNUNET_JSON_parse_free (spec);
+ return upload (state,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "pay" action.
+ * Returns an #ANASTASIS_ReduxAction as operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+pay_truths_backup (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ /* Clear 'payments' if it exists */
+ (void) json_object_del (state,
+ "payments");
+ if (NULL != arguments)
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "pay-arguments",
+ (json_t *) arguments));
+ return upload (state,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "pay" action.
+ * Returns an #ANASTASIS_ReduxAction as operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+pay_policies_backup (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ /* Clear 'policy_payment_requests' if it exists */
+ (void) json_object_del (state,
+ "policy_payment_requests");
+ if (NULL != arguments)
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "pay-arguments",
+ (json_t *) arguments));
+ return upload (state,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "back" action if state is "FINISHED".
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+back_finished (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ set_state (state,
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Signature of callback function that implements a state transition.
+ *
+ * @param state current state
+ * @param arguments arguments for the state transition
+ * @param cb function to call when done
+ * @param cb_cls closure for @a cb
+ */
+typedef struct ANASTASIS_ReduxAction *
+(*DispatchHandler)(json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Operates on a backup state depending on given #ANASTASIS_BackupState
+ * and #ANASTASIS_BackupAction. The new #ANASTASIS_BackupState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param[in,out] state input/output state (to be modified)
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_backup_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct Dispatcher
+ {
+ enum ANASTASIS_BackupState backup_state;
+ const char *backup_action;
+ DispatchHandler fun;
+ } dispatchers[] = {
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "add_authentication",
+ &add_authentication
+ },
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "delete_authentication",
+ &del_authentication
+ },
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "next",
+ &done_authentication
+ },
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "add_provider",
+ &add_provider
+ },
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "add_policy",
+ &add_policy
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "update_policy",
+ &update_policy
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "delete_policy",
+ &del_policy
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "delete_challenge",
+ &del_challenge
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "next",
+ &done_policy_review
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "enter_secret",
+ &enter_secret
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "clear_secret",
+ &clear_secret
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "enter_secret_name",
+ &enter_secret_name
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "update_expiration",
+ &update_expiration
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "next",
+ &finish_secret
+ },
+ {
+ ANASTASIS_BACKUP_STATE_TRUTHS_PAYING,
+ "pay",
+ &pay_truths_backup
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_PAYING,
+ "pay",
+ &pay_policies_backup
+ },
+ {
+ ANASTASIS_BACKUP_STATE_BACKUP_FINISHED,
+ "back",
+ &back_finished
+ },
+ { ANASTASIS_BACKUP_STATE_ERROR, NULL, NULL }
+ };
+ const char *s = json_string_value (json_object_get (state,
+ "backup_state"));
+ enum ANASTASIS_BackupState bs;
+
+ GNUNET_assert (NULL != s); /* holds as per invariant of caller */
+ bs = ANASTASIS_backup_state_from_string_ (s);
+ if (ANASTASIS_BACKUP_STATE_ERROR == bs)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "unknown 'backup_state'");
+ return NULL;
+ }
+ for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
+ {
+ if ( (bs == dispatchers[i].backup_state) &&
+ (0 == strcmp (action,
+ dispatchers[i].backup_action)) )
+ {
+ return dispatchers[i].fun (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ }
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
+ action);
+ return NULL;
+}
+
+
+/**
+ * State for a #ANASTASIS_REDUX_backup_begin_() operation.
+ */
+struct BackupStartState;
+
+
+/**
+ * Entry in the list of all known applicable Anastasis providers.
+ * Used to wait for it to complete downloading /config.
+ */
+struct BackupStartStateProviderEntry
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct BackupStartStateProviderEntry *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct BackupStartStateProviderEntry *prev;
+
+ /**
+ * Main operation this entry is part of.
+ */
+ struct BackupStartState *bss;
+
+ /**
+ * Resulting provider information, NULL if not (yet) available.
+ */
+ json_t *istate;
+
+ /**
+ * Ongoing reducer action to obtain /config, NULL if completed.
+ */
+ struct ANASTASIS_ReduxAction *ra;
+
+ /**
+ * Final result of the operation (once completed).
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+struct BackupStartState
+{
+ /**
+ * Head of list of provider /config operations we are doing.
+ */
+ struct BackupStartStateProviderEntry *pe_head;
+
+ /**
+ * Tail of list of provider /config operations we are doing.
+ */
+ struct BackupStartStateProviderEntry *pe_tail;
+
+ /**
+ * State we are updating.
+ */
+ json_t *state;
+
+ /**
+ * Function to call when we are done.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Redux action we returned to our controller.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Number of provider /config operations in @e ba_head that
+ * are still awaiting completion.
+ */
+ unsigned int pending;
+};
+
+
+/**
+ * The backup start operation is being aborted, terminate.
+ *
+ * @param cls a `struct BackupStartState *`
+ */
+static void
+abort_backup_begin_cb (void *cls)
+{
+ struct BackupStartState *bss = cls;
+ struct BackupStartStateProviderEntry *pe;
+
+ while (NULL != (pe = bss->pe_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (bss->pe_head,
+ bss->pe_tail,
+ pe);
+ if (NULL != pe->ra)
+ pe->ra->cleanup (pe->ra->cleanup_cls);
+ json_decref (pe->istate);
+ GNUNET_free (pe);
+ }
+ json_decref (bss->state);
+ GNUNET_free (bss);
+}
+
+
+/**
+ * We finished downloading /config from all providers, merge
+ * into the main state, trigger the continuation and free our
+ * state.
+ *
+ * @param[in] bss main state to merge into
+ */
+static void
+providers_complete (struct BackupStartState *bss)
+{
+ struct BackupStartStateProviderEntry *pe;
+ json_t *tlist;
+
+ tlist = json_object_get (bss->state,
+ "authentication_providers");
+ if (NULL == tlist)
+ {
+ tlist = json_object ();
+ GNUNET_assert (NULL != tlist);
+ GNUNET_assert (0 ==
+ json_object_set_new (bss->state,
+ "authentication_providers",
+ tlist));
+ }
+ while (NULL != (pe = bss->pe_head))
+ {
+ json_t *provider_list;
+
+ GNUNET_CONTAINER_DLL_remove (bss->pe_head,
+ bss->pe_tail,
+ pe);
+ provider_list = json_object_get (pe->istate,
+ "authentication_providers");
+ /* merge provider_list into tlist (overriding existing entries) */
+ if (NULL != provider_list)
+ {
+ const char *url;
+ json_t *value;
+
+ json_object_foreach (provider_list, url, value) {
+ GNUNET_assert (0 ==
+ json_object_set (tlist,
+ url,
+ value));
+ }
+ }
+ json_decref (pe->istate);
+ GNUNET_free (pe);
+ }
+ bss->cb (bss->cb_cls,
+ TALER_EC_NONE,
+ bss->state);
+ json_decref (bss->state);
+ GNUNET_free (bss);
+}
+
+
+/**
+ * Function called when the complete information about a provider
+ * was added to @a new_state.
+ *
+ * @param cls a `struct BackupStartStateProviderEntry`
+ * @param error error code
+ * @param new_state resulting new state
+ */
+static void
+provider_added_cb (void *cls,
+ enum TALER_ErrorCode error,
+ json_t *new_state)
+{
+ struct BackupStartStateProviderEntry *pe = cls;
+
+ pe->ra = NULL;
+ pe->istate = json_incref (new_state);
+ pe->ec = error;
+ pe->bss->pending--;
+ if (0 == pe->bss->pending)
+ providers_complete (pe->bss);
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_backup_begin_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *provider_list;
+ struct BackupStartState *bss;
+
+ provider_list = json_object_get (state,
+ "authentication_providers");
+ if (NULL == provider_list)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' missing");
+ return NULL;
+ }
+ bss = GNUNET_new (struct BackupStartState);
+ bss->state = json_incref (state);
+ bss->cb = cb;
+ bss->cb_cls = cb_cls;
+ bss->ra.cleanup_cls = bss;
+ bss->ra.cleanup = &abort_backup_begin_cb;
+ bss->pending = 1; /* decremented after initialization loop */
+
+ {
+ json_t *prov;
+ const char *url;
+ json_object_foreach (provider_list, url, prov) {
+ struct BackupStartStateProviderEntry *pe;
+ json_t *istate;
+
+ pe = GNUNET_new (struct BackupStartStateProviderEntry);
+ pe->bss = bss;
+ istate = json_object ();
+ GNUNET_assert (NULL != istate);
+ GNUNET_CONTAINER_DLL_insert (bss->pe_head,
+ bss->pe_tail,
+ pe);
+ pe->ra = ANASTASIS_REDUX_add_provider_to_state_ (url,
+ istate,
+ &provider_added_cb,
+ pe);
+ json_decref (istate);
+ if (NULL != pe->ra)
+ bss->pending++;
+ }
+ }
+ bss->pending--;
+ if (0 == bss->pending)
+ {
+ providers_complete (bss);
+ return NULL;
+ }
+ return &bss->ra;
+}
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
new file mode 100644
index 0000000..8a900ec
--- /dev/null
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -0,0 +1,2558 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 lib/anastasis_api_recovery_redux.c
+ * @brief anastasis reducer recovery api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+
+#include <platform.h>
+#include <jansson.h>
+#include "anastasis_redux.h"
+#include "anastasis_error_codes.h"
+#include "anastasis_api_redux.h"
+
+
+#define GENERATE_STRING(STRING) #STRING,
+static const char *recovery_strings[] = {
+ ANASTASIS_RECOVERY_STATES (GENERATE_STRING)
+};
+#undef GENERATE_STRING
+
+
+enum ANASTASIS_RecoveryState
+ANASTASIS_recovery_state_from_string_ (const char *state_string)
+{
+ for (enum ANASTASIS_RecoveryState i = 0;
+ i < sizeof (recovery_strings) / sizeof(*recovery_strings);
+ i++)
+ if (0 == strcmp (state_string,
+ recovery_strings[i]))
+ return i;
+ return ANASTASIS_RECOVERY_STATE_ERROR;
+}
+
+
+const char *
+ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs)
+{
+ if ( (rs < 0) ||
+ (rs >= sizeof (recovery_strings) / sizeof(*recovery_strings)) )
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return recovery_strings[rs];
+}
+
+
+static void
+set_state (json_t *state,
+ enum ANASTASIS_RecoveryState new_recovery_state)
+{
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (
+ state,
+ "recovery_state",
+ json_string (ANASTASIS_recovery_state_to_string_ (new_recovery_state))));
+}
+
+
+/**
+ * Returns an initial ANASTASIS recovery state.
+ *
+ * @return NULL on failure
+ */
+json_t *
+ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ json_t *initial_state;
+
+ (void) cfg;
+ initial_state = ANASTASIS_REDUX_load_continents_ ();
+ if (NULL == initial_state)
+ return NULL;
+ set_state (initial_state,
+ ANASTASIS_RECOVERY_STATE_CONTINENT_SELECTING);
+ return initial_state;
+}
+
+
+/**
+ * Context for a "select_challenge" operation.
+ */
+struct SelectChallengeContext
+{
+ /**
+ * Handle we returned for cancellation of the operation.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * UUID of the challenge selected by the user for solving.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+
+ /**
+ * Which timeout was set for the operation?
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Overall recovery action.
+ */
+ struct ANASTASIS_Recovery *r;
+
+ /**
+ * Function to call with the next state.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Our state.
+ */
+ json_t *state;
+
+ /**
+ * Our arguments (like answers to the challenge, if already provided).
+ */
+ json_t *args;
+
+ /**
+ * Task scheduled for delayed success reporting. Needed to make
+ * sure that the solved challenge was really the final result,
+ * cancelled if the solved challenge resulted in the secret being
+ * recovered.
+ */
+ struct GNUNET_SCHEDULER_Task *delayed_report;
+
+ /**
+ * Payment secret, if we are in the "pay" state.
+ */
+ struct ANASTASIS_PaymentSecretP ps;
+};
+
+
+/**
+ * Cleanup a select challenge context.
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ */
+static void
+sctx_free (void *cls)
+{
+ struct SelectChallengeContext *sctx = cls;
+
+ if (NULL != sctx->r)
+ {
+ ANASTASIS_recovery_abort (sctx->r);
+ sctx->r = NULL;
+ }
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ if (NULL != sctx->delayed_report)
+ {
+ GNUNET_SCHEDULER_cancel (sctx->delayed_report);
+ sctx->delayed_report = NULL;
+ }
+ GNUNET_free (sctx);
+}
+
+
+/**
+ * Update @a state to reflect the error provided in @a rc.
+ *
+ * @param[in,out] state state to update
+ * @param rc error code to translate to JSON
+ * @return error code to use
+ */
+static enum TALER_ErrorCode
+update_state_by_error (json_t *state,
+ enum ANASTASIS_RecoveryStatus rc)
+{
+ const char *msg = NULL;
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+
+ switch (rc)
+ {
+ case ANASTASIS_RS_SUCCESS:
+ GNUNET_assert (0);
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_FAILED:
+ msg = gettext_noop ("download failed due to unexpected network issue");
+ ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED;
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY:
+ GNUNET_break (0);
+ msg = gettext_noop ("policy document returned was malformed");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG:
+ GNUNET_break (0);
+ msg = gettext_noop ("policy document too large for client memory");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION:
+ GNUNET_break (0);
+ msg = gettext_noop ("failed to decompress policy document");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON:
+ GNUNET_break (0);
+ msg = gettext_noop ("policy document returned was not in JSON format");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_MALFORMED_JSON:
+ GNUNET_break (0);
+ msg = gettext_noop (
+ "policy document returned was not in required JSON format");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_SERVER_ERROR:
+ msg = gettext_noop ("Anastasis server reported transient internal error");
+ ec = TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED;
+ break;
+ case ANASTASIS_RS_POLICY_GONE:
+ msg = gettext_noop ("policy document no longer exists");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
+ break;
+ case ANASTASIS_RS_POLICY_UNKNOWN:
+ msg = gettext_noop ("account unknown to Anastasis server");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
+ break;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "error_message",
+ json_string (msg)));
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "error_code",
+ json_integer (rc)));
+ set_state (state,
+ ANASTASIS_GENERIC_STATE_ERROR);
+ return ec;
+}
+
+
+/**
+ * This function is called whenever the recovery process ends.
+ * On success, the secret is returned in @a secret.
+ *
+ * @param cls handle for the callback
+ * @param ec error code
+ * @param secret contains the core secret which is passed to the user
+ * @param secret_size defines the size of the core secret
+ */
+static void
+core_secret_cb (void *cls,
+ enum ANASTASIS_RecoveryStatus rc,
+ const void *secret,
+ size_t secret_size)
+{
+ struct SelectChallengeContext *sctx = cls;
+ enum TALER_ErrorCode ec;
+
+ sctx->r = NULL;
+ if (ANASTASIS_RS_SUCCESS == rc)
+ {
+ json_t *jsecret;
+
+ jsecret = json_loadb (secret,
+ secret_size,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == jsecret)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_SECRET_MALFORMED,
+ NULL);
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (sctx->state,
+ "core_secret",
+ jsecret));
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_RECOVERY_FINISHED);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ }
+ ec = update_state_by_error (sctx->state,
+ rc);
+ sctx->cb (sctx->cb_cls,
+ ec,
+ sctx->state);
+ sctx_free (sctx);
+}
+
+
+/**
+ * A challenge was solved, but we are not yet finished.
+ * Report to caller that the challenge was completed.
+ *
+ * @param cls a `struct SelectChallengeContext`
+ */
+static void
+report_solved (void *cls)
+{
+ struct SelectChallengeContext *sctx = cls;
+
+ sctx->delayed_report = NULL;
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+}
+
+
+/**
+ * Find challenge of @a uuid in @a state under "recovery_information".
+ *
+ * @param state the state to search
+ * @param uuid the UUID to search for
+ * @return NULL on error, otherwise challenge entry; RC is NOT incremented
+ */
+static json_t *
+find_challenge_in_ri (json_t *state,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
+{
+ struct ANASTASIS_CRYPTO_TruthUUIDP u;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &u),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *ri;
+ json_t *challenges;
+ json_t *challenge;
+ size_t index;
+
+ ri = json_object_get (state,
+ "recovery_information");
+ if (NULL == ri)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ challenges = json_object_get (ri,
+ "challenges");
+ if (NULL == challenges)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ json_array_foreach (challenges, index, challenge)
+ {
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (challenge,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (0 ==
+ GNUNET_memcmp (&u,
+ uuid))
+ {
+ return challenge;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Defines a callback for the response status for a challenge start
+ * operation.
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param csr response details
+ */
+static void
+answer_feedback_cb (
+ void *cls,
+ const struct ANASTASIS_ChallengeStartResponse *csr)
+{
+ struct SelectChallengeContext *sctx = cls;
+ const struct ANASTASIS_ChallengeDetails *cd;
+ char uuid[sizeof (cd->uuid) * 2];
+ char *end;
+ json_t *feedback;
+
+ cd = ANASTASIS_challenge_get_details (csr->challenge);
+ end = GNUNET_STRINGS_data_to_string (&cd->uuid,
+ sizeof (cd->uuid),
+ uuid,
+ sizeof (uuid));
+ GNUNET_assert (NULL != end);
+ *end = '\0';
+ feedback = json_object_get (sctx->state,
+ "challenge_feedback");
+ if (NULL == feedback)
+ {
+ feedback = json_object ();
+ GNUNET_assert (0 ==
+ json_object_set_new (sctx->state,
+ "challenge_feedback",
+ feedback));
+ }
+ switch (csr->cs)
+ {
+ case ANASTASIS_CHALLENGE_STATUS_SOLVED:
+ {
+ json_t *rd;
+
+ rd = ANASTASIS_recovery_serialize (sctx->r);
+ if (NULL == rd)
+ {
+ GNUNET_break (0);
+ set_state (sctx->state,
+ ANASTASIS_GENERIC_STATE_ERROR);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (sctx->state,
+ "recovery_document",
+ rd));
+ }
+ {
+ json_t *solved;
+
+ solved = json_pack ("{s:s}",
+ "state",
+ "solved");
+ GNUNET_assert (NULL != solved);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ solved));
+ }
+ /* Delay reporting challenge success, as we MAY still
+ also see a secret recovery success (and we can only
+ call the callback once) */
+ sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved,
+ sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS:
+ {
+ json_t *instructions;
+ json_t *val;
+ const char *mime;
+
+ mime = csr->details.open_challenge.content_type;
+ if (NULL != mime)
+ {
+ if ( (0 == strcasecmp (mime,
+ "text/plain")) ||
+ (0 == strcasecmp (mime,
+ "text/utf8")) )
+ {
+ char *s = GNUNET_strndup (csr->details.open_challenge.body,
+ csr->details.open_challenge.body_size);
+
+ instructions = json_pack (
+ "{s:s, s:s, s:I}",
+ "state",
+ "hint",
+ "hint",
+ s,
+ "http_status",
+ (json_int_t) csr->details.open_challenge.http_status);
+ GNUNET_free (s);
+ }
+ else if (0 == strcasecmp (mime,
+ "application/json"))
+ {
+ json_t *body;
+
+ body = json_loadb (csr->details.open_challenge.body,
+ csr->details.open_challenge.body_size,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == body)
+ {
+ GNUNET_break_op (0);
+ mime = NULL;
+ }
+ else
+ {
+ instructions = json_pack (
+ "{s:s, s:o, s:I}",
+ "state",
+ "details",
+ "details",
+ body,
+ "http_status",
+ (json_int_t) csr->details.open_challenge.http_status);
+ }
+ }
+ else
+ {
+ /* unexpected / unsupported mime type */
+ mime = NULL;
+ }
+ }
+ if (NULL == mime)
+ {
+ val = GNUNET_JSON_from_data (csr->details.open_challenge.body,
+ csr->details.open_challenge.body_size);
+ GNUNET_assert (NULL != val);
+ instructions = json_pack (
+ "{s:s, s:o, s:I, s:s?}",
+ "state",
+ "body",
+ "body",
+ val,
+ "http_status",
+ (json_int_t) csr->details.open_challenge.http_status,
+ "mime_type",
+ mime);
+ }
+ GNUNET_assert (NULL != instructions);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ instructions));
+ }
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION:
+ {
+ json_t *redir;
+
+ redir = json_pack ("{s:s, s:s}",
+ "state",
+ "redirect",
+ "redirect_url",
+ csr->details.redirect_url);
+ GNUNET_assert (NULL != redir);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ redir));
+ }
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED:
+ {
+ json_t *pay;
+
+ pay = json_pack ("{s:s, s:s, s:s, s:o}",
+ "state",
+ "payment",
+ "taler_pay_uri",
+ csr->details.payment_required.taler_pay_uri,
+ "provider",
+ cd->provider_url,
+ "payment_secret",
+ GNUNET_JSON_from_data_auto (
+ &csr->details.payment_required.payment_secret));
+ GNUNET_assert (NULL != pay);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ pay));
+ }
+ /* Remember payment secret for later (once application claims it paid) */
+ {
+ json_t *challenge = find_challenge_in_ri (sctx->state,
+ &cd->uuid);
+
+ GNUNET_assert (NULL != challenge);
+ GNUNET_assert (0 ==
+ json_object_set_new (challenge,
+ "payment_secret",
+ GNUNET_JSON_from_data_auto (
+ &csr->details.payment_required.
+ payment_secret)));
+ }
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE:
+ {
+ json_t *err;
+
+ err = json_pack ("{s:s, s:I, s:I}",
+ "state",
+ "server-failure",
+ "http_status",
+ (json_int_t) csr->details.server_failure.http_status,
+ "error_code",
+ (json_int_t) csr->details.server_failure.ec);
+ GNUNET_assert (NULL != err);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ err));
+ }
+ GNUNET_break_op (0);
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN:
+ {
+ json_t *err;
+
+ err = json_pack ("{s:s, s:I}",
+ "state",
+ "truth-unknown",
+ "error_code",
+ (json_int_t) TALER_EC_ANASTASIS_TRUTH_UNKNOWN);
+ GNUNET_assert (NULL != err);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ err));
+ }
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED:
+ {
+ json_t *err;
+
+ err = json_pack ("{s:s, s:I}",
+ "state",
+ "rate-limit-exceeded",
+ "error_code",
+ (json_int_t) TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED);
+
+ GNUNET_assert (NULL != err);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ err));
+ }
+ GNUNET_break_op (0);
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ sctx_free (sctx);
+}
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * We find the selected challenge and try to answer it (or begin
+ * the process).
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+solve_challenge_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct SelectChallengeContext *sctx = cls;
+ const struct ANASTASIS_PaymentSecretP *psp = NULL;
+ struct ANASTASIS_PaymentSecretP ps;
+ struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
+ struct GNUNET_JSON_Specification tspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &timeout)),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("payment_secret",
+ &ps),
+ GNUNET_JSON_spec_end ()
+ };
+
+ json_t *challenge;
+
+ if (NULL == ri)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "recovery information could not be deserialized");
+ sctx_free (sctx);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ tspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'timeout' malformed");
+ sctx_free (sctx);
+ return;
+ }
+
+ /* Check if we got a payment_secret */
+ challenge = find_challenge_in_ri (sctx->state,
+ &sctx->uuid);
+ if (NULL == challenge)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "challenge not found");
+ sctx_free (sctx);
+ return;
+ }
+
+ if (NULL !=
+ json_object_get (sctx->args,
+ "payment_secret"))
+ {
+ /* check if we got payment secret in args */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'payment_secret' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ psp = &ps;
+ }
+ else if (NULL !=
+ json_object_get (challenge,
+ "payment_secret"))
+ {
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (challenge,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'payment_secret' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ psp = &ps;
+ }
+
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *ci = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ int ret;
+
+ cd = ANASTASIS_challenge_get_details (ci);
+ if (0 !=
+ GNUNET_memcmp (&sctx->uuid,
+ &cd->uuid))
+ continue;
+ if (cd->solved)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "Selected challenge already solved");
+ sctx_free (sctx);
+ return;
+ }
+ if (0 == strcmp ("question",
+ cd->type))
+ {
+ /* security question, answer must be a string */
+ json_t *janswer = json_object_get (sctx->args,
+ "answer");
+ const char *answer = json_string_value (janswer);
+
+ if (NULL == answer)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'answer' missing");
+ sctx_free (sctx);
+ return;
+ }
+ /* persist answer, in case payment is required */
+ GNUNET_assert (0 ==
+ json_object_set (challenge,
+ "answer",
+ janswer));
+ ret = ANASTASIS_challenge_answer (ci,
+ psp,
+ timeout,
+ answer,
+ &answer_feedback_cb,
+ sctx);
+ }
+ else
+ {
+ /* Check if we got a PIN or a HASH */
+ json_t *pin = json_object_get (sctx->args,
+ "pin");
+ json_t *hash = json_object_get (sctx->args,
+ "hash");
+ if (json_is_integer (pin))
+ {
+ uint64_t ianswer = json_integer_value (pin);
+
+ ret = ANASTASIS_challenge_answer2 (ci,
+ psp,
+ timeout,
+ ianswer,
+ &answer_feedback_cb,
+ sctx);
+ }
+ else if (NULL != hash)
+ {
+ struct GNUNET_HashCode hashed_answer;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("hash",
+ &hashed_answer),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'answer' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ ret = ANASTASIS_challenge_start (ci,
+ psp,
+ timeout,
+ &hashed_answer,
+ &answer_feedback_cb,
+ sctx);
+ }
+ else
+ {
+ /* no answer provided */
+ ret = ANASTASIS_challenge_start (ci,
+ psp,
+ timeout,
+ NULL, /* no answer */
+ &answer_feedback_cb,
+ sctx);
+ }
+ }
+ if (GNUNET_OK != ret)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin answering challenge");
+ sctx_free (sctx);
+ return;
+ }
+ return; /* await answer feedback */
+ }
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'uuid' not in list of challenges");
+ sctx_free (sctx);
+}
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * We find the selected challenge and try to answer it (or begin
+ * the process).
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+pay_challenge_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct SelectChallengeContext *sctx = cls;
+ json_t *challenge;
+
+ if (NULL == ri)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "recovery information could not be deserialized");
+ sctx_free (sctx);
+ return;
+ }
+
+ challenge = find_challenge_in_ri (sctx->state,
+ &sctx->uuid);
+ if (NULL == challenge)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "challenge not found");
+ sctx_free (sctx);
+ return;
+ }
+ /* persist payment, in case we need to run the request again */
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (challenge,
+ "payment_secret",
+ GNUNET_JSON_from_data_auto (&sctx->ps)));
+
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *ci = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ int ret;
+
+ cd = ANASTASIS_challenge_get_details (ci);
+ if (0 !=
+ GNUNET_memcmp (&sctx->uuid,
+ &cd->uuid))
+ continue;
+ if (cd->solved)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "Selected challenge already solved");
+ sctx_free (sctx);
+ return;
+ }
+
+ if (0 == strcmp ("question",
+ cd->type))
+ {
+ /* security question, answer must be a string and already ready */
+ json_t *janswer = json_object_get (challenge,
+ "answer");
+ const char *answer = json_string_value (janswer);
+
+ if (NULL == answer)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'answer' missing");
+ sctx_free (sctx);
+ return;
+ }
+ ret = ANASTASIS_challenge_answer (ci,
+ &sctx->ps,
+ sctx->timeout,
+ answer,
+ &answer_feedback_cb,
+ sctx);
+ }
+ else
+ {
+ ret = ANASTASIS_challenge_start (ci,
+ &sctx->ps,
+ sctx->timeout,
+ NULL, /* no answer yet */
+ &answer_feedback_cb,
+ sctx);
+ }
+ if (GNUNET_OK != ret)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin answering challenge");
+ sctx_free (sctx);
+ return;
+ }
+ return; /* await answer feedback */
+ }
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'uuid' not in list of challenges");
+ sctx_free (sctx);
+}
+
+
+/**
+ * The user selected a challenge to be solved. Begin the solving
+ * process.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+solve_challenge (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct SelectChallengeContext *sctx
+ = GNUNET_new (struct SelectChallengeContext);
+ json_t *rd;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
+ &sctx->uuid),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'selected_challenge_uuid' missing");
+ return NULL;
+ }
+ rd = json_object_get (state,
+ "recovery_document");
+ if (NULL == rd)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "solve_challenge");
+ return NULL;
+ }
+ sctx->cb = cb;
+ sctx->cb_cls = cb_cls;
+ sctx->state = json_incref (state);
+ sctx->args = json_incref ((json_t*) arguments);
+ sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
+ rd,
+ &solve_challenge_cb,
+ sctx,
+ &core_secret_cb,
+ sctx);
+ if (NULL == sctx->r)
+ {
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ GNUNET_free (sctx);
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_document' invalid");
+ return NULL;
+ }
+ sctx->ra.cleanup = &sctx_free;
+ sctx->ra.cleanup_cls = sctx;
+ return &sctx->ra;
+}
+
+
+/**
+ * The user selected a challenge to be solved. Handle the payment
+ * process.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+pay_challenge (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct SelectChallengeContext *sctx
+ = GNUNET_new (struct SelectChallengeContext);
+ json_t *rd;
+ struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
+ &sctx->uuid),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification aspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &timeout)),
+ GNUNET_JSON_spec_fixed_auto ("payment_secret",
+ &sctx->ps),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ aspec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'payment_secret' missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'selected_challenge_uuid' missing");
+ return NULL;
+ }
+ rd = json_object_get (state,
+ "recovery_document");
+ if (NULL == rd)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "pay_challenge");
+ return NULL;
+ }
+ sctx->timeout = timeout;
+ sctx->cb = cb;
+ sctx->cb_cls = cb_cls;
+ sctx->state = json_incref (state);
+ sctx->args = json_incref ((json_t*) arguments);
+ sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
+ rd,
+ &pay_challenge_cb,
+ sctx,
+ &core_secret_cb,
+ sctx);
+ if (NULL == sctx->r)
+ {
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ GNUNET_free (sctx);
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_document' invalid");
+ return NULL;
+ }
+ sctx->ra.cleanup = &sctx_free;
+ sctx->ra.cleanup_cls = sctx;
+ return &sctx->ra;
+}
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * We find the selected challenge and try to answer it (or begin
+ * the process).
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+select_challenge_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct SelectChallengeContext *sctx = cls;
+ const struct ANASTASIS_PaymentSecretP *psp = NULL;
+ struct ANASTASIS_PaymentSecretP ps;
+ struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
+ struct GNUNET_JSON_Specification tspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &timeout)),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("payment_secret",
+ &ps),
+ GNUNET_JSON_spec_end ()
+ };
+
+
+ if (NULL == ri)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "recovery information could not be deserialized");
+ sctx_free (sctx);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ tspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'timeout' malformed");
+ sctx_free (sctx);
+ return;
+ }
+
+ /* NOTE: do we need both ways to pass payment secrets? */
+ if (NULL !=
+ json_object_get (sctx->args,
+ "payment_secret"))
+ {
+ /* check if we got payment secret in args */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'payment_secret' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ psp = &ps;
+ }
+ else
+ {
+ /* Check if we got a payment_secret in state */
+ json_t *challenge = find_challenge_in_ri (sctx->state,
+ &sctx->uuid);
+
+ if (NULL == challenge)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "challenge not found");
+ sctx_free (sctx);
+ return;
+ }
+ if (NULL !=
+ json_object_get (challenge,
+ "payment_secret"))
+ {
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (challenge,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'payment_secret' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ psp = &ps;
+ }
+ }
+
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *ci = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ int ret;
+
+ cd = ANASTASIS_challenge_get_details (ci);
+ if (0 !=
+ GNUNET_memcmp (&sctx->uuid,
+ &cd->uuid))
+ continue;
+ if (cd->solved)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "Selected challenge already solved");
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (sctx->state,
+ "selected_challenge_uuid",
+ GNUNET_JSON_from_data_auto (&cd->uuid)));
+ if (0 == strcmp ("question",
+ cd->type))
+ {
+ /* security question, immediately request user to answer it */
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ }
+ /* trigger challenge */
+ ret = ANASTASIS_challenge_start (ci,
+ psp,
+ timeout,
+ NULL, /* no answer */
+ &answer_feedback_cb,
+ sctx);
+ if (GNUNET_OK != ret)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin answering challenge");
+ sctx_free (sctx);
+ return;
+ }
+ return; /* await answer feedback */
+ }
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'uuid' not in list of challenges");
+ sctx_free (sctx);
+}
+
+
+/**
+ * The user selected a challenge to be solved. Begin the solving
+ * process.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+select_challenge (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct SelectChallengeContext *sctx
+ = GNUNET_new (struct SelectChallengeContext);
+ json_t *rd;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &sctx->uuid),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'uuid' missing");
+ return NULL;
+ }
+ rd = json_object_get (state,
+ "recovery_document");
+ if (NULL == rd)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "select_challenge");
+ return NULL;
+ }
+ sctx->cb = cb;
+ sctx->cb_cls = cb_cls;
+ sctx->state = json_incref (state);
+ sctx->args = json_incref ((json_t*) arguments);
+ sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
+ rd,
+ &select_challenge_cb,
+ sctx,
+ &core_secret_cb,
+ sctx);
+ if (NULL == sctx->r)
+ {
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ GNUNET_free (sctx);
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_document' invalid");
+ return NULL;
+ }
+ sctx->ra.cleanup = &sctx_free;
+ sctx->ra.cleanup_cls = sctx;
+ return &sctx->ra;
+}
+
+
+/**
+ * The user pressed "back" during challenge solving.
+ * Transition back to selecting another challenge.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments (unused)
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return NULL (synchronous operation)
+ */
+static struct ANASTASIS_ReduxAction *
+back_challenge_solving (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ (void) arguments;
+ GNUNET_assert (0 ==
+ json_object_del (state,
+ "selected_challenge_uuid"));
+ set_state (state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * The user wants us to change the policy version. Download another version.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+change_version (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ uint64_t version;
+ const char *provider_url;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("version",
+ &version),
+ GNUNET_JSON_spec_string ("provider_url",
+ &provider_url),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *ia;
+ json_t *args;
+ struct ANASTASIS_ReduxAction *ra;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'version' invalid");
+ return NULL;
+ }
+ GNUNET_assert (NULL != provider_url);
+ ia = json_object_get (state,
+ "identity_attributes");
+ if (NULL == ia)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'identity_attributes' missing");
+ return NULL;
+ }
+ args = json_pack ("{s:I, s:O, s:s}",
+ "version",
+ (json_int_t) version,
+ "identity_attributes",
+ ia,
+ "provider_url",
+ provider_url);
+ if (NULL == args)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ ra = ANASTASIS_REDUX_recovery_challenge_begin_ (state,
+ args,
+ cb,
+ cb_cls);
+ json_decref (args);
+ return ra;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "next" action in "secret_selecting" state.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+done_secret_selecting (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *ri;
+
+ ri = json_object_get (state,
+ "recovery_information");
+ if ( (NULL == ri) ||
+ (NULL == json_object_get (ri,
+ "challenges")) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "no valid version selected");
+ return NULL;
+ }
+ set_state (state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Signature of callback function that implements a state transition.
+ *
+ * @param state current state
+ * @param arguments arguments for the state transition
+ * @param cb function to call when done
+ * @param cb_cls closure for @a cb
+ */
+typedef struct ANASTASIS_ReduxAction *
+(*DispatchHandler)(json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Operates on a recovery state depending on given #ANASTASIS_RecoveryState
+ * and #ANASTASIS_RecoveryAction. The new #ANASTASIS_RecoveryState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param[in,out] state input/output state (to be modified)
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_recovery_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct Dispatcher
+ {
+ enum ANASTASIS_RecoveryState recovery_state;
+ const char *recovery_action;
+ DispatchHandler fun;
+ } dispatchers[] = {
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "change_version",
+ &change_version
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "next",
+ &done_secret_selecting
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
+ "select_challenge",
+ &select_challenge
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING,
+ "pay",
+ &pay_challenge
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING,
+ "solve_challenge",
+ &solve_challenge
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING,
+ "back",
+ &back_challenge_solving
+ },
+ { ANASTASIS_RECOVERY_STATE_ERROR, NULL, NULL }
+ };
+ const char *s = json_string_value (json_object_get (state,
+ "recovery_state"));
+ enum ANASTASIS_RecoveryState rs;
+
+ GNUNET_assert (NULL != s);
+ rs = ANASTASIS_recovery_state_from_string_ (s);
+ if (ANASTASIS_RECOVERY_STATE_ERROR == rs)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_state' field invalid");
+ return NULL;
+ }
+ for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
+ {
+ if ( (rs == dispatchers[i].recovery_state) &&
+ (0 == strcmp (action,
+ dispatchers[i].recovery_action)) )
+ {
+ return dispatchers[i].fun (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ }
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
+ action);
+ return NULL;
+}
+
+
+/**
+ * State for a "recover secret" CMD.
+ */
+struct RecoverSecretState;
+
+
+/**
+ * State for a "policy download" as part of a recovery operation.
+ */
+struct PolicyDownloadEntry
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PolicyDownloadEntry *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PolicyDownloadEntry *next;
+
+ /**
+ * Backend we are querying.
+ */
+ char *backend_url;
+
+ /**
+ * Salt to be used to derive the id for this provider
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+
+ /**
+ * Context we operate in.
+ */
+ struct RecoverSecretState *rss;
+
+ /**
+ * The /policy GET operation handle.
+ */
+ struct ANASTASIS_Recovery *recovery;
+
+};
+
+
+/**
+ * Entry in the list of all known applicable Anastasis providers.
+ * Used to wait for it to complete downloading /config.
+ */
+struct RecoveryStartStateProviderEntry
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct RecoveryStartStateProviderEntry *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct RecoveryStartStateProviderEntry *prev;
+
+ /**
+ * Main operation this entry is part of.
+ */
+ struct RecoverSecretState *rss;
+
+ /**
+ * Resulting provider information, NULL if not (yet) available.
+ */
+ json_t *istate;
+
+ /**
+ * Ongoing reducer action to obtain /config, NULL if completed.
+ */
+ struct ANASTASIS_ReduxAction *ra;
+
+ /**
+ * Final result of the operation (once completed).
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+/**
+ * State for a "recover secret" CMD.
+ */
+struct RecoverSecretState
+{
+
+ /**
+ * Redux action handle associated with this state.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Head of list of provider /config operations we are doing.
+ */
+ struct RecoveryStartStateProviderEntry *pe_head;
+
+ /**
+ * Tail of list of provider /config operations we are doing.
+ */
+ struct RecoveryStartStateProviderEntry *pe_tail;
+
+ /**
+ * Identification data from the user
+ */
+ json_t *id_data;
+
+ /**
+ * Head of DLL of policy downloads.
+ */
+ struct PolicyDownloadEntry *pd_head;
+
+ /**
+ * Tail of DLL of policy downloads.
+ */
+ struct PolicyDownloadEntry *pd_tail;
+
+ /**
+ * Reference to our state.
+ */
+ json_t *state;
+
+ /**
+ * callback to call during/after operation
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * closure for action callback @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set if recovery must be done with this provider.
+ */
+ char *provider_url;
+
+ /**
+ * version of the recovery document to request.
+ */
+ unsigned int version;
+
+ /**
+ * Number of provider /config operations in @e ba_head that
+ * are still awaiting completion.
+ */
+ unsigned int pending;
+
+ /**
+ * Is @e version set?
+ */
+ bool have_version;
+};
+
+
+/**
+ * Function to free a #RecoverSecretState.
+ *
+ * @param cls closure for a #RecoverSecretState.
+ */
+static void
+free_rss (void *cls)
+{
+ struct RecoverSecretState *rss = cls;
+ struct PolicyDownloadEntry *pd;
+ struct RecoveryStartStateProviderEntry *pe;
+
+ while (NULL != (pe = rss->pe_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (rss->pe_head,
+ rss->pe_tail,
+ pe);
+ ANASTASIS_redux_action_cancel (pe->ra);
+ rss->pending--;
+ GNUNET_free (pe);
+ }
+ while (NULL != (pd = rss->pd_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (rss->pd_head,
+ rss->pd_tail,
+ pd);
+ if (NULL != pd->recovery)
+ {
+ ANASTASIS_recovery_abort (pd->recovery);
+ pd->recovery = NULL;
+ }
+ GNUNET_free (pd->backend_url);
+ GNUNET_free (pd);
+ }
+ json_decref (rss->state);
+ json_decref (rss->id_data);
+ GNUNET_assert (0 == rss->pending);
+ GNUNET_free (rss->provider_url);
+ GNUNET_free (rss);
+}
+
+
+/**
+ * This function is called whenever the recovery process ends.
+ * In this case, that should not be possible as this callback
+ * is used before we even begin with the challenges. So if
+ * we are called, it is because of some fatal error.
+ *
+ * @param cls a `struct PolicyDownloadEntry`
+ * @param ec error code
+ * @param secret contains the core secret which is passed to the user
+ * @param secret_size defines the size of the core secret
+ */
+static void
+core_early_secret_cb (void *cls,
+ enum ANASTASIS_RecoveryStatus rc,
+ const void *secret,
+ size_t secret_size)
+{
+ struct PolicyDownloadEntry *pd = cls;
+ struct RecoverSecretState *rss = pd->rss;
+ enum TALER_ErrorCode ec;
+
+ pd->recovery = NULL;
+ GNUNET_assert (NULL == secret);
+ GNUNET_CONTAINER_DLL_remove (rss->pd_head,
+ rss->pd_tail,
+ pd);
+ GNUNET_free (pd->backend_url);
+ GNUNET_free (pd);
+ if (NULL != rss->pd_head)
+ return; /* wait for another one */
+ /* all failed! report failure! */
+ GNUNET_assert (ANASTASIS_RS_SUCCESS != rc);
+ ec = update_state_by_error (rss->state,
+ rc);
+ rss->cb (rss->cb_cls,
+ ec,
+ rss->state);
+ rss->cb = NULL;
+ free_rss (rss);
+}
+
+
+/**
+ * Determine recovery @a cost of solving a challenge of type @a type
+ * at @a provider_url by inspecting @a state.
+ *
+ * @param state the state to inspect
+ * @param provider_url the provider to lookup config info from
+ * @param type the method to lookup the cost of
+ * @param[out] cost the recovery cost to return
+ * @return #GNUNET_OK on success, #GNUNET_NO if not found, #GNUNET_SYSERR on state error
+ */
+static int
+lookup_cost (const json_t *state,
+ const char *provider_url,
+ const char *type,
+ struct TALER_Amount *cost)
+{
+ const json_t *providers;
+ const json_t *provider;
+ const json_t *methods;
+
+ providers = json_object_get (state,
+ "authentication_providers");
+ if (NULL == providers)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ provider = json_object_get (providers,
+ provider_url);
+ if (NULL == provider)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ methods = json_object_get (provider,
+ "methods");
+ if ( (NULL == methods) ||
+ (! json_is_array (methods)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ size_t index;
+ json_t *method;
+
+ json_array_foreach (methods, index, method) {
+ const char *t;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &t),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ cost),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ if (0 == strcmp (t,
+ type))
+ return GNUNET_OK;
+ }
+ }
+ return GNUNET_NO; /* not found */
+}
+
+
+/**
+ * We failed to download a policy. Show an error to the user and
+ * allow the user to specify alternative providers and/or policy
+ * versions.
+ *
+ * @param[in] rss state to fail with the policy download
+ * @param offline true of the reason to show is that all providers
+ * were offline / did not return a salt to us
+ */
+static void
+return_no_policy (struct RecoverSecretState *rss,
+ bool offline)
+{
+ json_t *msg;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No provider online, need user to manually specify providers!\n");
+ msg = json_pack ("{s:s, s:b}",
+ "hint",
+ offline ? "could not contact provider" :
+ "provider does not know you",
+ "offline",
+ offline);
+ GNUNET_assert (0 ==
+ json_object_set_new (rss->state,
+ "recovery_error",
+ msg));
+ /* In case there are old ones, remove them! */
+ (void) json_object_del (rss->state,
+ "recovery_document");
+ (void) json_object_del (rss->state,
+ "recovery_information");
+ set_state (rss->state,
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING);
+ rss->cb (rss->cb_cls,
+ TALER_EC_NONE,
+ rss->state);
+ free_rss (rss);
+}
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * Once the first policy lookup succeeds, we update our state and
+ * cancel all of the others, passing the obtained recovery information
+ * back to the user.
+ *
+ * @param cls closure for the callback
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+policy_lookup_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct PolicyDownloadEntry *pd = cls;
+ struct RecoverSecretState *rss = pd->rss;
+ json_t *policies;
+ json_t *challenges;
+ json_t *recovery_information;
+
+ if (NULL == ri)
+ {
+ /* Woopsie, failed hard. */
+ GNUNET_CONTAINER_DLL_remove (rss->pd_head,
+ rss->pd_tail,
+ pd);
+ ANASTASIS_recovery_abort (pd->recovery);
+ GNUNET_free (pd->backend_url);
+ GNUNET_free (pd);
+ if (NULL != rss->pd_head)
+ return; /* wait for another one */
+ /* all failed! report failure! */
+ return_no_policy (rss,
+ false);
+ return;
+ }
+ policies = json_array ();
+ GNUNET_assert (NULL != policies);
+ for (unsigned int i = 0; i<ri->dps_len; i++)
+ {
+ struct ANASTASIS_DecryptionPolicy *dps = ri->dps[i];
+ json_t *pchallenges;
+
+ pchallenges = json_array ();
+ GNUNET_assert (NULL != pchallenges);
+ for (unsigned int j = 0; j<dps->challenges_length; j++)
+ {
+ struct ANASTASIS_Challenge *c = dps->challenges[j];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ json_t *cj;
+
+ cd = ANASTASIS_challenge_get_details (c);
+ cj = json_pack ("{s:o}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&cd->uuid));
+ GNUNET_assert (NULL != cj);
+ GNUNET_assert (0 ==
+ json_array_append_new (pchallenges,
+ cj));
+
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (policies,
+ pchallenges));
+ } /* end for all policies */
+ challenges = json_array ();
+ GNUNET_assert (NULL != challenges);
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *c = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ json_t *cj;
+ struct TALER_Amount cost;
+ int ret;
+
+ cd = ANASTASIS_challenge_get_details (c);
+ ret = lookup_cost (rss->state,
+ cd->provider_url,
+ cd->type,
+ &cost);
+ if (GNUNET_SYSERR == ret)
+ {
+ json_decref (challenges);
+ json_decref (policies);
+ set_state (rss->state,
+ ANASTASIS_GENERIC_STATE_ERROR);
+ ANASTASIS_redux_fail_ (rss->cb,
+ rss->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "failed to 'lookup_cost'");
+ free_rss (rss);
+ return;
+ }
+
+ cj = json_pack ("{s:o,s:o?,s:s,s:s}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&cd->uuid),
+ "cost",
+ (GNUNET_NO == ret)
+ ? NULL
+ : TALER_JSON_from_amount (&cost),
+ "type",
+ cd->type,
+ "instructions",
+ cd->instructions);
+ GNUNET_assert (NULL != cj);
+ GNUNET_assert (0 ==
+ json_array_append_new (challenges,
+ cj));
+ } /* end for all challenges */
+ recovery_information = json_pack ("{s:o, s:o, s:s?, s:s, s:I}",
+ "challenges", challenges,
+ "policies", policies,
+ "secret_name", ri->secret_name,
+ "provider_url", pd->backend_url,
+ "version", (json_int_t) ri->version);
+ GNUNET_assert (NULL != recovery_information);
+ GNUNET_assert (0 ==
+ json_object_set_new (rss->state,
+ "recovery_information",
+ recovery_information));
+ {
+ json_t *rd;
+
+ rd = ANASTASIS_recovery_serialize (pd->recovery);
+ if (NULL == rd)
+ {
+ GNUNET_break (0);
+ set_state (rss->state,
+ ANASTASIS_GENERIC_STATE_ERROR);
+ rss->cb (rss->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ rss->state);
+ free_rss (rss);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (rss->state,
+ "recovery_document",
+ rd));
+ }
+ /* In case there is an old error remove it! */
+ (void) json_object_del (rss->state,
+ "recovery_error");
+ set_state (rss->state,
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING);
+ rss->cb (rss->cb_cls,
+ TALER_EC_NONE,
+ rss->state);
+ free_rss (rss);
+}
+
+
+/**
+ * Try to launch recovery at provider @a provider_url with config @a p_cfg.
+ *
+ * @param[in,out] rss recovery context
+ * @param provider_url base URL of the provider to try
+ * @param p_cfg configuration of the provider
+ * @return true if a recovery was launched
+ */
+static bool
+launch_recovery (struct RecoverSecretState *rss,
+ const char *provider_url,
+ const json_t *p_cfg)
+{
+ struct PolicyDownloadEntry *pd = GNUNET_new (struct PolicyDownloadEntry);
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &pd->salt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (p_cfg,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No salt for `%s', provider offline?\n",
+ provider_url);
+ GNUNET_free (pd);
+ return false;
+ }
+ pd->backend_url = GNUNET_strdup (provider_url);
+ pd->rss = rss;
+ pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_,
+ rss->id_data,
+ rss->have_version
+ ? rss->version
+ : 0,
+ pd->backend_url,
+ &pd->salt,
+ &policy_lookup_cb,
+ pd,
+ &core_early_secret_cb,
+ pd);
+ if (NULL != pd->recovery)
+ {
+ GNUNET_CONTAINER_DLL_insert (rss->pd_head,
+ rss->pd_tail,
+ pd);
+ return true;
+ }
+ GNUNET_free (pd->backend_url);
+ GNUNET_free (pd);
+ return false;
+}
+
+
+/**
+ * We finished downloading /config from all providers, merge
+ * into the main state, trigger the continuation and free our
+ * state.
+ *
+ * @param[in] rss main state to merge into
+ */
+static void
+providers_complete (struct RecoverSecretState *rss)
+{
+ bool launched = false;
+ struct RecoveryStartStateProviderEntry *pe;
+ json_t *tlist;
+
+ tlist = json_object_get (rss->state,
+ "authentication_providers");
+ if (NULL == tlist)
+ {
+ tlist = json_object ();
+ GNUNET_assert (NULL != tlist);
+ GNUNET_assert (0 ==
+ json_object_set_new (rss->state,
+ "authentication_providers",
+ tlist));
+ }
+ while (NULL != (pe = rss->pe_head))
+ {
+ json_t *provider_list;
+
+ GNUNET_CONTAINER_DLL_remove (rss->pe_head,
+ rss->pe_tail,
+ pe);
+ provider_list = json_object_get (pe->istate,
+ "authentication_providers");
+ /* merge provider_list into tlist (overriding existing entries) */
+ if (NULL != provider_list)
+ {
+ const char *url;
+ json_t *value;
+
+ json_object_foreach (provider_list, url, value) {
+ GNUNET_assert (0 ==
+ json_object_set (tlist,
+ url,
+ value));
+ }
+ }
+ json_decref (pe->istate);
+ GNUNET_free (pe);
+ }
+
+ /* now iterate over providers and begin downloading */
+ if (NULL != rss->provider_url)
+ {
+ json_t *p_cfg;
+
+ p_cfg = json_object_get (tlist,
+ rss->provider_url);
+ if (NULL != p_cfg)
+ launched = launch_recovery (rss,
+ rss->provider_url,
+ p_cfg);
+ }
+ else
+ {
+ json_t *p_cfg;
+ const char *provider_url;
+
+ json_object_foreach (tlist, provider_url, p_cfg)
+ {
+ launched |= launch_recovery (rss,
+ provider_url,
+ p_cfg);
+ }
+ }
+ if (! launched)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No provider online, need user to specify different provider!\n");
+ return_no_policy (rss,
+ true);
+ return;
+ }
+}
+
+
+/**
+ * Function called when the complete information about a provider
+ * was added to @a new_state.
+ *
+ * @param cls a `struct RecoveryStartStateProviderEntry`
+ * @param error error code
+ * @param new_state resulting new state
+ */
+static void
+provider_added_cb (void *cls,
+ enum TALER_ErrorCode error,
+ json_t *new_state)
+{
+ struct RecoveryStartStateProviderEntry *pe = cls;
+
+ pe->ra = NULL;
+ pe->istate = json_incref (new_state);
+ pe->ec = error;
+ pe->rss->pending--;
+ if (0 == pe->rss->pending)
+ providers_complete (pe->rss);
+}
+
+
+/**
+ * Start to query provider for recovery document.
+ *
+ * @param[in,out] rss overall recovery state
+ * @param provider_url base URL of the provider to query
+ */
+static void
+begin_query_provider (struct RecoverSecretState *rss,
+ const char *provider_url)
+{
+ struct RecoveryStartStateProviderEntry *pe;
+ json_t *istate;
+
+ pe = GNUNET_new (struct RecoveryStartStateProviderEntry);
+ pe->rss = rss;
+ istate = json_object ();
+ GNUNET_assert (NULL != istate);
+ GNUNET_CONTAINER_DLL_insert (rss->pe_head,
+ rss->pe_tail,
+ pe);
+ pe->ra = ANASTASIS_REDUX_add_provider_to_state_ (provider_url,
+ istate,
+ &provider_added_cb,
+ pe);
+ json_decref (istate);
+ if (NULL != pe->ra)
+ rss->pending++;
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *version;
+ json_t *providers;
+ const json_t *attributes;
+ struct RecoverSecretState *rss;
+ const char *provider_url;
+
+ providers = json_object_get (state,
+ "authentication_providers");
+ if ( (NULL == providers) ||
+ (! json_is_object (providers)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' missing");
+ return NULL;
+ }
+ attributes = json_object_get (arguments,
+ "identity_attributes");
+ if ( (NULL == attributes) ||
+ (! json_is_object (attributes)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'identity_attributes' missing");
+ return NULL;
+ }
+ rss = GNUNET_new (struct RecoverSecretState);
+ rss->id_data = json_incref ((json_t *) attributes);
+ version = json_object_get (arguments,
+ "version");
+ if (NULL != version)
+ {
+ rss->version = (unsigned int) json_integer_value (version);
+ rss->have_version = true;
+ }
+ rss->state = json_incref (state);
+ rss->cb = cb;
+ rss->cb_cls = cb_cls;
+ rss->pending = 1; /* decremented after initialization loop */
+
+ provider_url = json_string_value (json_object_get (arguments,
+ "provider_url"));
+ if (NULL != provider_url)
+ {
+ rss->provider_url = GNUNET_strdup (provider_url);
+ begin_query_provider (rss,
+ provider_url);
+ }
+ else
+ {
+ json_t *prov;
+ const char *url;
+
+ json_object_foreach (providers, url, prov) {
+ begin_query_provider (rss,
+ url);
+ }
+ }
+ rss->pending--;
+ if (0 == rss->pending)
+ {
+ providers_complete (rss);
+ if (NULL == rss->cb)
+ return NULL;
+ }
+ rss->ra.cleanup = &free_rss;
+ rss->ra.cleanup_cls = rss;
+ return &rss->ra;
+}
diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c
new file mode 100644
index 0000000..eb7e362
--- /dev/null
+++ b/src/reducer/anastasis_api_redux.c
@@ -0,0 +1,1730 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 lib/anastasis_api_redux.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <platform.h>
+#include <jansson.h>
+#include "anastasis_redux.h"
+#include "anastasis_error_codes.h"
+#include <taler/taler_json_lib.h>
+#include "anastasis_api_redux.h"
+#include <dlfcn.h>
+
+
+/**
+ * How long do we wait at most for a /config reply from an Anastasis provider.
+ * 60s is very generous, given the tiny bandwidth required, even for the most
+ * remote locations.
+ */
+#define CONFIG_GENERIC_TIMEOUT GNUNET_TIME_UNIT_MINUTES
+
+
+#define GENERATE_STRING(STRING) #STRING,
+static const char *generic_strings[] = {
+ ANASTASIS_GENERIC_STATES (GENERATE_STRING)
+};
+#undef GENERATE_STRING
+
+
+/**
+ * #ANASTASIS_REDUX_add_provider_to_state_ waiting for the
+ * configuration request to complete or fail.
+ */
+struct ConfigReduxWaiting
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct ConfigReduxWaiting *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ConfigReduxWaiting *next;
+
+ /**
+ * Associated redux action.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Config request we are waiting for.
+ */
+ struct ConfigRequest *cr;
+
+ /**
+ * State we are processing.
+ */
+ json_t *state;
+
+ /**
+ * Function to call with updated @e state.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Anastasis authorization method configuration
+ */
+struct AuthorizationMethodConfig
+{
+ /**
+ * Type of the method, i.e. "question".
+ */
+ char *type;
+
+ /**
+ * Fee charged for accessing key share using this method.
+ */
+ struct TALER_Amount usage_fee;
+};
+
+
+/**
+ * State for a "get config" operation.
+ */
+struct ConfigRequest
+{
+
+ /**
+ * Kept in a DLL, given that we may have multiple backends.
+ */
+ struct ConfigRequest *next;
+
+ /**
+ * Kept in a DLL, given that we may have multiple backends.
+ */
+ struct ConfigRequest *prev;
+
+ /**
+ * Head of DLL of REDUX operations waiting for an answer.
+ */
+ struct ConfigReduxWaiting *w_head;
+
+ /**
+ * Tail of DLL of REDUX operations waiting for an answer.
+ */
+ struct ConfigReduxWaiting *w_tail;
+
+ /**
+ * Obtained status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * The /config GET operation handle.
+ */
+ struct ANASTASIS_ConfigOperation *co;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ char *url;
+
+ /**
+ * Business name of the anastasis backend.
+ */
+ char *business_name;
+
+ /**
+ * currency used by the anastasis backend.
+ */
+ char *currency;
+
+ /**
+ * Array of authorization methods supported by the server.
+ */
+ struct AuthorizationMethodConfig *methods;
+
+ /**
+ * Length of the @e methods array.
+ */
+ unsigned int methods_length;
+
+ /**
+ * Maximum size of an upload in megabytes.
+ */
+ uint32_t storage_limit_in_megabytes;
+
+ /**
+ * Annual fee for an account / policy upload.
+ */
+ struct TALER_Amount annual_fee;
+
+ /**
+ * Fee for a truth upload.
+ */
+ struct TALER_Amount truth_upload_fee;
+
+ /**
+ * Maximum legal liability for data loss covered by the
+ * provider.
+ */
+ struct TALER_Amount liability_limit;
+
+ /**
+ * Server salt.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+
+ /**
+ * Task to timeout /config requests.
+ */
+ struct GNUNET_SCHEDULER_Task *tt;
+
+ /**
+ * Status of the /config request.
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+/**
+ * Reducer API's CURL context handle.
+ */
+struct GNUNET_CURL_Context *ANASTASIS_REDUX_ctx_;
+
+/**
+ * JSON containing country specific identity attributes to ask the user for.
+ */
+static json_t *redux_id_attr;
+
+/**
+ * Head of DLL of Anastasis backend configuration requests.
+ */
+static struct ConfigRequest *cr_head;
+
+/**
+ * Tail of DLL of Anastasis backend configuration requests.
+ */
+static struct ConfigRequest *cr_tail;
+
+/**
+ * JSON containing country specific information.
+ */
+static json_t *redux_countries;
+
+/**
+ * List of Anastasis providers.
+ */
+static json_t *provider_list;
+
+
+/**
+ * Extract the mode of a state from json
+ *
+ * @param state the state to operate on
+ * @return "backup_state" or "recovery_state"
+ */
+static const char *
+get_state_mode (const json_t *state)
+{
+ if (json_object_get (state, "backup_state"))
+ return "backup_state";
+ if (json_object_get (state, "recovery_state"))
+ return "recovery_state";
+ GNUNET_assert (0);
+ return NULL;
+}
+
+
+enum ANASTASIS_GenericState
+ANASTASIS_generic_state_from_string_ (const char *state_string)
+{
+ for (enum ANASTASIS_GenericState i = 0;
+ i < sizeof (generic_strings) / sizeof(*generic_strings);
+ i++)
+ if (0 == strcmp (state_string,
+ generic_strings[i]))
+ return i;
+ return ANASTASIS_GENERIC_STATE_ERROR;
+}
+
+
+const char *
+ANASTASIS_generic_state_to_string_ (enum ANASTASIS_GenericState gs)
+{
+ if ( (gs < 0) ||
+ (gs >= sizeof (generic_strings) / sizeof(*generic_strings)) )
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return generic_strings[gs];
+}
+
+
+void
+ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ json_t *estate;
+
+ estate = json_pack ("{s:s?, s:I, s:s}",
+ "detail", detail,
+ "code", (json_int_t) ec,
+ "hint", TALER_ErrorCode_get_hint (ec));
+ cb (cb_cls,
+ ec,
+ estate);
+ json_decref (estate);
+}
+
+
+/**
+ * Transition the @a state to @a gs.
+ *
+ * @param[in,out] state to transition
+ * @param gs state to transition to
+ */
+static void
+redux_transition (json_t *state,
+ enum ANASTASIS_GenericState gs)
+{
+ const char *s_mode = get_state_mode (state);
+
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_generic_state_to_string_ (gs))));
+
+}
+
+
+void
+ANASTASIS_redux_init (struct GNUNET_CURL_Context *ctx)
+{
+ ANASTASIS_REDUX_ctx_ = ctx;
+}
+
+
+/**
+ * Function to free a #ConfigRequest, an async operation.
+ *
+ * @param cr state for a "get config" operation
+ */
+static void
+free_config_request (struct ConfigRequest *cr)
+{
+ GNUNET_assert (NULL == cr->w_head);
+ if (NULL != cr->co)
+ ANASTASIS_config_cancel (cr->co);
+ if (NULL != cr->tt)
+ GNUNET_SCHEDULER_cancel (cr->tt);
+ GNUNET_free (cr->currency);
+ GNUNET_free (cr->url);
+ GNUNET_free (cr->business_name);
+ for (unsigned int i = 0; i<cr->methods_length; i++)
+ GNUNET_free (cr->methods[i].type);
+ GNUNET_free (cr->methods);
+ GNUNET_free (cr);
+}
+
+
+void
+ANASTASIS_redux_done ()
+{
+ struct ConfigRequest *cr;
+
+ while (NULL != (cr = cr_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (cr_head,
+ cr_tail,
+ cr);
+ free_config_request (cr);
+ }
+ ANASTASIS_REDUX_ctx_ = NULL;
+ if (NULL != redux_countries)
+ {
+ json_decref (redux_countries);
+ redux_countries = NULL;
+ }
+ if (NULL != redux_id_attr)
+ {
+ json_decref (redux_id_attr);
+ redux_id_attr = NULL;
+ }
+ if (NULL != provider_list)
+ {
+ json_decref (provider_list);
+ provider_list = NULL;
+ }
+}
+
+
+const json_t *
+ANASTASIS_redux_countries_init_ (void)
+{
+ char *dn;
+ json_error_t error;
+
+ if (NULL != redux_countries)
+ return redux_countries;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/redux.countries.json",
+ path);
+ GNUNET_free (path);
+ }
+ redux_countries = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == redux_countries)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return NULL;
+ }
+ GNUNET_free (dn);
+ return redux_countries;
+}
+
+
+/**
+ * Abort waiting for /config reply.
+ *
+ * @param cls a `struct ConfigReduxWaiting` handle.
+ */
+static void
+abort_provider_config_cb (void *cls)
+{
+ struct ConfigReduxWaiting *w = cls;
+ struct ConfigRequest *cr = w->cr;
+
+ GNUNET_CONTAINER_DLL_remove (cr->w_head,
+ cr->w_tail,
+ w);
+ json_decref (w->state);
+ GNUNET_free (w);
+}
+
+
+/**
+ * Notify anyone waiting on @a cr that the request is done
+ * (successful or failed).
+ *
+ * @param[in,out] cr request that completed
+ */
+static void
+notify_waiting (struct ConfigRequest *cr)
+{
+ struct ConfigReduxWaiting *w;
+
+ while (NULL != (w = cr->w_head))
+ {
+ json_t *provider_list;
+ json_t *prov;
+
+ if (NULL == (provider_list = json_object_get (w->state,
+ "authentication_providers")))
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (w->state,
+ "authentication_providers",
+ provider_list = json_object ()));
+ }
+ provider_list = json_object_get (w->state,
+ "authentication_providers");
+ GNUNET_assert (NULL != provider_list);
+
+ if (TALER_EC_NONE != cr->ec)
+ {
+ prov = json_pack ("{s:I, s:I}",
+ "error_code",
+ (json_int_t) cr->ec,
+ "http_status",
+ (json_int_t) cr->http_status);
+ }
+ else
+ {
+ json_t *methods_list;
+
+ methods_list = json_array ();
+ GNUNET_assert (NULL != methods_list);
+ for (unsigned int i = 0; i<cr->methods_length; i++)
+ {
+ struct AuthorizationMethodConfig *method = &cr->methods[i];
+ json_t *mj = json_pack ("{s:s, s:o}",
+ "type",
+ method->type,
+ "usage_fee",
+ TALER_JSON_from_amount (&method->usage_fee));
+
+ GNUNET_assert (NULL != mj);
+ GNUNET_assert (0 ==
+ json_array_append_new (methods_list,
+ mj));
+ }
+ prov = json_pack ("{s:o, s:o, s:o, s:o, s:s,"
+ " s:s, s:I, s:o, s:I}",
+ "methods",
+ methods_list,
+ "annual_fee",
+ TALER_JSON_from_amount (&cr->annual_fee),
+ "truth_upload_fee",
+ TALER_JSON_from_amount (&cr->truth_upload_fee),
+ "liability_limit",
+ TALER_JSON_from_amount (&cr->liability_limit),
+ "currency",
+ cr->currency,
+ /* 6 */
+ "business_name",
+ cr->business_name,
+ "storage_limit_in_megabytes",
+ (json_int_t) cr->storage_limit_in_megabytes,
+ "salt",
+ GNUNET_JSON_from_data_auto (&cr->salt),
+ "http_status",
+ (json_int_t) cr->http_status);
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (provider_list,
+ cr->url,
+ prov));
+ w->cb (w->cb_cls,
+ cr->ec,
+ w->state);
+ abort_provider_config_cb (w);
+ }
+
+}
+
+
+/**
+ * Function called with the results of a #ANASTASIS_get_config().
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param acfg anastasis configuration
+ */
+static void
+config_cb (void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_Config *acfg)
+{
+ struct ConfigRequest *cr = cls;
+
+ cr->co = NULL;
+ GNUNET_SCHEDULER_cancel (cr->tt);
+ cr->tt = NULL;
+ cr->http_status = http_status;
+ if (MHD_HTTP_OK != http_status)
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
+ if ( (MHD_HTTP_OK == http_status) &&
+ (NULL == acfg) )
+ {
+ cr->http_status = MHD_HTTP_NOT_FOUND;
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
+ }
+ else if (NULL != acfg)
+ {
+ if (0 == acfg->storage_limit_in_megabytes)
+ {
+ cr->http_status = 0;
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG;
+ }
+ else
+ {
+ cr->currency = GNUNET_strdup (acfg->currency);
+ cr->business_name = GNUNET_strdup (acfg->business_name);
+ cr->methods = GNUNET_new_array (acfg->methods_length,
+ struct AuthorizationMethodConfig);
+ for (unsigned int i = 0; i<acfg->methods_length; i++)
+ {
+ cr->methods[i].type = GNUNET_strdup (acfg->methods[i].type);
+ cr->methods[i].usage_fee = acfg->methods[i].usage_fee;
+ }
+ cr->methods_length = acfg->methods_length;
+ cr->storage_limit_in_megabytes = acfg->storage_limit_in_megabytes;
+ cr->annual_fee = acfg->annual_fee;
+ cr->truth_upload_fee = acfg->truth_upload_fee;
+ cr->liability_limit = acfg->liability_limit;
+ cr->salt = acfg->salt;
+ }
+ }
+ notify_waiting (cr);
+}
+
+
+/**
+ * Aborts a "get config" after timeout.
+ *
+ * @param cls closure for a "get config" request
+ */
+static void
+config_request_timeout (void *cls)
+{
+ struct ConfigRequest *cr = cls;
+
+ cr->tt = NULL;
+ ANASTASIS_config_cancel (cr->co);
+ cr->co = NULL;
+ cr->http_status = 0;
+ cr->ec = TALER_EC_GENERIC_TIMEOUT;
+ notify_waiting (cr);
+}
+
+
+/**
+ * Schedule job to obtain Anastasis provider configuration at @a url.
+ *
+ * @param url base URL of Anastasis provider
+ * @return check config handle
+ */
+static struct ConfigRequest *
+check_config (const char *url)
+{
+ struct ConfigRequest *cr;
+
+ for (cr = cr_head; NULL != cr; cr = cr->next)
+ {
+ if (0 != strcmp (url,
+ cr->url))
+ continue;
+ if (NULL != cr->co)
+ return cr; /* already on it */
+ break;
+ }
+ if (NULL == cr)
+ {
+ cr = GNUNET_new (struct ConfigRequest);
+ cr->url = GNUNET_strdup (url);
+ GNUNET_CONTAINER_DLL_insert (cr_head,
+ cr_tail,
+ cr);
+ }
+ cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_,
+ cr->url,
+ &config_cb,
+ cr);
+ if (NULL == cr->co)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ else
+ {
+ cr->tt = GNUNET_SCHEDULER_add_delayed (CONFIG_GENERIC_TIMEOUT,
+ &config_request_timeout,
+ cr);
+ }
+ return cr;
+}
+
+
+/**
+ * Begin asynchronous check for provider configurations.
+ *
+ * @param currencies the currencies to initiate the provider checks for
+ * @param[in,out] state to set provider list for
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+begin_provider_config_check (const json_t *currencies,
+ json_t *state)
+{
+ if (NULL == provider_list)
+ {
+ json_error_t error;
+ char *dn;
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/provider-list.json",
+ path);
+ GNUNET_free (path);
+ provider_list = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == provider_list)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
+ }
+ GNUNET_free (dn);
+ }
+
+ {
+ size_t index;
+ json_t *provider;
+ const json_t *provider_arr = json_object_get (provider_list,
+ "anastasis_provider");
+ json_t *pl;
+
+ pl = json_object ();
+ json_array_foreach (provider_arr, index, provider)
+ {
+ const char *url;
+ const char *cur;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("currency",
+ &cur),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (pl);
+ return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
+ }
+
+ {
+ bool found = false;
+ json_t *cu;
+ size_t off;
+
+ json_array_foreach (currencies, off, cu)
+ {
+ const char *currency;
+
+ currency = json_string_value (cu);
+ if (NULL == currency)
+ {
+ json_decref (pl);
+ return TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID;
+ }
+ found = (0 == strcasecmp (currency,
+ cur));
+ }
+ if (! found)
+ continue;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (pl,
+ url,
+ json_object ()));
+ check_config (url);
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "authentication_providers",
+ pl));
+ }
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Function to validate an input by regular expression ("validation-regex").
+ *
+ * @param input text to validate
+ * @param regexp regular expression to validate
+ * @return true if validation passed, else false
+ */
+static bool
+validate_regex (const char *input,
+ const char *regexp)
+{
+ regex_t regex;
+
+ if (0 != regcomp (&regex,
+ regexp,
+ REG_EXTENDED))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to compile regular expression `%s'.",
+ regexp);
+ return true;
+ }
+ /* check if input has correct form */
+ if (0 != regexec (&regex,
+ input,
+ 0,
+ NULL,
+ 0))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input `%s' does not match regex `%s'\n",
+ input,
+ regexp);
+ regfree (&regex);
+ return false;
+ }
+ regfree (&regex);
+ return true;
+}
+
+
+/**
+ * Function to load json containing country specific identity
+ * attributes. Uses a single-slot cache to avoid loading
+ * exactly the same attributes twice.
+ *
+ * @param country_code country code (e.g. "de")
+ * @return NULL on error
+ */
+static const json_t *
+redux_id_attr_init (const char *country_code)
+{
+ static char redux_id_cc[3];
+ char *dn;
+ json_error_t error;
+
+ if (0 == strcmp (country_code,
+ redux_id_cc))
+ return redux_id_attr;
+
+ if (NULL != redux_id_attr)
+ {
+ json_decref (redux_id_attr);
+ redux_id_attr = NULL;
+ }
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/redux.%s.json",
+ path,
+ country_code);
+ GNUNET_free (path);
+ }
+ redux_id_attr = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == redux_id_attr)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return NULL;
+ }
+ GNUNET_free (dn);
+ strncpy (redux_id_cc,
+ country_code,
+ sizeof (redux_id_cc));
+ redux_id_cc[2] = '\0';
+ return redux_id_attr;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "select_continent" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+select_continent (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *redux_countries = ANASTASIS_redux_countries_init_ ();
+ const json_t *root = json_object_get (redux_countries,
+ "countries");
+ const json_t *continent;
+ json_t *countries;
+
+ if (NULL == root)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
+ "'countries' missing");
+ return NULL;
+ }
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ continent = json_object_get (arguments,
+ "continent");
+ if (NULL == continent)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'continent' missing");
+ return NULL;
+ }
+ countries = json_array ();
+ GNUNET_assert (NULL != countries);
+ {
+ size_t index;
+ const json_t *country;
+ bool found = false;
+
+ json_array_foreach (root, index, country)
+ {
+ json_t *temp_continent = json_object_get (country,
+ "continent");
+ if (1 == json_equal (continent,
+ temp_continent))
+ {
+ GNUNET_assert (0 ==
+ json_array_append (countries,
+ (json_t *) country));
+ found = true;
+ }
+ }
+ if (! found)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'continent' unknown");
+ return NULL;
+ }
+ }
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING);
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "selected_continent",
+ (json_t *) continent));
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "countries",
+ countries));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "select_country" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return #ANASTASIS_ReduxAction
+ */
+static struct ANASTASIS_ReduxAction *
+select_country (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *required_attrs;
+ const json_t *country_code;
+ const json_t *currencies;
+ const json_t *redux_id_attr;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ country_code = json_object_get (arguments,
+ "country_code");
+ if (NULL == country_code)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'country_code' missing");
+ return NULL;
+ }
+
+ {
+ json_t *countries = json_object_get (state,
+ "countries");
+ size_t index;
+ json_t *country;
+ bool found = false;
+
+ json_array_foreach (countries, index, country)
+ {
+ json_t *cc = json_object_get (country,
+ "code");
+ if (1 == json_equal (country_code,
+ cc))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "specified country not on selected continent");
+ return NULL;
+ }
+ }
+
+ currencies = json_object_get (arguments,
+ "currencies");
+ if ( (NULL == currencies) ||
+ (! json_is_array (currencies)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'currencies' missing");
+ return NULL;
+ }
+ /* We now have an idea of the currency, begin fetching
+ provider /configs (we likely need them later) */
+ {
+ enum TALER_ErrorCode ec;
+
+ ec = begin_provider_config_check (currencies,
+ state);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ ec,
+ NULL);
+ return NULL;
+ }
+ }
+ redux_id_attr = redux_id_attr_init (json_string_value (country_code));
+ if (NULL == redux_id_attr)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MISSING,
+ json_string_value (country_code));
+ return NULL;
+ }
+ required_attrs = json_object_get (redux_id_attr,
+ "required_attributes");
+ if (NULL == required_attrs)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
+ "'required_attributes' missing");
+ return NULL;
+ }
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING);
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "selected_country",
+ (json_t *) country_code));
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "currencies",
+ (json_t *) currencies));
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "required_attributes",
+ (json_t *) required_attrs));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "unselect_continent" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+unselect_continent (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_add_provider_to_state_ (const char *url,
+ json_t *state,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct ConfigRequest *cr;
+ struct ConfigReduxWaiting *w;
+
+ cr = check_config (url);
+ w = GNUNET_new (struct ConfigReduxWaiting);
+ w->cr = cr;
+ w->state = json_incref (state);
+ w->cb = cb;
+ w->cb_cls = cb_cls;
+ w->ra.cleanup = &abort_provider_config_cb;
+ w->ra.cleanup_cls = w;
+ GNUNET_CONTAINER_DLL_insert (cr->w_head,
+ cr->w_tail,
+ w);
+ if (NULL == cr->co)
+ {
+ notify_waiting (cr);
+ return NULL;
+ }
+ return &w->ra;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_user_attributes" action.
+ * Returns an #ANASTASIS_ReduxAction if operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+enter_user_attributes (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *attributes;
+ const json_t *required_attributes;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ attributes = json_object_get (arguments,
+ "identity_attributes");
+ if (NULL == attributes)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'identity_attributes' missing");
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "identity_attributes",
+ (json_t *) attributes));
+
+ /* Verify required attributes are present and well-formed */
+ required_attributes = json_object_get (state,
+ "required_attributes");
+ if ( (NULL == required_attributes) ||
+ (! json_is_array (required_attributes)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'required_attributes' must be an array");
+ return NULL;
+ }
+ {
+ size_t index;
+ json_t *required_attribute;
+
+ json_array_foreach (required_attributes, index, required_attribute)
+ {
+ const char *name;
+ const char *attribute_value;
+ const char *regexp = NULL;
+ const char *reglog = NULL;
+ int optional = false;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &name),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("validation-regex",
+ &regexp)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("validation-logic",
+ &reglog)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_boolean ("optional",
+ &optional)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (required_attribute,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'required_attributes' lacks required fields");
+ return NULL;
+ }
+ attribute_value = json_string_value (json_object_get (attributes,
+ name));
+ if (NULL == attribute_value)
+ {
+ if (optional)
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Request is missing required attribute `%s'\n",
+ name);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ name);
+ return NULL;
+ }
+ if ( (NULL != regexp) &&
+ (! validate_regex (attribute_value,
+ regexp)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_REGEX_FAILED,
+ name);
+ return NULL;
+ }
+
+ if (NULL != reglog)
+ {
+ bool (*regfun)(const char *);
+
+ regfun = dlsym (RTLD_DEFAULT,
+ reglog);
+ if (NULL == regfun)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Custom validation function `%s' is not available: %s\n",
+ reglog,
+ dlerror ());
+ }
+ else if (! regfun (attribute_value))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED,
+ name);
+ return NULL;
+ }
+ }
+ } /* end for all attributes loop */
+ } /* end for all attributes scope */
+
+ /* Transition based on mode */
+ {
+ const char *s_mode = get_state_mode (state);
+
+ if (0 == strcmp (s_mode,
+ "backup_state"))
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ "backup_state",
+ json_string (
+ ANASTASIS_backup_state_to_string_ (
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING))));
+ return ANASTASIS_REDUX_backup_begin_ (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ else
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ "recovery_state",
+ json_string (
+ ANASTASIS_recovery_state_to_string_ (
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING))));
+ return ANASTASIS_REDUX_recovery_challenge_begin_ (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ }
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_provider" action. Adds another Anastasis provider
+ * to the list of available providers for storing information.
+ *
+ * @param state state to operate on
+ * @param arguments arguments with a provider URL to add
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+add_provider (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ if (ANASTASIS_add_provider_ (state,
+ arguments,
+ cb,
+ cb_cls))
+ return NULL;
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+bool
+ANASTASIS_add_provider_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *urls;
+ json_t *tlist;
+
+ tlist = json_object_get (state,
+ "authentication_providers");
+ if (NULL == tlist)
+ {
+ tlist = json_object ();
+ GNUNET_assert (NULL != tlist);
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "authentication_providers",
+ tlist));
+ }
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return true;
+ }
+ urls = json_object_get (arguments,
+ "urls");
+ if (NULL == urls)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'urls' missing");
+ return true;
+ }
+ {
+ size_t index;
+ json_t *url;
+
+ json_array_foreach (urls, index, url)
+ {
+ const char *url_str = json_string_value (url);
+
+ if (NULL == url_str)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'urls' must be strings");
+ return true;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (tlist,
+ url_str,
+ json_object ()));
+ }
+ }
+ return false;
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_back_generic_decrement_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const char *s_mode = get_state_mode (state);
+ const char *state_string = json_string_value (json_object_get (state,
+ s_mode));
+
+ (void) arguments;
+ GNUNET_assert (NULL != state_string);
+ if (0 == strcmp ("backup_state",
+ s_mode))
+ {
+ enum ANASTASIS_BackupState state_index;
+
+ state_index = ANASTASIS_backup_state_from_string_ (state_string);
+ GNUNET_assert (state_index > 0);
+ state_index = state_index - 1;
+
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_backup_state_to_string_ (state_index))));
+ }
+ else
+ {
+ enum ANASTASIS_RecoveryState state_index;
+
+ state_index = ANASTASIS_recovery_state_from_string_ (state_string);
+ GNUNET_assert (state_index > 0);
+ state_index = state_index - 1;
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_recovery_state_to_string_ (state_index))));
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Callback function which is called by the reducer in dependence of
+ * given state and action.
+ *
+ * @param state the previous state to operate on
+ * @param arguments the arguments needed by operation to operate on state
+ * @param cb Callback function which returns the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel async actions, NULL if @a cb was already called
+ */
+typedef struct ANASTASIS_ReduxAction *
+(*DispatchHandler)(json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_redux_action (const json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct Dispatcher
+ {
+ enum ANASTASIS_GenericState redux_state;
+ const char *redux_action;
+ DispatchHandler fun;
+ } dispatchers[] = {
+ {
+ ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING,
+ "select_continent",
+ &select_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "unselect_continent",
+ &unselect_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "select_country",
+ &select_country
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "select_continent",
+ &select_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "unselect_continent",
+ &unselect_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "enter_user_attributes",
+ &enter_user_attributes
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "add_provider",
+ &add_provider
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ { ANASTASIS_GENERIC_STATE_ERROR, NULL, NULL }
+ };
+ bool recovery_mode = false;
+ const char *s = json_string_value (json_object_get (state,
+ "backup_state"));
+ enum ANASTASIS_GenericState gs;
+
+ if (NULL == s)
+ {
+ s = json_string_value (json_object_get (state,
+ "recovery_state"));
+ if (NULL == s)
+ {
+ GNUNET_break_op (0);
+ cb (cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ recovery_mode = true;
+ }
+ gs = ANASTASIS_generic_state_from_string_ (s);
+ {
+ json_t *new_state;
+ struct ANASTASIS_ReduxAction *ret;
+
+ new_state = json_deep_copy (state);
+ GNUNET_assert (NULL != new_state);
+ if (gs != ANASTASIS_GENERIC_STATE_ERROR)
+ {
+ for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
+ {
+ if ( (gs == dispatchers[i].redux_state) &&
+ (0 == strcmp (action,
+ dispatchers[i].redux_action)) )
+ {
+ ret = dispatchers[i].fun (new_state,
+ arguments,
+ cb,
+ cb_cls);
+ json_decref (new_state);
+ return ret;
+ }
+ }
+ }
+ if (recovery_mode)
+ {
+ ret = ANASTASIS_recovery_action_ (new_state,
+ action,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ else
+ {
+ ret = ANASTASIS_backup_action_ (new_state,
+ action,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ json_decref (new_state);
+ return ret;
+ }
+}
+
+
+void
+ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra)
+{
+ ra->cleanup (ra->cleanup_cls);
+}
+
+
+json_t *
+ANASTASIS_REDUX_load_continents_ ()
+{
+ const json_t *countries;
+ json_t *continents;
+ const json_t *redux_countries = ANASTASIS_redux_countries_init_ ();
+
+ if (NULL == redux_countries)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ countries = json_object_get (redux_countries,
+ "countries");
+ if (NULL == countries)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ continents = json_array ();
+ GNUNET_assert (NULL != continents);
+
+ {
+ json_t *country;
+ size_t index;
+
+ json_array_foreach (countries, index, country)
+ {
+ json_t *ex = NULL;
+ const json_t *continent;
+
+ continent = json_object_get (country,
+ "continent");
+ if ( (NULL == continent) ||
+ (! json_is_string (continent)) )
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ {
+ size_t inner_index;
+ json_t *inner_continent;
+
+ json_array_foreach (continents, inner_index, inner_continent)
+ {
+ const json_t *name;
+
+ name = json_object_get (inner_continent,
+ "name");
+ if (1 == json_equal (continent,
+ name))
+ {
+ ex = inner_continent;
+ break;
+ }
+ }
+ }
+ if (NULL == ex)
+ {
+ ex = json_pack ("{s:O}",
+ "name",
+ continent);
+ GNUNET_assert (0 ==
+ json_array_append_new (continents,
+ ex));
+ }
+
+ {
+ json_t *i18n_continent;
+ json_t *name_ex;
+
+ i18n_continent = json_object_get (country,
+ "continent_i18n");
+ name_ex = json_object_get (ex,
+ "name_i18n");
+ if (NULL != i18n_continent)
+ {
+ const char *lang;
+ json_t *trans;
+
+ json_object_foreach (i18n_continent, lang, trans)
+ {
+ if (NULL == name_ex)
+ {
+ name_ex = json_object ();
+ json_object_set_new (ex,
+ "name_i18n",
+ name_ex);
+ }
+ if (NULL == json_object_get (name_ex,
+ lang))
+ {
+ json_object_set (name_ex,
+ lang,
+ trans);
+ }
+ }
+ }
+ }
+ }
+ }
+ return json_pack ("{s:o}",
+ "continents",
+ continents);
+}
diff --git a/src/reducer/anastasis_api_redux.h b/src/reducer/anastasis_api_redux.h
new file mode 100644
index 0000000..19d9466
--- /dev/null
+++ b/src/reducer/anastasis_api_redux.h
@@ -0,0 +1,347 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 redux/anastasis_api_redux.h
+ * @brief anastasis reducer api, internal data structures
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#ifndef ANASTASIS_API_REDUX_H
+#define ANASTASIS_API_REDUX_H
+
+
+#define ANASTASIS_GENERIC_STATES(REDUX_STATE) \
+ REDUX_STATE (ERROR) \
+ REDUX_STATE (CONTINENT_SELECTING) \
+ REDUX_STATE (COUNTRY_SELECTING) \
+ REDUX_STATE (USER_ATTRIBUTES_COLLECTING)
+
+#define GENERATE_GENERIC_ENUM(ENUM) ANASTASIS_GENERIC_STATE_ ## ENUM,
+
+enum ANASTASIS_GenericState
+{
+ ANASTASIS_GENERIC_STATES (GENERATE_GENERIC_ENUM)
+};
+
+#undef GENERATE_GENERIC_ENUM
+
+#define ANASTASIS_BACKUP_STATES(REDUX_STATE) \
+ ANASTASIS_GENERIC_STATES (REDUX_STATE) \
+ REDUX_STATE (AUTHENTICATIONS_EDITING) \
+ REDUX_STATE (POLICIES_REVIEWING) \
+ REDUX_STATE (SECRET_EDITING) \
+ REDUX_STATE (TRUTHS_PAYING) \
+ REDUX_STATE (POLICIES_PAYING) \
+ REDUX_STATE (BACKUP_FINISHED)
+
+#define GENERATE_BACKUP_ENUM(ENUM) ANASTASIS_BACKUP_STATE_ ## ENUM,
+
+enum ANASTASIS_BackupState
+{
+ ANASTASIS_BACKUP_STATES (GENERATE_BACKUP_ENUM)
+};
+
+#undef GENERATE_BACKUP_ENUM
+
+#define ANASTASIS_RECOVERY_STATES(REDUX_STATE) \
+ ANASTASIS_GENERIC_STATES (REDUX_STATE) \
+ REDUX_STATE (SECRET_SELECTING) \
+ REDUX_STATE (CHALLENGE_SELECTING) \
+ REDUX_STATE (CHALLENGE_PAYING) \
+ REDUX_STATE (CHALLENGE_SOLVING) \
+ REDUX_STATE (RECOVERY_FINISHED)
+
+#define GENERATE_RECOVERY_ENUM(ENUM) ANASTASIS_RECOVERY_STATE_ ## ENUM,
+
+enum ANASTASIS_RecoveryState
+{
+ ANASTASIS_RECOVERY_STATES (GENERATE_RECOVERY_ENUM)
+};
+
+#undef GENERATE_RECOVERY_ENUM
+
+
+/**
+ * CURL context to be used by all operations.
+ */
+extern struct GNUNET_CURL_Context *ANASTASIS_REDUX_ctx_;
+
+
+/**
+ * Initialize reducer subsystem.
+ *
+ * @param ctx context to use for CURL requests.
+ */
+void
+ANASTASIS_redux_init (struct GNUNET_CURL_Context *ctx);
+
+
+/**
+ * Terminate reducer subsystem.
+ */
+void
+ANASTASIS_redux_done (void);
+
+
+/**
+ * Produce an initial state with an initialized list of
+ * continents.
+ */
+json_t *
+ANASTASIS_REDUX_load_continents_ (void);
+
+
+/**
+ * Returns the enum value to a string value of a state.
+ *
+ * @param state_string
+ * @return #ANASTASIS_GENERIC_STATE_ERROR on error
+ */
+enum ANASTASIS_GenericState
+ANASTASIS_generic_state_from_string_ (const char *state_string);
+
+
+/**
+ * Returns the string value of a state.
+ *
+ * @param state_string
+ * @return NULL on error
+ */
+const char *
+ANASTASIS_generic_state_to_string_ (enum ANASTASIS_GenericState gs);
+
+
+/**
+ * Returns the enum value to a string value of a state.
+ *
+ * @param state_string
+ * @return #ANASTASIS_BACKUP_STATE_ERROR on error
+ */
+enum ANASTASIS_BackupState
+ANASTASIS_backup_state_from_string_ (const char *state_string);
+
+
+/**
+ * Returns the string value of a state.
+ *
+ * @param state_string
+ * @return NULL on error
+ */
+const char *
+ANASTASIS_backup_state_to_string_ (enum ANASTASIS_BackupState bs);
+
+
+/**
+ * Returns the enum value to a string value of a state.
+ *
+ * @param state_string
+ * @return XXX on error
+ */
+enum ANASTASIS_RecoveryState
+ANASTASIS_recovery_state_from_string_ (const char *state_string);
+
+
+/**
+ * Returns the string value of a state.
+ *
+ * @param state_string
+ * @return NULL on error
+ */
+const char *
+ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs);
+
+
+/**
+ * Function to return a json error response.
+ *
+ * @param cb callback to give error to
+ * @param cb_cls callback closure
+ * @param ec error code
+ * @param detail error detail
+ */
+void
+ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode ec,
+ const char *detail);
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_provider" action. Adds another Anastasis provider
+ * to the list of available providers for storing information.
+ *
+ * @param state state to operate on
+ * @param arguments arguments with a provider URL to add
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return true if @a cb was invoked
+ */
+bool
+ANASTASIS_add_provider_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Adds the server configuration of the Anastasis provider
+ * at @a url to the json @a state. Checks if we have
+ * the provider information already available. If so,
+ * imports it into @a state. If not, queries the provider,
+ * generating a success or failure outcome asynchronously.
+ *
+ * @param cr the config request
+ * @param[in,out] state the json state to operate on
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return handle to cancel asynchronous operation, NULL if
+ * we completed synchronously
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_add_provider_to_state_ (const char *url,
+ json_t *state,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * A generic DispatchHandler/Callback function which is called for a
+ * "back" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL (no asynchronous action)
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_back_generic_decrement_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Function to load json containing all countries.
+ * Returns the countries.
+ *
+ * @return json_t *
+ */
+const json_t *
+ANASTASIS_redux_countries_init_ (void);
+
+
+/**
+ * Operates on a recovery state depending on given #ANASTASIS_RecoveryState
+ * and #ANASTASIS_RecoveryAction. The new #ANASTASIS_RecoveryState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param[in,out] state input/output state (to be modified)
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_recovery_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_user_attributes" action after verifying that the
+ * arguments provided were OK and the state transition was
+ * initiated. Begins the actual recovery logic.
+ *
+ * Returns an #ANASTASIS_ReduxAction.
+ *
+ * @param state state to operate on
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_user_attributes" action after verifying that the
+ * arguments provided were OK and the state transition was
+ * initiated. Begins the actual backup logic.
+ *
+ * Returns an #ANASTASIS_ReduxAction.
+ *
+ * @param state state to operate on
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_backup_begin_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Operates on a backup state depending on given #ANASTASIS_BackupState
+ * and #ANASTASIS_BackupAction. The new #ANASTASIS_BackupState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param[in,out] state input/output state (to be modified)
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_backup_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Generic container for an action with asynchronous activities.
+ */
+struct ANASTASIS_ReduxAction
+{
+ /**
+ * Function to call to clean up.
+ */
+ void (*cleanup)(void *cls);
+
+ /**
+ * Action-specific state, closure for @e cleanup.
+ */
+ void *cleanup_cls;
+};
+
+
+#endif
diff --git a/src/reducer/validation_CH_AHV.c b/src/reducer/validation_CH_AHV.c
new file mode 100644
index 0000000..e6a2f25
--- /dev/null
+++ b/src/reducer/validation_CH_AHV.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 redux/validation_CH_AHV.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <string.h>
+#include <stdbool.h>
+
+/**
+ * Function to validate a Swiss AHV number.
+ *
+ * @param avh_number ahv number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+CH_AHV_check (const char *ahv_number)
+{
+ unsigned int checknum;
+ unsigned int next_ten;
+ const char *pos = &ahv_number[strlen (ahv_number) - 1];
+ bool phase = true;
+ unsigned int calculation = 0;
+
+ checknum = *pos - 48;
+ while (pos > ahv_number)
+ {
+ pos--;
+ if ('.' == *pos)
+ continue;
+ if (phase)
+ calculation += ((*pos - 48) * 3);
+ else
+ calculation += *pos - 48;
+ phase = ! phase;
+ }
+ /* round up to the next ten */
+ next_ten = ((calculation + 9) / 10) * 10;
+ calculation = next_ten - calculation;
+ return (checknum == calculation);
+}
diff --git a/src/reducer/validation_CZ_BN.c b/src/reducer/validation_CZ_BN.c
new file mode 100644
index 0000000..4598c42
--- /dev/null
+++ b/src/reducer/validation_CZ_BN.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 redux/validation_CZ_BN.c
+ * @brief validation of Czeck Birth Numbers
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <gcrypt.h>
+
+/**
+ * Function to validate a Check birth number. Basically,
+ * if it has 10 digits, it must be divisible by 11.
+ *
+ * @param b_number birth number to validate (input)
+ * @return true if b_number is valid
+ */
+bool
+CZ_BN_check (const char *b_number)
+{
+ unsigned long long n;
+ char dummy;
+ char in[11];
+
+ if (10 == strlen (b_number))
+ return true;
+ if (11 != strlen (b_number))
+ return false;
+ if (b_number[6] != '/')
+ return false;
+ memcpy (in,
+ b_number,
+ 6);
+ memcpy (&in[6],
+ &b_number[7],
+ 4);
+ in[10] = '\0';
+ if (1 != sscanf (in,
+ "%llu%c",
+ &n,
+ &dummy))
+ return false;
+ return 0 == (n % 11);
+}
diff --git a/src/reducer/validation_DE_SVN.c b/src/reducer/validation_DE_SVN.c
new file mode 100644
index 0000000..bfc406d
--- /dev/null
+++ b/src/reducer/validation_DE_SVN.c
@@ -0,0 +1,98 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 redux/validation_DE_SVN.c
+ * @brief
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <string.h>
+#include <stdbool.h>
+
+/**
+ * Sum up all digits in @a v and return the result.
+ */
+static unsigned int
+q (unsigned int v)
+{
+ unsigned int r = 0;
+
+ while (0 != v)
+ {
+ r += v % 10;
+ v = v / 10;
+ }
+ return r;
+}
+
+
+/**
+ * Function to validate a German Social Security number.
+ *
+ * See https://www.financescout24.de/wissen/ratgeber/sozialversicherungsnummer
+ * and https://de.wikipedia.org/wiki/Versicherungsnummer
+ * for the structure!
+ *
+ * @param avh_number ahv number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+DE_SVN_check (const char *ssn_number)
+{
+ static const unsigned int factors[] = {
+ 2, 1, 2, 5, 7, 1, 2, 1, 2, 1, 2, 1
+ };
+ unsigned int sum = 0;
+
+ if (strlen (ssn_number) != 12)
+ return false;
+ for (unsigned int i = 0; i<8; i++)
+ {
+ unsigned char c = (unsigned char) ssn_number[i];
+
+ if ( ('0' > c) || ('9' < c) )
+ return false;
+ sum += q ((c - '0') * factors[i]);
+ }
+ {
+ unsigned char c = (unsigned char) ssn_number[8];
+ unsigned int v = (c - 'A' + 1);
+
+ if ( ('A' > c) || ('Z' < c) )
+ return false;
+ sum += q ((v / 10) * factors[8]);
+ sum += q ((v % 10) * factors[9]);
+ }
+ for (unsigned int i = 9; i<11; i++)
+ {
+ unsigned char c = ssn_number[i];
+
+ if ( ('0' > c) || ('9' < c) )
+ return false;
+ sum += q ((c - '0') * factors[i + 1]);
+ }
+ if (ssn_number[11] != '0' + (sum % 10))
+ return false;
+ {
+ unsigned int month = (ssn_number[4] - '0') * 10 + (ssn_number[5] - '0');
+
+ if ( (0 == month) ||
+ (12 < month) )
+ return false;
+ }
+ return true;
+}
diff --git a/src/reducer/validation_DE_TIN.c b/src/reducer/validation_DE_TIN.c
new file mode 100644
index 0000000..9c97365
--- /dev/null
+++ b/src/reducer/validation_DE_TIN.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 redux/validation_DE_TIN.c
+ * @brief validation logic for German taxpayer identification numbers
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+
+
+/**
+ * Function to validate a German Taxpayer identification number.
+ *
+ * See https://de.wikipedia.org/wiki/Steuerliche_Identifikationsnummer
+ * for the structure!
+ *
+ * @param tin_number tax number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+DE_TIN_check (const char *tin_number)
+{
+ unsigned int csum;
+ unsigned int product = 10;
+
+ if (strlen (tin_number) != 11)
+ return false;
+ for (unsigned int i = 0; i<10; i++)
+ {
+ unsigned int sum = ((tin_number[i] - '0') + product) % 10;
+ if (0 == sum)
+ sum = 10;
+ product = sum * 2 % 11;
+ }
+ csum = 11 - product;
+ if (10 == csum)
+ csum = 0;
+ if (tin_number[10] != '0' + csum)
+ return false;
+ if (tin_number[0] == '0')
+ return false;
+ return true;
+}
diff --git a/src/reducer/validation_IN_AADHAR.c b/src/reducer/validation_IN_AADHAR.c
new file mode 100644
index 0000000..939ee72
--- /dev/null
+++ b/src/reducer/validation_IN_AADHAR.c
@@ -0,0 +1,113 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 redux/validation_IN_AADHAR.c
+ * @brief validation logic for Indian Aadhar numbers
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+
+/**
+ * The multiplication table.
+ */
+static int m[10][10] = {
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 0, 6, 7, 8, 9, 5},
+ {2, 3, 4, 0, 1, 7, 8, 9, 5, 6},
+ {3, 4, 0, 1, 2, 8, 9, 5, 6, 7},
+ {4, 0, 1, 2, 3, 9, 5, 6, 7, 8},
+ {5, 9, 8, 7, 6, 0, 4, 3, 2, 1},
+ {6, 5, 9, 8, 7, 1, 0, 4, 3, 2},
+ {7, 6, 5, 9, 8, 2, 1, 0, 4, 3},
+ {8, 7, 6, 5, 9, 3, 2, 1, 0, 4},
+ {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
+};
+
+
+/**
+ * The permutation table.
+ */
+static int p[10][10] = {
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 5, 7, 6, 2, 8, 3, 0, 9, 4},
+ {5, 8, 0, 3, 7, 9, 6, 1, 4, 2},
+ {8, 9, 1, 6, 0, 4, 3, 5, 2, 7},
+ {9, 4, 5, 3, 1, 2, 6, 8, 7, 0},
+ {4, 2, 8, 6, 5, 7, 3, 9, 0, 1},
+ {2, 7, 9, 3, 8, 0, 6, 4, 1, 5},
+ {7, 0, 4, 6, 9, 1, 3, 2, 5, 8}
+};
+
+
+/**
+ * Converts a string to a reversed integer array.
+ *
+ * @param input The numeric string data converted to reversed int array.
+ * @param[out] Integer array containing the digits in the numeric string
+ * in reverse order
+ */
+static bool
+string_to_vals (const char *input,
+ int output[12])
+{
+ unsigned int off = 0;
+
+ for (unsigned int i = 0; i < 12;)
+ {
+ int c = input[i + off];
+
+ if (0 == c)
+ return false;
+ if (isspace (c))
+ {
+ off++;
+ continue;
+ }
+ if (! isdigit (c))
+ return false;
+ output[11 - i++] = c - '0';
+ }
+ if ('\0' != input[12 + off])
+ return false;
+ return true;
+}
+
+
+/**
+ * Function to validate an Indian Aadhar number.
+ *
+ * See https://www.geeksforgeeks.org/how-to-check-aadhar-number-is-valid-or-not-using-regular-expression/
+ * and http://en.wikipedia.org/wiki/Verhoeff_algorithm/.
+ *
+ * @param aadhar_number aadhar number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+IN_AADHAR_check (const char *aadhar_number)
+{
+ int c = 0;
+ int vals[12];
+
+ if (! string_to_vals (aadhar_number,
+ vals))
+ return false;
+ for (unsigned int i = 0; i < 12; i++)
+ c = m[c][p[(i % 8)][vals[i]]];
+
+ return (0 == c);
+}
diff --git a/src/reducer/validation_IT_CF.c b/src/reducer/validation_IT_CF.c
new file mode 100644
index 0000000..ecdad90
--- /dev/null
+++ b/src/reducer/validation_IT_CF.c
@@ -0,0 +1,198 @@
+/*
+ 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 redux/validation_IT_CF.c
+ * @brief validation of Italian Code Fiscales
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <math.h>
+
+struct MapEntry
+{
+ char in;
+ unsigned int out;
+};
+
+
+static const struct MapEntry odd[] = {
+ { '0', 1},
+ { '9', 21},
+ { 'I', 19 },
+ { 'R', 8},
+ { '1', 0},
+ { 'A', 1},
+ { 'J', 21},
+ { 'S', 12},
+ {'2', 5},
+ {'B', 0},
+ {'K', 2},
+ {'T', 14},
+ {'3', 7},
+ {'C', 5},
+ {'L', 4},
+ {'U', 16},
+ {'4', 9},
+ {'D', 7},
+ {'M', 18},
+ {'V', 10},
+ {'5', 13},
+ {'E', 9},
+ {'N', 20},
+ {'W', 22},
+ {'6', 15},
+ {'F', 13},
+ {'O', 11},
+ {'X', 25},
+ {'7', 17},
+ {'G', 15},
+ {'P', 3},
+ {'Y', 24},
+ {'8', 19},
+ {'H', 17},
+ {'Q', 6},
+ {'Z', 23},
+ {'\0', 0}
+};
+
+
+static const struct MapEntry even[] = {
+ { '0', 0},
+ { '1', 1},
+ { '2', 2 },
+ { '3', 3},
+ { '4', 4},
+ { '5', 5},
+ { '6', 6 },
+ { '7', 7 },
+ {'8', 8},
+ {'9', 9},
+ {'A', 0},
+ {'B', 1},
+ {'C', 2},
+ {'D', 3},
+ {'E', 4},
+ {'F', 5},
+ {'G', 6},
+ {'H', 7},
+ {'I', 8},
+ {'J', 9},
+ {'K', 10},
+ {'L', 11},
+ {'M', 12},
+ {'N', 13},
+ {'O', 14},
+ {'P', 15},
+ {'Q', 16},
+ {'R', 17},
+ {'S', 18},
+ {'T', 19},
+ {'U', 20},
+ {'V', 21},
+ {'W', 22},
+ {'X', 23},
+ {'Y', 24},
+ {'Z', 25},
+ {'\0', 0}
+};
+
+
+static const struct MapEntry rem[] = {
+ {'A', 0},
+ {'B', 1},
+ {'C', 2},
+ {'D', 3},
+ {'E', 4},
+ {'F', 5},
+ {'G', 6},
+ {'H', 7},
+ {'I', 8},
+ {'J', 9},
+ {'K', 10},
+ {'L', 11},
+ {'M', 12},
+ {'N', 13},
+ {'O', 14},
+ {'P', 15},
+ {'Q', 16},
+ {'R', 17},
+ {'S', 18},
+ {'T', 19},
+ {'U', 20},
+ {'V', 21},
+ {'W', 22},
+ {'X', 23},
+ {'Y', 24},
+ {'Z', 25},
+ {'\0', 0}
+};
+
+
+/**
+ * Lookup @a in in @a map. Set @a fail to true if @a in is not found.
+ *
+ * @param map character map to search
+ * @param in character to search for
+ * @param[out] fail set to true on error
+ * @return map value, 0 on error
+ */
+static unsigned int
+lookup (const struct MapEntry *map,
+ char in,
+ bool *fail)
+{
+ for (unsigned int i = 0; '\0' != map[i].in; i++)
+ if (in == map[i].in)
+ return map[i].out;
+ *fail = true;
+ return 0;
+}
+
+
+/**
+ * Function to validate an italian code fiscale number.
+ * See https://en.wikipedia.org/wiki/Italian_fiscal_code
+ *
+ * @param cf_number square number to validate (input)
+ * @return true if @a cf_number is a valid, else false
+ */
+bool
+IT_CF_check (const char *cf_number)
+{
+ unsigned int sum = 0;
+ bool fail = false;
+
+ if (strlen (cf_number) != 16)
+ return false;
+ for (unsigned int i = 0; i<15; i += 2)
+ sum += lookup (odd,
+ cf_number[i],
+ &fail);
+ for (unsigned int i = 1; i<15; i += 2)
+ sum += lookup (even,
+ cf_number[i],
+ &fail);
+ sum %= 26;
+ if (sum != lookup (rem,
+ cf_number[15],
+ &fail))
+ return false;
+ if (fail)
+ return false;
+ return true;
+}
diff --git a/src/reducer/validation_XX_SQUARE.c b/src/reducer/validation_XX_SQUARE.c
new file mode 100644
index 0000000..fa3ebfb
--- /dev/null
+++ b/src/reducer/validation_XX_SQUARE.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 redux/validation_XX_PRIME.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <math.h>
+
+/**
+ * Function to validate a square number.
+ *
+ * @param sq_number square number to validate (input)
+ * @return true if sq_number is a square, else false
+ */
+bool
+XX_SQUARE_check (const char *sq_number)
+{
+ unsigned long long n;
+ unsigned long long r;
+ char dummy;
+
+ if (1 != sscanf (sq_number,
+ "%llu%c",
+ &n,
+ &dummy))
+ return false;
+ r = sqrt (n);
+ return (n == r * r);
+}
diff --git a/src/reducer/validation_XY_PRIME.c b/src/reducer/validation_XY_PRIME.c
new file mode 100644
index 0000000..ab24014
--- /dev/null
+++ b/src/reducer/validation_XY_PRIME.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 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 redux/validation_XY_PRIME.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <gcrypt.h>
+
+/**
+ * Function to validate a prime number.
+ *
+ * @param pr_number prime number to validate (input)
+ * @return true if pr_number is a prime, else false
+ */
+bool
+XY_PRIME_check (const char *pr_number)
+{
+ unsigned long long n;
+ char dummy;
+ gcry_mpi_t p;
+ bool is_prime;
+
+ if (1 != sscanf (pr_number,
+ "%llu%c",
+ &n,
+ &dummy))
+ return false;
+ p = gcry_mpi_set_ui (NULL,
+ (unsigned long) n);
+ is_prime = (0 == gcry_prime_check (p,
+ 0));
+ gcry_mpi_release (p);
+ return is_prime;
+}