anastasis-httpd.c (40965B)
1 /* 2 This file is part of Anastasis 3 (C) 2020-2025 Anastasis SARL 4 5 Anastasis is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file backend/anastasis-httpd.c 18 * @brief HTTP serving layer intended to provide basic backup operations 19 * @author Christian Grothoff 20 * @author Dennis Neufeld 21 * @author Dominik Meister 22 */ 23 #include "platform.h" 24 #include "anastasis-httpd.h" 25 #include "anastasis_util_lib.h" 26 #include "anastasis-httpd_mhd.h" 27 #include "anastasis_database_lib.h" 28 #include "anastasis-httpd_policy.h" 29 #include "anastasis-httpd_policy-meta.h" 30 #include "anastasis-httpd_truth.h" 31 #include "anastasis-httpd_terms.h" 32 #include "anastasis-httpd_config.h" 33 #include <taler/taler_json_lib.h> 34 35 36 /** 37 * Upload limit to the service, in megabytes. 38 */ 39 unsigned long long int AH_upload_limit_mb; 40 41 /** 42 * Currencies this provider prices its service in, primary currency first. 43 */ 44 char **AH_currencies; 45 46 /** 47 * Length of the #AH_currencies array. 48 */ 49 unsigned int AH_currencies_len; 50 51 /** 52 * Annual fee for the backup account, per currency. 53 */ 54 struct TALER_AmountList AH_annual_fees; 55 56 /** 57 * Fee for a truth upload, per currency. 58 */ 59 struct TALER_AmountList AH_truth_upload_fees; 60 61 /** 62 * Amount of insurance, per currency. 63 */ 64 struct TALER_AmountList AH_insurance; 65 66 /** 67 * Cost for secure question truth download, per currency. 68 */ 69 struct TALER_AmountList AH_question_costs; 70 71 /** 72 * Our configuration. 73 */ 74 const struct GNUNET_CONFIGURATION_Handle *AH_cfg; 75 76 /** 77 * Our Taler backend to process payments. 78 */ 79 char *AH_backend_url; 80 81 /** 82 * Our fulfillment URL. 83 */ 84 char *AH_fulfillment_url; 85 86 /** 87 * Our business name. 88 */ 89 char *AH_business_name; 90 91 /** 92 * Our provider salt. 93 */ 94 struct ANASTASIS_CRYPTO_ProviderSaltP AH_provider_salt; 95 96 /** 97 * Number of policy uploads permitted per annual fee payment. 98 */ 99 unsigned long long AH_post_counter = 64LLU; 100 101 /** 102 * Our context for making HTTP requests. 103 */ 104 struct GNUNET_CURL_Context *AH_ctx; 105 106 /** 107 * Should a "Connection: close" header be added to each HTTP response? 108 */ 109 static int AH_connection_close; 110 111 /** 112 * True if we started any HTTP daemon. 113 */ 114 static bool have_daemons; 115 116 /** 117 * Heap for processing timeouts of requests. 118 */ 119 struct GNUNET_CONTAINER_Heap *AH_to_heap; 120 121 /** 122 * Global return code 123 */ 124 static int global_result; 125 126 /** 127 * Reschedule context for #AH_ctx. 128 */ 129 static struct GNUNET_CURL_RescheduleContext *rc; 130 131 /** 132 * Username and password to use for client authentication 133 * (optional). 134 */ 135 static char *userpass; 136 137 /** 138 * Type of the client's TLS certificate (optional). 139 */ 140 static char *certtype; 141 142 /** 143 * File with the client's TLS certificate (optional). 144 */ 145 static char *certfile; 146 147 /** 148 * File with the client's TLS private key (optional). 149 */ 150 static char *keyfile; 151 152 /** 153 * This value goes in the Authorization:-header. 154 */ 155 static char *apikey; 156 157 /** 158 * Passphrase to decrypt client's TLS private key file (optional). 159 */ 160 static char *keypass; 161 162 163 /** 164 * Kick MHD to run now, to be called after MHD_resume_connection(). 165 * Basically, we need to explicitly resume MHD's event loop whenever 166 * we made progress serving a request. This function re-schedules 167 * the task processing MHD's activities to run immediately. 168 * 169 * @param cls NULL -- FIXME: why cls? 170 * 171 * FIXME: maybe call directly? 172 */ 173 void 174 AH_trigger_daemon (void *cls) 175 { 176 TALER_MHD_daemon_trigger (); 177 } 178 179 180 /** 181 * Kick GNUnet Curl scheduler to begin curl interactions. 182 */ 183 void 184 AH_trigger_curl (void) 185 { 186 GNUNET_CURL_gnunet_scheduler_reschedule (&rc); 187 } 188 189 190 /** 191 * A client has requested the given url using the given method 192 * (MHD_HTTP_METHOD_GET, MHD_HTTP_METHOD_PUT, 193 * MHD_HTTP_METHOD_DELETE, MHD_HTTP_METHOD_POST, etc). The callback 194 * must call MHD callbacks to provide content to give back to the 195 * client and return an HTTP status code (i.e. MHD_HTTP_OK, 196 * MHD_HTTP_NOT_FOUND, etc.). 197 * 198 * @param cls argument given together with the function 199 * pointer when the handler was registered with MHD 200 * @param connection MHD connection handle with further request details 201 * @param url the requested url 202 * @param method the HTTP method used (MHD_HTTP_METHOD_GET, 203 * MHD_HTTP_METHOD_PUT, etc.) 204 * @param version the HTTP version string (i.e. 205 * MHD_HTTP_VERSION_1_1) 206 * @param upload_data the data being uploaded (excluding HEADERS, 207 * for a POST that fits into memory and that is encoded 208 * with a supported encoding, the POST data will NOT be 209 * given in upload_data and is instead available as 210 * part of MHD_get_connection_values(); very large POST 211 * data *will* be made available incrementally in 212 * @a upload_data) 213 * @param upload_data_size set initially to the size of the 214 * @a upload_data provided; the method must update this 215 * value to the number of bytes NOT processed; 216 * @param con_cls pointer that the callback can set to some 217 * address and that will be preserved by MHD for future 218 * calls for this request; since the access handler may 219 * be called many times (i.e., for a PUT/POST operation 220 * with plenty of upload data) this allows the application 221 * to easily associate some request-specific state. 222 * If necessary, this state can be cleaned up in the 223 * global MHD_RequestCompletedCallback (which 224 * can be set with the MHD_OPTION_NOTIFY_COMPLETED). 225 * Initially, `*con_cls` will be NULL. 226 * @return #MHD_YES if the connection was handled successfully, 227 * #MHD_NO if the socket must be closed due to a serious 228 * error while handling the request 229 */ 230 static enum MHD_Result 231 url_handler (void *cls, 232 struct MHD_Connection *connection, 233 const char *url, 234 const char *method, 235 const char *version, 236 const char *upload_data, 237 size_t *upload_data_size, 238 void **con_cls) 239 { 240 static struct AH_RequestHandler handlers[] = { 241 /* Landing page, tell humans to go away. */ 242 { "/", MHD_HTTP_METHOD_GET, "text/plain", 243 "Hello, I'm Anastasis. This HTTP server is not for humans.\n", 0, 244 &TMH_MHD_handler_static_response, MHD_HTTP_OK }, 245 { "/agpl", MHD_HTTP_METHOD_GET, "text/plain", 246 NULL, 0, 247 &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND }, 248 { "/terms", MHD_HTTP_METHOD_GET, NULL, 249 NULL, 0, 250 &AH_handler_terms, MHD_HTTP_OK }, 251 { "/privacy", MHD_HTTP_METHOD_GET, NULL, 252 NULL, 0, 253 &AH_handler_privacy, MHD_HTTP_OK }, 254 { "/config", MHD_HTTP_METHOD_GET, "text/json", 255 NULL, 0, 256 &AH_handler_config, MHD_HTTP_OK }, 257 {NULL, NULL, NULL, NULL, 0, 0 } 258 }; 259 static struct AH_RequestHandler h404 = { 260 "", NULL, "text/html", 261 "<html><title>404: not found</title></html>", 0, 262 &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND 263 }; 264 static struct AH_RequestHandler h405 = { 265 "", NULL, "text/html", 266 "<html><title>405: method not allowed</title></html>", 0, 267 &TMH_MHD_handler_static_response, MHD_HTTP_METHOD_NOT_ALLOWED 268 }; 269 struct TM_HandlerContext *hc = *con_cls; 270 const char *correlation_id = NULL; 271 bool path_matched; 272 273 if (NULL == hc) 274 { 275 struct GNUNET_AsyncScopeId aid; 276 277 GNUNET_async_scope_fresh (&aid); 278 /* We only read the correlation ID on the first callback for every client */ 279 correlation_id = MHD_lookup_connection_value (connection, 280 MHD_HEADER_KIND, 281 "Anastasis-Correlation-Id"); 282 if ((NULL != correlation_id) && 283 (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id))) 284 { 285 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 286 "Invalid incoming correlation ID\n"); 287 correlation_id = NULL; 288 } 289 hc = GNUNET_new (struct TM_HandlerContext); 290 *con_cls = hc; 291 hc->async_scope_id = aid; 292 hc->url = url; 293 } 294 if (0 == strcasecmp (method, 295 MHD_HTTP_METHOD_HEAD)) 296 method = MHD_HTTP_METHOD_GET; /* MHD will throw away the body */ 297 298 GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); 299 if (NULL != correlation_id) 300 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 301 "Handling request for (%s) URL '%s', correlation_id=%s\n", 302 method, 303 url, 304 correlation_id); 305 else 306 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 307 "Handling request (%s) for URL '%s'\n", 308 method, 309 url); 310 if (0 == strncmp (url, 311 "/policy/", 312 strlen ("/policy/"))) 313 { 314 const char *account = url + strlen ("/policy/"); 315 const char *end = strchr (account, '/'); 316 struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub; 317 318 if (GNUNET_OK != 319 GNUNET_STRINGS_string_to_data ( 320 account, 321 (NULL == end) 322 ? strlen (account) 323 : end - account, 324 &account_pub, 325 sizeof (struct ANASTASIS_CRYPTO_AccountPublicKeyP))) 326 { 327 return TALER_MHD_reply_with_error (connection, 328 MHD_HTTP_BAD_REQUEST, 329 TALER_EC_GENERIC_PARAMETER_MALFORMED, 330 "account public key"); 331 } 332 if ( (NULL != end) && 333 (0 != strcmp (end, 334 "/meta")) ) 335 return TMH_MHD_handler_static_response (&h404, 336 connection); 337 if (0 == strcmp (method, 338 MHD_HTTP_METHOD_GET)) 339 { 340 if (NULL == end) 341 return AH_policy_get (connection, 342 &account_pub); 343 return AH_policy_meta_get (connection, 344 &account_pub); 345 } 346 if ( (0 == strcmp (method, 347 MHD_HTTP_METHOD_POST)) && 348 (NULL == end) ) 349 { 350 return AH_handler_policy_post (connection, 351 hc, 352 &account_pub, 353 upload_data, 354 upload_data_size); 355 } 356 if (0 == strcmp (method, 357 MHD_HTTP_METHOD_OPTIONS)) 358 { 359 return TALER_MHD_reply_cors_preflight (connection); 360 } 361 return TMH_MHD_handler_static_response (&h405, 362 connection); 363 } 364 if (0 == strncmp (url, 365 "/truth/", 366 strlen ("/truth/"))) 367 { 368 struct ANASTASIS_CRYPTO_TruthUUIDP tu; 369 const char *pub_key_str; 370 const char *end; 371 size_t len; 372 373 pub_key_str = &url[strlen ("/truth/")]; 374 end = strchr (pub_key_str, 375 '/'); 376 if (NULL == end) 377 len = strlen (pub_key_str); 378 else 379 len = end - pub_key_str; 380 if (GNUNET_OK != 381 GNUNET_STRINGS_string_to_data ( 382 pub_key_str, 383 len, 384 &tu, 385 sizeof(tu))) 386 { 387 GNUNET_break_op (0); 388 return TALER_MHD_reply_with_error (connection, 389 MHD_HTTP_BAD_REQUEST, 390 TALER_EC_GENERIC_PARAMETER_MALFORMED, 391 "truth UUID"); 392 } 393 if ( (NULL != end) && 394 (0 != strcmp (end, "/solve")) && 395 (0 != strcmp (end, "/challenge")) ) 396 return TMH_MHD_handler_static_response (&h404, 397 connection); 398 if (0 == strcmp (method, 399 MHD_HTTP_METHOD_OPTIONS)) 400 return TALER_MHD_reply_cors_preflight (connection); 401 if (0 != strcmp (method, 402 MHD_HTTP_METHOD_POST)) 403 return TMH_MHD_handler_static_response (&h405, 404 connection); 405 if (NULL == end) 406 { 407 return AH_handler_truth_post (connection, 408 hc, 409 &tu, 410 upload_data, 411 upload_data_size); 412 } 413 if (0 == strcmp (end, 414 "/solve")) 415 { 416 return AH_handler_truth_solve (connection, 417 hc, 418 &tu, 419 upload_data, 420 upload_data_size); 421 } 422 if (0 == strcmp (end, 423 "/challenge")) 424 { 425 return AH_handler_truth_challenge (connection, 426 hc, 427 &tu, 428 upload_data, 429 upload_data_size); 430 } 431 /* should be impossible to get here */ 432 GNUNET_assert (0); 433 } /* end of "/truth/" prefix */ 434 path_matched = false; 435 for (unsigned int i = 0; NULL != handlers[i].url; i++) 436 { 437 struct AH_RequestHandler *rh = &handlers[i]; 438 439 if (0 == strcmp (url, 440 rh->url)) 441 { 442 path_matched = true; 443 if (0 == strcasecmp (method, 444 MHD_HTTP_METHOD_OPTIONS)) 445 { 446 return TALER_MHD_reply_cors_preflight (connection); 447 } 448 if ( (NULL == rh->method) || 449 (0 == strcasecmp (method, 450 rh->method)) ) 451 { 452 return rh->handler (rh, 453 connection); 454 } 455 } 456 } 457 if (path_matched) 458 return TMH_MHD_handler_static_response (&h405, 459 connection); 460 return TMH_MHD_handler_static_response (&h404, 461 connection); 462 } 463 464 465 /** 466 * Shutdown task (magically invoked when the application is being 467 * quit) 468 * 469 * @param cls NULL 470 */ 471 static void 472 do_shutdown (void *cls) 473 { 474 (void) cls; 475 TALER_MHD_daemons_halt (); 476 AH_resume_all_bc (); 477 AH_truth_challenge_shutdown (); 478 AH_truth_solve_shutdown (); 479 AH_truth_upload_shutdown (); 480 TALER_MHD_daemons_destroy (); 481 if (NULL != AH_ctx) 482 { 483 GNUNET_CURL_fini (AH_ctx); 484 AH_ctx = NULL; 485 } 486 if (NULL != rc) 487 { 488 GNUNET_CURL_gnunet_rc_destroy (rc); 489 rc = NULL; 490 } 491 ANASTASIS_DB_fini (); 492 if (NULL != AH_to_heap) 493 { 494 GNUNET_CONTAINER_heap_destroy (AH_to_heap); 495 AH_to_heap = NULL; 496 } 497 TALER_amount_list_free (&AH_annual_fees); 498 TALER_amount_list_free (&AH_truth_upload_fees); 499 TALER_amount_list_free (&AH_insurance); 500 TALER_amount_list_free (&AH_question_costs); 501 for (unsigned int i = 0; i<AH_currencies_len; i++) 502 GNUNET_free (AH_currencies[i]); 503 GNUNET_array_grow (AH_currencies, 504 AH_currencies_len, 505 0); 506 } 507 508 509 json_t * 510 AH_make_order (const char *order_id, 511 const char *summary, 512 const struct TALER_AmountList *prices) 513 { 514 json_t *choices; 515 json_t *order; 516 517 GNUNET_assert (0 != prices->tal_len); 518 choices = json_array (); 519 GNUNET_assert (NULL != choices); 520 for (unsigned int i = 0; i<prices->tal_len; i++) 521 { 522 json_t *choice; 523 524 GNUNET_assert (! TALER_amount_is_zero (&prices->tal[i])); 525 choice = GNUNET_JSON_PACK ( 526 TALER_JSON_pack_amount ("amount", 527 &prices->tal[i]), 528 GNUNET_JSON_pack_string ("description", 529 summary)); 530 GNUNET_assert (0 == 531 json_array_append_new (choices, 532 choice)); 533 } 534 order = GNUNET_JSON_PACK ( 535 GNUNET_JSON_pack_uint64 ("version", 536 1), 537 GNUNET_JSON_pack_string ("order_id", 538 order_id), 539 GNUNET_JSON_pack_string ("summary", 540 summary), 541 GNUNET_JSON_pack_array_steal ("choices", 542 choices)); 543 return order; 544 } 545 546 547 enum GNUNET_GenericReturnValue 548 AH_paid_amount (const struct TALER_MERCHANT_GetPrivateOrderResponse *osr, 549 struct TALER_Amount *amount) 550 { 551 const json_t *contract = osr->details.ok.details.paid.contract_terms; 552 int ci = osr->details.ok.details.paid.choice_index; 553 const json_t *amount_obj; 554 struct GNUNET_JSON_Specification cspec[] = { 555 TALER_JSON_spec_amount_any ("amount", 556 amount), 557 GNUNET_JSON_spec_end () 558 }; 559 560 amount_obj = (0 > ci) 561 ? contract /* legacy "v0" order, made before the upgrade */ 562 : json_array_get (json_object_get (contract, 563 "choices"), 564 ci); 565 if (NULL == amount_obj) 566 { 567 GNUNET_break (0); 568 return GNUNET_SYSERR; 569 } 570 return GNUNET_JSON_parse (amount_obj, 571 cspec, 572 NULL, NULL); 573 } 574 575 576 const struct TALER_Amount * 577 AH_primary_price (const struct TALER_AmountList *al) 578 { 579 static struct TALER_Amount zero; 580 const struct TALER_Amount *a; 581 582 GNUNET_assert (0 < AH_currencies_len); 583 a = TALER_amount_list_find (al, 584 AH_currencies[0]); 585 if (NULL != a) 586 return a; 587 /* The option is free. Report zero in the primary currency, which is 588 exactly what a single-currency provider running for free emitted 589 before there were price lists. */ 590 GNUNET_assert (GNUNET_OK == 591 TALER_amount_set_zero (AH_currencies[0], 592 &zero)); 593 return &zero; 594 } 595 596 597 enum GNUNET_GenericReturnValue 598 AH_check_price (const struct TALER_AmountList *al, 599 const char *section, 600 const char *option) 601 { 602 switch (TALER_amount_list_check_uniform (al)) 603 { 604 case GNUNET_SYSERR: 605 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 606 "Price list error: option `%s' in section `%s' is `%s'," 607 " which prices some currencies at zero and others above" 608 " zero. Either all of them are zero (the option is free)" 609 " or none is.\n", 610 option, 611 section, 612 TALER_amount_list2s (al)); 613 return GNUNET_SYSERR; 614 case GNUNET_NO: 615 /* Free is a first-class answer, and exempt from the coverage check 616 below: an operator must be able to say "free" without having to 617 enumerate every currency to do it. */ 618 return GNUNET_NO; 619 case GNUNET_OK: 620 break; 621 } 622 if (! TALER_amount_list_covers (al, 623 (const char *const *) AH_currencies, 624 AH_currencies_len)) 625 { 626 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 627 "Price list error: option `%s' in section `%s' is `%s'," 628 " which does not price in exactly the currencies given in" 629 " [anastasis] CURRENCIES.\n", 630 option, 631 section, 632 TALER_amount_list2s (al)); 633 /* Naming the missing currency is what makes this actionable: the 634 failure mode it guards against is an operator adding a currency 635 and overlooking one of the many COST options. */ 636 for (unsigned int i = 0; i<AH_currencies_len; i++) 637 if (NULL == 638 TALER_amount_list_find (al, 639 AH_currencies[i])) 640 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 641 "No price given for currency `%s' in option `%s' of" 642 " section `%s'\n", 643 AH_currencies[i], 644 option, 645 section); 646 for (unsigned int i = 0; i<al->tal_len; i++) 647 { 648 bool found = false; 649 650 for (unsigned int j = 0; j<AH_currencies_len; j++) 651 if (0 == strcasecmp (al->tal[i].currency, 652 AH_currencies[j])) 653 found = true; 654 if (! found) 655 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 656 "Currency `%s' is priced in option `%s' of section" 657 " `%s' but is not in [anastasis] CURRENCIES\n", 658 al->tal[i].currency, 659 option, 660 section); 661 } 662 return GNUNET_SYSERR; 663 } 664 return GNUNET_OK; 665 } 666 667 668 /** 669 * Closure for #check_method_cost_cb(). 670 */ 671 struct MethodCostContext 672 { 673 /** 674 * Set to true if some enabled authorization method is misconfigured. 675 */ 676 bool failure; 677 }; 678 679 680 /** 681 * Validate the COST of one enabled authorization method. 682 * 683 * The plugins themselves are loaded lazily, on the first ``/config`` 684 * request, so waiting for the loader to notice a bad COST would mean the 685 * provider starts and only fails once a client asks --- by which time the 686 * operator is no longer watching. The configuration file can be checked 687 * without loading anything, so it is checked here. 688 * 689 * @param cls a `struct MethodCostContext *` 690 * @param section configuration section to inspect 691 */ 692 static void 693 check_method_cost_cb (void *cls, 694 const char *section) 695 { 696 struct MethodCostContext *mcc = cls; 697 struct TALER_AmountList costs; 698 699 if (0 != strncasecmp (section, 700 "authorization-", 701 strlen ("authorization-"))) 702 return; 703 if (GNUNET_YES != 704 GNUNET_CONFIGURATION_get_value_yesno (AH_cfg, 705 section, 706 "ENABLED")) 707 return; 708 /* An absent COST is an error, not "free": a typo in the option name 709 must not silently give the method away. Free is spelled with an 710 empty value, or with a zero in every currency. */ 711 if (GNUNET_OK != 712 TALER_config_get_amount_list (AH_cfg, 713 section, 714 "COST", 715 &costs)) 716 { 717 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 718 section, 719 "COST"); 720 mcc->failure = true; 721 return; 722 } 723 if (GNUNET_SYSERR == 724 AH_check_price (&costs, 725 section, 726 "COST")) 727 mcc->failure = true; 728 TALER_amount_list_free (&costs); 729 } 730 731 732 /** 733 * Determine the currencies this provider prices its service in. 734 * 735 * Normally these are given explicitly in [anastasis] CURRENCIES, primary 736 * currency first. A configuration written before multi-currency support 737 * has no such option; for those, the single currency the provider was 738 * already using is derived from the first priced option, which keeps every 739 * existing configuration file working unchanged. Any disagreement between 740 * the options is then caught by the per-option coverage check, which can 741 * name the offending option. 742 * 743 * @param cfg configuration to use 744 * @return #GNUNET_OK on success 745 */ 746 static enum GNUNET_GenericReturnValue 747 parse_currencies (const struct GNUNET_CONFIGURATION_Handle *cfg) 748 { 749 char *str; 750 751 if (GNUNET_OK != 752 GNUNET_CONFIGURATION_get_value_string (cfg, 753 "anastasis", 754 "CURRENCIES", 755 &str)) 756 { 757 const struct TALER_AmountList *als[] = { 758 &AH_annual_fees, 759 &AH_truth_upload_fees, 760 &AH_insurance, 761 &AH_question_costs 762 }; 763 764 /* A priced option names the currency authoritatively; failing that, 765 an option that is free but written out as e.g. "EUR:0" still names 766 one, which is how a provider running entirely for free is 767 configured today. */ 768 for (unsigned int pass = 0; pass < 2; pass++) 769 for (unsigned int i = 0; i<sizeof (als) / sizeof (als[0]); i++) 770 { 771 if ( (0 == pass) && 772 (GNUNET_OK != 773 TALER_amount_list_check_uniform (als[i])) ) 774 continue; 775 if (0 == als[i]->tal_len) 776 continue; 777 if (1 != als[i]->tal_len) 778 { 779 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 780 "anastasis", 781 "CURRENCIES"); 782 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 783 "A provider pricing in more than one currency must say so explicitly.\n"); 784 return GNUNET_SYSERR; 785 } 786 GNUNET_array_append (AH_currencies, 787 AH_currencies_len, 788 GNUNET_strdup (als[i]->tal[0].currency)); 789 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 790 "[anastasis] CURRENCIES not configured, assuming `%s'\n", 791 AH_currencies[0]); 792 return GNUNET_OK; 793 } 794 /* Not one option names a currency, so there is nothing to derive 795 from and the legacy scalar fields of /config would have no 796 currency to report either. */ 797 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 798 "anastasis", 799 "CURRENCIES"); 800 return GNUNET_SYSERR; 801 } 802 for (const char *tok = strtok (str, ";"); 803 NULL != tok; 804 tok = strtok (NULL, ";")) 805 { 806 if (GNUNET_OK != 807 TALER_check_currency (tok)) 808 { 809 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 810 "anastasis", 811 "CURRENCIES", 812 tok); 813 GNUNET_free (str); 814 return GNUNET_SYSERR; 815 } 816 for (unsigned int i = 0; i<AH_currencies_len; i++) 817 if (0 == strcasecmp (tok, 818 AH_currencies[i])) 819 { 820 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 821 "anastasis", 822 "CURRENCIES", 823 "currency given more than once"); 824 GNUNET_free (str); 825 return GNUNET_SYSERR; 826 } 827 GNUNET_array_append (AH_currencies, 828 AH_currencies_len, 829 GNUNET_strdup (tok)); 830 } 831 GNUNET_free (str); 832 if (0 == AH_currencies_len) 833 { 834 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 835 "anastasis", 836 "CURRENCIES", 837 "no currency given"); 838 return GNUNET_SYSERR; 839 } 840 return GNUNET_OK; 841 } 842 843 844 /** 845 * Parse and validate everything this provider charges for. 846 * 847 * @param cfg configuration to use 848 * @return #GNUNET_OK on success 849 */ 850 static enum GNUNET_GenericReturnValue 851 parse_prices (const struct GNUNET_CONFIGURATION_Handle *cfg) 852 { 853 static const struct 854 { 855 const char *section; 856 const char *option; 857 struct TALER_AmountList *al; 858 } prices[] = { 859 { "anastasis", "INSURANCE", &AH_insurance }, 860 { "authorization-question", "COST", &AH_question_costs }, 861 { "anastasis", "ANNUAL_FEE", &AH_annual_fees }, 862 { "anastasis", "TRUTH_UPLOAD_FEE", &AH_truth_upload_fees } 863 }; 864 struct MethodCostContext mcc = { 865 .failure = false 866 }; 867 868 for (unsigned int i = 0; i<sizeof (prices) / sizeof (prices[0]); i++) 869 { 870 if (GNUNET_OK != 871 TALER_config_get_amount_list (cfg, 872 prices[i].section, 873 prices[i].option, 874 prices[i].al)) 875 { 876 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 877 prices[i].section, 878 prices[i].option); 879 return GNUNET_SYSERR; 880 } 881 } 882 /* The currency list has to exist before anything can be checked 883 against it, but deriving it from the prices needs them parsed. */ 884 if (GNUNET_OK != 885 parse_currencies (cfg)) 886 return GNUNET_SYSERR; 887 for (unsigned int i = 0; i<sizeof (prices) / sizeof (prices[0]); i++) 888 if (GNUNET_SYSERR == 889 AH_check_price (prices[i].al, 890 prices[i].section, 891 prices[i].option)) 892 return GNUNET_SYSERR; 893 GNUNET_CONFIGURATION_iterate_sections (cfg, 894 &check_method_cost_cb, 895 &mcc); 896 if (mcc.failure) 897 return GNUNET_SYSERR; 898 return GNUNET_OK; 899 } 900 901 902 /** 903 * Function called whenever MHD is done with a request. If the 904 * request was a POST, we may have stored a `struct Buffer *` in the 905 * @a con_cls that might still need to be cleaned up. Call the 906 * respective function to free the memory. 907 * 908 * @param cls client-defined closure 909 * @param connection connection handle 910 * @param con_cls value as set by the last call to 911 * the #MHD_AccessHandlerCallback 912 * @param toe reason for request termination 913 * @see #MHD_OPTION_NOTIFY_COMPLETED 914 * @ingroup request 915 */ 916 static void 917 handle_mhd_completion_callback (void *cls, 918 struct MHD_Connection *connection, 919 void **con_cls, 920 enum MHD_RequestTerminationCode toe) 921 { 922 struct TM_HandlerContext *hc = *con_cls; 923 struct GNUNET_AsyncScopeSave old_scope; 924 925 (void) cls; 926 (void) connection; 927 if (NULL == hc) 928 return; 929 GNUNET_async_scope_enter (&hc->async_scope_id, 930 &old_scope); 931 { 932 #if MHD_VERSION >= 0x00097304 933 const union MHD_ConnectionInfo *ci; 934 unsigned int http_status = 0; 935 936 ci = MHD_get_connection_info (connection, 937 MHD_CONNECTION_INFO_HTTP_STATUS); 938 if (NULL != ci) 939 http_status = ci->http_status; 940 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 941 "Request for `%s' completed with HTTP status %u (%d)\n", 942 hc->url, 943 http_status, 944 toe); 945 #else 946 (void) connection; 947 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 948 "Request for `%s' completed (%d)\n", 949 hc->url, 950 toe); 951 #endif 952 } 953 if (NULL != hc->cc) 954 hc->cc (hc); 955 GNUNET_free (hc); 956 *con_cls = NULL; 957 } 958 959 960 /** 961 * Callback invoked on every listen socket to start the 962 * respective MHD HTTP daemon. 963 * 964 * @param cls unused 965 * @param lsock the listen socket 966 */ 967 static void 968 start_daemon (void *cls, 969 int lsock) 970 { 971 struct MHD_Daemon *mhd; 972 973 (void) cls; 974 GNUNET_assert (-1 != lsock); 975 mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK, 976 0 /* port */, 977 NULL, NULL, 978 &url_handler, NULL, 979 MHD_OPTION_LISTEN_SOCKET, lsock, 980 MHD_OPTION_NOTIFY_COMPLETED, 981 &handle_mhd_completion_callback, NULL, 982 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned 983 int) 10 /* 10s */, 984 MHD_OPTION_END); 985 if (NULL == mhd) 986 { 987 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 988 "Failed to launch HTTP service.\n"); 989 GNUNET_SCHEDULER_shutdown (); 990 return; 991 } 992 have_daemons = true; 993 TALER_MHD_daemon_start (mhd); 994 } 995 996 997 /** 998 * Main function that will be run by the scheduler. 999 * 1000 * @param cls closure 1001 * @param args remaining command-line arguments 1002 * @param cfgfile name of the configuration file used (for saving, can be 1003 * NULL!) 1004 * @param config configuration 1005 */ 1006 static void 1007 run (void *cls, 1008 char *const *args, 1009 const char *cfgfile, 1010 const struct GNUNET_CONFIGURATION_Handle *config) 1011 { 1012 enum TALER_MHD_GlobalOptions go; 1013 1014 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1015 "Starting anastasis-httpd\n"); 1016 go = TALER_MHD_GO_NONE; 1017 if (AH_connection_close) 1018 go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; 1019 AH_load_terms (config); 1020 TALER_MHD_setup (go); 1021 AH_cfg = config; 1022 global_result = EXIT_NO_RESTART; 1023 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1024 NULL); 1025 if (GNUNET_OK != 1026 GNUNET_CONFIGURATION_get_value_number (config, 1027 "anastasis", 1028 "UPLOAD_LIMIT_MB", 1029 &AH_upload_limit_mb)) 1030 { 1031 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1032 "anastasis", 1033 "UPLOAD_LIMIT_MB"); 1034 GNUNET_SCHEDULER_shutdown (); 1035 return; 1036 } 1037 if (GNUNET_OK != 1038 parse_prices (config)) 1039 { 1040 GNUNET_SCHEDULER_shutdown (); 1041 return; 1042 } 1043 if (GNUNET_OK != 1044 GNUNET_CONFIGURATION_get_value_string (config, 1045 "anastasis-merchant-backend", 1046 "PAYMENT_BACKEND_URL", 1047 &AH_backend_url)) 1048 { 1049 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1050 "anastasis-merchant-backend", 1051 "PAYMENT_BACKEND_URL"); 1052 GNUNET_SCHEDULER_shutdown (); 1053 return; 1054 } 1055 if ( (0 != strncasecmp ("https://", 1056 AH_backend_url, 1057 strlen ("https://"))) && 1058 (0 != strncasecmp ("http://", 1059 AH_backend_url, 1060 strlen ("http://"))) ) 1061 { 1062 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1063 "anastasis-merchant-backend", 1064 "PAYMENT_BACKEND_URL", 1065 "Must be HTTP(S) URL"); 1066 GNUNET_SCHEDULER_shutdown (); 1067 return; 1068 } 1069 1070 if ( (0 == strcasecmp ("https://", 1071 AH_backend_url)) || 1072 (0 == strcasecmp ("http://", 1073 AH_backend_url)) ) 1074 { 1075 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1076 "anastasis-merchant-backend", 1077 "PAYMENT_BACKEND_URL", 1078 "Must have domain name"); 1079 GNUNET_SCHEDULER_shutdown (); 1080 return; 1081 } 1082 1083 if (GNUNET_OK != 1084 GNUNET_CONFIGURATION_get_value_string (config, 1085 "anastasis", 1086 "FULFILLMENT_URL", 1087 &AH_fulfillment_url)) 1088 { 1089 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1090 "anastasis", 1091 "FULFILLMENT_URL"); 1092 GNUNET_SCHEDULER_shutdown (); 1093 return; 1094 } 1095 if (GNUNET_OK != 1096 GNUNET_CONFIGURATION_get_value_number (config, 1097 "anastasis", 1098 "ANNUAL_POLICY_UPLOAD_LIMIT", 1099 &AH_post_counter)) 1100 { 1101 /* only warn, we will use the default */ 1102 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 1103 "anastasis", 1104 "ANNUAL_POLICY_UPLOAD_LIMIT"); 1105 } 1106 1107 if (GNUNET_OK != 1108 GNUNET_CONFIGURATION_get_value_string (config, 1109 "anastasis", 1110 "BUSINESS_NAME", 1111 &AH_business_name)) 1112 { 1113 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1114 "anastasis", 1115 "BUSINESS_NAME"); 1116 GNUNET_SCHEDULER_shutdown (); 1117 return; 1118 } 1119 { 1120 char *provider_salt; 1121 1122 if (GNUNET_OK != 1123 GNUNET_CONFIGURATION_get_value_string (config, 1124 "anastasis", 1125 "PROVIDER_SALT", 1126 &provider_salt)) 1127 { 1128 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1129 "anastasis", 1130 "PROVIDER_SALT"); 1131 GNUNET_SCHEDULER_shutdown (); 1132 return; 1133 } 1134 GNUNET_assert (GNUNET_YES == 1135 GNUNET_CRYPTO_hkdf_gnunet (&AH_provider_salt, 1136 sizeof (AH_provider_salt), 1137 "anastasis-provider-salt", 1138 strlen ("anastasis-provider-salt") 1139 , 1140 provider_salt, 1141 strlen (provider_salt))); 1142 GNUNET_free (provider_salt); 1143 } 1144 1145 /* setup HTTP client event loop */ 1146 AH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1147 &rc); 1148 rc = GNUNET_CURL_gnunet_rc_create (AH_ctx); 1149 if (NULL != userpass) 1150 GNUNET_CURL_set_userpass (AH_ctx, 1151 userpass); 1152 if (NULL != keyfile) 1153 GNUNET_CURL_set_tlscert (AH_ctx, 1154 certtype, 1155 certfile, 1156 keyfile, 1157 keypass); 1158 if (NULL == apikey) 1159 { 1160 (void) GNUNET_CONFIGURATION_get_value_string (config, 1161 "anastasis-merchant-backend", 1162 "API_KEY", 1163 &apikey); 1164 } 1165 if (NULL != apikey) 1166 { 1167 char *auth_header; 1168 1169 GNUNET_asprintf (&auth_header, 1170 "%s: %s", 1171 MHD_HTTP_HEADER_AUTHORIZATION, 1172 apikey); 1173 if (GNUNET_OK != 1174 GNUNET_CURL_append_header (AH_ctx, 1175 auth_header)) 1176 { 1177 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1178 "Failed so set %s header, trying without\n", 1179 MHD_HTTP_HEADER_AUTHORIZATION); 1180 } 1181 GNUNET_free (auth_header); 1182 } 1183 1184 if (GNUNET_OK != 1185 ANASTASIS_DB_init (config)) 1186 { 1187 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1188 "Database not setup. Did you run anastasis-dbinit?\n"); 1189 GNUNET_SCHEDULER_shutdown (); 1190 return; 1191 } 1192 1193 { 1194 enum GNUNET_GenericReturnValue ret; 1195 1196 ret = TALER_MHD_listen_bind (config, 1197 "anastasis", 1198 &start_daemon, 1199 NULL); 1200 switch (ret) 1201 { 1202 case GNUNET_SYSERR: 1203 global_result = EXIT_NOTCONFIGURED; 1204 GNUNET_SCHEDULER_shutdown (); 1205 return; 1206 case GNUNET_NO: 1207 if (! have_daemons) 1208 { 1209 global_result = EXIT_NOTCONFIGURED; 1210 GNUNET_SCHEDULER_shutdown (); 1211 return; 1212 } 1213 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1214 "Could not open all configured listen sockets\n"); 1215 break; 1216 case GNUNET_OK: 1217 break; 1218 } 1219 } 1220 global_result = EXIT_SUCCESS; 1221 } 1222 1223 1224 /** 1225 * The main function of the serve tool 1226 * 1227 * @param argc number of arguments from the command line 1228 * @param argv command line arguments 1229 * @return 0 ok, 1 on error 1230 */ 1231 int 1232 main (int argc, 1233 char *const *argv) 1234 { 1235 enum GNUNET_GenericReturnValue res; 1236 struct GNUNET_GETOPT_CommandLineOption options[] = { 1237 GNUNET_GETOPT_option_string ('A', 1238 "auth", 1239 "USERNAME:PASSWORD", 1240 "use the given USERNAME and PASSWORD for client authentication", 1241 &userpass), 1242 GNUNET_GETOPT_option_flag ('C', 1243 "connection-close", 1244 "force HTTP connections to be closed after each request", 1245 &AH_connection_close), 1246 GNUNET_GETOPT_option_string ('k', 1247 "key", 1248 "KEYFILE", 1249 "file with the private TLS key for TLS client authentication", 1250 &keyfile), 1251 GNUNET_GETOPT_option_string ('p', 1252 "pass", 1253 "KEYFILEPASSPHRASE", 1254 "passphrase needed to decrypt the TLS client private key file", 1255 &keypass), 1256 GNUNET_GETOPT_option_string ('K', 1257 "apikey", 1258 "APIKEY", 1259 "API key to use in the HTTP request to the merchant backend", 1260 &apikey), 1261 GNUNET_GETOPT_option_string ('t', 1262 "type", 1263 "CERTTYPE", 1264 "type of the TLS client certificate, defaults to PEM if not specified", 1265 &certtype), 1266 GNUNET_GETOPT_OPTION_END 1267 }; 1268 1269 res = GNUNET_PROGRAM_run (ANASTASIS_project_data (), 1270 argc, argv, 1271 "anastasis-httpd", 1272 "Anastasis HTTP interface", 1273 options, &run, NULL); 1274 if (GNUNET_SYSERR == res) 1275 return 3; 1276 if (GNUNET_NO == res) 1277 return 0; 1278 return global_result; 1279 }