anastasis

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

anastasis_api_redux.c (59671B)


      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_OS_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_OS_process_wait_status (red_cls->reducer_process,
   1659                                            &type,
   1660                                            &code);
   1661 
   1662     GNUNET_assert (GNUNET_SYSERR != pwret);
   1663     if (GNUNET_NO == pwret)
   1664     {
   1665       GNUNET_assert (0 ==
   1666                      GNUNET_OS_process_kill (red_cls->reducer_process,
   1667                                              SIGTERM));
   1668       GNUNET_assert (GNUNET_SYSERR !=
   1669                      GNUNET_OS_process_wait (red_cls->reducer_process));
   1670     }
   1671 
   1672     GNUNET_OS_process_destroy (red_cls->reducer_process);
   1673     red_cls->reducer_process = NULL;
   1674   }
   1675 
   1676   GNUNET_free (red_cls);
   1677 }
   1678 
   1679 
   1680 /**
   1681  * Task called when
   1682  *
   1683  * @param cls closure, a 'struct ExternalReducerCls *'
   1684  */
   1685 static void
   1686 external_reducer_read_cb (void *cls)
   1687 {
   1688   struct ExternalReducerCls *red_cls = cls;
   1689   ssize_t sret;
   1690   char buf[256];
   1691 
   1692   red_cls->read_task = NULL;
   1693 
   1694   sret = GNUNET_DISK_file_read (GNUNET_DISK_pipe_handle (
   1695                                   red_cls->reducer_stdout,
   1696                                   GNUNET_DISK_PIPE_END_READ),
   1697                                 buf,
   1698                                 256);
   1699   if (sret < 0)
   1700   {
   1701     GNUNET_break (0);
   1702     red_cls->action_cb (red_cls->action_cb_cls,
   1703                         TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR,
   1704                         NULL);
   1705     cleanup_external_reducer (red_cls);
   1706     return;
   1707   }
   1708   else if (0 == sret)
   1709   {
   1710     char *str = GNUNET_buffer_reap_str (&red_cls->read_buffer);
   1711     json_t *json;
   1712 
   1713     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1714                 "Got external reducer response: '%s'\n",
   1715                 str);
   1716 
   1717     json = json_loads (str, 0, NULL);
   1718 
   1719     if (NULL == json)
   1720     {
   1721       GNUNET_break (0);
   1722       red_cls->action_cb (red_cls->action_cb_cls,
   1723                           TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR,
   1724                           NULL);
   1725       cleanup_external_reducer (red_cls);
   1726       return;
   1727     }
   1728 
   1729     {
   1730       enum TALER_ErrorCode ec;
   1731       ec = json_integer_value (json_object_get (json, "code"));
   1732 
   1733       red_cls->action_cb (red_cls->action_cb_cls,
   1734                           ec,
   1735                           json);
   1736     }
   1737     cleanup_external_reducer (red_cls);
   1738     return;
   1739   }
   1740   else
   1741   {
   1742     GNUNET_buffer_write (&red_cls->read_buffer,
   1743                          buf,
   1744                          sret);
   1745 
   1746     red_cls->read_task = GNUNET_SCHEDULER_add_read_file (
   1747       GNUNET_TIME_UNIT_FOREVER_REL,
   1748       GNUNET_DISK_pipe_handle (
   1749         red_cls->reducer_stdout,
   1750         GNUNET_DISK_PIPE_END_READ),
   1751       external_reducer_read_cb,
   1752       red_cls);
   1753   }
   1754 }
   1755 
   1756 
   1757 /**
   1758  * Handle an action using an external reducer, i.e.
   1759  * by shelling out to another process.
   1760  */
   1761 static struct ANASTASIS_ReduxAction *
   1762 redux_action_external (const char *ext_reducer,
   1763                        const json_t *state,
   1764                        const char *action,
   1765                        const json_t *arguments,
   1766                        ANASTASIS_ActionCallback cb,
   1767                        void *cb_cls)
   1768 {
   1769   char *arg_str;
   1770   char *state_str = json_dumps (state, JSON_COMPACT);
   1771   ssize_t sret;
   1772   struct ExternalReducerCls *red_cls = GNUNET_new (struct ExternalReducerCls);
   1773 
   1774   if (NULL == arguments)
   1775     arg_str = GNUNET_strdup ("{}");
   1776   else
   1777     arg_str = json_dumps (arguments, JSON_COMPACT);
   1778 
   1779   red_cls->action_cb = cb;
   1780   red_cls->action_cb_cls = cb_cls;
   1781 
   1782   GNUNET_assert (NULL != (red_cls->reducer_stdin = GNUNET_DISK_pipe (
   1783                             GNUNET_DISK_PF_NONE)));
   1784   GNUNET_assert (NULL != (red_cls->reducer_stdout = GNUNET_DISK_pipe (
   1785                             GNUNET_DISK_PF_NONE)));
   1786 
   1787   /* By the time we're here, this variable should be unset, because
   1788      otherwise using anastasis-reducer as the external reducer
   1789      will lead to infinite recursion. */
   1790   GNUNET_assert (NULL == getenv ("ANASTASIS_EXTERNAL_REDUCER"));
   1791 
   1792   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1793               "Starting external reducer with action '%s' and argument '%s'\n",
   1794               action,
   1795               arg_str);
   1796 
   1797   red_cls->reducer_process = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
   1798                                                       red_cls->reducer_stdin,
   1799                                                       red_cls->reducer_stdout,
   1800                                                       NULL,
   1801                                                       ext_reducer,
   1802                                                       ext_reducer,
   1803                                                       "-a",
   1804                                                       arg_str,
   1805                                                       action,
   1806                                                       NULL);
   1807 
   1808   GNUNET_free (arg_str);
   1809 
   1810   if (NULL == red_cls->reducer_process)
   1811   {
   1812     GNUNET_break (0);
   1813     GNUNET_free (state_str);
   1814     cleanup_external_reducer (red_cls);
   1815     return NULL;
   1816   }
   1817 
   1818   /* Close pipe ends we don't use. */
   1819   GNUNET_assert (GNUNET_OK ==
   1820                  GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin,
   1821                                              GNUNET_DISK_PIPE_END_READ));
   1822   GNUNET_assert (GNUNET_OK ==
   1823                  GNUNET_DISK_pipe_close_end (red_cls->reducer_stdout,
   1824                                              GNUNET_DISK_PIPE_END_WRITE));
   1825 
   1826   sret = GNUNET_DISK_file_write_blocking (GNUNET_DISK_pipe_handle (
   1827                                             red_cls->reducer_stdin,
   1828                                             GNUNET_DISK_PIPE_END_WRITE),
   1829                                           state_str,
   1830                                           strlen (state_str));
   1831   GNUNET_free (state_str);
   1832   if (sret <= 0)
   1833   {
   1834     GNUNET_break (0);
   1835     cleanup_external_reducer (red_cls);
   1836     return NULL;
   1837   }
   1838 
   1839   GNUNET_assert (GNUNET_OK ==
   1840                  GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin,
   1841                                              GNUNET_DISK_PIPE_END_WRITE));
   1842 
   1843   red_cls->read_task = GNUNET_SCHEDULER_add_read_file (
   1844     GNUNET_TIME_UNIT_FOREVER_REL,
   1845     GNUNET_DISK_pipe_handle (
   1846       red_cls->reducer_stdout,
   1847       GNUNET_DISK_PIPE_END_READ),
   1848     external_reducer_read_cb,
   1849     red_cls);
   1850 
   1851   {
   1852     struct ANASTASIS_ReduxAction *ra = GNUNET_new (struct
   1853                                                    ANASTASIS_ReduxAction);
   1854     ra->cleanup_cls = red_cls;
   1855     ra->cleanup = cleanup_external_reducer;
   1856     return ra;
   1857   }
   1858 }
   1859 
   1860 
   1861 struct ANASTASIS_ReduxAction *
   1862 ANASTASIS_redux_action (const json_t *state,
   1863                         const char *action,
   1864                         const json_t *arguments,
   1865                         ANASTASIS_ActionCallback cb,
   1866                         void *cb_cls)
   1867 {
   1868   struct Dispatcher
   1869   {
   1870     enum ANASTASIS_GenericState redux_state;
   1871     const char *redux_action;
   1872     DispatchHandler fun;
   1873   } dispatchers[] = {
   1874     {
   1875       ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING,
   1876       "select_continent",
   1877       &select_continent
   1878     },
   1879     /* Deprecated alias for "back" from that state, should be removed eventually. */
   1880     {
   1881       ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
   1882       "unselect_continent",
   1883       &unselect_continent
   1884     },
   1885     {
   1886       ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
   1887       "back",
   1888       &unselect_continent
   1889     },
   1890     {
   1891       ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
   1892       "select_country",
   1893       &select_country
   1894     },
   1895     {
   1896       ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
   1897       "select_continent",
   1898       &select_continent
   1899     },
   1900     {
   1901       ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
   1902       "enter_user_attributes",
   1903       &enter_user_attributes
   1904     },
   1905     {
   1906       ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
   1907       "add_provider",
   1908       &add_provider
   1909     },
   1910     {
   1911       ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
   1912       "back",
   1913       &ANASTASIS_back_generic_decrement_
   1914     },
   1915     { ANASTASIS_GENERIC_STATE_INVALID, NULL, NULL }
   1916   };
   1917   bool recovery_mode = false;
   1918   const char *s = json_string_value (json_object_get (state,
   1919                                                       "backup_state"));
   1920   enum ANASTASIS_GenericState gs;
   1921 
   1922   /* If requested, handle action with external reducer, used for testing. */
   1923   {
   1924     const char *ext_reducer = ANASTASIS_REDUX_probe_external_reducer ();
   1925     if (NULL != ext_reducer)
   1926       return redux_action_external (ext_reducer,
   1927                                     state,
   1928                                     action,
   1929                                     arguments,
   1930                                     cb,
   1931                                     cb_cls);
   1932   }
   1933 
   1934   if (NULL == s)
   1935   {
   1936     s = json_string_value (json_object_get (state,
   1937                                             "recovery_state"));
   1938     if (NULL == s)
   1939     {
   1940       GNUNET_break_op (0);
   1941       cb (cb_cls,
   1942           TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1943           NULL);
   1944       return NULL;
   1945     }
   1946     recovery_mode = true;
   1947   }
   1948   gs = ANASTASIS_generic_state_from_string_ (s);
   1949   {
   1950     json_t *new_state;
   1951     struct ANASTASIS_ReduxAction *ret;
   1952 
   1953     new_state = json_deep_copy (state);
   1954     GNUNET_assert (NULL != new_state);
   1955     if (gs != ANASTASIS_GENERIC_STATE_INVALID)
   1956     {
   1957       for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
   1958       {
   1959         if ( (gs == dispatchers[i].redux_state) &&
   1960              (0 == strcmp (action,
   1961                            dispatchers[i].redux_action)) )
   1962         {
   1963           ret = dispatchers[i].fun (new_state,
   1964                                     arguments,
   1965                                     cb,
   1966                                     cb_cls);
   1967           json_decref (new_state);
   1968           return ret;
   1969         }
   1970       }
   1971     }
   1972     if (recovery_mode)
   1973     {
   1974       ret = ANASTASIS_recovery_action_ (new_state,
   1975                                         action,
   1976                                         arguments,
   1977                                         cb,
   1978                                         cb_cls);
   1979     }
   1980     else
   1981     {
   1982       ret = ANASTASIS_backup_action_ (new_state,
   1983                                       action,
   1984                                       arguments,
   1985                                       cb,
   1986                                       cb_cls);
   1987     }
   1988     json_decref (new_state);
   1989     return ret;
   1990   }
   1991 }
   1992 
   1993 
   1994 void
   1995 ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra)
   1996 {
   1997   ra->cleanup (ra->cleanup_cls);
   1998 }
   1999 
   2000 
   2001 json_t *
   2002 ANASTASIS_REDUX_load_continents_ ()
   2003 {
   2004   const json_t *countries;
   2005   json_t *continents;
   2006   const json_t *rc = ANASTASIS_redux_countries_init_ ();
   2007 
   2008   if (NULL == rc)
   2009   {
   2010     GNUNET_break (0);
   2011     return NULL;
   2012   }
   2013   countries = json_object_get (rc,
   2014                                "countries");
   2015   if (NULL == countries)
   2016   {
   2017     GNUNET_break (0);
   2018     return NULL;
   2019   }
   2020   continents = json_array ();
   2021   GNUNET_assert (NULL != continents);
   2022 
   2023   {
   2024     json_t *country;
   2025     size_t index;
   2026 
   2027     json_array_foreach (countries, index, country)
   2028     {
   2029       json_t *ex = NULL;
   2030       const json_t *continent;
   2031 
   2032       continent = json_object_get (country,
   2033                                    "continent");
   2034       if ( (NULL == continent) ||
   2035            (! json_is_string (continent)) )
   2036       {
   2037         GNUNET_break (0);
   2038         continue;
   2039       }
   2040       {
   2041         size_t inner_index;
   2042         json_t *inner_continent;
   2043 
   2044         json_array_foreach (continents, inner_index, inner_continent)
   2045         {
   2046           const json_t *name;
   2047 
   2048           name = json_object_get (inner_continent,
   2049                                   "name");
   2050           if (1 == json_equal (continent,
   2051                                name))
   2052           {
   2053             ex = inner_continent;
   2054             break;
   2055           }
   2056         }
   2057       }
   2058       if (NULL == ex)
   2059       {
   2060         ex = GNUNET_JSON_PACK (
   2061           GNUNET_JSON_pack_string ("name",
   2062                                    json_string_value (continent)));
   2063         GNUNET_assert (0 ==
   2064                        json_array_append_new (continents,
   2065                                               ex));
   2066       }
   2067 
   2068       {
   2069         json_t *i18n_continent;
   2070         json_t *name_ex;
   2071 
   2072         i18n_continent = json_object_get (country,
   2073                                           "continent_i18n");
   2074         name_ex = json_object_get (ex,
   2075                                    "name_i18n");
   2076         if (NULL != i18n_continent)
   2077         {
   2078           const char *lang;
   2079           json_t *trans;
   2080 
   2081           json_object_foreach (i18n_continent, lang, trans)
   2082           {
   2083             if (NULL == name_ex)
   2084             {
   2085               name_ex = json_object ();
   2086               GNUNET_assert (NULL != name_ex);
   2087               GNUNET_assert (0 ==
   2088                              json_object_set_new (ex,
   2089                                                   "name_i18n",
   2090                                                   name_ex));
   2091             }
   2092             if (NULL == json_object_get (name_ex,
   2093                                          lang))
   2094             {
   2095               GNUNET_assert (0 ==
   2096                              json_object_set (name_ex,
   2097                                               lang,
   2098                                               trans));
   2099             }
   2100           }
   2101         }
   2102       }
   2103     }
   2104   }
   2105   return GNUNET_JSON_PACK (
   2106     GNUNET_JSON_pack_array_steal ("continents",
   2107                                   continents));
   2108 }
   2109 
   2110 
   2111 /**
   2112  * Lookup @a provider_salt of @a provider_url in @a state.
   2113  *
   2114  * @param state the state to inspect
   2115  * @param provider_url provider to look into
   2116  * @param[out] provider_salt value to extract
   2117  * @return #GNUNET_OK on success
   2118  */
   2119 enum GNUNET_GenericReturnValue
   2120 ANASTASIS_reducer_lookup_salt (
   2121   const json_t *state,
   2122   const char *provider_url,
   2123   struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt)
   2124 {
   2125   const json_t *aps;
   2126   const json_t *cfg;
   2127   uint32_t http_status = 0;
   2128   const char *status;
   2129   bool no_salt;
   2130   struct GNUNET_JSON_Specification spec[] = {
   2131     GNUNET_JSON_spec_string ("status",
   2132                              &status),
   2133     GNUNET_JSON_spec_mark_optional (
   2134       GNUNET_JSON_spec_uint32 ("http_status",
   2135                                &http_status),
   2136       NULL),
   2137     GNUNET_JSON_spec_mark_optional (
   2138       GNUNET_JSON_spec_fixed_auto ("provider_salt",
   2139                                    provider_salt),
   2140       &no_salt),
   2141     GNUNET_JSON_spec_end ()
   2142   };
   2143 
   2144   aps = json_object_get (state,
   2145                          "authentication_providers");
   2146   if (NULL == aps)
   2147   {
   2148     GNUNET_break (0);
   2149     return GNUNET_SYSERR;
   2150   }
   2151   cfg = json_object_get (aps,
   2152                          provider_url);
   2153   if (NULL == cfg)
   2154   {
   2155     GNUNET_break (0);
   2156     return GNUNET_SYSERR;
   2157   }
   2158   if (GNUNET_OK !=
   2159       GNUNET_JSON_parse (cfg,
   2160                          spec,
   2161                          NULL, NULL))
   2162   {
   2163     /* provider not working */
   2164     GNUNET_break_op (0);
   2165     return GNUNET_NO;
   2166   }
   2167   if (0 == strcmp (status,
   2168                    "disabled"))
   2169     return GNUNET_NO;
   2170   if (no_salt)
   2171     return GNUNET_NO;
   2172   return GNUNET_OK;
   2173 }