summaryrefslogtreecommitdiff
path: root/src/reducer/anastasis_api_recovery_redux.c
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/anastasis_api_recovery_redux.c
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/anastasis_api_recovery_redux.c')
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c2558
1 files changed, 2558 insertions, 0 deletions
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;
+}