diff options
Diffstat (limited to 'src/reducer')
-rw-r--r-- | src/reducer/Makefile.am | 1 | ||||
-rw-r--r-- | src/reducer/anastasis_api_backup_redux.c | 248 | ||||
-rw-r--r-- | src/reducer/anastasis_api_providers.c | 300 | ||||
-rw-r--r-- | src/reducer/anastasis_api_recovery_redux.c | 306 | ||||
-rw-r--r-- | src/reducer/anastasis_api_redux.c | 188 | ||||
-rw-r--r-- | src/reducer/anastasis_api_redux.h | 36 |
6 files changed, 664 insertions, 415 deletions
diff --git a/src/reducer/Makefile.am b/src/reducer/Makefile.am index 6dad659..1536d21 100644 --- a/src/reducer/Makefile.am +++ b/src/reducer/Makefile.am @@ -16,6 +16,7 @@ libanastasisredux_la_LDFLAGS = \ -no-undefined libanastasisredux_la_SOURCES = \ anastasis_api_discovery.c \ + anastasis_api_providers.c \ anastasis_api_redux.c anastasis_api_redux.h \ anastasis_api_recovery_redux.c \ anastasis_api_backup_redux.c \ diff --git a/src/reducer/anastasis_api_backup_redux.c b/src/reducer/anastasis_api_backup_redux.c index 2e73587..6ca6de7 100644 --- a/src/reducer/anastasis_api_backup_redux.c +++ b/src/reducer/anastasis_api_backup_redux.c @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020-2023 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 @@ -32,7 +32,7 @@ * anastasis-httpd.h. */ #define ANASTASIS_FREE_STORAGE GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_YEARS, 5) + GNUNET_TIME_UNIT_YEARS, 5) /** * CPU limiter: do not evaluate more than 16k @@ -353,7 +353,7 @@ add_authentication (json_t *state, json_object_foreach (auth_providers, url, details) { - json_t *methods = NULL; + const json_t *methods = NULL; json_t *method; size_t index; uint32_t size_limit_in_mb = 0; @@ -371,8 +371,8 @@ add_authentication (json_t *state, &http_status), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("methods", - &methods), + GNUNET_JSON_spec_array_const ("methods", + &methods), NULL), GNUNET_JSON_spec_end () }; @@ -411,7 +411,6 @@ add_authentication (json_t *state, break; } } - GNUNET_JSON_parse_free (ispec); if (! challenge_size_ok (size_limit_in_mb, challenge_size)) { @@ -767,17 +766,20 @@ free_costs (struct Costs *costs) * Check if providers @a p1 and @a p2 have equivalent * methods and cost structures. * + * @param pb policy builder with list of providers + * @param p1 name of provider to compare + * @param p2 name of provider to compare * @return true if the providers are fully equivalent */ static bool -equiv_provider (struct PolicyBuilder *pb, +equiv_provider (const struct PolicyBuilder *pb, const char *p1, const char *p2) { - json_t *j1; - json_t *j2; - json_t *m1; - json_t *m2; + const json_t *j1; + const json_t *j2; + const json_t *m1; + const json_t *m2; struct TALER_Amount uc1; struct TALER_Amount uc2; @@ -794,8 +796,8 @@ equiv_provider (struct PolicyBuilder *pb, { struct GNUNET_JSON_Specification s1[] = { - GNUNET_JSON_spec_json ("methods", - &m1), + GNUNET_JSON_spec_array_const ("methods", + &m1), TALER_JSON_spec_amount_any ("truth_upload_fee", &uc1), GNUNET_JSON_spec_end () @@ -813,8 +815,8 @@ equiv_provider (struct PolicyBuilder *pb, { struct GNUNET_JSON_Specification s2[] = { - GNUNET_JSON_spec_json ("methods", - &m2), + GNUNET_JSON_spec_array_const ("methods", + &m2), TALER_JSON_spec_amount_any ("truth_upload_fee", &uc2), GNUNET_JSON_spec_end () @@ -933,7 +935,7 @@ eval_provider_selection (struct PolicyBuilder *pb, pb->m_idx[i]); const json_t *provider_cfg = json_object_get (pb->providers, prov_sel[i]); - json_t *provider_methods; + const json_t *provider_methods; const char *method_type; json_t *md; size_t index; @@ -943,8 +945,8 @@ eval_provider_selection (struct PolicyBuilder *pb, struct GNUNET_JSON_Specification pspec[] = { GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", &size_limit_in_mb), - GNUNET_JSON_spec_json ("methods", - &provider_methods), + GNUNET_JSON_spec_array_const ("methods", + &provider_methods), TALER_JSON_spec_amount_any ("truth_upload_fee", &upload_cost), GNUNET_JSON_spec_end () @@ -1016,7 +1018,6 @@ eval_provider_selection (struct PolicyBuilder *pb, GNUNET_break (0); pb->ec = TALER_EC_ANASTASIS_REDUCER_STATE_INVALID; pb->hint = "'methods' of provider"; - GNUNET_JSON_parse_free (pspec); for (unsigned int i = 0; i<pb->req_methods; i++) free_costs (policy_ent[i].usage_fee); return; @@ -1038,14 +1039,12 @@ eval_provider_selection (struct PolicyBuilder *pb, { /* Provider does not OFFER this method, combination not possible. Cost is basically 'infinite', but we simply then skip this. */ - GNUNET_JSON_parse_free (pspec); GNUNET_JSON_parse_free (mspec); for (unsigned int i = 0; i<pb->req_methods; i++) free_costs (policy_ent[i].usage_fee); return; } GNUNET_JSON_parse_free (mspec); - GNUNET_JSON_parse_free (pspec); } /* calculate provider diversity by counting number of different @@ -1666,7 +1665,8 @@ done_authentication (json_t *state, pb.methods = json_object_get (state, "authentication_methods"); if ( (NULL == pb.methods) || - (! json_is_array (pb.methods)) ) + (! json_is_array (pb.methods)) || + (json_array_size (pb.methods) > UINT_MAX) ) { ANASTASIS_redux_fail_ (cb, cb_cls, @@ -1674,7 +1674,8 @@ done_authentication (json_t *state, "'authentication_methods' must be provided"); return NULL; } - pb.num_methods = json_array_size (pb.methods); + pb.num_methods + = (unsigned int) json_array_size (pb.methods); switch (pb.num_methods) { case 0: @@ -1945,7 +1946,7 @@ add_policy (json_t *state, const char *provider_url; uint32_t method_idx; const char *method_type; - json_t *prov_methods; + const json_t *prov_methods; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("provider", &provider_url), @@ -1982,8 +1983,8 @@ add_policy (json_t *state, NULL), GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", &limit), - GNUNET_JSON_spec_json ("methods", - &prov_methods), + GNUNET_JSON_spec_array_const ("methods", + &prov_methods), GNUNET_JSON_spec_end () }; @@ -2016,17 +2017,6 @@ add_policy (json_t *state, json_decref (methods); continue; } - if (! json_is_array (prov_methods)) - { - GNUNET_break (0); - json_decref (methods); - json_decref (prov_methods); - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "provider lacks authentication methods"); - return NULL; - } } { @@ -2038,7 +2028,6 @@ add_policy (json_t *state, { GNUNET_break (0); json_decref (methods); - json_decref (prov_methods); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, @@ -2051,7 +2040,6 @@ add_policy (json_t *state, { GNUNET_break (0); json_decref (methods); - json_decref (prov_methods); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, @@ -2083,7 +2071,6 @@ add_policy (json_t *state, { GNUNET_break (0); json_decref (methods); - json_decref (prov_methods); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, @@ -2100,7 +2087,6 @@ add_policy (json_t *state, { GNUNET_break (0); json_decref (methods); - json_decref (prov_methods); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, @@ -2111,7 +2097,6 @@ add_policy (json_t *state, GNUNET_assert (0 == json_array_append (methods, method)); - json_decref (prov_methods); } /* end of json_array_foreach (arg_array, index, method) */ } @@ -2970,6 +2955,62 @@ serialize_truth (struct UploadContext *uc) /** + * Test if the given @a provider_url is used by any of the + * authentication methods and thus the provider should be + * considered mandatory for storing the policy. + * + * @param state state to inspect + * @param provider_url provider to test + * @return false if the provider can be removed from policy + * upload considerations without causing a problem + */ +static bool +provider_required (const json_t *state, + const char *provider_url) +{ + json_t *policies + = json_object_get (state, + "policies"); + size_t pidx; + json_t *policy; + + json_array_foreach (policies, pidx, policy) + { + json_t *methods = json_object_get (policy, + "methods"); + size_t midx; + json_t *method; + + json_array_foreach (methods, midx, method) + { + const char *provider + = json_string_value (json_object_get (method, + "provider")); + + if (NULL == provider) + { + GNUNET_break (0); + continue; + } + if (0 == strcmp (provider, + provider_url)) + return true; + } + } + return false; +} + + +/** + * All truth uploads are done, begin with uploading the policy. + * + * @param[in,out] uc context for the operation + */ +static void +share_secret (struct UploadContext *uc); + + +/** * Function called with the results of a #ANASTASIS_secret_share(). * * @param cls closure with a `struct UploadContext *` @@ -3053,7 +3094,8 @@ secret_share_result_cb (void *cls, json_array_foreach (providers, off, provider) { const char *purl = json_string_value (json_object_get (provider, - "provider_url")); + "provider_url") + ); if (NULL == purl) { @@ -3094,6 +3136,45 @@ secret_share_result_cb (void *cls, { json_t *details; + if (! provider_required (uc->state, + sr->details.provider_failure.provider_url)) + { + /* try again without that provider */ + json_t *provider; + json_t *providers; + size_t idx; + + provider + = json_object_get ( + json_object_get (uc->state, + "authentication_providers"), + sr->details.provider_failure.provider_url); + GNUNET_break (0 == + json_object_set_new (provider, + "status", + json_string ("disabled"))); + providers + = json_object_get (uc->state, + "policy_providers"); + json_array_foreach (providers, idx, provider) + { + const char *url + = json_string_value (json_object_get (provider, + "provider_url")); + + if ( (NULL != url) && + (0 == strcmp (sr->details.provider_failure.provider_url, + url)) ) + { + GNUNET_break (0 == + json_array_remove (providers, + idx)); + break; + } + } + share_secret (uc); + return; + } details = GNUNET_JSON_PACK ( GNUNET_JSON_pack_uint64 ("http_status", sr->details.provider_failure.http_status), @@ -3130,23 +3211,25 @@ secret_share_result_cb (void *cls, static void share_secret (struct UploadContext *uc) { - json_t *user_id; - json_t *core_secret; - json_t *jpolicies; - json_t *providers = NULL; + const json_t *user_id; + const json_t *core_secret; + const json_t *jpolicies; + const json_t *providers = NULL; size_t policies_len; const char *secret_name = NULL; unsigned int pds_len; struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("identity_attributes", - &user_id), - GNUNET_JSON_spec_json ("policies", - &jpolicies), - GNUNET_JSON_spec_json ("policy_providers", - &providers), - GNUNET_JSON_spec_json ("core_secret", - &core_secret), + GNUNET_JSON_spec_object_const ("identity_attributes", + &user_id), + GNUNET_JSON_spec_array_const ("policies", + &jpolicies), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("policy_providers", + &providers), + NULL), + GNUNET_JSON_spec_object_const ("core_secret", + &core_secret), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("secret_name", &secret_name), @@ -3198,40 +3281,43 @@ share_secret (struct UploadContext *uc) } } - if ( (! json_is_object (user_id)) || - (! json_is_array (jpolicies)) || - (0 == json_array_size (jpolicies)) || - ( (NULL != providers) && - (! json_is_array (providers)) ) ) + policies_len = json_array_size (jpolicies); + if (0 == policies_len) { ANASTASIS_redux_fail_ (uc->cb, uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "State parsing failed checks when preparing to share secret"); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } - policies_len = json_array_size (jpolicies); - pds_len = json_array_size (providers); - + if (json_array_size (providers) > UINT_MAX) + { + GNUNET_break_op (0); + ANASTASIS_redux_fail_ (uc->cb, + uc->cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "provider array excessively long"); + upload_cancel_cb (uc); + return; + } + pds_len + = (unsigned int) json_array_size (providers); if (0 == pds_len) { ANASTASIS_redux_fail_ (uc->cb, uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "no workable providers in state"); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } - { struct ANASTASIS_Policy *vpolicies[policies_len]; const struct ANASTASIS_Policy *policies[policies_len]; - struct ANASTASIS_ProviderDetails pds[GNUNET_NZL (pds_len)]; + struct ANASTASIS_ProviderDetails pds[pds_len]; /* initialize policies/vpolicies arrays */ memset (pds, @@ -3246,18 +3332,19 @@ share_secret (struct UploadContext *uc) unsigned int methods_len; if ( (! json_is_array (jmethods)) || - (0 == json_array_size (jmethods)) ) + (0 == json_array_size (jmethods)) || + (json_array_size (jmethods) > UINT_MAX) ) { GNUNET_break (0); ANASTASIS_redux_fail_ (uc->cb, uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - "'methods' must be an array"); - GNUNET_JSON_parse_free (spec); + "'methods' must be an array of sane length"); upload_cancel_cb (uc); return; } - methods_len = json_array_size (jmethods); + methods_len + = (unsigned int) json_array_size (jmethods); { struct ANASTASIS_Policy *p; struct ANASTASIS_Truth *truths[methods_len]; @@ -3267,13 +3354,13 @@ share_secret (struct UploadContext *uc) { const json_t *jmethod = json_array_get (jmethods, j); - json_t *jtruth = NULL; + const json_t *jtruth = NULL; uint32_t truth_index; const char *provider_url; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("truth", - &jtruth), + GNUNET_JSON_spec_object_const ("truth", + &jtruth), NULL), GNUNET_JSON_spec_string ("provider", &provider_url), @@ -3295,7 +3382,6 @@ share_secret (struct UploadContext *uc) uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'truth' failed to decode"); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } @@ -3312,8 +3398,6 @@ share_secret (struct UploadContext *uc) uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'truth' failed to decode"); - GNUNET_JSON_parse_free (ispec); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } @@ -3351,13 +3435,10 @@ share_secret (struct UploadContext *uc) uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'truth' failed to decode"); - GNUNET_JSON_parse_free (ispec); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } } - GNUNET_JSON_parse_free (ispec); ctruths[j] = truths[j]; } p = ANASTASIS_policy_create (ctruths, @@ -3401,7 +3482,6 @@ share_secret (struct UploadContext *uc) for (unsigned int i = 0; i<policies_len; i++) ANASTASIS_policy_destroy (vpolicies[i]); upload_cancel_cb (uc); - GNUNET_JSON_parse_free (spec); return; } } @@ -3432,7 +3512,6 @@ share_secret (struct UploadContext *uc) for (unsigned int i = 0; i<policies_len; i++) ANASTASIS_policy_destroy (vpolicies[i]); } - GNUNET_JSON_parse_free (spec); if (NULL == uc->ss) { GNUNET_break (0); @@ -4718,6 +4797,11 @@ ANASTASIS_backup_action_ (json_t *state, }, { ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING, + "poll_providers", + &ANASTASIS_REDUX_poll_providers_ + }, + { + ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING, "back", &ANASTASIS_back_generic_decrement_ }, diff --git a/src/reducer/anastasis_api_providers.c b/src/reducer/anastasis_api_providers.c new file mode 100644 index 0000000..82243f5 --- /dev/null +++ b/src/reducer/anastasis_api_providers.c @@ -0,0 +1,300 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021, 2022 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/anastasis_api_providers.c + * @brief anastasis provider synchronization logic + * @author Christian Grothoff + */ + +#include <platform.h> +#include <jansson.h> +#include "anastasis_redux.h" +#include "anastasis_error_codes.h" +#include "anastasis_api_redux.h" + + +/** + * 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); +} + + +struct ANASTASIS_ReduxAction * +ANASTASIS_REDUX_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, + "challenges"); + 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 provider_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, + &provider_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_PROVIDERS_ALREADY_SYNCED, + "already in sync"); + return NULL; + } + ms->ra.cleanup = &clean_sync; + ms->ra.cleanup_cls = ms; + return &ms->ra; +} + + +struct ANASTASIS_ReduxAction * +ANASTASIS_REDUX_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 provider_salt; + struct SyncEntry *se; + struct ANASTASIS_ReduxAction *ra; + + if (GNUNET_OK == + ANASTASIS_reducer_lookup_salt (state, + url, + &provider_salt)) + continue; + se = GNUNET_new (struct SyncEntry); + se->ms = ms; + GNUNET_CONTAINER_DLL_insert (ms->se_head, + ms->se_tail, + se); + ra = ANASTASIS_REDUX_add_provider_to_state_ (url, + state, + &sync_progress, + se); + if (NULL == ra) + return NULL; /* sync_progress already called! */ + se->ra = ra; + } + 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; +} diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c index 22be38d..e795c55 100644 --- a/src/reducer/anastasis_api_recovery_redux.c +++ b/src/reducer/anastasis_api_recovery_redux.c @@ -2013,301 +2013,6 @@ select_challenge (json_t *state, /** - * 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, - "challenges"); - 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 provider_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, - &provider_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_PROVIDERS_ALREADY_SYNCED, - "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 provider_salt; - struct SyncEntry *se; - struct ANASTASIS_ReduxAction *ra; - - if (GNUNET_OK == - ANASTASIS_reducer_lookup_salt (state, - url, - &provider_salt)) - continue; - se = GNUNET_new (struct SyncEntry); - se->ms = ms; - GNUNET_CONTAINER_DLL_insert (ms->se_head, - ms->se_tail, - se); - ra = ANASTASIS_REDUX_add_provider_to_state_ (url, - state, - &sync_progress, - se); - if (NULL == ra) - return NULL; /* sync_progress already called! */ - se->ra = ra; - } - 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. * @@ -2603,12 +2308,12 @@ done_secret_selecting (json_t *state, void *cb_cls) { uint32_t mask; - json_t *pa; + const json_t *pa; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_uint32 ("attribute_mask", &mask), - GNUNET_JSON_spec_json ("providers", - &pa), + GNUNET_JSON_spec_array_const ("providers", + &pa), GNUNET_JSON_spec_end () }; struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; @@ -2743,7 +2448,6 @@ done_secret_selecting (json_t *state, pd->ra.cleanup_cls = pd; return &pd->ra; } - } } @@ -2833,7 +2537,7 @@ ANASTASIS_recovery_action_ (json_t *state, { ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, "poll_providers", - &poll_providers + &ANASTASIS_REDUX_poll_providers_ }, { ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, @@ -2853,7 +2557,7 @@ ANASTASIS_recovery_action_ (json_t *state, { ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, "sync_providers", - &sync_providers + &ANASTASIS_REDUX_sync_providers_ }, { ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c index 2271942..4b5ad7b 100644 --- a/src/reducer/anastasis_api_redux.c +++ b/src/reducer/anastasis_api_redux.c @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020, 2021, 2022 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 @@ -36,6 +36,12 @@ */ #define CONFIG_GENERIC_TIMEOUT GNUNET_TIME_UNIT_MINUTES +/** + * How long do we wait in a more "synchronous" + * scenaro for a /config reply from an Anastasis provider. + */ +#define CONFIG_FAST_TIMEOUT GNUNET_TIME_UNIT_SECONDS + #define GENERATE_STRING(STRING) #STRING, static const char *generic_strings[] = { @@ -132,6 +138,21 @@ struct ConfigRequest struct ConfigReduxWaiting *w_tail; /** + * When did we start? + */ + struct GNUNET_TIME_Absolute start_time; + + /** + * When do we time out? + */ + struct GNUNET_TIME_Absolute timeout_at; + + /** + * How long do we wait before trying again? + */ + struct GNUNET_TIME_Relative backoff; + + /** * Obtained status code. */ unsigned int http_status; @@ -545,44 +566,70 @@ notify_waiting (struct ConfigRequest *cr) w->state); abort_provider_config_cb (w); } +} + +/** + * Notify anyone waiting on @a cr that the request is done + * (successful or failed). + * + * @param[in,out] cls request that completed + */ +static void +notify_waiting_cb (void *cls) +{ + struct ConfigRequest *cr = cls; + + cr->tt = NULL; + notify_waiting (cr); } /** + * Function called when it is time to retry a + * failed /config request. + * + * @param cls the `struct ConfigRequest *` to retry. + */ +static void +retry_config (void *cls); + + +/** * Function called with the results of a #ANASTASIS_get_config(). * * @param cls closure - * @param http_status HTTP status of the request * @param acfg anastasis configuration */ static void config_cb (void *cls, - unsigned int http_status, const struct ANASTASIS_Config *acfg) { struct ConfigRequest *cr = cls; cr->co = NULL; - GNUNET_SCHEDULER_cancel (cr->tt); - cr->tt = NULL; - cr->http_status = http_status; - if (MHD_HTTP_OK != http_status) + if (NULL != cr->tt) + { + GNUNET_SCHEDULER_cancel (cr->tt); + cr->tt = NULL; + } + cr->http_status = acfg->http_status; + if (MHD_HTTP_OK != acfg->http_status) { - if (0 == http_status) + if (0 == acfg->http_status) cr->ec = TALER_EC_ANASTASIS_GENERIC_PROVIDER_UNREACHABLE; else cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED; } - if ( (MHD_HTTP_OK == http_status) && + if ( (MHD_HTTP_OK == acfg->http_status) && (NULL == acfg) ) { cr->http_status = MHD_HTTP_NOT_FOUND; cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED; } - else if (NULL != acfg) + else { - if (0 == acfg->storage_limit_in_megabytes) + if (0 == acfg->details.ok.storage_limit_in_megabytes) { cr->http_status = 0; cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG; @@ -591,26 +638,36 @@ config_cb (void *cls, { cr->ec = TALER_EC_NONE; GNUNET_free (cr->business_name); - cr->business_name = GNUNET_strdup (acfg->business_name); + cr->business_name = GNUNET_strdup (acfg->details.ok.business_name); for (unsigned int i = 0; i<cr->methods_length; i++) GNUNET_free (cr->methods[i].type); GNUNET_free (cr->methods); - cr->methods = GNUNET_new_array (acfg->methods_length, + cr->methods = GNUNET_new_array (acfg->details.ok.methods_length, struct AuthorizationMethodConfig); - for (unsigned int i = 0; i<acfg->methods_length; i++) + for (unsigned int i = 0; i<acfg->details.ok.methods_length; i++) { - cr->methods[i].type = GNUNET_strdup (acfg->methods[i].type); - cr->methods[i].usage_fee = acfg->methods[i].usage_fee; + cr->methods[i].type = GNUNET_strdup (acfg->details.ok.methods[i].type); + cr->methods[i].usage_fee = acfg->details.ok.methods[i].usage_fee; } - cr->methods_length = acfg->methods_length; - cr->storage_limit_in_megabytes = acfg->storage_limit_in_megabytes; - cr->annual_fee = acfg->annual_fee; - cr->truth_upload_fee = acfg->truth_upload_fee; - cr->liability_limit = acfg->liability_limit; - cr->provider_salt = acfg->provider_salt; + cr->methods_length = acfg->details.ok.methods_length; + cr->storage_limit_in_megabytes = + acfg->details.ok.storage_limit_in_megabytes; + cr->annual_fee = acfg->details.ok.annual_fee; + cr->truth_upload_fee = acfg->details.ok.truth_upload_fee; + cr->liability_limit = acfg->details.ok.liability_limit; + cr->provider_salt = acfg->details.ok.provider_salt; } } notify_waiting (cr); + if (MHD_HTTP_OK != acfg->http_status) + { + cr->backoff = GNUNET_TIME_STD_BACKOFF (cr->backoff); + GNUNET_assert (NULL == cr->tt); + GNUNET_assert (NULL != cr->url); + cr->tt = GNUNET_SCHEDULER_add_delayed (cr->backoff, + &retry_config, + cr); + } } @@ -625,22 +682,57 @@ config_request_timeout (void *cls) struct ConfigRequest *cr = cls; cr->tt = NULL; - ANASTASIS_config_cancel (cr->co); - cr->co = NULL; + if (NULL != cr->co) + { + ANASTASIS_config_cancel (cr->co); + cr->co = NULL; + } cr->http_status = 0; cr->ec = TALER_EC_GENERIC_TIMEOUT; notify_waiting (cr); + cr->backoff = GNUNET_TIME_STD_BACKOFF (cr->backoff); + GNUNET_assert (NULL == cr->tt); + GNUNET_assert (NULL != cr->url); + cr->tt = GNUNET_SCHEDULER_add_delayed (cr->backoff, + &retry_config, + cr); +} + + +static void +retry_config (void *cls) +{ + struct ConfigRequest *cr = cls; + + cr->tt = NULL; + if (NULL != cr->co) + { + ANASTASIS_config_cancel (cr->co); + cr->co = NULL; + } + cr->timeout_at = GNUNET_TIME_relative_to_absolute (CONFIG_GENERIC_TIMEOUT); + GNUNET_assert (NULL == cr->tt); + cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at, + &config_request_timeout, + cr); + cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_, + cr->url, + &config_cb, + cr); + GNUNET_break (NULL != cr->co); } /** * Schedule job to obtain Anastasis provider configuration at @a url. * + * @param timeout how long to wait for a reply * @param url base URL of Anastasis provider * @return check config handle */ static struct ConfigRequest * -check_config (const char *url) +check_config (struct GNUNET_TIME_Relative timeout, + const char *url) { struct ConfigRequest *cr; @@ -650,12 +742,34 @@ check_config (const char *url) cr->url)) continue; if (NULL != cr->co) + { + struct GNUNET_TIME_Relative duration; + struct GNUNET_TIME_Relative left; + struct GNUNET_TIME_Relative xleft; + + duration = GNUNET_TIME_absolute_get_duration (cr->start_time); + left = GNUNET_TIME_relative_subtract (timeout, + duration); + xleft = GNUNET_TIME_absolute_get_remaining (cr->timeout_at); + if (GNUNET_TIME_relative_cmp (left, + <, + xleft)) + { + /* new timeout is shorter! */ + cr->timeout_at = GNUNET_TIME_relative_to_absolute (left); + GNUNET_SCHEDULER_cancel (cr->tt); + cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at, + &config_request_timeout, + cr); + } return cr; /* already on it */ + } break; } if (NULL == cr) { cr = GNUNET_new (struct ConfigRequest); + cr->start_time = GNUNET_TIME_absolute_get (); cr->url = GNUNET_strdup (url); GNUNET_CONTAINER_DLL_insert (cr_head, cr_tail, @@ -663,6 +777,12 @@ check_config (const char *url) } if (MHD_HTTP_OK == cr->http_status) return cr; + cr->timeout_at = GNUNET_TIME_relative_to_absolute (timeout); + if (NULL != cr->tt) + GNUNET_SCHEDULER_cancel (cr->tt); + cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at, + &config_request_timeout, + cr); cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_, cr->url, &config_cb, @@ -672,9 +792,6 @@ check_config (const char *url) GNUNET_break (0); return NULL; } - cr->tt = GNUNET_SCHEDULER_add_delayed (CONFIG_GENERIC_TIMEOUT, - &config_request_timeout, - cr); return cr; } @@ -761,6 +878,9 @@ begin_provider_config_check (const char *cc, cc)) ) { /* skip */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Skipping provider restricted to country `%s'\n", + restricted); continue; } if ( (NULL == restricted) && @@ -780,7 +900,8 @@ begin_provider_config_check (const char *cc, json_object_set_new (pl, url, prov)); - check_config (url); + check_config (CONFIG_GENERIC_TIMEOUT, + url); } GNUNET_assert (0 == json_object_set_new (state, @@ -1144,7 +1265,8 @@ ANASTASIS_REDUX_add_provider_to_state_ (const char *url, struct ConfigRequest *cr; struct ConfigReduxWaiting *w; - cr = check_config (url); + cr = check_config (CONFIG_FAST_TIMEOUT, + url); w = GNUNET_new (struct ConfigReduxWaiting); w->cr = cr; w->state = json_incref (state); @@ -1157,8 +1279,10 @@ ANASTASIS_REDUX_add_provider_to_state_ (const char *url, w); if (NULL == cr->co) { - notify_waiting (cr); - return NULL; + if (NULL != cr->tt) + GNUNET_SCHEDULER_cancel (cr->tt); + cr->tt = GNUNET_SCHEDULER_add_now (¬ify_waiting_cb, + cr); } return &w->ra; } diff --git a/src/reducer/anastasis_api_redux.h b/src/reducer/anastasis_api_redux.h index cc59713..03eef33 100644 --- a/src/reducer/anastasis_api_redux.h +++ b/src/reducer/anastasis_api_redux.h @@ -108,6 +108,42 @@ ANASTASIS_REDUX_load_continents_ (void); /** + * 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 + */ +struct ANASTASIS_ReduxAction * +ANASTASIS_REDUX_poll_providers_ (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls); + + +/** + * 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 + */ +struct ANASTASIS_ReduxAction * +ANASTASIS_REDUX_sync_providers_ (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls); + + +/** * Returns the enum value to a string value of a state. * * @param state_string string to convert |