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 }