donau-httpd.c (33590B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024 Taler Systems SA 4 5 TALER 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 TALER 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 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file donau-httpd.c 18 * @brief Serve the HTTP interface of the donau 19 * @author Johannes Casaburi 20 */ 21 #include "donau_config.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <jansson.h> 24 #include <microhttpd.h> 25 #include <sched.h> 26 #include <sys/resource.h> 27 #include <limits.h> 28 #include <taler/taler_mhd_lib.h> 29 #include "donaudb_lib.h" 30 #include "donau_util.h" 31 #include "donau_json_lib.h" 32 #include "donau-httpd_get-config.h" 33 #include "donau-httpd_get-keys.h" 34 #include "donau-httpd_get-charities.h" 35 #include "donau-httpd_get-charity-CHARITY_ID.h" 36 #include "donau-httpd_post-charities.h" 37 #include "donau-httpd_patch-charities-CHARITY_ID.h" 38 #include "donau-httpd_delete-charities-CHARITY_ID.h" 39 #include "donau-httpd_get-donation-statement-YEAR-HASH_DONOR_ID.h" 40 #include "donau-httpd_post-batch-issue-CHARITY_ID.h" 41 #include "donau-httpd_post-batch-submit.h" 42 #include "donau-httpd_get-history.h" 43 #include "donau-database/preflight.h" 44 #include "donau-httpd_post-csr-issue.h" 45 #include "donau-httpd_terms.h" 46 #include <gnunet/gnunet_mhd_compat.h> 47 48 /* LSB-style exit status codes */ 49 #ifndef EXIT_INVALIDARGUMENT 50 /** 51 * Command-line arguments are invalid. 52 * Restarting useless. 53 */ 54 #define EXIT_INVALIDARGUMENT 2 55 #endif 56 57 58 #ifndef EXIT_NOTCONFIGURED 59 /** 60 * Key configuration settings are missing or invalid. 61 * Restarting useless. 62 */ 63 #define EXIT_NOTCONFIGURED 6 64 #endif 65 66 67 /** 68 * Backlog for listen operation on unix domain sockets. 69 */ 70 #define UNIX_BACKLOG 50 71 72 /** 73 * How often will we try to connect to the database before giving up? 74 */ 75 #define MAX_DB_RETRIES 5 76 77 /** 78 * Above what request latency do we start to log? 79 */ 80 #define WARN_LATENCY GNUNET_TIME_relative_multiply ( \ 81 GNUNET_TIME_UNIT_MILLISECONDS, 500) 82 83 /** 84 * Are clients allowed to request /keys for times other than the 85 * current time? Allowing this could be abused in a DoS-attack 86 * as building new /keys responses is expensive. Should only be 87 * enabled for testcases, development and test systems. 88 */ 89 int DH_allow_keys_timetravel; 90 91 /** 92 * Should we allow two HTTPDs to bind to the same port? 93 */ 94 static int allow_address_reuse; 95 96 /** 97 * The donau's configuration (global) 98 */ 99 const struct GNUNET_CONFIGURATION_Handle *DH_cfg; 100 101 /** 102 * Set to true if we started *any* HTTP daemons. 103 */ 104 static bool have_daemons; 105 106 /** 107 * Our DB context. (global) 108 */ 109 struct DONAUDB_PostgresContext *DH_context; 110 111 /** 112 * Our currency. 113 */ 114 char *DH_currency; 115 116 /** 117 * Our base URL. 118 */ 119 char *DH_base_url; 120 121 /** 122 * Our administrative bearer token. 123 */ 124 static char *admin_bearer; 125 126 /** 127 * Default timeout in seconds for HTTP requests. 128 */ 129 static unsigned int connection_timeout = 30; 130 131 /** 132 * -C command-line flag given? 133 */ 134 static int connection_close; 135 136 /** 137 * True if we should commit suicide once all active 138 * connections are finished. 139 */ 140 bool DH_suicide; 141 142 /** 143 * Legal domain for this donau, to be shown to the user. 144 */ 145 char *DH_legal_domain; 146 147 /** 148 * Value to return from main() 149 */ 150 int DH_global_ret; 151 152 /** 153 * Counter for the number of open connections. 154 */ 155 static unsigned long long active_connections; 156 157 /** 158 * Limit for the number of requests this HTTP may process before restarting. 159 * (This was added as one way of dealing with unavoidable memory fragmentation 160 * happening slowly over time.) 161 */ 162 static unsigned long long req_max; 163 164 /** 165 * Context for all CURL operations (useful to the event loop) 166 */ 167 struct GNUNET_CURL_Context *DH_curl_ctx; 168 169 /** 170 * Context for integrating #DH_curl_ctx with the 171 * GNUnet event loop. 172 */ 173 static struct GNUNET_CURL_RescheduleContext *donau_curl_rc; 174 175 /** 176 * Signature of functions that handle operations on coins. 177 * 178 * @param connection the MHD connection to handle 179 * @param coin_pub the public key of the coin 180 * @param root uploaded JSON data 181 * @return MHD result code 182 */ 183 typedef enum MHD_Result 184 (*CoinOpHandler)(struct MHD_Connection *connection, 185 const struct TALER_CoinSpendPublicKeyP *coin_pub, 186 const json_t *root); 187 188 /** 189 * Function called whenever MHD is done with a request. If the 190 * request was a POST, we may have stored a `struct Buffer *` in the 191 * @a con_cls that might still need to be cleaned up. Call the 192 * respective function to free the memory. 193 * 194 * @param cls client-defined closure 195 * @param connection connection handle 196 * @param con_cls value as set by the last call to 197 * the #MHD_AccessHandlerCallback 198 * @param toe reason for request termination 199 * @see #MHD_OPTION_NOTIFY_COMPLETED 200 * @ingroup request 201 */ 202 static void 203 handle_mhd_completion_callback (void *cls, 204 struct MHD_Connection *connection, 205 void **con_cls, 206 enum MHD_RequestTerminationCode toe) 207 { 208 struct DH_RequestContext *rc = *con_cls; 209 struct GNUNET_AsyncScopeSave old_scope; 210 211 (void) cls; 212 if (NULL == rc) 213 return; 214 GNUNET_async_scope_enter (&rc->async_scope_id, 215 &old_scope); 216 if (NULL != rc->rh_cleaner) 217 rc->rh_cleaner (rc); 218 { 219 #if MHD_VERSION >= 0x00097304 220 const union MHD_ConnectionInfo *ci; 221 unsigned int http_status = 0; 222 223 ci = MHD_get_connection_info (connection, 224 MHD_CONNECTION_INFO_HTTP_STATUS); 225 if (NULL != ci) 226 http_status = ci->http_status; 227 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 228 "Request for `%s' completed with HTTP status %u (%d)\n", 229 rc->url, 230 http_status, 231 toe); 232 #else 233 (void) connection; 234 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 235 "Request for `%s' completed (%d)\n", 236 rc->url, 237 toe); 238 #endif 239 } 240 241 TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context); 242 /* Sanity-check that we didn't leave any transactions hanging */ 243 GNUNET_break (GNUNET_OK == 244 DONAUDB_preflight (DH_context)); 245 { 246 struct GNUNET_TIME_Relative latency; 247 latency = GNUNET_TIME_absolute_get_duration (rc->start_time); 248 if (latency.rel_value_us > 249 WARN_LATENCY.rel_value_us) 250 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 251 "Request for `%s' took %s\n", 252 rc->url, 253 GNUNET_STRINGS_relative_time_to_string (latency, 254 GNUNET_YES)); 255 } 256 GNUNET_free (rc); 257 *con_cls = NULL; 258 GNUNET_async_scope_restore (&old_scope); 259 } 260 261 262 /** 263 * We found a request handler responsible for handling a request. Parse the 264 * @a upload_data (if applicable) and the @a url and call the 265 * handler. 266 * 267 * @param rc request context 268 * @param url rest of the URL to parse 269 * @param upload_data upload data to parse (if available) 270 * @param[in,out] upload_data_size number of bytes in @a upload_data 271 * @return MHD result code 272 */ 273 static enum MHD_Result 274 proceed_with_handler (struct DH_RequestContext *rc, 275 const char *url, 276 const char *upload_data, 277 size_t *upload_data_size) 278 { 279 const struct DH_RequestHandler *rh = rc->rh; 280 const char *args[rh->nargs + 2]; 281 size_t ulen = strlen (url) + 1; 282 json_t *root = NULL; 283 const bool is_post = (0 == strcasecmp (rh->method, 284 MHD_HTTP_METHOD_POST)); 285 const bool is_patch = (0 == strcasecmp (rh->method, 286 MHD_HTTP_METHOD_PATCH)); 287 enum MHD_Result ret; 288 289 if (rh->needs_authorization) 290 { 291 const char *ah; 292 293 ah = MHD_lookup_connection_value (rc->connection, 294 MHD_HEADER_KIND, 295 MHD_HTTP_HEADER_AUTHORIZATION); 296 /* If 'admin_bearer' is not set, we do not require authorization; 297 should basically only apply for test systems */ 298 if ( (NULL != admin_bearer) && 299 ( (NULL == ah) || 300 (0 != strncasecmp (ah, 301 "Bearer ", 302 strlen ("Bearer "))) || 303 (0 != strcmp (ah + strlen ("Bearer "), 304 admin_bearer)) ) ) 305 { 306 GNUNET_break_op (0); 307 return TALER_MHD_reply_with_error (rc->connection, 308 MHD_HTTP_FORBIDDEN, 309 TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT, 310 "authorization required"); 311 } 312 } 313 /* We do check for "ulen" here, because we'll later stack-allocate a buffer 314 of that size and don't want to enable malicious clients to cause us 315 huge stack allocations. */ 316 if (ulen > 512) 317 { 318 /* 512 is simply "big enough", as it is bigger than "6 * 54", 319 which is the longest URL format we ever get (for 320 /deposits/). The value should be adjusted if we ever define protocol 321 endpoints with plausibly longer inputs. */ 322 GNUNET_break_op (0); 323 return TALER_MHD_reply_with_error (rc->connection, 324 MHD_HTTP_URI_TOO_LONG, 325 TALER_EC_GENERIC_URI_TOO_LONG, 326 url); 327 } 328 329 /* All POST and PATCH endpoints come with a body in JSON format. So we parse 330 the JSON here. */ 331 if (is_post || is_patch) 332 { 333 enum GNUNET_GenericReturnValue res; 334 335 res = TALER_MHD_parse_post_json (rc->connection, 336 &rc->opaque_post_parsing_context, 337 upload_data, 338 upload_data_size, 339 &root); 340 if (GNUNET_SYSERR == res) 341 { 342 GNUNET_assert (NULL == root); 343 return MHD_NO; /* bad upload, could not even generate error */ 344 } 345 if ( (GNUNET_NO == res) || 346 (NULL == root) ) 347 { 348 GNUNET_assert (NULL == root); 349 return MHD_YES; /* so far incomplete upload or parser error */ 350 } 351 } 352 353 { 354 char d[ulen]; 355 unsigned int i; 356 char *sp; 357 358 /* Parse command-line arguments */ 359 /* make a copy of 'url' because 'strtok_r()' will modify */ 360 GNUNET_memcpy (d, 361 url, 362 ulen); 363 i = 0; 364 args[i++] = strtok_r (d, "/", &sp); 365 while ( (NULL != args[i - 1]) && 366 (i <= rh->nargs + 1) ) 367 args[i++] = strtok_r (NULL, "/", &sp); 368 /* make sure above loop ran nicely until completion, and also 369 that there is no excess data in 'd' afterwards */ 370 if ( ( (rh->nargs_is_upper_bound) && 371 (i - 1 > rh->nargs) ) || 372 ( (! rh->nargs_is_upper_bound) && 373 (i - 1 != rh->nargs) ) ) 374 { 375 char emsg[128 + 512]; 376 377 GNUNET_snprintf (emsg, 378 sizeof (emsg), 379 "Got %u+/%u segments for `%s' request (`%s')", 380 i - 1, 381 rh->nargs, 382 rh->url, 383 url); 384 GNUNET_break_op (0); 385 json_decref (root); 386 return TALER_MHD_reply_with_error (rc->connection, 387 MHD_HTTP_NOT_FOUND, 388 TALER_EC_DONAU_GENERIC_WRONG_NUMBER_OF_SEGMENTS, 389 emsg); 390 } 391 GNUNET_assert (NULL == args[i - 1]); 392 393 /* Above logic ensures that 'root' is exactly non-NULL for POST operations, 394 so we test for 'root' to decide which handler to invoke. */ 395 if (is_post) 396 ret = rh->handler.post (rc, 397 root, 398 args); 399 else if (is_patch) 400 ret = rh->handler.patch (rc, 401 root, 402 args); 403 else if (0 == strcasecmp (rh->method, 404 MHD_HTTP_METHOD_DELETE)) 405 ret = rh->handler.delete (rc, 406 args); 407 else /* Only GET left */ 408 ret = rh->handler.get (rc, 409 args); 410 } 411 json_decref (root); 412 return ret; 413 } 414 415 416 /** 417 * Handle a "/charities" GET request. 418 * 419 * @param rc request context 420 * @param args empty array 421 * @return MHD result code 422 */ 423 static enum MHD_Result 424 handle_get_charities (struct DH_RequestContext *rc, 425 const char *const args[]) 426 { 427 GNUNET_break (NULL == args[0]); 428 return DH_handler_get_charities (rc); 429 } 430 431 432 /** 433 * Handle a "/charities/$CHARITY_ID" GET request. 434 * 435 * @param rc request context 436 * @param args charity id (in args[0]) 437 * @return MHD result code 438 */ 439 static enum MHD_Result 440 handle_get_charity_id (struct DH_RequestContext *rc, 441 const char *const args[1]) 442 { 443 struct DONAU_CharitySignatureP charity_sig; 444 bool sig_required = true; 445 446 TALER_MHD_parse_request_header_auto ( 447 rc->connection, 448 DONAU_HTTP_HEADER_CHARITY_SIGNATURE, 449 &charity_sig, 450 sig_required); 451 return DH_handler_get_charity (rc, 452 &charity_sig, 453 args[0]); 454 } 455 456 457 /** 458 * Handle incoming HTTP request. 459 * 460 * @param cls closure for MHD daemon (unused) 461 * @param connection the connection 462 * @param url the requested url 463 * @param method the method (POST, GET, ...) 464 * @param version HTTP version (ignored) 465 * @param upload_data request data 466 * @param upload_data_size size of @a upload_data in bytes 467 * @param con_cls closure for request (a `struct DH_RequestContext *`) 468 * @return MHD result code 469 */ 470 static enum MHD_Result 471 handle_mhd_request (void *cls, 472 struct MHD_Connection *connection, 473 const char *url, 474 const char *method, 475 const char *version, 476 const char *upload_data, 477 size_t *upload_data_size, 478 void **con_cls) 479 { 480 static struct DH_RequestHandler handlers[] = { 481 /* Terms of service */ 482 { 483 .url = "terms", 484 .method = MHD_HTTP_METHOD_GET, 485 .handler.get = &DH_handler_terms 486 }, 487 /* Privacy policy */ 488 { 489 .url = "privacy", 490 .method = MHD_HTTP_METHOD_GET, 491 .handler.get = &DH_handler_privacy 492 }, 493 /* Configuration */ 494 { 495 .url = "config", 496 .method = MHD_HTTP_METHOD_GET, 497 .handler.get = &DH_handler_get_config 498 }, 499 /* GET keys endpoints (we only really have "/keys") */ 500 { 501 .url = "keys", 502 .method = MHD_HTTP_METHOD_GET, 503 .handler.get = &DH_handler_get_keys 504 }, 505 /* GET charities */ 506 { 507 .url = "charities", 508 .method = MHD_HTTP_METHOD_GET, 509 .handler.get = &handle_get_charities, 510 .needs_authorization = true 511 }, 512 /* GET charity/$ID */ 513 { 514 .url = "charity", 515 .method = MHD_HTTP_METHOD_GET, 516 .handler.get = &handle_get_charity_id, 517 .nargs = 1, 518 }, 519 /* POST charities */ 520 { 521 .url = "charities", 522 .method = MHD_HTTP_METHOD_POST, 523 .handler.post = &DH_handler_post_charities, 524 .needs_authorization = true 525 }, 526 /* GET history */ 527 { 528 .url = "history", 529 .method = MHD_HTTP_METHOD_GET, 530 .handler.get = &DH_handler_get_history, 531 .needs_authorization = true 532 }, 533 /* PATCH charities */ 534 { 535 .url = "charities", 536 .method = MHD_HTTP_METHOD_PATCH, 537 .handler.patch = &DH_handler_patch_charities, 538 .nargs = 1, 539 .needs_authorization = true 540 }, 541 /* DELETE charities */ 542 { 543 .url = "charities", 544 .method = MHD_HTTP_METHOD_DELETE, 545 .handler.delete = &DH_handler_delete_charities, 546 .nargs = 1, 547 .needs_authorization = true 548 }, 549 /* POST get csr values*/ 550 { 551 .url = "csr-issue", 552 .method = MHD_HTTP_METHOD_POST, 553 .handler.post = &DH_handler_post_csr_issue, 554 .nargs = 0 555 }, 556 /* POST batch issue receipts */ 557 { 558 .url = "batch-issue", 559 .method = MHD_HTTP_METHOD_POST, 560 .handler.post = &DH_handler_post_batch_issue, 561 .nargs = 1 562 }, 563 /* POST submitted receipts */ 564 { 565 .url = "batch-submit", 566 .method = MHD_HTTP_METHOD_POST, 567 .handler.post = &DH_handler_post_batch_submit 568 }, 569 /* GET donation statement */ 570 { 571 .url = "donation-statement", 572 .method = MHD_HTTP_METHOD_GET, 573 .handler.get = &DH_handler_get_donation_statement, 574 .nargs = 2 575 }, 576 /* mark end of list */ 577 { 578 .url = NULL 579 } 580 }; 581 struct DH_RequestContext *rc = *con_cls; 582 struct GNUNET_AsyncScopeSave old_scope; 583 const char *correlation_id = NULL; 584 585 (void) cls; 586 (void) version; 587 if (NULL == rc) 588 { 589 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 590 "Handling new request\n"); 591 592 /* We're in a new async scope! */ 593 rc = *con_cls = GNUNET_new (struct DH_RequestContext); 594 rc->start_time = GNUNET_TIME_absolute_get (); 595 GNUNET_async_scope_fresh (&rc->async_scope_id); 596 rc->url = url; 597 rc->connection = connection; 598 /* We only read the correlation ID on the first callback for every client */ 599 correlation_id = MHD_lookup_connection_value (connection, 600 MHD_HEADER_KIND, 601 "Taler-Correlation-Id"); 602 if ( (NULL != correlation_id) && 603 (GNUNET_YES != 604 GNUNET_CURL_is_valid_scope_id (correlation_id)) ) 605 { 606 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 607 "illegal incoming correlation ID\n"); 608 correlation_id = NULL; 609 } 610 611 /* Check if upload is in bounds */ 612 if ( (0 == strcasecmp (method, 613 MHD_HTTP_METHOD_POST)) || 614 (0 == strcasecmp (method, 615 MHD_HTTP_METHOD_PATCH)) ) 616 { 617 TALER_MHD_check_content_length (connection, 618 TALER_MHD_REQUEST_BUFFER_MAX); 619 } 620 } 621 622 GNUNET_async_scope_enter (&rc->async_scope_id, 623 &old_scope); 624 if (NULL != correlation_id) 625 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 626 "Handling request (%s) for URL '%s', correlation_id=%s\n", 627 method, 628 url, 629 correlation_id); 630 else 631 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 632 "Handling request (%s) for URL '%s'\n", 633 method, 634 url); 635 /* on repeated requests, check our cache first */ 636 if (NULL != rc->rh) 637 { 638 enum MHD_Result ret; 639 const char *start; 640 641 if ('\0' == url[0]) 642 /* strange, should start with '/', treat as just "/" */ 643 url = "/"; 644 start = strchr (url + 1, '/'); 645 if (NULL == start) 646 start = ""; 647 ret = proceed_with_handler (rc, 648 start, 649 upload_data, 650 upload_data_size); 651 GNUNET_async_scope_restore (&old_scope); 652 return ret; 653 } 654 655 if ( (0 == strcasecmp (method, 656 MHD_HTTP_METHOD_OPTIONS)) && 657 (0 == strcmp ("*", 658 url)) ) 659 { 660 GNUNET_async_scope_restore (&old_scope); 661 return TALER_MHD_reply_cors_preflight (connection); 662 } 663 664 if (0 == strcasecmp (method, 665 MHD_HTTP_METHOD_HEAD)) 666 method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ 667 668 /* parse first part of URL */ 669 { 670 bool found = false; 671 size_t tok_size; 672 const char *tok; 673 const char *rest; 674 675 if ('\0' == url[0]) 676 /* strange, should start with '/', treat as just "/" */ 677 url = "/"; 678 tok = url + 1; 679 rest = strchr (tok, '/'); 680 if (NULL == rest) 681 { 682 tok_size = strlen (tok); 683 } 684 else 685 { 686 tok_size = rest - tok; 687 rest++; /* skip over '/' */ 688 } 689 for (unsigned int i = 0; NULL != handlers[i].url; i++) 690 { 691 struct DH_RequestHandler *rh = &handlers[i]; 692 693 if ( (0 != strncmp (tok, 694 rh->url, 695 tok_size)) || 696 (tok_size != strlen (rh->url) ) ) 697 continue; 698 found = true; 699 /* The URL is a match! What we now do depends on the method. */ 700 if (0 == strcasecmp (method, 701 MHD_HTTP_METHOD_OPTIONS)) 702 { 703 GNUNET_async_scope_restore (&old_scope); 704 return TALER_MHD_reply_cors_preflight (connection); 705 } 706 GNUNET_assert (NULL != rh->method); 707 if (0 == strcasecmp (method, 708 rh->method)) 709 { 710 enum MHD_Result ret; 711 712 /* cache to avoid the loop next time */ 713 rc->rh = rh; 714 /* run handler */ 715 ret = proceed_with_handler (rc, 716 url + tok_size + 1, 717 upload_data, 718 upload_data_size); 719 GNUNET_async_scope_restore (&old_scope); 720 return ret; 721 } 722 } 723 724 if (found) 725 { 726 /* we found a matching address, but the method is wrong */ 727 struct MHD_Response *reply; 728 enum MHD_Result ret; 729 char *allowed = NULL; 730 731 GNUNET_break_op (0); 732 for (unsigned int i = 0; NULL != handlers[i].url; i++) 733 { 734 struct DH_RequestHandler *rh = &handlers[i]; 735 736 if ( (0 != strncmp (tok, 737 rh->url, 738 tok_size)) || 739 (tok_size != strlen (rh->url) ) ) 740 continue; 741 if (NULL == allowed) 742 { 743 allowed = GNUNET_strdup (rh->method); 744 } 745 else 746 { 747 char *tmp; 748 749 GNUNET_asprintf (&tmp, 750 "%s, %s", 751 allowed, 752 rh->method); 753 GNUNET_free (allowed); 754 allowed = tmp; 755 } 756 if (0 == strcasecmp (rh->method, 757 MHD_HTTP_METHOD_GET)) 758 { 759 char *tmp; 760 761 GNUNET_asprintf (&tmp, 762 "%s, %s", 763 allowed, 764 MHD_HTTP_METHOD_HEAD); 765 GNUNET_free (allowed); 766 allowed = tmp; 767 } 768 } 769 reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID, 770 method); 771 GNUNET_break (MHD_YES == 772 MHD_add_response_header (reply, 773 MHD_HTTP_HEADER_ALLOW, 774 allowed)); 775 GNUNET_free (allowed); 776 ret = MHD_queue_response (connection, 777 MHD_HTTP_METHOD_NOT_ALLOWED, 778 reply); 779 MHD_destroy_response (reply); 780 GNUNET_async_scope_restore (&old_scope); 781 return ret; 782 } 783 } 784 785 /* No handler matches, generate not found */ 786 { 787 enum MHD_Result ret; 788 789 ret = TALER_MHD_reply_with_error (connection, 790 MHD_HTTP_NOT_FOUND, 791 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 792 url); 793 GNUNET_async_scope_restore (&old_scope); 794 return ret; 795 } 796 } 797 798 799 /** 800 * Load configuration parameters for the donau 801 * server into the corresponding global variables. 802 * 803 * @return #GNUNET_OK on success 804 */ 805 static enum GNUNET_GenericReturnValue 806 donau_serve_process_config (void) 807 { 808 if (GNUNET_OK != 809 GNUNET_CONFIGURATION_get_value_number (DH_cfg, 810 "donau", 811 "MAX_REQUESTS", 812 &req_max)) 813 { 814 req_max = ULLONG_MAX; 815 } 816 817 if (GNUNET_OK != 818 TALER_config_get_currency (DH_cfg, 819 "donau", 820 &DH_currency)) 821 { 822 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 823 "donau", 824 "CURRENCY"); 825 return GNUNET_SYSERR; 826 } 827 828 if (GNUNET_OK != 829 GNUNET_CONFIGURATION_get_value_string (DH_cfg, 830 "donau", 831 "LEGAL_DOMAIN", 832 &DH_legal_domain)) 833 { 834 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 835 "donau", 836 "LEGAL_DOMAIN"); 837 return GNUNET_SYSERR; 838 } 839 if (GNUNET_OK != 840 GNUNET_CONFIGURATION_get_value_string (DH_cfg, 841 "donau", 842 "ADMIN_BEARER_TOKEN", 843 &admin_bearer)) 844 { 845 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 846 "donau", 847 "ADMIN_BEARER_TOKEN"); 848 } 849 if (GNUNET_OK != 850 GNUNET_CONFIGURATION_get_value_string (DH_cfg, 851 "donau", 852 "BASE_URL", 853 &DH_base_url)) 854 { 855 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 856 "donau", 857 "BASE_URL"); 858 return GNUNET_SYSERR; 859 } 860 if (! TALER_url_valid_charset (DH_base_url)) 861 { 862 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 863 "donau", 864 "BASE_URL", 865 "invalid URL"); 866 return GNUNET_SYSERR; 867 } 868 869 for (unsigned int i = 0; i<MAX_DB_RETRIES; i++) 870 { 871 DH_context = DONAUDB_connect (DH_cfg); 872 if (NULL != DH_context) 873 break; 874 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 875 "Failed to connect to DB, will try again %u times\n", 876 MAX_DB_RETRIES - i); 877 sleep (1); 878 } 879 if (NULL == DH_context) 880 { 881 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 882 "Failed to initialize DB subsystem. Giving up.\n"); 883 return GNUNET_SYSERR; 884 } 885 886 return GNUNET_OK; 887 } 888 889 890 /** 891 * Signature of the callback used by MHD to notify the application 892 * about completed connections. If we are running in test-mode with 893 * an input_filename, this function is used to terminate the HTTPD 894 * after the first request has been processed. 895 * 896 * @param cls client-defined closure, NULL 897 * @param connection connection handle (ignored) 898 * @param socket_context socket-specific pointer (ignored) 899 * @param toe reason for connection notification 900 */ 901 static void 902 connection_done (void *cls, 903 struct MHD_Connection *connection, 904 void **socket_context, 905 enum MHD_ConnectionNotificationCode toe) 906 { 907 (void) cls; 908 (void) connection; 909 (void) socket_context; 910 911 switch (toe) 912 { 913 case MHD_CONNECTION_NOTIFY_STARTED: 914 active_connections++; 915 break; 916 case MHD_CONNECTION_NOTIFY_CLOSED: 917 active_connections--; 918 if (DH_suicide && 919 (0 == active_connections) ) 920 GNUNET_SCHEDULER_shutdown (); 921 break; 922 } 923 } 924 925 926 /** 927 * Function run on shutdown. 928 * 929 * @param cls NULL 930 */ 931 static void 932 do_shutdown (void *cls) 933 { 934 (void) cls; 935 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 936 "Shutdown initiated\n"); 937 TALER_MHD_daemons_halt (); 938 TALER_MHD_daemons_destroy (); 939 DH_keys_finished (); 940 if (NULL != DH_context) 941 { 942 DONAUDB_disconnect (DH_context); 943 DH_context = NULL; 944 } 945 if (NULL != DH_curl_ctx) 946 { 947 GNUNET_CURL_fini (DH_curl_ctx); 948 DH_curl_ctx = NULL; 949 } 950 if (NULL != donau_curl_rc) 951 { 952 GNUNET_CURL_gnunet_rc_destroy (donau_curl_rc); 953 donau_curl_rc = NULL; 954 } 955 } 956 957 958 /** 959 * Callback invoked on every listen socket to start the 960 * respective MHD HTTP daemon. 961 * 962 * @param cls unused 963 * @param lsock the listen socket 964 */ 965 static void 966 start_daemon (void *cls, 967 int lsock) 968 { 969 struct MHD_Daemon *mhd; 970 971 (void) cls; 972 GNUNET_assert (-1 != lsock); 973 mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME 974 | MHD_USE_PIPE_FOR_SHUTDOWN 975 | MHD_USE_DEBUG | MHD_USE_DUAL_STACK 976 | MHD_USE_TCP_FASTOPEN, 977 0 /* port */, 978 NULL, NULL, 979 &handle_mhd_request, NULL, 980 MHD_OPTION_LISTEN_SOCKET, 981 lsock, 982 MHD_OPTION_EXTERNAL_LOGGER, 983 &TALER_MHD_handle_logs, 984 NULL, 985 MHD_OPTION_NOTIFY_COMPLETED, 986 &handle_mhd_completion_callback, 987 NULL, 988 MHD_OPTION_NOTIFY_CONNECTION, 989 &connection_done, 990 NULL, 991 MHD_OPTION_CONNECTION_TIMEOUT, 992 connection_timeout, 993 (0 == allow_address_reuse) 994 ? MHD_OPTION_END 995 : MHD_OPTION_LISTENING_ADDRESS_REUSE, 996 (unsigned int) allow_address_reuse, 997 MHD_OPTION_END); 998 if (NULL == mhd) 999 { 1000 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1001 "Failed to launch HTTP service. Is the port in use?\n"); 1002 GNUNET_SCHEDULER_shutdown (); 1003 return; 1004 } 1005 have_daemons = true; 1006 TALER_MHD_daemon_start (mhd); 1007 } 1008 1009 1010 /** 1011 * Main function that will be run by the scheduler. 1012 * 1013 * @param cls closure 1014 * @param args remaining command-line arguments 1015 * @param cfgfile name of the configuration file used (for saving, can be 1016 * NULL!) 1017 * @param config configuration 1018 */ 1019 static void 1020 run (void *cls, 1021 char *const *args, 1022 const char *cfgfile, 1023 const struct GNUNET_CONFIGURATION_Handle *config) 1024 { 1025 enum TALER_MHD_GlobalOptions go; 1026 1027 (void) cls; 1028 (void) args; 1029 (void ) cfgfile; 1030 go = TALER_MHD_GO_NONE; 1031 if (connection_close) 1032 go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; 1033 TALER_MHD_setup (go); 1034 DH_cfg = config; 1035 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1036 NULL); 1037 1038 if (GNUNET_OK != 1039 donau_serve_process_config ()) 1040 { 1041 DH_global_ret = EXIT_NOTCONFIGURED; 1042 GNUNET_SCHEDULER_shutdown (); 1043 return; 1044 } 1045 1046 if (GNUNET_OK != 1047 DH_keys_init ()) 1048 { 1049 DH_global_ret = EXIT_FAILURE; 1050 GNUNET_SCHEDULER_shutdown (); 1051 return; 1052 } 1053 1054 if (GNUNET_SYSERR == 1055 DONAUDB_preflight (DH_context)) 1056 { 1057 DH_global_ret = EXIT_FAILURE; 1058 GNUNET_SCHEDULER_shutdown (); 1059 return; 1060 } 1061 1062 DH_load_terms (DH_cfg); 1063 DH_curl_ctx 1064 = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1065 &donau_curl_rc); 1066 if (NULL == DH_curl_ctx) 1067 { 1068 GNUNET_break (0); 1069 DH_global_ret = EXIT_FAILURE; 1070 GNUNET_SCHEDULER_shutdown (); 1071 return; 1072 } 1073 donau_curl_rc = GNUNET_CURL_gnunet_rc_create (DH_curl_ctx); 1074 { 1075 enum GNUNET_GenericReturnValue ret; 1076 1077 ret = TALER_MHD_listen_bind (DH_cfg, 1078 "donau", 1079 &start_daemon, 1080 NULL); 1081 switch (ret) 1082 { 1083 case GNUNET_SYSERR: 1084 DH_global_ret = EXIT_NOTCONFIGURED; 1085 GNUNET_SCHEDULER_shutdown (); 1086 return; 1087 case GNUNET_NO: 1088 if (! have_daemons) 1089 { 1090 DH_global_ret = EXIT_NOTCONFIGURED; 1091 GNUNET_SCHEDULER_shutdown (); 1092 return; 1093 } 1094 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1095 "Could not open all configured listen sockets\n"); 1096 break; 1097 case GNUNET_OK: 1098 break; 1099 } 1100 } 1101 DH_global_ret = EXIT_SUCCESS; 1102 } 1103 1104 1105 /** 1106 * The main function of the donau-httpd server ("the donau"). 1107 * 1108 * @param argc number of arguments from the command line 1109 * @param argv command line arguments 1110 * @return 0 ok, 1 on error 1111 */ 1112 int 1113 main (int argc, 1114 char *const *argv) 1115 { 1116 const struct GNUNET_GETOPT_CommandLineOption options[] = { 1117 GNUNET_GETOPT_option_flag ('C', 1118 "connection-close", 1119 "force HTTP connections to be closed after each request", 1120 &connection_close), 1121 GNUNET_GETOPT_option_flag ('r', 1122 "allow-reuse-address", 1123 "allow multiple HTTPDs to listen to the same port", 1124 &allow_address_reuse), 1125 GNUNET_GETOPT_option_uint ('t', 1126 "timeout", 1127 "SECONDS", 1128 "after how long do connections timeout by default (in seconds)", 1129 &connection_timeout), 1130 GNUNET_GETOPT_option_timetravel ('T', 1131 "timetravel"), 1132 GNUNET_GETOPT_option_help ( 1133 DONAU_project_data (), 1134 "HTTP server providing a RESTful API to access a Taler donau"), 1135 GNUNET_GETOPT_OPTION_END 1136 }; 1137 enum GNUNET_GenericReturnValue ret; 1138 1139 ret = GNUNET_PROGRAM_run (DONAU_project_data (), 1140 argc, argv, 1141 "donau-httpd", 1142 "Taler donau HTTP service", 1143 options, 1144 &run, NULL); 1145 if (GNUNET_SYSERR == ret) 1146 return EXIT_INVALIDARGUMENT; 1147 if (GNUNET_NO == ret) 1148 return EXIT_SUCCESS; 1149 return DH_global_ret; 1150 } 1151 1152 1153 /* end of donau-httpd.c */