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