/*
This file is part of Anastasis
Copyright (C) 2020, 2021 Anastasis SARL
Anastasis is free software; you can redistribute it and/or modify it under the
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 General Public License for more details.
You should have received a copy of the GNU General Public License along with
Anastasis; see the file COPYING.GPL. If not, see
*/
/**
* @file reducer/anastasis_api_recovery_redux.c
* @brief anastasis reducer recovery api
* @author Christian Grothoff
* @author Dominik Meister
* @author Dennis Neufeld
*/
#include
#include
#include "anastasis_redux.h"
#include "anastasis_error_codes.h"
#include "anastasis_api_redux.h"
#define GENERATE_STRING(STRING) #STRING,
static const char *recovery_strings[] = {
ANASTASIS_RECOVERY_STATES (GENERATE_STRING)
};
#undef GENERATE_STRING
enum ANASTASIS_RecoveryState
ANASTASIS_recovery_state_from_string_ (const char *state_string)
{
for (enum ANASTASIS_RecoveryState i = 0;
i < sizeof (recovery_strings) / sizeof(*recovery_strings);
i++)
if (0 == strcmp (state_string,
recovery_strings[i]))
return i;
return ANASTASIS_RECOVERY_STATE_INVALID;
}
const char *
ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs)
{
if ( (rs < 0) ||
(rs >= sizeof (recovery_strings) / sizeof(*recovery_strings)) )
{
GNUNET_break_op (0);
return NULL;
}
return recovery_strings[rs];
}
static void
set_state (json_t *state,
enum ANASTASIS_RecoveryState new_recovery_state)
{
GNUNET_assert (
0 ==
json_object_set_new (
state,
"recovery_state",
json_string (ANASTASIS_recovery_state_to_string_ (new_recovery_state))));
}
/**
* Returns an initial ANASTASIS recovery state.
*
* @return NULL on failure
*/
json_t *
ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
json_t *initial_state;
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;
}
/**
* Context for a "select_challenge" operation.
*/
struct SelectChallengeContext
{
/**
* Handle we returned for cancellation of the operation.
*/
struct ANASTASIS_ReduxAction ra;
/**
* UUID of the challenge selected by the user for solving.
*/
struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
/**
* Which timeout was set for the operation?
*/
struct GNUNET_TIME_Relative timeout;
/**
* Overall recovery action.
*/
struct ANASTASIS_Recovery *r;
/**
* Function to call with the next state.
*/
ANASTASIS_ActionCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* Our state.
*/
json_t *state;
/**
* Our arguments (like answers to the challenge, if already provided).
*/
json_t *args;
/**
* Task scheduled for delayed success reporting. Needed to make
* sure that the solved challenge was really the final result,
* cancelled if the solved challenge resulted in the secret being
* recovered.
*/
struct GNUNET_SCHEDULER_Task *delayed_report;
/**
* Payment secret, if we are in the "pay" state.
*/
struct ANASTASIS_PaymentSecretP ps;
/**
* Application asked us to only poll for existing
* asynchronous challenges, and not to being a
* new one.
*/
bool poll_only;
};
/**
* Cleanup a select challenge context.
*
* @param cls a `struct SelectChallengeContext *`
*/
static void
sctx_free (void *cls)
{
struct SelectChallengeContext *sctx = cls;
if (NULL != sctx->r)
{
ANASTASIS_recovery_abort (sctx->r);
sctx->r = NULL;
}
json_decref (sctx->state);
json_decref (sctx->args);
if (NULL != sctx->delayed_report)
{
GNUNET_SCHEDULER_cancel (sctx->delayed_report);
sctx->delayed_report = NULL;
}
GNUNET_free (sctx);
}
/**
* Call the action callback with an error result
*
* @param cb action callback to call
* @param cb_cls closure for @a cb
* @param rc error code to translate to JSON
*/
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;
switch (rc)
{
case ANASTASIS_RS_SUCCESS:
GNUNET_assert (0);
break;
case ANASTASIS_RS_POLICY_DOWNLOAD_FAILED:
msg = gettext_noop ("download failed due to unexpected network issue");
ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED;
break;
case ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY:
GNUNET_break (0);
msg = gettext_noop ("policy document returned was malformed");
ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
break;
case ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG:
GNUNET_break (0);
msg = gettext_noop ("policy document too large for client memory");
ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
break;
case ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION:
GNUNET_break (0);
msg = gettext_noop ("failed to decompress policy document");
ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
break;
case ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON:
GNUNET_break (0);
msg = gettext_noop ("policy document returned was not in JSON format");
ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
break;
case ANASTASIS_RS_POLICY_MALFORMED_JSON:
GNUNET_break (0);
msg = gettext_noop (
"policy document returned was not in required JSON format");
ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
break;
case ANASTASIS_RS_POLICY_SERVER_ERROR:
msg = gettext_noop ("Anastasis server reported transient internal error");
ec = TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED;
break;
case ANASTASIS_RS_POLICY_GONE:
msg = gettext_noop ("policy document no longer exists");
ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
break;
case ANASTASIS_RS_POLICY_UNKNOWN:
msg = gettext_noop ("account unknown to Anastasis server");
ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
break;
}
ANASTASIS_redux_fail_ (cb,
cb_cls,
ec,
msg);
}
/**
* This function is called whenever the recovery process ends.
* On success, the secret is returned in @a secret.
*
* @param cls handle for the callback
* @param 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_secret_cb (void *cls,
enum ANASTASIS_RecoveryStatus rc,
const void *secret,
size_t secret_size)
{
struct SelectChallengeContext *sctx = cls;
sctx->r = NULL;
if (ANASTASIS_RS_SUCCESS == rc)
{
json_t *jsecret;
jsecret = json_loadb (secret,
secret_size,
JSON_REJECT_DUPLICATES,
NULL);
if (NULL == jsecret)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_SECRET_MALFORMED,
NULL);
sctx_free (sctx);
return;
}
GNUNET_assert (0 ==
json_object_set_new (sctx->state,
"core_secret",
jsecret));
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_RECOVERY_FINISHED);
sctx->cb (sctx->cb_cls,
TALER_EC_NONE,
sctx->state);
sctx_free (sctx);
return;
}
fail_by_error (sctx->cb,
sctx->cb_cls,
rc);
sctx_free (sctx);
}
/**
* A challenge was solved, but we are not yet finished.
* Report to caller that the challenge was completed.
*
* @param cls a `struct SelectChallengeContext`
*/
static void
report_solved (void *cls)
{
struct SelectChallengeContext *sctx = cls;
sctx->delayed_report = NULL;
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
TALER_EC_NONE,
sctx->state);
sctx_free (sctx);
}
/**
* Find challenge of @a uuid in @a state under "recovery_information".
*
* @param state the state to search
* @param uuid the UUID to search for
* @return NULL on error, otherwise challenge entry; RC is NOT incremented
*/
static json_t *
find_challenge_in_ri (json_t *state,
const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
{
struct ANASTASIS_CRYPTO_TruthUUIDP u;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("uuid",
&u),
GNUNET_JSON_spec_end ()
};
json_t *ri;
json_t *challenges;
json_t *challenge;
size_t index;
ri = json_object_get (state,
"recovery_information");
if (NULL == ri)
{
GNUNET_break (0);
return NULL;
}
challenges = json_object_get (ri,
"challenges");
if (NULL == challenges)
{
GNUNET_break (0);
return NULL;
}
json_array_foreach (challenges, index, challenge)
{
if (GNUNET_OK !=
GNUNET_JSON_parse (challenge,
spec,
NULL, NULL))
{
GNUNET_break (0);
return NULL;
}
if (0 ==
GNUNET_memcmp (&u,
uuid))
{
return challenge;
}
}
return NULL;
}
/**
* Find challenge of @a uuid in @a state under "cs".
*
* @param state the state to search
* @param uuid the UUID to search for
* @return NULL on error, otherwise challenge entry; RC is NOT incremented
*/
static json_t *
find_challenge_in_cs (json_t *state,
const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
{
json_t *rd = json_object_get (state,
"recovery_document");
json_t *cs = json_object_get (rd,
"cs");
json_t *c;
size_t off;
json_array_foreach (cs, off, c)
{
struct ANASTASIS_CRYPTO_TruthUUIDP u;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("uuid",
&u),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (c,
spec,
NULL, NULL))
{
GNUNET_break (0);
continue;
}
if (0 !=
GNUNET_memcmp (uuid,
&u))
continue;
return c;
}
return NULL;
}
/**
* Defines a callback for the response status for a challenge start
* operation.
*
* @param cls a `struct SelectChallengeContext *`
* @param csr response details
*/
static void
start_feedback_cb (
void *cls,
const struct ANASTASIS_ChallengeStartResponse *csr)
{
struct SelectChallengeContext *sctx = cls;
const struct ANASTASIS_ChallengeDetails *cd;
char uuid[sizeof (cd->uuid) * 2];
char *end;
json_t *feedback;
cd = ANASTASIS_challenge_get_details (csr->challenge);
end = GNUNET_STRINGS_data_to_string (&cd->uuid,
sizeof (cd->uuid),
uuid,
sizeof (uuid));
GNUNET_assert (NULL != end);
*end = '\0';
feedback = json_object_get (sctx->state,
"challenge_feedback");
if (NULL == feedback)
{
feedback = json_object ();
GNUNET_assert (0 ==
json_object_set_new (sctx->state,
"challenge_feedback",
feedback));
}
switch (csr->cs)
{
case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED:
{
json_t *instructions;
char *hint;
GNUNET_asprintf (&hint,
_ ("Required TAN can be found in `%s'"),
csr->details.tan_filename);
instructions = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
"code-in-file"),
GNUNET_JSON_pack_string ("filename",
csr->details.tan_filename),
GNUNET_JSON_pack_string ("display_hint",
hint),
GNUNET_JSON_pack_uint64 ("http_status",
(json_int_t) csr->http_status));
GNUNET_free (hint);
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_START_STATUS_TAN_SENT_HINT_PROVIDED:
{
json_t *instructions;
char *hint;
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_JSON_pack_uint64 ("http_status",
(json_int_t) csr->http_status));
GNUNET_free (hint);
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_START_STATUS_TAN_ALREADY_SENT:
{
json_t *instructions;
char *hint;
GNUNET_asprintf (&hint,
_ ("TAN code already sent."));
instructions = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
"send-to-address"),
GNUNET_JSON_pack_string ("display_hint",
hint),
GNUNET_JSON_pack_uint64 ("http_status",
(json_int_t) csr->http_status));
GNUNET_free (hint);
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_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",
"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,
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_START_STATUS_SERVER_FAILURE:
{
json_t *err;
err = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
"server-failure"),
GNUNET_JSON_pack_uint64 ("http_status",
csr->http_status),
GNUNET_JSON_pack_uint64 ("error_code",
csr->ec));
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
err));
}
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
csr->ec,
sctx->state);
sctx_free (sctx);
return;
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 ==
json_object_set_new (feedback,
uuid,
err));
}
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
sctx->state);
sctx_free (sctx);
return;
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_JSON_pack_uint64 ("http_status",
csr->http_status));
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",
"server-failure"),
GNUNET_JSON_pack_uint64 ("http_status",
csr->http_status),
GNUNET_JSON_pack_uint64 ("error_code",
csr->ec));
GNUNET_assert (0 ==
json_object_set_new (feedback,
uuid,
err));
}
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
csr->ec,
sctx->state);
sctx_free (sctx);
return;
case ANASTASIS_CHALLENGE_ANSWER_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 ==
json_object_set_new (feedback,
uuid,
err));
}
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
sctx->state);
sctx_free (sctx);
return;
case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED:
{
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,
err));
}
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
sctx->cb (sctx->cb_cls,
TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
sctx->state);
sctx_free (sctx);
return;
case ANASTASIS_CHALLENGE_ANSWER_STATUS_AUTH_TIMEOUT:
// FIXME: check if this status code is even properly generated!
{
json_t *err;
err = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("state",
"authentication-timeout"),
GNUNET_JSON_pack_string ("display_hint",
_ ("Challenge not yet satisfied")),
GNUNET_JSON_pack_uint64 ("error_code",
TALER_EC_ANASTASIS_TRUTH_AUTH_TIMEOUT));
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,
sctx->state);
sctx_free (sctx);
return;
}
GNUNET_break (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
NULL);
sctx_free (sctx);
}
/**
* Callback which passes back the recovery document and its possible
* policies. Also passes back the version of the document for the user
* to check.
*
* We find the selected challenge and try to answer it (or begin
* the process).
*
* @param cls a `struct SelectChallengeContext *`
* @param ri recovery information struct which contains the policies
*/
static void
solve_challenge_cb (void *cls,
const struct ANASTASIS_RecoveryInformation *ri)
{
struct SelectChallengeContext *sctx = cls;
const struct ANASTASIS_PaymentSecretP *psp = NULL;
struct ANASTASIS_PaymentSecretP ps;
struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
struct GNUNET_JSON_Specification tspec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("timeout",
&timeout),
NULL),
GNUNET_JSON_spec_end ()
};
struct GNUNET_JSON_Specification pspec[] = {
GNUNET_JSON_spec_fixed_auto ("payment_secret",
&ps),
GNUNET_JSON_spec_end ()
};
json_t *challenge;
if (NULL == ri)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"recovery information could not be deserialized");
sctx_free (sctx);
return;
}
if ( (NULL != sctx->args) &&
(GNUNET_OK !=
GNUNET_JSON_parse (sctx->args,
tspec,
NULL, NULL)) )
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'timeout' malformed");
sctx_free (sctx);
return;
}
/* resume all async, unsolved challenges */
{
bool poll_started = false;
for (unsigned int i = 0; ics_len; i++)
{
struct ANASTASIS_Challenge *ci = ri->cs[i];
const struct ANASTASIS_ChallengeDetails *cd;
json_t *challenge;
json_t *pin;
cd = ANASTASIS_challenge_get_details (ci);
if (cd->solved ||
(! cd->async) )
continue;
challenge = find_challenge_in_cs (sctx->state,
&cd->uuid);
if (NULL == challenge)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"challenge not found");
sctx_free (sctx);
return;
}
pin = json_object_get (challenge,
"answer-pin");
if (! json_is_integer (pin))
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"async challenge 'answer-pin' not found");
sctx_free (sctx);
return;
}
if (GNUNET_OK !=
ANASTASIS_challenge_answer2 (ci,
psp,
timeout,
json_integer_value (pin),
&answer_feedback_cb,
sctx))
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"Failed to begin answering asynchronous challenge");
sctx_free (sctx);
return;
}
poll_started = true;
}
if (sctx->poll_only)
{
if (! poll_started)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
"no challenge available for polling");
return;
}
/* only polling, do not start new challenges */
return;
}
} /* end resuming async challenges */
/* Check if we got a payment_secret */
challenge = find_challenge_in_ri (sctx->state,
&sctx->uuid);
if (NULL == challenge)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"challenge not found");
sctx_free (sctx);
return;
}
if (NULL !=
json_object_get (sctx->args,
"payment_secret"))
{
/* check if we got payment secret in args */
if (GNUNET_OK !=
GNUNET_JSON_parse (sctx->args,
pspec,
NULL, NULL))
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'payment_secret' malformed");
sctx_free (sctx);
return;
}
psp = &ps;
}
else if (NULL !=
json_object_get (challenge,
"payment_secret"))
{
if (GNUNET_OK !=
GNUNET_JSON_parse (challenge,
pspec,
NULL, NULL))
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'payment_secret' malformed");
sctx_free (sctx);
return;
}
psp = &ps;
}
/* start or solve selected challenge */
for (unsigned int i = 0; ics_len; i++)
{
struct ANASTASIS_Challenge *ci = ri->cs[i];
const struct ANASTASIS_ChallengeDetails *cd;
int ret;
json_t *c;
cd = ANASTASIS_challenge_get_details (ci);
if (cd->async)
continue; /* handled above */
if (0 !=
GNUNET_memcmp (&sctx->uuid,
&cd->uuid))
continue;
if (cd->solved)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"Selected challenge already solved");
sctx_free (sctx);
return;
}
c = find_challenge_in_cs (sctx->state,
&cd->uuid);
GNUNET_assert (NULL != c);
if (0 == strcmp ("question",
cd->type))
{
/* security question, answer must be a string */
json_t *janswer = json_object_get (sctx->args,
"answer");
const char *answer = json_string_value (janswer);
if (NULL == answer)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'answer' missing");
sctx_free (sctx);
return;
}
/* persist answer, in case payment is required */
GNUNET_assert (0 ==
json_object_set (c,
"answer",
janswer));
ret = ANASTASIS_challenge_answer (ci,
psp,
timeout,
answer,
&answer_feedback_cb,
sctx);
}
else
{
/* Check if we got a PIN or a HASH */
json_t *pin = json_object_get (sctx->args,
"pin");
json_t *hash = json_object_get (sctx->args,
"hash");
if (json_is_integer (pin))
{
uint64_t ianswer = json_integer_value (pin);
/* persist answer, in case async processing
happens via poll */
GNUNET_assert (0 ==
json_object_set (c,
"answer-pin",
pin));
ret = ANASTASIS_challenge_answer2 (ci,
psp,
timeout,
ianswer,
&answer_feedback_cb,
sctx);
}
else if (NULL != hash)
{
struct GNUNET_HashCode hashed_answer;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("hash",
&hashed_answer),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (sctx->args,
spec,
NULL, NULL))
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'answer' malformed");
sctx_free (sctx);
return;
}
ret = ANASTASIS_challenge_answer3 (ci,
psp,
timeout,
&hashed_answer,
&answer_feedback_cb,
sctx);
}
else
{
/* no answer provided */
ret = ANASTASIS_challenge_start (ci,
psp,
&start_feedback_cb,
sctx);
}
}
if (GNUNET_OK != ret)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"Failed to begin answering challenge");
sctx_free (sctx);
return;
}
return; /* await answer feedback */
}
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'uuid' not in list of challenges");
sctx_free (sctx);
}
/**
* Callback which passes back the recovery document and its possible
* policies. Also passes back the version of the document for the user
* to check.
*
* We find the selected challenge and try to answer it (or begin
* the process).
*
* @param cls a `struct SelectChallengeContext *`
* @param ri recovery information struct which contains the policies
*/
static void
pay_challenge_cb (void *cls,
const struct ANASTASIS_RecoveryInformation *ri)
{
struct SelectChallengeContext *sctx = cls;
json_t *challenge;
if (NULL == ri)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"recovery information could not be deserialized");
sctx_free (sctx);
return;
}
challenge = find_challenge_in_ri (sctx->state,
&sctx->uuid);
if (NULL == challenge)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"challenge not found");
sctx_free (sctx);
return;
}
/* persist payment, in case we need to run the request again */
GNUNET_assert (
0 ==
json_object_set_new (challenge,
"payment_secret",
GNUNET_JSON_from_data_auto (&sctx->ps)));
for (unsigned int i = 0; ics_len; i++)
{
struct ANASTASIS_Challenge *ci = ri->cs[i];
const struct ANASTASIS_ChallengeDetails *cd;
int ret;
cd = ANASTASIS_challenge_get_details (ci);
if (0 !=
GNUNET_memcmp (&sctx->uuid,
&cd->uuid))
continue;
if (cd->solved)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"Selected challenge already solved");
sctx_free (sctx);
return;
}
if (0 == strcmp ("question",
cd->type))
{
/* security question, answer must be a string and already ready */
json_t *janswer = json_object_get (challenge,
"answer");
const char *answer = json_string_value (janswer);
if (NULL == answer)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'answer' missing");
sctx_free (sctx);
return;
}
ret = ANASTASIS_challenge_answer (ci,
&sctx->ps,
sctx->timeout,
answer,
&answer_feedback_cb,
sctx);
}
else
{
ret = ANASTASIS_challenge_start (ci,
&sctx->ps,
&start_feedback_cb,
sctx);
}
if (GNUNET_OK != ret)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"Failed to begin answering challenge");
sctx_free (sctx);
return;
}
return; /* await answer feedback */
}
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'uuid' not in list of challenges");
sctx_free (sctx);
}
/**
* The user selected a challenge to be solved. Begin the solving
* process.
*
* @param[in] state we are in
* @param arguments our arguments with the solution
* @param cb functiont o call with the new state
* @param cb_cls closure for @a cb
* @return handle to cancel challenge selection step
*/
static struct ANASTASIS_ReduxAction *
solve_challenge (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
struct SelectChallengeContext *sctx
= GNUNET_new (struct SelectChallengeContext);
json_t *rd;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
&sctx->uuid),
GNUNET_JSON_spec_end ()
};
if (NULL == arguments)
{
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"arguments missing");
return NULL;
}
if (GNUNET_OK !=
GNUNET_JSON_parse (state,
spec,
NULL, NULL))
{
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'selected_challenge_uuid' missing");
return NULL;
}
rd = json_object_get (state,
"recovery_document");
if (NULL == rd)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"solve_challenge");
return NULL;
}
sctx->cb = cb;
sctx->cb_cls = cb_cls;
sctx->state = json_incref (state);
sctx->args = json_incref ((json_t*) arguments);
sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
rd,
&solve_challenge_cb,
sctx,
&core_secret_cb,
sctx);
if (NULL == sctx->r)
{
json_decref (sctx->state);
json_decref (sctx->args);
GNUNET_free (sctx);
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'recovery_document' invalid");
return NULL;
}
sctx->ra.cleanup = &sctx_free;
sctx->ra.cleanup_cls = sctx;
return &sctx->ra;
}
/**
* The user asked for us to poll on pending
* asynchronous challenges to see if they have
* now completed / been satisfied.
*
* @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 *
poll_challenges (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
struct SelectChallengeContext *sctx
= GNUNET_new (struct SelectChallengeContext);
json_t *rd;
rd = json_object_get (state,
"recovery_document");
if (NULL == rd)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"poll_challenges");
return NULL;
}
sctx->poll_only = true;
sctx->cb = cb;
sctx->cb_cls = cb_cls;
sctx->state = json_incref (state);
sctx->args = json_incref ((json_t*) arguments);
sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
rd,
&solve_challenge_cb,
sctx,
&core_secret_cb,
sctx);
if (NULL == sctx->r)
{
json_decref (sctx->state);
json_decref (sctx->args);
GNUNET_free (sctx);
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'recovery_document' invalid");
return NULL;
}
sctx->ra.cleanup = &sctx_free;
sctx->ra.cleanup_cls = sctx;
return &sctx->ra;
}
/**
* The user selected a challenge to be solved. Handle the payment
* process.
*
* @param[in] state we are in
* @param arguments our arguments with the solution
* @param cb functiont o call with the new state
* @param cb_cls closure for @a cb
* @return handle to cancel challenge selection step
*/
static struct ANASTASIS_ReduxAction *
pay_challenge (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
struct SelectChallengeContext *sctx
= GNUNET_new (struct SelectChallengeContext);
json_t *rd;
struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
&sctx->uuid),
GNUNET_JSON_spec_end ()
};
struct GNUNET_JSON_Specification aspec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("timeout",
&timeout),
NULL),
GNUNET_JSON_spec_fixed_auto ("payment_secret",
&sctx->ps),
GNUNET_JSON_spec_end ()
};
if (NULL == arguments)
{
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"arguments missing");
return NULL;
}
if (GNUNET_OK !=
GNUNET_JSON_parse (arguments,
aspec,
NULL, NULL))
{
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'payment_secret' missing");
return NULL;
}
if (GNUNET_OK !=
GNUNET_JSON_parse (state,
spec,
NULL, NULL))
{
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'selected_challenge_uuid' missing");
return NULL;
}
rd = json_object_get (state,
"recovery_document");
if (NULL == rd)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"pay_challenge");
return NULL;
}
sctx->timeout = timeout;
sctx->cb = cb;
sctx->cb_cls = cb_cls;
sctx->state = json_incref (state);
sctx->args = json_incref ((json_t*) arguments);
sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
rd,
&pay_challenge_cb,
sctx,
&core_secret_cb,
sctx);
if (NULL == sctx->r)
{
json_decref (sctx->state);
json_decref (sctx->args);
GNUNET_free (sctx);
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'recovery_document' invalid");
return NULL;
}
sctx->ra.cleanup = &sctx_free;
sctx->ra.cleanup_cls = sctx;
return &sctx->ra;
}
/**
* Callback which passes back the recovery document and its possible
* policies. Also passes back the version of the document for the user
* to check.
*
* We find the selected challenge and try to answer it (or begin
* the process).
*
* @param cls a `struct SelectChallengeContext *`
* @param ri recovery information struct which contains the policies
*/
static void
select_challenge_cb (void *cls,
const struct ANASTASIS_RecoveryInformation *ri)
{
struct SelectChallengeContext *sctx = cls;
const struct ANASTASIS_PaymentSecretP *psp = NULL;
struct ANASTASIS_PaymentSecretP ps;
struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
struct GNUNET_JSON_Specification tspec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("timeout",
&timeout),
NULL),
GNUNET_JSON_spec_end ()
};
struct GNUNET_JSON_Specification pspec[] = {
GNUNET_JSON_spec_fixed_auto ("payment_secret",
&ps),
GNUNET_JSON_spec_end ()
};
if (NULL == ri)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"recovery information could not be deserialized");
sctx_free (sctx);
return;
}
if (GNUNET_OK !=
GNUNET_JSON_parse (sctx->args,
tspec,
NULL, NULL))
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'timeout' malformed");
sctx_free (sctx);
return;
}
/* NOTE: do we need both ways to pass payment secrets? */
if (NULL !=
json_object_get (sctx->args,
"payment_secret"))
{
/* check if we got payment secret in args */
if (GNUNET_OK !=
GNUNET_JSON_parse (sctx->args,
pspec,
NULL, NULL))
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'payment_secret' malformed");
sctx_free (sctx);
return;
}
psp = &ps;
}
else
{
/* Check if we got a payment_secret in state */
json_t *challenge = find_challenge_in_ri (sctx->state,
&sctx->uuid);
if (NULL == challenge)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"challenge not found");
sctx_free (sctx);
return;
}
if (NULL !=
json_object_get (challenge,
"payment_secret"))
{
if (GNUNET_OK !=
GNUNET_JSON_parse (challenge,
pspec,
NULL, NULL))
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'payment_secret' malformed");
sctx_free (sctx);
return;
}
psp = &ps;
}
}
for (unsigned int i = 0; ics_len; i++)
{
struct ANASTASIS_Challenge *ci = ri->cs[i];
const struct ANASTASIS_ChallengeDetails *cd;
int ret;
cd = ANASTASIS_challenge_get_details (ci);
if (0 !=
GNUNET_memcmp (&sctx->uuid,
&cd->uuid))
continue;
if (cd->solved)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"Selected challenge already solved");
sctx_free (sctx);
return;
}
GNUNET_assert (
0 ==
json_object_set_new (sctx->state,
"selected_challenge_uuid",
GNUNET_JSON_from_data_auto (&cd->uuid)));
if ( (0 == strcmp ("question",
cd->type)) ||
(0 == strcmp ("totp",
cd->type)) )
{
/* security question or TOTP:
immediately request user to answer it */
set_state (sctx->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
sctx->cb (sctx->cb_cls,
TALER_EC_NONE,
sctx->state);
sctx_free (sctx);
return;
}
/* trigger challenge */
{
json_t *c = find_challenge_in_cs (sctx->state,
&cd->uuid);
json_t *pin = json_object_get (c,
"answer-pin");
if (NULL != pin)
{
uint64_t ianswer = json_integer_value (pin);
ret = ANASTASIS_challenge_answer2 (ci,
psp,
timeout,
ianswer,
&answer_feedback_cb,
sctx);
}
else
{
ret = ANASTASIS_challenge_start (ci,
psp,
&start_feedback_cb,
sctx);
}
}
if (GNUNET_OK != ret)
{
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"Failed to begin answering challenge");
sctx_free (sctx);
return;
}
return; /* await answer feedback */
}
ANASTASIS_redux_fail_ (sctx->cb,
sctx->cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'uuid' not in list of challenges");
sctx_free (sctx);
}
/**
* The user selected a challenge to be solved. Begin the solving
* process.
*
* @param[in] state we are in
* @param arguments our arguments with the solution
* @param cb functiont o call with the new state
* @param cb_cls closure for @a cb
* @return handle to cancel challenge selection step
*/
static struct ANASTASIS_ReduxAction *
select_challenge (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
struct SelectChallengeContext *sctx
= GNUNET_new (struct SelectChallengeContext);
json_t *rd;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("uuid",
&sctx->uuid),
GNUNET_JSON_spec_end ()
};
if (NULL == arguments)
{
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"arguments missing");
return NULL;
}
if (GNUNET_OK !=
GNUNET_JSON_parse (arguments,
spec,
NULL, NULL))
{
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"'uuid' missing");
return NULL;
}
rd = json_object_get (state,
"recovery_document");
if (NULL == rd)
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"select_challenge");
return NULL;
}
sctx->cb = cb;
sctx->cb_cls = cb_cls;
sctx->state = json_incref (state);
sctx->args = json_incref ((json_t*) arguments);
sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
rd,
&select_challenge_cb,
sctx,
&core_secret_cb,
sctx);
if (NULL == sctx->r)
{
json_decref (sctx->state);
json_decref (sctx->args);
GNUNET_free (sctx);
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'recovery_document' invalid");
return NULL;
}
sctx->ra.cleanup = &sctx_free;
sctx->ra.cleanup_cls = sctx;
return &sctx->ra;
}
/**
* Main data structure for sync_providers().
*/
struct MasterSync;
/**
* Data structure for one provider we are syncing /config with.
*/
struct SyncEntry
{
/**
* Kept in a DLL.
*/
struct SyncEntry *next;
/**
* Kept in a DLL.
*/
struct SyncEntry *prev;
/**
* Sync operation we are part of.
*/
struct MasterSync *ms;
/**
* Redux action for this provider.
*/
struct ANASTASIS_ReduxAction *ra;
};
/**
* Main data structure for sync_providers().
*/
struct MasterSync
{
/**
* Our own sync action we expose externally.
*/
struct ANASTASIS_ReduxAction ra;
/**
* Head of DLL with entries per provider.
*/
struct SyncEntry *se_head;
/**
* Tail of DLL with entries per provider.
*/
struct SyncEntry *se_tail;
/**
* Function to call with the result.
*/
ANASTASIS_ActionCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
};
/**
* Free @a cls data structure.
*
* @param[in] cls data structure to free, must be a `struct MasterSync *`
*/
static void
clean_sync (void *cls)
{
struct MasterSync *ms = cls;
struct SyncEntry *se;
while (NULL != (se = ms->se_head))
{
GNUNET_CONTAINER_DLL_remove (ms->se_head,
ms->se_tail,
se);
se->ra->cleanup (se->ra->cleanup_cls);
GNUNET_free (se);
}
GNUNET_free (ms);
}
/**
* Function called when we have made progress on any of the
* providers we are trying to sync with.
*
* @param cls closure
* @param error error code, #TALER_EC_NONE if @a new_bs is the new successful state
* @param new_state the new state of the operation (client should json_incref() to keep an alias)
*/
static void
sync_progress (void *cls,
enum TALER_ErrorCode error,
json_t *new_state)
{
struct SyncEntry *se = cls;
struct MasterSync *ms = se->ms;
GNUNET_CONTAINER_DLL_remove (ms->se_head,
ms->se_tail,
se);
GNUNET_free (se);
ms->cb (ms->cb_cls,
error,
new_state);
clean_sync (ms);
}
/**
* Check if we have information on all providers involved in
* a recovery procedure, and if not, try to obtain it. Upon
* success, call @a cb with the updated provider status data.
*
* @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 *
sync_providers (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
json_t *rd;
json_t *cs_arr;
struct MasterSync *ms;
rd = json_object_get (state,
"recovery_document");
if (NULL == rd)
{
GNUNET_break (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'recovery_document' missing");
return NULL;
}
cs_arr = json_object_get (rd,
"cs");
if (! json_is_array (cs_arr))
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'recovery_document' must be an array");
return NULL;
}
ms = GNUNET_new (struct MasterSync);
ms->cb = cb;
ms->cb_cls = cb_cls;
{
json_t *cs;
unsigned int n_index;
json_array_foreach (cs_arr, n_index, cs)
{
const char *provider_url;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("url",
&provider_url),
GNUNET_JSON_spec_end ()
};
struct ANASTASIS_CRYPTO_ProviderSaltP salt;
struct SyncEntry *se;
if (GNUNET_OK !=
GNUNET_JSON_parse (cs,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'recovery_document' missing");
clean_sync (ms);
return NULL;
}
if (GNUNET_OK ==
ANASTASIS_reducer_lookup_salt (state,
provider_url,
&salt))
continue; /* provider already ready */
se = GNUNET_new (struct SyncEntry);
se->ms = ms;
GNUNET_CONTAINER_DLL_insert (ms->se_head,
ms->se_tail,
se);
se->ra = ANASTASIS_REDUX_add_provider_to_state_ (provider_url,
state,
&sync_progress,
se);
}
}
if (NULL == ms->se_head)
{
/* everything already synced */
clean_sync (ms);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
"already in sync");
return NULL;
}
ms->ra.cleanup = &clean_sync;
ms->ra.cleanup_cls = ms;
return &ms->ra;
}
/**
* Try to obtain configuration information on all configured
* providers. Upon success, call @a cb with the updated provider
* status data.
*
* @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 *
poll_providers (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
json_t *ap;
const char *url;
json_t *obj;
struct MasterSync *ms;
ap = json_object_get (state,
"authentication_providers");
if (NULL == ap)
{
GNUNET_break (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'authentication_providers' missing");
return NULL;
}
ms = GNUNET_new (struct MasterSync);
ms->cb = cb;
ms->cb_cls = cb_cls;
json_object_foreach (ap, url, obj)
{
struct ANASTASIS_CRYPTO_ProviderSaltP salt;
struct SyncEntry *se;
if (GNUNET_OK ==
ANASTASIS_reducer_lookup_salt (state,
url,
&salt))
continue;
se = GNUNET_new (struct SyncEntry);
se->ms = ms;
GNUNET_CONTAINER_DLL_insert (ms->se_head,
ms->se_tail,
se);
se->ra = ANASTASIS_REDUX_add_provider_to_state_ (url,
state,
&sync_progress,
se);
}
if (NULL == ms->se_head)
{
/* everything already synced */
clean_sync (ms);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
"already in sync");
return NULL;
}
ms->ra.cleanup = &clean_sync;
ms->ra.cleanup_cls = ms;
return &ms->ra;
}
/**
* The user pressed "back" during challenge solving.
* Transition back to selecting another challenge.
*
* @param[in] state we are in
* @param arguments our arguments (unused)
* @param cb functiont o call with the new state
* @param cb_cls closure for @a cb
* @return NULL (synchronous operation)
*/
static struct ANASTASIS_ReduxAction *
back_challenge_solving (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
(void) arguments;
GNUNET_assert (0 ==
json_object_del (state,
"selected_challenge_uuid"));
set_state (state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
cb (cb_cls,
TALER_EC_NONE,
state);
return NULL;
}
/**
* State for a "policy download" as part of a recovery operation.
*/
struct PolicyDownloadEntry
{
/**
* Redux action handle associated with this state.
*/
struct ANASTASIS_ReduxAction ra;
/**
* Backend we are querying.
*/
char *backend_url;
/**
* The /policy GET operation handle.
*/
struct ANASTASIS_Recovery *recovery;
/**
* Function to call with the result.
*/
ANASTASIS_ActionCallback cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
/**
* State we are using.
*/
json_t *state;
};
/**
* Free @a cls data structure.
*
* @param[in] cls data structure to free, must be a `struct PolicyDownloadEntry *`
*/
static void
free_pd (void *cls)
{
struct PolicyDownloadEntry *pd = cls;
if (NULL != pd->recovery)
{
ANASTASIS_recovery_abort (pd->recovery);
pd->recovery = NULL;
}
GNUNET_free (pd->backend_url);
json_decref (pd->state);
GNUNET_free (pd);
}
/**
* We failed to download a policy. Show an error to the user and
* allow the user to specify alternative providers and/or policy
* versions.
*
* @param[in] 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 PolicyDownloadEntry *pd,
bool offline)
{
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,
"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",
TALER_ErrorCode_get_hint (ec)));
pd->cb (pd->cb_cls,
ec,
estate);
free_pd (pd);
}
/**
* Callback which passes back the recovery document and its possible
* policies. Also passes back the version of the document for the user
* to check.
*
* Once the first policy lookup succeeds, we update our state and
* cancel all of the others, passing the obtained recovery information
* back to the user.
*
* @param cls closure for the callback with a `struct PolicyDownloadEntry *`
* @param ri recovery information struct which contains the policies
*/
static void
policy_lookup_cb (void *cls,
const struct ANASTASIS_RecoveryInformation *ri)
{
struct PolicyDownloadEntry *pd = cls;
json_t *policies;
json_t *challenges;
json_t *recovery_information;
if (NULL == ri)
{
/* Woopsie, failed hard. */
ANASTASIS_recovery_abort (pd->recovery);
GNUNET_free (pd->backend_url);
GNUNET_free (pd);
return_no_policy (pd,
false);
return;
}
policies = json_array ();
GNUNET_assert (NULL != policies);
for (unsigned int i = 0; idps_len; i++)
{
struct ANASTASIS_DecryptionPolicy *dps = ri->dps[i];
json_t *pchallenges;
pchallenges = json_array ();
GNUNET_assert (NULL != pchallenges);
for (unsigned int j = 0; jchallenges_length; j++)
{
struct ANASTASIS_Challenge *c = dps->challenges[j];
const struct ANASTASIS_ChallengeDetails *cd;
json_t *cj;
cd = ANASTASIS_challenge_get_details (c);
cj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("uuid",
&cd->uuid));
GNUNET_assert (0 ==
json_array_append_new (pchallenges,
cj));
}
GNUNET_assert (0 ==
json_array_append_new (policies,
pchallenges));
} /* end for all policies */
challenges = json_array ();
GNUNET_assert (NULL != challenges);
for (unsigned int i = 0; ics_len; i++)
{
struct ANASTASIS_Challenge *c = ri->cs[i];
const struct ANASTASIS_ChallengeDetails *cd;
json_t *cj;
cd = ANASTASIS_challenge_get_details (c);
cj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("uuid",
&cd->uuid),
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 ==
json_array_append_new (challenges,
cj));
} /* end for all challenges */
recovery_information = GNUNET_JSON_PACK (
GNUNET_JSON_pack_array_steal ("challenges",
challenges),
GNUNET_JSON_pack_array_steal ("policies",
policies),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("secret_name",
ri->secret_name)),
GNUNET_JSON_pack_string ("provider_url",
pd->backend_url),
GNUNET_JSON_pack_uint64 ("version",
ri->version));
GNUNET_assert (0 ==
json_object_set_new (pd->state,
"recovery_information",
recovery_information));
{
json_t *rd;
rd = ANASTASIS_recovery_serialize (pd->recovery);
if (NULL == rd)
{
GNUNET_break (0);
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 (pd->state,
"recovery_document",
rd));
}
set_state (pd->state,
ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
pd->cb (pd->cb_cls,
TALER_EC_NONE,
pd->state);
free_pd (pd);
}
/**
* 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;
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);
}
/**
* 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)
{
uint32_t mask;
json_t *pa;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_uint32 ("attribute_mask",
&mask),
GNUNET_JSON_spec_json ("providers",
&pa),
GNUNET_JSON_spec_end ()
};
struct ANASTASIS_CRYPTO_ProviderSaltP salt;
struct GNUNET_JSON_Specification pspec[] = {
GNUNET_JSON_spec_fixed_auto ("salt",
&salt),
GNUNET_JSON_spec_end ()
};
json_t *p_cfg;
json_t *id_data;
const json_t *providers;
if (GNUNET_OK !=
GNUNET_JSON_parse (arguments,
spec,
NULL, NULL))
{
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;
}
providers = json_object_get (state,
"authentication_providers");
if ( (NULL == providers) ||
(! json_is_object (providers)) )
{
GNUNET_break (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'authentication_providers' missing");
return NULL;
}
{
size_t poff;
json_t *pe;
uint64_t version;
const char *provider_url;
json_array_foreach (pa, poff, pe)
{
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,
&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;
}
}
}
/* no provider worked */
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
"selected provider is not online");
return NULL;
}
/**
* The user wants us to add another provider. Download /config.
*
* @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 struct ANASTASIS_ReduxAction *
add_provider (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
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);
}
/**
* 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,
"add_provider",
&add_provider
},
{
ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
"poll_providers",
&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",
&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;
}
struct ANASTASIS_ReduxAction *
ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state,
const json_t *arguments,
ANASTASIS_ActionCallback cb,
void *cb_cls)
{
const json_t *providers;
json_t *attributes;
providers = json_object_get (state,
"authentication_providers");
if ( (NULL == providers) ||
(! json_is_object (providers)) )
{
GNUNET_break (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'authentication_providers' missing");
return NULL;
}
attributes = json_object_get (arguments,
"identity_attributes");
if ( (NULL == attributes) ||
(! json_is_object (attributes)) )
{
GNUNET_break (0);
ANASTASIS_redux_fail_ (cb,
cb_cls,
TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
"'identity_attributes' missing");
return NULL;
}
json_object_set (state,
"identity_attributes",
attributes);
set_state (state,
ANASTASIS_RECOVERY_STATE_SECRET_SELECTING);
cb (cb_cls,
TALER_EC_NONE,
state);
return NULL;
}