paivana-httpd_daemon.c (26248B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2026 Taler Systems SA 4 5 Paivana is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Affero General Public License 7 as published by the Free Software Foundation; either version 8 3, or (at your option) any later version. 9 10 Paivana is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty 12 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 13 the GNU Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public 16 License along with Paivana; see the file COPYING. If not, 17 write to the Free Software Foundation, Inc., 51 Franklin 18 Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 21 /** 22 * @author Christian Grothoff 23 * @file paivana-httpd_daemon.c 24 * @brief daemon functions 25 */ 26 27 #include "platform.h" 28 #include <curl/curl.h> 29 #include <gnunet/gnunet_util_lib.h> 30 #include <gnunet/gnunet_curl_lib.h> 31 #include <taler/taler_mhd_lib.h> 32 #include "paivana-httpd_cookie.h" 33 #include "paivana-httpd_daemon.h" 34 #include "paivana-httpd_helper.h" 35 #include "paivana-httpd_pay.h" 36 #include "paivana-httpd_reverse.h" 37 #include "paivana-httpd_templates.h" 38 39 40 struct RequestContext 41 { 42 43 /** 44 * HTTP connection to the client. 45 */ 46 struct MHD_Connection *connection; 47 48 /** 49 * Handle for request forwarding as reverse proxy. 50 */ 51 struct HttpRequest *hr; 52 53 /** 54 * Handle for processing actual payment. 55 */ 56 struct PayRequest *hp; 57 58 /** 59 * Full request URL. 60 */ 61 char *url; 62 63 /** 64 * True if this is a POST to the .well-known/paivana endpoint. 65 */ 66 bool is_paivana; 67 68 /** 69 * We are past the paywall, forward to client. 70 */ 71 bool do_forward; 72 73 /** 74 * Admission class charged to this request. Classification waits until the 75 * access handler has both the decoded path and method; the URI callback 76 * alone cannot distinguish a payment POST from a GET to the same path. 77 */ 78 enum 79 { 80 AC_UNDECIDED, 81 AC_ORDINARY, 82 AC_PAYMENT, 83 AC_REJECTED 84 } admission_class; 85 }; 86 87 88 /** 89 * How many seconds MHD lets a client connection sit idle before it 90 * closes it. 91 * 92 * This is *not* a per-request ceiling: MHD takes a suspended 93 * connection off its timeout lists, and every request that waits for 94 * the upstream or the merchant backend is suspended for exactly that 95 * time. What it bounds is the phases MHD itself owns -- reading the 96 * request line and headers, reading an upload, writing the response -- 97 * which is what keeps a client that opens a connection and says 98 * nothing from holding one of the #PH_connection_limit slots. The 99 * time a request may spend waiting is bounded by the libcurl timeouts 100 * in paivana-httpd_reverse.c instead. 101 */ 102 #define CLIENT_CONNECTION_TIMEOUT 16 103 104 105 /** 106 * Set to true if we started a daemon. 107 */ 108 static bool have_daemons; 109 110 /** 111 * MHD daemons retained so shutdown can quiesce only their listen sockets 112 * while the Taler scheduler adapter continues driving accepted requests. 113 */ 114 static struct MHD_Daemon **mhd_daemons; 115 116 /** 117 * Length of #mhd_daemons. 118 */ 119 static unsigned int mhd_daemons_length; 120 121 /** 122 * Number of requests whose URI callback has run and whose completion 123 * callback has not. This is the drain condition; half-written request lines 124 * are connections rather than requests and are closed at final cleanup. 125 */ 126 static unsigned int active_requests; 127 128 /** 129 * Ordinary requests admitted against the non-payment part of the connection 130 * budget. 131 */ 132 static unsigned int ordinary_requests; 133 134 /** 135 * Payment redemption requests admitted against the reserved part of the 136 * connection budget. 137 */ 138 static unsigned int payment_requests; 139 140 /** 141 * True after listeners have been quiesced for graceful shutdown. 142 */ 143 static bool draining; 144 145 146 /** 147 * Queue a controlled-overload response and force the connection closed. 148 * 149 * Payment callers receive the machine-readable Taler error used for local 150 * resource exhaustion. Other requests receive a small static HTML body so 151 * overload itself does not allocate or render a template. `Retry-After: 1' 152 * discourages a hot retry loop without claiming a longer outage. 153 * 154 * @param connection client connection 155 * @param payment true for the payment endpoint 156 * @return MHD result 157 */ 158 static enum MHD_Result 159 reply_overloaded (struct MHD_Connection *connection, 160 bool payment) 161 { 162 static const char body[] = 163 "<!doctype html><title>Service unavailable</title>" 164 "<p>Paivana is temporarily at capacity. Please retry.</p>"; 165 struct MHD_Response *response; 166 enum MHD_Result ret; 167 168 if (payment) 169 response = TALER_MHD_make_error ( 170 TALER_EC_GENERIC_OS_RESOURCE_ALLOCATION_FAILURE, 171 "Paivana payment-check capacity is exhausted"); 172 else 173 response = MHD_create_response_from_buffer_static (sizeof (body) - 1, 174 body); 175 if (NULL == response) 176 return MHD_NO; 177 if ( (! payment) && 178 (MHD_YES != 179 MHD_add_response_header (response, 180 MHD_HTTP_HEADER_CONTENT_TYPE, 181 "text/html; charset=utf-8")) ) 182 goto fail; 183 if ( (MHD_YES != 184 MHD_add_response_header (response, 185 MHD_HTTP_HEADER_CONNECTION, 186 "close")) || 187 (MHD_YES != 188 MHD_add_response_header (response, 189 MHD_HTTP_HEADER_RETRY_AFTER, 190 "1")) ) 191 goto fail; 192 ret = MHD_queue_response (connection, 193 MHD_HTTP_SERVICE_UNAVAILABLE, 194 response); 195 MHD_destroy_response (response); 196 return ret; 197 fail: 198 MHD_destroy_response (response); 199 return MHD_NO; 200 } 201 202 203 /** 204 * Decide whether a request may consume one of the process-wide slots. 205 * 206 * MHD's connection limit remains the hard transport ceiling. This second 207 * request-level gate stops ordinary requests at CONNECTION_LIMIT minus 208 * PAYMENT_CONNECTION_LIMIT, leaving the remainder available for payment 209 * POSTs. It cannot reserve against clients that connect but never finish a 210 * request line; CLIENT_CONNECTION_TIMEOUT bounds that separate slowloris 211 * case. 212 * 213 * @param rc request context 214 * @param payment whether this is a payment POST 215 * @return true if admitted 216 */ 217 static bool 218 admit_request (struct RequestContext *rc, 219 bool payment) 220 { 221 GNUNET_assert (AC_UNDECIDED == rc->admission_class); 222 if (draining) 223 { 224 rc->admission_class = AC_REJECTED; 225 return false; 226 } 227 if (payment) 228 { 229 if (payment_requests >= PH_payment_connection_limit) 230 { 231 rc->admission_class = AC_REJECTED; 232 return false; 233 } 234 payment_requests++; 235 rc->admission_class = AC_PAYMENT; 236 return true; 237 } 238 if (ordinary_requests >= 239 PH_connection_limit - PH_payment_connection_limit) 240 { 241 rc->admission_class = AC_REJECTED; 242 return false; 243 } 244 ordinary_requests++; 245 rc->admission_class = AC_ORDINARY; 246 return true; 247 } 248 249 250 /** 251 * Is the request target something we may decide about and then forward 252 * unchanged? 253 * 254 * Two strings describe one request here, and they are not the same: 255 * @a raw is the target exactly as it arrived on the request line -- 256 * that is what we hand to libcurl -- while @a url is MHD's 257 * percent-decoded, query-stripped path, and that is what the WHITELIST 258 * expression and the templates' expressions are matched against. 259 * Neither is normalized, but libcurl normalizes the URL we give it 260 * (CURLOPT_PATH_AS_IS is not set), so "/assets/../premium/x" matches a 261 * WHITELIST of "/assets/.*" and is then fetched as "/premium/x". 262 * Rather than decide about one resource and fetch another, refuse 263 * anything not already in normal form; browsers normalize before 264 * sending, so only hand-built requests are affected. 265 * 266 * Non-origin-form targets are refused for a second reason: such a 267 * target -- an absolute-form one, or merely one starting with "@" -- 268 * is concatenated onto DESTINATION_BASE_URL, where it can re-parse as 269 * userinfo (RFC 3986 section 3.2.1) and let the client choose the host 270 * we connect to. 271 * 272 * @param raw request target as it arrived on the request line 273 * @param url percent-decoded path MHD gives the handler 274 * @return true if the request may proceed 275 */ 276 static bool 277 canonical_request_target (const char *raw, 278 const char *url) 279 { 280 const char *p; 281 282 if ( ('/' != raw[0]) || 283 ('/' != url[0]) ) 284 return false; /* not origin-form; RFC 9112 section 3.2.1 */ 285 /* Reject ".", ".." and empty segments: RFC 3986 section 5.2.4's 286 remove_dot_segments is exactly what libcurl would apply behind our 287 back, after we have already decided. */ 288 for (p = url; NULL != p; p = strchr (p + 1, '/')) 289 { 290 const char *seg = p + 1; 291 const char *end = strchr (seg, '/'); 292 size_t len = (NULL == end) ? strlen (seg) : (size_t) (end - seg); 293 294 if (0 == len) 295 return ('\0' == *seg); /* a trailing '/' is fine, "//" is not */ 296 if ( ( (1 == len) && 297 ('.' == seg[0]) ) || 298 ( (2 == len) && 299 ('.' == seg[0]) && 300 ('.' == seg[1]) ) ) 301 return false; 302 } 303 return true; 304 } 305 306 307 /** 308 * Main MHD callback for handling requests. 309 * 310 * @param cls unused 311 * @param con MHD connection handle 312 * @param url the url in the request 313 * @param meth the HTTP method used ("GET", "PUT", etc.) 314 * @param ver the HTTP version string (i.e. "HTTP/1.1") 315 * @param upload_data the data being uploaded (excluding HEADERS, 316 * for a POST that fits into memory and that is encoded 317 * with a supported encoding, the POST data will NOT be 318 * given in upload_data and is instead available as 319 * part of MHD_get_connection_values; very large POST 320 * data *will* be made available incrementally in 321 * upload_data) 322 * @param upload_data_size set initially to the size of the 323 * @a upload_data provided; the method must update this 324 * value to the number of bytes NOT processed; 325 * @param con_cls pointer to the `struct RequestContext` that 326 * mhd_log_callback() made for this request 327 * @return #MHD_YES if the connection was handled successfully, 328 * #MHD_NO if the socket must be closed due to a serious 329 * error while handling the request 330 */ 331 static enum MHD_Result 332 create_response (void *cls, 333 struct MHD_Connection *con, 334 const char *url, 335 const char *meth, 336 const char *ver, 337 const char *upload_data, 338 size_t *upload_data_size, 339 void **con_cls) 340 { 341 struct RequestContext *rc = *con_cls; 342 const char *cookie; 343 bool ok = false; 344 struct GNUNET_Buffer buf; 345 char *website; 346 const bool payment 347 = ( (0 == strcmp (url, 348 "/.well-known/paivana")) && 349 (0 == strcasecmp (meth, 350 MHD_HTTP_METHOD_POST)) ); 351 352 (void) cls; 353 memset (&buf, 354 0, 355 sizeof (buf)); 356 if (AC_UNDECIDED == rc->admission_class) 357 (void) admit_request (rc, 358 payment); 359 if (AC_REJECTED == rc->admission_class) 360 return reply_overloaded (con, 361 payment); 362 if (! canonical_request_target (rc->url, 363 url)) 364 { 365 GNUNET_break_op (0); 366 return TALER_MHD_reply_with_error ( 367 rc->connection, 368 MHD_HTTP_BAD_REQUEST, 369 TALER_EC_GENERIC_PARAMETER_MALFORMED, 370 "request-target must be in origin-form and free of '.', '..'" 371 " and empty path segments"); 372 } 373 if ( (! rc->is_paivana) && 374 payment ) 375 { 376 rc->is_paivana = true; 377 } 378 if (rc->is_paivana) 379 { 380 if (PH_no_check) 381 { 382 /* paywall disabled, 501 */ 383 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 384 "Paywall disabled, refusing to respond to %s\n", 385 url); 386 return TALER_MHD_reply_with_error (rc->connection, 387 MHD_HTTP_NOT_IMPLEMENTED, 388 TALER_EC_PAIVANA_PAYWALL_DISABLED, 389 NULL); 390 } 391 if (NULL == rc->hp) 392 { 393 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 394 "Client POSTed payment, checking validity\n"); 395 rc->hp = PAIVANA_HTTPD_payment_create (rc->connection); 396 } 397 return PAIVANA_HTTPD_payment_handle (rc->hp, 398 upload_data, 399 upload_data_size); 400 } 401 402 if ( (! rc->do_forward) && 403 (PH_MAX_URL_LENGTH < strlen (url)) ) 404 { 405 /* Refuse before either regexec() -- the whitelist just below and 406 the templates' expressions further down both run on the 407 pre-payment path with a client-controlled subject. Refusing 408 outright (rather than skipping the whitelist) also keeps this 409 from being read as "not whitelisted, so charge for it": there 410 is nothing to charge for at this length. Requests already 411 destined to be forwarded reach no expression at all, and are 412 left alone -- notably every request under -n, where paivana is 413 a plain reverse proxy. */ 414 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 415 "Refusing %llu byte URL\n", 416 (unsigned long long) strlen (url)); 417 return TALER_MHD_reply_with_error (rc->connection, 418 MHD_HTTP_URI_TOO_LONG, 419 TALER_EC_GENERIC_URI_TOO_LONG, 420 NULL); 421 } 422 423 if (PH_have_whitelist_ex && (! rc->do_forward)) 424 { 425 rc->do_forward = (0 == 426 regexec (&PH_whitelist_ex, 427 url, 428 0, 429 NULL, 430 0)); 431 } 432 433 if (rc->do_forward) 434 goto do_forward; 435 436 if ( (0 == strncmp (url, 437 "/.well-known/paivana/templates/", 438 strlen ("/.well-known/paivana/templates/"))) && 439 ( (0 == strcasecmp (meth, 440 MHD_HTTP_METHOD_GET)) || 441 /* HEAD must be answered like GET (MHD suppresses the body); 442 otherwise it falls through to the paywall check below, 443 which redirects to this very URL again -- forever. */ 444 (0 == strcasecmp (meth, 445 MHD_HTTP_METHOD_HEAD)) ) ) 446 { 447 const char *id = &url[strlen ("/.well-known/paivana/templates/")]; 448 449 return PAIVANA_HTTPD_return_template (rc->connection, 450 id); 451 } 452 453 if (! PAIVANA_HTTPD_get_base_url (con, 454 &buf)) 455 { 456 GNUNET_break (0); 457 GNUNET_buffer_clear (&buf); 458 return TALER_MHD_reply_with_error ( 459 con, 460 MHD_HTTP_BAD_REQUEST, 461 TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, 462 "Host or X-Forwarded-Host required"); 463 } 464 GNUNET_buffer_write_str (&buf, 465 url); 466 website = GNUNET_buffer_reap_str (&buf); 467 cookie = MHD_lookup_connection_value (con, 468 MHD_COOKIE_KIND, 469 PAIVANA_COOKIE_NAME); 470 if (NULL != cookie) 471 { 472 void *ca = NULL; 473 size_t ca_len = 0; 474 475 /* If we cannot get the client address, we just 476 use 0/NULL and log an error. */ 477 GNUNET_break (PAIVANA_HTTPD_get_client_address (con, 478 &ca, 479 &ca_len)); 480 ok = PAIVANA_HTTPD_check_cookie (cookie, 481 website, 482 ca_len, 483 ca); 484 /* The cookie value itself is deliberately not logged: it is the 485 bearer credential the client paid for, it outlives its own 486 Max-Age in the log file, and anyone sharing the client's address 487 -- everyone behind one NAT -- could replay it. */ 488 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 489 "Client sent an access cookie for %s: %s\n", 490 website, 491 ok ? "good" : "invalid"); 492 GNUNET_free (ca); 493 } 494 if (! ok) 495 { 496 enum GNUNET_GenericReturnValue ret; 497 498 ret = PAIVANA_HTTPD_search_templates (con, 499 website); 500 if (GNUNET_SYSERR != ret) 501 { 502 GNUNET_free (website); 503 /* #GNUNET_OK only says a response was queued; besides the 504 redirect to the paywall that may be a 414 or a 400. */ 505 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 506 "A paywall applies, response queued: %s\n", 507 (GNUNET_OK == ret) ? "ok" : "failed"); 508 return (GNUNET_OK == ret) ? MHD_YES : MHD_NO; 509 } 510 } 511 GNUNET_free (website); 512 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 513 "Request OK, no paywall applies!\n"); 514 rc->do_forward = true; 515 do_forward: 516 if (NULL == rc->hr) 517 rc->hr = PAIVANA_HTTPD_reverse_create (rc->connection, 518 rc->url); 519 return PAIVANA_HTTPD_reverse (rc->hr, 520 con, 521 url, 522 meth, 523 ver, 524 upload_data, 525 upload_data_size); 526 } 527 528 529 /** 530 * Function called when MHD decides that we 531 * are done with a request. 532 * 533 * @param cls NULL 534 * @param connection connection handle 535 * @param con_cls value as set by the last call to 536 * the MHD_AccessHandlerCallback, should be 537 * our `struct RequestContext *` (created in `mhd_log_callback()`) 538 * @param toe reason for request termination (ignored) 539 */ 540 static void 541 mhd_completed_cb (void *cls, 542 struct MHD_Connection *connection, 543 void **con_cls, 544 enum MHD_RequestTerminationCode toe) 545 { 546 struct RequestContext *rc = *con_cls; 547 548 (void) cls; 549 (void) connection; 550 if (NULL == rc) 551 return; 552 if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) 553 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 554 "MHD encountered error handling request to %s: %d\n", 555 rc->url, 556 toe); 557 if (NULL != rc->hr) 558 PAIVANA_HTTPD_reverse_cleanup (rc->hr); 559 if (NULL != rc->hp) 560 PAIVANA_HTTPD_payment_destroy (rc->hp); 561 switch (rc->admission_class) 562 { 563 case AC_ORDINARY: 564 GNUNET_assert (ordinary_requests > 0); 565 ordinary_requests--; 566 break; 567 case AC_PAYMENT: 568 GNUNET_assert (payment_requests > 0); 569 payment_requests--; 570 break; 571 case AC_UNDECIDED: 572 case AC_REJECTED: 573 break; 574 } 575 GNUNET_assert (active_requests > 0); 576 active_requests--; 577 GNUNET_free (rc->url); 578 GNUNET_free (rc); 579 *con_cls = NULL; 580 } 581 582 583 /** 584 * Function called when MHD first processes an incoming connection. 585 * Gives us the respective URI information. 586 * 587 * We use this to associate the `struct MHD_Connection` with our 588 * internal `struct HttpRequest` data structure (by checking 589 * for matching sockets). 590 * 591 * @param cls unused 592 * @param url the request target exactly as it arrived on the request 593 * line: RAW, i.e. neither percent-decoded nor stripped of its 594 * query, unlike the @a url create_response() is given. That is 595 * the string we forward, and the difference between the two is 596 * why canonical_request_target() has to see both. 597 * @param connection MHD connection object for the request 598 * @return the `struct RequestContext` that this @a connection is for 599 */ 600 static void * 601 mhd_log_callback (void *cls, 602 const char *url, 603 struct MHD_Connection *connection) 604 { 605 struct RequestContext *rc; 606 607 (void) cls; 608 rc = GNUNET_new (struct RequestContext); 609 active_requests++; 610 rc->connection = connection; 611 rc->url = GNUNET_strdup (url); 612 rc->do_forward = (1 == PH_no_check); 613 return rc; 614 } 615 616 617 /** 618 * Listen sockets handed to us by TALER_MHD_listen_bind(), collected 619 * before any daemon is started; see #collect_socket(). 620 */ 621 static int *lsocks; 622 623 /** 624 * Length of #lsocks. 625 */ 626 static unsigned int lsocks_length; 627 628 629 /** 630 * Callback invoked on every listen socket: remember it. 631 * 632 * The daemons are not started here because 633 * MHD_OPTION_CONNECTION_LIMIT is per daemon and cannot be changed 634 * afterwards, while the budget it has to divide is process-wide -- 635 * TALER_MHD_listen_bind() starts one daemon per getaddrinfo() result, 636 * so an empty BIND_TO yields two, and giving each the whole budget 637 * would double it. We cannot know the divisor until the last socket 638 * has been handed over. 639 * 640 * @param cls unused 641 * @param lsock the listen socket 642 */ 643 static void 644 collect_socket (void *cls, 645 int lsock) 646 { 647 (void) cls; 648 GNUNET_assert (-1 != lsock); 649 GNUNET_array_append (lsocks, 650 lsocks_length, 651 lsock); 652 } 653 654 655 /** 656 * Start one MHD daemon on @a lsock, entitled to @a climit concurrent 657 * connections. 658 * 659 * @param lsock the listen socket, ownership passes to MHD on success 660 * @param climit connection limit for this daemon 661 */ 662 static void 663 start_daemon (int lsock, 664 unsigned int climit) 665 { 666 struct MHD_Daemon *mhd; 667 668 mhd = MHD_start_daemon ( 669 MHD_USE_DEBUG 670 | MHD_ALLOW_SUSPEND_RESUME, 671 0, 672 NULL, NULL, 673 &create_response, NULL, 674 /* First, and MHD says so itself: it logs about the options it is 675 still parsing, and anything before this one would be printed by 676 its built-in logger. Without the option at all -- MHD_USE_DEBUG 677 above is the same bit as MHD_USE_ERROR_LOG -- MHD fprintf()s to 678 stderr for the lifetime of the daemon, outside GNUnet's level 679 and logfile handling entirely. Malformed request lines are 680 remotely triggerable, so that is client-influenced output 681 arriving on stderr regardless of -L ERROR. */ 682 MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs, NULL, 683 MHD_OPTION_LISTEN_SOCKET, 684 lsock, 685 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) CLIENT_CONNECTION_TIMEOUT, 686 MHD_OPTION_CONNECTION_LIMIT, climit, 687 MHD_OPTION_PER_IP_CONNECTION_LIMIT, PH_per_ip_connection_limit, 688 MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL, 689 MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL, 690 MHD_OPTION_END); 691 692 if (NULL == mhd) 693 { 694 /* Not fatal on its own: TALER_MHD_listen_bind() starts one daemon 695 per getaddrinfo() result, so an empty BIND_TO yields a v4 and a 696 v6 socket, and losing the whole process because MHD would not 697 take the v6 one is the same over-reaction the GNUNET_NO arm in 698 PAIVANA_HTTPD_serve_requests() already refuses to make for a 699 socket that would not bind. Serving on nothing at all is still 700 fatal; that is decided once, by the caller. */ 701 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 702 "Failed to start HTTP daemon on one of the listen sockets\n"); 703 /* MHD did not take ownership of the socket; close it ourselves. */ 704 GNUNET_break (0 == close (lsock)); 705 return; 706 } 707 have_daemons = true; 708 GNUNET_array_append (mhd_daemons, 709 mhd_daemons_length, 710 mhd); 711 TALER_MHD_daemon_start (mhd); 712 } 713 714 715 unsigned int 716 PAIVANA_HTTPD_begin_drain (void) 717 { 718 if (! draining) 719 { 720 draining = true; 721 for (unsigned int i = 0; i < mhd_daemons_length; i++) 722 { 723 MHD_socket fd; 724 725 fd = MHD_quiesce_daemon (mhd_daemons[i]); 726 if (MHD_INVALID_SOCKET == fd) 727 { 728 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 729 "HTTP listen socket was already quiesced\n"); 730 continue; 731 } 732 /* TALER_MHD_daemon_start() has a GNUnet select task whose fdset was 733 built before quiescing and therefore still contains @a fd. Triggering 734 cancels that pending task and schedules a fresh MHD_run(), which will 735 rebuild the set without the listener. Closing first leaves select() 736 holding an invalid descriptor and makes the scheduler abort with 737 EBADF. The trigger task cannot run until this callback returns, so 738 the returned socket is no longer referenced when close() follows. */ 739 TALER_MHD_daemon_trigger (); 740 GNUNET_break (0 == close (fd)); 741 } 742 } 743 return active_requests; 744 } 745 746 747 unsigned int 748 PAIVANA_HTTPD_active_requests (void) 749 { 750 return active_requests; 751 } 752 753 754 void 755 PAIVANA_HTTPD_daemons_destroy (void) 756 { 757 TALER_MHD_daemons_halt (); 758 TALER_MHD_daemons_destroy (); 759 GNUNET_array_grow (mhd_daemons, 760 mhd_daemons_length, 761 0); 762 have_daemons = false; 763 } 764 765 766 void 767 PAIVANA_HTTPD_serve_requests () 768 { 769 enum GNUNET_GenericReturnValue ret; 770 771 ret = TALER_MHD_listen_bind (PH_cfg, 772 "paivana", 773 &collect_socket, 774 NULL); 775 if (0 != lsocks_length) 776 { 777 unsigned int climit 778 = GNUNET_MAX (1, 779 PH_connection_limit / lsocks_length); 780 781 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 782 "Serving on %u listen socket(s), %u connection(s) each" 783 " (%u per client address, 0 = unlimited)\n", 784 lsocks_length, 785 climit, 786 PH_per_ip_connection_limit); 787 for (unsigned int i = 0; i < lsocks_length; i++) 788 start_daemon (lsocks[i], 789 climit); 790 GNUNET_array_grow (lsocks, 791 lsocks_length, 792 0); 793 } 794 switch (ret) 795 { 796 case GNUNET_SYSERR: 797 /* Configuration error; TALER_MHD_listen_bind() has diagnosed it. */ 798 PH_global_ret = EXIT_NOTCONFIGURED; 799 GNUNET_SCHEDULER_shutdown (); 800 return; 801 case GNUNET_NO: 802 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 803 "Could not open all configured listen sockets\n"); 804 break; 805 case GNUNET_OK: 806 break; 807 } 808 if (! have_daemons) 809 { 810 /* One policy for both ways a listener can fail to come up: a 811 socket that would not bind, and a socket MHD would not serve 812 on. Some is enough; none is not. */ 813 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 814 "Not a single listen socket came up, refusing to run\n"); 815 PH_global_ret = EXIT_NOTCONFIGURED; 816 GNUNET_SCHEDULER_shutdown (); 817 return; 818 } 819 }