anastasis_api_redux.c (59671B)
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_OS_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_OS_process_wait_status (red_cls->reducer_process, 1659 &type, 1660 &code); 1661 1662 GNUNET_assert (GNUNET_SYSERR != pwret); 1663 if (GNUNET_NO == pwret) 1664 { 1665 GNUNET_assert (0 == 1666 GNUNET_OS_process_kill (red_cls->reducer_process, 1667 SIGTERM)); 1668 GNUNET_assert (GNUNET_SYSERR != 1669 GNUNET_OS_process_wait (red_cls->reducer_process)); 1670 } 1671 1672 GNUNET_OS_process_destroy (red_cls->reducer_process); 1673 red_cls->reducer_process = NULL; 1674 } 1675 1676 GNUNET_free (red_cls); 1677 } 1678 1679 1680 /** 1681 * Task called when 1682 * 1683 * @param cls closure, a 'struct ExternalReducerCls *' 1684 */ 1685 static void 1686 external_reducer_read_cb (void *cls) 1687 { 1688 struct ExternalReducerCls *red_cls = cls; 1689 ssize_t sret; 1690 char buf[256]; 1691 1692 red_cls->read_task = NULL; 1693 1694 sret = GNUNET_DISK_file_read (GNUNET_DISK_pipe_handle ( 1695 red_cls->reducer_stdout, 1696 GNUNET_DISK_PIPE_END_READ), 1697 buf, 1698 256); 1699 if (sret < 0) 1700 { 1701 GNUNET_break (0); 1702 red_cls->action_cb (red_cls->action_cb_cls, 1703 TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, 1704 NULL); 1705 cleanup_external_reducer (red_cls); 1706 return; 1707 } 1708 else if (0 == sret) 1709 { 1710 char *str = GNUNET_buffer_reap_str (&red_cls->read_buffer); 1711 json_t *json; 1712 1713 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1714 "Got external reducer response: '%s'\n", 1715 str); 1716 1717 json = json_loads (str, 0, NULL); 1718 1719 if (NULL == json) 1720 { 1721 GNUNET_break (0); 1722 red_cls->action_cb (red_cls->action_cb_cls, 1723 TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, 1724 NULL); 1725 cleanup_external_reducer (red_cls); 1726 return; 1727 } 1728 1729 { 1730 enum TALER_ErrorCode ec; 1731 ec = json_integer_value (json_object_get (json, "code")); 1732 1733 red_cls->action_cb (red_cls->action_cb_cls, 1734 ec, 1735 json); 1736 } 1737 cleanup_external_reducer (red_cls); 1738 return; 1739 } 1740 else 1741 { 1742 GNUNET_buffer_write (&red_cls->read_buffer, 1743 buf, 1744 sret); 1745 1746 red_cls->read_task = GNUNET_SCHEDULER_add_read_file ( 1747 GNUNET_TIME_UNIT_FOREVER_REL, 1748 GNUNET_DISK_pipe_handle ( 1749 red_cls->reducer_stdout, 1750 GNUNET_DISK_PIPE_END_READ), 1751 external_reducer_read_cb, 1752 red_cls); 1753 } 1754 } 1755 1756 1757 /** 1758 * Handle an action using an external reducer, i.e. 1759 * by shelling out to another process. 1760 */ 1761 static struct ANASTASIS_ReduxAction * 1762 redux_action_external (const char *ext_reducer, 1763 const json_t *state, 1764 const char *action, 1765 const json_t *arguments, 1766 ANASTASIS_ActionCallback cb, 1767 void *cb_cls) 1768 { 1769 char *arg_str; 1770 char *state_str = json_dumps (state, JSON_COMPACT); 1771 ssize_t sret; 1772 struct ExternalReducerCls *red_cls = GNUNET_new (struct ExternalReducerCls); 1773 1774 if (NULL == arguments) 1775 arg_str = GNUNET_strdup ("{}"); 1776 else 1777 arg_str = json_dumps (arguments, JSON_COMPACT); 1778 1779 red_cls->action_cb = cb; 1780 red_cls->action_cb_cls = cb_cls; 1781 1782 GNUNET_assert (NULL != (red_cls->reducer_stdin = GNUNET_DISK_pipe ( 1783 GNUNET_DISK_PF_NONE))); 1784 GNUNET_assert (NULL != (red_cls->reducer_stdout = GNUNET_DISK_pipe ( 1785 GNUNET_DISK_PF_NONE))); 1786 1787 /* By the time we're here, this variable should be unset, because 1788 otherwise using anastasis-reducer as the external reducer 1789 will lead to infinite recursion. */ 1790 GNUNET_assert (NULL == getenv ("ANASTASIS_EXTERNAL_REDUCER")); 1791 1792 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1793 "Starting external reducer with action '%s' and argument '%s'\n", 1794 action, 1795 arg_str); 1796 1797 red_cls->reducer_process = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, 1798 red_cls->reducer_stdin, 1799 red_cls->reducer_stdout, 1800 NULL, 1801 ext_reducer, 1802 ext_reducer, 1803 "-a", 1804 arg_str, 1805 action, 1806 NULL); 1807 1808 GNUNET_free (arg_str); 1809 1810 if (NULL == red_cls->reducer_process) 1811 { 1812 GNUNET_break (0); 1813 GNUNET_free (state_str); 1814 cleanup_external_reducer (red_cls); 1815 return NULL; 1816 } 1817 1818 /* Close pipe ends we don't use. */ 1819 GNUNET_assert (GNUNET_OK == 1820 GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin, 1821 GNUNET_DISK_PIPE_END_READ)); 1822 GNUNET_assert (GNUNET_OK == 1823 GNUNET_DISK_pipe_close_end (red_cls->reducer_stdout, 1824 GNUNET_DISK_PIPE_END_WRITE)); 1825 1826 sret = GNUNET_DISK_file_write_blocking (GNUNET_DISK_pipe_handle ( 1827 red_cls->reducer_stdin, 1828 GNUNET_DISK_PIPE_END_WRITE), 1829 state_str, 1830 strlen (state_str)); 1831 GNUNET_free (state_str); 1832 if (sret <= 0) 1833 { 1834 GNUNET_break (0); 1835 cleanup_external_reducer (red_cls); 1836 return NULL; 1837 } 1838 1839 GNUNET_assert (GNUNET_OK == 1840 GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin, 1841 GNUNET_DISK_PIPE_END_WRITE)); 1842 1843 red_cls->read_task = GNUNET_SCHEDULER_add_read_file ( 1844 GNUNET_TIME_UNIT_FOREVER_REL, 1845 GNUNET_DISK_pipe_handle ( 1846 red_cls->reducer_stdout, 1847 GNUNET_DISK_PIPE_END_READ), 1848 external_reducer_read_cb, 1849 red_cls); 1850 1851 { 1852 struct ANASTASIS_ReduxAction *ra = GNUNET_new (struct 1853 ANASTASIS_ReduxAction); 1854 ra->cleanup_cls = red_cls; 1855 ra->cleanup = cleanup_external_reducer; 1856 return ra; 1857 } 1858 } 1859 1860 1861 struct ANASTASIS_ReduxAction * 1862 ANASTASIS_redux_action (const json_t *state, 1863 const char *action, 1864 const json_t *arguments, 1865 ANASTASIS_ActionCallback cb, 1866 void *cb_cls) 1867 { 1868 struct Dispatcher 1869 { 1870 enum ANASTASIS_GenericState redux_state; 1871 const char *redux_action; 1872 DispatchHandler fun; 1873 } dispatchers[] = { 1874 { 1875 ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING, 1876 "select_continent", 1877 &select_continent 1878 }, 1879 /* Deprecated alias for "back" from that state, should be removed eventually. */ 1880 { 1881 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, 1882 "unselect_continent", 1883 &unselect_continent 1884 }, 1885 { 1886 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, 1887 "back", 1888 &unselect_continent 1889 }, 1890 { 1891 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, 1892 "select_country", 1893 &select_country 1894 }, 1895 { 1896 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, 1897 "select_continent", 1898 &select_continent 1899 }, 1900 { 1901 ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING, 1902 "enter_user_attributes", 1903 &enter_user_attributes 1904 }, 1905 { 1906 ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING, 1907 "add_provider", 1908 &add_provider 1909 }, 1910 { 1911 ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING, 1912 "back", 1913 &ANASTASIS_back_generic_decrement_ 1914 }, 1915 { ANASTASIS_GENERIC_STATE_INVALID, NULL, NULL } 1916 }; 1917 bool recovery_mode = false; 1918 const char *s = json_string_value (json_object_get (state, 1919 "backup_state")); 1920 enum ANASTASIS_GenericState gs; 1921 1922 /* If requested, handle action with external reducer, used for testing. */ 1923 { 1924 const char *ext_reducer = ANASTASIS_REDUX_probe_external_reducer (); 1925 if (NULL != ext_reducer) 1926 return redux_action_external (ext_reducer, 1927 state, 1928 action, 1929 arguments, 1930 cb, 1931 cb_cls); 1932 } 1933 1934 if (NULL == s) 1935 { 1936 s = json_string_value (json_object_get (state, 1937 "recovery_state")); 1938 if (NULL == s) 1939 { 1940 GNUNET_break_op (0); 1941 cb (cb_cls, 1942 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, 1943 NULL); 1944 return NULL; 1945 } 1946 recovery_mode = true; 1947 } 1948 gs = ANASTASIS_generic_state_from_string_ (s); 1949 { 1950 json_t *new_state; 1951 struct ANASTASIS_ReduxAction *ret; 1952 1953 new_state = json_deep_copy (state); 1954 GNUNET_assert (NULL != new_state); 1955 if (gs != ANASTASIS_GENERIC_STATE_INVALID) 1956 { 1957 for (unsigned int i = 0; NULL != dispatchers[i].fun; i++) 1958 { 1959 if ( (gs == dispatchers[i].redux_state) && 1960 (0 == strcmp (action, 1961 dispatchers[i].redux_action)) ) 1962 { 1963 ret = dispatchers[i].fun (new_state, 1964 arguments, 1965 cb, 1966 cb_cls); 1967 json_decref (new_state); 1968 return ret; 1969 } 1970 } 1971 } 1972 if (recovery_mode) 1973 { 1974 ret = ANASTASIS_recovery_action_ (new_state, 1975 action, 1976 arguments, 1977 cb, 1978 cb_cls); 1979 } 1980 else 1981 { 1982 ret = ANASTASIS_backup_action_ (new_state, 1983 action, 1984 arguments, 1985 cb, 1986 cb_cls); 1987 } 1988 json_decref (new_state); 1989 return ret; 1990 } 1991 } 1992 1993 1994 void 1995 ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra) 1996 { 1997 ra->cleanup (ra->cleanup_cls); 1998 } 1999 2000 2001 json_t * 2002 ANASTASIS_REDUX_load_continents_ () 2003 { 2004 const json_t *countries; 2005 json_t *continents; 2006 const json_t *rc = ANASTASIS_redux_countries_init_ (); 2007 2008 if (NULL == rc) 2009 { 2010 GNUNET_break (0); 2011 return NULL; 2012 } 2013 countries = json_object_get (rc, 2014 "countries"); 2015 if (NULL == countries) 2016 { 2017 GNUNET_break (0); 2018 return NULL; 2019 } 2020 continents = json_array (); 2021 GNUNET_assert (NULL != continents); 2022 2023 { 2024 json_t *country; 2025 size_t index; 2026 2027 json_array_foreach (countries, index, country) 2028 { 2029 json_t *ex = NULL; 2030 const json_t *continent; 2031 2032 continent = json_object_get (country, 2033 "continent"); 2034 if ( (NULL == continent) || 2035 (! json_is_string (continent)) ) 2036 { 2037 GNUNET_break (0); 2038 continue; 2039 } 2040 { 2041 size_t inner_index; 2042 json_t *inner_continent; 2043 2044 json_array_foreach (continents, inner_index, inner_continent) 2045 { 2046 const json_t *name; 2047 2048 name = json_object_get (inner_continent, 2049 "name"); 2050 if (1 == json_equal (continent, 2051 name)) 2052 { 2053 ex = inner_continent; 2054 break; 2055 } 2056 } 2057 } 2058 if (NULL == ex) 2059 { 2060 ex = GNUNET_JSON_PACK ( 2061 GNUNET_JSON_pack_string ("name", 2062 json_string_value (continent))); 2063 GNUNET_assert (0 == 2064 json_array_append_new (continents, 2065 ex)); 2066 } 2067 2068 { 2069 json_t *i18n_continent; 2070 json_t *name_ex; 2071 2072 i18n_continent = json_object_get (country, 2073 "continent_i18n"); 2074 name_ex = json_object_get (ex, 2075 "name_i18n"); 2076 if (NULL != i18n_continent) 2077 { 2078 const char *lang; 2079 json_t *trans; 2080 2081 json_object_foreach (i18n_continent, lang, trans) 2082 { 2083 if (NULL == name_ex) 2084 { 2085 name_ex = json_object (); 2086 GNUNET_assert (NULL != name_ex); 2087 GNUNET_assert (0 == 2088 json_object_set_new (ex, 2089 "name_i18n", 2090 name_ex)); 2091 } 2092 if (NULL == json_object_get (name_ex, 2093 lang)) 2094 { 2095 GNUNET_assert (0 == 2096 json_object_set (name_ex, 2097 lang, 2098 trans)); 2099 } 2100 } 2101 } 2102 } 2103 } 2104 } 2105 return GNUNET_JSON_PACK ( 2106 GNUNET_JSON_pack_array_steal ("continents", 2107 continents)); 2108 } 2109 2110 2111 /** 2112 * Lookup @a provider_salt of @a provider_url in @a state. 2113 * 2114 * @param state the state to inspect 2115 * @param provider_url provider to look into 2116 * @param[out] provider_salt value to extract 2117 * @return #GNUNET_OK on success 2118 */ 2119 enum GNUNET_GenericReturnValue 2120 ANASTASIS_reducer_lookup_salt ( 2121 const json_t *state, 2122 const char *provider_url, 2123 struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt) 2124 { 2125 const json_t *aps; 2126 const json_t *cfg; 2127 uint32_t http_status = 0; 2128 const char *status; 2129 bool no_salt; 2130 struct GNUNET_JSON_Specification spec[] = { 2131 GNUNET_JSON_spec_string ("status", 2132 &status), 2133 GNUNET_JSON_spec_mark_optional ( 2134 GNUNET_JSON_spec_uint32 ("http_status", 2135 &http_status), 2136 NULL), 2137 GNUNET_JSON_spec_mark_optional ( 2138 GNUNET_JSON_spec_fixed_auto ("provider_salt", 2139 provider_salt), 2140 &no_salt), 2141 GNUNET_JSON_spec_end () 2142 }; 2143 2144 aps = json_object_get (state, 2145 "authentication_providers"); 2146 if (NULL == aps) 2147 { 2148 GNUNET_break (0); 2149 return GNUNET_SYSERR; 2150 } 2151 cfg = json_object_get (aps, 2152 provider_url); 2153 if (NULL == cfg) 2154 { 2155 GNUNET_break (0); 2156 return GNUNET_SYSERR; 2157 } 2158 if (GNUNET_OK != 2159 GNUNET_JSON_parse (cfg, 2160 spec, 2161 NULL, NULL)) 2162 { 2163 /* provider not working */ 2164 GNUNET_break_op (0); 2165 return GNUNET_NO; 2166 } 2167 if (0 == strcmp (status, 2168 "disabled")) 2169 return GNUNET_NO; 2170 if (no_salt) 2171 return GNUNET_NO; 2172 return GNUNET_OK; 2173 }