anastasis

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

anastasis_api_recovery_redux.c (66299B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020, 2021 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_recovery_redux.c
     18  * @brief anastasis reducer recovery 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 "anastasis_api_redux.h"
     28 #include "anastasis_api_redux_state.h"
     29 
     30 
     31 #define GENERATE_STRING(STRING) #STRING,
     32 static const char *recovery_strings[] = {
     33   ANASTASIS_RECOVERY_STATES (GENERATE_STRING)
     34 };
     35 #undef GENERATE_STRING
     36 
     37 
     38 enum ANASTASIS_RecoveryState
     39 ANASTASIS_recovery_state_from_string_ (const char *state_string)
     40 {
     41   for (enum ANASTASIS_RecoveryState i = 0;
     42        i < sizeof (recovery_strings) / sizeof(*recovery_strings);
     43        i++)
     44     if (0 == strcmp (state_string,
     45                      recovery_strings[i]))
     46       return i;
     47   return ANASTASIS_RECOVERY_STATE_INVALID;
     48 }
     49 
     50 
     51 const char *
     52 ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs)
     53 {
     54   if ( (rs < 0) ||
     55        (rs >= sizeof (recovery_strings) / sizeof(*recovery_strings)) )
     56   {
     57     GNUNET_break_op (0);
     58     return NULL;
     59   }
     60   return recovery_strings[rs];
     61 }
     62 
     63 
     64 /**
     65  * Transition @a rs to @a new_recovery_state.
     66  *
     67  * @param[in,out] rs state to transition
     68  * @param new_recovery_state state to transition to
     69  */
     70 static void
     71 set_state (struct ANASTASIS_ReduxState *rs,
     72            enum ANASTASIS_RecoveryState new_recovery_state)
     73 {
     74   GNUNET_assert (ANASTASIS_RT_RECOVERY == rs->type);
     75   rs->details.recovery.state = new_recovery_state;
     76 }
     77 
     78 
     79 /**
     80  * Returns an initial ANASTASIS recovery state.
     81  *
     82  * @return NULL on failure
     83  */
     84 json_t *
     85 ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
     86 {
     87   json_t *initial_state;
     88   const char *external_reducer = ANASTASIS_REDUX_probe_external_reducer ();
     89 
     90   if (NULL != external_reducer)
     91   {
     92     int pipefd_stdout[2];
     93     pid_t pid = 0;
     94     int status;
     95     FILE *reducer_stdout;
     96 
     97     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     98                 "Using external reducer '%s' for recovery start status\n",
     99                 external_reducer);
    100 
    101     GNUNET_assert (0 == pipe (pipefd_stdout));
    102     pid = fork ();
    103     if (pid == 0)
    104     {
    105       GNUNET_assert (0 ==
    106                      close (pipefd_stdout[0]));
    107       GNUNET_assert (STDOUT_FILENO ==
    108                      dup2 (pipefd_stdout[1],
    109                            STDOUT_FILENO));
    110       execlp (external_reducer,
    111               external_reducer,
    112               "-r",
    113               NULL);
    114       GNUNET_assert (0);
    115     }
    116 
    117     GNUNET_assert (0 ==
    118                    close (pipefd_stdout[1]));
    119     reducer_stdout = fdopen (pipefd_stdout[0],
    120                              "r");
    121     {
    122       json_error_t err;
    123 
    124       initial_state = json_loadf (reducer_stdout,
    125                                   0,
    126                                   &err);
    127 
    128       if (NULL == initial_state)
    129       {
    130         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    131                     "External reducer did not output valid JSON: %s:%d:%d %s\n",
    132                     err.source,
    133                     err.line,
    134                     err.column,
    135                     err.text);
    136         GNUNET_assert (0 == fclose (reducer_stdout));
    137         waitpid (pid, &status, 0);
    138         return NULL;
    139       }
    140     }
    141 
    142     GNUNET_assert (NULL != initial_state);
    143     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    144                 "Waiting for external reducer to terminate.\n");
    145     GNUNET_assert (0 == fclose (reducer_stdout));
    146     reducer_stdout = NULL;
    147     waitpid (pid, &status, 0);
    148 
    149     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    150                 "External reducer finished with exit status '%d'\n",
    151                 status);
    152     return initial_state;
    153   }
    154 
    155   (void) cfg;
    156   initial_state = ANASTASIS_REDUX_load_continents_ ();
    157   if (NULL == initial_state)
    158     return NULL;
    159   GNUNET_assert (
    160     0 ==
    161     json_object_set_new (initial_state,
    162                          "reducer_type",
    163                          json_string ("recovery")));
    164   GNUNET_assert (
    165     0 ==
    166     json_object_set_new (
    167       initial_state,
    168       "recovery_state",
    169       json_string (
    170         ANASTASIS_recovery_state_to_string_ (
    171           ANASTASIS_RECOVERY_STATE_CONTINENT_SELECTING))));
    172   return initial_state;
    173 }
    174 
    175 
    176 /**
    177  * Context for a "select_challenge" operation.
    178  */
    179 struct SelectChallengeContext
    180 {
    181   /**
    182    * Handle we returned for cancellation of the operation.
    183    */
    184   struct ANASTASIS_ReduxAction ra;
    185 
    186   /**
    187    * UUID of the challenge selected by the user for solving.
    188    */
    189   struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
    190 
    191   /**
    192    * Which timeout was set for the operation?
    193    */
    194   struct GNUNET_TIME_Relative timeout;
    195 
    196   /**
    197    * Function to call with the next state.
    198    */
    199   ANASTASIS_ActionCallback cb;
    200 
    201   /**
    202    * Closure for @e cb.
    203    */
    204   void *cb_cls;
    205 
    206   /**
    207    * Our state; we own it.  The recovery operation we drive lives in
    208    * `rs->details.recovery.r`, so there is no second handle to keep in
    209    * sync here.
    210    */
    211   struct ANASTASIS_ReduxState *rs;
    212 
    213   /**
    214    * Our arguments (like answers to the challenge, if already provided).
    215    */
    216   json_t *args;
    217 
    218   /**
    219    * Task scheduled for delayed success reporting. Needed to make
    220    * sure that the solved challenge was really the final result,
    221    * cancelled if the solved challenge resulted in the secret being
    222    * recovered.
    223    */
    224   struct GNUNET_SCHEDULER_Task *delayed_report;
    225 
    226   /**
    227    * Payment secret, if we are in the "pay" state.
    228    */
    229   struct ANASTASIS_PaymentSecretP ps;
    230 
    231   /**
    232    * Application asked us to only poll for existing
    233    * asynchronous challenges, and not to being a
    234    * new one.
    235    */
    236   bool poll_only;
    237 };
    238 
    239 
    240 /**
    241  * Cleanup a select challenge context.
    242  *
    243  * @param cls a `struct SelectChallengeContext *`
    244  */
    245 static void
    246 sctx_free (void *cls)
    247 {
    248   struct SelectChallengeContext *sctx = cls;
    249 
    250   /* Freeing the state also aborts the recovery operation, if one is
    251      still running. */
    252   ANASTASIS_REDUX_state_free_ (sctx->rs);
    253   json_decref (sctx->args);
    254   if (NULL != sctx->delayed_report)
    255   {
    256     GNUNET_SCHEDULER_cancel (sctx->delayed_report);
    257     sctx->delayed_report = NULL;
    258   }
    259   GNUNET_free (sctx);
    260 }
    261 
    262 
    263 /**
    264  * Hand our state back to the application and dispose of @a sctx.
    265  *
    266  * @param[in] sctx context to finish
    267  * @param ec error code to report alongside the state
    268  */
    269 static void
    270 sctx_return (struct SelectChallengeContext *sctx,
    271              enum TALER_ErrorCode ec)
    272 {
    273   struct ANASTASIS_ReduxState *rs = sctx->rs;
    274   ANASTASIS_ActionCallback cb = sctx->cb;
    275   void *cb_cls = sctx->cb_cls;
    276 
    277   sctx->rs = NULL;
    278   sctx_free (sctx);
    279   ANASTASIS_REDUX_return_ (rs,
    280                            cb,
    281                            cb_cls,
    282                            ec);
    283 }
    284 
    285 
    286 /**
    287  * Report an error to the application and dispose of @a sctx.
    288  *
    289  * @param[in] sctx context to finish
    290  * @param ec error to report
    291  * @param detail human-readable detail, may be NULL
    292  */
    293 static void
    294 sctx_fail (struct SelectChallengeContext *sctx,
    295            enum TALER_ErrorCode ec,
    296            const char *detail)
    297 {
    298   ANASTASIS_ActionCallback cb = sctx->cb;
    299   void *cb_cls = sctx->cb_cls;
    300 
    301   sctx_free (sctx);
    302   ANASTASIS_redux_fail_ (cb,
    303                          cb_cls,
    304                          ec,
    305                          detail);
    306 }
    307 
    308 
    309 /**
    310  * Translate a recovery failure into an error code and message.
    311  *
    312  * @param rc status to translate
    313  * @param[out] msg set to a human-readable, translatable message
    314  * @return the error code to report
    315  */
    316 static enum TALER_ErrorCode
    317 error_by_status (enum ANASTASIS_RecoveryStatus rc,
    318                  const char **msg)
    319 {
    320   enum TALER_ErrorCode ec = TALER_EC_INVALID;
    321 
    322   *msg = NULL;
    323   switch (rc)
    324   {
    325   case ANASTASIS_RS_SUCCESS:
    326     GNUNET_assert (0);
    327     break;
    328   case ANASTASIS_RS_POLICY_DOWNLOAD_FAILED:
    329     *msg = gettext_noop ("download failed due to unexpected network issue");
    330     ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED;
    331     break;
    332   case ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY:
    333     GNUNET_break (0);
    334     *msg = gettext_noop ("policy document returned was malformed");
    335     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    336     break;
    337   case ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG:
    338     GNUNET_break (0);
    339     *msg = gettext_noop ("policy document too large for client memory");
    340     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    341     break;
    342   case ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION:
    343     GNUNET_break (0);
    344     *msg = gettext_noop ("failed to decompress policy document");
    345     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    346     break;
    347   case ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON:
    348     GNUNET_break (0);
    349     *msg = gettext_noop ("policy document returned was not in JSON format");
    350     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    351     break;
    352   case ANASTASIS_RS_POLICY_MALFORMED_JSON:
    353     GNUNET_break (0);
    354     *msg = gettext_noop (
    355       "policy document returned was not in required JSON format");
    356     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    357     break;
    358   case ANASTASIS_RS_POLICY_SERVER_ERROR:
    359     *msg = gettext_noop ("Anastasis server reported transient internal error");
    360     ec = TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED;
    361     break;
    362   case ANASTASIS_RS_POLICY_GONE:
    363     *msg = gettext_noop ("policy document no longer exists");
    364     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
    365     break;
    366   case ANASTASIS_RS_POLICY_UNKNOWN:
    367     *msg = gettext_noop ("account unknown to Anastasis server");
    368     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
    369     break;
    370   case ANASTASIS_RS_POLICY_DECRYPTION_FAILED:
    371     /* The two causes are indistinguishable to us: a wrong key and a key
    372        derived differently both just fail to authenticate the ciphertext.
    373        Name both, or a user with a pre-flag-day backup retypes their
    374        identity attributes forever.  See NEWS on the key derivation
    375        change. */
    376     *msg = gettext_noop (
    377       "failed to decrypt policy document: either the identity attributes are not the ones used for the backup, or the backup was made before the key derivation change and cannot be recovered with this version");
    378     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    379     break;
    380   case ANASTASIS_RS_CORE_SECRET_RECOVERY_FAILED:
    381     *msg = gettext_noop (
    382       "failed to reassemble the core secret from the recovered key shares");
    383     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    384     break;
    385   }
    386   return ec;
    387 }
    388 
    389 
    390 /**
    391  * This function is called whenever the recovery process ends.
    392  * On success, the secret is returned in @a secret.
    393  *
    394  * @param cls handle for the callback
    395  * @param rc error code
    396  * @param secret contains the core secret which is passed to the user
    397  * @param secret_size defines the size of the core secret
    398  */
    399 static void
    400 core_secret_cb (void *cls,
    401                 enum ANASTASIS_RecoveryStatus rc,
    402                 const void *secret,
    403                 size_t secret_size)
    404 {
    405   struct SelectChallengeContext *sctx = cls;
    406   struct ANASTASIS_ReduxRecovery *rr = &sctx->rs->details.recovery;
    407 
    408   /* the library disposed of the operation before calling us */
    409   rr->r = NULL;
    410   if (ANASTASIS_RS_SUCCESS == rc)
    411   {
    412     json_t *jsecret;
    413 
    414     jsecret = json_loadb (secret,
    415                           secret_size,
    416                           JSON_REJECT_DUPLICATES,
    417                           NULL);
    418     if (NULL == jsecret)
    419     {
    420       sctx_fail (sctx,
    421                  TALER_EC_ANASTASIS_REDUCER_SECRET_MALFORMED,
    422                  NULL);
    423       return;
    424     }
    425     json_decref (rr->core_secret);
    426     rr->core_secret = jsecret;
    427     set_state (sctx->rs,
    428                ANASTASIS_RECOVERY_STATE_RECOVERY_FINISHED);
    429     sctx_return (sctx,
    430                  TALER_EC_NONE);
    431     return;
    432   }
    433   {
    434     const char *msg;
    435     enum TALER_ErrorCode ec;
    436 
    437     ec = error_by_status (rc,
    438                           &msg);
    439     sctx_fail (sctx,
    440                ec,
    441                msg);
    442   }
    443 }
    444 
    445 
    446 /**
    447  * A challenge was solved, but we are not yet finished.
    448  * Report to caller that the challenge was completed.
    449  *
    450  * @param cls a `struct SelectChallengeContext`
    451  */
    452 static void
    453 report_solved (void *cls)
    454 {
    455   struct SelectChallengeContext *sctx = cls;
    456 
    457   sctx->delayed_report = NULL;
    458   set_state (sctx->rs,
    459              ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    460   sctx_return (sctx,
    461                TALER_EC_NONE);
    462 }
    463 
    464 
    465 /**
    466  * Obtain the feedback entry for challenge @a uuid, reset to a fresh
    467  * variant of kind @a status.
    468  *
    469  * @param[in,out] sctx context whose state to update
    470  * @param uuid challenge the feedback is about
    471  * @param status which variant the caller is about to fill in
    472  * @return the entry to fill in
    473  */
    474 static struct ANASTASIS_ReduxFeedback *
    475 set_feedback (struct SelectChallengeContext *sctx,
    476               const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
    477               enum ANASTASIS_ReduxFeedbackStatus status)
    478 {
    479   struct ANASTASIS_ReduxFeedback *fb;
    480 
    481   fb = ANASTASIS_REDUX_feedback_get_ (&sctx->rs->details.recovery,
    482                                       uuid);
    483   fb->status = status;
    484   return fb;
    485 }
    486 
    487 
    488 /**
    489  * Defines a callback for the response status for a challenge start
    490  * operation.
    491  *
    492  * @param cls a `struct SelectChallengeContext *`
    493  * @param csr response details
    494  */
    495 static void
    496 start_feedback_cb (
    497   void *cls,
    498   const struct ANASTASIS_ChallengeStartResponse *csr)
    499 {
    500   struct SelectChallengeContext *sctx = cls;
    501   struct ANASTASIS_ReduxRecovery *rr = &sctx->rs->details.recovery;
    502   const struct ANASTASIS_ChallengeDetails *cd;
    503   struct ANASTASIS_ReduxFeedback *fb;
    504 
    505   cd = ANASTASIS_challenge_get_details (csr->challenge);
    506   switch (csr->cs)
    507   {
    508   case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED:
    509     fb = set_feedback (sctx,
    510                        &cd->uuid,
    511                        ANASTASIS_RFS_CODE_IN_FILE);
    512     fb->details.code_in_file.filename
    513       = GNUNET_strdup (csr->details.tan_filename);
    514     GNUNET_asprintf (&fb->display_hint,
    515                      _ ("Required TAN can be found in `%s'"),
    516                      csr->details.tan_filename);
    517     set_state (sctx->rs,
    518                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    519     sctx_return (sctx,
    520                  TALER_EC_NONE);
    521     return;
    522   case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED:
    523     fb = set_feedback (sctx,
    524                        &cd->uuid,
    525                        ANASTASIS_RFS_SEND_TO_ADDRESS);
    526     fb->details.send_to_address.address_hint
    527       = GNUNET_strdup (csr->details.tan_address_hint);
    528     GNUNET_asprintf (&fb->display_hint,
    529                      _ ("TAN code was sent to `%s'"),
    530                      csr->details.tan_address_hint);
    531     set_state (sctx->rs,
    532                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    533     sctx_return (sctx,
    534                  TALER_EC_NONE);
    535     return;
    536 
    537   case ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT:
    538     fb = set_feedback (sctx,
    539                        &cd->uuid,
    540                        ANASTASIS_RFS_SEND_TO_ADDRESS);
    541     GNUNET_asprintf (&fb->display_hint,
    542                      _ ("TAN code already sent."));
    543     set_state (sctx->rs,
    544                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    545     sctx_return (sctx,
    546                  TALER_EC_NONE);
    547     return;
    548 
    549   case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED:
    550     fb = set_feedback (sctx,
    551                        &cd->uuid,
    552                        ANASTASIS_RFS_TALER_PAYMENT);
    553     fb->details.taler_payment.taler_pay_uri
    554       = GNUNET_strdup (csr->details.payment_required.taler_pay_uri);
    555     ANASTASIS_REDUX_provider_url_set_ (&fb->details.taler_payment.provider,
    556                                        cd->provider_url);
    557     fb->details.taler_payment.payment_secret
    558       = csr->details.payment_required.payment_secret;
    559     GNUNET_asprintf (&fb->display_hint,
    560                      _ ("Taler payment to `%s' required"),
    561                      csr->details.payment_required.taler_pay_uri);
    562     /* Remember payment secret for later (once application claims it paid) */
    563     {
    564       struct ANASTASIS_ReduxChallengeInfo *ci;
    565 
    566       ci = ANASTASIS_REDUX_challenge_find_ (rr,
    567                                             &cd->uuid);
    568       GNUNET_assert (NULL != ci);
    569       ci->payment_secret = csr->details.payment_required.payment_secret;
    570       ci->have_payment_secret = true;
    571     }
    572     set_state (sctx->rs,
    573                ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING);
    574     sctx_return (sctx,
    575                  TALER_EC_NONE);
    576     return;
    577   case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE:
    578     fb = set_feedback (sctx,
    579                        &cd->uuid,
    580                        ANASTASIS_RFS_SERVER_FAILURE);
    581     fb->details.server_failure.http_status = csr->http_status;
    582     fb->details.server_failure.ec = csr->ec;
    583     set_state (sctx->rs,
    584                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    585     sctx_return (sctx,
    586                  csr->ec);
    587     return;
    588   case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN:
    589     fb = set_feedback (sctx,
    590                        &cd->uuid,
    591                        ANASTASIS_RFS_TRUTH_UNKNOWN);
    592     fb->details.server_failure.http_status = csr->http_status;
    593     fb->details.server_failure.ec = TALER_EC_ANASTASIS_TRUTH_UNKNOWN;
    594     set_state (sctx->rs,
    595                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    596     sctx_return (sctx,
    597                  TALER_EC_ANASTASIS_TRUTH_UNKNOWN);
    598     return;
    599   case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED:
    600     /* The provider turned this into an asynchronous challenge: remember
    601        the answer code so that a later "poll" can pick it up again. */
    602     ANASTASIS_challenge_set_async (
    603       csr->challenge,
    604       csr->details.bank_transfer_required.answer_code);
    605     fb = set_feedback (sctx,
    606                        &cd->uuid,
    607                        ANASTASIS_RFS_IBAN_INSTRUCTIONS);
    608     fb->details.iban_instructions.target_iban
    609       = GNUNET_strdup (csr->details.bank_transfer_required.target_iban);
    610     fb->details.iban_instructions.target_business_name
    611       = GNUNET_strdup (
    612           csr->details.bank_transfer_required.target_business_name);
    613     fb->details.iban_instructions.wire_transfer_subject
    614       = GNUNET_strdup (
    615           csr->details.bank_transfer_required.wire_transfer_subject);
    616     fb->details.iban_instructions.amount
    617       = csr->details.bank_transfer_required.amount;
    618     GNUNET_asprintf (&fb->display_hint,
    619                      _ ("Wire %s to %s (%s) with subject %s\n"),
    620                      TALER_amount2s (
    621                        &csr->details.bank_transfer_required.amount),
    622                      csr->details.bank_transfer_required.target_iban,
    623                      csr->details.bank_transfer_required.target_business_name,
    624                      csr->details.bank_transfer_required.wire_transfer_subject);
    625     rr->selected_challenge = cd->uuid;
    626     rr->have_selected_challenge = true;
    627     set_state (sctx->rs,
    628                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    629     sctx_return (sctx,
    630                  TALER_EC_NONE);
    631     return;
    632   }
    633   GNUNET_break (0);
    634   sctx_fail (sctx,
    635              TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    636              NULL);
    637 }
    638 
    639 
    640 /**
    641  * Defines a callback for the response status for a challenge answer
    642  * operation.
    643  *
    644  * @param cls a `struct SelectChallengeContext *`
    645  * @param csr response details
    646  */
    647 static void
    648 answer_feedback_cb (
    649   void *cls,
    650   const struct ANASTASIS_ChallengeAnswerResponse *csr)
    651 {
    652   struct SelectChallengeContext *sctx = cls;
    653   struct ANASTASIS_ReduxRecovery *rr = &sctx->rs->details.recovery;
    654   const struct ANASTASIS_ChallengeDetails *cd;
    655   struct ANASTASIS_ReduxFeedback *fb;
    656 
    657   cd = ANASTASIS_challenge_get_details (csr->challenge);
    658   switch (csr->cs)
    659   {
    660   case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED:
    661     (void) set_feedback (sctx,
    662                          &cd->uuid,
    663                          ANASTASIS_RFS_SOLVED);
    664     /* Delay reporting challenge success, as we MAY still
    665        also see a secret recovery success (and we can only
    666        call the callback once) */
    667     sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved,
    668                                                      sctx);
    669     return;
    670   case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER:
    671     fb = set_feedback (sctx,
    672                        &cd->uuid,
    673                        ANASTASIS_RFS_INCORRECT_ANSWER);
    674     fb->details.incorrect_answer.ec = csr->ec;
    675     set_state (sctx->rs,
    676                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    677     sctx_return (sctx,
    678                  TALER_EC_NONE);
    679     return;
    680   case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED:
    681     fb = set_feedback (sctx,
    682                        &cd->uuid,
    683                        ANASTASIS_RFS_TALER_PAYMENT);
    684     fb->details.taler_payment.taler_pay_uri
    685       = GNUNET_strdup (csr->details.payment_required.taler_pay_uri);
    686     ANASTASIS_REDUX_provider_url_set_ (&fb->details.taler_payment.provider,
    687                                        cd->provider_url);
    688     fb->details.taler_payment.payment_secret
    689       = csr->details.payment_required.payment_secret;
    690     GNUNET_asprintf (&fb->display_hint,
    691                      _ ("Taler payment to `%s' required"),
    692                      csr->details.payment_required.taler_pay_uri);
    693     /* Remember payment secret for later (once application claims it paid) */
    694     {
    695       struct ANASTASIS_ReduxChallengeInfo *ci;
    696 
    697       ci = ANASTASIS_REDUX_challenge_find_ (rr,
    698                                             &cd->uuid);
    699       GNUNET_assert (NULL != ci);
    700       ci->payment_secret = csr->details.payment_required.payment_secret;
    701       ci->have_payment_secret = true;
    702     }
    703     set_state (sctx->rs,
    704                ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING);
    705     sctx_return (sctx,
    706                  TALER_EC_NONE);
    707     return;
    708   case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE:
    709     fb = set_feedback (sctx,
    710                        &cd->uuid,
    711                        ANASTASIS_RFS_SERVER_FAILURE);
    712     fb->details.server_failure.http_status = csr->http_status;
    713     fb->details.server_failure.ec = csr->ec;
    714     set_state (sctx->rs,
    715                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    716     sctx_return (sctx,
    717                  csr->ec);
    718     return;
    719   case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN:
    720     fb = set_feedback (sctx,
    721                        &cd->uuid,
    722                        ANASTASIS_RFS_TRUTH_UNKNOWN);
    723     fb->details.server_failure.http_status = csr->http_status;
    724     fb->details.server_failure.ec = TALER_EC_ANASTASIS_TRUTH_UNKNOWN;
    725     set_state (sctx->rs,
    726                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    727     sctx_return (sctx,
    728                  TALER_EC_ANASTASIS_TRUTH_UNKNOWN);
    729     return;
    730   case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED:
    731     fb = set_feedback (sctx,
    732                        &cd->uuid,
    733                        ANASTASIS_RFS_RATE_LIMIT_EXCEEDED);
    734     fb->details.rate_limit_exceeded.ec
    735       = TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED;
    736     fb->details.rate_limit_exceeded.request_limit
    737       = csr->details.rate_limit_exceeded.request_limit;
    738     fb->details.rate_limit_exceeded.request_frequency
    739       = csr->details.rate_limit_exceeded.request_frequency;
    740     GNUNET_asprintf (
    741       &fb->display_hint,
    742       _ ("exceeded limit of %llu attempts in %s"),
    743       (unsigned long long) csr->details.rate_limit_exceeded.request_limit,
    744       GNUNET_TIME_relative2s (
    745         csr->details.rate_limit_exceeded.request_frequency,
    746         true));
    747     set_state (sctx->rs,
    748                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    749     sctx_return (sctx,
    750                  TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED);
    751     return;
    752   }
    753   GNUNET_break (0);
    754   sctx_fail (sctx,
    755              TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    756              NULL);
    757 }
    758 
    759 
    760 /**
    761  * Callback which passes back the recovery document and its possible
    762  * policies. Also passes back the version of the document for the user
    763  * to check.
    764  *
    765  * We find the selected challenge and try to answer it (or begin
    766  * the process).
    767  *
    768  * @param cls a `struct SelectChallengeContext *`
    769  * @param ri recovery information struct which contains the policies
    770  */
    771 static void
    772 solve_challenge_cb (void *cls,
    773                     const struct ANASTASIS_RecoveryInformation *ri)
    774 {
    775   struct SelectChallengeContext *sctx = cls;
    776   struct ANASTASIS_ReduxRecovery *rr = &sctx->rs->details.recovery;
    777   const struct ANASTASIS_PaymentSecretP *psp = NULL;
    778   struct ANASTASIS_PaymentSecretP ps;
    779   struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
    780   struct GNUNET_JSON_Specification tspec[] = {
    781     GNUNET_JSON_spec_mark_optional (
    782       GNUNET_JSON_spec_relative_time ("timeout",
    783                                       &timeout),
    784       NULL),
    785     GNUNET_JSON_spec_end ()
    786   };
    787   struct GNUNET_JSON_Specification pspec[] = {
    788     GNUNET_JSON_spec_fixed_auto ("payment_secret",
    789                                  &ps),
    790     GNUNET_JSON_spec_end ()
    791   };
    792   struct ANASTASIS_ReduxChallengeInfo *sci;
    793 
    794   if (NULL == ri)
    795   {
    796     GNUNET_break_op (0);
    797     sctx_fail (sctx,
    798                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
    799                "recovery information could not be deserialized");
    800     return;
    801   }
    802 
    803   if ( (NULL != sctx->args) &&
    804        (GNUNET_OK !=
    805         GNUNET_JSON_parse (sctx->args,
    806                            tspec,
    807                            NULL, NULL)) )
    808   {
    809     GNUNET_break_op (0);
    810     sctx_fail (sctx,
    811                TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
    812                "'timeout' malformed");
    813     return;
    814   }
    815 
    816   /* resume all async, unsolved challenges */
    817   {
    818     bool poll_started = false;
    819 
    820     for (unsigned int i = 0; i<ri->cs_len; i++)
    821     {
    822       struct ANASTASIS_Challenge *ci = ri->cs[i];
    823       const struct ANASTASIS_ChallengeDetails *cd;
    824 
    825       cd = ANASTASIS_challenge_get_details (ci);
    826       if (cd->solved ||
    827           (! cd->async) )
    828         continue;
    829       if (! cd->have_answer_pin)
    830       {
    831         GNUNET_break_op (0);
    832         sctx_fail (sctx,
    833                    TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
    834                    "async challenge 'answer-pin' not found");
    835         return;
    836       }
    837       if (GNUNET_OK !=
    838           ANASTASIS_challenge_answer2 (ci,
    839                                        psp,
    840                                        timeout,
    841                                        cd->answer_pin,
    842                                        &answer_feedback_cb,
    843                                        sctx))
    844       {
    845         sctx_fail (sctx,
    846                    TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    847                    "Failed to begin answering asynchronous challenge");
    848         return;
    849       }
    850       poll_started = true;
    851     }
    852 
    853     if (sctx->poll_only)
    854     {
    855       if (! poll_started)
    856       {
    857         GNUNET_break_op (0);
    858         ANASTASIS_redux_fail_ (sctx->cb,
    859                                sctx->cb_cls,
    860                                TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
    861                                "no challenge available for polling");
    862         return;
    863       }
    864       /* only polling, do not start new challenges */
    865       return;
    866     }
    867   } /* end resuming async challenges */
    868 
    869   /* Check if we got a payment_secret */
    870   sci = ANASTASIS_REDUX_challenge_find_ (rr,
    871                                          &sctx->uuid);
    872   if (NULL == sci)
    873   {
    874     GNUNET_break_op (0);
    875     sctx_fail (sctx,
    876                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
    877                "challenge not found");
    878     return;
    879   }
    880   if (NULL !=
    881       json_object_get (sctx->args,
    882                        "payment_secret"))
    883   {
    884     /* check if we got payment secret in args */
    885     if (GNUNET_OK !=
    886         GNUNET_JSON_parse (sctx->args,
    887                            pspec,
    888                            NULL, NULL))
    889     {
    890       GNUNET_break_op (0);
    891       sctx_fail (sctx,
    892                  TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
    893                  "'payment_secret' malformed");
    894       return;
    895     }
    896     psp = &ps;
    897   }
    898   else if (sci->have_payment_secret)
    899   {
    900     psp = &sci->payment_secret;
    901   }
    902 
    903   /* start or solve selected challenge */
    904   for (unsigned int i = 0; i<ri->cs_len; i++)
    905   {
    906     struct ANASTASIS_Challenge *ci = ri->cs[i];
    907     const struct ANASTASIS_ChallengeDetails *cd;
    908     int ret;
    909 
    910     cd = ANASTASIS_challenge_get_details (ci);
    911     if (cd->async)
    912       continue; /* handled above */
    913     if (0 !=
    914         GNUNET_memcmp (&sctx->uuid,
    915                        &cd->uuid))
    916       continue;
    917     if (cd->solved)
    918     {
    919       sctx_fail (sctx,
    920                  TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
    921                  "Selected challenge already solved");
    922       return;
    923     }
    924     if (0 == strcmp ("question",
    925                      cd->type))
    926     {
    927       /* security question, answer must be a string */
    928       const char *answer = json_string_value (json_object_get (sctx->args,
    929                                                                "answer"));
    930 
    931       if (NULL == answer)
    932       {
    933         sctx_fail (sctx,
    934                    TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
    935                    "'answer' missing");
    936         return;
    937       }
    938       /* persist answer, in case payment is required */
    939       GNUNET_free (sci->answer);
    940       sci->answer = GNUNET_strdup (answer);
    941       ret = ANASTASIS_challenge_answer (ci,
    942                                         psp,
    943                                         timeout,
    944                                         answer,
    945                                         &answer_feedback_cb,
    946                                         sctx);
    947     }
    948     else
    949     {
    950       /* Check if we got a PIN or a HASH */
    951       json_t *pin = json_object_get (sctx->args,
    952                                      "pin");
    953       json_t *hash = json_object_get (sctx->args,
    954                                       "hash");
    955       if (json_is_integer (pin))
    956       {
    957         uint64_t ianswer = json_integer_value (pin);
    958 
    959         /* persist answer, in case async processing
    960            happens via poll */
    961         ANASTASIS_challenge_set_answer_pin (ci,
    962                                             ianswer);
    963         ret = ANASTASIS_challenge_answer2 (ci,
    964                                            psp,
    965                                            timeout,
    966                                            ianswer,
    967                                            &answer_feedback_cb,
    968                                            sctx);
    969       }
    970       else if (NULL != hash)
    971       {
    972         struct GNUNET_HashCode hashed_answer;
    973         struct GNUNET_JSON_Specification spec[] = {
    974           GNUNET_JSON_spec_fixed_auto ("hash",
    975                                        &hashed_answer),
    976           GNUNET_JSON_spec_end ()
    977         };
    978 
    979         if (GNUNET_OK !=
    980             GNUNET_JSON_parse (sctx->args,
    981                                spec,
    982                                NULL, NULL))
    983         {
    984           sctx_fail (sctx,
    985                      TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
    986                      "'answer' malformed");
    987           return;
    988         }
    989         ret = ANASTASIS_challenge_answer3 (ci,
    990                                            psp,
    991                                            timeout,
    992                                            &hashed_answer,
    993                                            &answer_feedback_cb,
    994                                            sctx);
    995       }
    996       else
    997       {
    998         /* no answer provided */
    999         ret = ANASTASIS_challenge_start (ci,
   1000                                          psp,
   1001                                          &start_feedback_cb,
   1002                                          sctx);
   1003       }
   1004     }
   1005     if (GNUNET_OK != ret)
   1006     {
   1007       sctx_fail (sctx,
   1008                  TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1009                  "Failed to begin answering challenge");
   1010       return;
   1011     }
   1012     return;   /* await answer feedback */
   1013   }
   1014   sctx_fail (sctx,
   1015              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1016              "'uuid' not in list of challenges");
   1017 }
   1018 
   1019 
   1020 /**
   1021  * Callback which passes back the recovery document and its possible
   1022  * policies. Also passes back the version of the document for the user
   1023  * to check.
   1024  *
   1025  * We find the selected challenge and try to answer it (or begin
   1026  * the process).
   1027  *
   1028  * @param cls a `struct SelectChallengeContext *`
   1029  * @param ri recovery information struct which contains the policies
   1030  */
   1031 static void
   1032 pay_challenge_cb (void *cls,
   1033                   const struct ANASTASIS_RecoveryInformation *ri)
   1034 {
   1035   struct SelectChallengeContext *sctx = cls;
   1036   struct ANASTASIS_ReduxRecovery *rr = &sctx->rs->details.recovery;
   1037   struct ANASTASIS_ReduxChallengeInfo *sci;
   1038 
   1039   if (NULL == ri)
   1040   {
   1041     GNUNET_break_op (0);
   1042     sctx_fail (sctx,
   1043                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1044                "recovery information could not be deserialized");
   1045     return;
   1046   }
   1047 
   1048   sci = ANASTASIS_REDUX_challenge_find_ (rr,
   1049                                          &sctx->uuid);
   1050   if (NULL == sci)
   1051   {
   1052     GNUNET_break_op (0);
   1053     sctx_fail (sctx,
   1054                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1055                "challenge not found");
   1056     return;
   1057   }
   1058   /* persist payment, in case we need to run the request again */
   1059   sci->payment_secret = sctx->ps;
   1060   sci->have_payment_secret = true;
   1061 
   1062   for (unsigned int i = 0; i<ri->cs_len; i++)
   1063   {
   1064     struct ANASTASIS_Challenge *ci = ri->cs[i];
   1065     const struct ANASTASIS_ChallengeDetails *cd;
   1066     int ret;
   1067 
   1068     cd = ANASTASIS_challenge_get_details (ci);
   1069     if (0 !=
   1070         GNUNET_memcmp (&sctx->uuid,
   1071                        &cd->uuid))
   1072       continue;
   1073     if (cd->solved)
   1074     {
   1075       sctx_fail (sctx,
   1076                  TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1077                  "Selected challenge already solved");
   1078       return;
   1079     }
   1080 
   1081     if (0 == strcmp ("question",
   1082                      cd->type))
   1083     {
   1084       /* security question, answer must be a string and already ready */
   1085       if (NULL == sci->answer)
   1086       {
   1087         sctx_fail (sctx,
   1088                    TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1089                    "'answer' missing");
   1090         return;
   1091       }
   1092       ret = ANASTASIS_challenge_answer (ci,
   1093                                         &sctx->ps,
   1094                                         sctx->timeout,
   1095                                         sci->answer,
   1096                                         &answer_feedback_cb,
   1097                                         sctx);
   1098     }
   1099     else
   1100     {
   1101       ret = ANASTASIS_challenge_start (ci,
   1102                                        &sctx->ps,
   1103                                        &start_feedback_cb,
   1104                                        sctx);
   1105     }
   1106     if (GNUNET_OK != ret)
   1107     {
   1108       sctx_fail (sctx,
   1109                  TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1110                  "Failed to begin answering challenge");
   1111       return;
   1112     }
   1113     return;   /* await answer feedback */
   1114   }
   1115   sctx_fail (sctx,
   1116              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1117              "'uuid' not in list of challenges");
   1118 }
   1119 
   1120 
   1121 /**
   1122  * The user selected a challenge to be solved. Begin the solving
   1123  * process.
   1124  *
   1125  * @param[in] state we are in
   1126  * @param arguments our arguments with the solution
   1127  * @param cb functiont o call with the new state
   1128  * @param cb_cls closure for @a cb
   1129  * @return handle to cancel challenge selection step
   1130  */
   1131 static struct ANASTASIS_ReduxAction *
   1132 solve_challenge (struct ANASTASIS_ReduxState *rs,
   1133                  const json_t *arguments,
   1134                  ANASTASIS_ActionCallback cb,
   1135                  void *cb_cls)
   1136 {
   1137   struct ANASTASIS_ReduxRecovery *rr = &rs->details.recovery;
   1138   struct SelectChallengeContext *sctx;
   1139 
   1140   if (NULL == arguments)
   1141   {
   1142     ANASTASIS_REDUX_fail_ (rs,
   1143                            cb,
   1144                            cb_cls,
   1145                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1146                            "arguments missing");
   1147     return NULL;
   1148   }
   1149   if (! rr->have_selected_challenge)
   1150   {
   1151     ANASTASIS_REDUX_fail_ (rs,
   1152                            cb,
   1153                            cb_cls,
   1154                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1155                            "'selected_challenge_uuid' missing");
   1156     return NULL;
   1157   }
   1158   if (NULL == rr->r)
   1159   {
   1160     GNUNET_break_op (0);
   1161     ANASTASIS_REDUX_fail_ (rs,
   1162                            cb,
   1163                            cb_cls,
   1164                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1165                            "solve_challenge");
   1166     return NULL;
   1167   }
   1168   sctx = GNUNET_new (struct SelectChallengeContext);
   1169   sctx->uuid = rr->selected_challenge;
   1170   sctx->cb = cb;
   1171   sctx->cb_cls = cb_cls;
   1172   sctx->rs = rs;
   1173   sctx->args = json_incref ((json_t*) arguments);
   1174   if (GNUNET_OK !=
   1175       ANASTASIS_recovery_resume (rr->r,
   1176                                  &solve_challenge_cb,
   1177                                  sctx,
   1178                                  &core_secret_cb,
   1179                                  sctx))
   1180   {
   1181     GNUNET_break_op (0);
   1182     sctx_fail (sctx,
   1183                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1184                "'recovery_document' invalid");
   1185     return NULL;
   1186   }
   1187   sctx->ra.cleanup = &sctx_free;
   1188   sctx->ra.cleanup_cls = sctx;
   1189   return &sctx->ra;
   1190 }
   1191 
   1192 
   1193 /**
   1194  * The user asked for us to poll on pending
   1195  * asynchronous challenges to see if they have
   1196  * now completed / been satisfied.
   1197  *
   1198  * @param[in] state we are in
   1199  * @param arguments our arguments with the solution
   1200  * @param cb functiont o call with the new state
   1201  * @param cb_cls closure for @a cb
   1202  * @return handle to cancel challenge selection step
   1203  */
   1204 static struct ANASTASIS_ReduxAction *
   1205 poll_challenges (struct ANASTASIS_ReduxState *rs,
   1206                  const json_t *arguments,
   1207                  ANASTASIS_ActionCallback cb,
   1208                  void *cb_cls)
   1209 {
   1210   struct ANASTASIS_ReduxRecovery *rr = &rs->details.recovery;
   1211   struct SelectChallengeContext *sctx;
   1212 
   1213   if (NULL == rr->r)
   1214   {
   1215     GNUNET_break_op (0);
   1216     ANASTASIS_REDUX_fail_ (rs,
   1217                            cb,
   1218                            cb_cls,
   1219                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1220                            "poll_challenges");
   1221     return NULL;
   1222   }
   1223   sctx = GNUNET_new (struct SelectChallengeContext);
   1224   sctx->poll_only = true;
   1225   sctx->cb = cb;
   1226   sctx->cb_cls = cb_cls;
   1227   sctx->rs = rs;
   1228   sctx->args = json_incref ((json_t*) arguments);
   1229   if (GNUNET_OK !=
   1230       ANASTASIS_recovery_resume (rr->r,
   1231                                  &solve_challenge_cb,
   1232                                  sctx,
   1233                                  &core_secret_cb,
   1234                                  sctx))
   1235   {
   1236     GNUNET_break_op (0);
   1237     sctx_fail (sctx,
   1238                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1239                "'recovery_document' invalid");
   1240     return NULL;
   1241   }
   1242   sctx->ra.cleanup = &sctx_free;
   1243   sctx->ra.cleanup_cls = sctx;
   1244   return &sctx->ra;
   1245 }
   1246 
   1247 
   1248 /**
   1249  * The user selected a challenge to be solved. Handle the payment
   1250  * process.
   1251  *
   1252  * @param[in] state we are in
   1253  * @param arguments our arguments with the solution
   1254  * @param cb functiont o call with the new state
   1255  * @param cb_cls closure for @a cb
   1256  * @return handle to cancel challenge selection step
   1257  */
   1258 static struct ANASTASIS_ReduxAction *
   1259 pay_challenge (struct ANASTASIS_ReduxState *rs,
   1260                const json_t *arguments,
   1261                ANASTASIS_ActionCallback cb,
   1262                void *cb_cls)
   1263 {
   1264   struct ANASTASIS_ReduxRecovery *rr = &rs->details.recovery;
   1265   struct SelectChallengeContext *sctx;
   1266   struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
   1267   struct ANASTASIS_PaymentSecretP ps;
   1268   struct GNUNET_JSON_Specification aspec[] = {
   1269     GNUNET_JSON_spec_mark_optional (
   1270       GNUNET_JSON_spec_relative_time ("timeout",
   1271                                       &timeout),
   1272       NULL),
   1273     GNUNET_JSON_spec_fixed_auto ("payment_secret",
   1274                                  &ps),
   1275     GNUNET_JSON_spec_end ()
   1276   };
   1277 
   1278   if (NULL == arguments)
   1279   {
   1280     ANASTASIS_REDUX_fail_ (rs,
   1281                            cb,
   1282                            cb_cls,
   1283                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1284                            "arguments missing");
   1285     return NULL;
   1286   }
   1287   if (GNUNET_OK !=
   1288       GNUNET_JSON_parse (arguments,
   1289                          aspec,
   1290                          NULL, NULL))
   1291   {
   1292     ANASTASIS_REDUX_fail_ (rs,
   1293                            cb,
   1294                            cb_cls,
   1295                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1296                            "'payment_secret' missing");
   1297     return NULL;
   1298   }
   1299   if (! rr->have_selected_challenge)
   1300   {
   1301     ANASTASIS_REDUX_fail_ (rs,
   1302                            cb,
   1303                            cb_cls,
   1304                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1305                            "'selected_challenge_uuid' missing");
   1306     return NULL;
   1307   }
   1308   if (NULL == rr->r)
   1309   {
   1310     GNUNET_break_op (0);
   1311     ANASTASIS_REDUX_fail_ (rs,
   1312                            cb,
   1313                            cb_cls,
   1314                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1315                            "pay_challenge");
   1316     return NULL;
   1317   }
   1318   sctx = GNUNET_new (struct SelectChallengeContext);
   1319   sctx->uuid = rr->selected_challenge;
   1320   sctx->ps = ps;
   1321   sctx->timeout = timeout;
   1322   sctx->cb = cb;
   1323   sctx->cb_cls = cb_cls;
   1324   sctx->rs = rs;
   1325   sctx->args = json_incref ((json_t*) arguments);
   1326   if (GNUNET_OK !=
   1327       ANASTASIS_recovery_resume (rr->r,
   1328                                  &pay_challenge_cb,
   1329                                  sctx,
   1330                                  &core_secret_cb,
   1331                                  sctx))
   1332   {
   1333     GNUNET_break_op (0);
   1334     sctx_fail (sctx,
   1335                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1336                "'recovery_document' invalid");
   1337     return NULL;
   1338   }
   1339   sctx->ra.cleanup = &sctx_free;
   1340   sctx->ra.cleanup_cls = sctx;
   1341   return &sctx->ra;
   1342 }
   1343 
   1344 
   1345 /**
   1346  * Callback which passes back the recovery document and its possible
   1347  * policies. Also passes back the version of the document for the user
   1348  * to check.
   1349  *
   1350  * We find the selected challenge and try to answer it (or begin
   1351  * the process).
   1352  *
   1353  * @param cls a `struct SelectChallengeContext *`
   1354  * @param ri recovery information struct which contains the policies
   1355  */
   1356 static void
   1357 select_challenge_cb (void *cls,
   1358                      const struct ANASTASIS_RecoveryInformation *ri)
   1359 {
   1360   struct SelectChallengeContext *sctx = cls;
   1361   struct ANASTASIS_ReduxRecovery *rr = &sctx->rs->details.recovery;
   1362   const struct ANASTASIS_PaymentSecretP *psp = NULL;
   1363   struct ANASTASIS_PaymentSecretP ps;
   1364   struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
   1365   struct GNUNET_JSON_Specification tspec[] = {
   1366     GNUNET_JSON_spec_mark_optional (
   1367       GNUNET_JSON_spec_relative_time ("timeout",
   1368                                       &timeout),
   1369       NULL),
   1370     GNUNET_JSON_spec_end ()
   1371   };
   1372   struct GNUNET_JSON_Specification pspec[] = {
   1373     GNUNET_JSON_spec_fixed_auto ("payment_secret",
   1374                                  &ps),
   1375     GNUNET_JSON_spec_end ()
   1376   };
   1377 
   1378   if (NULL == ri)
   1379   {
   1380     GNUNET_break_op (0);
   1381     sctx_fail (sctx,
   1382                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1383                "recovery information could not be deserialized");
   1384     return;
   1385   }
   1386 
   1387   if (GNUNET_OK !=
   1388       GNUNET_JSON_parse (sctx->args,
   1389                          tspec,
   1390                          NULL, NULL))
   1391   {
   1392     GNUNET_break_op (0);
   1393     sctx_fail (sctx,
   1394                TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1395                "'timeout' malformed");
   1396     return;
   1397   }
   1398 
   1399   /* NOTE: do we need both ways to pass payment secrets? */
   1400   if (NULL !=
   1401       json_object_get (sctx->args,
   1402                        "payment_secret"))
   1403   {
   1404     /* check if we got payment secret in args */
   1405     if (GNUNET_OK !=
   1406         GNUNET_JSON_parse (sctx->args,
   1407                            pspec,
   1408                            NULL, NULL))
   1409     {
   1410       GNUNET_break_op (0);
   1411       sctx_fail (sctx,
   1412                  TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1413                  "'payment_secret' malformed");
   1414       return;
   1415     }
   1416     psp = &ps;
   1417   }
   1418   else
   1419   {
   1420     /* Check if we got a payment_secret in state */
   1421     const struct ANASTASIS_ReduxChallengeInfo *sci;
   1422 
   1423     sci = ANASTASIS_REDUX_challenge_find_ (rr,
   1424                                            &sctx->uuid);
   1425     if (NULL == sci)
   1426     {
   1427       GNUNET_break_op (0);
   1428       sctx_fail (sctx,
   1429                  TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1430                  "challenge not found");
   1431       return;
   1432     }
   1433     if (sci->have_payment_secret)
   1434     {
   1435       ps = sci->payment_secret;
   1436       psp = &ps;
   1437     }
   1438   }
   1439 
   1440   for (unsigned int i = 0; i<ri->cs_len; i++)
   1441   {
   1442     struct ANASTASIS_Challenge *ci = ri->cs[i];
   1443     const struct ANASTASIS_ChallengeDetails *cd;
   1444     int ret;
   1445 
   1446     cd = ANASTASIS_challenge_get_details (ci);
   1447     if (0 !=
   1448         GNUNET_memcmp (&sctx->uuid,
   1449                        &cd->uuid))
   1450       continue;
   1451     if (cd->solved)
   1452     {
   1453       sctx_fail (sctx,
   1454                  TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1455                  "Selected challenge already solved");
   1456       return;
   1457     }
   1458     rr->selected_challenge = cd->uuid;
   1459     rr->have_selected_challenge = true;
   1460     if ( (0 == strcmp ("question",
   1461                        cd->type)) ||
   1462          (0 == strcmp ("totp",
   1463                        cd->type)) )
   1464     {
   1465       /* security question or TOTP:
   1466          immediately request user to answer it */
   1467       set_state (sctx->rs,
   1468                  ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
   1469       sctx_return (sctx,
   1470                    TALER_EC_NONE);
   1471       return;
   1472     }
   1473     /* trigger challenge */
   1474     if (cd->have_answer_pin)
   1475       ret = ANASTASIS_challenge_answer2 (ci,
   1476                                          psp,
   1477                                          timeout,
   1478                                          cd->answer_pin,
   1479                                          &answer_feedback_cb,
   1480                                          sctx);
   1481     else
   1482       ret = ANASTASIS_challenge_start (ci,
   1483                                        psp,
   1484                                        &start_feedback_cb,
   1485                                        sctx);
   1486     if (GNUNET_OK != ret)
   1487     {
   1488       sctx_fail (sctx,
   1489                  TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1490                  "Failed to begin answering challenge");
   1491       return;
   1492     }
   1493     return;   /* await answer feedback */
   1494   }
   1495   sctx_fail (sctx,
   1496              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1497              "'uuid' not in list of challenges");
   1498 }
   1499 
   1500 
   1501 /**
   1502  * The user selected a challenge to be solved. Begin the solving
   1503  * process.
   1504  *
   1505  * @param[in] state we are in
   1506  * @param arguments our arguments with the solution
   1507  * @param cb functiont o call with the new state
   1508  * @param cb_cls closure for @a cb
   1509  * @return handle to cancel challenge selection step
   1510  */
   1511 static struct ANASTASIS_ReduxAction *
   1512 select_challenge (struct ANASTASIS_ReduxState *rs,
   1513                   const json_t *arguments,
   1514                   ANASTASIS_ActionCallback cb,
   1515                   void *cb_cls)
   1516 {
   1517   struct ANASTASIS_ReduxRecovery *rr = &rs->details.recovery;
   1518   struct SelectChallengeContext *sctx;
   1519   struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
   1520   struct GNUNET_JSON_Specification spec[] = {
   1521     GNUNET_JSON_spec_fixed_auto ("uuid",
   1522                                  &uuid),
   1523     GNUNET_JSON_spec_end ()
   1524   };
   1525 
   1526   if (NULL == arguments)
   1527   {
   1528     ANASTASIS_REDUX_fail_ (rs,
   1529                            cb,
   1530                            cb_cls,
   1531                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1532                            "arguments missing");
   1533     return NULL;
   1534   }
   1535   if (GNUNET_OK !=
   1536       GNUNET_JSON_parse (arguments,
   1537                          spec,
   1538                          NULL, NULL))
   1539   {
   1540     ANASTASIS_REDUX_fail_ (rs,
   1541                            cb,
   1542                            cb_cls,
   1543                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1544                            "'uuid' missing");
   1545     return NULL;
   1546   }
   1547   if (NULL == rr->r)
   1548   {
   1549     GNUNET_break_op (0);
   1550     ANASTASIS_REDUX_fail_ (rs,
   1551                            cb,
   1552                            cb_cls,
   1553                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1554                            "select_challenge");
   1555     return NULL;
   1556   }
   1557   sctx = GNUNET_new (struct SelectChallengeContext);
   1558   sctx->uuid = uuid;
   1559   sctx->cb = cb;
   1560   sctx->cb_cls = cb_cls;
   1561   sctx->rs = rs;
   1562   sctx->args = json_incref ((json_t*) arguments);
   1563   if (GNUNET_OK !=
   1564       ANASTASIS_recovery_resume (rr->r,
   1565                                  &select_challenge_cb,
   1566                                  sctx,
   1567                                  &core_secret_cb,
   1568                                  sctx))
   1569   {
   1570     GNUNET_break_op (0);
   1571     sctx_fail (sctx,
   1572                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1573                "'recovery_document' invalid");
   1574     return NULL;
   1575   }
   1576   sctx->ra.cleanup = &sctx_free;
   1577   sctx->ra.cleanup_cls = sctx;
   1578   return &sctx->ra;
   1579 }
   1580 
   1581 
   1582 /**
   1583  * The user pressed "back" during challenge solving.
   1584  * Transition back to selecting another challenge.
   1585  *
   1586  * @param[in] state we are in
   1587  * @param arguments our arguments (unused)
   1588  * @param cb functiont o call with the new state
   1589  * @param cb_cls closure for @a cb
   1590  * @return NULL (synchronous operation)
   1591  */
   1592 static struct ANASTASIS_ReduxAction *
   1593 back_challenge_solving (struct ANASTASIS_ReduxState *rs,
   1594                         const json_t *arguments,
   1595                         ANASTASIS_ActionCallback cb,
   1596                         void *cb_cls)
   1597 {
   1598   (void) arguments;
   1599   rs->details.recovery.have_selected_challenge = false;
   1600   set_state (rs,
   1601              ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
   1602   ANASTASIS_REDUX_return_ (rs,
   1603                            cb,
   1604                            cb_cls,
   1605                            TALER_EC_NONE);
   1606   return NULL;
   1607 }
   1608 
   1609 
   1610 /**
   1611  * State for a "policy download" as part of a recovery operation.
   1612  */
   1613 struct PolicyDownloadEntry
   1614 {
   1615 
   1616   /**
   1617    * Redux action handle associated with this state.
   1618    */
   1619   struct ANASTASIS_ReduxAction ra;
   1620 
   1621   /**
   1622    * Backend we are querying.
   1623    */
   1624   char *backend_url;
   1625 
   1626   /**
   1627    * The /policy GET operation handle.
   1628    */
   1629   struct ANASTASIS_Recovery *recovery;
   1630 
   1631   /**
   1632    * Function to call with the result.
   1633    */
   1634   ANASTASIS_ActionCallback cb;
   1635 
   1636   /**
   1637    * Closure for @e cb.
   1638    */
   1639   void *cb_cls;
   1640 
   1641   /**
   1642    * State we are using; we own it.
   1643    */
   1644   struct ANASTASIS_ReduxState *rs;
   1645 
   1646 };
   1647 
   1648 
   1649 /**
   1650  * Free @a cls data structure.
   1651  *
   1652  * @param[in] cls data structure to free, must be a `struct PolicyDownloadEntry *`
   1653  */
   1654 static void
   1655 free_pd (void *cls)
   1656 {
   1657   struct PolicyDownloadEntry *pd = cls;
   1658 
   1659   if (NULL != pd->recovery)
   1660   {
   1661     ANASTASIS_recovery_abort (pd->recovery);
   1662     pd->recovery = NULL;
   1663   }
   1664   GNUNET_free (pd->backend_url);
   1665   ANASTASIS_REDUX_state_free_ (pd->rs);
   1666   GNUNET_free (pd);
   1667 }
   1668 
   1669 
   1670 /**
   1671  * We failed to download a policy. Show an error to the user and
   1672  * allow the user to specify alternative providers and/or policy
   1673  * versions.
   1674  *
   1675  * @param[in] pd state to fail with the policy download
   1676  * @param offline true of the reason to show is that all providers
   1677  *        were offline / did not return a salt to us
   1678  */
   1679 static void
   1680 return_no_policy (struct PolicyDownloadEntry *pd,
   1681                   bool offline)
   1682 {
   1683   enum TALER_ErrorCode ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED;
   1684   const char *detail = (offline)
   1685                        ? "could not contact provider (offline)"
   1686                        : "provider does not know this policy";
   1687   ANASTASIS_ActionCallback cb = pd->cb;
   1688   void *cb_cls = pd->cb_cls;
   1689 
   1690   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1691               "Provider offline!\n");
   1692   free_pd (pd);
   1693   ANASTASIS_redux_fail_ (cb,
   1694                          cb_cls,
   1695                          ec,
   1696                          detail);
   1697 }
   1698 
   1699 
   1700 /**
   1701  * Callback which passes back the recovery document and its possible
   1702  * policies. Also passes back the version of the document for the user
   1703  * to check.
   1704  *
   1705  * Once the first policy lookup succeeds, we update our state and
   1706  * cancel all of the others, passing the obtained recovery information
   1707  * back to the user.
   1708  *
   1709  * @param cls closure for the callback with a `struct PolicyDownloadEntry *`
   1710  * @param ri recovery information struct which contains the policies
   1711  */
   1712 static void
   1713 policy_lookup_cb (void *cls,
   1714                   const struct ANASTASIS_RecoveryInformation *ri)
   1715 {
   1716   struct PolicyDownloadEntry *pd = cls;
   1717   struct ANASTASIS_ReduxRecovery *rr = &pd->rs->details.recovery;
   1718   struct ANASTASIS_ReduxRecoveryInfo *nri;
   1719   struct ANASTASIS_ReduxState *rs;
   1720   ANASTASIS_ActionCallback cb;
   1721   void *cb_cls;
   1722 
   1723   if (NULL == ri)
   1724   {
   1725     /* Woopsie, failed hard. */
   1726     return_no_policy (pd,
   1727                       false);
   1728     return;
   1729   }
   1730   nri = GNUNET_new (struct ANASTASIS_ReduxRecoveryInfo);
   1731   nri->challenges_len = ri->cs_len;
   1732   nri->challenges = GNUNET_new_array (nri->challenges_len,
   1733                                       struct ANASTASIS_ReduxChallengeInfo);
   1734   for (unsigned int i = 0; i<ri->cs_len; i++)
   1735   {
   1736     struct ANASTASIS_ReduxChallengeInfo *ci = &nri->challenges[i];
   1737     const struct ANASTASIS_ChallengeDetails *cd;
   1738 
   1739     cd = ANASTASIS_challenge_get_details (ri->cs[i]);
   1740     ci->uuid = cd->uuid;
   1741     ci->type = GNUNET_strdup (cd->type);
   1742     ci->instructions = GNUNET_strdup (cd->instructions);
   1743   } /* end for all challenges */
   1744   nri->policies_len = ri->dps_len;
   1745   nri->policies = GNUNET_new_array (nri->policies_len,
   1746                                     struct ANASTASIS_ReduxRecoveryPolicy);
   1747   for (unsigned int i = 0; i<ri->dps_len; i++)
   1748   {
   1749     const struct ANASTASIS_DecryptionPolicy *dps = ri->dps[i];
   1750     struct ANASTASIS_ReduxRecoveryPolicy *p = &nri->policies[i];
   1751 
   1752     p->uuids_len = dps->challenges_length;
   1753     p->uuids = GNUNET_new_array (p->uuids_len,
   1754                                  struct ANASTASIS_CRYPTO_TruthUUIDP);
   1755     for (unsigned int j = 0; j<dps->challenges_length; j++)
   1756     {
   1757       const struct ANASTASIS_ChallengeDetails *cd;
   1758 
   1759       cd = ANASTASIS_challenge_get_details (dps->challenges[j]);
   1760       p->uuids[j] = cd->uuid;
   1761     }
   1762   } /* end for all policies */
   1763   if (NULL != ri->secret_name)
   1764     nri->secret_name = GNUNET_strdup (ri->secret_name);
   1765   ANASTASIS_REDUX_provider_url_set_ (&nri->provider_url,
   1766                                      pd->backend_url);
   1767   nri->version = ri->version;
   1768   ANASTASIS_REDUX_recovery_info_free_ (rr->ri);
   1769   rr->ri = nri;
   1770   /* The state takes over the operation: it is what gets serialized as
   1771      `recovery_document`, and it is aborted when the state is freed. */
   1772   GNUNET_assert (NULL == rr->r);
   1773   rr->r = pd->recovery;
   1774   pd->recovery = NULL;
   1775   set_state (pd->rs,
   1776              ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
   1777   rs = pd->rs;
   1778   cb = pd->cb;
   1779   cb_cls = pd->cb_cls;
   1780   pd->rs = NULL;
   1781   free_pd (pd);
   1782   ANASTASIS_REDUX_return_ (rs,
   1783                            cb,
   1784                            cb_cls,
   1785                            TALER_EC_NONE);
   1786 }
   1787 
   1788 
   1789 /**
   1790  * This function is called whenever the recovery process ends.
   1791  * In this case, that should not be possible as this callback
   1792  * is used before we even begin with the challenges. So if
   1793  * we are called, it is because of some fatal error.
   1794  *
   1795  * @param cls a `struct PolicyDownloadEntry`
   1796  * @param rc error code
   1797  * @param secret contains the core secret which is passed to the user
   1798  * @param secret_size defines the size of the core secret
   1799  */
   1800 static void
   1801 core_early_secret_cb (void *cls,
   1802                       enum ANASTASIS_RecoveryStatus rc,
   1803                       const void *secret,
   1804                       size_t secret_size)
   1805 {
   1806   struct PolicyDownloadEntry *pd = cls;
   1807   ANASTASIS_ActionCallback cb = pd->cb;
   1808   void *cb_cls = pd->cb_cls;
   1809   const char *msg;
   1810   enum TALER_ErrorCode ec;
   1811 
   1812   pd->recovery = NULL;
   1813   GNUNET_assert (NULL == secret);
   1814   GNUNET_assert (ANASTASIS_RS_SUCCESS != rc);
   1815   ec = error_by_status (rc,
   1816                         &msg);
   1817   free_pd (pd);
   1818   ANASTASIS_redux_fail_ (cb,
   1819                          cb_cls,
   1820                          ec,
   1821                          msg);
   1822 }
   1823 
   1824 
   1825 /**
   1826  * DispatchHandler/Callback function which is called for a
   1827  * "next" action in "secret_selecting" state.
   1828  *
   1829  * @param state state to operate on
   1830  * @param arguments arguments to use for operation on state
   1831  * @param cb callback to call during/after operation
   1832  * @param cb_cls callback closure
   1833  * @return NULL
   1834  */
   1835 static struct ANASTASIS_ReduxAction *
   1836 done_secret_selecting (struct ANASTASIS_ReduxState *rs,
   1837                        const json_t *arguments,
   1838                        ANASTASIS_ActionCallback cb,
   1839                        void *cb_cls)
   1840 {
   1841   uint32_t mask;
   1842   const json_t *pa;
   1843   struct GNUNET_JSON_Specification spec[] = {
   1844     GNUNET_JSON_spec_uint32 ("attribute_mask",
   1845                              &mask),
   1846     GNUNET_JSON_spec_array_const ("providers",
   1847                                   &pa),
   1848     GNUNET_JSON_spec_end ()
   1849   };
   1850 
   1851   if (GNUNET_OK !=
   1852       GNUNET_JSON_parse (arguments,
   1853                          spec,
   1854                          NULL, NULL))
   1855   {
   1856     GNUNET_break (0);
   1857     json_dumpf (arguments,
   1858                 stderr,
   1859                 JSON_INDENT (2));
   1860     ANASTASIS_REDUX_fail_ (rs,
   1861                            cb,
   1862                            cb_cls,
   1863                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1864                            NULL);
   1865     return NULL;
   1866   }
   1867   if (! rs->common.have_providers)
   1868   {
   1869     GNUNET_break (0);
   1870     ANASTASIS_REDUX_fail_ (rs,
   1871                            cb,
   1872                            cb_cls,
   1873                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1874                            "'authentication_providers' missing");
   1875     return NULL;
   1876   }
   1877 
   1878   {
   1879     size_t poff;
   1880     json_t *pe;
   1881     uint64_t version;
   1882     const char *provider_url;
   1883 
   1884     json_array_foreach (pa, poff, pe)
   1885     {
   1886       const struct ANASTASIS_ReduxProvider *p;
   1887       struct GNUNET_JSON_Specification ispec[] = {
   1888         GNUNET_JSON_spec_uint64 ("version",
   1889                                  &version),
   1890         GNUNET_JSON_spec_string ("url",
   1891                                  &provider_url),
   1892         GNUNET_JSON_spec_end ()
   1893       };
   1894 
   1895       if (GNUNET_OK !=
   1896           GNUNET_JSON_parse (pe,
   1897                              ispec,
   1898                              NULL, NULL))
   1899       {
   1900         GNUNET_break (0);
   1901         json_dumpf (pe,
   1902                     stderr,
   1903                     JSON_INDENT (2));
   1904         ANASTASIS_REDUX_fail_ (rs,
   1905                                cb,
   1906                                cb_cls,
   1907                                TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1908                                NULL);
   1909         return NULL;
   1910       }
   1911 
   1912       p = ANASTASIS_REDUX_provider_find_ (&rs->common,
   1913                                           provider_url);
   1914       if ( (NULL == p) ||
   1915            (! p->have_config) ||
   1916            (MHD_HTTP_OK != p->config.http_status) )
   1917         continue;
   1918       if (NULL == rs->common.identity_attributes)
   1919       {
   1920         GNUNET_break (0); /* should be impossible for well-formed state */
   1921         ANASTASIS_REDUX_fail_ (rs,
   1922                                cb,
   1923                                cb_cls,
   1924                                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1925                                "'identity_attributes' missing");
   1926         return NULL;
   1927       }
   1928       {
   1929         struct PolicyDownloadEntry *pd
   1930           = GNUNET_new (struct PolicyDownloadEntry);
   1931 
   1932         pd->cb = cb;
   1933         pd->cb_cls = cb_cls;
   1934         pd->rs = rs;
   1935         pd->backend_url = GNUNET_strdup (provider_url);
   1936         pd->recovery = ANASTASIS_recovery_begin (
   1937           ANASTASIS_REDUX_ctx_,
   1938           rs->common.identity_attributes,
   1939           version,
   1940           pd->backend_url,
   1941           &p->config.provider_salt,
   1942           &policy_lookup_cb,
   1943           pd,
   1944           &core_early_secret_cb,
   1945           pd);
   1946         if (NULL == pd->recovery)
   1947         {
   1948           GNUNET_break (0);
   1949           free_pd (pd);
   1950           ANASTASIS_redux_fail_ (cb,
   1951                                  cb_cls,
   1952                                  TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR,
   1953                                  NULL);
   1954           return NULL;
   1955         }
   1956         pd->ra.cleanup = &free_pd;
   1957         pd->ra.cleanup_cls = pd;
   1958         return &pd->ra;
   1959       }
   1960     }
   1961   }
   1962 
   1963   /* no provider worked */
   1964   ANASTASIS_REDUX_fail_ (rs,
   1965                          cb,
   1966                          cb_cls,
   1967                          TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1968                          "selected provider is not online");
   1969   return NULL;
   1970 }
   1971 
   1972 
   1973 /**
   1974  * The user wants us to add another provider. Download /config.
   1975  *
   1976  * @param[in] state we are in
   1977  * @param arguments our arguments with the solution
   1978  * @param cb function to call with the new state
   1979  * @param cb_cls closure for @a cb
   1980  * @return handle to cancel challenge selection step
   1981  */
   1982 static struct ANASTASIS_ReduxAction *
   1983 add_provider (struct ANASTASIS_ReduxState *rs,
   1984               const json_t *arguments,
   1985               ANASTASIS_ActionCallback cb,
   1986               void *cb_cls)
   1987 {
   1988   const char *provider_url;
   1989   struct GNUNET_JSON_Specification spec[] = {
   1990     GNUNET_JSON_spec_string ("provider_url",
   1991                              &provider_url),
   1992     GNUNET_JSON_spec_end ()
   1993   };
   1994 
   1995   if (GNUNET_OK !=
   1996       GNUNET_JSON_parse (arguments,
   1997                          spec,
   1998                          NULL, NULL))
   1999   {
   2000     GNUNET_break (0);
   2001     ANASTASIS_REDUX_fail_ (rs,
   2002                            cb,
   2003                            cb_cls,
   2004                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   2005                            NULL);
   2006     return NULL;
   2007   }
   2008   return ANASTASIS_REDUX_provider_add_ (provider_url,
   2009                                         rs,
   2010                                         cb,
   2011                                         cb_cls);
   2012 }
   2013 
   2014 
   2015 /**
   2016  * Signature of callback function that implements a state transition.
   2017  *
   2018  *  @param[in] rs current state
   2019  *  @param arguments arguments for the state transition
   2020  *  @param cb function to call when done
   2021  *  @param cb_cls closure for @a cb
   2022  */
   2023 typedef struct ANASTASIS_ReduxAction *
   2024 (*DispatchHandler)(struct ANASTASIS_ReduxState *rs,
   2025                    const json_t *arguments,
   2026                    ANASTASIS_ActionCallback cb,
   2027                    void *cb_cls);
   2028 
   2029 
   2030 struct ANASTASIS_ReduxAction *
   2031 ANASTASIS_recovery_action_ (struct ANASTASIS_ReduxState *rs,
   2032                             const char *action,
   2033                             const json_t *arguments,
   2034                             ANASTASIS_ActionCallback cb,
   2035                             void *cb_cls)
   2036 {
   2037   struct Dispatcher
   2038   {
   2039     enum ANASTASIS_RecoveryState recovery_state;
   2040     const char *recovery_action;
   2041     DispatchHandler fun;
   2042   } dispatchers[] = {
   2043     {
   2044       ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
   2045       "add_provider",
   2046       &add_provider
   2047     },
   2048     {
   2049       ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
   2050       "poll_providers",
   2051       &ANASTASIS_REDUX_poll_providers_
   2052     },
   2053     {
   2054       ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
   2055       "select_version",
   2056       &done_secret_selecting
   2057     },
   2058     {
   2059       ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
   2060       "back",
   2061       &ANASTASIS_back_generic_decrement_
   2062     },
   2063     {
   2064       ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
   2065       "select_challenge",
   2066       &select_challenge
   2067     },
   2068     {
   2069       ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
   2070       "sync_providers",
   2071       &ANASTASIS_REDUX_sync_providers_
   2072     },
   2073     {
   2074       ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
   2075       "poll",
   2076       &poll_challenges
   2077     },
   2078     {
   2079       ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
   2080       "back",
   2081       &ANASTASIS_back_generic_decrement_
   2082     },
   2083     {
   2084       ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING,
   2085       "pay",
   2086       &pay_challenge
   2087     },
   2088     {
   2089       ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING,
   2090       "back",
   2091       &ANASTASIS_back_generic_decrement_
   2092     },
   2093     {
   2094       ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING,
   2095       "solve_challenge",
   2096       &solve_challenge
   2097     },
   2098     {
   2099       ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING,
   2100       "back",
   2101       &back_challenge_solving
   2102     },
   2103     { ANASTASIS_RECOVERY_STATE_INVALID, NULL, NULL }
   2104   };
   2105   enum ANASTASIS_RecoveryState state = rs->details.recovery.state;
   2106 
   2107   GNUNET_assert (ANASTASIS_RT_RECOVERY == rs->type);
   2108   for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
   2109   {
   2110     if ( (state == dispatchers[i].recovery_state) &&
   2111          (0 == strcmp (action,
   2112                        dispatchers[i].recovery_action)) )
   2113     {
   2114       return dispatchers[i].fun (rs,
   2115                                  arguments,
   2116                                  cb,
   2117                                  cb_cls);
   2118     }
   2119   }
   2120   ANASTASIS_REDUX_fail_ (rs,
   2121                          cb,
   2122                          cb_cls,
   2123                          TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
   2124                          action);
   2125   return NULL;
   2126 }
   2127 
   2128 
   2129 struct ANASTASIS_ReduxAction *
   2130 ANASTASIS_REDUX_recovery_challenge_begin_ (struct ANASTASIS_ReduxState *rs,
   2131                                            const json_t *arguments,
   2132                                            ANASTASIS_ActionCallback cb,
   2133                                            void *cb_cls)
   2134 {
   2135   json_t *attributes;
   2136 
   2137   if (! rs->common.have_providers)
   2138   {
   2139     GNUNET_break (0);
   2140     ANASTASIS_REDUX_fail_ (rs,
   2141                            cb,
   2142                            cb_cls,
   2143                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2144                            "'authentication_providers' missing");
   2145     return NULL;
   2146   }
   2147   attributes = json_object_get (arguments,
   2148                                 "identity_attributes");
   2149   if ( (NULL == attributes) ||
   2150        (! json_is_object (attributes)) )
   2151   {
   2152     GNUNET_break (0);
   2153     ANASTASIS_REDUX_fail_ (rs,
   2154                            cb,
   2155                            cb_cls,
   2156                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2157                            "'identity_attributes' missing");
   2158     return NULL;
   2159   }
   2160   json_decref (rs->common.identity_attributes);
   2161   rs->common.identity_attributes = json_incref (attributes);
   2162   set_state (rs,
   2163              ANASTASIS_RECOVERY_STATE_SECRET_SELECTING);
   2164   ANASTASIS_REDUX_return_ (rs,
   2165                            cb,
   2166                            cb_cls,
   2167                            TALER_EC_NONE);
   2168   return NULL;
   2169 }