anastasis_api_recovery_redux.c (82290B)
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 29 30 #define GENERATE_STRING(STRING) #STRING, 31 static const char *recovery_strings[] = { 32 ANASTASIS_RECOVERY_STATES (GENERATE_STRING) 33 }; 34 #undef GENERATE_STRING 35 36 37 enum ANASTASIS_RecoveryState 38 ANASTASIS_recovery_state_from_string_ (const char *state_string) 39 { 40 for (enum ANASTASIS_RecoveryState i = 0; 41 i < sizeof (recovery_strings) / sizeof(*recovery_strings); 42 i++) 43 if (0 == strcmp (state_string, 44 recovery_strings[i])) 45 return i; 46 return ANASTASIS_RECOVERY_STATE_INVALID; 47 } 48 49 50 const char * 51 ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs) 52 { 53 if ( (rs < 0) || 54 (rs >= sizeof (recovery_strings) / sizeof(*recovery_strings)) ) 55 { 56 GNUNET_break_op (0); 57 return NULL; 58 } 59 return recovery_strings[rs]; 60 } 61 62 63 static void 64 set_state (json_t *state, 65 enum ANASTASIS_RecoveryState new_recovery_state) 66 { 67 GNUNET_assert ( 68 0 == 69 json_object_set_new ( 70 state, 71 "recovery_state", 72 json_string (ANASTASIS_recovery_state_to_string_ (new_recovery_state)))); 73 } 74 75 76 /** 77 * Returns an initial ANASTASIS recovery state. 78 * 79 * @return NULL on failure 80 */ 81 json_t * 82 ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg) 83 { 84 json_t *initial_state; 85 const char *external_reducer = ANASTASIS_REDUX_probe_external_reducer (); 86 87 if (NULL != external_reducer) 88 { 89 int pipefd_stdout[2]; 90 pid_t pid = 0; 91 int status; 92 FILE *reducer_stdout; 93 94 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 95 "Using external reducer '%s' for recovery start status\n", 96 external_reducer); 97 98 GNUNET_assert (0 == pipe (pipefd_stdout)); 99 pid = fork (); 100 if (pid == 0) 101 { 102 GNUNET_assert (0 == 103 close (pipefd_stdout[0])); 104 GNUNET_assert (STDOUT_FILENO == 105 dup2 (pipefd_stdout[1], 106 STDOUT_FILENO)); 107 execlp (external_reducer, 108 external_reducer, 109 "-r", 110 NULL); 111 GNUNET_assert (0); 112 } 113 114 GNUNET_assert (0 == 115 close (pipefd_stdout[1])); 116 reducer_stdout = fdopen (pipefd_stdout[0], 117 "r"); 118 { 119 json_error_t err; 120 121 initial_state = json_loadf (reducer_stdout, 122 0, 123 &err); 124 125 if (NULL == initial_state) 126 { 127 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 128 "External reducer did not output valid JSON: %s:%d:%d %s\n", 129 err.source, 130 err.line, 131 err.column, 132 err.text); 133 GNUNET_assert (0 == fclose (reducer_stdout)); 134 waitpid (pid, &status, 0); 135 return NULL; 136 } 137 } 138 139 GNUNET_assert (NULL != initial_state); 140 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 141 "Waiting for external reducer to terminate.\n"); 142 GNUNET_assert (0 == fclose (reducer_stdout)); 143 reducer_stdout = NULL; 144 waitpid (pid, &status, 0); 145 146 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 147 "External reducer finished with exit status '%d'\n", 148 status); 149 return initial_state; 150 } 151 152 (void) cfg; 153 initial_state = ANASTASIS_REDUX_load_continents_ (); 154 if (NULL == initial_state) 155 return NULL; 156 GNUNET_assert ( 157 0 == 158 json_object_set_new (initial_state, 159 "reducer_type", 160 json_string ("recovery"))); 161 set_state (initial_state, 162 ANASTASIS_RECOVERY_STATE_CONTINENT_SELECTING); 163 return initial_state; 164 } 165 166 167 /** 168 * Context for a "select_challenge" operation. 169 */ 170 struct SelectChallengeContext 171 { 172 /** 173 * Handle we returned for cancellation of the operation. 174 */ 175 struct ANASTASIS_ReduxAction ra; 176 177 /** 178 * UUID of the challenge selected by the user for solving. 179 */ 180 struct ANASTASIS_CRYPTO_TruthUUIDP uuid; 181 182 /** 183 * Which timeout was set for the operation? 184 */ 185 struct GNUNET_TIME_Relative timeout; 186 187 /** 188 * Overall recovery action. 189 */ 190 struct ANASTASIS_Recovery *r; 191 192 /** 193 * Function to call with the next state. 194 */ 195 ANASTASIS_ActionCallback cb; 196 197 /** 198 * Closure for @e cb. 199 */ 200 void *cb_cls; 201 202 /** 203 * Our state. 204 */ 205 json_t *state; 206 207 /** 208 * Our arguments (like answers to the challenge, if already provided). 209 */ 210 json_t *args; 211 212 /** 213 * Task scheduled for delayed success reporting. Needed to make 214 * sure that the solved challenge was really the final result, 215 * cancelled if the solved challenge resulted in the secret being 216 * recovered. 217 */ 218 struct GNUNET_SCHEDULER_Task *delayed_report; 219 220 /** 221 * Payment secret, if we are in the "pay" state. 222 */ 223 struct ANASTASIS_PaymentSecretP ps; 224 225 /** 226 * Application asked us to only poll for existing 227 * asynchronous challenges, and not to being a 228 * new one. 229 */ 230 bool poll_only; 231 }; 232 233 234 /** 235 * Cleanup a select challenge context. 236 * 237 * @param cls a `struct SelectChallengeContext *` 238 */ 239 static void 240 sctx_free (void *cls) 241 { 242 struct SelectChallengeContext *sctx = cls; 243 244 if (NULL != sctx->r) 245 { 246 ANASTASIS_recovery_abort (sctx->r); 247 sctx->r = NULL; 248 } 249 json_decref (sctx->state); 250 json_decref (sctx->args); 251 if (NULL != sctx->delayed_report) 252 { 253 GNUNET_SCHEDULER_cancel (sctx->delayed_report); 254 sctx->delayed_report = NULL; 255 } 256 GNUNET_free (sctx); 257 } 258 259 260 /** 261 * Call the action callback with an error result 262 * 263 * @param cb action callback to call 264 * @param cb_cls closure for @a cb 265 * @param rc error code to translate to JSON 266 */ 267 static void 268 fail_by_error (ANASTASIS_ActionCallback cb, 269 void *cb_cls, 270 enum ANASTASIS_RecoveryStatus rc) 271 { 272 const char *msg = NULL; 273 enum TALER_ErrorCode ec = TALER_EC_INVALID; 274 275 switch (rc) 276 { 277 case ANASTASIS_RS_SUCCESS: 278 GNUNET_assert (0); 279 break; 280 case ANASTASIS_RS_POLICY_DOWNLOAD_FAILED: 281 msg = gettext_noop ("download failed due to unexpected network issue"); 282 ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED; 283 break; 284 case ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY: 285 GNUNET_break (0); 286 msg = gettext_noop ("policy document returned was malformed"); 287 ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED; 288 break; 289 case ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG: 290 GNUNET_break (0); 291 msg = gettext_noop ("policy document too large for client memory"); 292 ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED; 293 break; 294 case ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION: 295 GNUNET_break (0); 296 msg = gettext_noop ("failed to decompress policy document"); 297 ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED; 298 break; 299 case ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON: 300 GNUNET_break (0); 301 msg = gettext_noop ("policy document returned was not in JSON format"); 302 ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED; 303 break; 304 case ANASTASIS_RS_POLICY_MALFORMED_JSON: 305 GNUNET_break (0); 306 msg = gettext_noop ( 307 "policy document returned was not in required JSON format"); 308 ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED; 309 break; 310 case ANASTASIS_RS_POLICY_SERVER_ERROR: 311 msg = gettext_noop ("Anastasis server reported transient internal error"); 312 ec = TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED; 313 break; 314 case ANASTASIS_RS_POLICY_GONE: 315 msg = gettext_noop ("policy document no longer exists"); 316 ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED; 317 break; 318 case ANASTASIS_RS_POLICY_UNKNOWN: 319 msg = gettext_noop ("account unknown to Anastasis server"); 320 ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED; 321 break; 322 } 323 ANASTASIS_redux_fail_ (cb, 324 cb_cls, 325 ec, 326 msg); 327 } 328 329 330 /** 331 * This function is called whenever the recovery process ends. 332 * On success, the secret is returned in @a secret. 333 * 334 * @param cls handle for the callback 335 * @param rc error code 336 * @param secret contains the core secret which is passed to the user 337 * @param secret_size defines the size of the core secret 338 */ 339 static void 340 core_secret_cb (void *cls, 341 enum ANASTASIS_RecoveryStatus rc, 342 const void *secret, 343 size_t secret_size) 344 { 345 struct SelectChallengeContext *sctx = cls; 346 347 sctx->r = NULL; 348 if (ANASTASIS_RS_SUCCESS == rc) 349 { 350 json_t *jsecret; 351 352 jsecret = json_loadb (secret, 353 secret_size, 354 JSON_REJECT_DUPLICATES, 355 NULL); 356 if (NULL == jsecret) 357 { 358 ANASTASIS_redux_fail_ (sctx->cb, 359 sctx->cb_cls, 360 TALER_EC_ANASTASIS_REDUCER_SECRET_MALFORMED, 361 NULL); 362 sctx_free (sctx); 363 return; 364 } 365 GNUNET_assert (0 == 366 json_object_set_new (sctx->state, 367 "core_secret", 368 jsecret)); 369 set_state (sctx->state, 370 ANASTASIS_RECOVERY_STATE_RECOVERY_FINISHED); 371 sctx->cb (sctx->cb_cls, 372 TALER_EC_NONE, 373 sctx->state); 374 sctx_free (sctx); 375 return; 376 } 377 fail_by_error (sctx->cb, 378 sctx->cb_cls, 379 rc); 380 sctx_free (sctx); 381 } 382 383 384 /** 385 * A challenge was solved, but we are not yet finished. 386 * Report to caller that the challenge was completed. 387 * 388 * @param cls a `struct SelectChallengeContext` 389 */ 390 static void 391 report_solved (void *cls) 392 { 393 struct SelectChallengeContext *sctx = cls; 394 395 sctx->delayed_report = NULL; 396 set_state (sctx->state, 397 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); 398 sctx->cb (sctx->cb_cls, 399 TALER_EC_NONE, 400 sctx->state); 401 sctx_free (sctx); 402 } 403 404 405 /** 406 * Find challenge of @a uuid in @a state under "recovery_information". 407 * 408 * @param state the state to search 409 * @param uuid the UUID to search for 410 * @return NULL on error, otherwise challenge entry; RC is NOT incremented 411 */ 412 static json_t * 413 find_challenge_in_ri (json_t *state, 414 const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid) 415 { 416 struct ANASTASIS_CRYPTO_TruthUUIDP u; 417 struct GNUNET_JSON_Specification spec[] = { 418 GNUNET_JSON_spec_fixed_auto ("uuid", 419 &u), 420 GNUNET_JSON_spec_end () 421 }; 422 json_t *ri; 423 json_t *challenges; 424 json_t *challenge; 425 size_t index; 426 427 ri = json_object_get (state, 428 "recovery_information"); 429 if (NULL == ri) 430 { 431 GNUNET_break (0); 432 return NULL; 433 } 434 challenges = json_object_get (ri, 435 "challenges"); 436 if (NULL == challenges) 437 { 438 GNUNET_break (0); 439 return NULL; 440 } 441 json_array_foreach (challenges, index, challenge) 442 { 443 if (GNUNET_OK != 444 GNUNET_JSON_parse (challenge, 445 spec, 446 NULL, NULL)) 447 { 448 GNUNET_break (0); 449 return NULL; 450 } 451 if (0 == 452 GNUNET_memcmp (&u, 453 uuid)) 454 { 455 return challenge; 456 } 457 } 458 return NULL; 459 } 460 461 462 /** 463 * Find challenge of @a uuid in @a state under "challenges". 464 * 465 * @param state the state to search 466 * @param uuid the UUID to search for 467 * @return NULL on error, otherwise challenge entry; RC is NOT incremented 468 */ 469 static json_t * 470 find_challenge_in_cs (json_t *state, 471 const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid) 472 { 473 json_t *rd = json_object_get (state, 474 "recovery_document"); 475 json_t *cs = json_object_get (rd, 476 "challenges"); 477 json_t *c; 478 size_t off; 479 480 json_array_foreach (cs, off, c) 481 { 482 struct ANASTASIS_CRYPTO_TruthUUIDP u; 483 struct GNUNET_JSON_Specification spec[] = { 484 GNUNET_JSON_spec_fixed_auto ("uuid", 485 &u), 486 GNUNET_JSON_spec_end () 487 }; 488 489 if (GNUNET_OK != 490 GNUNET_JSON_parse (c, 491 spec, 492 NULL, NULL)) 493 { 494 GNUNET_break (0); 495 continue; 496 } 497 if (0 != 498 GNUNET_memcmp (uuid, 499 &u)) 500 continue; 501 return c; 502 } 503 return NULL; 504 } 505 506 507 /** 508 * Defines a callback for the response status for a challenge start 509 * operation. 510 * 511 * @param cls a `struct SelectChallengeContext *` 512 * @param csr response details 513 */ 514 static void 515 start_feedback_cb ( 516 void *cls, 517 const struct ANASTASIS_ChallengeStartResponse *csr) 518 { 519 struct SelectChallengeContext *sctx = cls; 520 const struct ANASTASIS_ChallengeDetails *cd; 521 char uuid[sizeof (cd->uuid) * 2]; 522 char *end; 523 json_t *feedback; 524 525 cd = ANASTASIS_challenge_get_details (csr->challenge); 526 end = GNUNET_STRINGS_data_to_string (&cd->uuid, 527 sizeof (cd->uuid), 528 uuid, 529 sizeof (uuid)); 530 GNUNET_assert (NULL != end); 531 *end = '\0'; 532 feedback = json_object_get (sctx->state, 533 "challenge_feedback"); 534 if (NULL == feedback) 535 { 536 feedback = json_object (); 537 GNUNET_assert (0 == 538 json_object_set_new (sctx->state, 539 "challenge_feedback", 540 feedback)); 541 } 542 switch (csr->cs) 543 { 544 case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED: 545 { 546 json_t *instructions; 547 char *hint; 548 549 GNUNET_asprintf (&hint, 550 _ ("Required TAN can be found in `%s'"), 551 csr->details.tan_filename); 552 instructions = GNUNET_JSON_PACK ( 553 GNUNET_JSON_pack_string ("state", 554 "code-in-file"), 555 GNUNET_JSON_pack_string ("filename", 556 csr->details.tan_filename), 557 GNUNET_JSON_pack_string ("display_hint", 558 hint)); 559 GNUNET_free (hint); 560 GNUNET_assert (0 == 561 json_object_set_new (feedback, 562 uuid, 563 instructions)); 564 } 565 set_state (sctx->state, 566 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); 567 sctx->cb (sctx->cb_cls, 568 TALER_EC_NONE, 569 sctx->state); 570 sctx_free (sctx); 571 return; 572 case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED: 573 { 574 json_t *instructions; 575 char *hint; 576 577 GNUNET_asprintf (&hint, 578 _ ("TAN code was sent to `%s'"), 579 csr->details.tan_address_hint); 580 instructions = GNUNET_JSON_PACK ( 581 GNUNET_JSON_pack_string ("state", 582 "send-to-address"), 583 GNUNET_JSON_pack_string ("address_hint", 584 csr->details.tan_address_hint), 585 GNUNET_JSON_pack_string ("display_hint", 586 hint)); 587 GNUNET_free (hint); 588 GNUNET_assert (0 == 589 json_object_set_new (feedback, 590 uuid, 591 instructions)); 592 } 593 set_state (sctx->state, 594 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); 595 sctx->cb (sctx->cb_cls, 596 TALER_EC_NONE, 597 sctx->state); 598 sctx_free (sctx); 599 return; 600 601 case ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT: 602 { 603 json_t *instructions; 604 char *hint; 605 606 GNUNET_asprintf (&hint, 607 _ ("TAN code already sent.")); 608 instructions = GNUNET_JSON_PACK ( 609 GNUNET_JSON_pack_string ("state", 610 "send-to-address"), 611 GNUNET_JSON_pack_string ("display_hint", 612 hint)); 613 GNUNET_free (hint); 614 GNUNET_assert (0 == 615 json_object_set_new (feedback, 616 uuid, 617 instructions)); 618 } 619 set_state (sctx->state, 620 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); 621 sctx->cb (sctx->cb_cls, 622 TALER_EC_NONE, 623 sctx->state); 624 sctx_free (sctx); 625 return; 626 627 case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED: 628 { 629 json_t *pay; 630 char *hint; 631 632 GNUNET_asprintf (&hint, 633 _ ("Taler payment to `%s' required"), 634 csr->details.payment_required.taler_pay_uri); 635 pay = GNUNET_JSON_PACK ( 636 GNUNET_JSON_pack_string ("state", 637 "taler-payment"), 638 GNUNET_JSON_pack_string ( 639 "taler_pay_uri", 640 csr->details.payment_required.taler_pay_uri), 641 GNUNET_JSON_pack_string ("provider", 642 cd->provider_url), 643 GNUNET_JSON_pack_string ("display_hint", 644 hint), 645 GNUNET_JSON_pack_data_auto ( 646 "payment_secret", 647 &csr->details.payment_required.payment_secret)); 648 GNUNET_free (hint); 649 GNUNET_assert (0 == 650 json_object_set_new (feedback, 651 uuid, 652 pay)); 653 } 654 /* Remember payment secret for later (once application claims it paid) */ 655 { 656 json_t *challenge = find_challenge_in_ri (sctx->state, 657 &cd->uuid); 658 659 GNUNET_assert (NULL != challenge); 660 GNUNET_assert (0 == 661 json_object_set_new ( 662 challenge, 663 "payment_secret", 664 GNUNET_JSON_from_data_auto ( 665 &csr->details.payment_required.payment_secret))); 666 } 667 set_state (sctx->state, 668 ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING); 669 sctx->cb (sctx->cb_cls, 670 TALER_EC_NONE, 671 sctx->state); 672 sctx_free (sctx); 673 return; 674 case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE: 675 { 676 json_t *err; 677 678 err = GNUNET_JSON_PACK ( 679 GNUNET_JSON_pack_string ("state", 680 "server-failure"), 681 GNUNET_JSON_pack_uint64 ("http_status", 682 csr->http_status), 683 GNUNET_JSON_pack_uint64 ("error_code", 684 csr->ec)); 685 GNUNET_assert (0 == 686 json_object_set_new (feedback, 687 uuid, 688 err)); 689 } 690 set_state (sctx->state, 691 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); 692 sctx->cb (sctx->cb_cls, 693 csr->ec, 694 sctx->state); 695 sctx_free (sctx); 696 return; 697 case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN: 698 { 699 json_t *err; 700 701 err = GNUNET_JSON_PACK ( 702 GNUNET_JSON_pack_string ("state", 703 "truth-unknown"), 704 GNUNET_JSON_pack_uint64 ("http_status", 705 csr->http_status), 706 GNUNET_JSON_pack_uint64 ("error_code", 707 TALER_EC_ANASTASIS_TRUTH_UNKNOWN)); 708 GNUNET_assert (0 == 709 json_object_set_new (feedback, 710 uuid, 711 err)); 712 } 713 set_state (sctx->state, 714 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); 715 sctx->cb (sctx->cb_cls, 716 TALER_EC_ANASTASIS_TRUTH_UNKNOWN, 717 sctx->state); 718 sctx_free (sctx); 719 return; 720 case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED: 721 { 722 json_t *reply; 723 json_t *c; 724 char *hint; 725 726 c = find_challenge_in_cs (sctx->state, 727 &cd->uuid); 728 if (NULL == c) 729 { 730 GNUNET_break (0); 731 ANASTASIS_redux_fail_ (sctx->cb, 732 sctx->cb_cls, 733 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 734 NULL); 735 sctx_free (sctx); 736 return; 737 } 738 GNUNET_assert (0 == 739 json_object_set_new (c, 740 "async", 741 json_true ())); 742 GNUNET_assert ( 743 0 == 744 json_object_set_new ( 745 c, 746 "answer-pin", 747 json_integer ( 748 csr->details.bank_transfer_required.answer_code))); 749 GNUNET_asprintf (&hint, 750 _ ("Wire %s to %s (%s) with subject %s\n"), 751 TALER_amount2s ( 752 &csr->details.bank_transfer_required.amount), 753 csr->details.bank_transfer_required.target_iban, 754 csr->details.bank_transfer_required.target_business_name, 755 csr->details.bank_transfer_required.wire_transfer_subject 756 ); 757 reply = GNUNET_JSON_PACK ( 758 GNUNET_JSON_pack_string ("state", 759 "iban-instructions"), 760 GNUNET_JSON_pack_string ( 761 "target_iban", 762 csr->details.bank_transfer_required.target_iban), 763 GNUNET_JSON_pack_string ( 764 "display_hint", 765 hint), 766 GNUNET_JSON_pack_string ( 767 "target_business_name", 768 csr->details.bank_transfer_required.target_business_name), 769 GNUNET_JSON_pack_string ( 770 "wire_transfer_subject", 771 csr->details.bank_transfer_required.wire_transfer_subject), 772 TALER_JSON_pack_amount ( 773 "challenge_amount", 774 &csr->details.bank_transfer_required.amount)); 775 GNUNET_free (hint); 776 GNUNET_assert (0 == 777 json_object_set_new (feedback, 778 uuid, 779 reply)); 780 } 781 GNUNET_assert (0 == 782 json_object_set_new (sctx->state, 783 "selected_challenge_uuid", 784 GNUNET_JSON_from_data_auto ( 785 &cd->uuid))); 786 set_state (sctx->state, 787 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); 788 sctx->cb (sctx->cb_cls, 789 TALER_EC_NONE, 790 sctx->state); 791 sctx_free (sctx); 792 return; 793 } 794 GNUNET_break (0); 795 ANASTASIS_redux_fail_ (sctx->cb, 796 sctx->cb_cls, 797 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 798 NULL); 799 sctx_free (sctx); 800 } 801 802 803 /** 804 * Defines a callback for the response status for a challenge answer 805 * operation. 806 * 807 * @param cls a `struct SelectChallengeContext *` 808 * @param csr response details 809 */ 810 static void 811 answer_feedback_cb ( 812 void *cls, 813 const struct ANASTASIS_ChallengeAnswerResponse *csr) 814 { 815 struct SelectChallengeContext *sctx = cls; 816 const struct ANASTASIS_ChallengeDetails *cd; 817 char uuid[sizeof (cd->uuid) * 2]; 818 char *end; 819 json_t *feedback; 820 821 cd = ANASTASIS_challenge_get_details (csr->challenge); 822 end = GNUNET_STRINGS_data_to_string (&cd->uuid, 823 sizeof (cd->uuid), 824 uuid, 825 sizeof (uuid)); 826 GNUNET_assert (NULL != end); 827 *end = '\0'; 828 feedback = json_object_get (sctx->state, 829 "challenge_feedback"); 830 if (NULL == feedback) 831 { 832 feedback = json_object (); 833 GNUNET_assert (0 == 834 json_object_set_new (sctx->state, 835 "challenge_feedback", 836 feedback)); 837 } 838 switch (csr->cs) 839 { 840 case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED: 841 { 842 json_t *rd; 843 844 rd = ANASTASIS_recovery_serialize (sctx->r); 845 if (NULL == rd) 846 { 847 GNUNET_break (0); 848 ANASTASIS_redux_fail_ (sctx->cb, 849 sctx->cb_cls, 850 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 851 "unable to serialize recovery state"); 852 sctx_free (sctx); 853 return; 854 } 855 GNUNET_assert (0 == 856 json_object_set_new (sctx->state, 857 "recovery_document", 858 rd)); 859 } 860 { 861 json_t *solved; 862 863 solved = GNUNET_JSON_PACK ( 864 GNUNET_JSON_pack_string ("state", 865 "solved")); 866 GNUNET_assert (0 == 867 json_object_set_new (feedback, 868 uuid, 869 solved)); 870 } 871 /* Delay reporting challenge success, as we MAY still 872 also see a secret recovery success (and we can only 873 call the callback once) */ 874 sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved, 875 sctx); 876 return; 877 case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER: 878 { 879 json_t *instructions; 880 881 instructions = GNUNET_JSON_PACK ( 882 GNUNET_JSON_pack_string ("state", 883 "incorrect-answer"), 884 GNUNET_JSON_pack_uint64 ("error_code", 885 csr->ec)); 886 GNUNET_assert (0 == 887 json_object_set_new (feedback, 888 uuid, 889 instructions)); 890 } 891 set_state (sctx->state, 892 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); 893 sctx->cb (sctx->cb_cls, 894 TALER_EC_NONE, 895 sctx->state); 896 sctx_free (sctx); 897 return; 898 case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED: 899 { 900 json_t *pay; 901 char *hint; 902 903 GNUNET_asprintf (&hint, 904 _ ("Taler payment to `%s' required"), 905 csr->details.payment_required.taler_pay_uri); 906 pay = GNUNET_JSON_PACK ( 907 GNUNET_JSON_pack_string ("state", 908 "taler-payment"), 909 GNUNET_JSON_pack_string ( 910 "taler_pay_uri", 911 csr->details.payment_required.taler_pay_uri), 912 GNUNET_JSON_pack_string ( 913 "display_hint", 914 hint), 915 GNUNET_JSON_pack_string ("provider", 916 cd->provider_url), 917 GNUNET_JSON_pack_data_auto ( 918 "payment_secret", 919 &csr->details.payment_required.payment_secret)); 920 GNUNET_free (hint); 921 GNUNET_assert (0 == 922 json_object_set_new (feedback, 923 uuid, 924 pay)); 925 } 926 /* Remember payment secret for later (once application claims it paid) */ 927 { 928 json_t *challenge = find_challenge_in_ri (sctx->state, 929 &cd->uuid); 930 931 GNUNET_assert (NULL != challenge); 932 GNUNET_assert (0 == 933 json_object_set_new ( 934 challenge, 935 "payment_secret", 936 GNUNET_JSON_from_data_auto ( 937 &csr->details.payment_required.payment_secret))); 938 } 939 set_state (sctx->state, 940 ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING); 941 sctx->cb (sctx->cb_cls, 942 TALER_EC_NONE, 943 sctx->state); 944 sctx_free (sctx); 945 return; 946 case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE: 947 { 948 json_t *err; 949 950 err = GNUNET_JSON_PACK ( 951 GNUNET_JSON_pack_string ("state", 952 "server-failure"), 953 GNUNET_JSON_pack_uint64 ("http_status", 954 csr->http_status), 955 GNUNET_JSON_pack_uint64 ("error_code", 956 csr->ec)); 957 GNUNET_assert (0 == 958 json_object_set_new (feedback, 959 uuid, 960 err)); 961 } 962 set_state (sctx->state, 963 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); 964 sctx->cb (sctx->cb_cls, 965 csr->ec, 966 sctx->state); 967 sctx_free (sctx); 968 return; 969 case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN: 970 { 971 json_t *err; 972 973 err = GNUNET_JSON_PACK ( 974 GNUNET_JSON_pack_string ("state", 975 "truth-unknown"), 976 GNUNET_JSON_pack_uint64 ("http_status", 977 csr->http_status), 978 GNUNET_JSON_pack_uint64 ("error_code", 979 TALER_EC_ANASTASIS_TRUTH_UNKNOWN)); 980 GNUNET_assert (0 == 981 json_object_set_new (feedback, 982 uuid, 983 err)); 984 } 985 set_state (sctx->state, 986 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); 987 sctx->cb (sctx->cb_cls, 988 TALER_EC_ANASTASIS_TRUTH_UNKNOWN, 989 sctx->state); 990 sctx_free (sctx); 991 return; 992 case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED: 993 { 994 json_t *err; 995 char *hint; 996 997 GNUNET_asprintf ( 998 &hint, 999 _ ("exceeded limit of %llu attempts in %s"), 1000 (unsigned long long) csr->details.rate_limit_exceeded.request_limit, 1001 GNUNET_TIME_relative2s ( 1002 csr->details.rate_limit_exceeded.request_frequency, 1003 true)); 1004 err = GNUNET_JSON_PACK ( 1005 GNUNET_JSON_pack_string ( 1006 "state", 1007 "rate-limit-exceeded"), 1008 GNUNET_JSON_pack_string ( 1009 "display_hint", 1010 hint), 1011 GNUNET_JSON_pack_uint64 ( 1012 "request_limit", 1013 csr->details.rate_limit_exceeded.request_limit), 1014 GNUNET_JSON_pack_time_rel ( 1015 "request_frequency", 1016 csr->details.rate_limit_exceeded.request_frequency), 1017 GNUNET_JSON_pack_uint64 ( 1018 "error_code", 1019 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); 1020 GNUNET_free (hint); 1021 GNUNET_assert (0 == 1022 json_object_set_new (feedback, 1023 uuid, 1024 err)); 1025 } 1026 set_state (sctx->state, 1027 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); 1028 sctx->cb (sctx->cb_cls, 1029 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, 1030 sctx->state); 1031 sctx_free (sctx); 1032 return; 1033 } 1034 GNUNET_break (0); 1035 ANASTASIS_redux_fail_ (sctx->cb, 1036 sctx->cb_cls, 1037 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1038 NULL); 1039 sctx_free (sctx); 1040 } 1041 1042 1043 /** 1044 * Callback which passes back the recovery document and its possible 1045 * policies. Also passes back the version of the document for the user 1046 * to check. 1047 * 1048 * We find the selected challenge and try to answer it (or begin 1049 * the process). 1050 * 1051 * @param cls a `struct SelectChallengeContext *` 1052 * @param ri recovery information struct which contains the policies 1053 */ 1054 static void 1055 solve_challenge_cb (void *cls, 1056 const struct ANASTASIS_RecoveryInformation *ri) 1057 { 1058 struct SelectChallengeContext *sctx = cls; 1059 const struct ANASTASIS_PaymentSecretP *psp = NULL; 1060 struct ANASTASIS_PaymentSecretP ps; 1061 struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO; 1062 struct GNUNET_JSON_Specification tspec[] = { 1063 GNUNET_JSON_spec_mark_optional ( 1064 GNUNET_JSON_spec_relative_time ("timeout", 1065 &timeout), 1066 NULL), 1067 GNUNET_JSON_spec_end () 1068 }; 1069 struct GNUNET_JSON_Specification pspec[] = { 1070 GNUNET_JSON_spec_fixed_auto ("payment_secret", 1071 &ps), 1072 GNUNET_JSON_spec_end () 1073 }; 1074 1075 if (NULL == ri) 1076 { 1077 GNUNET_break_op (0); 1078 ANASTASIS_redux_fail_ (sctx->cb, 1079 sctx->cb_cls, 1080 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1081 "recovery information could not be deserialized"); 1082 sctx_free (sctx); 1083 return; 1084 } 1085 1086 if ( (NULL != sctx->args) && 1087 (GNUNET_OK != 1088 GNUNET_JSON_parse (sctx->args, 1089 tspec, 1090 NULL, NULL)) ) 1091 { 1092 GNUNET_break_op (0); 1093 ANASTASIS_redux_fail_ (sctx->cb, 1094 sctx->cb_cls, 1095 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1096 "'timeout' malformed"); 1097 sctx_free (sctx); 1098 return; 1099 } 1100 1101 /* resume all async, unsolved challenges */ 1102 { 1103 bool poll_started = false; 1104 1105 for (unsigned int i = 0; i<ri->cs_len; i++) 1106 { 1107 struct ANASTASIS_Challenge *ci = ri->cs[i]; 1108 const struct ANASTASIS_ChallengeDetails *cd; 1109 json_t *challenge; 1110 json_t *pin; 1111 1112 cd = ANASTASIS_challenge_get_details (ci); 1113 if (cd->solved || 1114 (! cd->async) ) 1115 continue; 1116 1117 challenge = find_challenge_in_cs (sctx->state, 1118 &cd->uuid); 1119 if (NULL == challenge) 1120 { 1121 GNUNET_break_op (0); 1122 ANASTASIS_redux_fail_ (sctx->cb, 1123 sctx->cb_cls, 1124 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1125 "challenge not found"); 1126 sctx_free (sctx); 1127 return; 1128 } 1129 pin = json_object_get (challenge, 1130 "answer-pin"); 1131 if (! json_is_integer (pin)) 1132 { 1133 GNUNET_break_op (0); 1134 ANASTASIS_redux_fail_ (sctx->cb, 1135 sctx->cb_cls, 1136 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1137 "async challenge 'answer-pin' not found"); 1138 sctx_free (sctx); 1139 return; 1140 } 1141 if (GNUNET_OK != 1142 ANASTASIS_challenge_answer2 (ci, 1143 psp, 1144 timeout, 1145 json_integer_value (pin), 1146 &answer_feedback_cb, 1147 sctx)) 1148 { 1149 ANASTASIS_redux_fail_ (sctx->cb, 1150 sctx->cb_cls, 1151 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1152 "Failed to begin answering asynchronous challenge"); 1153 sctx_free (sctx); 1154 return; 1155 } 1156 poll_started = true; 1157 } 1158 1159 if (sctx->poll_only) 1160 { 1161 if (! poll_started) 1162 { 1163 GNUNET_break_op (0); 1164 ANASTASIS_redux_fail_ (sctx->cb, 1165 sctx->cb_cls, 1166 TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID, 1167 "no challenge available for polling"); 1168 return; 1169 } 1170 /* only polling, do not start new challenges */ 1171 return; 1172 } 1173 } /* end resuming async challenges */ 1174 1175 /* Check if we got a payment_secret */ 1176 { 1177 json_t *challenge; 1178 1179 challenge = find_challenge_in_ri (sctx->state, 1180 &sctx->uuid); 1181 if (NULL == challenge) 1182 { 1183 GNUNET_break_op (0); 1184 ANASTASIS_redux_fail_ (sctx->cb, 1185 sctx->cb_cls, 1186 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1187 "challenge not found"); 1188 sctx_free (sctx); 1189 return; 1190 } 1191 1192 if (NULL != 1193 json_object_get (sctx->args, 1194 "payment_secret")) 1195 { 1196 /* check if we got payment secret in args */ 1197 if (GNUNET_OK != 1198 GNUNET_JSON_parse (sctx->args, 1199 pspec, 1200 NULL, NULL)) 1201 { 1202 GNUNET_break_op (0); 1203 ANASTASIS_redux_fail_ (sctx->cb, 1204 sctx->cb_cls, 1205 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1206 "'payment_secret' malformed"); 1207 sctx_free (sctx); 1208 return; 1209 } 1210 psp = &ps; 1211 } 1212 else if (NULL != 1213 json_object_get (challenge, 1214 "payment_secret")) 1215 { 1216 if (GNUNET_OK != 1217 GNUNET_JSON_parse (challenge, 1218 pspec, 1219 NULL, NULL)) 1220 { 1221 GNUNET_break_op (0); 1222 ANASTASIS_redux_fail_ (sctx->cb, 1223 sctx->cb_cls, 1224 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1225 "'payment_secret' malformed"); 1226 sctx_free (sctx); 1227 return; 1228 } 1229 psp = &ps; 1230 } 1231 } 1232 1233 /* start or solve selected challenge */ 1234 for (unsigned int i = 0; i<ri->cs_len; i++) 1235 { 1236 struct ANASTASIS_Challenge *ci = ri->cs[i]; 1237 const struct ANASTASIS_ChallengeDetails *cd; 1238 int ret; 1239 json_t *c; 1240 1241 cd = ANASTASIS_challenge_get_details (ci); 1242 if (cd->async) 1243 continue; /* handled above */ 1244 if (0 != 1245 GNUNET_memcmp (&sctx->uuid, 1246 &cd->uuid)) 1247 continue; 1248 if (cd->solved) 1249 { 1250 ANASTASIS_redux_fail_ (sctx->cb, 1251 sctx->cb_cls, 1252 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1253 "Selected challenge already solved"); 1254 sctx_free (sctx); 1255 return; 1256 } 1257 c = find_challenge_in_cs (sctx->state, 1258 &cd->uuid); 1259 GNUNET_assert (NULL != c); 1260 if (0 == strcmp ("question", 1261 cd->type)) 1262 { 1263 /* security question, answer must be a string */ 1264 json_t *janswer = json_object_get (sctx->args, 1265 "answer"); 1266 const char *answer = json_string_value (janswer); 1267 1268 if (NULL == answer) 1269 { 1270 ANASTASIS_redux_fail_ (sctx->cb, 1271 sctx->cb_cls, 1272 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1273 "'answer' missing"); 1274 sctx_free (sctx); 1275 return; 1276 } 1277 /* persist answer, in case payment is required */ 1278 GNUNET_assert (0 == 1279 json_object_set (c, 1280 "answer", 1281 janswer)); 1282 ret = ANASTASIS_challenge_answer (ci, 1283 psp, 1284 timeout, 1285 answer, 1286 &answer_feedback_cb, 1287 sctx); 1288 } 1289 else 1290 { 1291 /* Check if we got a PIN or a HASH */ 1292 json_t *pin = json_object_get (sctx->args, 1293 "pin"); 1294 json_t *hash = json_object_get (sctx->args, 1295 "hash"); 1296 if (json_is_integer (pin)) 1297 { 1298 uint64_t ianswer = json_integer_value (pin); 1299 1300 /* persist answer, in case async processing 1301 happens via poll */ 1302 GNUNET_assert (0 == 1303 json_object_set (c, 1304 "answer-pin", 1305 pin)); 1306 ret = ANASTASIS_challenge_answer2 (ci, 1307 psp, 1308 timeout, 1309 ianswer, 1310 &answer_feedback_cb, 1311 sctx); 1312 } 1313 else if (NULL != hash) 1314 { 1315 struct GNUNET_HashCode hashed_answer; 1316 struct GNUNET_JSON_Specification spec[] = { 1317 GNUNET_JSON_spec_fixed_auto ("hash", 1318 &hashed_answer), 1319 GNUNET_JSON_spec_end () 1320 }; 1321 1322 if (GNUNET_OK != 1323 GNUNET_JSON_parse (sctx->args, 1324 spec, 1325 NULL, NULL)) 1326 { 1327 ANASTASIS_redux_fail_ (sctx->cb, 1328 sctx->cb_cls, 1329 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1330 "'answer' malformed"); 1331 sctx_free (sctx); 1332 return; 1333 } 1334 ret = ANASTASIS_challenge_answer3 (ci, 1335 psp, 1336 timeout, 1337 &hashed_answer, 1338 &answer_feedback_cb, 1339 sctx); 1340 } 1341 else 1342 { 1343 /* no answer provided */ 1344 ret = ANASTASIS_challenge_start (ci, 1345 psp, 1346 &start_feedback_cb, 1347 sctx); 1348 } 1349 } 1350 if (GNUNET_OK != ret) 1351 { 1352 ANASTASIS_redux_fail_ (sctx->cb, 1353 sctx->cb_cls, 1354 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1355 "Failed to begin answering challenge"); 1356 sctx_free (sctx); 1357 return; 1358 } 1359 return; /* await answer feedback */ 1360 } 1361 ANASTASIS_redux_fail_ (sctx->cb, 1362 sctx->cb_cls, 1363 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1364 "'uuid' not in list of challenges"); 1365 sctx_free (sctx); 1366 } 1367 1368 1369 /** 1370 * Callback which passes back the recovery document and its possible 1371 * policies. Also passes back the version of the document for the user 1372 * to check. 1373 * 1374 * We find the selected challenge and try to answer it (or begin 1375 * the process). 1376 * 1377 * @param cls a `struct SelectChallengeContext *` 1378 * @param ri recovery information struct which contains the policies 1379 */ 1380 static void 1381 pay_challenge_cb (void *cls, 1382 const struct ANASTASIS_RecoveryInformation *ri) 1383 { 1384 struct SelectChallengeContext *sctx = cls; 1385 json_t *challenge; 1386 1387 if (NULL == ri) 1388 { 1389 GNUNET_break_op (0); 1390 ANASTASIS_redux_fail_ (sctx->cb, 1391 sctx->cb_cls, 1392 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1393 "recovery information could not be deserialized"); 1394 sctx_free (sctx); 1395 return; 1396 } 1397 1398 challenge = find_challenge_in_ri (sctx->state, 1399 &sctx->uuid); 1400 if (NULL == challenge) 1401 { 1402 GNUNET_break_op (0); 1403 ANASTASIS_redux_fail_ (sctx->cb, 1404 sctx->cb_cls, 1405 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1406 "challenge not found"); 1407 sctx_free (sctx); 1408 return; 1409 } 1410 /* persist payment, in case we need to run the request again */ 1411 GNUNET_assert ( 1412 0 == 1413 json_object_set_new (challenge, 1414 "payment_secret", 1415 GNUNET_JSON_from_data_auto (&sctx->ps))); 1416 1417 for (unsigned int i = 0; i<ri->cs_len; i++) 1418 { 1419 struct ANASTASIS_Challenge *ci = ri->cs[i]; 1420 const struct ANASTASIS_ChallengeDetails *cd; 1421 int ret; 1422 1423 cd = ANASTASIS_challenge_get_details (ci); 1424 if (0 != 1425 GNUNET_memcmp (&sctx->uuid, 1426 &cd->uuid)) 1427 continue; 1428 if (cd->solved) 1429 { 1430 ANASTASIS_redux_fail_ (sctx->cb, 1431 sctx->cb_cls, 1432 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1433 "Selected challenge already solved"); 1434 sctx_free (sctx); 1435 return; 1436 } 1437 1438 if (0 == strcmp ("question", 1439 cd->type)) 1440 { 1441 /* security question, answer must be a string and already ready */ 1442 json_t *janswer = json_object_get (challenge, 1443 "answer"); 1444 const char *answer = json_string_value (janswer); 1445 1446 if (NULL == answer) 1447 { 1448 ANASTASIS_redux_fail_ (sctx->cb, 1449 sctx->cb_cls, 1450 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1451 "'answer' missing"); 1452 sctx_free (sctx); 1453 return; 1454 } 1455 ret = ANASTASIS_challenge_answer (ci, 1456 &sctx->ps, 1457 sctx->timeout, 1458 answer, 1459 &answer_feedback_cb, 1460 sctx); 1461 } 1462 else 1463 { 1464 ret = ANASTASIS_challenge_start (ci, 1465 &sctx->ps, 1466 &start_feedback_cb, 1467 sctx); 1468 } 1469 if (GNUNET_OK != ret) 1470 { 1471 ANASTASIS_redux_fail_ (sctx->cb, 1472 sctx->cb_cls, 1473 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1474 "Failed to begin answering challenge"); 1475 sctx_free (sctx); 1476 return; 1477 } 1478 return; /* await answer feedback */ 1479 } 1480 ANASTASIS_redux_fail_ (sctx->cb, 1481 sctx->cb_cls, 1482 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1483 "'uuid' not in list of challenges"); 1484 sctx_free (sctx); 1485 } 1486 1487 1488 /** 1489 * The user selected a challenge to be solved. Begin the solving 1490 * process. 1491 * 1492 * @param[in] state we are in 1493 * @param arguments our arguments with the solution 1494 * @param cb functiont o call with the new state 1495 * @param cb_cls closure for @a cb 1496 * @return handle to cancel challenge selection step 1497 */ 1498 static struct ANASTASIS_ReduxAction * 1499 solve_challenge (json_t *state, 1500 const json_t *arguments, 1501 ANASTASIS_ActionCallback cb, 1502 void *cb_cls) 1503 { 1504 struct SelectChallengeContext *sctx 1505 = GNUNET_new (struct SelectChallengeContext); 1506 json_t *rd; 1507 struct GNUNET_JSON_Specification spec[] = { 1508 GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid", 1509 &sctx->uuid), 1510 GNUNET_JSON_spec_end () 1511 }; 1512 1513 if (NULL == arguments) 1514 { 1515 ANASTASIS_redux_fail_ (cb, 1516 cb_cls, 1517 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1518 "arguments missing"); 1519 return NULL; 1520 } 1521 if (GNUNET_OK != 1522 GNUNET_JSON_parse (state, 1523 spec, 1524 NULL, NULL)) 1525 { 1526 ANASTASIS_redux_fail_ (cb, 1527 cb_cls, 1528 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1529 "'selected_challenge_uuid' missing"); 1530 return NULL; 1531 } 1532 rd = json_object_get (state, 1533 "recovery_document"); 1534 if (NULL == rd) 1535 { 1536 GNUNET_break_op (0); 1537 ANASTASIS_redux_fail_ (cb, 1538 cb_cls, 1539 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1540 "solve_challenge"); 1541 return NULL; 1542 } 1543 sctx->cb = cb; 1544 sctx->cb_cls = cb_cls; 1545 sctx->state = json_incref (state); 1546 sctx->args = json_incref ((json_t*) arguments); 1547 sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_, 1548 rd, 1549 &solve_challenge_cb, 1550 sctx, 1551 &core_secret_cb, 1552 sctx); 1553 if (NULL == sctx->r) 1554 { 1555 json_decref (sctx->state); 1556 json_decref (sctx->args); 1557 GNUNET_free (sctx); 1558 GNUNET_break_op (0); 1559 ANASTASIS_redux_fail_ (cb, 1560 cb_cls, 1561 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1562 "'recovery_document' invalid"); 1563 return NULL; 1564 } 1565 sctx->ra.cleanup = &sctx_free; 1566 sctx->ra.cleanup_cls = sctx; 1567 return &sctx->ra; 1568 } 1569 1570 1571 /** 1572 * The user asked for us to poll on pending 1573 * asynchronous challenges to see if they have 1574 * now completed / been satisfied. 1575 * 1576 * @param[in] state we are in 1577 * @param arguments our arguments with the solution 1578 * @param cb functiont o call with the new state 1579 * @param cb_cls closure for @a cb 1580 * @return handle to cancel challenge selection step 1581 */ 1582 static struct ANASTASIS_ReduxAction * 1583 poll_challenges (json_t *state, 1584 const json_t *arguments, 1585 ANASTASIS_ActionCallback cb, 1586 void *cb_cls) 1587 { 1588 struct SelectChallengeContext *sctx 1589 = GNUNET_new (struct SelectChallengeContext); 1590 json_t *rd; 1591 1592 rd = json_object_get (state, 1593 "recovery_document"); 1594 if (NULL == rd) 1595 { 1596 GNUNET_break_op (0); 1597 ANASTASIS_redux_fail_ (cb, 1598 cb_cls, 1599 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1600 "poll_challenges"); 1601 return NULL; 1602 } 1603 sctx->poll_only = true; 1604 sctx->cb = cb; 1605 sctx->cb_cls = cb_cls; 1606 sctx->state = json_incref (state); 1607 sctx->args = json_incref ((json_t*) arguments); 1608 sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_, 1609 rd, 1610 &solve_challenge_cb, 1611 sctx, 1612 &core_secret_cb, 1613 sctx); 1614 if (NULL == sctx->r) 1615 { 1616 json_decref (sctx->state); 1617 json_decref (sctx->args); 1618 GNUNET_free (sctx); 1619 GNUNET_break_op (0); 1620 ANASTASIS_redux_fail_ (cb, 1621 cb_cls, 1622 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1623 "'recovery_document' invalid"); 1624 return NULL; 1625 } 1626 sctx->ra.cleanup = &sctx_free; 1627 sctx->ra.cleanup_cls = sctx; 1628 return &sctx->ra; 1629 } 1630 1631 1632 /** 1633 * The user selected a challenge to be solved. Handle the payment 1634 * process. 1635 * 1636 * @param[in] state we are in 1637 * @param arguments our arguments with the solution 1638 * @param cb functiont o call with the new state 1639 * @param cb_cls closure for @a cb 1640 * @return handle to cancel challenge selection step 1641 */ 1642 static struct ANASTASIS_ReduxAction * 1643 pay_challenge (json_t *state, 1644 const json_t *arguments, 1645 ANASTASIS_ActionCallback cb, 1646 void *cb_cls) 1647 { 1648 struct SelectChallengeContext *sctx 1649 = GNUNET_new (struct SelectChallengeContext); 1650 json_t *rd; 1651 struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO; 1652 struct GNUNET_JSON_Specification spec[] = { 1653 GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid", 1654 &sctx->uuid), 1655 GNUNET_JSON_spec_end () 1656 }; 1657 struct GNUNET_JSON_Specification aspec[] = { 1658 GNUNET_JSON_spec_mark_optional ( 1659 GNUNET_JSON_spec_relative_time ("timeout", 1660 &timeout), 1661 NULL), 1662 GNUNET_JSON_spec_fixed_auto ("payment_secret", 1663 &sctx->ps), 1664 GNUNET_JSON_spec_end () 1665 }; 1666 1667 if (NULL == arguments) 1668 { 1669 ANASTASIS_redux_fail_ (cb, 1670 cb_cls, 1671 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1672 "arguments missing"); 1673 return NULL; 1674 } 1675 if (GNUNET_OK != 1676 GNUNET_JSON_parse (arguments, 1677 aspec, 1678 NULL, NULL)) 1679 { 1680 ANASTASIS_redux_fail_ (cb, 1681 cb_cls, 1682 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1683 "'payment_secret' missing"); 1684 return NULL; 1685 } 1686 if (GNUNET_OK != 1687 GNUNET_JSON_parse (state, 1688 spec, 1689 NULL, NULL)) 1690 { 1691 ANASTASIS_redux_fail_ (cb, 1692 cb_cls, 1693 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1694 "'selected_challenge_uuid' missing"); 1695 return NULL; 1696 } 1697 rd = json_object_get (state, 1698 "recovery_document"); 1699 if (NULL == rd) 1700 { 1701 GNUNET_break_op (0); 1702 ANASTASIS_redux_fail_ (cb, 1703 cb_cls, 1704 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1705 "pay_challenge"); 1706 return NULL; 1707 } 1708 sctx->timeout = timeout; 1709 sctx->cb = cb; 1710 sctx->cb_cls = cb_cls; 1711 sctx->state = json_incref (state); 1712 sctx->args = json_incref ((json_t*) arguments); 1713 sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_, 1714 rd, 1715 &pay_challenge_cb, 1716 sctx, 1717 &core_secret_cb, 1718 sctx); 1719 if (NULL == sctx->r) 1720 { 1721 json_decref (sctx->state); 1722 json_decref (sctx->args); 1723 GNUNET_free (sctx); 1724 GNUNET_break_op (0); 1725 ANASTASIS_redux_fail_ (cb, 1726 cb_cls, 1727 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1728 "'recovery_document' invalid"); 1729 return NULL; 1730 } 1731 sctx->ra.cleanup = &sctx_free; 1732 sctx->ra.cleanup_cls = sctx; 1733 return &sctx->ra; 1734 } 1735 1736 1737 /** 1738 * Callback which passes back the recovery document and its possible 1739 * policies. Also passes back the version of the document for the user 1740 * to check. 1741 * 1742 * We find the selected challenge and try to answer it (or begin 1743 * the process). 1744 * 1745 * @param cls a `struct SelectChallengeContext *` 1746 * @param ri recovery information struct which contains the policies 1747 */ 1748 static void 1749 select_challenge_cb (void *cls, 1750 const struct ANASTASIS_RecoveryInformation *ri) 1751 { 1752 struct SelectChallengeContext *sctx = cls; 1753 const struct ANASTASIS_PaymentSecretP *psp = NULL; 1754 struct ANASTASIS_PaymentSecretP ps; 1755 struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO; 1756 struct GNUNET_JSON_Specification tspec[] = { 1757 GNUNET_JSON_spec_mark_optional ( 1758 GNUNET_JSON_spec_relative_time ("timeout", 1759 &timeout), 1760 NULL), 1761 GNUNET_JSON_spec_end () 1762 }; 1763 struct GNUNET_JSON_Specification pspec[] = { 1764 GNUNET_JSON_spec_fixed_auto ("payment_secret", 1765 &ps), 1766 GNUNET_JSON_spec_end () 1767 }; 1768 1769 1770 if (NULL == ri) 1771 { 1772 GNUNET_break_op (0); 1773 ANASTASIS_redux_fail_ (sctx->cb, 1774 sctx->cb_cls, 1775 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1776 "recovery information could not be deserialized"); 1777 sctx_free (sctx); 1778 return; 1779 } 1780 1781 if (GNUNET_OK != 1782 GNUNET_JSON_parse (sctx->args, 1783 tspec, 1784 NULL, NULL)) 1785 { 1786 GNUNET_break_op (0); 1787 ANASTASIS_redux_fail_ (sctx->cb, 1788 sctx->cb_cls, 1789 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1790 "'timeout' malformed"); 1791 sctx_free (sctx); 1792 return; 1793 } 1794 1795 /* NOTE: do we need both ways to pass payment secrets? */ 1796 if (NULL != 1797 json_object_get (sctx->args, 1798 "payment_secret")) 1799 { 1800 /* check if we got payment secret in args */ 1801 if (GNUNET_OK != 1802 GNUNET_JSON_parse (sctx->args, 1803 pspec, 1804 NULL, NULL)) 1805 { 1806 GNUNET_break_op (0); 1807 ANASTASIS_redux_fail_ (sctx->cb, 1808 sctx->cb_cls, 1809 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1810 "'payment_secret' malformed"); 1811 sctx_free (sctx); 1812 return; 1813 } 1814 psp = &ps; 1815 } 1816 else 1817 { 1818 /* Check if we got a payment_secret in state */ 1819 json_t *challenge = find_challenge_in_ri (sctx->state, 1820 &sctx->uuid); 1821 1822 if (NULL == challenge) 1823 { 1824 GNUNET_break_op (0); 1825 ANASTASIS_redux_fail_ (sctx->cb, 1826 sctx->cb_cls, 1827 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1828 "challenge not found"); 1829 sctx_free (sctx); 1830 return; 1831 } 1832 if (NULL != 1833 json_object_get (challenge, 1834 "payment_secret")) 1835 { 1836 if (GNUNET_OK != 1837 GNUNET_JSON_parse (challenge, 1838 pspec, 1839 NULL, NULL)) 1840 { 1841 GNUNET_break_op (0); 1842 ANASTASIS_redux_fail_ (sctx->cb, 1843 sctx->cb_cls, 1844 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1845 "'payment_secret' malformed"); 1846 sctx_free (sctx); 1847 return; 1848 } 1849 psp = &ps; 1850 } 1851 } 1852 1853 for (unsigned int i = 0; i<ri->cs_len; i++) 1854 { 1855 struct ANASTASIS_Challenge *ci = ri->cs[i]; 1856 const struct ANASTASIS_ChallengeDetails *cd; 1857 int ret; 1858 1859 cd = ANASTASIS_challenge_get_details (ci); 1860 if (0 != 1861 GNUNET_memcmp (&sctx->uuid, 1862 &cd->uuid)) 1863 continue; 1864 if (cd->solved) 1865 { 1866 ANASTASIS_redux_fail_ (sctx->cb, 1867 sctx->cb_cls, 1868 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1869 "Selected challenge already solved"); 1870 sctx_free (sctx); 1871 return; 1872 } 1873 GNUNET_assert ( 1874 0 == 1875 json_object_set_new (sctx->state, 1876 "selected_challenge_uuid", 1877 GNUNET_JSON_from_data_auto (&cd->uuid))); 1878 if ( (0 == strcmp ("question", 1879 cd->type)) || 1880 (0 == strcmp ("totp", 1881 cd->type)) ) 1882 { 1883 /* security question or TOTP: 1884 immediately request user to answer it */ 1885 set_state (sctx->state, 1886 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); 1887 sctx->cb (sctx->cb_cls, 1888 TALER_EC_NONE, 1889 sctx->state); 1890 sctx_free (sctx); 1891 return; 1892 } 1893 /* trigger challenge */ 1894 { 1895 json_t *c = find_challenge_in_cs (sctx->state, 1896 &cd->uuid); 1897 json_t *pin = json_object_get (c, 1898 "answer-pin"); 1899 1900 if (NULL != pin) 1901 { 1902 uint64_t ianswer = json_integer_value (pin); 1903 1904 ret = ANASTASIS_challenge_answer2 (ci, 1905 psp, 1906 timeout, 1907 ianswer, 1908 &answer_feedback_cb, 1909 sctx); 1910 } 1911 else 1912 { 1913 ret = ANASTASIS_challenge_start (ci, 1914 psp, 1915 &start_feedback_cb, 1916 sctx); 1917 } 1918 } 1919 if (GNUNET_OK != ret) 1920 { 1921 ANASTASIS_redux_fail_ (sctx->cb, 1922 sctx->cb_cls, 1923 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1924 "Failed to begin answering challenge"); 1925 sctx_free (sctx); 1926 return; 1927 } 1928 return; /* await answer feedback */ 1929 } 1930 ANASTASIS_redux_fail_ (sctx->cb, 1931 sctx->cb_cls, 1932 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1933 "'uuid' not in list of challenges"); 1934 sctx_free (sctx); 1935 } 1936 1937 1938 /** 1939 * The user selected a challenge to be solved. Begin the solving 1940 * process. 1941 * 1942 * @param[in] state we are in 1943 * @param arguments our arguments with the solution 1944 * @param cb functiont o call with the new state 1945 * @param cb_cls closure for @a cb 1946 * @return handle to cancel challenge selection step 1947 */ 1948 static struct ANASTASIS_ReduxAction * 1949 select_challenge (json_t *state, 1950 const json_t *arguments, 1951 ANASTASIS_ActionCallback cb, 1952 void *cb_cls) 1953 { 1954 struct SelectChallengeContext *sctx 1955 = GNUNET_new (struct SelectChallengeContext); 1956 json_t *rd; 1957 struct GNUNET_JSON_Specification spec[] = { 1958 GNUNET_JSON_spec_fixed_auto ("uuid", 1959 &sctx->uuid), 1960 GNUNET_JSON_spec_end () 1961 }; 1962 1963 if (NULL == arguments) 1964 { 1965 ANASTASIS_redux_fail_ (cb, 1966 cb_cls, 1967 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1968 "arguments missing"); 1969 return NULL; 1970 } 1971 if (GNUNET_OK != 1972 GNUNET_JSON_parse (arguments, 1973 spec, 1974 NULL, NULL)) 1975 { 1976 ANASTASIS_redux_fail_ (cb, 1977 cb_cls, 1978 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 1979 "'uuid' missing"); 1980 return NULL; 1981 } 1982 rd = json_object_get (state, 1983 "recovery_document"); 1984 if (NULL == rd) 1985 { 1986 GNUNET_break_op (0); 1987 ANASTASIS_redux_fail_ (cb, 1988 cb_cls, 1989 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1990 "select_challenge"); 1991 return NULL; 1992 } 1993 sctx->cb = cb; 1994 sctx->cb_cls = cb_cls; 1995 sctx->state = json_incref (state); 1996 sctx->args = json_incref ((json_t*) arguments); 1997 sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_, 1998 rd, 1999 &select_challenge_cb, 2000 sctx, 2001 &core_secret_cb, 2002 sctx); 2003 if (NULL == sctx->r) 2004 { 2005 json_decref (sctx->state); 2006 json_decref (sctx->args); 2007 GNUNET_free (sctx); 2008 GNUNET_break_op (0); 2009 ANASTASIS_redux_fail_ (cb, 2010 cb_cls, 2011 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 2012 "'recovery_document' invalid"); 2013 return NULL; 2014 } 2015 sctx->ra.cleanup = &sctx_free; 2016 sctx->ra.cleanup_cls = sctx; 2017 return &sctx->ra; 2018 } 2019 2020 2021 /** 2022 * The user pressed "back" during challenge solving. 2023 * Transition back to selecting another challenge. 2024 * 2025 * @param[in] state we are in 2026 * @param arguments our arguments (unused) 2027 * @param cb functiont o call with the new state 2028 * @param cb_cls closure for @a cb 2029 * @return NULL (synchronous operation) 2030 */ 2031 static struct ANASTASIS_ReduxAction * 2032 back_challenge_solving (json_t *state, 2033 const json_t *arguments, 2034 ANASTASIS_ActionCallback cb, 2035 void *cb_cls) 2036 { 2037 (void) arguments; 2038 GNUNET_assert (0 == 2039 json_object_del (state, 2040 "selected_challenge_uuid")); 2041 set_state (state, 2042 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); 2043 cb (cb_cls, 2044 TALER_EC_NONE, 2045 state); 2046 return NULL; 2047 } 2048 2049 2050 /** 2051 * State for a "policy download" as part of a recovery operation. 2052 */ 2053 struct PolicyDownloadEntry 2054 { 2055 2056 /** 2057 * Redux action handle associated with this state. 2058 */ 2059 struct ANASTASIS_ReduxAction ra; 2060 2061 /** 2062 * Backend we are querying. 2063 */ 2064 char *backend_url; 2065 2066 /** 2067 * The /policy GET operation handle. 2068 */ 2069 struct ANASTASIS_Recovery *recovery; 2070 2071 /** 2072 * Function to call with the result. 2073 */ 2074 ANASTASIS_ActionCallback cb; 2075 2076 /** 2077 * Closure for @e cb. 2078 */ 2079 void *cb_cls; 2080 2081 /** 2082 * State we are using. 2083 */ 2084 json_t *state; 2085 2086 }; 2087 2088 2089 /** 2090 * Free @a cls data structure. 2091 * 2092 * @param[in] cls data structure to free, must be a `struct PolicyDownloadEntry *` 2093 */ 2094 static void 2095 free_pd (void *cls) 2096 { 2097 struct PolicyDownloadEntry *pd = cls; 2098 2099 if (NULL != pd->recovery) 2100 { 2101 ANASTASIS_recovery_abort (pd->recovery); 2102 pd->recovery = NULL; 2103 } 2104 GNUNET_free (pd->backend_url); 2105 json_decref (pd->state); 2106 GNUNET_free (pd); 2107 } 2108 2109 2110 /** 2111 * We failed to download a policy. Show an error to the user and 2112 * allow the user to specify alternative providers and/or policy 2113 * versions. 2114 * 2115 * @param[in] pd state to fail with the policy download 2116 * @param offline true of the reason to show is that all providers 2117 * were offline / did not return a salt to us 2118 */ 2119 static void 2120 return_no_policy (struct PolicyDownloadEntry *pd, 2121 bool offline) 2122 { 2123 enum TALER_ErrorCode ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED; 2124 const char *detail = (offline) 2125 ? "could not contact provider (offline)" 2126 : "provider does not know this policy"; 2127 json_t *estate; 2128 2129 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2130 "Provider offline!\n"); 2131 estate = GNUNET_JSON_PACK ( 2132 GNUNET_JSON_pack_allow_null ( 2133 GNUNET_JSON_pack_string ("detail", 2134 detail)), 2135 GNUNET_JSON_pack_uint64 ("code", 2136 ec), 2137 GNUNET_JSON_pack_string ("hint", 2138 TALER_ErrorCode_get_hint (ec))); 2139 pd->cb (pd->cb_cls, 2140 ec, 2141 estate); 2142 free_pd (pd); 2143 } 2144 2145 2146 /** 2147 * Callback which passes back the recovery document and its possible 2148 * policies. Also passes back the version of the document for the user 2149 * to check. 2150 * 2151 * Once the first policy lookup succeeds, we update our state and 2152 * cancel all of the others, passing the obtained recovery information 2153 * back to the user. 2154 * 2155 * @param cls closure for the callback with a `struct PolicyDownloadEntry *` 2156 * @param ri recovery information struct which contains the policies 2157 */ 2158 static void 2159 policy_lookup_cb (void *cls, 2160 const struct ANASTASIS_RecoveryInformation *ri) 2161 { 2162 struct PolicyDownloadEntry *pd = cls; 2163 json_t *policies; 2164 json_t *challenges; 2165 json_t *recovery_information; 2166 2167 if (NULL == ri) 2168 { 2169 /* Woopsie, failed hard. */ 2170 ANASTASIS_recovery_abort (pd->recovery); 2171 GNUNET_free (pd->backend_url); 2172 GNUNET_free (pd); 2173 return_no_policy (pd, 2174 false); 2175 return; 2176 } 2177 policies = json_array (); 2178 GNUNET_assert (NULL != policies); 2179 for (unsigned int i = 0; i<ri->dps_len; i++) 2180 { 2181 struct ANASTASIS_DecryptionPolicy *dps = ri->dps[i]; 2182 json_t *pchallenges; 2183 2184 pchallenges = json_array (); 2185 GNUNET_assert (NULL != pchallenges); 2186 for (unsigned int j = 0; j<dps->challenges_length; j++) 2187 { 2188 struct ANASTASIS_Challenge *c = dps->challenges[j]; 2189 const struct ANASTASIS_ChallengeDetails *cd; 2190 json_t *cj; 2191 2192 cd = ANASTASIS_challenge_get_details (c); 2193 cj = GNUNET_JSON_PACK ( 2194 GNUNET_JSON_pack_data_auto ("uuid", 2195 &cd->uuid)); 2196 GNUNET_assert (0 == 2197 json_array_append_new (pchallenges, 2198 cj)); 2199 2200 } 2201 GNUNET_assert (0 == 2202 json_array_append_new (policies, 2203 pchallenges)); 2204 } /* end for all policies */ 2205 challenges = json_array (); 2206 GNUNET_assert (NULL != challenges); 2207 for (unsigned int i = 0; i<ri->cs_len; i++) 2208 { 2209 struct ANASTASIS_Challenge *c = ri->cs[i]; 2210 const struct ANASTASIS_ChallengeDetails *cd; 2211 json_t *cj; 2212 2213 cd = ANASTASIS_challenge_get_details (c); 2214 cj = GNUNET_JSON_PACK ( 2215 GNUNET_JSON_pack_data_auto ("uuid", 2216 &cd->uuid), 2217 GNUNET_JSON_pack_string ("type", 2218 cd->type), 2219 GNUNET_JSON_pack_string ("uuid-display", 2220 ANASTASIS_CRYPTO_uuid2s (&cd->uuid)), 2221 GNUNET_JSON_pack_string ("instructions", 2222 cd->instructions)); 2223 GNUNET_assert (0 == 2224 json_array_append_new (challenges, 2225 cj)); 2226 } /* end for all challenges */ 2227 recovery_information = GNUNET_JSON_PACK ( 2228 GNUNET_JSON_pack_array_steal ("challenges", 2229 challenges), 2230 GNUNET_JSON_pack_array_steal ("policies", 2231 policies), 2232 GNUNET_JSON_pack_allow_null ( 2233 GNUNET_JSON_pack_string ("secret_name", 2234 ri->secret_name)), 2235 GNUNET_JSON_pack_string ("provider_url", 2236 pd->backend_url), 2237 GNUNET_JSON_pack_uint64 ("version", 2238 ri->version)); 2239 GNUNET_assert (0 == 2240 json_object_set_new (pd->state, 2241 "recovery_information", 2242 recovery_information)); 2243 { 2244 json_t *rd; 2245 2246 rd = ANASTASIS_recovery_serialize (pd->recovery); 2247 if (NULL == rd) 2248 { 2249 GNUNET_break (0); 2250 ANASTASIS_redux_fail_ (pd->cb, 2251 pd->cb_cls, 2252 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 2253 "unable to serialize recovery state"); 2254 free_pd (pd); 2255 return; 2256 } 2257 GNUNET_assert (0 == 2258 json_object_set_new (pd->state, 2259 "recovery_document", 2260 rd)); 2261 } 2262 set_state (pd->state, 2263 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); 2264 pd->cb (pd->cb_cls, 2265 TALER_EC_NONE, 2266 pd->state); 2267 free_pd (pd); 2268 } 2269 2270 2271 /** 2272 * This function is called whenever the recovery process ends. 2273 * In this case, that should not be possible as this callback 2274 * is used before we even begin with the challenges. So if 2275 * we are called, it is because of some fatal error. 2276 * 2277 * @param cls a `struct PolicyDownloadEntry` 2278 * @param rc error code 2279 * @param secret contains the core secret which is passed to the user 2280 * @param secret_size defines the size of the core secret 2281 */ 2282 static void 2283 core_early_secret_cb (void *cls, 2284 enum ANASTASIS_RecoveryStatus rc, 2285 const void *secret, 2286 size_t secret_size) 2287 { 2288 struct PolicyDownloadEntry *pd = cls; 2289 2290 pd->recovery = NULL; 2291 GNUNET_assert (NULL == secret); 2292 GNUNET_assert (ANASTASIS_RS_SUCCESS != rc); 2293 fail_by_error (pd->cb, 2294 pd->cb_cls, 2295 rc); 2296 free_pd (pd); 2297 } 2298 2299 2300 /** 2301 * DispatchHandler/Callback function which is called for a 2302 * "next" action in "secret_selecting" state. 2303 * 2304 * @param state state to operate on 2305 * @param arguments arguments to use for operation on state 2306 * @param cb callback to call during/after operation 2307 * @param cb_cls callback closure 2308 * @return NULL 2309 */ 2310 static struct ANASTASIS_ReduxAction * 2311 done_secret_selecting (json_t *state, 2312 const json_t *arguments, 2313 ANASTASIS_ActionCallback cb, 2314 void *cb_cls) 2315 { 2316 uint32_t mask; 2317 const json_t *pa; 2318 struct GNUNET_JSON_Specification spec[] = { 2319 GNUNET_JSON_spec_uint32 ("attribute_mask", 2320 &mask), 2321 GNUNET_JSON_spec_array_const ("providers", 2322 &pa), 2323 GNUNET_JSON_spec_end () 2324 }; 2325 struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; 2326 struct GNUNET_JSON_Specification pspec[] = { 2327 GNUNET_JSON_spec_fixed_auto ("provider_salt", 2328 &provider_salt), 2329 GNUNET_JSON_spec_end () 2330 }; 2331 json_t *p_cfg; 2332 json_t *id_data; 2333 const json_t *providers; 2334 2335 if (GNUNET_OK != 2336 GNUNET_JSON_parse (arguments, 2337 spec, 2338 NULL, NULL)) 2339 { 2340 GNUNET_break (0); 2341 json_dumpf (arguments, 2342 stderr, 2343 JSON_INDENT (2)); 2344 ANASTASIS_redux_fail_ (cb, 2345 cb_cls, 2346 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 2347 NULL); 2348 return NULL; 2349 } 2350 providers = json_object_get (state, 2351 "authentication_providers"); 2352 if ( (NULL == providers) || 2353 (! json_is_object (providers)) ) 2354 { 2355 GNUNET_break (0); 2356 ANASTASIS_redux_fail_ (cb, 2357 cb_cls, 2358 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 2359 "'authentication_providers' missing"); 2360 return NULL; 2361 } 2362 2363 { 2364 size_t poff; 2365 json_t *pe; 2366 uint64_t version; 2367 const char *provider_url; 2368 2369 json_array_foreach (pa, poff, pe) 2370 { 2371 struct GNUNET_JSON_Specification ispec[] = { 2372 GNUNET_JSON_spec_uint64 ("version", 2373 &version), 2374 GNUNET_JSON_spec_string ("url", 2375 &provider_url), 2376 GNUNET_JSON_spec_end () 2377 }; 2378 2379 if (GNUNET_OK != 2380 GNUNET_JSON_parse (pe, 2381 ispec, 2382 NULL, NULL)) 2383 { 2384 GNUNET_break (0); 2385 json_dumpf (pe, 2386 stderr, 2387 JSON_INDENT (2)); 2388 ANASTASIS_redux_fail_ (cb, 2389 cb_cls, 2390 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 2391 NULL); 2392 return NULL; 2393 } 2394 2395 p_cfg = json_object_get (providers, 2396 provider_url); 2397 if (MHD_HTTP_OK != 2398 json_integer_value (json_object_get (p_cfg, 2399 "http_status"))) 2400 continue; 2401 if (GNUNET_OK != 2402 GNUNET_JSON_parse (p_cfg, 2403 pspec, 2404 NULL, NULL)) 2405 { 2406 GNUNET_break (0); /* should be impossible for well-formed state */ 2407 ANASTASIS_redux_fail_ (cb, 2408 cb_cls, 2409 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 2410 "Salt unknown for selected provider"); 2411 return NULL; 2412 } 2413 id_data = json_object_get (state, 2414 "identity_attributes"); 2415 if (NULL == id_data) 2416 { 2417 GNUNET_break (0); /* should be impossible for well-formed state */ 2418 ANASTASIS_redux_fail_ (cb, 2419 cb_cls, 2420 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 2421 "'identity_attributes' missing"); 2422 return NULL; 2423 } 2424 { 2425 struct PolicyDownloadEntry *pd 2426 = GNUNET_new (struct PolicyDownloadEntry); 2427 2428 pd->cb = cb; 2429 pd->cb_cls = cb_cls; 2430 pd->state = json_incref (state); 2431 pd->backend_url = GNUNET_strdup (provider_url); 2432 pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_, 2433 id_data, 2434 version, 2435 pd->backend_url, 2436 &provider_salt, 2437 &policy_lookup_cb, 2438 pd, 2439 &core_early_secret_cb, 2440 pd); 2441 if (NULL == pd->recovery) 2442 { 2443 GNUNET_break (0); 2444 ANASTASIS_redux_fail_ (cb, 2445 cb_cls, 2446 TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, 2447 NULL); 2448 GNUNET_free (pd->backend_url); 2449 json_decref (pd->state); 2450 GNUNET_free (pd); 2451 return NULL; 2452 } 2453 pd->ra.cleanup = &free_pd; 2454 pd->ra.cleanup_cls = pd; 2455 return &pd->ra; 2456 } 2457 } 2458 } 2459 2460 /* no provider worked */ 2461 ANASTASIS_redux_fail_ (cb, 2462 cb_cls, 2463 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 2464 "selected provider is not online"); 2465 return NULL; 2466 } 2467 2468 2469 /** 2470 * The user wants us to add another provider. Download /config. 2471 * 2472 * @param[in] state we are in 2473 * @param arguments our arguments with the solution 2474 * @param cb function to call with the new state 2475 * @param cb_cls closure for @a cb 2476 * @return handle to cancel challenge selection step 2477 */ 2478 static struct ANASTASIS_ReduxAction * 2479 add_provider (json_t *state, 2480 const json_t *arguments, 2481 ANASTASIS_ActionCallback cb, 2482 void *cb_cls) 2483 { 2484 const char *provider_url; 2485 struct GNUNET_JSON_Specification spec[] = { 2486 GNUNET_JSON_spec_string ("provider_url", 2487 &provider_url), 2488 GNUNET_JSON_spec_end () 2489 }; 2490 2491 if (GNUNET_OK != 2492 GNUNET_JSON_parse (arguments, 2493 spec, 2494 NULL, NULL)) 2495 { 2496 GNUNET_break (0); 2497 ANASTASIS_redux_fail_ (cb, 2498 cb_cls, 2499 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, 2500 NULL); 2501 return NULL; 2502 } 2503 return ANASTASIS_REDUX_add_provider_to_state_ (provider_url, 2504 state, 2505 cb, 2506 cb_cls); 2507 } 2508 2509 2510 /** 2511 * Signature of callback function that implements a state transition. 2512 * 2513 * @param state current state 2514 * @param arguments arguments for the state transition 2515 * @param cb function to call when done 2516 * @param cb_cls closure for @a cb 2517 */ 2518 typedef struct ANASTASIS_ReduxAction * 2519 (*DispatchHandler)(json_t *state, 2520 const json_t *arguments, 2521 ANASTASIS_ActionCallback cb, 2522 void *cb_cls); 2523 2524 2525 struct ANASTASIS_ReduxAction * 2526 ANASTASIS_recovery_action_ (json_t *state, 2527 const char *action, 2528 const json_t *arguments, 2529 ANASTASIS_ActionCallback cb, 2530 void *cb_cls) 2531 { 2532 struct Dispatcher 2533 { 2534 enum ANASTASIS_RecoveryState recovery_state; 2535 const char *recovery_action; 2536 DispatchHandler fun; 2537 } dispatchers[] = { 2538 { 2539 ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, 2540 "add_provider", 2541 &add_provider 2542 }, 2543 { 2544 ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, 2545 "poll_providers", 2546 &ANASTASIS_REDUX_poll_providers_ 2547 }, 2548 { 2549 ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, 2550 "select_version", 2551 &done_secret_selecting 2552 }, 2553 { 2554 ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, 2555 "back", 2556 &ANASTASIS_back_generic_decrement_ 2557 }, 2558 { 2559 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, 2560 "select_challenge", 2561 &select_challenge 2562 }, 2563 { 2564 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, 2565 "sync_providers", 2566 &ANASTASIS_REDUX_sync_providers_ 2567 }, 2568 { 2569 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, 2570 "poll", 2571 &poll_challenges 2572 }, 2573 { 2574 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, 2575 "back", 2576 &ANASTASIS_back_generic_decrement_ 2577 }, 2578 { 2579 ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, 2580 "pay", 2581 &pay_challenge 2582 }, 2583 { 2584 ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, 2585 "back", 2586 &ANASTASIS_back_generic_decrement_ 2587 }, 2588 { 2589 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, 2590 "solve_challenge", 2591 &solve_challenge 2592 }, 2593 { 2594 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, 2595 "back", 2596 &back_challenge_solving 2597 }, 2598 { ANASTASIS_RECOVERY_STATE_INVALID, NULL, NULL } 2599 }; 2600 const char *s = json_string_value (json_object_get (state, 2601 "recovery_state")); 2602 enum ANASTASIS_RecoveryState rs; 2603 2604 GNUNET_assert (NULL != s); 2605 rs = ANASTASIS_recovery_state_from_string_ (s); 2606 if (ANASTASIS_RECOVERY_STATE_INVALID == rs) 2607 { 2608 ANASTASIS_redux_fail_ (cb, 2609 cb_cls, 2610 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 2611 "'recovery_state' field invalid"); 2612 return NULL; 2613 } 2614 for (unsigned int i = 0; NULL != dispatchers[i].fun; i++) 2615 { 2616 if ( (rs == dispatchers[i].recovery_state) && 2617 (0 == strcmp (action, 2618 dispatchers[i].recovery_action)) ) 2619 { 2620 return dispatchers[i].fun (state, 2621 arguments, 2622 cb, 2623 cb_cls); 2624 } 2625 } 2626 ANASTASIS_redux_fail_ (cb, 2627 cb_cls, 2628 TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID, 2629 action); 2630 return NULL; 2631 } 2632 2633 2634 struct ANASTASIS_ReduxAction * 2635 ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state, 2636 const json_t *arguments, 2637 ANASTASIS_ActionCallback cb, 2638 void *cb_cls) 2639 { 2640 const json_t *providers; 2641 json_t *attributes; 2642 2643 providers = json_object_get (state, 2644 "authentication_providers"); 2645 if ( (NULL == providers) || 2646 (! json_is_object (providers)) ) 2647 { 2648 GNUNET_break (0); 2649 ANASTASIS_redux_fail_ (cb, 2650 cb_cls, 2651 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 2652 "'authentication_providers' missing"); 2653 return NULL; 2654 } 2655 attributes = json_object_get (arguments, 2656 "identity_attributes"); 2657 if ( (NULL == attributes) || 2658 (! json_is_object (attributes)) ) 2659 { 2660 GNUNET_break (0); 2661 ANASTASIS_redux_fail_ (cb, 2662 cb_cls, 2663 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 2664 "'identity_attributes' missing"); 2665 return NULL; 2666 } 2667 GNUNET_assert (0 == 2668 json_object_set (state, 2669 "identity_attributes", 2670 attributes)); 2671 set_state (state, 2672 ANASTASIS_RECOVERY_STATE_SECRET_SELECTING); 2673 cb (cb_cls, 2674 TALER_EC_NONE, 2675 state); 2676 return NULL; 2677 }