diff options
Diffstat (limited to 'src/reducer/anastasis_api_recovery_redux.c')
-rw-r--r-- | src/reducer/anastasis_api_recovery_redux.c | 1863 |
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; } |