diff options
Diffstat (limited to 'src/reducer')
-rw-r--r-- | src/reducer/Makefile.am | 5 | ||||
-rw-r--r-- | src/reducer/anastasis_api_backup_redux.c | 658 | ||||
-rw-r--r-- | src/reducer/anastasis_api_discovery.c | 549 | ||||
-rw-r--r-- | src/reducer/anastasis_api_providers.c | 300 | ||||
-rw-r--r-- | src/reducer/anastasis_api_recovery_redux.c | 1863 | ||||
-rw-r--r-- | src/reducer/anastasis_api_redux.c | 735 | ||||
-rw-r--r-- | src/reducer/anastasis_api_redux.h | 68 | ||||
-rw-r--r-- | src/reducer/validation_CH_AHV.c | 6 | ||||
-rw-r--r-- | src/reducer/validation_CZ_BN.c | 6 | ||||
-rw-r--r-- | src/reducer/validation_DE_SVN.c | 6 | ||||
-rw-r--r-- | src/reducer/validation_DE_TIN.c | 6 | ||||
-rw-r--r-- | src/reducer/validation_ES_DNI.c | 184 | ||||
-rw-r--r-- | src/reducer/validation_FR_INSEE.c | 66 | ||||
-rw-r--r-- | src/reducer/validation_IN_AADHAR.c | 6 | ||||
-rw-r--r-- | src/reducer/validation_IT_CF.c | 6 | ||||
-rw-r--r-- | src/reducer/validation_NL_BSN.c | 57 | ||||
-rw-r--r-- | src/reducer/validation_XX_SQUARE.c | 6 | ||||
-rw-r--r-- | src/reducer/validation_XY_PRIME.c | 6 |
18 files changed, 3101 insertions, 1432 deletions
diff --git a/src/reducer/Makefile.am b/src/reducer/Makefile.am index 5cbe6f7..1536d21 100644 --- a/src/reducer/Makefile.am +++ b/src/reducer/Makefile.am @@ -15,6 +15,8 @@ libanastasisredux_la_LDFLAGS = \ -version-info 0:0:0 \ -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 \ @@ -22,8 +24,11 @@ libanastasisredux_la_SOURCES = \ validation_CZ_BN.c \ validation_DE_SVN.c \ validation_DE_TIN.c \ + validation_ES_DNI.c \ + validation_FR_INSEE.c \ validation_IN_AADHAR.c \ validation_IT_CF.c \ + validation_NL_BSN.c \ validation_XX_SQUARE.c \ validation_XY_PRIME.c libanastasisredux_la_LIBADD = \ diff --git a/src/reducer/anastasis_api_backup_redux.c b/src/reducer/anastasis_api_backup_redux.c index cfef852..6ca6de7 100644 --- a/src/reducer/anastasis_api_backup_redux.c +++ b/src/reducer/anastasis_api_backup_redux.c @@ -1,16 +1,16 @@ /* 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 Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -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 @@ -131,7 +131,7 @@ ANASTASIS_backup_state_from_string_ (const char *state_string) if (0 == strcmp (state_string, backup_strings[i])) return i; - return ANASTASIS_BACKUP_STATE_ERROR; + return ANASTASIS_BACKUP_STATE_INVALID; } @@ -177,11 +177,78 @@ json_t * ANASTASIS_backup_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 backup start status\n", + external_reducer); + + GNUNET_assert (0 == pipe (pipefd_stdout)); + pid = fork (); + if (pid == 0) + { + close (pipefd_stdout[0]); + dup2 (pipefd_stdout[1], STDOUT_FILENO); + execlp (external_reducer, + external_reducer, + "-b", + 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 ("backup"))); set_state (initial_state, ANASTASIS_BACKUP_STATE_CONTINENT_SELECTING); return initial_state; @@ -286,22 +353,30 @@ add_authentication (json_t *state, json_object_foreach (auth_providers, url, details) { - json_t *methods; + const json_t *methods = NULL; json_t *method; size_t index; - uint32_t size_limit_in_mb; + uint32_t size_limit_in_mb = 0; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", - &size_limit_in_mb), - GNUNET_JSON_spec_json ("methods", - &methods), + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", + &size_limit_in_mb), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("methods", + &methods), + NULL), GNUNET_JSON_spec_end () }; - if (MHD_HTTP_OK != - json_integer_value (json_object_get (details, - "http_status"))) - continue; /* skip providers that are down */ if (GNUNET_OK != GNUNET_JSON_parse (details, ispec, @@ -310,6 +385,17 @@ add_authentication (json_t *state, GNUNET_break (0); continue; } + if (0 != strcmp (status, + "ok")) + continue; + if (MHD_HTTP_OK != http_status) + continue; /* skip providers that are down */ + if ( (NULL == methods) || + (0 == size_limit_in_mb) ) + { + GNUNET_break (0); + continue; + } json_array_foreach (methods, index, method) { const char *type; @@ -325,7 +411,6 @@ add_authentication (json_t *state, break; } } - GNUNET_JSON_parse_free (ispec); if (! challenge_size_ok (size_limit_in_mb, challenge_size)) { @@ -681,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; @@ -708,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 () @@ -727,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 () @@ -847,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; @@ -857,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 () @@ -930,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; @@ -952,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 @@ -1082,6 +1167,34 @@ provider_candidate (struct PolicyBuilder *pb, json_object_foreach (pb->providers, url, pconfig) { + const char *status; + uint32_t http_status = 0; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (pconfig, + spec, + NULL, NULL)) + { + GNUNET_break (0); + continue; + } + if ( (MHD_HTTP_OK != http_status) || + (0 == strcmp (status, + "disabled")) ) + { + GNUNET_JSON_parse_free (spec); + continue; + } + GNUNET_JSON_parse_free (spec); prov_sel[i] = url; if (i == pb->req_methods - 1) { @@ -1171,58 +1284,6 @@ method_candidate (struct PolicyBuilder *pb, /** - * Lookup @a salt of @a provider_url in @a state. - * - * @param state the state to inspect - * @param provider_url provider to look into - * @param[out] salt value to extract - * @return #GNUNET_OK on success - */ -static int -lookup_salt (const json_t *state, - const char *provider_url, - struct ANASTASIS_CRYPTO_ProviderSaltP *salt) -{ - const json_t *aps; - const json_t *cfg; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("salt", - salt), - GNUNET_JSON_spec_end () - }; - - aps = json_object_get (state, - "authentication_providers"); - if (NULL == aps) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - cfg = json_object_get (aps, - provider_url); - if (NULL == cfg) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (MHD_HTTP_OK != - json_integer_value (json_object_get (cfg, - "http_status"))) - return GNUNET_NO; /* skip providers that are down */ - if (GNUNET_OK != - GNUNET_JSON_parse (cfg, - spec, - NULL, NULL)) - { - /* provider not working */ - GNUNET_break_op (0); - return GNUNET_NO; - } - return GNUNET_OK; -} - - -/** * Compare two cost lists. * * @param my cost to compare @@ -1604,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, @@ -1612,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: @@ -1622,6 +1685,11 @@ done_authentication (json_t *state, "'authentication_methods' must not be empty"); return NULL; case 1: + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "Two factor authentication (2-FA) is required"); + return NULL; case 2: pb.req_methods = pb.num_methods; break; @@ -1685,9 +1753,9 @@ done_authentication (json_t *state, struct ANASTASIS_CRYPTO_ProviderSaltP salt; if (GNUNET_OK != - lookup_salt (state, - url, - &salt)) + ANASTASIS_reducer_lookup_salt (state, + url, + &salt)) continue; /* skip providers that are down */ provider = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("provider_url", @@ -1714,9 +1782,9 @@ done_authentication (json_t *state, url_str = json_string_value (url); if ( (NULL == url_str) || (GNUNET_OK != - lookup_salt (state, - url_str, - &salt)) ) + ANASTASIS_reducer_lookup_salt (state, + url_str, + &salt)) ) { GNUNET_break (0); ANASTASIS_redux_fail_ (cb, @@ -1877,8 +1945,8 @@ add_policy (json_t *state, { const char *provider_url; uint32_t method_idx; - json_t *prov_methods; const char *method_type; + const json_t *prov_methods; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("provider", &provider_url), @@ -1904,11 +1972,19 @@ add_policy (json_t *state, { const json_t *prov_cfg; uint32_t limit; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + 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 () }; @@ -1924,10 +2000,6 @@ add_policy (json_t *state, "provider URL unknown"); return NULL; } - if (MHD_HTTP_OK != - json_integer_value (json_object_get (prov_cfg, - "http_status"))) - continue; if (GNUNET_OK != GNUNET_JSON_parse (prov_cfg, spec, @@ -1937,16 +2009,13 @@ add_policy (json_t *state, json_decref (methods); continue; } - if (! json_is_array (prov_methods)) + if ( (MHD_HTTP_OK != http_status) || + (0 != strcmp (status, + "ok")) ) { - GNUNET_break (0); + /* skip provider, disabled or down */ 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; + continue; } } @@ -1959,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, @@ -1972,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, @@ -2004,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, @@ -2021,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, @@ -2032,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) */ } @@ -2323,12 +2387,12 @@ del_challenge (json_t *state, * @return number of years of service to pay for */ static unsigned int -expiration_to_years (struct GNUNET_TIME_Absolute expiration) +expiration_to_years (struct GNUNET_TIME_Timestamp expiration) { struct GNUNET_TIME_Relative rem; unsigned int years; - rem = GNUNET_TIME_absolute_get_remaining (expiration); + rem = GNUNET_TIME_absolute_get_remaining (expiration.abs_time); years = rem.rel_value_us / GNUNET_TIME_UNIT_YEARS.rel_value_us; if (0 != rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us) years++; @@ -2348,7 +2412,7 @@ expiration_to_years (struct GNUNET_TIME_Absolute expiration) */ static enum GNUNET_GenericReturnValue update_expiration_cost (json_t *state, - struct GNUNET_TIME_Absolute expiration) + struct GNUNET_TIME_Timestamp expiration) { struct Costs *costs = NULL; unsigned int years; @@ -2373,26 +2437,33 @@ update_expiration_cost (json_t *state, json_object_foreach (providers, url, provider) { struct TALER_Amount annual_fee; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification pspec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), TALER_JSON_spec_amount_any ("annual_fee", &annual_fee), GNUNET_JSON_spec_end () }; struct TALER_Amount fee; - if (MHD_HTTP_OK != - json_integer_value (json_object_get (provider, - "http_status"))) - continue; /* skip providers that are down */ if (GNUNET_OK != GNUNET_JSON_parse (provider, pspec, NULL, NULL)) { - /* strange, skip as well */ - GNUNET_break_op (0); + /* likely down, skip */ continue; } + if ( (MHD_HTTP_OK != http_status) || + (0 != strcmp (status, + "ok")) ) + continue; /* skip providers that are down or disabled */ if (0 > TALER_amount_multiply (&fee, &annual_fee, @@ -2473,7 +2544,15 @@ update_expiration_cost (json_t *state, off++; { struct TALER_Amount upload_cost; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification pspec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), TALER_JSON_spec_amount_any ("truth_upload_fee", &upload_cost), GNUNET_JSON_spec_end () @@ -2491,6 +2570,13 @@ update_expiration_cost (json_t *state, GNUNET_break (0); return GNUNET_SYSERR; } + if ( (MHD_HTTP_OK != http_status) || + (0 != strcmp (status, + "ok")) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } if (0 > TALER_amount_multiply (&fee, &upload_cost, @@ -2519,8 +2605,7 @@ update_expiration_cost (json_t *state, { struct Costs *nxt = costs->next; - if ( (0 != costs->cost.value) || - (0 != costs->cost.fraction) ) + if (! TALER_amount_is_zero (&costs->cost)) { json_t *ao; @@ -2542,13 +2627,12 @@ update_expiration_cost (json_t *state, } if (is_free) - expiration = GNUNET_TIME_relative_to_absolute (ANASTASIS_FREE_STORAGE); + expiration = GNUNET_TIME_relative_to_timestamp (ANASTASIS_FREE_STORAGE); /* update 'expiration' in state */ { json_t *eo; - (void) GNUNET_TIME_round_abs (&expiration); - eo = GNUNET_JSON_from_time_abs (expiration); + eo = GNUNET_JSON_from_timestamp (expiration); GNUNET_assert (0 == json_object_set_new (state, "expiration", @@ -2589,11 +2673,13 @@ done_policy_review (json_t *state, return NULL; } { - struct GNUNET_TIME_Absolute exp = {0}; + struct GNUNET_TIME_Timestamp exp + = GNUNET_TIME_UNIT_ZERO_TS; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_absolute_time ("expiration", - &exp)), + GNUNET_JSON_spec_timestamp ("expiration", + &exp), + NULL), GNUNET_JSON_spec_end () }; @@ -2608,8 +2694,8 @@ done_policy_review (json_t *state, "invalid expiration specified"); return NULL; } - if (0 == exp.abs_value_us) - exp = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS); + if (GNUNET_TIME_absolute_is_zero (exp.abs_time)) + exp = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS); if (GNUNET_OK != update_expiration_cost (state, exp)) @@ -2869,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 *` @@ -2901,8 +3043,8 @@ secret_share_result_cb (void *cls, d = GNUNET_JSON_PACK ( GNUNET_JSON_pack_uint64 ("policy_version", pssi->policy_version), - GNUNET_JSON_pack_time_abs ("policy_expiration", - pssi->policy_expiration)); + GNUNET_JSON_pack_timestamp ("policy_expiration", + pssi->policy_expiration)); GNUNET_assert (NULL != d); GNUNET_assert (0 == json_object_set_new (sa, @@ -2952,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) { @@ -2993,13 +3136,53 @@ 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_string ("backup_state", - "ERROR"), GNUNET_JSON_pack_uint64 ("http_status", sr->details.provider_failure.http_status), - GNUNET_JSON_pack_uint64 ("upload_status", + GNUNET_JSON_pack_uint64 ("code", sr->details.provider_failure.ec), + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + sr->details.provider_failure.ec)), GNUNET_JSON_pack_string ("provider_url", sr->details.provider_failure.provider_url)); uc->cb (uc->cb_cls, @@ -3028,26 +3211,29 @@ 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)), + &secret_name), + NULL), GNUNET_JSON_spec_end () }; @@ -3069,12 +3255,13 @@ share_secret (struct UploadContext *uc) struct GNUNET_JSON_Specification pspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &timeout)), + &timeout), + NULL), GNUNET_JSON_spec_end () }; args = json_object_get (uc->state, - "pay-arguments"); + "pay_arguments"); if ( (NULL != args) && (GNUNET_OK != GNUNET_JSON_parse (args, @@ -3094,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, @@ -3142,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]; @@ -3163,13 +3354,14 @@ 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), GNUNET_JSON_spec_uint32 ("authentication_method", @@ -3190,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; } @@ -3207,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; } @@ -3246,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, @@ -3272,7 +3458,8 @@ share_secret (struct UploadContext *uc) struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("payment_secret", - &pds[i].payment_secret)), + &pds[i].payment_secret), + NULL), GNUNET_JSON_spec_string ("provider_url", &pds[i].provider_url), GNUNET_JSON_spec_end () @@ -3283,9 +3470,9 @@ share_secret (struct UploadContext *uc) ispec, NULL, NULL)) || (GNUNET_OK != - lookup_salt (uc->state, - pds[i].provider_url, - &pds[i].provider_salt)) ) + ANASTASIS_reducer_lookup_salt (uc->state, + pds[i].provider_url, + &pds[i].provider_salt)) ) { GNUNET_break (0); ANASTASIS_redux_fail_ (uc->cb, @@ -3295,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; } } @@ -3326,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); @@ -3519,7 +3704,8 @@ add_truth_object (struct UploadContext *uc, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("upload_status", - &status)), + &status), + NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != @@ -3568,9 +3754,9 @@ add_truth_object (struct UploadContext *uc, }; if (GNUNET_OK != - lookup_salt (uc->state, - provider_url, - &salt)) + ANASTASIS_reducer_lookup_salt (uc->state, + provider_url, + &salt)) { GNUNET_break (0); return GNUNET_SYSERR; @@ -3703,10 +3889,12 @@ check_truth_upload (struct UploadContext *uc, &type), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("mime_type", - &mime_type)), + &mime_type), + NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("instructions", - &instructions)), + &instructions), + NULL), GNUNET_JSON_spec_varsize ("challenge", &truth_data, &truth_data_size), @@ -3737,9 +3925,9 @@ check_truth_upload (struct UploadContext *uc, tue->am_idx = am_idx; tue->policies_length = 1; if (GNUNET_OK != - lookup_salt (uc->state, - provider_url, - &provider_salt)) + ANASTASIS_reducer_lookup_salt (uc->state, + provider_url, + &provider_salt)) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); @@ -3755,7 +3943,6 @@ check_truth_upload (struct UploadContext *uc, struct ANASTASIS_CRYPTO_TruthKeyP truth_key; struct ANASTASIS_CRYPTO_KeyShareP key_share; struct ANASTASIS_CRYPTO_NonceP nonce; - struct GNUNET_JSON_Specification jspec[] = { GNUNET_JSON_spec_fixed_auto ("salt", &question_salt), @@ -3841,10 +4028,10 @@ upload (json_t *state, struct UploadContext *uc; json_t *auth_methods; json_t *policies; - struct GNUNET_TIME_Absolute expiration; + struct GNUNET_TIME_Timestamp expiration; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_absolute_time ("expiration", - &expiration), + GNUNET_JSON_spec_timestamp ("expiration", + &expiration), GNUNET_JSON_spec_end () }; @@ -3895,12 +4082,13 @@ upload (json_t *state, struct GNUNET_JSON_Specification pspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &uc->timeout)), + &uc->timeout), + NULL), GNUNET_JSON_spec_end () }; args = json_object_get (uc->state, - "pay-arguments"); + "pay_arguments"); if ( (NULL != args) && (GNUNET_OK != GNUNET_JSON_parse (args, @@ -3954,7 +4142,8 @@ upload (json_t *state, &am_idx), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("truth", - &truth)), + &truth), + NULL), GNUNET_JSON_spec_end () }; @@ -4111,17 +4300,23 @@ check_upload_size_limit (json_t *state, see #6760. */ json_object_foreach (aps, url, ap) { - uint32_t limit; + uint32_t limit = 0; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", - &limit), + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", + &limit), + NULL), GNUNET_JSON_spec_end () }; - if (MHD_HTTP_OK != - json_integer_value (json_object_get (ap, - "http_status"))) - continue; /* skip providers that are down */ if (GNUNET_OK != GNUNET_JSON_parse (ap, spec, @@ -4131,6 +4326,10 @@ check_upload_size_limit (json_t *state, GNUNET_break_op (0); continue; } + if ( (MHD_HTTP_OK != http_status) || + (0 != strcmp (status, + "ok")) ) + continue; if (0 == limit) return GNUNET_SYSERR; min_limit = GNUNET_MIN (min_limit, @@ -4161,13 +4360,15 @@ enter_secret (json_t *state, void *cb_cls) { json_t *jsecret; - struct GNUNET_TIME_Absolute expiration = {0}; + struct GNUNET_TIME_Timestamp expiration + = GNUNET_TIME_UNIT_ZERO_TS; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_json ("secret", &jsecret), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_absolute_time ("expiration", - &expiration)), + GNUNET_JSON_spec_timestamp ("expiration", + &expiration), + NULL), GNUNET_JSON_spec_end () }; @@ -4215,7 +4416,7 @@ enter_secret (json_t *state, break; } } - if (0 != expiration.abs_value_us) + if (! GNUNET_TIME_absolute_is_zero (expiration.abs_time)) { if (GNUNET_OK != update_expiration_cost (state, @@ -4346,10 +4547,10 @@ update_expiration (json_t *state, ANASTASIS_ActionCallback cb, void *cb_cls) { - struct GNUNET_TIME_Absolute expiration; + struct GNUNET_TIME_Timestamp expiration; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_absolute_time ("expiration", - &expiration), + GNUNET_JSON_spec_timestamp ("expiration", + &expiration), GNUNET_JSON_spec_end () }; @@ -4483,7 +4684,7 @@ pay_truths_backup (json_t *state, if (NULL != arguments) GNUNET_assert (0 == json_object_set (state, - "pay-arguments", + "pay_arguments", (json_t *) arguments)); return upload (state, cb, @@ -4513,7 +4714,7 @@ pay_policies_backup (json_t *state, if (NULL != arguments) GNUNET_assert (0 == json_object_set (state, - "pay-arguments", + "pay_arguments", (json_t *) arguments)); return upload (state, cb, @@ -4596,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_ }, @@ -4674,7 +4880,7 @@ ANASTASIS_backup_action_ (json_t *state, "back", &back_finished }, - { ANASTASIS_BACKUP_STATE_ERROR, NULL, NULL } + { ANASTASIS_BACKUP_STATE_INVALID, NULL, NULL } }; const char *s = json_string_value (json_object_get (state, "backup_state")); @@ -4682,7 +4888,7 @@ ANASTASIS_backup_action_ (json_t *state, GNUNET_assert (NULL != s); /* holds as per invariant of caller */ bs = ANASTASIS_backup_state_from_string_ (s); - if (ANASTASIS_BACKUP_STATE_ERROR == bs) + if (ANASTASIS_BACKUP_STATE_INVALID == bs) { ANASTASIS_redux_fail_ (cb, cb_cls, @@ -4935,7 +5141,25 @@ ANASTASIS_REDUX_backup_begin_ (json_t *state, json_object_foreach (provider_list, url, prov) { struct BackupStartStateProviderEntry *pe; json_t *istate; + const char *status; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (prov, + spec, + NULL, NULL)) + { + /* skip malformed provider entry */ + GNUNET_break_op (0); + continue; + } + if (0 == strcmp (status, + "disabled")) + continue; pe = GNUNET_new (struct BackupStartStateProviderEntry); pe->bss = bss; istate = json_object (); diff --git a/src/reducer/anastasis_api_discovery.c b/src/reducer/anastasis_api_discovery.c new file mode 100644 index 0000000..3470d97 --- /dev/null +++ b/src/reducer/anastasis_api_discovery.c @@ -0,0 +1,549 @@ +/* + This file is part of Anastasis + Copyright (C) 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_discovery.c + * @brief anastasis recovery policy discovery api + * @author Christian Grothoff + */ +#include <platform.h> +#include <jansson.h> +#include "anastasis_redux.h" +#include "anastasis_error_codes.h" +#include <taler/taler_json_lib.h> +#include "anastasis_api_redux.h" +#include <dlfcn.h> + + +/** + * Handle for one request we are doing at a specific provider. + */ +struct ProviderOperation +{ + /** + * Kept in a DLL. + */ + struct ProviderOperation *next; + + /** + * Kept in a DLL. + */ + struct ProviderOperation *prev; + + /** + * Base URL of the provider. + */ + char *provider_url; + + /** + * Handle to the version check operation we are performing. + */ + struct ANASTASIS_VersionCheck *vc; + + /** + * Handle discovery operation we this is a part of. + */ + struct ANASTASIS_PolicyDiscovery *pd; + + /** + * Attribute mask applied to the identity attributes + * for this operation. + */ + json_int_t attribute_mask; +}; + + +/** + * Handle for a discovery operation. + */ +struct ANASTASIS_PolicyDiscovery +{ + /** + * Head of HTTP requests, kept in a DLL. + */ + struct ProviderOperation *po_head; + + /** + * Tail of HTTP requests, kept in a DLL. + */ + struct ProviderOperation *po_tail; + + /** + * Function to call with results. + */ + ANASTASIS_PolicyDiscoveryCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Map for duplicate detection, maps hashes of policies we + * have already seen to a json_array with all providers + * and versions corresponding to this policy hash. + */ + struct GNUNET_CONTAINER_MultiHashMap *dd_map; + + /** + * State we are operating on. + */ + json_t *state; + + /** + * Number of optional fields in our identity attributes. + */ + json_int_t opt_cnt; +}; + + +/** + * Callback which passes back meta data about one of the + * recovery documents available at the provider. + * + * @param cls our `struct ProviderOperation *` + * @param version version number of the policy document, + * 0 for the end of the list + * @param server_time time of the backup at the provider + * @param recdoc_id hash of the compressed recovery document, uniquely + * identifies the document; NULL for the end of the list + * @param secret_name name of the secret as chosen by the user, + * or NULL if the user did not provide a name + */ +static void +meta_cb (void *cls, + uint32_t version, + struct GNUNET_TIME_Timestamp server_time, + const struct GNUNET_HashCode *recdoc_id, + const char *secret_name) +{ + struct ProviderOperation *po = cls; + struct ANASTASIS_PolicyDiscovery *pd = po->pd; + json_t *pa; + json_t *pe; + + if (NULL == recdoc_id) + { + po->vc = NULL; + GNUNET_CONTAINER_DLL_remove (pd->po_head, + pd->po_tail, + po); + GNUNET_free (po->provider_url); + GNUNET_free (po); + return; + } + pe = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("version", + version), + GNUNET_JSON_pack_string ("url", + po->provider_url)); + + pa = GNUNET_CONTAINER_multihashmap_get (pd->dd_map, + recdoc_id); + if (NULL != pa) + { + GNUNET_break (0 == + json_array_append_new (pa, + pe)); + return; + } + pa = json_array (); + GNUNET_assert (NULL != pa); + GNUNET_break (0 == + json_array_append_new (pa, + pe)); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + pd->dd_map, + recdoc_id, + pa, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + pd->cb (pd->cb_cls, + recdoc_id, + po->provider_url, + version, + po->attribute_mask, + server_time, + secret_name, + pa); +} + + +/** + * Start policy operation for @a pd using identity @a id_data + * at provider @a provider_url. + * + * @param pd policy discovery operation + * @param id_data our identity data, derived using @a mask + * @param mask the mask describing which optional attributes were removed + * @param provider_url which provider to query + * @param cursor cursor telling us from where to query + */ +static void +start_po (struct ANASTASIS_PolicyDiscovery *pd, + const json_t *id_data, + json_int_t mask, + const char *provider_url, + const json_t *cursor) +{ + const json_t *state = pd->state; + struct ProviderOperation *po; + uint32_t max_version = UINT32_MAX; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + + if (NULL != cursor) + { + size_t i; + json_t *obj; + + json_array_foreach ((json_t *) cursor, i, obj) + { + const char *url; + uint64_t cmask; + uint32_t mv; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("provider_url", + &url), + GNUNET_JSON_spec_uint64 ("mask", + &cmask), + GNUNET_JSON_spec_uint32 ("max_version", + &mv), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, NULL)) + { + /* cursor invalid */ + GNUNET_break (0); + json_dumpf (obj, + stderr, + JSON_INDENT (2)); + return; + } + if ( (cmask == mask) && + (0 == strcmp (url, + provider_url)) ) + { + max_version = mv; + break; + } + } + } + + if (GNUNET_OK != + ANASTASIS_reducer_lookup_salt (state, + provider_url, + &provider_salt)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No /config for `%s', skipping provider\n", + provider_url); + return; + } + po = GNUNET_new (struct ProviderOperation); + po->pd = pd; + po->attribute_mask = mask; + po->provider_url = GNUNET_strdup (provider_url); + po->vc = ANASTASIS_recovery_get_versions (ANASTASIS_REDUX_ctx_, + id_data, + max_version, + provider_url, + &provider_salt, + &meta_cb, + po); + if (NULL == po->vc) + { + GNUNET_free (po); + } + else + { + GNUNET_CONTAINER_DLL_insert (pd->po_head, + pd->po_tail, + po); + } +} + + +struct ANASTASIS_PolicyDiscovery * +ANASTASIS_policy_discovery_start (const json_t *state, + const json_t *cursor, + ANASTASIS_PolicyDiscoveryCallback cb, + void *cb_cls) +{ + struct ANASTASIS_PolicyDiscovery *pd; + json_t *master_id = json_object_get (state, + "identity_attributes"); + json_t *providers = json_object_get (state, + "authentication_providers"); + json_t *required_attributes = json_object_get (state, + "required_attributes"); + unsigned int opt_cnt; + + if ( (NULL == master_id) || + (! json_is_object (master_id)) ) + { + GNUNET_break (0); + json_dumpf (state, + stderr, + JSON_INDENT (2)); + return NULL; + } + if ( (NULL == providers) || + (! json_is_object (providers)) ) + { + GNUNET_break (0); + json_dumpf (state, + stderr, + JSON_INDENT (2)); + return NULL; + } + if ( (NULL == required_attributes) || + (! json_is_array (required_attributes)) ) + { + GNUNET_break (0); + json_dumpf (required_attributes, + stderr, + JSON_INDENT (2)); + return NULL; + } + + /* count optional attributes present in 'master_id' */ + opt_cnt = 0; + { + size_t index; + json_t *required_attribute; + + json_array_foreach (required_attributes, + index, + required_attribute) + { + const char *name; + int optional = false; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_boolean ("optional", + &optional), + NULL), + GNUNET_JSON_spec_end () + }; + bool present; + + if (GNUNET_OK != + GNUNET_JSON_parse (required_attribute, + spec, + NULL, NULL)) + { + GNUNET_break (0); + json_dumpf (required_attribute, + stderr, + JSON_INDENT (2)); + return NULL; + } + present = (NULL != + json_object_get (master_id, + name)); + if ((! present) && (! optional)) + { + GNUNET_break (0); + json_dumpf (master_id, + stderr, + JSON_INDENT (2)); + return NULL; + } + if (present && optional) + opt_cnt++; + } + } + + pd = GNUNET_new (struct ANASTASIS_PolicyDiscovery); + pd->dd_map = GNUNET_CONTAINER_multihashmap_create (128, + GNUNET_NO); + pd->cb = cb; + pd->cb_cls = cb_cls; + pd->opt_cnt = opt_cnt; + pd->state = json_deep_copy (state); + + /* Compute 'id_data' for all possible masks, and then + start downloads at all providers for 'id_data' */ + for (json_int_t mask = 0; mask < (1LL << opt_cnt); mask++) + { + json_t *id_data = ANASTASIS_mask_id_data (state, + master_id, + mask); + json_t *value; + const char *url; + + json_object_foreach (providers, url, value) + { + start_po (pd, + id_data, + mask, + url, + cursor); + } + json_decref (id_data); + } + return pd; +} + + +void +ANASTASIS_policy_discovery_more (struct ANASTASIS_PolicyDiscovery *pd, + const char *provider_url, + json_t *provider_state) +{ + json_t *master_id = json_object_get (pd->state, + "identity_attributes"); + json_t *providers = json_object_get (pd->state, + "authentication_providers"); + + GNUNET_assert (NULL != master_id); + GNUNET_assert (NULL != providers); + GNUNET_assert (0 == + json_object_set (providers, + provider_url, + provider_state)); + /* Compute 'id_data' for all possible masks, and then + start downloads at provider for 'id_data' */ + for (json_int_t mask = 0; mask < (1LL << pd->opt_cnt); mask++) + { + json_t *id_data = ANASTASIS_mask_id_data (pd->state, + master_id, + mask); + + start_po (pd, + id_data, + mask, + provider_url, + NULL); + json_decref (id_data); + } +} + + +/** + * Free JSON Arrays from our hash map. + * + * @param cls NULL + * @param key ignored + * @param value `json_t *` to free + * @return #GNUNET_OK + */ +static enum GNUNET_GenericReturnValue +free_dd_json (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + json_t *j = value; + + (void) cls; + (void) key; + json_decref (j); + return GNUNET_OK; +} + + +void +ANASTASIS_policy_discovery_stop (struct ANASTASIS_PolicyDiscovery *pd) +{ + struct ProviderOperation *po; + + while (NULL != (po = pd->po_head)) + { + GNUNET_CONTAINER_DLL_remove (pd->po_head, + pd->po_tail, + po); + ANASTASIS_recovery_get_versions_cancel (po->vc); + GNUNET_free (po->provider_url); + GNUNET_free (po); + } + GNUNET_CONTAINER_multihashmap_iterate (pd->dd_map, + &free_dd_json, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (pd->dd_map); + json_decref (pd->state); + GNUNET_free (pd); +} + + +json_t * +ANASTASIS_mask_id_data (const json_t *state, + const json_t *master_id, + json_int_t mask) +{ + json_t *required_attributes = json_object_get (state, + "required_attributes"); + size_t index; + json_t *required_attribute; + json_t *ret = json_deep_copy (master_id); + unsigned int bit = 0; + + if ( (NULL == required_attributes) || + (! json_is_array (required_attributes)) ) + { + GNUNET_break (0); + return NULL; + } + + json_array_foreach (required_attributes, index, required_attribute) + { + const char *name; + int optional = false; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_boolean ("optional", + &optional), + NULL), + GNUNET_JSON_spec_end () + }; + bool present; + + if (GNUNET_OK != + GNUNET_JSON_parse (required_attribute, + spec, + NULL, NULL)) + { + GNUNET_break (0); + return NULL; + } + present = (NULL != + json_object_get (master_id, + name)); + if ((! present) && (! optional)) + { + GNUNET_break (0); + return NULL; + } + if (present && optional) + { + if (0 != ((1LL << bit) & mask)) + { + GNUNET_assert (0 == + json_object_del (ret, + name)); + } + bit++; + } + } + return ret; +} 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 897a6dd..e795c55 100644 --- a/src/reducer/anastasis_api_recovery_redux.c +++ b/src/reducer/anastasis_api_recovery_redux.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -44,7 +44,7 @@ ANASTASIS_recovery_state_from_string_ (const char *state_string) if (0 == strcmp (state_string, recovery_strings[i])) return i; - return ANASTASIS_RECOVERY_STATE_ERROR; + return ANASTASIS_RECOVERY_STATE_INVALID; } @@ -83,11 +83,79 @@ json_t * ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg) { json_t *initial_state; + const char *external_reducer = ANASTASIS_REDUX_probe_external_reducer (); + + if (NULL != external_reducer) + { + int pipefd_stdout[2]; + pid_t pid = 0; + int status; + FILE *reducer_stdout; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Using external reducer '%s' for recovery start status\n", + external_reducer); + + GNUNET_assert (0 == pipe (pipefd_stdout)); + pid = fork (); + if (pid == 0) + { + (void) close (pipefd_stdout[0]); + (void) dup2 (pipefd_stdout[1], + STDOUT_FILENO); + execlp (external_reducer, + external_reducer, + "-r", + NULL); + GNUNET_assert (0); + } + + close (pipefd_stdout[1]); + reducer_stdout = fdopen (pipefd_stdout[0], + "r"); + { + json_error_t err; + + initial_state = json_loadf (reducer_stdout, + 0, + &err); + + if (NULL == initial_state) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "External reducer did not output valid JSON: %s:%d:%d %s\n", + err.source, + err.line, + err.column, + err.text); + GNUNET_assert (0 == fclose (reducer_stdout)); + waitpid (pid, &status, 0); + return NULL; + } + } + + GNUNET_assert (NULL != initial_state); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Waiting for external reducer to terminate.\n"); + GNUNET_assert (0 == fclose (reducer_stdout)); + reducer_stdout = NULL; + waitpid (pid, &status, 0); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "External reducer finished with exit status '%d'\n", + status); + return initial_state; + } (void) cfg; initial_state = ANASTASIS_REDUX_load_continents_ (); if (NULL == initial_state) return NULL; + GNUNET_assert ( + 0 == + json_object_set_new (initial_state, + "reducer_type", + json_string ("recovery"))); set_state (initial_state, ANASTASIS_RECOVERY_STATE_CONTINENT_SELECTING); return initial_state; @@ -188,15 +256,16 @@ sctx_free (void *cls) /** - * Update @a state to reflect the error provided in @a rc. + * Call the action callback with an error result * - * @param[in,out] state state to update + * @param cb action callback to call + * @param cb_cls closure for @a cb * @param rc error code to translate to JSON - * @return error code to use */ -static enum TALER_ErrorCode -update_state_by_error (json_t *state, - enum ANASTASIS_RecoveryStatus rc) +void +fail_by_error (ANASTASIS_ActionCallback cb, + void *cb_cls, + enum ANASTASIS_RecoveryStatus rc) { const char *msg = NULL; enum TALER_ErrorCode ec = TALER_EC_INVALID; @@ -249,17 +318,10 @@ update_state_by_error (json_t *state, ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED; break; } - GNUNET_assert (0 == - json_object_set_new (state, - "error_message", - json_string (msg))); - GNUNET_assert (0 == - json_object_set_new (state, - "error_code", - json_integer (rc))); - set_state (state, - ANASTASIS_RECOVERY_STATE_ERROR); - return ec; + ANASTASIS_redux_fail_ (cb, + cb_cls, + ec, + msg); } @@ -279,7 +341,6 @@ core_secret_cb (void *cls, size_t secret_size) { struct SelectChallengeContext *sctx = cls; - enum TALER_ErrorCode ec; sctx->r = NULL; if (ANASTASIS_RS_SUCCESS == rc) @@ -311,11 +372,9 @@ core_secret_cb (void *cls, sctx_free (sctx); return; } - ec = update_state_by_error (sctx->state, - rc); - sctx->cb (sctx->cb_cls, - ec, - sctx->state); + fail_by_error (sctx->cb, + sctx->cb_cls, + rc); sctx_free (sctx); } @@ -399,7 +458,7 @@ find_challenge_in_ri (json_t *state, /** - * Find challenge of @a uuid in @a state under "cs". + * Find challenge of @a uuid in @a state under "challenges". * * @param state the state to search * @param uuid the UUID to search for @@ -412,7 +471,7 @@ find_challenge_in_cs (json_t *state, json_t *rd = json_object_get (state, "recovery_document"); json_t *cs = json_object_get (rd, - "cs"); + "challenges"); json_t *c; size_t off; @@ -451,7 +510,7 @@ find_challenge_in_cs (json_t *state, * @param csr response details */ static void -answer_feedback_cb ( +start_feedback_cb ( void *cls, const struct ANASTASIS_ChallengeStartResponse *csr) { @@ -480,115 +539,50 @@ answer_feedback_cb ( } switch (csr->cs) { - case ANASTASIS_CHALLENGE_STATUS_SOLVED: + case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED: { - json_t *rd; - - rd = ANASTASIS_recovery_serialize (sctx->r); - if (NULL == rd) - { - GNUNET_break (0); - set_state (sctx->state, - ANASTASIS_RECOVERY_STATE_ERROR); - sctx->cb (sctx->cb_cls, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - sctx->state); - sctx_free (sctx); - return; - } - GNUNET_assert (0 == - json_object_set_new (sctx->state, - "recovery_document", - rd)); - } - { - json_t *solved; + json_t *instructions; + char *hint; - solved = GNUNET_JSON_PACK ( + GNUNET_asprintf (&hint, + _ ("Required TAN can be found in `%s'"), + csr->details.tan_filename); + instructions = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "solved")); + "code-in-file"), + GNUNET_JSON_pack_string ("filename", + csr->details.tan_filename), + GNUNET_JSON_pack_string ("display_hint", + hint)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, - solved)); + instructions)); } - /* Delay reporting challenge success, as we MAY still - also see a secret recovery success (and we can only - call the callback once) */ - sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved, - sctx); + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS: + case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED: { json_t *instructions; - const char *mime; - - mime = csr->details.open_challenge.content_type; - if (NULL != mime) - { - if ( (0 == strcasecmp (mime, - "text/plain")) || - (0 == strcasecmp (mime, - "text/utf8")) ) - { - char *s = GNUNET_strndup (csr->details.open_challenge.body, - csr->details.open_challenge.body_size); - - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "hint"), - GNUNET_JSON_pack_string ("hint", - s), - GNUNET_JSON_pack_uint64 ("http_status", - (json_int_t) csr->details.open_challenge. - http_status)); - GNUNET_free (s); - } - else if (0 == strcasecmp (mime, - "application/json")) - { - json_t *body; + char *hint; - body = json_loadb (csr->details.open_challenge.body, - csr->details.open_challenge.body_size, - JSON_REJECT_DUPLICATES, - NULL); - if (NULL == body) - { - GNUNET_break_op (0); - mime = NULL; - } - else - { - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "details"), - GNUNET_JSON_pack_object_steal ("details", - body), - GNUNET_JSON_pack_uint64 ("http_status", - csr->details.open_challenge.http_status)); - } - } - else - { - /* unexpected / unsupported mime type */ - mime = NULL; - } - } - if (NULL == mime) - { - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "body"), - GNUNET_JSON_pack_data_varsize ("body", - csr->details.open_challenge.body, - csr->details.open_challenge.body_size), - GNUNET_JSON_pack_uint64 ("http_status", - csr->details.open_challenge.http_status), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("mime_type", - mime))); - } + GNUNET_asprintf (&hint, + _ ("TAN code was sent to `%s'"), + csr->details.tan_address_hint); + instructions = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "send-to-address"), + GNUNET_JSON_pack_string ("address_hint", + csr->details.tan_address_hint), + GNUNET_JSON_pack_string ("display_hint", + hint)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -601,20 +595,24 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION: + + case ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT: { - json_t *redir; + json_t *instructions; + char *hint; - redir = GNUNET_JSON_PACK ( + GNUNET_asprintf (&hint, + _ ("TAN code already sent.")); + instructions = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "redirect"), - GNUNET_JSON_pack_string ("redirect_url", - csr->details.redirect_url)); - GNUNET_assert (NULL != redir); + "send-to-address"), + GNUNET_JSON_pack_string ("display_hint", + hint)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, - redir)); + instructions)); } set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); @@ -623,21 +621,29 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED: + + case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED: { json_t *pay; + char *hint; + GNUNET_asprintf (&hint, + _ ("Taler payment to `%s' required"), + csr->details.payment_required.taler_pay_uri); pay = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "payment"), - GNUNET_JSON_pack_string ("taler_pay_uri", - csr->details.payment_required. - taler_pay_uri), + "taler-payment"), + GNUNET_JSON_pack_string ( + "taler_pay_uri", + csr->details.payment_required.taler_pay_uri), GNUNET_JSON_pack_string ("provider", cd->provider_url), + GNUNET_JSON_pack_string ("display_hint", + hint), GNUNET_JSON_pack_data_auto ( "payment_secret", &csr->details.payment_required.payment_secret)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -663,7 +669,7 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE: + case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE: { json_t *err; @@ -671,10 +677,9 @@ answer_feedback_cb ( GNUNET_JSON_pack_string ("state", "server-failure"), GNUNET_JSON_pack_uint64 ("http_status", - csr->details.server_failure. - http_status), + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", - csr->details.server_failure.ec)); + csr->ec)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -683,17 +688,19 @@ answer_feedback_cb ( set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - csr->details.server_failure.ec, + csr->ec, sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN: + case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN: { json_t *err; err = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", "truth-unknown"), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", TALER_EC_ANASTASIS_TRUTH_UNKNOWN)); GNUNET_assert (0 == @@ -708,15 +715,242 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED: + case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED: + { + json_t *reply; + json_t *c; + char *hint; + + c = find_challenge_in_cs (sctx->state, + &cd->uuid); + if (NULL == c) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (sctx->cb, + sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + sctx_free (sctx); + return; + } + GNUNET_assert (0 == + json_object_set_new (c, + "async", + json_true ())); + GNUNET_assert ( + 0 == + json_object_set_new ( + c, + "answer-pin", + json_integer ( + csr->details.bank_transfer_required.answer_code))); + GNUNET_asprintf (&hint, + _ ("Wire %s to %s (%s) with subject %s\n"), + TALER_amount2s ( + &csr->details.bank_transfer_required.amount), + csr->details.bank_transfer_required.target_iban, + csr->details.bank_transfer_required.target_business_name, + csr->details.bank_transfer_required.wire_transfer_subject); + reply = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "iban-instructions"), + GNUNET_JSON_pack_string ( + "target_iban", + csr->details.bank_transfer_required.target_iban), + GNUNET_JSON_pack_string ( + "display_hint", + hint), + GNUNET_JSON_pack_string ( + "target_business_name", + csr->details.bank_transfer_required.target_business_name), + GNUNET_JSON_pack_string ( + "wire_transfer_subject", + csr->details.bank_transfer_required.wire_transfer_subject), + TALER_JSON_pack_amount ( + "challenge_amount", + &csr->details.bank_transfer_required.amount)); + GNUNET_free (hint); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + reply)); + } + GNUNET_assert (0 == + json_object_set_new (sctx->state, + "selected_challenge_uuid", + GNUNET_JSON_from_data_auto ( + &cd->uuid))); + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + } + GNUNET_break (0); + ANASTASIS_redux_fail_ (sctx->cb, + sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + sctx_free (sctx); +} + + +/** + * Defines a callback for the response status for a challenge answer + * operation. + * + * @param cls a `struct SelectChallengeContext *` + * @param csr response details + */ +static void +answer_feedback_cb ( + void *cls, + const struct ANASTASIS_ChallengeAnswerResponse *csr) +{ + struct SelectChallengeContext *sctx = cls; + const struct ANASTASIS_ChallengeDetails *cd; + char uuid[sizeof (cd->uuid) * 2]; + char *end; + json_t *feedback; + + cd = ANASTASIS_challenge_get_details (csr->challenge); + end = GNUNET_STRINGS_data_to_string (&cd->uuid, + sizeof (cd->uuid), + uuid, + sizeof (uuid)); + GNUNET_assert (NULL != end); + *end = '\0'; + feedback = json_object_get (sctx->state, + "challenge_feedback"); + if (NULL == feedback) + { + feedback = json_object (); + GNUNET_assert (0 == + json_object_set_new (sctx->state, + "challenge_feedback", + feedback)); + } + switch (csr->cs) + { + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED: + { + json_t *rd; + + rd = ANASTASIS_recovery_serialize (sctx->r); + if (NULL == rd) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (sctx->cb, + sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "unable to serialize recovery state"); + sctx_free (sctx); + return; + } + GNUNET_assert (0 == + json_object_set_new (sctx->state, + "recovery_document", + rd)); + } + { + json_t *solved; + + solved = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "solved")); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + solved)); + } + /* Delay reporting challenge success, as we MAY still + also see a secret recovery success (and we can only + call the callback once) */ + sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved, + sctx); + return; + case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER: + { + json_t *instructions; + + instructions = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "incorrect-answer"), + GNUNET_JSON_pack_uint64 ("error_code", + csr->ec)); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + instructions)); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED: + { + json_t *pay; + char *hint; + + GNUNET_asprintf (&hint, + _ ("Taler payment to `%s' required"), + csr->details.payment_required.taler_pay_uri); + pay = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "taler-payment"), + GNUNET_JSON_pack_string ( + "taler_pay_uri", + csr->details.payment_required.taler_pay_uri), + GNUNET_JSON_pack_string ( + "display_hint", + hint), + GNUNET_JSON_pack_string ("provider", + cd->provider_url), + GNUNET_JSON_pack_data_auto ( + "payment_secret", + &csr->details.payment_required.payment_secret)); + GNUNET_free (hint); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + pay)); + } + /* Remember payment secret for later (once application claims it paid) */ + { + json_t *challenge = find_challenge_in_ri (sctx->state, + &cd->uuid); + + GNUNET_assert (NULL != challenge); + GNUNET_assert (0 == + json_object_set_new ( + challenge, + "payment_secret", + GNUNET_JSON_from_data_auto ( + &csr->details.payment_required.payment_secret))); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE: { json_t *err; err = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "rate-limit-exceeded"), + "server-failure"), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); + csr->ec)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -725,119 +959,71 @@ answer_feedback_cb ( set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, + csr->ec, sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN: { json_t *err; err = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "authentication-timeout"), + "truth-unknown"), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", - TALER_EC_ANASTASIS_TRUTH_AUTH_TIMEOUT)); + TALER_EC_ANASTASIS_TRUTH_UNKNOWN)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, err)); } - GNUNET_break_op (0); set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - TALER_EC_ANASTASIS_TRUTH_AUTH_TIMEOUT, + TALER_EC_ANASTASIS_TRUTH_UNKNOWN, sctx->state); sctx_free (sctx); return; - - case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED: { - const json_t *body = csr->details.external_challenge; - const char *method; - json_t *details; - bool is_async = false; - uint64_t code = 0; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("method", - &method), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("async", - &is_async)), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint64 ("answer_code", - &code)), - GNUNET_JSON_spec_json ("details", - &details), - GNUNET_JSON_spec_end () - }; - json_t *reply; - - if (GNUNET_OK != - GNUNET_JSON_parse (body, - spec, - NULL, NULL)) - { - json_t *err; - - GNUNET_break_op (0); - err = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "server-failure"), - GNUNET_JSON_pack_uint64 ("error_code", - TALER_EC_GENERIC_REPLY_MALFORMED)); - GNUNET_assert (0 == - json_object_set_new (feedback, - uuid, - err)); - return; - } - if (is_async) - { - json_t *c = find_challenge_in_cs (sctx->state, - &cd->uuid); - - if (NULL == c) - { - GNUNET_break (0); - set_state (sctx->state, - ANASTASIS_RECOVERY_STATE_ERROR); - sctx->cb (sctx->cb_cls, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - sctx->state); - sctx_free (sctx); - return; - } - GNUNET_assert (0 == - json_object_set_new (c, - "async", - json_true ())); - GNUNET_assert (0 == - json_object_set_new (c, - "answer-pin", - json_integer (code))); - } - reply = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "external-instructions"), - GNUNET_JSON_pack_string ("method", - method), - GNUNET_JSON_pack_object_incref ("details", - details)); - GNUNET_JSON_parse_free (spec); + json_t *err; + char *hint; + + GNUNET_asprintf ( + &hint, + _ ("exceeded limit of %llu attempts in %s"), + (unsigned long long) csr->details.rate_limit_exceeded.request_limit, + GNUNET_TIME_relative2s ( + csr->details.rate_limit_exceeded.request_frequency, + true)); + err = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "state", + "rate-limit-exceeded"), + GNUNET_JSON_pack_string ( + "display_hint", + hint), + GNUNET_JSON_pack_uint64 ( + "request_limit", + csr->details.rate_limit_exceeded.request_limit), + GNUNET_JSON_pack_time_rel ( + "request_frequency", + csr->details.rate_limit_exceeded.request_frequency), + GNUNET_JSON_pack_uint64 ( + "error_code", + TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, - reply)); + err)); } - json_object_set_new (sctx->state, - "selected_challenge_uuid", - GNUNET_JSON_from_data_auto (&cd->uuid)); set_state (sctx->state, - ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - TALER_EC_NONE, + TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, sctx->state); sctx_free (sctx); return; @@ -873,7 +1059,8 @@ solve_challenge_cb (void *cls, struct GNUNET_JSON_Specification tspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &timeout)), + &timeout), + NULL), GNUNET_JSON_spec_end () }; struct GNUNET_JSON_Specification pspec[] = { @@ -1138,21 +1325,19 @@ solve_challenge_cb (void *cls, sctx_free (sctx); return; } - ret = ANASTASIS_challenge_start (ci, - psp, - timeout, - &hashed_answer, - &answer_feedback_cb, - sctx); + ret = ANASTASIS_challenge_answer3 (ci, + psp, + timeout, + &hashed_answer, + &answer_feedback_cb, + sctx); } else { /* no answer provided */ ret = ANASTASIS_challenge_start (ci, psp, - timeout, - NULL, /* no answer */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } } @@ -1272,9 +1457,7 @@ pay_challenge_cb (void *cls, { ret = ANASTASIS_challenge_start (ci, &sctx->ps, - sctx->timeout, - NULL, /* no answer yet */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } if (GNUNET_OK != ret) @@ -1468,7 +1651,8 @@ pay_challenge (json_t *state, struct GNUNET_JSON_Specification aspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &timeout)), + &timeout), + NULL), GNUNET_JSON_spec_fixed_auto ("payment_secret", &sctx->ps), GNUNET_JSON_spec_end () @@ -1566,7 +1750,8 @@ select_challenge_cb (void *cls, struct GNUNET_JSON_Specification tspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &timeout)), + &timeout), + NULL), GNUNET_JSON_spec_end () }; struct GNUNET_JSON_Specification pspec[] = { @@ -1684,10 +1869,13 @@ select_challenge_cb (void *cls, json_object_set_new (sctx->state, "selected_challenge_uuid", GNUNET_JSON_from_data_auto (&cd->uuid))); - if (0 == strcmp ("question", - cd->type)) + if ( (0 == strcmp ("question", + cd->type)) || + (0 == strcmp ("totp", + cd->type)) ) { - /* security question, immediately request user to answer it */ + /* security question or TOTP: + immediately request user to answer it */ set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); sctx->cb (sctx->cb_cls, @@ -1718,9 +1906,7 @@ select_challenge_cb (void *cls, { ret = ANASTASIS_challenge_start (ci, psp, - timeout, - NULL, /* no answer */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } } @@ -1856,256 +2042,15 @@ back_challenge_solving (json_t *state, /** - * The user wants us to change the policy version. Download another version. - * - * @param[in] state we are in - * @param arguments our arguments with the solution - * @param cb functiont o call with the new state - * @param cb_cls closure for @a cb - * @return handle to cancel challenge selection step - */ -static struct ANASTASIS_ReduxAction * -change_version (json_t *state, - const json_t *arguments, - ANASTASIS_ActionCallback cb, - void *cb_cls) -{ - uint64_t version; - const char *provider_url; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("version", - &version), - GNUNET_JSON_spec_string ("provider_url", - &provider_url), - GNUNET_JSON_spec_end () - }; - json_t *ia; - json_t *args; - struct ANASTASIS_ReduxAction *ra; - - if (GNUNET_OK != - GNUNET_JSON_parse (arguments, - spec, - NULL, NULL)) - { - GNUNET_break (0); - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "'version' invalid"); - return NULL; - } - GNUNET_assert (NULL != provider_url); - ia = json_object_get (state, - "identity_attributes"); - if (NULL == ia) - { - GNUNET_break (0); - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "'identity_attributes' missing"); - return NULL; - } - args = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("version", - version), - GNUNET_JSON_pack_object_incref ("identity_attributes", - (json_t *) ia), - GNUNET_JSON_pack_string ("provider_url", - provider_url)); - if (NULL == args) - { - GNUNET_break (0); - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - NULL); - return NULL; - } - ra = ANASTASIS_REDUX_recovery_challenge_begin_ (state, - args, - cb, - cb_cls); - json_decref (args); - return ra; -} - - -/** - * DispatchHandler/Callback function which is called for a - * "next" action in "secret_selecting" state. - * - * @param state state to operate on - * @param arguments arguments to use for operation on state - * @param cb callback to call during/after operation - * @param cb_cls callback closure - * @return NULL - */ -static struct ANASTASIS_ReduxAction * -done_secret_selecting (json_t *state, - const json_t *arguments, - ANASTASIS_ActionCallback cb, - void *cb_cls) -{ - const json_t *ri; - - ri = json_object_get (state, - "recovery_information"); - if ( (NULL == ri) || - (NULL == json_object_get (ri, - "challenges")) ) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE, - "no valid version selected"); - return NULL; - } - set_state (state, - ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); - cb (cb_cls, - TALER_EC_NONE, - state); - return NULL; -} - - -/** - * Signature of callback function that implements a state transition. - * - * @param state current state - * @param arguments arguments for the state transition - * @param cb function to call when done - * @param cb_cls closure for @a cb - */ -typedef struct ANASTASIS_ReduxAction * -(*DispatchHandler)(json_t *state, - const json_t *arguments, - ANASTASIS_ActionCallback cb, - void *cb_cls); - - -struct ANASTASIS_ReduxAction * -ANASTASIS_recovery_action_ (json_t *state, - const char *action, - const json_t *arguments, - ANASTASIS_ActionCallback cb, - void *cb_cls) -{ - struct Dispatcher - { - enum ANASTASIS_RecoveryState recovery_state; - const char *recovery_action; - DispatchHandler fun; - } dispatchers[] = { - { - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, - "change_version", - &change_version - }, - { - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, - "next", - &done_secret_selecting - }, - { - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, - "back", - &ANASTASIS_back_generic_decrement_ - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, - "select_challenge", - &select_challenge - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, - "poll", - &poll_challenges - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, - "back", - &ANASTASIS_back_generic_decrement_ - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, - "pay", - &pay_challenge - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, - "back", - &ANASTASIS_back_generic_decrement_ - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, - "solve_challenge", - &solve_challenge - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, - "back", - &back_challenge_solving - }, - { ANASTASIS_RECOVERY_STATE_ERROR, NULL, NULL } - }; - const char *s = json_string_value (json_object_get (state, - "recovery_state")); - enum ANASTASIS_RecoveryState rs; - - GNUNET_assert (NULL != s); - rs = ANASTASIS_recovery_state_from_string_ (s); - if (ANASTASIS_RECOVERY_STATE_ERROR == rs) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - "'recovery_state' field invalid"); - return NULL; - } - for (unsigned int i = 0; NULL != dispatchers[i].fun; i++) - { - if ( (rs == dispatchers[i].recovery_state) && - (0 == strcmp (action, - dispatchers[i].recovery_action)) ) - { - return dispatchers[i].fun (state, - arguments, - cb, - cb_cls); - } - } - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID, - action); - return NULL; -} - - -/** - * State for a "recover secret" CMD. - */ -struct RecoverSecretState; - - -/** * State for a "policy download" as part of a recovery operation. */ struct PolicyDownloadEntry { /** - * Kept in a DLL. - */ - struct PolicyDownloadEntry *prev; - - /** - * Kept in a DLL. + * Redux action handle associated with this state. */ - struct PolicyDownloadEntry *next; + struct ANASTASIS_ReduxAction ra; /** * Backend we are querying. @@ -2113,289 +2058,46 @@ struct PolicyDownloadEntry char *backend_url; /** - * Salt to be used to derive the id for this provider - */ - struct ANASTASIS_CRYPTO_ProviderSaltP salt; - - /** - * Context we operate in. - */ - struct RecoverSecretState *rss; - - /** * The /policy GET operation handle. */ struct ANASTASIS_Recovery *recovery; -}; - - -/** - * Entry in the list of all known applicable Anastasis providers. - * Used to wait for it to complete downloading /config. - */ -struct RecoveryStartStateProviderEntry -{ - /** - * Kept in a DLL. - */ - struct RecoveryStartStateProviderEntry *next; - - /** - * Kept in a DLL. - */ - struct RecoveryStartStateProviderEntry *prev; - - /** - * Main operation this entry is part of. - */ - struct RecoverSecretState *rss; - /** - * Resulting provider information, NULL if not (yet) available. - */ - json_t *istate; - - /** - * Ongoing reducer action to obtain /config, NULL if completed. - */ - struct ANASTASIS_ReduxAction *ra; - - /** - * Final result of the operation (once completed). - */ - enum TALER_ErrorCode ec; -}; - - -/** - * State for a "recover secret" CMD. - */ -struct RecoverSecretState -{ - - /** - * Redux action handle associated with this state. - */ - struct ANASTASIS_ReduxAction ra; - - /** - * Head of list of provider /config operations we are doing. - */ - struct RecoveryStartStateProviderEntry *pe_head; - - /** - * Tail of list of provider /config operations we are doing. - */ - struct RecoveryStartStateProviderEntry *pe_tail; - - /** - * Identification data from the user - */ - json_t *id_data; - - /** - * Head of DLL of policy downloads. - */ - struct PolicyDownloadEntry *pd_head; - - /** - * Tail of DLL of policy downloads. - */ - struct PolicyDownloadEntry *pd_tail; - - /** - * Reference to our state. - */ - json_t *state; - - /** - * callback to call during/after operation + * Function to call with the result. */ ANASTASIS_ActionCallback cb; /** - * closure for action callback @e cb. + * Closure for @e cb. */ void *cb_cls; /** - * Set if recovery must be done with this provider. - */ - char *provider_url; - - /** - * version of the recovery document to request. - */ - unsigned int version; - - /** - * Number of provider /config operations in @e ba_head that - * are still awaiting completion. + * State we are using. */ - unsigned int pending; + json_t *state; - /** - * Is @e version set? - */ - bool have_version; }; /** - * Function to free a `struct RecoverSecretState` + * Free @a cls data structure. * - * @param cls must be a `struct RecoverSecretState` + * @param[in] cls data structure to free, must be a `struct PolicyDownloadEntry *` */ static void -free_rss (void *cls) +free_pd (void *cls) { - struct RecoverSecretState *rss = cls; - struct PolicyDownloadEntry *pd; - struct RecoveryStartStateProviderEntry *pe; + struct PolicyDownloadEntry *pd = cls; - while (NULL != (pe = rss->pe_head)) - { - GNUNET_CONTAINER_DLL_remove (rss->pe_head, - rss->pe_tail, - pe); - ANASTASIS_redux_action_cancel (pe->ra); - rss->pending--; - GNUNET_free (pe); - } - while (NULL != (pd = rss->pd_head)) + if (NULL != pd->recovery) { - GNUNET_CONTAINER_DLL_remove (rss->pd_head, - rss->pd_tail, - pd); - if (NULL != pd->recovery) - { - ANASTASIS_recovery_abort (pd->recovery); - pd->recovery = NULL; - } - GNUNET_free (pd->backend_url); - GNUNET_free (pd); + ANASTASIS_recovery_abort (pd->recovery); + pd->recovery = NULL; } - json_decref (rss->state); - json_decref (rss->id_data); - GNUNET_assert (0 == rss->pending); - GNUNET_free (rss->provider_url); - GNUNET_free (rss); -} - - -/** - * This function is called whenever the recovery process ends. - * In this case, that should not be possible as this callback - * is used before we even begin with the challenges. So if - * we are called, it is because of some fatal error. - * - * @param cls a `struct PolicyDownloadEntry` - * @param rc error code - * @param secret contains the core secret which is passed to the user - * @param secret_size defines the size of the core secret - */ -static void -core_early_secret_cb (void *cls, - enum ANASTASIS_RecoveryStatus rc, - const void *secret, - size_t secret_size) -{ - struct PolicyDownloadEntry *pd = cls; - struct RecoverSecretState *rss = pd->rss; - enum TALER_ErrorCode ec; - - pd->recovery = NULL; - GNUNET_assert (NULL == secret); - GNUNET_CONTAINER_DLL_remove (rss->pd_head, - rss->pd_tail, - pd); GNUNET_free (pd->backend_url); + json_decref (pd->state); GNUNET_free (pd); - if (NULL != rss->pd_head) - return; /* wait for another one */ - /* all failed! report failure! */ - GNUNET_assert (ANASTASIS_RS_SUCCESS != rc); - ec = update_state_by_error (rss->state, - rc); - rss->cb (rss->cb_cls, - ec, - rss->state); - rss->cb = NULL; - free_rss (rss); -} - - -/** - * Determine recovery @a cost of solving a challenge of type @a type - * at @a provider_url by inspecting @a state. - * - * @param state the state to inspect - * @param provider_url the provider to lookup config info from - * @param type the method to lookup the cost of - * @param[out] cost the recovery cost to return - * @return #GNUNET_OK on success, #GNUNET_NO if not found, #GNUNET_SYSERR on state error - */ -static int -lookup_cost (const json_t *state, - const char *provider_url, - const char *type, - struct TALER_Amount *cost) -{ - const json_t *providers; - const json_t *provider; - const json_t *methods; - - providers = json_object_get (state, - "authentication_providers"); - if (NULL == providers) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - provider = json_object_get (providers, - provider_url); - if (NULL == provider) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - methods = json_object_get (provider, - "methods"); - if ( (NULL == methods) || - (! json_is_array (methods)) ) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - { - size_t index; - json_t *method; - - json_array_foreach (methods, index, method) { - const char *t; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("type", - &t), - TALER_JSON_spec_amount_any ("usage_fee", - cost), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (method, - spec, - NULL, NULL)) - { - GNUNET_break (0); - continue; - } - if (0 == strcmp (t, - type)) - return GNUNET_OK; - } - } - return GNUNET_NO; /* not found */ } @@ -2404,40 +2106,34 @@ lookup_cost (const json_t *state, * allow the user to specify alternative providers and/or policy * versions. * - * @param[in] rss state to fail with the policy download + * @param[in] pd state to fail with the policy download * @param offline true of the reason to show is that all providers * were offline / did not return a salt to us */ static void -return_no_policy (struct RecoverSecretState *rss, +return_no_policy (struct PolicyDownloadEntry *pd, bool offline) { - json_t *msg; + enum TALER_ErrorCode ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED; + const char *detail = (offline) + ? "could not contact provider (offline)" + : "provider does not know this policy"; + json_t *estate; GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No provider online, need user to manually specify providers!\n"); - msg = GNUNET_JSON_PACK ( + "Provider offline!\n"); + estate = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", + detail)), + GNUNET_JSON_pack_uint64 ("code", + ec), GNUNET_JSON_pack_string ("hint", - offline - ? "could not contact provider" - : "provider does not know you"), - GNUNET_JSON_pack_bool ("offline", - offline)); - GNUNET_assert (0 == - json_object_set_new (rss->state, - "recovery_error", - msg)); - /* In case there are old ones, remove them! */ - (void) json_object_del (rss->state, - "recovery_document"); - (void) json_object_del (rss->state, - "recovery_information"); - set_state (rss->state, - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING); - rss->cb (rss->cb_cls, - TALER_EC_NONE, - rss->state); - free_rss (rss); + TALER_ErrorCode_get_hint (ec))); + pd->cb (pd->cb_cls, + ec, + estate); + free_pd (pd); } @@ -2450,7 +2146,7 @@ return_no_policy (struct RecoverSecretState *rss, * cancel all of the others, passing the obtained recovery information * back to the user. * - * @param cls closure for the callback + * @param cls closure for the callback with a `struct PolicyDownloadEntry *` * @param ri recovery information struct which contains the policies */ static void @@ -2458,7 +2154,6 @@ policy_lookup_cb (void *cls, const struct ANASTASIS_RecoveryInformation *ri) { struct PolicyDownloadEntry *pd = cls; - struct RecoverSecretState *rss = pd->rss; json_t *policies; json_t *challenges; json_t *recovery_information; @@ -2466,16 +2161,10 @@ policy_lookup_cb (void *cls, if (NULL == ri) { /* Woopsie, failed hard. */ - GNUNET_CONTAINER_DLL_remove (rss->pd_head, - rss->pd_tail, - pd); ANASTASIS_recovery_abort (pd->recovery); GNUNET_free (pd->backend_url); GNUNET_free (pd); - if (NULL != rss->pd_head) - return; /* wait for another one */ - /* all failed! report failure! */ - return_no_policy (rss, + return_no_policy (pd, false); return; } @@ -2514,37 +2203,15 @@ policy_lookup_cb (void *cls, struct ANASTASIS_Challenge *c = ri->cs[i]; const struct ANASTASIS_ChallengeDetails *cd; json_t *cj; - struct TALER_Amount cost; - int ret; cd = ANASTASIS_challenge_get_details (c); - ret = lookup_cost (rss->state, - cd->provider_url, - cd->type, - &cost); - if (GNUNET_SYSERR == ret) - { - json_decref (challenges); - json_decref (policies); - set_state (rss->state, - ANASTASIS_RECOVERY_STATE_ERROR); - ANASTASIS_redux_fail_ (rss->cb, - rss->cb_cls, - TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - "failed to 'lookup_cost'"); - free_rss (rss); - return; - } - cj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("uuid", &cd->uuid), - TALER_JSON_pack_amount ("cost", - (GNUNET_NO == ret) - ? NULL - : &cost), GNUNET_JSON_pack_string ("type", cd->type), + GNUNET_JSON_pack_string ("uuid-display", + ANASTASIS_CRYPTO_uuid2s (&cd->uuid)), GNUNET_JSON_pack_string ("instructions", cd->instructions)); GNUNET_assert (0 == @@ -2564,7 +2231,7 @@ policy_lookup_cb (void *cls, GNUNET_JSON_pack_uint64 ("version", ri->version)); GNUNET_assert (0 == - json_object_set_new (rss->state, + json_object_set_new (pd->state, "recovery_information", recovery_information)); { @@ -2574,229 +2241,387 @@ policy_lookup_cb (void *cls, if (NULL == rd) { GNUNET_break (0); - set_state (rss->state, - ANASTASIS_RECOVERY_STATE_ERROR); - rss->cb (rss->cb_cls, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - rss->state); - free_rss (rss); + ANASTASIS_redux_fail_ (pd->cb, + pd->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "unable to serialize recovery state"); + free_pd (pd); return; } GNUNET_assert (0 == - json_object_set_new (rss->state, + json_object_set_new (pd->state, "recovery_document", rd)); } - /* In case there is an old error remove it! */ - (void) json_object_del (rss->state, - "recovery_error"); - set_state (rss->state, - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING); - rss->cb (rss->cb_cls, - TALER_EC_NONE, - rss->state); - free_rss (rss); + set_state (pd->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); + pd->cb (pd->cb_cls, + TALER_EC_NONE, + pd->state); + free_pd (pd); } /** - * Try to launch recovery at provider @a provider_url with config @a p_cfg. + * This function is called whenever the recovery process ends. + * In this case, that should not be possible as this callback + * is used before we even begin with the challenges. So if + * we are called, it is because of some fatal error. * - * @param[in,out] rss recovery context - * @param provider_url base URL of the provider to try - * @param p_cfg configuration of the provider - * @return true if a recovery was launched + * @param cls a `struct PolicyDownloadEntry` + * @param rc error code + * @param secret contains the core secret which is passed to the user + * @param secret_size defines the size of the core secret */ -static bool -launch_recovery (struct RecoverSecretState *rss, - const char *provider_url, - const json_t *p_cfg) +static void +core_early_secret_cb (void *cls, + enum ANASTASIS_RecoveryStatus rc, + const void *secret, + size_t secret_size) { - struct PolicyDownloadEntry *pd = GNUNET_new (struct PolicyDownloadEntry); - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("salt", - &pd->salt), - GNUNET_JSON_spec_end () - }; + struct PolicyDownloadEntry *pd = cls; - if (MHD_HTTP_OK != - json_integer_value (json_object_get (p_cfg, - "http_status"))) - return false; /* skip providers that are down */ - if (GNUNET_OK != - GNUNET_JSON_parse (p_cfg, - spec, - NULL, NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No salt for `%s', provider offline?\n", - provider_url); - GNUNET_free (pd); - return false; - } - pd->backend_url = GNUNET_strdup (provider_url); - pd->rss = rss; - pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_, - rss->id_data, - rss->have_version - ? rss->version - : 0, - pd->backend_url, - &pd->salt, - &policy_lookup_cb, - pd, - &core_early_secret_cb, - pd); - if (NULL != pd->recovery) - { - GNUNET_CONTAINER_DLL_insert (rss->pd_head, - rss->pd_tail, - pd); - return true; - } - GNUNET_free (pd->backend_url); - GNUNET_free (pd); - return false; + pd->recovery = NULL; + GNUNET_assert (NULL == secret); + GNUNET_assert (ANASTASIS_RS_SUCCESS != rc); + fail_by_error (pd->cb, + pd->cb_cls, + rc); + free_pd (pd); } /** - * We finished downloading /config from all providers, merge - * into the main state, trigger the continuation and free our - * state. + * DispatchHandler/Callback function which is called for a + * "next" action in "secret_selecting" state. * - * @param[in] rss main state to merge into + * @param state state to operate on + * @param arguments arguments to use for operation on state + * @param cb callback to call during/after operation + * @param cb_cls callback closure + * @return NULL */ -static void -providers_complete (struct RecoverSecretState *rss) +static struct ANASTASIS_ReduxAction * +done_secret_selecting (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) { - bool launched = false; - struct RecoveryStartStateProviderEntry *pe; - json_t *tlist; + uint32_t mask; + const json_t *pa; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("attribute_mask", + &mask), + GNUNET_JSON_spec_array_const ("providers", + &pa), + GNUNET_JSON_spec_end () + }; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + struct GNUNET_JSON_Specification pspec[] = { + GNUNET_JSON_spec_fixed_auto ("provider_salt", + &provider_salt), + GNUNET_JSON_spec_end () + }; + json_t *p_cfg; + json_t *id_data; + const json_t *providers; - tlist = json_object_get (rss->state, - "authentication_providers"); - if (NULL == tlist) + if (GNUNET_OK != + GNUNET_JSON_parse (arguments, + spec, + NULL, NULL)) { - tlist = json_object (); - GNUNET_assert (NULL != tlist); - GNUNET_assert (0 == - json_object_set_new (rss->state, - "authentication_providers", - tlist)); + GNUNET_break (0); + json_dumpf (arguments, + stderr, + JSON_INDENT (2)); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + NULL); + return NULL; } - while (NULL != (pe = rss->pe_head)) + providers = json_object_get (state, + "authentication_providers"); + if ( (NULL == providers) || + (! json_is_object (providers)) ) { - json_t *provider_list; - - GNUNET_CONTAINER_DLL_remove (rss->pe_head, - rss->pe_tail, - pe); - provider_list = json_object_get (pe->istate, - "authentication_providers"); - /* merge provider_list into tlist (overriding existing entries) */ - if (NULL != provider_list) - { - const char *url; - json_t *value; - - json_object_foreach (provider_list, url, value) { - GNUNET_assert (0 == - json_object_set (tlist, - url, - value)); - } - } - json_decref (pe->istate); - GNUNET_free (pe); + GNUNET_break (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'authentication_providers' missing"); + return NULL; } - /* now iterate over providers and begin downloading */ - if (NULL != rss->provider_url) { - json_t *p_cfg; - - p_cfg = json_object_get (tlist, - rss->provider_url); - if (NULL != p_cfg) - launched = launch_recovery (rss, - rss->provider_url, - p_cfg); - } - else - { - json_t *p_cfg; + size_t poff; + json_t *pe; + uint64_t version; const char *provider_url; - json_object_foreach (tlist, provider_url, p_cfg) + json_array_foreach (pa, poff, pe) { - launched |= launch_recovery (rss, - provider_url, - p_cfg); + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_uint64 ("version", + &version), + GNUNET_JSON_spec_string ("url", + &provider_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (pe, + ispec, + NULL, NULL)) + { + GNUNET_break (0); + json_dumpf (pe, + stderr, + JSON_INDENT (2)); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + NULL); + return NULL; + } + + p_cfg = json_object_get (providers, + provider_url); + if (MHD_HTTP_OK != + json_integer_value (json_object_get (p_cfg, + "http_status"))) + continue; + if (GNUNET_OK != + GNUNET_JSON_parse (p_cfg, + pspec, + NULL, NULL)) + { + GNUNET_break (0); /* should be impossible for well-formed state */ + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "Salt unknown for selected provider"); + return NULL; + } + id_data = json_object_get (state, + "identity_attributes"); + if (NULL == id_data) + { + GNUNET_break (0); /* should be impossible for well-formed state */ + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'identity_attributes' missing"); + return NULL; + } + { + struct PolicyDownloadEntry *pd + = GNUNET_new (struct PolicyDownloadEntry); + + pd->cb = cb; + pd->cb_cls = cb_cls; + pd->state = json_incref (state); + pd->backend_url = GNUNET_strdup (provider_url); + pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_, + id_data, + version, + pd->backend_url, + &provider_salt, + &policy_lookup_cb, + pd, + &core_early_secret_cb, + pd); + if (NULL == pd->recovery) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, + NULL); + GNUNET_free (pd->backend_url); + json_decref (pd->state); + GNUNET_free (pd); + return NULL; + } + pd->ra.cleanup = &free_pd; + pd->ra.cleanup_cls = pd; + return &pd->ra; + } } } - if (! launched) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No provider online, need user to specify different provider!\n"); - return_no_policy (rss, - true); - return; - } + + /* no provider worked */ + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + "selected provider is not online"); + return NULL; } /** - * Function called when the complete information about a provider - * was added to @a new_state. + * The user wants us to add another provider. Download /config. * - * @param cls a `struct RecoveryStartStateProviderEntry` - * @param error error code - * @param new_state resulting new state + * @param[in] state we are in + * @param arguments our arguments with the solution + * @param cb function to call with the new state + * @param cb_cls closure for @a cb + * @return handle to cancel challenge selection step */ -static void -provider_added_cb (void *cls, - enum TALER_ErrorCode error, - json_t *new_state) +static struct ANASTASIS_ReduxAction * +add_provider (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) { - struct RecoveryStartStateProviderEntry *pe = cls; - - pe->ra = NULL; - pe->istate = json_incref (new_state); - pe->ec = error; - pe->rss->pending--; - if (0 == pe->rss->pending) - providers_complete (pe->rss); + const char *provider_url; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("provider_url", + &provider_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (arguments, + spec, + NULL, NULL)) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + NULL); + return NULL; + } + return ANASTASIS_REDUX_add_provider_to_state_ (provider_url, + state, + cb, + cb_cls); } /** - * Start to query provider for recovery document. + * Signature of callback function that implements a state transition. * - * @param[in,out] rss overall recovery state - * @param provider_url base URL of the provider to query + * @param state current state + * @param arguments arguments for the state transition + * @param cb function to call when done + * @param cb_cls closure for @a cb */ -static void -begin_query_provider (struct RecoverSecretState *rss, - const char *provider_url) +typedef struct ANASTASIS_ReduxAction * +(*DispatchHandler)(json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls); + + +struct ANASTASIS_ReduxAction * +ANASTASIS_recovery_action_ (json_t *state, + const char *action, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) { - struct RecoveryStartStateProviderEntry *pe; - json_t *istate; - - pe = GNUNET_new (struct RecoveryStartStateProviderEntry); - pe->rss = rss; - istate = json_object (); - GNUNET_assert (NULL != istate); - GNUNET_CONTAINER_DLL_insert (rss->pe_head, - rss->pe_tail, - pe); - pe->ra = ANASTASIS_REDUX_add_provider_to_state_ (provider_url, - istate, - &provider_added_cb, - pe); - json_decref (istate); - if (NULL != pe->ra) - rss->pending++; + struct Dispatcher + { + enum ANASTASIS_RecoveryState recovery_state; + const char *recovery_action; + DispatchHandler fun; + } dispatchers[] = { + { + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, + "add_provider", + &add_provider + }, + { + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, + "poll_providers", + &ANASTASIS_REDUX_poll_providers_ + }, + { + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, + "select_version", + &done_secret_selecting + }, + { + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, + "back", + &ANASTASIS_back_generic_decrement_ + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, + "select_challenge", + &select_challenge + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, + "sync_providers", + &ANASTASIS_REDUX_sync_providers_ + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, + "poll", + &poll_challenges + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, + "back", + &ANASTASIS_back_generic_decrement_ + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, + "pay", + &pay_challenge + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, + "back", + &ANASTASIS_back_generic_decrement_ + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, + "solve_challenge", + &solve_challenge + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, + "back", + &back_challenge_solving + }, + { ANASTASIS_RECOVERY_STATE_INVALID, NULL, NULL } + }; + const char *s = json_string_value (json_object_get (state, + "recovery_state")); + enum ANASTASIS_RecoveryState rs; + + GNUNET_assert (NULL != s); + rs = ANASTASIS_recovery_state_from_string_ (s); + if (ANASTASIS_RECOVERY_STATE_INVALID == rs) + { + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'recovery_state' field invalid"); + return NULL; + } + for (unsigned int i = 0; NULL != dispatchers[i].fun; i++) + { + if ( (rs == dispatchers[i].recovery_state) && + (0 == strcmp (action, + dispatchers[i].recovery_action)) ) + { + return dispatchers[i].fun (state, + arguments, + cb, + cb_cls); + } + } + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID, + action); + return NULL; } @@ -2806,11 +2631,8 @@ ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state, ANASTASIS_ActionCallback cb, void *cb_cls) { - json_t *version; - json_t *providers; - const json_t *attributes; - struct RecoverSecretState *rss; - const char *provider_url; + const json_t *providers; + json_t *attributes; providers = json_object_get (state, "authentication_providers"); @@ -2836,46 +2658,13 @@ ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state, "'identity_attributes' missing"); return NULL; } - rss = GNUNET_new (struct RecoverSecretState); - rss->id_data = json_incref ((json_t *) attributes); - version = json_object_get (arguments, - "version"); - if (NULL != version) - { - rss->version = (unsigned int) json_integer_value (version); - rss->have_version = true; - } - rss->state = json_incref (state); - rss->cb = cb; - rss->cb_cls = cb_cls; - rss->pending = 1; /* decremented after initialization loop */ - - provider_url = json_string_value (json_object_get (arguments, - "provider_url")); - if (NULL != provider_url) - { - rss->provider_url = GNUNET_strdup (provider_url); - begin_query_provider (rss, - provider_url); - } - else - { - json_t *prov; - const char *url; - - json_object_foreach (providers, url, prov) { - begin_query_provider (rss, - url); - } - } - rss->pending--; - if (0 == rss->pending) - { - providers_complete (rss); - if (NULL == rss->cb) - return NULL; - } - rss->ra.cleanup = &free_rss; - rss->ra.cleanup_cls = rss; - return &rss->ra; + json_object_set (state, + "identity_attributes", + attributes); + set_state (state, + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING); + cb (cb_cls, + TALER_EC_NONE, + state); + return NULL; } diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c index f55eece..4b5ad7b 100644 --- a/src/reducer/anastasis_api_redux.c +++ b/src/reducer/anastasis_api_redux.c @@ -1,16 +1,16 @@ /* 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 Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -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; @@ -152,11 +173,6 @@ struct ConfigRequest char *business_name; /** - * currency used by the anastasis backend. - */ - char *currency; - - /** * Array of authorization methods supported by the server. */ struct AuthorizationMethodConfig *methods; @@ -188,9 +204,9 @@ struct ConfigRequest struct TALER_Amount liability_limit; /** - * Server salt. + * Provider salt. */ - struct ANASTASIS_CRYPTO_ProviderSaltP salt; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; /** * Task to timeout /config requests. @@ -234,6 +250,26 @@ static json_t *redux_countries; */ static json_t *provider_list; +/** + * External reducer binary or NULL + * to use internal reducer. + */ +static char *external_reducer_binary; + + +const char * +ANASTASIS_REDUX_probe_external_reducer (void) +{ + if (NULL != external_reducer_binary) + return external_reducer_binary; + external_reducer_binary = getenv ("ANASTASIS_EXTERNAL_REDUCER"); + if (NULL != external_reducer_binary) + unsetenv ("ANASTASIS_EXTERNAL_REDUCER"); + + return external_reducer_binary; + +} + /** * Extract the mode of a state from json @@ -262,7 +298,7 @@ ANASTASIS_generic_state_from_string_ (const char *state_string) if (0 == strcmp (state_string, generic_strings[i])) return i; - return ANASTASIS_GENERIC_STATE_ERROR; + return ANASTASIS_GENERIC_STATE_INVALID; } @@ -291,6 +327,8 @@ ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb, GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("detail", detail)), + GNUNET_JSON_pack_string ("reducer_type", + "error"), GNUNET_JSON_pack_uint64 ("code", ec), GNUNET_JSON_pack_string ("hint", @@ -344,7 +382,6 @@ free_config_request (struct ConfigRequest *cr) ANASTASIS_config_cancel (cr->co); if (NULL != cr->tt) GNUNET_SCHEDULER_cancel (cr->tt); - GNUNET_free (cr->currency); GNUNET_free (cr->url); GNUNET_free (cr->business_name); for (unsigned int i = 0; i<cr->methods_length; i++) @@ -471,13 +508,11 @@ notify_waiting (struct ConfigRequest *cr) "authentication_providers", provider_list = json_object ())); } - provider_list = json_object_get (w->state, - "authentication_providers"); - GNUNET_assert (NULL != provider_list); - if (TALER_EC_NONE != cr->ec) { prov = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("status", + "error"), GNUNET_JSON_pack_uint64 ("error_code", cr->ec), GNUNET_JSON_pack_uint64 ("http_status", @@ -503,6 +538,8 @@ notify_waiting (struct ConfigRequest *cr) mj)); } prov = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("status", + "ok"), GNUNET_JSON_pack_array_steal ("methods", methods_list), TALER_JSON_pack_amount ("annual_fee", @@ -511,14 +548,12 @@ notify_waiting (struct ConfigRequest *cr) &cr->truth_upload_fee), TALER_JSON_pack_amount ("liability_limit", &cr->liability_limit), - GNUNET_JSON_pack_string ("currency", - cr->currency), GNUNET_JSON_pack_string ("business_name", cr->business_name), GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes", cr->storage_limit_in_megabytes), - GNUNET_JSON_pack_data_auto ("salt", - &cr->salt), + GNUNET_JSON_pack_data_auto ("provider_salt", + &cr->provider_salt), GNUNET_JSON_pack_uint64 ("http_status", cr->http_status)); } @@ -531,68 +566,108 @@ 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) - cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED; - 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 == 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 == 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; } else { - GNUNET_free (cr->currency); - cr->currency = GNUNET_strdup (acfg->currency); + 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->salt = acfg->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); + } } @@ -607,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; @@ -632,17 +742,47 @@ 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, cr); } + 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, @@ -652,12 +792,6 @@ check_config (const char *url) GNUNET_break (0); return NULL; } - else - { - cr->tt = GNUNET_SCHEDULER_add_delayed (CONFIG_GENERIC_TIMEOUT, - &config_request_timeout, - cr); - } return cr; } @@ -665,12 +799,12 @@ check_config (const char *url) /** * Begin asynchronous check for provider configurations. * - * @param currencies the currencies to initiate the provider checks for + * @param cc country code that was selected * @param[in,out] state to set provider list for * @return #TALER_EC_NONE on success */ static enum TALER_ErrorCode -begin_provider_config_check (const json_t *currencies, +begin_provider_config_check (const char *cc, json_t *state) { if (NULL == provider_list) @@ -718,14 +852,17 @@ begin_provider_config_check (const json_t *currencies, json_array_foreach (provider_arr, index, provider) { const char *url; - const char *cur; + const char *restricted = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("url", &url), - GNUNET_JSON_spec_string ("currency", - &cur), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("restricted", + &restricted), + NULL), GNUNET_JSON_spec_end () }; + json_t *prov; if (GNUNET_OK != GNUNET_JSON_parse (provider, @@ -736,33 +873,35 @@ begin_provider_config_check (const json_t *currencies, json_decref (pl); return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED; } - + if ( (NULL != restricted) && + (0 != strcmp (restricted, + cc)) ) { - bool found = false; - json_t *cu; - size_t off; - - json_array_foreach (currencies, off, cu) - { - const char *currency; - - currency = json_string_value (cu); - if (NULL == currency) - { - json_decref (pl); - return TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID; - } - found = (0 == strcasecmp (currency, - cur)); - } - if (! found) - continue; + /* skip */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Skipping provider restricted to country `%s'\n", + restricted); + continue; + } + if ( (NULL == restricted) && + (0 == strcmp (cc, + "xx")) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Running in demo mode, skipping unrestricted providers\n"); + /* demo mode, skipping regular providers */ + continue; } + prov = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("status", + "not-contacted")); + GNUNET_assert (NULL != prov); GNUNET_assert (0 == json_object_set_new (pl, url, - json_object ())); - check_config (url); + prov)); + check_config (CONFIG_GENERIC_TIMEOUT, + url); } GNUNET_assert (0 == json_object_set_new (state, @@ -990,7 +1129,6 @@ select_country (json_t *state, { const json_t *required_attrs; const json_t *country_code; - const json_t *currencies; const json_t *redux_id_attr; if (NULL == arguments) @@ -1040,23 +1178,11 @@ select_country (json_t *state, } } - currencies = json_object_get (arguments, - "currencies"); - if ( (NULL == currencies) || - (! json_is_array (currencies)) ) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - "'currencies' missing"); - return NULL; - } - /* We now have an idea of the currency, begin fetching - provider /configs (we likely need them later) */ + /* Begin fetching provider /configs (we likely need them later) */ { enum TALER_ErrorCode ec; - ec = begin_provider_config_check (currencies, + ec = begin_provider_config_check (json_string_value (country_code), state); if (TALER_EC_NONE != ec) { @@ -1096,10 +1222,6 @@ select_country (json_t *state, (json_t *) country_code)); GNUNET_assert (0 == json_object_set (state, - "currencies", - (json_t *) currencies)); - GNUNET_assert (0 == - json_object_set (state, "required_attributes", (json_t *) required_attrs)); cb (cb_cls, @@ -1143,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); @@ -1156,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; } @@ -1228,19 +1353,22 @@ enter_user_attributes (json_t *state, const char *attribute_value; const char *regexp = NULL; const char *reglog = NULL; - int optional = false; + bool optional = false; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("name", &name), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("validation-regex", - ®exp)), + ®exp), + NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("validation-logic", - ®log)), + ®log), + NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_boolean ("optional", - &optional)), + GNUNET_JSON_spec_bool ("optional", + &optional), + NULL), GNUNET_JSON_spec_end () }; @@ -1378,9 +1506,16 @@ ANASTASIS_add_provider_ (json_t *state, ANASTASIS_ActionCallback cb, void *cb_cls) { - json_t *urls; json_t *tlist; + if (NULL == arguments) + { + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + "arguments missing"); + return true; /* cb was invoked */ + } tlist = json_object_get (state, "authentication_providers"); if (NULL == tlist) @@ -1392,47 +1527,19 @@ ANASTASIS_add_provider_ (json_t *state, "authentication_providers", tlist)); } - if (NULL == arguments) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "arguments missing"); - return true; - } - urls = json_object_get (arguments, - "urls"); - if (NULL == urls) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "'urls' missing"); - return true; - } { - size_t index; - json_t *url; + json_t *params; + const char *url; - json_array_foreach (urls, index, url) + json_object_foreach (((json_t *) arguments), url, params) { - const char *url_str = json_string_value (url); - - if (NULL == url_str) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "'urls' must be strings"); - return true; - } GNUNET_assert (0 == - json_object_set_new (tlist, - url_str, - json_object ())); + json_object_set (tlist, + url, + params)); } } - return false; + return false; /* cb not invoked */ } @@ -1502,6 +1609,256 @@ typedef struct ANASTASIS_ReduxAction * void *cb_cls); +/** + * Closure for read operations on the external reducer. + */ +struct ExternalReducerCls +{ + struct GNUNET_Buffer read_buffer; + struct GNUNET_SCHEDULER_Task *read_task; + struct GNUNET_DISK_PipeHandle *reducer_stdin; + struct GNUNET_DISK_PipeHandle *reducer_stdout; + struct GNUNET_OS_Process *reducer_process; + ANASTASIS_ActionCallback action_cb; + void *action_cb_cls; +}; + +/** + * Clean up and destroy the external reducer state. + * + * @param cls closure, a 'struct ExternalReducerCls *' + */ +static void +cleanup_external_reducer (void *cls) +{ + struct ExternalReducerCls *red_cls = cls; + + if (NULL != red_cls->read_task) + { + GNUNET_SCHEDULER_cancel (red_cls->read_task); + red_cls->read_task = NULL; + } + + GNUNET_buffer_clear (&red_cls->read_buffer); + if (NULL != red_cls->reducer_stdin) + { + GNUNET_DISK_pipe_close (red_cls->reducer_stdin); + red_cls->reducer_stdin = NULL; + } + if (NULL != red_cls->reducer_stdout) + { + GNUNET_DISK_pipe_close (red_cls->reducer_stdout); + red_cls->reducer_stdout = NULL; + } + + if (NULL != red_cls->reducer_process) + { + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + enum GNUNET_GenericReturnValue pwret; + + pwret = GNUNET_OS_process_wait_status (red_cls->reducer_process, + &type, + &code); + + GNUNET_assert (GNUNET_SYSERR != pwret); + if (GNUNET_NO == pwret) + { + GNUNET_OS_process_kill (red_cls->reducer_process, + SIGTERM); + GNUNET_assert (GNUNET_SYSERR != GNUNET_OS_process_wait ( + red_cls->reducer_process)); + } + + GNUNET_OS_process_destroy (red_cls->reducer_process); + red_cls->reducer_process = NULL; + } + + GNUNET_free (red_cls); +} + + +/** + * Task called when + * + * @param cls closure, a 'struct ExternalReducerCls *' + */ +static void +external_reducer_read_cb (void *cls) +{ + struct ExternalReducerCls *red_cls = cls; + ssize_t sret; + char buf[256]; + + red_cls->read_task = NULL; + + sret = GNUNET_DISK_file_read (GNUNET_DISK_pipe_handle ( + red_cls->reducer_stdout, + GNUNET_DISK_PIPE_END_READ), + buf, + 256); + if (sret < 0) + { + GNUNET_break (0); + red_cls->action_cb (red_cls->action_cb_cls, + TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, + NULL); + cleanup_external_reducer (red_cls); + return; + } + else if (0 == sret) + { + char *str = GNUNET_buffer_reap_str (&red_cls->read_buffer); + json_t *json; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got external reducer response: '%s'\n", + str); + + json = json_loads (str, 0, NULL); + + if (NULL == json) + { + GNUNET_break (0); + red_cls->action_cb (red_cls->action_cb_cls, + TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, + NULL); + cleanup_external_reducer (red_cls); + return; + } + + { + enum TALER_ErrorCode ec; + ec = json_integer_value (json_object_get (json, "code")); + + red_cls->action_cb (red_cls->action_cb_cls, + ec, + json); + } + cleanup_external_reducer (red_cls); + return; + } + else + { + GNUNET_buffer_write (&red_cls->read_buffer, + buf, + sret); + + red_cls->read_task = GNUNET_SCHEDULER_add_read_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle ( + red_cls->reducer_stdout, + GNUNET_DISK_PIPE_END_READ), + external_reducer_read_cb, + red_cls); + } +} + + +/** + * Handle an action using an external reducer, i.e. + * by shelling out to another process. + */ +static struct ANASTASIS_ReduxAction * +redux_action_external (const char *ext_reducer, + const json_t *state, + const char *action, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) +{ + char *arg_str; + char *state_str = json_dumps (state, JSON_COMPACT); + ssize_t sret; + struct ExternalReducerCls *red_cls = GNUNET_new (struct ExternalReducerCls); + + if (NULL == arguments) + arg_str = GNUNET_strdup ("{}"); + else + arg_str = json_dumps (arguments, JSON_COMPACT); + + red_cls->action_cb = cb; + red_cls->action_cb_cls = cb_cls; + + GNUNET_assert (NULL != (red_cls->reducer_stdin = GNUNET_DISK_pipe ( + GNUNET_DISK_PF_NONE))); + GNUNET_assert (NULL != (red_cls->reducer_stdout = GNUNET_DISK_pipe ( + GNUNET_DISK_PF_NONE))); + + /* By the time we're here, this variable should be unset, because + otherwise using anastasis-reducer as the external reducer + will lead to infinite recursion. */ + GNUNET_assert (NULL == getenv ("ANASTASIS_EXTERNAL_REDUCER")); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting external reducer with action '%s' and argument '%s'\n", + action, + arg_str); + + red_cls->reducer_process = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, + red_cls->reducer_stdin, + red_cls->reducer_stdout, + NULL, + ext_reducer, + ext_reducer, + "-a", + arg_str, + action, + NULL); + + GNUNET_free (arg_str); + + if (NULL == red_cls->reducer_process) + { + GNUNET_break (0); + GNUNET_free (state_str); + cleanup_external_reducer (red_cls); + return NULL; + } + + /* Close pipe ends we don't use. */ + GNUNET_assert (GNUNET_OK == + GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin, + GNUNET_DISK_PIPE_END_READ)); + GNUNET_assert (GNUNET_OK == + GNUNET_DISK_pipe_close_end (red_cls->reducer_stdout, + GNUNET_DISK_PIPE_END_WRITE)); + + sret = GNUNET_DISK_file_write_blocking (GNUNET_DISK_pipe_handle ( + red_cls->reducer_stdin, + GNUNET_DISK_PIPE_END_WRITE), + state_str, + strlen (state_str)); + GNUNET_free (state_str); + if (sret <= 0) + { + GNUNET_break (0); + cleanup_external_reducer (red_cls); + return NULL; + } + + GNUNET_assert (GNUNET_OK == + GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin, + GNUNET_DISK_PIPE_END_WRITE)); + + red_cls->read_task = GNUNET_SCHEDULER_add_read_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle ( + red_cls->reducer_stdout, + GNUNET_DISK_PIPE_END_READ), + external_reducer_read_cb, + red_cls); + + { + struct ANASTASIS_ReduxAction *ra = GNUNET_new (struct + ANASTASIS_ReduxAction); + ra->cleanup_cls = red_cls; + ra->cleanup = cleanup_external_reducer; + return ra; + } +} + + struct ANASTASIS_ReduxAction * ANASTASIS_redux_action (const json_t *state, const char *action, @@ -1520,6 +1877,7 @@ ANASTASIS_redux_action (const json_t *state, "select_continent", &select_continent }, + /* Deprecated alias for "back" from that state, should be removed eventually. */ { ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, "unselect_continent", @@ -1527,6 +1885,11 @@ ANASTASIS_redux_action (const json_t *state, }, { ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, + "back", + &unselect_continent + }, + { + ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, "select_country", &select_country }, @@ -1536,11 +1899,6 @@ ANASTASIS_redux_action (const json_t *state, &select_continent }, { - ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, - "unselect_continent", - &unselect_continent - }, - { ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING, "enter_user_attributes", &enter_user_attributes @@ -1555,13 +1913,25 @@ ANASTASIS_redux_action (const json_t *state, "back", &ANASTASIS_back_generic_decrement_ }, - { ANASTASIS_GENERIC_STATE_ERROR, NULL, NULL } + { ANASTASIS_GENERIC_STATE_INVALID, NULL, NULL } }; bool recovery_mode = false; const char *s = json_string_value (json_object_get (state, "backup_state")); enum ANASTASIS_GenericState gs; + /* If requested, handle action with external reducer, used for testing. */ + { + const char *ext_reducer = ANASTASIS_REDUX_probe_external_reducer (); + if (NULL != ext_reducer) + return redux_action_external (ext_reducer, + state, + action, + arguments, + cb, + cb_cls); + } + if (NULL == s) { s = json_string_value (json_object_get (state, @@ -1583,7 +1953,7 @@ ANASTASIS_redux_action (const json_t *state, new_state = json_deep_copy (state); GNUNET_assert (NULL != new_state); - if (gs != ANASTASIS_GENERIC_STATE_ERROR) + if (gs != ANASTASIS_GENERIC_STATE_INVALID) { for (unsigned int i = 0; NULL != dispatchers[i].fun; i++) { @@ -1737,3 +2107,68 @@ ANASTASIS_REDUX_load_continents_ () GNUNET_JSON_pack_array_steal ("continents", continents)); } + + +/** + * Lookup @a provider_salt of @a provider_url in @a state. + * + * @param state the state to inspect + * @param provider_url provider to look into + * @param[out] provider_salt value to extract + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +ANASTASIS_reducer_lookup_salt ( + const json_t *state, + const char *provider_url, + struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt) +{ + const json_t *aps; + const json_t *cfg; + uint32_t http_status = 0; + const char *status; + bool no_salt; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("provider_salt", + provider_salt), + &no_salt), + GNUNET_JSON_spec_end () + }; + + aps = json_object_get (state, + "authentication_providers"); + if (NULL == aps) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + cfg = json_object_get (aps, + provider_url); + if (NULL == cfg) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_JSON_parse (cfg, + spec, + NULL, NULL)) + { + /* provider not working */ + GNUNET_break_op (0); + return GNUNET_NO; + } + if (0 == strcmp (status, + "disabled")) + return GNUNET_NO; + if (no_salt) + return GNUNET_NO; + return GNUNET_OK; +} diff --git a/src/reducer/anastasis_api_redux.h b/src/reducer/anastasis_api_redux.h index 4d62d5e..03eef33 100644 --- a/src/reducer/anastasis_api_redux.h +++ b/src/reducer/anastasis_api_redux.h @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -25,7 +25,7 @@ #define ANASTASIS_GENERIC_STATES(REDUX_STATE) \ - REDUX_STATE (ERROR) \ + REDUX_STATE (INVALID) \ REDUX_STATE (CONTINENT_SELECTING) \ REDUX_STATE (COUNTRY_SELECTING) \ REDUX_STATE (USER_ATTRIBUTES_COLLECTING) @@ -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 @@ -168,6 +204,20 @@ ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs); /** + * Lookup @a salt of @a provider_url in @a state. + * + * @param state the state to inspect + * @param provider_url provider to look into + * @param[out] salt value to extract + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +ANASTASIS_reducer_lookup_salt (const json_t *state, + const char *provider_url, + struct ANASTASIS_CRYPTO_ProviderSaltP *salt); + + +/** * Function to return a json error response. * * @param cb callback to give error to @@ -328,6 +378,16 @@ ANASTASIS_backup_action_ (json_t *state, /** + * Check if an external reducer binary is requested. + * Cache the result and unset the corresponding environment + * variable. + * + * @returns name of the external reducer or NULL to user internal reducer + */ +const char * +ANASTASIS_REDUX_probe_external_reducer (void); + +/** * Generic container for an action with asynchronous activities. */ struct ANASTASIS_ReduxAction diff --git a/src/reducer/validation_CH_AHV.c b/src/reducer/validation_CH_AHV.c index 6beded1..4ea973c 100644 --- a/src/reducer/validation_CH_AHV.c +++ b/src/reducer/validation_CH_AHV.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_CZ_BN.c b/src/reducer/validation_CZ_BN.c index 85dea4a..b570841 100644 --- a/src/reducer/validation_CZ_BN.c +++ b/src/reducer/validation_CZ_BN.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_DE_SVN.c b/src/reducer/validation_DE_SVN.c index 7096708..e753f0c 100644 --- a/src/reducer/validation_DE_SVN.c +++ b/src/reducer/validation_DE_SVN.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_DE_TIN.c b/src/reducer/validation_DE_TIN.c index 0412f87..5678579 100644 --- a/src/reducer/validation_DE_TIN.c +++ b/src/reducer/validation_DE_TIN.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_ES_DNI.c b/src/reducer/validation_ES_DNI.c new file mode 100644 index 0000000..5fb3885 --- /dev/null +++ b/src/reducer/validation_ES_DNI.c @@ -0,0 +1,184 @@ +/* + This file is part of Anastasis + Copyright (C) 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation_ES_DNI.c + * @brief validation logic for Spanish Documento Nacional de Identidad numbers, and Número de Identificación de Extranjeros + * @author Christian Grothoff + * + * Examples: + * 12345678Z, 39740191D, 14741806W, X8095495R + */ +#include <string.h> +#include <stdbool.h> +#include <stdio.h> + +/** + * Function to validate a Spanish CIF number. + * + * @param civ number to validate + * @return true if validation passed, else false + */ +static bool +validate_cif (const char *cif) +{ + size_t slen = strlen (cif); + char letter = cif[0]; + const char *number = &cif[1]; + char control = cif[slen - 1]; + unsigned int sum = 0; + + if (9 != slen) + return false; + + for (unsigned int i = 0; i < slen - 2; i++) + { + unsigned int n = number[i] - '0'; + + if (n >= 10) + return false; + if (0 == (i % 2)) + { + n *= 2; + sum += n < 10 ? n : n - 9; + } + else + { + sum += n; + } + } + sum %= 10; + if (0 != sum) + sum = 10 - sum; + { + char control_digit = "0123456789"[sum]; + char control_letter = "JABCDEFGHI"[sum]; + + switch (letter) + { + case 'A': + case 'B': + case 'E': + case 'H': + return control == control_digit; + case 'N': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'W': + return control == control_letter; + default: + return (control == control_letter) || + (control == control_digit); + } + } +} + + +/** + * Function to validate a Spanish DNI number. + * + * See https://www.ordenacionjuego.es/en/calculo-digito-control + * + * @param dni_number number to validate (input) + * @return true if validation passed, else false + */ +bool +ES_DNI_check (const char *dni_number) +{ + const char map[] = "TRWAGMYFPDXBNJZSQVHLCKE"; + unsigned int num; + char chksum; + unsigned int fact; + char dummy; + + if (strlen (dni_number) < 8) + return false; + switch (dni_number[0]) + { + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + /* CIF: [A-W]\d{7}[0-9A-J] */ + /* CIF is for companies, we only take those + of individuals here! */ + return false; /* CIV is not allowed! */ + case 'M': + /* special NIE, with CIF validation (?), + but for individuals, see + https://www.strongabogados.com/tax-id-spain.php */ + return validate_cif (dni_number); + case 'X': + case 'Y': + case 'Z': + /* NIE */ + fact = dni_number[0] - 'X'; + /* 7 or 8 digits */ + if (2 == sscanf (&dni_number[1], + "%8u%c%c", + &num, + &chksum, + &dummy)) + { + num += fact * 100000000; + } + else if (2 == sscanf (&dni_number[1], + "%7u%c%c", + &num, + &chksum, + &dummy)) + { + num += fact * 10000000; + } + else + { + return false; + } + break; + default: + fact = 0; + /* DNI */ + if (2 != sscanf (dni_number, + "%8u%c%c", + &num, + &chksum, + &dummy)) + return false; + break; + } + if (map[num % 23] != chksum) + return false; + return true; +} diff --git a/src/reducer/validation_FR_INSEE.c b/src/reducer/validation_FR_INSEE.c new file mode 100644 index 0000000..19d81fd --- /dev/null +++ b/src/reducer/validation_FR_INSEE.c @@ -0,0 +1,66 @@ +/* + This file is part of Anastasis + Copyright (C) 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/validation_FR_INSEE.c + * @brief Validation for French INSEE Numbers + * @author Christian Grothoff + */ +#include <string.h> +#include <stdbool.h> +#include <stdio.h> + + +/** + * Function to validate a French INSEE number. + * + * See https://en.wikipedia.org/wiki/INSEE_code + * + * Note that we do not implement checks on the month + * and also allow non-binary prefixes. + * + * @param insee_number social security number to validate (input) + * @return true if validation passed, else false + */ +bool +FR_INSEE_check (const char *insee_number) +{ + char pfx[14]; + unsigned long long num; + unsigned int cc; + char dum; + + if (strlen (insee_number) != 15) + return false; + memcpy (pfx, + insee_number, + 13); + pfx[13] = '\0'; + if (1 != + sscanf (pfx, + "%llu%c", + &num, + &dum)) + return false; + if (1 != + sscanf (&insee_number[13], + "%u%c", + &cc, + &dum)) + return false; + if (97 - cc != num % 97) + return false; + return true; +} diff --git a/src/reducer/validation_IN_AADHAR.c b/src/reducer/validation_IN_AADHAR.c index 4c4901a..d53b655 100644 --- a/src/reducer/validation_IN_AADHAR.c +++ b/src/reducer/validation_IN_AADHAR.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_IT_CF.c b/src/reducer/validation_IT_CF.c index ca66a11..6e9c6c6 100644 --- a/src/reducer/validation_IT_CF.c +++ b/src/reducer/validation_IT_CF.c @@ -3,14 +3,14 @@ Copyright (C) 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_NL_BSN.c b/src/reducer/validation_NL_BSN.c new file mode 100644 index 0000000..f92bb38 --- /dev/null +++ b/src/reducer/validation_NL_BSN.c @@ -0,0 +1,57 @@ +/* + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation_NL_BSN.c + * @brief Validation for Dutch Buergerservicenummern + * @author Christian Grothoff + */ +#include <string.h> +#include <stdbool.h> + + +/** + * Function to validate a Dutch Social Security number. + * + * See https://nl.wikipedia.org/wiki/Burgerservicenummer + * + * @param bsn_number social security number to validate (input) + * @return true if validation passed, else false + */ +bool +NL_BSN_check (const char *bsn_number) +{ + static const int factors[] = { + 9, 8, 7, 6, 5, 4, 3, 2, -1 + }; + unsigned int sum = 0; + + if (strlen (bsn_number) != 9) + return false; + for (unsigned int i = 0; i<8; i++) + { + unsigned char c = (unsigned char) bsn_number[i]; + + if ( ('0' > c) || ('9' < c) ) + return false; + sum += (c - '0') * factors[i]; + } + { + unsigned char c = (unsigned char) bsn_number[8]; + unsigned int v = (c - '0'); + + return (sum % 11 == v); + } +} diff --git a/src/reducer/validation_XX_SQUARE.c b/src/reducer/validation_XX_SQUARE.c index 88cf890..1f43400 100644 --- a/src/reducer/validation_XX_SQUARE.c +++ b/src/reducer/validation_XX_SQUARE.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_XY_PRIME.c b/src/reducer/validation_XY_PRIME.c index 32bdce0..56aa724 100644 --- a/src/reducer/validation_XY_PRIME.c +++ b/src/reducer/validation_XY_PRIME.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** |