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