anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

anastasis_api_redux.c (60042B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020, 2021, 2022 Anastasis SARL
      4 
      5   Anastasis is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file reducer/anastasis_api_redux.c
     18  * @brief anastasis reducer api
     19  * @author Christian Grothoff
     20  * @author Dominik Meister
     21  * @author Dennis Neufeld
     22  */
     23 #include <platform.h>
     24 #include <jansson.h>
     25 #include "anastasis_redux.h"
     26 #include "anastasis_error_codes.h"
     27 #include <taler/taler_json_lib.h>
     28 #include "anastasis_api_redux.h"
     29 #include <dlfcn.h>
     30 
     31 
     32 /**
     33  * How long do we wait at most for a /config reply from an Anastasis provider.
     34  * 60s is very generous, given the tiny bandwidth required, even for the most
     35  * remote locations.
     36  */
     37 #define CONFIG_GENERIC_TIMEOUT GNUNET_TIME_UNIT_MINUTES
     38 
     39 /**
     40  * How long do we wait in a more "synchronous"
     41  * scenaro for a /config reply from an Anastasis provider.
     42  */
     43 #define CONFIG_FAST_TIMEOUT GNUNET_TIME_UNIT_SECONDS
     44 
     45 
     46 #define GENERATE_STRING(STRING) #STRING,
     47 static const char *generic_strings[] = {
     48   ANASTASIS_GENERIC_STATES (GENERATE_STRING)
     49 };
     50 #undef GENERATE_STRING
     51 
     52 
     53 /**
     54  * #ANASTASIS_REDUX_add_provider_to_state_ waiting for the
     55  * configuration request to complete or fail.
     56  */
     57 struct ConfigReduxWaiting
     58 {
     59   /**
     60    * Kept in a DLL.
     61    */
     62   struct ConfigReduxWaiting *prev;
     63 
     64   /**
     65    * Kept in a DLL.
     66    */
     67   struct ConfigReduxWaiting *next;
     68 
     69   /**
     70    * Associated redux action.
     71    */
     72   struct ANASTASIS_ReduxAction ra;
     73 
     74   /**
     75    * Config request we are waiting for.
     76    */
     77   struct ConfigRequest *cr;
     78 
     79   /**
     80    * State we are processing.
     81    */
     82   json_t *state;
     83 
     84   /**
     85    * Function to call with updated @e state.
     86    */
     87   ANASTASIS_ActionCallback cb;
     88 
     89   /**
     90    * Closure for @e cb.
     91    */
     92   void *cb_cls;
     93 
     94 };
     95 
     96 
     97 /**
     98  * Anastasis authorization method configuration
     99  */
    100 struct AuthorizationMethodConfig
    101 {
    102   /**
    103    * Type of the method, i.e. "question".
    104    */
    105   char *type;
    106 
    107   /**
    108    * Fee charged for accessing key share using this method.
    109    */
    110   struct TALER_Amount usage_fee;
    111 };
    112 
    113 
    114 /**
    115  * State for a "get config" operation.
    116  */
    117 struct ConfigRequest
    118 {
    119 
    120   /**
    121    * Kept in a DLL, given that we may have multiple backends.
    122    */
    123   struct ConfigRequest *next;
    124 
    125   /**
    126    * Kept in a DLL, given that we may have multiple backends.
    127    */
    128   struct ConfigRequest *prev;
    129 
    130   /**
    131    * Head of DLL of REDUX operations waiting for an answer.
    132    */
    133   struct ConfigReduxWaiting *w_head;
    134 
    135   /**
    136    * Tail of DLL of REDUX operations waiting for an answer.
    137    */
    138   struct ConfigReduxWaiting *w_tail;
    139 
    140   /**
    141    * When did we start?
    142    */
    143   struct GNUNET_TIME_Absolute start_time;
    144 
    145   /**
    146    * When do we time out?
    147    */
    148   struct GNUNET_TIME_Absolute timeout_at;
    149 
    150   /**
    151    * How long do we wait before trying again?
    152    */
    153   struct GNUNET_TIME_Relative backoff;
    154 
    155   /**
    156    * Obtained status code.
    157    */
    158   unsigned int http_status;
    159 
    160   /**
    161    * The /config GET operation handle.
    162    */
    163   struct ANASTASIS_ConfigOperation *co;
    164 
    165   /**
    166    * URL of the anastasis backend.
    167    */
    168   char *url;
    169 
    170   /**
    171    * Business name of the anastasis backend.
    172    */
    173   char *business_name;
    174 
    175   /**
    176    * Array of authorization methods supported by the server.
    177    */
    178   struct AuthorizationMethodConfig *methods;
    179 
    180   /**
    181    * Length of the @e methods array.
    182    */
    183   unsigned int methods_length;
    184 
    185   /**
    186    * Maximum size of an upload in megabytes.
    187    */
    188   uint32_t storage_limit_in_megabytes;
    189 
    190   /**
    191    * Annual fee for an account / policy upload.
    192    */
    193   struct TALER_Amount annual_fee;
    194 
    195   /**
    196    * Fee for a truth upload.
    197    */
    198   struct TALER_Amount truth_upload_fee;
    199 
    200   /**
    201    * Maximum legal liability for data loss covered by the
    202    * provider.
    203    */
    204   struct TALER_Amount liability_limit;
    205 
    206   /**
    207    * Provider salt.
    208    */
    209   struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
    210 
    211   /**
    212    * Task to timeout /config requests.
    213    */
    214   struct GNUNET_SCHEDULER_Task *tt;
    215 
    216   /**
    217    * Status of the /config request.
    218    */
    219   enum TALER_ErrorCode ec;
    220 };
    221 
    222 
    223 /**
    224  * Reducer API's CURL context handle.
    225  */
    226 struct GNUNET_CURL_Context *ANASTASIS_REDUX_ctx_;
    227 
    228 /**
    229  * JSON containing country specific identity attributes to ask the user for.
    230  */
    231 static json_t *redux_id_attr;
    232 
    233 /**
    234  * Head of DLL of Anastasis backend configuration requests.
    235  */
    236 static struct ConfigRequest *cr_head;
    237 
    238 /**
    239  * Tail of DLL of Anastasis backend configuration requests.
    240  */
    241 static struct ConfigRequest *cr_tail;
    242 
    243 /**
    244  * JSON containing country specific information.
    245  */
    246 static json_t *redux_countries;
    247 
    248 /**
    249  * List of Anastasis providers.
    250  */
    251 static json_t *provider_list;
    252 
    253 /**
    254  * External reducer binary or NULL
    255  * to use internal reducer.
    256  */
    257 static char *external_reducer_binary;
    258 
    259 
    260 const char *
    261 ANASTASIS_REDUX_probe_external_reducer (void)
    262 {
    263   if (NULL != external_reducer_binary)
    264     return external_reducer_binary;
    265   external_reducer_binary = getenv ("ANASTASIS_EXTERNAL_REDUCER");
    266   if (NULL != external_reducer_binary)
    267     unsetenv ("ANASTASIS_EXTERNAL_REDUCER");
    268 
    269   return external_reducer_binary;
    270 
    271 }
    272 
    273 
    274 /**
    275  * Extract the mode of a state from json
    276  *
    277  * @param state the state to operate on
    278  * @return "backup_state" or "recovery_state"
    279  */
    280 static const char *
    281 get_state_mode (const json_t *state)
    282 {
    283   if (json_object_get (state, "backup_state"))
    284     return "backup_state";
    285   if (json_object_get (state, "recovery_state"))
    286     return "recovery_state";
    287   GNUNET_assert (0);
    288   return NULL;
    289 }
    290 
    291 
    292 enum ANASTASIS_GenericState
    293 ANASTASIS_generic_state_from_string_ (const char *state_string)
    294 {
    295   for (enum ANASTASIS_GenericState i = 0;
    296        i < sizeof (generic_strings) / sizeof(*generic_strings);
    297        i++)
    298     if (0 == strcmp (state_string,
    299                      generic_strings[i]))
    300       return i;
    301   return ANASTASIS_GENERIC_STATE_INVALID;
    302 }
    303 
    304 
    305 const char *
    306 ANASTASIS_generic_state_to_string_ (enum ANASTASIS_GenericState gs)
    307 {
    308   if ( (gs < 0) ||
    309        (gs >= sizeof (generic_strings) / sizeof(*generic_strings)) )
    310   {
    311     GNUNET_break_op (0);
    312     return NULL;
    313   }
    314   return generic_strings[gs];
    315 }
    316 
    317 
    318 void
    319 ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb,
    320                        void *cb_cls,
    321                        enum TALER_ErrorCode ec,
    322                        const char *detail)
    323 {
    324   json_t *estate;
    325 
    326   estate = GNUNET_JSON_PACK (
    327     GNUNET_JSON_pack_allow_null (
    328       GNUNET_JSON_pack_string ("detail",
    329                                detail)),
    330     GNUNET_JSON_pack_string ("reducer_type",
    331                              "error"),
    332     GNUNET_JSON_pack_uint64 ("code",
    333                              ec),
    334     GNUNET_JSON_pack_string ("hint",
    335                              TALER_ErrorCode_get_hint (ec)));
    336   cb (cb_cls,
    337       ec,
    338       estate);
    339   json_decref (estate);
    340 }
    341 
    342 
    343 /**
    344  * Transition the @a state to @a gs.
    345  *
    346  * @param[in,out] state to transition
    347  * @param gs state to transition to
    348  */
    349 static void
    350 redux_transition (json_t *state,
    351                   enum ANASTASIS_GenericState gs)
    352 {
    353   const char *s_mode = get_state_mode (state);
    354 
    355   GNUNET_assert (0 ==
    356                  json_object_set_new (
    357                    state,
    358                    s_mode,
    359                    json_string (
    360                      ANASTASIS_generic_state_to_string_ (gs))));
    361 
    362 }
    363 
    364 
    365 void
    366 ANASTASIS_redux_init (struct GNUNET_CURL_Context *ctx)
    367 {
    368   ANASTASIS_REDUX_ctx_ = ctx;
    369 }
    370 
    371 
    372 /**
    373  * Function to free a `struct ConfigRequest`, an async operation.
    374  *
    375  * @param cr state for a "get config" operation
    376  */
    377 static void
    378 free_config_request (struct ConfigRequest *cr)
    379 {
    380   GNUNET_assert (NULL == cr->w_head);
    381   if (NULL != cr->co)
    382     ANASTASIS_config_cancel (cr->co);
    383   if (NULL != cr->tt)
    384     GNUNET_SCHEDULER_cancel (cr->tt);
    385   GNUNET_free (cr->url);
    386   GNUNET_free (cr->business_name);
    387   for (unsigned int i = 0; i<cr->methods_length; i++)
    388     GNUNET_free (cr->methods[i].type);
    389   GNUNET_free (cr->methods);
    390   GNUNET_free (cr);
    391 }
    392 
    393 
    394 void
    395 ANASTASIS_redux_done ()
    396 {
    397   struct ConfigRequest *cr;
    398 
    399   while (NULL != (cr = cr_head))
    400   {
    401     GNUNET_CONTAINER_DLL_remove (cr_head,
    402                                  cr_tail,
    403                                  cr);
    404     free_config_request (cr);
    405   }
    406   ANASTASIS_REDUX_ctx_ = NULL;
    407   if (NULL != redux_countries)
    408   {
    409     json_decref (redux_countries);
    410     redux_countries = NULL;
    411   }
    412   if (NULL != redux_id_attr)
    413   {
    414     json_decref (redux_id_attr);
    415     redux_id_attr = NULL;
    416   }
    417   if (NULL != provider_list)
    418   {
    419     json_decref (provider_list);
    420     provider_list = NULL;
    421   }
    422 }
    423 
    424 
    425 const json_t *
    426 ANASTASIS_redux_countries_init_ (void)
    427 {
    428   char *dn;
    429   json_error_t error;
    430 
    431   if (NULL != redux_countries)
    432     return redux_countries;
    433 
    434   {
    435     char *path;
    436 
    437     path = GNUNET_OS_installation_get_path (ANASTASIS_project_data (),
    438                                             GNUNET_OS_IPK_DATADIR);
    439     if (NULL == path)
    440     {
    441       GNUNET_break (0);
    442       return NULL;
    443     }
    444     GNUNET_asprintf (&dn,
    445                      "%s/redux.countries.json",
    446                      path);
    447     GNUNET_free (path);
    448   }
    449   redux_countries = json_load_file (dn,
    450                                     JSON_COMPACT,
    451                                     &error);
    452   if (NULL == redux_countries)
    453   {
    454     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    455                 "Failed to parse `%s': %s at %d:%d (%d)\n",
    456                 dn,
    457                 error.text,
    458                 error.line,
    459                 error.column,
    460                 error.position);
    461     GNUNET_free (dn);
    462     return NULL;
    463   }
    464   GNUNET_free (dn);
    465   return redux_countries;
    466 }
    467 
    468 
    469 /**
    470  * Abort waiting for /config reply.
    471  *
    472  * @param cls a `struct ConfigReduxWaiting` handle.
    473  */
    474 static void
    475 abort_provider_config_cb (void *cls)
    476 {
    477   struct ConfigReduxWaiting *w = cls;
    478   struct ConfigRequest *cr = w->cr;
    479 
    480   GNUNET_CONTAINER_DLL_remove (cr->w_head,
    481                                cr->w_tail,
    482                                w);
    483   json_decref (w->state);
    484   GNUNET_free (w);
    485 }
    486 
    487 
    488 /**
    489  * Notify anyone waiting on @a cr that the request is done
    490  * (successful or failed).
    491  *
    492  * @param[in,out] cr request that completed
    493  */
    494 static void
    495 notify_waiting (struct ConfigRequest *cr)
    496 {
    497   struct ConfigReduxWaiting *w;
    498 
    499   while (NULL != (w = cr->w_head))
    500   {
    501     json_t *apl;
    502     json_t *prov;
    503 
    504     if (NULL == (apl = json_object_get (w->state,
    505                                         "authentication_providers")))
    506     {
    507       GNUNET_assert (0 ==
    508                      json_object_set_new (w->state,
    509                                           "authentication_providers",
    510                                           apl = json_object ()));
    511     }
    512     if (TALER_EC_NONE != cr->ec)
    513     {
    514       prov = GNUNET_JSON_PACK (
    515         GNUNET_JSON_pack_string ("status",
    516                                  "error"),
    517         GNUNET_JSON_pack_uint64 ("error_code",
    518                                  cr->ec),
    519         GNUNET_JSON_pack_uint64 ("http_status",
    520                                  cr->http_status));
    521     }
    522     else
    523     {
    524       json_t *methods_list;
    525 
    526       methods_list = json_array ();
    527       GNUNET_assert (NULL != methods_list);
    528       for (unsigned int i = 0; i<cr->methods_length; i++)
    529       {
    530         struct AuthorizationMethodConfig *method = &cr->methods[i];
    531         json_t *mj = GNUNET_JSON_PACK (
    532           GNUNET_JSON_pack_string ("type",
    533                                    method->type),
    534           TALER_JSON_pack_amount ("usage_fee",
    535                                   &method->usage_fee));
    536 
    537         GNUNET_assert (0 ==
    538                        json_array_append_new (methods_list,
    539                                               mj));
    540       }
    541       prov = GNUNET_JSON_PACK (
    542         GNUNET_JSON_pack_string ("status",
    543                                  "ok"),
    544         GNUNET_JSON_pack_array_steal ("methods",
    545                                       methods_list),
    546         TALER_JSON_pack_amount ("annual_fee",
    547                                 &cr->annual_fee),
    548         TALER_JSON_pack_amount ("truth_upload_fee",
    549                                 &cr->truth_upload_fee),
    550         TALER_JSON_pack_amount ("liability_limit",
    551                                 &cr->liability_limit),
    552         GNUNET_JSON_pack_string ("business_name",
    553                                  cr->business_name),
    554         GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes",
    555                                  cr->storage_limit_in_megabytes),
    556         GNUNET_JSON_pack_data_auto ("provider_salt",
    557                                     &cr->provider_salt),
    558         GNUNET_JSON_pack_uint64 ("http_status",
    559                                  cr->http_status));
    560     }
    561     GNUNET_assert (0 ==
    562                    json_object_set_new (apl,
    563                                         cr->url,
    564                                         prov));
    565     w->cb (w->cb_cls,
    566            cr->ec,
    567            w->state);
    568     abort_provider_config_cb (w);
    569   }
    570 }
    571 
    572 
    573 /**
    574  * Notify anyone waiting on @a cr that the request is done
    575  * (successful or failed).
    576  *
    577  * @param[in,out] cls request that completed
    578  */
    579 static void
    580 notify_waiting_cb (void *cls)
    581 {
    582   struct ConfigRequest *cr = cls;
    583 
    584   cr->tt = NULL;
    585   notify_waiting (cr);
    586 }
    587 
    588 
    589 /**
    590  * Function called when it is time to retry a
    591  * failed /config request.
    592  *
    593  * @param cls the `struct ConfigRequest *` to retry.
    594  */
    595 static void
    596 retry_config (void *cls);
    597 
    598 
    599 /**
    600  * Function called with the results of a #ANASTASIS_get_config().
    601  *
    602  * @param cls closure
    603  * @param acfg anastasis configuration
    604  */
    605 static void
    606 config_cb (void *cls,
    607            const struct ANASTASIS_Config *acfg)
    608 {
    609   struct ConfigRequest *cr = cls;
    610 
    611   cr->co = NULL;
    612   if (NULL != cr->tt)
    613   {
    614     GNUNET_SCHEDULER_cancel (cr->tt);
    615     cr->tt = NULL;
    616   }
    617   cr->http_status = acfg->http_status;
    618   if (MHD_HTTP_OK != acfg->http_status)
    619   {
    620     if (0 == acfg->http_status)
    621       cr->ec = TALER_EC_ANASTASIS_GENERIC_PROVIDER_UNREACHABLE;
    622     else
    623       cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
    624   }
    625   if (0 == acfg->details.ok.storage_limit_in_megabytes)
    626   {
    627     cr->http_status = 0;
    628     cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG;
    629   }
    630   else
    631   {
    632     cr->ec = TALER_EC_NONE;
    633     GNUNET_free (cr->business_name);
    634     cr->business_name = GNUNET_strdup (acfg->details.ok.business_name);
    635     for (unsigned int i = 0; i<cr->methods_length; i++)
    636       GNUNET_free (cr->methods[i].type);
    637     GNUNET_free (cr->methods);
    638     cr->methods = GNUNET_new_array (acfg->details.ok.methods_length,
    639                                     struct AuthorizationMethodConfig);
    640     for (unsigned int i = 0; i<acfg->details.ok.methods_length; i++)
    641     {
    642       cr->methods[i].type = GNUNET_strdup (acfg->details.ok.methods[i].type);
    643       cr->methods[i].usage_fee = acfg->details.ok.methods[i].usage_fee;
    644     }
    645     cr->methods_length = acfg->details.ok.methods_length;
    646     cr->storage_limit_in_megabytes =
    647       acfg->details.ok.storage_limit_in_megabytes;
    648     cr->annual_fee = acfg->details.ok.annual_fee;
    649     cr->truth_upload_fee = acfg->details.ok.truth_upload_fee;
    650     cr->liability_limit = acfg->details.ok.liability_limit;
    651     cr->provider_salt = acfg->details.ok.provider_salt;
    652   }
    653   notify_waiting (cr);
    654   if (MHD_HTTP_OK != acfg->http_status)
    655   {
    656     cr->backoff = GNUNET_TIME_STD_BACKOFF (cr->backoff);
    657     GNUNET_assert (NULL == cr->tt);
    658     GNUNET_assert (NULL != cr->url);
    659     cr->tt = GNUNET_SCHEDULER_add_delayed (cr->backoff,
    660                                            &retry_config,
    661                                            cr);
    662   }
    663 }
    664 
    665 
    666 /**
    667  * Aborts a "get config" after timeout.
    668  *
    669  * @param cls closure for a "get config" request
    670  */
    671 static void
    672 config_request_timeout (void *cls)
    673 {
    674   struct ConfigRequest *cr = cls;
    675 
    676   cr->tt = NULL;
    677   if (NULL != cr->co)
    678   {
    679     ANASTASIS_config_cancel (cr->co);
    680     cr->co = NULL;
    681   }
    682   cr->http_status = 0;
    683   cr->ec = TALER_EC_GENERIC_TIMEOUT;
    684   notify_waiting (cr);
    685   cr->backoff = GNUNET_TIME_STD_BACKOFF (cr->backoff);
    686   GNUNET_assert (NULL == cr->tt);
    687   GNUNET_assert (NULL != cr->url);
    688   cr->tt = GNUNET_SCHEDULER_add_delayed (cr->backoff,
    689                                          &retry_config,
    690                                          cr);
    691 }
    692 
    693 
    694 static void
    695 retry_config (void *cls)
    696 {
    697   struct ConfigRequest *cr = cls;
    698 
    699   cr->tt = NULL;
    700   if (NULL != cr->co)
    701   {
    702     ANASTASIS_config_cancel (cr->co);
    703     cr->co = NULL;
    704   }
    705   cr->timeout_at = GNUNET_TIME_relative_to_absolute (CONFIG_GENERIC_TIMEOUT);
    706   GNUNET_assert (NULL == cr->tt);
    707   cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at,
    708                                     &config_request_timeout,
    709                                     cr);
    710   cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_,
    711                                  cr->url,
    712                                  &config_cb,
    713                                  cr);
    714   GNUNET_break (NULL != cr->co);
    715 }
    716 
    717 
    718 /**
    719  * Schedule job to obtain Anastasis provider configuration at @a url.
    720  *
    721  * @param timeout how long to wait for a reply
    722  * @param url base URL of Anastasis provider
    723  * @return check config handle
    724  */
    725 static struct ConfigRequest *
    726 check_config (struct GNUNET_TIME_Relative timeout,
    727               const char *url)
    728 {
    729   struct ConfigRequest *cr;
    730 
    731   for (cr = cr_head; NULL != cr; cr = cr->next)
    732   {
    733     if (0 != strcmp (url,
    734                      cr->url))
    735       continue;
    736     if (NULL != cr->co)
    737     {
    738       struct GNUNET_TIME_Relative duration;
    739       struct GNUNET_TIME_Relative left;
    740       struct GNUNET_TIME_Relative xleft;
    741 
    742       duration = GNUNET_TIME_absolute_get_duration (cr->start_time);
    743       left = GNUNET_TIME_relative_subtract (timeout,
    744                                             duration);
    745       xleft = GNUNET_TIME_absolute_get_remaining (cr->timeout_at);
    746       if (GNUNET_TIME_relative_cmp (left,
    747                                     <,
    748                                     xleft))
    749       {
    750         /* new timeout is shorter! */
    751         cr->timeout_at = GNUNET_TIME_relative_to_absolute (left);
    752         GNUNET_SCHEDULER_cancel (cr->tt);
    753         cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at,
    754                                           &config_request_timeout,
    755                                           cr);
    756       }
    757       return cr; /* already on it */
    758     }
    759     break;
    760   }
    761   if (NULL == cr)
    762   {
    763     cr = GNUNET_new (struct ConfigRequest);
    764     cr->start_time = GNUNET_TIME_absolute_get ();
    765     cr->url = GNUNET_strdup (url);
    766     GNUNET_CONTAINER_DLL_insert (cr_head,
    767                                  cr_tail,
    768                                  cr);
    769   }
    770   if (MHD_HTTP_OK == cr->http_status)
    771     return cr;
    772   cr->timeout_at = GNUNET_TIME_relative_to_absolute (timeout);
    773   if (NULL != cr->tt)
    774     GNUNET_SCHEDULER_cancel (cr->tt);
    775   cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at,
    776                                     &config_request_timeout,
    777                                     cr);
    778   cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_,
    779                                  cr->url,
    780                                  &config_cb,
    781                                  cr);
    782   if (NULL == cr->co)
    783   {
    784     GNUNET_break (0);
    785     return NULL;
    786   }
    787   return cr;
    788 }
    789 
    790 
    791 /**
    792  * Begin asynchronous check for provider configurations.
    793  *
    794  * @param cc country code that was selected
    795  * @param[in,out] state to set provider list for
    796  * @return #TALER_EC_NONE on success
    797  */
    798 static enum TALER_ErrorCode
    799 begin_provider_config_check (const char *cc,
    800                              json_t *state)
    801 {
    802   if (NULL == provider_list)
    803   {
    804     json_error_t error;
    805     char *dn;
    806     char *path;
    807 
    808     path = GNUNET_OS_installation_get_path (ANASTASIS_project_data (),
    809                                             GNUNET_OS_IPK_DATADIR);
    810     if (NULL == path)
    811     {
    812       GNUNET_break (0);
    813       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    814     }
    815     GNUNET_asprintf (&dn,
    816                      "%s/provider-list.json",
    817                      path);
    818     GNUNET_free (path);
    819     provider_list = json_load_file (dn,
    820                                     JSON_COMPACT,
    821                                     &error);
    822     if (NULL == provider_list)
    823     {
    824       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    825                   "Failed to parse `%s': %s at %d:%d (%d)\n",
    826                   dn,
    827                   error.text,
    828                   error.line,
    829                   error.column,
    830                   error.position);
    831       GNUNET_free (dn);
    832       return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
    833     }
    834     GNUNET_free (dn);
    835   }
    836 
    837   {
    838     size_t index;
    839     json_t *provider;
    840     const json_t *provider_arr = json_object_get (provider_list,
    841                                                   "anastasis_provider");
    842     json_t *pl;
    843 
    844     pl = json_object ();
    845     json_array_foreach (provider_arr, index, provider)
    846     {
    847       const char *url;
    848       const char *restricted = NULL;
    849       struct GNUNET_JSON_Specification spec[] = {
    850         GNUNET_JSON_spec_string ("url",
    851                                  &url),
    852         GNUNET_JSON_spec_mark_optional (
    853           GNUNET_JSON_spec_string ("restricted",
    854                                    &restricted),
    855           NULL),
    856         GNUNET_JSON_spec_end ()
    857       };
    858       json_t *prov;
    859 
    860       if (GNUNET_OK !=
    861           GNUNET_JSON_parse (provider,
    862                              spec,
    863                              NULL, NULL))
    864       {
    865         GNUNET_break (0);
    866         json_decref (pl);
    867         return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
    868       }
    869       if ( (NULL != restricted) &&
    870            (0 != strcmp (restricted,
    871                          cc)) )
    872       {
    873         /* skip */
    874         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    875                     "Skipping provider restricted to country `%s'\n",
    876                     restricted);
    877         continue;
    878       }
    879       if ( (NULL == restricted) &&
    880            (0 == strcmp (cc,
    881                          "xx")) )
    882       {
    883         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    884                     "Running in demo mode, skipping unrestricted providers\n");
    885         /* demo mode, skipping regular providers */
    886         continue;
    887       }
    888       prov = GNUNET_JSON_PACK (
    889         GNUNET_JSON_pack_string ("status",
    890                                  "not-contacted"));
    891       GNUNET_assert (NULL != prov);
    892       GNUNET_assert (0 ==
    893                      json_object_set_new (pl,
    894                                           url,
    895                                           prov));
    896       check_config (CONFIG_GENERIC_TIMEOUT,
    897                     url);
    898     }
    899     GNUNET_assert (0 ==
    900                    json_object_set_new (state,
    901                                         "authentication_providers",
    902                                         pl));
    903   }
    904   return TALER_EC_NONE;
    905 }
    906 
    907 
    908 /**
    909  * Function to validate an input by regular expression ("validation-regex").
    910  *
    911  * @param input text to validate
    912  * @param regexp regular expression to validate
    913  * @return true if validation passed, else false
    914  */
    915 static bool
    916 validate_regex (const char *input,
    917                 const char *regexp)
    918 {
    919   regex_t regex;
    920 
    921   if (0 != regcomp (&regex,
    922                     regexp,
    923                     REG_EXTENDED))
    924   {
    925     GNUNET_break (0);
    926     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    927                 "Failed to compile regular expression `%s'.",
    928                 regexp);
    929     return true;
    930   }
    931   /* check if input has correct form */
    932   if (0 != regexec (&regex,
    933                     input,
    934                     0,
    935                     NULL,
    936                     0))
    937   {
    938     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    939                 "Input `%s' does not match regex `%s'\n",
    940                 input,
    941                 regexp);
    942     regfree (&regex);
    943     return false;
    944   }
    945   regfree (&regex);
    946   return true;
    947 }
    948 
    949 
    950 /**
    951  * Function to load json containing country specific identity
    952  * attributes.  Uses a single-slot cache to avoid loading
    953  * exactly the same attributes twice.
    954  *
    955  * @param country_code country code (e.g. "de")
    956  * @return NULL on error
    957  */
    958 static const json_t *
    959 redux_id_attr_init (const char *country_code)
    960 {
    961   static char redux_id_cc[3];
    962   char *dn;
    963   json_error_t error;
    964 
    965   if (0 == strcmp (country_code,
    966                    redux_id_cc))
    967     return redux_id_attr;
    968 
    969   if (NULL != redux_id_attr)
    970   {
    971     json_decref (redux_id_attr);
    972     redux_id_attr = NULL;
    973   }
    974   {
    975     char *path;
    976 
    977     path = GNUNET_OS_installation_get_path (ANASTASIS_project_data (),
    978                                             GNUNET_OS_IPK_DATADIR);
    979     if (NULL == path)
    980     {
    981       GNUNET_break (0);
    982       return NULL;
    983     }
    984     GNUNET_asprintf (&dn,
    985                      "%s/redux.%s.json",
    986                      path,
    987                      country_code);
    988     GNUNET_free (path);
    989   }
    990   redux_id_attr = json_load_file (dn,
    991                                   JSON_COMPACT,
    992                                   &error);
    993   if (NULL == redux_id_attr)
    994   {
    995     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    996                 "Failed to parse `%s': %s at %d:%d (%d)\n",
    997                 dn,
    998                 error.text,
    999                 error.line,
   1000                 error.column,
   1001                 error.position);
   1002     GNUNET_free (dn);
   1003     return NULL;
   1004   }
   1005   GNUNET_free (dn);
   1006   strncpy (redux_id_cc,
   1007            country_code,
   1008            sizeof (redux_id_cc));
   1009   redux_id_cc[2] = '\0';
   1010   return redux_id_attr;
   1011 }
   1012 
   1013 
   1014 /**
   1015  * DispatchHandler/Callback function which is called for a
   1016  * "select_continent" action.
   1017  *
   1018  * @param state state to operate on
   1019  * @param arguments arguments to use for operation on state
   1020  * @param cb callback to call during/after operation
   1021  * @param cb_cls callback closure
   1022  * @return NULL
   1023  */
   1024 static struct ANASTASIS_ReduxAction *
   1025 select_continent (json_t *state,
   1026                   const json_t *arguments,
   1027                   ANASTASIS_ActionCallback cb,
   1028                   void *cb_cls)
   1029 {
   1030   const json_t *rc = ANASTASIS_redux_countries_init_ ();
   1031   const json_t *root = json_object_get (rc,
   1032                                         "countries");
   1033   const json_t *continent;
   1034   json_t *countries;
   1035 
   1036   if (NULL == root)
   1037   {
   1038     ANASTASIS_redux_fail_ (cb,
   1039                            cb_cls,
   1040                            TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
   1041                            "'countries' missing");
   1042     return NULL;
   1043   }
   1044   if (NULL == arguments)
   1045   {
   1046     ANASTASIS_redux_fail_ (cb,
   1047                            cb_cls,
   1048                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1049                            "arguments missing");
   1050     return NULL;
   1051   }
   1052   continent = json_object_get (arguments,
   1053                                "continent");
   1054   if (NULL == continent)
   1055   {
   1056     ANASTASIS_redux_fail_ (cb,
   1057                            cb_cls,
   1058                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1059                            "'continent' missing");
   1060     return NULL;
   1061   }
   1062   countries = json_array ();
   1063   GNUNET_assert (NULL != countries);
   1064   {
   1065     size_t index;
   1066     const json_t *country;
   1067     bool found = false;
   1068 
   1069     json_array_foreach (root, index, country)
   1070     {
   1071       json_t *temp_continent = json_object_get (country,
   1072                                                 "continent");
   1073       if (1 == json_equal (continent,
   1074                            temp_continent))
   1075       {
   1076         GNUNET_assert (0 ==
   1077                        json_array_append (countries,
   1078                                           (json_t *) country));
   1079         found = true;
   1080       }
   1081     }
   1082     if (! found)
   1083     {
   1084       ANASTASIS_redux_fail_ (cb,
   1085                              cb_cls,
   1086                              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1087                              "'continent' unknown");
   1088       return NULL;
   1089     }
   1090   }
   1091   redux_transition (state,
   1092                     ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING);
   1093   GNUNET_assert (0 ==
   1094                  json_object_set (state,
   1095                                   "selected_continent",
   1096                                   (json_t *) continent));
   1097   GNUNET_assert (0 ==
   1098                  json_object_set_new (state,
   1099                                       "countries",
   1100                                       countries));
   1101   cb (cb_cls,
   1102       TALER_EC_NONE,
   1103       state);
   1104   return NULL;
   1105 }
   1106 
   1107 
   1108 /**
   1109  * DispatchHandler/Callback function which is called for a
   1110  * "select_country" action.
   1111  *
   1112  * @param state state to operate on
   1113  * @param arguments arguments to use for operation on state
   1114  * @param cb callback to call during/after operation
   1115  * @param cb_cls callback closure
   1116  * @return #ANASTASIS_ReduxAction
   1117  */
   1118 static struct ANASTASIS_ReduxAction *
   1119 select_country (json_t *state,
   1120                 const json_t *arguments,
   1121                 ANASTASIS_ActionCallback cb,
   1122                 void *cb_cls)
   1123 {
   1124   const json_t *required_attrs;
   1125   const json_t *country_code;
   1126 
   1127   if (NULL == arguments)
   1128   {
   1129     ANASTASIS_redux_fail_ (cb,
   1130                            cb_cls,
   1131                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1132                            "arguments missing");
   1133     return NULL;
   1134   }
   1135   country_code = json_object_get (arguments,
   1136                                   "country_code");
   1137   if (NULL == country_code)
   1138   {
   1139     ANASTASIS_redux_fail_ (cb,
   1140                            cb_cls,
   1141                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1142                            "'country_code' missing");
   1143     return NULL;
   1144   }
   1145 
   1146   {
   1147     json_t *countries = json_object_get (state,
   1148                                          "countries");
   1149     size_t index;
   1150     json_t *country;
   1151     bool found = false;
   1152 
   1153     json_array_foreach (countries, index, country)
   1154     {
   1155       json_t *cc = json_object_get (country,
   1156                                     "code");
   1157       if (1 == json_equal (country_code,
   1158                            cc))
   1159       {
   1160         found = true;
   1161         break;
   1162       }
   1163     }
   1164     if (! found)
   1165     {
   1166       ANASTASIS_redux_fail_ (cb,
   1167                              cb_cls,
   1168                              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1169                              "specified country not on selected continent");
   1170       return NULL;
   1171     }
   1172   }
   1173 
   1174   /* Begin fetching provider /configs (we likely need them later) */
   1175   {
   1176     enum TALER_ErrorCode ec;
   1177 
   1178     ec = begin_provider_config_check (json_string_value (country_code),
   1179                                       state);
   1180     if (TALER_EC_NONE != ec)
   1181     {
   1182       GNUNET_break (0);
   1183       ANASTASIS_redux_fail_ (cb,
   1184                              cb_cls,
   1185                              ec,
   1186                              NULL);
   1187       return NULL;
   1188     }
   1189   }
   1190 
   1191   {
   1192     const json_t *ria;
   1193 
   1194     ria = redux_id_attr_init (json_string_value (country_code));
   1195     if (NULL == ria)
   1196     {
   1197       GNUNET_break (0);
   1198       ANASTASIS_redux_fail_ (cb,
   1199                              cb_cls,
   1200                              TALER_EC_ANASTASIS_REDUCER_RESOURCE_MISSING,
   1201                              json_string_value (country_code));
   1202       return NULL;
   1203     }
   1204     required_attrs = json_object_get (ria,
   1205                                       "required_attributes");
   1206   }
   1207   if (NULL == required_attrs)
   1208   {
   1209     ANASTASIS_redux_fail_ (cb,
   1210                            cb_cls,
   1211                            TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
   1212                            "'required_attributes' missing");
   1213     return NULL;
   1214   }
   1215   redux_transition (state,
   1216                     ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING);
   1217   GNUNET_assert (0 ==
   1218                  json_object_set (state,
   1219                                   "selected_country",
   1220                                   (json_t *) country_code));
   1221   GNUNET_assert (0 ==
   1222                  json_object_set (state,
   1223                                   "required_attributes",
   1224                                   (json_t *) required_attrs));
   1225   cb (cb_cls,
   1226       TALER_EC_NONE,
   1227       state);
   1228   return NULL;
   1229 }
   1230 
   1231 
   1232 /**
   1233  * DispatchHandler/Callback function which is called for a
   1234  * "unselect_continent" action.
   1235  *
   1236  * @param state state to operate on
   1237  * @param arguments arguments to use for operation on state
   1238  * @param cb callback to call during/after operation
   1239  * @param cb_cls callback closure
   1240  * @return NULL
   1241  */
   1242 static struct ANASTASIS_ReduxAction *
   1243 unselect_continent (json_t *state,
   1244                     const json_t *arguments,
   1245                     ANASTASIS_ActionCallback cb,
   1246                     void *cb_cls)
   1247 {
   1248   redux_transition (state,
   1249                     ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING);
   1250   cb (cb_cls,
   1251       TALER_EC_NONE,
   1252       state);
   1253   return NULL;
   1254 }
   1255 
   1256 
   1257 struct ANASTASIS_ReduxAction *
   1258 ANASTASIS_REDUX_add_provider_to_state_ (const char *url,
   1259                                         json_t *state,
   1260                                         ANASTASIS_ActionCallback cb,
   1261                                         void *cb_cls)
   1262 {
   1263   struct ConfigRequest *cr;
   1264   struct ConfigReduxWaiting *w;
   1265 
   1266   cr = check_config (CONFIG_FAST_TIMEOUT,
   1267                      url);
   1268   w = GNUNET_new (struct ConfigReduxWaiting);
   1269   w->cr = cr;
   1270   w->state = json_incref (state);
   1271   w->cb = cb;
   1272   w->cb_cls = cb_cls;
   1273   w->ra.cleanup = &abort_provider_config_cb;
   1274   w->ra.cleanup_cls = w;
   1275   GNUNET_CONTAINER_DLL_insert (cr->w_head,
   1276                                cr->w_tail,
   1277                                w);
   1278   if (NULL == cr->co)
   1279   {
   1280     if (NULL != cr->tt)
   1281       GNUNET_SCHEDULER_cancel (cr->tt);
   1282     cr->tt = GNUNET_SCHEDULER_add_now (&notify_waiting_cb,
   1283                                        cr);
   1284   }
   1285   return &w->ra;
   1286 }
   1287 
   1288 
   1289 /**
   1290  * DispatchHandler/Callback function which is called for a
   1291  * "enter_user_attributes" action.
   1292  * Returns an #ANASTASIS_ReduxAction if operation is async.
   1293  *
   1294  * @param state state to operate on
   1295  * @param arguments arguments to use for operation on state
   1296  * @param cb callback to call during/after operation
   1297  * @param cb_cls callback closure
   1298  * @return NULL
   1299  */
   1300 static struct ANASTASIS_ReduxAction *
   1301 enter_user_attributes (json_t *state,
   1302                        const json_t *arguments,
   1303                        ANASTASIS_ActionCallback cb,
   1304                        void *cb_cls)
   1305 {
   1306   const json_t *attributes;
   1307   const json_t *required_attributes;
   1308 
   1309   if (NULL == arguments)
   1310   {
   1311     ANASTASIS_redux_fail_ (cb,
   1312                            cb_cls,
   1313                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1314                            "arguments missing");
   1315     return NULL;
   1316   }
   1317   attributes = json_object_get (arguments,
   1318                                 "identity_attributes");
   1319   if (NULL == attributes)
   1320   {
   1321     ANASTASIS_redux_fail_ (cb,
   1322                            cb_cls,
   1323                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1324                            "'identity_attributes' missing");
   1325     return NULL;
   1326   }
   1327   GNUNET_assert (0 ==
   1328                  json_object_set (state,
   1329                                   "identity_attributes",
   1330                                   (json_t *) attributes));
   1331 
   1332   /* Verify required attributes are present and well-formed */
   1333   required_attributes = json_object_get (state,
   1334                                          "required_attributes");
   1335   if ( (NULL == required_attributes) ||
   1336        (! json_is_array (required_attributes)) )
   1337   {
   1338     ANASTASIS_redux_fail_ (cb,
   1339                            cb_cls,
   1340                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1341                            "'required_attributes' must be an array");
   1342     return NULL;
   1343   }
   1344   {
   1345     size_t index;
   1346     json_t *required_attribute;
   1347 
   1348     json_array_foreach (required_attributes, index, required_attribute)
   1349     {
   1350       const char *name;
   1351       const char *attribute_value;
   1352       const char *regexp = NULL;
   1353       const char *reglog = NULL;
   1354       bool optional = false;
   1355       struct GNUNET_JSON_Specification spec[] = {
   1356         GNUNET_JSON_spec_string ("name",
   1357                                  &name),
   1358         GNUNET_JSON_spec_mark_optional (
   1359           GNUNET_JSON_spec_string ("validation-regex",
   1360                                    &regexp),
   1361           NULL),
   1362         GNUNET_JSON_spec_mark_optional (
   1363           GNUNET_JSON_spec_string ("validation-logic",
   1364                                    &reglog),
   1365           NULL),
   1366         GNUNET_JSON_spec_mark_optional (
   1367           GNUNET_JSON_spec_bool ("optional",
   1368                                  &optional),
   1369           NULL),
   1370         GNUNET_JSON_spec_end ()
   1371       };
   1372 
   1373       if (GNUNET_OK !=
   1374           GNUNET_JSON_parse (required_attribute,
   1375                              spec,
   1376                              NULL, NULL))
   1377       {
   1378         GNUNET_break (0);
   1379         ANASTASIS_redux_fail_ (cb,
   1380                                cb_cls,
   1381                                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1382                                "'required_attributes' lacks required fields");
   1383         return NULL;
   1384       }
   1385       attribute_value = json_string_value (json_object_get (attributes,
   1386                                                             name));
   1387       if (NULL == attribute_value)
   1388       {
   1389         if (optional)
   1390           continue;
   1391         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1392                     "Request is missing required attribute `%s'\n",
   1393                     name);
   1394         ANASTASIS_redux_fail_ (cb,
   1395                                cb_cls,
   1396                                TALER_EC_GENERIC_PARAMETER_MISSING,
   1397                                name);
   1398         return NULL;
   1399       }
   1400       if ( (NULL != regexp) &&
   1401            (! validate_regex (attribute_value,
   1402                               regexp)) )
   1403       {
   1404         ANASTASIS_redux_fail_ (cb,
   1405                                cb_cls,
   1406                                TALER_EC_ANASTASIS_REDUCER_INPUT_REGEX_FAILED,
   1407                                name);
   1408         return NULL;
   1409       }
   1410 
   1411       if (NULL != reglog)
   1412       {
   1413         bool (*regfun)(const char *);
   1414 
   1415         regfun = dlsym (RTLD_DEFAULT,
   1416                         reglog);
   1417         if (NULL == regfun)
   1418         {
   1419           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1420                       "Custom validation function `%s' is not available: %s\n",
   1421                       reglog,
   1422                       dlerror ());
   1423         }
   1424         else if (! regfun (attribute_value))
   1425         {
   1426           ANASTASIS_redux_fail_ (cb,
   1427                                  cb_cls,
   1428                                  TALER_EC_ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED,
   1429                                  name);
   1430           return NULL;
   1431         }
   1432       }
   1433     } /* end for all attributes loop */
   1434   } /* end for all attributes scope */
   1435 
   1436   /* Transition based on mode */
   1437   {
   1438     const char *s_mode = get_state_mode (state);
   1439 
   1440     if (0 == strcmp (s_mode,
   1441                      "backup_state"))
   1442     {
   1443       GNUNET_assert (0 ==
   1444                      json_object_set_new (
   1445                        state,
   1446                        "backup_state",
   1447                        json_string (
   1448                          ANASTASIS_backup_state_to_string_ (
   1449                            ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING))));
   1450       return ANASTASIS_REDUX_backup_begin_ (state,
   1451                                             arguments,
   1452                                             cb,
   1453                                             cb_cls);
   1454     }
   1455     else
   1456     {
   1457       GNUNET_assert (0 ==
   1458                      json_object_set_new (
   1459                        state,
   1460                        "recovery_state",
   1461                        json_string (
   1462                          ANASTASIS_recovery_state_to_string_ (
   1463                            ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING))));
   1464       return ANASTASIS_REDUX_recovery_challenge_begin_ (state,
   1465                                                         arguments,
   1466                                                         cb,
   1467                                                         cb_cls);
   1468     }
   1469   }
   1470 }
   1471 
   1472 
   1473 /**
   1474  * DispatchHandler/Callback function which is called for a
   1475  * "add_provider" action.  Adds another Anastasis provider
   1476  * to the list of available providers for storing information.
   1477  *
   1478  * @param state state to operate on
   1479  * @param arguments arguments with a provider URL to add
   1480  * @param cb callback to call during/after operation
   1481  * @param cb_cls callback closure
   1482  */
   1483 static struct ANASTASIS_ReduxAction *
   1484 add_provider (json_t *state,
   1485               const json_t *arguments,
   1486               ANASTASIS_ActionCallback cb,
   1487               void *cb_cls)
   1488 {
   1489   if (ANASTASIS_add_provider_ (state,
   1490                                arguments,
   1491                                cb,
   1492                                cb_cls))
   1493     return NULL;
   1494   cb (cb_cls,
   1495       TALER_EC_NONE,
   1496       state);
   1497   return NULL;
   1498 }
   1499 
   1500 
   1501 bool
   1502 ANASTASIS_add_provider_ (json_t *state,
   1503                          const json_t *arguments,
   1504                          ANASTASIS_ActionCallback cb,
   1505                          void *cb_cls)
   1506 {
   1507   json_t *tlist;
   1508 
   1509   if (NULL == arguments)
   1510   {
   1511     ANASTASIS_redux_fail_ (cb,
   1512                            cb_cls,
   1513                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1514                            "arguments missing");
   1515     return true; /* cb was invoked */
   1516   }
   1517   tlist = json_object_get (state,
   1518                            "authentication_providers");
   1519   if (NULL == tlist)
   1520   {
   1521     tlist = json_object ();
   1522     GNUNET_assert (NULL != tlist);
   1523     GNUNET_assert (0 ==
   1524                    json_object_set_new (state,
   1525                                         "authentication_providers",
   1526                                         tlist));
   1527   }
   1528   {
   1529     json_t *params;
   1530     const char *url;
   1531 
   1532     json_object_foreach (((json_t *) arguments), url, params)
   1533     {
   1534       GNUNET_assert (0 ==
   1535                      json_object_set (tlist,
   1536                                       url,
   1537                                       params));
   1538     }
   1539   }
   1540   return false; /* cb not invoked */
   1541 }
   1542 
   1543 
   1544 struct ANASTASIS_ReduxAction *
   1545 ANASTASIS_back_generic_decrement_ (json_t *state,
   1546                                    const json_t *arguments,
   1547                                    ANASTASIS_ActionCallback cb,
   1548                                    void *cb_cls)
   1549 {
   1550   const char *s_mode = get_state_mode (state);
   1551   const char *state_string = json_string_value (json_object_get (state,
   1552                                                                  s_mode));
   1553 
   1554   (void) arguments;
   1555   GNUNET_assert (NULL != state_string);
   1556   if (0 == strcmp ("backup_state",
   1557                    s_mode))
   1558   {
   1559     enum ANASTASIS_BackupState state_index;
   1560 
   1561     state_index = ANASTASIS_backup_state_from_string_ (state_string);
   1562     GNUNET_assert (state_index > 0);
   1563     state_index = state_index - 1;
   1564 
   1565     GNUNET_assert (0 ==
   1566                    json_object_set_new (
   1567                      state,
   1568                      s_mode,
   1569                      json_string (
   1570                        ANASTASIS_backup_state_to_string_ (state_index))));
   1571   }
   1572   else
   1573   {
   1574     enum ANASTASIS_RecoveryState state_index;
   1575 
   1576     state_index = ANASTASIS_recovery_state_from_string_ (state_string);
   1577     GNUNET_assert (state_index > 0);
   1578     state_index = state_index - 1;
   1579     GNUNET_assert (0 ==
   1580                    json_object_set_new (
   1581                      state,
   1582                      s_mode,
   1583                      json_string (
   1584                        ANASTASIS_recovery_state_to_string_ (state_index))));
   1585   }
   1586   cb (cb_cls,
   1587       TALER_EC_NONE,
   1588       state);
   1589   return NULL;
   1590 }
   1591 
   1592 
   1593 /**
   1594  * Callback function which is called by the reducer in dependence of
   1595  * given state and action.
   1596  *
   1597  * @param state the previous state to operate on
   1598  * @param arguments the arguments needed by operation to operate on state
   1599  * @param cb Callback function which returns the new state
   1600  * @param cb_cls closure for @a cb
   1601  * @return handle to cancel async actions, NULL if @a cb was already called
   1602  */
   1603 typedef struct ANASTASIS_ReduxAction *
   1604 (*DispatchHandler)(json_t *state,
   1605                    const json_t *arguments,
   1606                    ANASTASIS_ActionCallback cb,
   1607                    void *cb_cls);
   1608 
   1609 
   1610 /**
   1611  * Closure for read operations on the external reducer.
   1612  */
   1613 struct ExternalReducerCls
   1614 {
   1615   struct GNUNET_Buffer read_buffer;
   1616   struct GNUNET_SCHEDULER_Task *read_task;
   1617   struct GNUNET_DISK_PipeHandle *reducer_stdin;
   1618   struct GNUNET_DISK_PipeHandle *reducer_stdout;
   1619   struct GNUNET_Process *reducer_process;
   1620   ANASTASIS_ActionCallback action_cb;
   1621   void *action_cb_cls;
   1622 };
   1623 
   1624 /**
   1625  * Clean up and destroy the external reducer state.
   1626  *
   1627  * @param cls closure, a 'struct ExternalReducerCls *'
   1628  */
   1629 static void
   1630 cleanup_external_reducer (void *cls)
   1631 {
   1632   struct ExternalReducerCls *red_cls = cls;
   1633 
   1634   if (NULL != red_cls->read_task)
   1635   {
   1636     GNUNET_SCHEDULER_cancel (red_cls->read_task);
   1637     red_cls->read_task = NULL;
   1638   }
   1639 
   1640   GNUNET_buffer_clear (&red_cls->read_buffer);
   1641   if (NULL != red_cls->reducer_stdin)
   1642   {
   1643     GNUNET_DISK_pipe_close (red_cls->reducer_stdin);
   1644     red_cls->reducer_stdin = NULL;
   1645   }
   1646   if (NULL != red_cls->reducer_stdout)
   1647   {
   1648     GNUNET_DISK_pipe_close (red_cls->reducer_stdout);
   1649     red_cls->reducer_stdout = NULL;
   1650   }
   1651 
   1652   if (NULL != red_cls->reducer_process)
   1653   {
   1654     enum GNUNET_OS_ProcessStatusType type;
   1655     unsigned long code;
   1656     enum GNUNET_GenericReturnValue pwret;
   1657 
   1658     pwret = GNUNET_process_wait (red_cls->reducer_process,
   1659                                  false,
   1660                                  &type,
   1661                                  &code);
   1662     GNUNET_assert (GNUNET_SYSERR != pwret);
   1663     if (GNUNET_NO == pwret)
   1664     {
   1665       GNUNET_assert (GNUNET_OK ==
   1666                      GNUNET_process_kill (red_cls->reducer_process,
   1667                                           SIGTERM));
   1668       GNUNET_assert (GNUNET_SYSERR !=
   1669                      GNUNET_process_wait (red_cls->reducer_process,
   1670                                           true,
   1671                                           NULL,
   1672                                           NULL));
   1673     }
   1674 
   1675     GNUNET_process_destroy (red_cls->reducer_process);
   1676     red_cls->reducer_process = NULL;
   1677   }
   1678 
   1679   GNUNET_free (red_cls);
   1680 }
   1681 
   1682 
   1683 /**
   1684  * Task called when
   1685  *
   1686  * @param cls closure, a 'struct ExternalReducerCls *'
   1687  */
   1688 static void
   1689 external_reducer_read_cb (void *cls)
   1690 {
   1691   struct ExternalReducerCls *red_cls = cls;
   1692   ssize_t sret;
   1693   char buf[256];
   1694 
   1695   red_cls->read_task = NULL;
   1696 
   1697   sret = GNUNET_DISK_file_read (GNUNET_DISK_pipe_handle (
   1698                                   red_cls->reducer_stdout,
   1699                                   GNUNET_DISK_PIPE_END_READ),
   1700                                 buf,
   1701                                 256);
   1702   if (sret < 0)
   1703   {
   1704     GNUNET_break (0);
   1705     red_cls->action_cb (red_cls->action_cb_cls,
   1706                         TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR,
   1707                         NULL);
   1708     cleanup_external_reducer (red_cls);
   1709     return;
   1710   }
   1711   else if (0 == sret)
   1712   {
   1713     char *str = GNUNET_buffer_reap_str (&red_cls->read_buffer);
   1714     json_t *json;
   1715 
   1716     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1717                 "Got external reducer response: '%s'\n",
   1718                 str);
   1719 
   1720     json = json_loads (str, 0, NULL);
   1721 
   1722     if (NULL == json)
   1723     {
   1724       GNUNET_break (0);
   1725       red_cls->action_cb (red_cls->action_cb_cls,
   1726                           TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR,
   1727                           NULL);
   1728       cleanup_external_reducer (red_cls);
   1729       return;
   1730     }
   1731 
   1732     {
   1733       enum TALER_ErrorCode ec;
   1734       ec = json_integer_value (json_object_get (json, "code"));
   1735 
   1736       red_cls->action_cb (red_cls->action_cb_cls,
   1737                           ec,
   1738                           json);
   1739     }
   1740     cleanup_external_reducer (red_cls);
   1741     return;
   1742   }
   1743   else
   1744   {
   1745     GNUNET_buffer_write (&red_cls->read_buffer,
   1746                          buf,
   1747                          sret);
   1748 
   1749     red_cls->read_task = GNUNET_SCHEDULER_add_read_file (
   1750       GNUNET_TIME_UNIT_FOREVER_REL,
   1751       GNUNET_DISK_pipe_handle (
   1752         red_cls->reducer_stdout,
   1753         GNUNET_DISK_PIPE_END_READ),
   1754       external_reducer_read_cb,
   1755       red_cls);
   1756   }
   1757 }
   1758 
   1759 
   1760 /**
   1761  * Handle an action using an external reducer, i.e.
   1762  * by shelling out to another process.
   1763  */
   1764 static struct ANASTASIS_ReduxAction *
   1765 redux_action_external (const char *ext_reducer,
   1766                        const json_t *state,
   1767                        const char *action,
   1768                        const json_t *arguments,
   1769                        ANASTASIS_ActionCallback cb,
   1770                        void *cb_cls)
   1771 {
   1772   char *arg_str;
   1773   char *state_str = json_dumps (state, JSON_COMPACT);
   1774   ssize_t sret;
   1775   struct ExternalReducerCls *red_cls = GNUNET_new (struct ExternalReducerCls);
   1776 
   1777   if (NULL == arguments)
   1778     arg_str = GNUNET_strdup ("{}");
   1779   else
   1780     arg_str = json_dumps (arguments, JSON_COMPACT);
   1781 
   1782   red_cls->action_cb = cb;
   1783   red_cls->action_cb_cls = cb_cls;
   1784 
   1785   GNUNET_assert (NULL != (red_cls->reducer_stdin = GNUNET_DISK_pipe (
   1786                             GNUNET_DISK_PF_NONE)));
   1787   GNUNET_assert (NULL != (red_cls->reducer_stdout = GNUNET_DISK_pipe (
   1788                             GNUNET_DISK_PF_NONE)));
   1789 
   1790   /* By the time we're here, this variable should be unset, because
   1791      otherwise using anastasis-reducer as the external reducer
   1792      will lead to infinite recursion. */
   1793   GNUNET_assert (NULL == getenv ("ANASTASIS_EXTERNAL_REDUCER"));
   1794 
   1795   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1796               "Starting external reducer with action '%s' and argument '%s'\n",
   1797               action,
   1798               arg_str);
   1799 
   1800   red_cls->reducer_process
   1801     = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
   1802   GNUNET_assert (GNUNET_OK ==
   1803                  GNUNET_process_set_options (
   1804                    red_cls->reducer_process,
   1805                    GNUNET_process_option_inherit_rpipe (red_cls->reducer_stdin,
   1806                                                         STDIN_FILENO),
   1807                    GNUNET_process_option_inherit_rpipe (red_cls->reducer_stdout,
   1808                                                         STDOUT_FILENO)));
   1809   if (GNUNET_OK !=
   1810       GNUNET_process_run_command_va (red_cls->reducer_process,
   1811                                      ext_reducer,
   1812                                      ext_reducer,
   1813                                      "-a",
   1814                                      arg_str,
   1815                                      action,
   1816                                      NULL))
   1817   {
   1818     GNUNET_break (0);
   1819     GNUNET_process_destroy (red_cls->reducer_process);
   1820     red_cls->reducer_process = NULL;
   1821     GNUNET_free (arg_str);
   1822     GNUNET_free (state_str);
   1823     cleanup_external_reducer (red_cls);
   1824     return NULL;
   1825   }
   1826   GNUNET_free (arg_str);
   1827 
   1828   /* Close pipe ends we don't use. */
   1829   GNUNET_assert (GNUNET_OK ==
   1830                  GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin,
   1831                                              GNUNET_DISK_PIPE_END_READ));
   1832   GNUNET_assert (GNUNET_OK ==
   1833                  GNUNET_DISK_pipe_close_end (red_cls->reducer_stdout,
   1834                                              GNUNET_DISK_PIPE_END_WRITE));
   1835 
   1836   sret = GNUNET_DISK_file_write_blocking (GNUNET_DISK_pipe_handle (
   1837                                             red_cls->reducer_stdin,
   1838                                             GNUNET_DISK_PIPE_END_WRITE),
   1839                                           state_str,
   1840                                           strlen (state_str));
   1841   GNUNET_free (state_str);
   1842   if (sret <= 0)
   1843   {
   1844     GNUNET_break (0);
   1845     cleanup_external_reducer (red_cls);
   1846     return NULL;
   1847   }
   1848 
   1849   GNUNET_assert (GNUNET_OK ==
   1850                  GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin,
   1851                                              GNUNET_DISK_PIPE_END_WRITE));
   1852 
   1853   red_cls->read_task = GNUNET_SCHEDULER_add_read_file (
   1854     GNUNET_TIME_UNIT_FOREVER_REL,
   1855     GNUNET_DISK_pipe_handle (
   1856       red_cls->reducer_stdout,
   1857       GNUNET_DISK_PIPE_END_READ),
   1858     external_reducer_read_cb,
   1859     red_cls);
   1860 
   1861   {
   1862     struct ANASTASIS_ReduxAction *ra
   1863       = GNUNET_new (struct ANASTASIS_ReduxAction);
   1864     ra->cleanup_cls = red_cls;
   1865     ra->cleanup = cleanup_external_reducer;
   1866     return ra;
   1867   }
   1868 }
   1869 
   1870 
   1871 struct ANASTASIS_ReduxAction *
   1872 ANASTASIS_redux_action (const json_t *state,
   1873                         const char *action,
   1874                         const json_t *arguments,
   1875                         ANASTASIS_ActionCallback cb,
   1876                         void *cb_cls)
   1877 {
   1878   struct Dispatcher
   1879   {
   1880     enum ANASTASIS_GenericState redux_state;
   1881     const char *redux_action;
   1882     DispatchHandler fun;
   1883   } dispatchers[] = {
   1884     {
   1885       ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING,
   1886       "select_continent",
   1887       &select_continent
   1888     },
   1889     /* Deprecated alias for "back" from that state, should be removed eventually. */
   1890     {
   1891       ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
   1892       "unselect_continent",
   1893       &unselect_continent
   1894     },
   1895     {
   1896       ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
   1897       "back",
   1898       &unselect_continent
   1899     },
   1900     {
   1901       ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
   1902       "select_country",
   1903       &select_country
   1904     },
   1905     {
   1906       ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
   1907       "select_continent",
   1908       &select_continent
   1909     },
   1910     {
   1911       ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
   1912       "enter_user_attributes",
   1913       &enter_user_attributes
   1914     },
   1915     {
   1916       ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
   1917       "add_provider",
   1918       &add_provider
   1919     },
   1920     {
   1921       ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
   1922       "back",
   1923       &ANASTASIS_back_generic_decrement_
   1924     },
   1925     { ANASTASIS_GENERIC_STATE_INVALID, NULL, NULL }
   1926   };
   1927   bool recovery_mode = false;
   1928   const char *s = json_string_value (json_object_get (state,
   1929                                                       "backup_state"));
   1930   enum ANASTASIS_GenericState gs;
   1931 
   1932   /* If requested, handle action with external reducer, used for testing. */
   1933   {
   1934     const char *ext_reducer = ANASTASIS_REDUX_probe_external_reducer ();
   1935     if (NULL != ext_reducer)
   1936       return redux_action_external (ext_reducer,
   1937                                     state,
   1938                                     action,
   1939                                     arguments,
   1940                                     cb,
   1941                                     cb_cls);
   1942   }
   1943 
   1944   if (NULL == s)
   1945   {
   1946     s = json_string_value (json_object_get (state,
   1947                                             "recovery_state"));
   1948     if (NULL == s)
   1949     {
   1950       GNUNET_break_op (0);
   1951       cb (cb_cls,
   1952           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1953           NULL);
   1954       return NULL;
   1955     }
   1956     recovery_mode = true;
   1957   }
   1958   gs = ANASTASIS_generic_state_from_string_ (s);
   1959   {
   1960     json_t *new_state;
   1961     struct ANASTASIS_ReduxAction *ret;
   1962 
   1963     new_state = json_deep_copy (state);
   1964     GNUNET_assert (NULL != new_state);
   1965     if (gs != ANASTASIS_GENERIC_STATE_INVALID)
   1966     {
   1967       for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
   1968       {
   1969         if ( (gs == dispatchers[i].redux_state) &&
   1970              (0 == strcmp (action,
   1971                            dispatchers[i].redux_action)) )
   1972         {
   1973           ret = dispatchers[i].fun (new_state,
   1974                                     arguments,
   1975                                     cb,
   1976                                     cb_cls);
   1977           json_decref (new_state);
   1978           return ret;
   1979         }
   1980       }
   1981     }
   1982     if (recovery_mode)
   1983     {
   1984       ret = ANASTASIS_recovery_action_ (new_state,
   1985                                         action,
   1986                                         arguments,
   1987                                         cb,
   1988                                         cb_cls);
   1989     }
   1990     else
   1991     {
   1992       ret = ANASTASIS_backup_action_ (new_state,
   1993                                       action,
   1994                                       arguments,
   1995                                       cb,
   1996                                       cb_cls);
   1997     }
   1998     json_decref (new_state);
   1999     return ret;
   2000   }
   2001 }
   2002 
   2003 
   2004 void
   2005 ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra)
   2006 {
   2007   ra->cleanup (ra->cleanup_cls);
   2008 }
   2009 
   2010 
   2011 json_t *
   2012 ANASTASIS_REDUX_load_continents_ ()
   2013 {
   2014   const json_t *countries;
   2015   json_t *continents;
   2016   const json_t *rc = ANASTASIS_redux_countries_init_ ();
   2017 
   2018   if (NULL == rc)
   2019   {
   2020     GNUNET_break (0);
   2021     return NULL;
   2022   }
   2023   countries = json_object_get (rc,
   2024                                "countries");
   2025   if (NULL == countries)
   2026   {
   2027     GNUNET_break (0);
   2028     return NULL;
   2029   }
   2030   continents = json_array ();
   2031   GNUNET_assert (NULL != continents);
   2032 
   2033   {
   2034     json_t *country;
   2035     size_t index;
   2036 
   2037     json_array_foreach (countries, index, country)
   2038     {
   2039       json_t *ex = NULL;
   2040       const json_t *continent;
   2041 
   2042       continent = json_object_get (country,
   2043                                    "continent");
   2044       if ( (NULL == continent) ||
   2045            (! json_is_string (continent)) )
   2046       {
   2047         GNUNET_break (0);
   2048         continue;
   2049       }
   2050       {
   2051         size_t inner_index;
   2052         json_t *inner_continent;
   2053 
   2054         json_array_foreach (continents, inner_index, inner_continent)
   2055         {
   2056           const json_t *name;
   2057 
   2058           name = json_object_get (inner_continent,
   2059                                   "name");
   2060           if (1 == json_equal (continent,
   2061                                name))
   2062           {
   2063             ex = inner_continent;
   2064             break;
   2065           }
   2066         }
   2067       }
   2068       if (NULL == ex)
   2069       {
   2070         ex = GNUNET_JSON_PACK (
   2071           GNUNET_JSON_pack_string ("name",
   2072                                    json_string_value (continent)));
   2073         GNUNET_assert (0 ==
   2074                        json_array_append_new (continents,
   2075                                               ex));
   2076       }
   2077 
   2078       {
   2079         json_t *i18n_continent;
   2080         json_t *name_ex;
   2081 
   2082         i18n_continent = json_object_get (country,
   2083                                           "continent_i18n");
   2084         name_ex = json_object_get (ex,
   2085                                    "name_i18n");
   2086         if (NULL != i18n_continent)
   2087         {
   2088           const char *lang;
   2089           json_t *trans;
   2090 
   2091           json_object_foreach (i18n_continent, lang, trans)
   2092           {
   2093             if (NULL == name_ex)
   2094             {
   2095               name_ex = json_object ();
   2096               GNUNET_assert (NULL != name_ex);
   2097               GNUNET_assert (0 ==
   2098                              json_object_set_new (ex,
   2099                                                   "name_i18n",
   2100                                                   name_ex));
   2101             }
   2102             if (NULL == json_object_get (name_ex,
   2103                                          lang))
   2104             {
   2105               GNUNET_assert (0 ==
   2106                              json_object_set (name_ex,
   2107                                               lang,
   2108                                               trans));
   2109             }
   2110           }
   2111         }
   2112       }
   2113     }
   2114   }
   2115   return GNUNET_JSON_PACK (
   2116     GNUNET_JSON_pack_array_steal ("continents",
   2117                                   continents));
   2118 }
   2119 
   2120 
   2121 /**
   2122  * Lookup @a provider_salt of @a provider_url in @a state.
   2123  *
   2124  * @param state the state to inspect
   2125  * @param provider_url provider to look into
   2126  * @param[out] provider_salt value to extract
   2127  * @return #GNUNET_OK on success
   2128  */
   2129 enum GNUNET_GenericReturnValue
   2130 ANASTASIS_reducer_lookup_salt (
   2131   const json_t *state,
   2132   const char *provider_url,
   2133   struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt)
   2134 {
   2135   const json_t *aps;
   2136   const json_t *cfg;
   2137   uint32_t http_status = 0;
   2138   const char *status;
   2139   bool no_salt;
   2140   struct GNUNET_JSON_Specification spec[] = {
   2141     GNUNET_JSON_spec_string ("status",
   2142                              &status),
   2143     GNUNET_JSON_spec_mark_optional (
   2144       GNUNET_JSON_spec_uint32 ("http_status",
   2145                                &http_status),
   2146       NULL),
   2147     GNUNET_JSON_spec_mark_optional (
   2148       GNUNET_JSON_spec_fixed_auto ("provider_salt",
   2149                                    provider_salt),
   2150       &no_salt),
   2151     GNUNET_JSON_spec_end ()
   2152   };
   2153 
   2154   aps = json_object_get (state,
   2155                          "authentication_providers");
   2156   if (NULL == aps)
   2157   {
   2158     GNUNET_break (0);
   2159     return GNUNET_SYSERR;
   2160   }
   2161   cfg = json_object_get (aps,
   2162                          provider_url);
   2163   if (NULL == cfg)
   2164   {
   2165     GNUNET_break (0);
   2166     return GNUNET_SYSERR;
   2167   }
   2168   if (GNUNET_OK !=
   2169       GNUNET_JSON_parse (cfg,
   2170                          spec,
   2171                          NULL, NULL))
   2172   {
   2173     /* provider not working */
   2174     GNUNET_break_op (0);
   2175     return GNUNET_NO;
   2176   }
   2177   if (0 == strcmp (status,
   2178                    "disabled"))
   2179     return GNUNET_NO;
   2180   if (no_salt)
   2181     return GNUNET_NO;
   2182   return GNUNET_OK;
   2183 }