summaryrefslogtreecommitdiff
path: root/src/reducer/anastasis_api_recovery_redux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/reducer/anastasis_api_recovery_redux.c')
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c1863
1 files changed, 826 insertions, 1037 deletions
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
index 897a6dd..e795c55 100644
--- a/src/reducer/anastasis_api_recovery_redux.c
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -3,14 +3,14 @@
Copyright (C) 2020, 2021 Anastasis SARL
Anastasis is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
+ terms of the GNU 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 Affero General Public License for more details.
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- You should have received a copy of the GNU Affero General Public License along with
+ 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/>
*/
/**
@@ -44,7 +44,7 @@ ANASTASIS_recovery_state_from_string_ (const char *state_string)
if (0 == strcmp (state_string,
recovery_strings[i]))
return i;
- return ANASTASIS_RECOVERY_STATE_ERROR;
+ return ANASTASIS_RECOVERY_STATE_INVALID;
}
@@ -83,11 +83,79 @@ json_t *
ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
json_t *initial_state;
+ const char *external_reducer = ANASTASIS_REDUX_probe_external_reducer ();
+
+ if (NULL != external_reducer)
+ {
+ int pipefd_stdout[2];
+ pid_t pid = 0;
+ int status;
+ FILE *reducer_stdout;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Using external reducer '%s' for recovery start status\n",
+ external_reducer);
+
+ GNUNET_assert (0 == pipe (pipefd_stdout));
+ pid = fork ();
+ if (pid == 0)
+ {
+ (void) close (pipefd_stdout[0]);
+ (void) dup2 (pipefd_stdout[1],
+ STDOUT_FILENO);
+ execlp (external_reducer,
+ external_reducer,
+ "-r",
+ NULL);
+ GNUNET_assert (0);
+ }
+
+ close (pipefd_stdout[1]);
+ reducer_stdout = fdopen (pipefd_stdout[0],
+ "r");
+ {
+ json_error_t err;
+
+ initial_state = json_loadf (reducer_stdout,
+ 0,
+ &err);
+
+ if (NULL == initial_state)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "External reducer did not output valid JSON: %s:%d:%d %s\n",
+ err.source,
+ err.line,
+ err.column,
+ err.text);
+ GNUNET_assert (0 == fclose (reducer_stdout));
+ waitpid (pid, &status, 0);
+ return NULL;
+ }
+ }
+
+ GNUNET_assert (NULL != initial_state);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Waiting for external reducer to terminate.\n");
+ GNUNET_assert (0 == fclose (reducer_stdout));
+ reducer_stdout = NULL;
+ waitpid (pid, &status, 0);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "External reducer finished with exit status '%d'\n",
+ status);
+ return initial_state;
+ }
(void) cfg;
initial_state = ANASTASIS_REDUX_load_continents_ ();
if (NULL == initial_state)
return NULL;
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (initial_state,
+ "reducer_type",
+ json_string ("recovery")));
set_state (initial_state,
ANASTASIS_RECOVERY_STATE_CONTINENT_SELECTING);
return initial_state;
@@ -188,15 +256,16 @@ sctx_free (void *cls)
/**
- * Update @a state to reflect the error provided in @a rc.
+ * Call the action callback with an error result
*
- * @param[in,out] state state to update
+ * @param cb action callback to call
+ * @param cb_cls closure for @a cb
* @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)
+void
+fail_by_error (ANASTASIS_ActionCallback cb,
+ void *cb_cls,
+ enum ANASTASIS_RecoveryStatus rc)
{
const char *msg = NULL;
enum TALER_ErrorCode ec = TALER_EC_INVALID;
@@ -249,17 +318,10 @@ update_state_by_error (json_t *state,
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_RECOVERY_STATE_ERROR);
- return ec;
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ ec,
+ msg);
}
@@ -279,7 +341,6 @@ core_secret_cb (void *cls,
size_t secret_size)
{
struct SelectChallengeContext *sctx = cls;
- enum TALER_ErrorCode ec;
sctx->r = NULL;
if (ANASTASIS_RS_SUCCESS == rc)
@@ -311,11 +372,9 @@ core_secret_cb (void *cls,
sctx_free (sctx);
return;
}
- ec = update_state_by_error (sctx->state,
- rc);
- sctx->cb (sctx->cb_cls,
- ec,
- sctx->state);
+ fail_by_error (sctx->cb,
+ sctx->cb_cls,
+ rc);
sctx_free (sctx);
}
@@ -399,7 +458,7 @@ find_challenge_in_ri (json_t *state,
/**
- * Find challenge of @a uuid in @a state under "cs".
+ * Find challenge of @a uuid in @a state under "challenges".
*
* @param state the state to search
* @param uuid the UUID to search for
@@ -412,7 +471,7 @@ find_challenge_in_cs (json_t *state,
json_t *rd = json_object_get (state,
"recovery_document");
json_t *cs = json_object_get (rd,
- "cs");
+ "challenges");
json_t *c;
size_t off;
@@ -451,7 +510,7 @@ find_challenge_in_cs (json_t *state,
* @param csr response details
*/
static void
-answer_feedback_cb (
+start_feedback_cb (
void *cls,
const struct ANASTASIS_ChallengeStartResponse *csr)
{
@@ -480,115 +539,50 @@ answer_feedback_cb (
}
switch (csr->cs)
{
- case ANASTASIS_CHALLENGE_STATUS_SOLVED:
+ case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED:
{
- json_t *rd;
-
- rd = ANASTASIS_recovery_serialize (sctx->r);
- if (NULL == rd)
- {
- GNUNET_break (0);
- set_state (sctx->state,
- ANASTASIS_RECOVERY_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;
+ json_t *instructions;
+ char *hint;
- solved = GNUNET_JSON_PACK (
+ GNUNET_asprintf (&hint,
+ _ ("Required TAN can be found in `%s'"),
+ csr->details.tan_filename);
+ instructions = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
- "solved"));
+ "code-in-file"),
+ GNUNET_JSON_pack_string ("filename",
+ csr->details.tan_filename),
+ GNUNET_JSON_pack_string ("display_hint",
+ hint));
+ GNUNET_free (hint);
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
- solved));
+ instructions));
}
- /* 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);
+ 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_INSTRUCTIONS:
+ case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED:
{
json_t *instructions;
- 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 = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("state",
- "hint"),
- GNUNET_JSON_pack_string ("hint",
- s),
- GNUNET_JSON_pack_uint64 ("http_status",
- (json_int_t) csr->details.open_challenge.
- http_status));
- GNUNET_free (s);
- }
- else if (0 == strcasecmp (mime,
- "application/json"))
- {
- json_t *body;
+ char *hint;
- 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 = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("state",
- "details"),
- GNUNET_JSON_pack_object_steal ("details",
- body),
- GNUNET_JSON_pack_uint64 ("http_status",
- csr->details.open_challenge.http_status));
- }
- }
- else
- {
- /* unexpected / unsupported mime type */
- mime = NULL;
- }
- }
- if (NULL == mime)
- {
- instructions = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("state",
- "body"),
- GNUNET_JSON_pack_data_varsize ("body",
- csr->details.open_challenge.body,
- csr->details.open_challenge.body_size),
- GNUNET_JSON_pack_uint64 ("http_status",
- csr->details.open_challenge.http_status),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("mime_type",
- mime)));
- }
+ GNUNET_asprintf (&hint,
+ _ ("TAN code was sent to `%s'"),
+ csr->details.tan_address_hint);
+ instructions = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "send-to-address"),
+ GNUNET_JSON_pack_string ("address_hint",
+ csr->details.tan_address_hint),
+ GNUNET_JSON_pack_string ("display_hint",
+ hint));
+ GNUNET_free (hint);
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
@@ -601,20 +595,24 @@ answer_feedback_cb (
sctx->state);
sctx_free (sctx);
return;
- case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION:
+
+ case ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT:
{
- json_t *redir;
+ json_t *instructions;
+ char *hint;
- redir = GNUNET_JSON_PACK (
+ GNUNET_asprintf (&hint,
+ _ ("TAN code already sent."));
+ instructions = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
- "redirect"),
- GNUNET_JSON_pack_string ("redirect_url",
- csr->details.redirect_url));
- GNUNET_assert (NULL != redir);
+ "send-to-address"),
+ GNUNET_JSON_pack_string ("display_hint",
+ hint));
+ GNUNET_free (hint);
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
- redir));
+ instructions));
}
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
@@ -623,21 +621,29 @@ answer_feedback_cb (
sctx->state);
sctx_free (sctx);
return;
- case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED:
+
+ case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED:
{
json_t *pay;
+ char *hint;
+ GNUNET_asprintf (&hint,
+ _ ("Taler payment to `%s' required"),
+ csr->details.payment_required.taler_pay_uri);
pay = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
- "payment"),
- GNUNET_JSON_pack_string ("taler_pay_uri",
- csr->details.payment_required.
- taler_pay_uri),
+ "taler-payment"),
+ GNUNET_JSON_pack_string (
+ "taler_pay_uri",
+ csr->details.payment_required.taler_pay_uri),
GNUNET_JSON_pack_string ("provider",
cd->provider_url),
+ GNUNET_JSON_pack_string ("display_hint",
+ hint),
GNUNET_JSON_pack_data_auto (
"payment_secret",
&csr->details.payment_required.payment_secret));
+ GNUNET_free (hint);
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
@@ -663,7 +669,7 @@ answer_feedback_cb (
sctx->state);
sctx_free (sctx);
return;
- case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE:
+ case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE:
{
json_t *err;
@@ -671,10 +677,9 @@ answer_feedback_cb (
GNUNET_JSON_pack_string ("state",
"server-failure"),
GNUNET_JSON_pack_uint64 ("http_status",
- csr->details.server_failure.
- http_status),
+ csr->http_status),
GNUNET_JSON_pack_uint64 ("error_code",
- csr->details.server_failure.ec));
+ csr->ec));
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
@@ -683,17 +688,19 @@ answer_feedback_cb (
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
- csr->details.server_failure.ec,
+ csr->ec,
sctx->state);
sctx_free (sctx);
return;
- case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN:
+ case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN:
{
json_t *err;
err = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
"truth-unknown"),
+ GNUNET_JSON_pack_uint64 ("http_status",
+ csr->http_status),
GNUNET_JSON_pack_uint64 ("error_code",
TALER_EC_ANASTASIS_TRUTH_UNKNOWN));
GNUNET_assert (0 ==
@@ -708,15 +715,242 @@ answer_feedback_cb (
sctx->state);
sctx_free (sctx);
return;
- case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED:
+ case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED:
+ {
+ json_t *reply;
+ json_t *c;
+ char *hint;
+
+ c = find_challenge_in_cs (sctx->state,
+ &cd->uuid);
+ if (NULL == c)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (c,
+ "async",
+ json_true ()));
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (
+ c,
+ "answer-pin",
+ json_integer (
+ csr->details.bank_transfer_required.answer_code)));
+ GNUNET_asprintf (&hint,
+ _ ("Wire %s to %s (%s) with subject %s\n"),
+ TALER_amount2s (
+ &csr->details.bank_transfer_required.amount),
+ csr->details.bank_transfer_required.target_iban,
+ csr->details.bank_transfer_required.target_business_name,
+ csr->details.bank_transfer_required.wire_transfer_subject);
+ reply = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "iban-instructions"),
+ GNUNET_JSON_pack_string (
+ "target_iban",
+ csr->details.bank_transfer_required.target_iban),
+ GNUNET_JSON_pack_string (
+ "display_hint",
+ hint),
+ GNUNET_JSON_pack_string (
+ "target_business_name",
+ csr->details.bank_transfer_required.target_business_name),
+ GNUNET_JSON_pack_string (
+ "wire_transfer_subject",
+ csr->details.bank_transfer_required.wire_transfer_subject),
+ TALER_JSON_pack_amount (
+ "challenge_amount",
+ &csr->details.bank_transfer_required.amount));
+ GNUNET_free (hint);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ reply));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (sctx->state,
+ "selected_challenge_uuid",
+ GNUNET_JSON_from_data_auto (
+ &cd->uuid)));
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+ 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);
+}
+
+
+/**
+ * Defines a callback for the response status for a challenge answer
+ * operation.
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param csr response details
+ */
+static void
+answer_feedback_cb (
+ void *cls,
+ const struct ANASTASIS_ChallengeAnswerResponse *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_ANSWER_STATUS_SOLVED:
+ {
+ json_t *rd;
+
+ rd = ANASTASIS_recovery_serialize (sctx->r);
+ if (NULL == rd)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unable to serialize recovery state");
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (sctx->state,
+ "recovery_document",
+ rd));
+ }
+ {
+ json_t *solved;
+
+ solved = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "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_ANSWER_STATUS_INVALID_ANSWER:
+ {
+ json_t *instructions;
+
+ instructions = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "incorrect-answer"),
+ GNUNET_JSON_pack_uint64 ("error_code",
+ csr->ec));
+ 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_ANSWER_STATUS_PAYMENT_REQUIRED:
+ {
+ json_t *pay;
+ char *hint;
+
+ GNUNET_asprintf (&hint,
+ _ ("Taler payment to `%s' required"),
+ csr->details.payment_required.taler_pay_uri);
+ pay = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("state",
+ "taler-payment"),
+ GNUNET_JSON_pack_string (
+ "taler_pay_uri",
+ csr->details.payment_required.taler_pay_uri),
+ GNUNET_JSON_pack_string (
+ "display_hint",
+ hint),
+ GNUNET_JSON_pack_string ("provider",
+ cd->provider_url),
+ GNUNET_JSON_pack_data_auto (
+ "payment_secret",
+ &csr->details.payment_required.payment_secret));
+ GNUNET_free (hint);
+ 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_ANSWER_STATUS_SERVER_FAILURE:
{
json_t *err;
err = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
- "rate-limit-exceeded"),
+ "server-failure"),
+ GNUNET_JSON_pack_uint64 ("http_status",
+ csr->http_status),
GNUNET_JSON_pack_uint64 ("error_code",
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED));
+ csr->ec));
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
@@ -725,119 +959,71 @@ answer_feedback_cb (
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
+ csr->ec,
sctx->state);
sctx_free (sctx);
return;
- case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT:
+ case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN:
{
json_t *err;
err = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
- "authentication-timeout"),
+ "truth-unknown"),
+ GNUNET_JSON_pack_uint64 ("http_status",
+ csr->http_status),
GNUNET_JSON_pack_uint64 ("error_code",
- TALER_EC_ANASTASIS_TRUTH_AUTH_TIMEOUT));
+ TALER_EC_ANASTASIS_TRUTH_UNKNOWN));
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_ANASTASIS_TRUTH_AUTH_TIMEOUT,
+ TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
sctx->state);
sctx_free (sctx);
return;
-
- case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS:
+ case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED:
{
- const json_t *body = csr->details.external_challenge;
- const char *method;
- json_t *details;
- bool is_async = false;
- uint64_t code = 0;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("method",
- &method),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("async",
- &is_async)),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 ("answer_code",
- &code)),
- GNUNET_JSON_spec_json ("details",
- &details),
- GNUNET_JSON_spec_end ()
- };
- json_t *reply;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (body,
- spec,
- NULL, NULL))
- {
- json_t *err;
-
- GNUNET_break_op (0);
- err = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("state",
- "server-failure"),
- GNUNET_JSON_pack_uint64 ("error_code",
- TALER_EC_GENERIC_REPLY_MALFORMED));
- GNUNET_assert (0 ==
- json_object_set_new (feedback,
- uuid,
- err));
- return;
- }
- if (is_async)
- {
- json_t *c = find_challenge_in_cs (sctx->state,
- &cd->uuid);
-
- if (NULL == c)
- {
- GNUNET_break (0);
- set_state (sctx->state,
- ANASTASIS_RECOVERY_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 (c,
- "async",
- json_true ()));
- GNUNET_assert (0 ==
- json_object_set_new (c,
- "answer-pin",
- json_integer (code)));
- }
- reply = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("state",
- "external-instructions"),
- GNUNET_JSON_pack_string ("method",
- method),
- GNUNET_JSON_pack_object_incref ("details",
- details));
- GNUNET_JSON_parse_free (spec);
+ json_t *err;
+ char *hint;
+
+ GNUNET_asprintf (
+ &hint,
+ _ ("exceeded limit of %llu attempts in %s"),
+ (unsigned long long) csr->details.rate_limit_exceeded.request_limit,
+ GNUNET_TIME_relative2s (
+ csr->details.rate_limit_exceeded.request_frequency,
+ true));
+ err = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "state",
+ "rate-limit-exceeded"),
+ GNUNET_JSON_pack_string (
+ "display_hint",
+ hint),
+ GNUNET_JSON_pack_uint64 (
+ "request_limit",
+ csr->details.rate_limit_exceeded.request_limit),
+ GNUNET_JSON_pack_time_rel (
+ "request_frequency",
+ csr->details.rate_limit_exceeded.request_frequency),
+ GNUNET_JSON_pack_uint64 (
+ "error_code",
+ TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED));
+ GNUNET_free (hint);
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
- reply));
+ err));
}
- json_object_set_new (sctx->state,
- "selected_challenge_uuid",
- GNUNET_JSON_from_data_auto (&cd->uuid));
set_state (sctx->state,
- ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
- TALER_EC_NONE,
+ TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
sctx->state);
sctx_free (sctx);
return;
@@ -873,7 +1059,8 @@ solve_challenge_cb (void *cls,
struct GNUNET_JSON_Specification tspec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("timeout",
- &timeout)),
+ &timeout),
+ NULL),
GNUNET_JSON_spec_end ()
};
struct GNUNET_JSON_Specification pspec[] = {
@@ -1138,21 +1325,19 @@ solve_challenge_cb (void *cls,
sctx_free (sctx);
return;
}
- ret = ANASTASIS_challenge_start (ci,
- psp,
- timeout,
- &hashed_answer,
- &answer_feedback_cb,
- sctx);
+ ret = ANASTASIS_challenge_answer3 (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,
+ &start_feedback_cb,
sctx);
}
}
@@ -1272,9 +1457,7 @@ pay_challenge_cb (void *cls,
{
ret = ANASTASIS_challenge_start (ci,
&sctx->ps,
- sctx->timeout,
- NULL, /* no answer yet */
- &answer_feedback_cb,
+ &start_feedback_cb,
sctx);
}
if (GNUNET_OK != ret)
@@ -1468,7 +1651,8 @@ pay_challenge (json_t *state,
struct GNUNET_JSON_Specification aspec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("timeout",
- &timeout)),
+ &timeout),
+ NULL),
GNUNET_JSON_spec_fixed_auto ("payment_secret",
&sctx->ps),
GNUNET_JSON_spec_end ()
@@ -1566,7 +1750,8 @@ select_challenge_cb (void *cls,
struct GNUNET_JSON_Specification tspec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("timeout",
- &timeout)),
+ &timeout),
+ NULL),
GNUNET_JSON_spec_end ()
};
struct GNUNET_JSON_Specification pspec[] = {
@@ -1684,10 +1869,13 @@ select_challenge_cb (void *cls,
json_object_set_new (sctx->state,
"selected_challenge_uuid",
GNUNET_JSON_from_data_auto (&cd->uuid)));
- if (0 == strcmp ("question",
- cd->type))
+ if ( (0 == strcmp ("question",
+ cd->type)) ||
+ (0 == strcmp ("totp",
+ cd->type)) )
{
- /* security question, immediately request user to answer it */
+ /* security question or TOTP:
+ immediately request user to answer it */
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
sctx->cb (sctx->cb_cls,
@@ -1718,9 +1906,7 @@ select_challenge_cb (void *cls,
{
ret = ANASTASIS_challenge_start (ci,
psp,
- timeout,
- NULL, /* no answer */
- &answer_feedback_cb,
+ &start_feedback_cb,
sctx);
}
}
@@ -1856,256 +2042,15 @@ back_challenge_solving (json_t *state,
/**
- * 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 = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("version",
- version),
- GNUNET_JSON_pack_object_incref ("identity_attributes",
- (json_t *) ia),
- GNUNET_JSON_pack_string ("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);
-
-
-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,
- "poll",
- &poll_challenges
- },
- {
- 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.
+ * Redux action handle associated with this state.
*/
- struct PolicyDownloadEntry *next;
+ struct ANASTASIS_ReduxAction ra;
/**
* Backend we are querying.
@@ -2113,289 +2058,46 @@ struct PolicyDownloadEntry
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
+ * Function to call with the result.
*/
ANASTASIS_ActionCallback cb;
/**
- * closure for action callback @e cb.
+ * Closure for @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.
+ * State we are using.
*/
- unsigned int pending;
+ json_t *state;
- /**
- * Is @e version set?
- */
- bool have_version;
};
/**
- * Function to free a `struct RecoverSecretState`
+ * Free @a cls data structure.
*
- * @param cls must be a `struct RecoverSecretState`
+ * @param[in] cls data structure to free, must be a `struct PolicyDownloadEntry *`
*/
static void
-free_rss (void *cls)
+free_pd (void *cls)
{
- struct RecoverSecretState *rss = cls;
- struct PolicyDownloadEntry *pd;
- struct RecoveryStartStateProviderEntry *pe;
+ struct PolicyDownloadEntry *pd = cls;
- 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))
+ if (NULL != pd->recovery)
{
- 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);
+ ANASTASIS_recovery_abort (pd->recovery);
+ pd->recovery = NULL;
}
- 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 rc 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);
+ json_decref (pd->state);
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 */
}
@@ -2404,40 +2106,34 @@ lookup_cost (const json_t *state,
* allow the user to specify alternative providers and/or policy
* versions.
*
- * @param[in] rss state to fail with the policy download
+ * @param[in] pd 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,
+return_no_policy (struct PolicyDownloadEntry *pd,
bool offline)
{
- json_t *msg;
+ enum TALER_ErrorCode ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED;
+ const char *detail = (offline)
+ ? "could not contact provider (offline)"
+ : "provider does not know this policy";
+ json_t *estate;
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No provider online, need user to manually specify providers!\n");
- msg = GNUNET_JSON_PACK (
+ "Provider offline!\n");
+ estate = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("detail",
+ detail)),
+ GNUNET_JSON_pack_uint64 ("code",
+ ec),
GNUNET_JSON_pack_string ("hint",
- offline
- ? "could not contact provider"
- : "provider does not know you"),
- GNUNET_JSON_pack_bool ("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);
+ TALER_ErrorCode_get_hint (ec)));
+ pd->cb (pd->cb_cls,
+ ec,
+ estate);
+ free_pd (pd);
}
@@ -2450,7 +2146,7 @@ return_no_policy (struct RecoverSecretState *rss,
* cancel all of the others, passing the obtained recovery information
* back to the user.
*
- * @param cls closure for the callback
+ * @param cls closure for the callback with a `struct PolicyDownloadEntry *`
* @param ri recovery information struct which contains the policies
*/
static void
@@ -2458,7 +2154,6 @@ 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;
@@ -2466,16 +2161,10 @@ policy_lookup_cb (void *cls,
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,
+ return_no_policy (pd,
false);
return;
}
@@ -2514,37 +2203,15 @@ policy_lookup_cb (void *cls,
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_RECOVERY_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 = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("uuid",
&cd->uuid),
- TALER_JSON_pack_amount ("cost",
- (GNUNET_NO == ret)
- ? NULL
- : &cost),
GNUNET_JSON_pack_string ("type",
cd->type),
+ GNUNET_JSON_pack_string ("uuid-display",
+ ANASTASIS_CRYPTO_uuid2s (&cd->uuid)),
GNUNET_JSON_pack_string ("instructions",
cd->instructions));
GNUNET_assert (0 ==
@@ -2564,7 +2231,7 @@ policy_lookup_cb (void *cls,
GNUNET_JSON_pack_uint64 ("version",
ri->version));
GNUNET_assert (0 ==
- json_object_set_new (rss->state,
+ json_object_set_new (pd->state,
"recovery_information",
recovery_information));
{
@@ -2574,229 +2241,387 @@ policy_lookup_cb (void *cls,
if (NULL == rd)
{
GNUNET_break (0);
- set_state (rss->state,
- ANASTASIS_RECOVERY_STATE_ERROR);
- rss->cb (rss->cb_cls,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- rss->state);
- free_rss (rss);
+ ANASTASIS_redux_fail_ (pd->cb,
+ pd->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unable to serialize recovery state");
+ free_pd (pd);
return;
}
GNUNET_assert (0 ==
- json_object_set_new (rss->state,
+ json_object_set_new (pd->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);
+ set_state (pd->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ pd->cb (pd->cb_cls,
+ TALER_EC_NONE,
+ pd->state);
+ free_pd (pd);
}
/**
- * Try to launch recovery at provider @a provider_url with config @a p_cfg.
+ * 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[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
+ * @param cls a `struct PolicyDownloadEntry`
+ * @param rc 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 bool
-launch_recovery (struct RecoverSecretState *rss,
- const char *provider_url,
- const json_t *p_cfg)
+static void
+core_early_secret_cb (void *cls,
+ enum ANASTASIS_RecoveryStatus rc,
+ const void *secret,
+ size_t secret_size)
{
- struct PolicyDownloadEntry *pd = GNUNET_new (struct PolicyDownloadEntry);
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("salt",
- &pd->salt),
- GNUNET_JSON_spec_end ()
- };
+ struct PolicyDownloadEntry *pd = cls;
- if (MHD_HTTP_OK !=
- json_integer_value (json_object_get (p_cfg,
- "http_status")))
- return false; /* skip providers that are down */
- 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;
+ pd->recovery = NULL;
+ GNUNET_assert (NULL == secret);
+ GNUNET_assert (ANASTASIS_RS_SUCCESS != rc);
+ fail_by_error (pd->cb,
+ pd->cb_cls,
+ rc);
+ free_pd (pd);
}
/**
- * We finished downloading /config from all providers, merge
- * into the main state, trigger the continuation and free our
- * state.
+ * DispatchHandler/Callback function which is called for a
+ * "next" action in "secret_selecting" state.
*
- * @param[in] rss main state to merge into
+ * @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 void
-providers_complete (struct RecoverSecretState *rss)
+static struct ANASTASIS_ReduxAction *
+done_secret_selecting (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
{
- bool launched = false;
- struct RecoveryStartStateProviderEntry *pe;
- json_t *tlist;
+ uint32_t mask;
+ const json_t *pa;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("attribute_mask",
+ &mask),
+ GNUNET_JSON_spec_array_const ("providers",
+ &pa),
+ GNUNET_JSON_spec_end ()
+ };
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("provider_salt",
+ &provider_salt),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *p_cfg;
+ json_t *id_data;
+ const json_t *providers;
- tlist = json_object_get (rss->state,
- "authentication_providers");
- if (NULL == tlist)
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
{
- tlist = json_object ();
- GNUNET_assert (NULL != tlist);
- GNUNET_assert (0 ==
- json_object_set_new (rss->state,
- "authentication_providers",
- tlist));
+ GNUNET_break (0);
+ json_dumpf (arguments,
+ stderr,
+ JSON_INDENT (2));
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ NULL);
+ return NULL;
}
- while (NULL != (pe = rss->pe_head))
+ providers = json_object_get (state,
+ "authentication_providers");
+ if ( (NULL == providers) ||
+ (! json_is_object (providers)) )
{
- 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);
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' missing");
+ return NULL;
}
- /* 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;
+ size_t poff;
+ json_t *pe;
+ uint64_t version;
const char *provider_url;
- json_object_foreach (tlist, provider_url, p_cfg)
+ json_array_foreach (pa, poff, pe)
{
- launched |= launch_recovery (rss,
- provider_url,
- p_cfg);
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_uint64 ("version",
+ &version),
+ GNUNET_JSON_spec_string ("url",
+ &provider_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (pe,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_dumpf (pe,
+ stderr,
+ JSON_INDENT (2));
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ NULL);
+ return NULL;
+ }
+
+ p_cfg = json_object_get (providers,
+ provider_url);
+ if (MHD_HTTP_OK !=
+ json_integer_value (json_object_get (p_cfg,
+ "http_status")))
+ continue;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (p_cfg,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0); /* should be impossible for well-formed state */
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "Salt unknown for selected provider");
+ return NULL;
+ }
+ id_data = json_object_get (state,
+ "identity_attributes");
+ if (NULL == id_data)
+ {
+ GNUNET_break (0); /* should be impossible for well-formed state */
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'identity_attributes' missing");
+ return NULL;
+ }
+ {
+ struct PolicyDownloadEntry *pd
+ = GNUNET_new (struct PolicyDownloadEntry);
+
+ pd->cb = cb;
+ pd->cb_cls = cb_cls;
+ pd->state = json_incref (state);
+ pd->backend_url = GNUNET_strdup (provider_url);
+ pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_,
+ id_data,
+ version,
+ pd->backend_url,
+ &provider_salt,
+ &policy_lookup_cb,
+ pd,
+ &core_early_secret_cb,
+ pd);
+ if (NULL == pd->recovery)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR,
+ NULL);
+ GNUNET_free (pd->backend_url);
+ json_decref (pd->state);
+ GNUNET_free (pd);
+ return NULL;
+ }
+ pd->ra.cleanup = &free_pd;
+ pd->ra.cleanup_cls = pd;
+ return &pd->ra;
+ }
}
}
- if (! launched)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No provider online, need user to specify different provider!\n");
- return_no_policy (rss,
- true);
- return;
- }
+
+ /* no provider worked */
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "selected provider is not online");
+ return NULL;
}
/**
- * Function called when the complete information about a provider
- * was added to @a new_state.
+ * The user wants us to add another provider. Download /config.
*
- * @param cls a `struct RecoveryStartStateProviderEntry`
- * @param error error code
- * @param new_state resulting new state
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb function to call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
*/
-static void
-provider_added_cb (void *cls,
- enum TALER_ErrorCode error,
- json_t *new_state)
+static struct ANASTASIS_ReduxAction *
+add_provider (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
{
- 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);
+ const char *provider_url;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("provider_url",
+ &provider_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ 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,
+ NULL);
+ return NULL;
+ }
+ return ANASTASIS_REDUX_add_provider_to_state_ (provider_url,
+ state,
+ cb,
+ cb_cls);
}
/**
- * Start to query provider for recovery document.
+ * Signature of callback function that implements a state transition.
*
- * @param[in,out] rss overall recovery state
- * @param provider_url base URL of the provider to query
+ * @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
*/
-static void
-begin_query_provider (struct RecoverSecretState *rss,
- const char *provider_url)
+typedef struct ANASTASIS_ReduxAction *
+(*DispatchHandler)(json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_recovery_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
{
- 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 Dispatcher
+ {
+ enum ANASTASIS_RecoveryState recovery_state;
+ const char *recovery_action;
+ DispatchHandler fun;
+ } dispatchers[] = {
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "add_provider",
+ &add_provider
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "poll_providers",
+ &ANASTASIS_REDUX_poll_providers_
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "select_version",
+ &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,
+ "sync_providers",
+ &ANASTASIS_REDUX_sync_providers_
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
+ "poll",
+ &poll_challenges
+ },
+ {
+ 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_INVALID, 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_INVALID == 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;
}
@@ -2806,11 +2631,8 @@ ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
- json_t *version;
- json_t *providers;
- const json_t *attributes;
- struct RecoverSecretState *rss;
- const char *provider_url;
+ const json_t *providers;
+ json_t *attributes;
providers = json_object_get (state,
"authentication_providers");
@@ -2836,46 +2658,13 @@ ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state,
"'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;
+ json_object_set (state,
+ "identity_attributes",
+ attributes);
+ set_state (state,
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
}