paivana-httpd_reverse.c (47480B)
1 /* 2 This file is part of GNU Taler 3 Copyright (C) 2012-2014 GNUnet e.V. 4 Copyright (C) 2018, 2025, 2026 Taler Systems SA 5 6 GNU Taler is free software; you can redistribute it and/or 7 modify it under the terms of the GNU General Public License 8 as published by the Free Software Foundation; either version 9 3, or (at your option) any later version. 10 11 GNU Taler is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public 17 License along with GNU Taler; see the file COPYING. If not, 18 write to the Free Software Foundation, Inc., 51 Franklin 19 Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @author Martin Schanzenbach 24 * @author Christian Grothoff 25 * @author Marcello Stanisci 26 * @file src/backend/paivana-httpd_reverse.c 27 * @brief Reverse proxy logic that just forwards the request 28 */ 29 #include "platform.h" 30 #include <curl/curl.h> 31 #include <gnunet/gnunet_util_lib.h> 32 #include <taler/taler_mhd_lib.h> 33 #include "paivana-httpd.h" 34 #include "paivana-httpd_reverse.h" 35 36 37 /** 38 * Log curl error. 39 * 40 * @param level log level 41 * @param fun name of curl_easy-function that gave the error 42 * @param rc return code from curl 43 */ 44 #define LOG_CURL_EASY(level,fun,rc) \ 45 GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \ 46 __LINE__, \ 47 curl_easy_strerror (rc)) 48 49 50 /** 51 * State machine for HTTP requests (per request). 52 */ 53 enum RequestState 54 { 55 /** 56 * We've started receiving upload data from MHD. 57 * Initial state. 58 */ 59 REQUEST_STATE_CLIENT_UPLOAD_STARTED, 60 61 /** 62 * Wa have started uploading data to the proxied service. 63 */ 64 REQUEST_STATE_PROXY_UPLOAD_STARTED, 65 66 /** 67 * We're done with the upload from MHD. 68 */ 69 REQUEST_STATE_CLIENT_UPLOAD_DONE, 70 71 /** 72 * We're done uploading data to the proxied service. 73 */ 74 REQUEST_STATE_PROXY_UPLOAD_DONE, 75 76 /** 77 * We've finished uploading data via CURL and can now download. 78 */ 79 REQUEST_STATE_PROXY_DOWNLOAD_STARTED, 80 81 /** 82 * We've finished receiving download data from cURL. 83 */ 84 REQUEST_STATE_PROXY_DOWNLOAD_DONE 85 }; 86 87 88 /** 89 * A header list 90 */ 91 struct HttpResponseHeader 92 { 93 /** 94 * DLL 95 */ 96 struct HttpResponseHeader *next; 97 98 /** 99 * DLL 100 */ 101 struct HttpResponseHeader *prev; 102 103 /** 104 * Header type 105 */ 106 char *type; 107 108 /** 109 * Header value 110 */ 111 char *value; 112 }; 113 114 115 /** 116 * A structure for socks requests 117 */ 118 struct HttpRequest 119 { 120 121 /** 122 * Kept in DLL. 123 */ 124 struct HttpRequest *prev; 125 126 /** 127 * Kept in DLL. 128 */ 129 struct HttpRequest *next; 130 131 /** 132 * MHD request that triggered us. 133 */ 134 struct MHD_Connection *con; 135 136 /** 137 * Client socket read task 138 */ 139 struct GNUNET_SCHEDULER_Task *rtask; 140 141 /** 142 * Client socket write task 143 */ 144 struct GNUNET_SCHEDULER_Task *wtask; 145 146 /** 147 * Hold the response obtained by modifying the original one. 148 */ 149 struct MHD_Response *mod_response; 150 151 /** 152 * MHD response object for this request. 153 */ 154 struct MHD_Response *response; 155 156 /** 157 * The URL to fetch 158 */ 159 char *url; 160 161 /** 162 * Handle to cURL 163 */ 164 CURL *curl; 165 166 /** 167 * HTTP request headers for the curl request. 168 */ 169 struct curl_slist *headers; 170 171 /** 172 * Headers from response 173 */ 174 struct HttpResponseHeader *header_head; 175 176 /** 177 * Headers from response 178 */ 179 struct HttpResponseHeader *header_tail; 180 181 /** 182 * Buffer we use for moving data between MHD and 183 * curl (in both directions). 184 */ 185 char *io_buf; 186 187 /** 188 * Number of bytes already in the IO buffer. 189 */ 190 size_t io_len; 191 192 /** 193 * Number of bytes allocated for the IO buffer. 194 */ 195 unsigned int io_size; 196 197 /** 198 * HTTP response code to give to MHD for the response. 199 */ 200 unsigned int response_code; 201 202 /** 203 * Request processing state machine. 204 */ 205 enum RequestState state; 206 207 /** 208 * Did we suspend MHD processing? 209 */ 210 enum GNUNET_GenericReturnValue suspended; 211 212 /** 213 * Did we pause CURL processing? 214 */ 215 int curl_paused; 216 217 /** 218 * Have we observed the initial `HEADERS_PROCESSED` (a.k.a. "first") 219 * access-handler call for this request yet? MHD invokes the 220 * handler once immediately after parsing the request headers with 221 * `upload_data_size == 0` (and no body data yet), then again later 222 * with body chunks (if any), and finally once more with 223 * `upload_data_size == 0` at `FULL_REQ_RECEIVED`. We must defer 224 * curl setup to the "final" call so that request bodies are 225 * available in `io_buf` by the time curl runs. 226 */ 227 bool accepted; 228 229 /** 230 * Set when the request body exceeded `PH_request_buffer_max`. We 231 * must drain the rest of the upload silently and queue the 413 232 * response on the "final" access-handler call. 233 */ 234 bool reject_upload; 235 236 /** 237 * Concatenated value of the client's Via header(s), if any. Per 238 * RFC 9110 §7.6.3 a proxy must *append* its own entry to this 239 * list, not replace it; we capture the inbound value here before 240 * iterating over the other headers and emit our appended Via when 241 * building the curl request. 242 */ 243 char *client_via; 244 245 /** 246 * Concatenated value of the client's Connection header(s), if 247 * any. Per RFC 9110 §7.6.1 this lists additional header names 248 * that are connection-specific and must not be forwarded; we 249 * consult this list in `con_val_iter` to filter such headers out 250 * of the upstream request. 251 */ 252 char *client_connection; 253 }; 254 255 256 /** 257 * DLL of active HTTP requests. 258 */ 259 static struct HttpRequest *hr_head; 260 261 /** 262 * DLL of active HTTP requests. 263 */ 264 static struct HttpRequest *hr_tail; 265 266 /** 267 * Response we return on cURL failures. 268 */ 269 static struct MHD_Response *curl_failure_response; 270 271 /** 272 * The cURL multi handle 273 */ 274 static CURLM *curl_multi; 275 276 /** 277 * The cURL download task (curl multi API). 278 */ 279 static struct GNUNET_SCHEDULER_Task *curl_download_task; 280 281 282 bool 283 PAIVANA_HTTPD_reverse_init (void) 284 { 285 static const char *failure_body = 286 "<!DOCTYPE html>\n" 287 "<html><head><title>Bad Gateway</title></head>" 288 "<body><h1>502 Bad Gateway</h1>" 289 "<p>The upstream server could not be reached.</p>" 290 "</body></html>\n"; 291 292 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 293 { 294 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 295 "cURL global init failed!\n"); 296 return false; 297 } 298 if (NULL == (curl_multi = curl_multi_init ())) 299 { 300 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 301 "Failed to create cURL multi handle!\n"); 302 return false; 303 } 304 curl_failure_response 305 = MHD_create_response_from_buffer_static (strlen (failure_body), 306 failure_body); 307 if (NULL == curl_failure_response) 308 { 309 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 310 "Failed to create cURL failure response!\n"); 311 return false; 312 } 313 GNUNET_break (MHD_YES == 314 MHD_add_response_header (curl_failure_response, 315 MHD_HTTP_HEADER_CONTENT_TYPE, 316 "text/html; charset=utf-8")); 317 return true; 318 } 319 320 321 void 322 PAIVANA_HTTPD_reverse_shutdown (void) 323 { 324 for (struct HttpRequest *hr = hr_head; 325 NULL != hr; 326 hr = hr->next) 327 { 328 if (GNUNET_YES == hr->suspended) 329 { 330 hr->suspended = GNUNET_NO; 331 MHD_resume_connection (hr->con); 332 } 333 } 334 if (NULL != curl_download_task) 335 { 336 GNUNET_SCHEDULER_cancel (curl_download_task); 337 curl_download_task = NULL; 338 } 339 if (NULL != curl_multi) 340 { 341 curl_multi_cleanup (curl_multi); 342 curl_multi = NULL; 343 } 344 if (NULL != curl_failure_response) 345 { 346 MHD_destroy_response (curl_failure_response); 347 curl_failure_response = NULL; 348 } 349 } 350 351 352 /** 353 * Is @a name a hop-by-hop HTTP header name that a proxy must not 354 * forward (RFC 9110 section 7.6.1 and RFC 7230, section 6.1). 355 * The client may name *additional* hop-by-hop headers in its 356 * Connection header; those are handled separately in 357 * `connection_lists_header()`. 358 * 359 * @param name header field name 360 * @return true if @a name is a hop-by-hop header 361 */ 362 static bool 363 is_hop_by_hop_header (const char *name) 364 { 365 static const char *const hop_headers[] = { 366 MHD_HTTP_HEADER_CONNECTION, 367 MHD_HTTP_HEADER_KEEP_ALIVE, 368 MHD_HTTP_HEADER_PROXY_AUTHENTICATE, 369 MHD_HTTP_HEADER_PROXY_AUTHORIZATION, 370 MHD_HTTP_HEADER_TE, 371 MHD_HTTP_HEADER_TRAILER, 372 MHD_HTTP_HEADER_TRANSFER_ENCODING, 373 MHD_HTTP_HEADER_UPGRADE, 374 NULL 375 }; 376 377 for (unsigned int i = 0; NULL != hop_headers[i]; i++) 378 if (0 == strcasecmp (name, 379 hop_headers[i])) 380 return true; 381 return false; 382 } 383 384 385 /** 386 * Return true if @a name appears (case-insensitively) as a 387 * comma-separated element of @a list. Used to honor RFC 9110 388 * §7.6.1: the client's Connection header lists additional 389 * hop-by-hop header names that must not be forwarded. 390 * 391 * @param list comma-separated list of header names, may be NULL 392 * @param name header name to look for 393 * @return true if @a list names @a name 394 */ 395 static bool 396 connection_lists_header (const char *list, 397 const char *name) 398 { 399 size_t namelen; 400 const char *p; 401 402 if (NULL == list) 403 return false; 404 namelen = strlen (name); 405 p = list; 406 while ('\0' != *p) 407 { 408 const char *comma; 409 size_t len; 410 411 while ( (' ' == *p) || 412 ('\t' == *p) || 413 (',' == *p) ) 414 p++; 415 if ('\0' == *p) 416 break; 417 comma = strchr (p, ','); 418 len = (NULL == comma) ? strlen (p) : (size_t) (comma - p); 419 while ( (len > 0) && 420 ( (' ' == p[len - 1]) || 421 ('\t' == p[len - 1]) ) ) 422 len--; 423 if ( (len == namelen) && 424 (0 == strncasecmp (p, name, namelen)) ) 425 return true; 426 if (NULL == comma) 427 break; 428 p = comma + 1; 429 } 430 return false; 431 } 432 433 434 /** 435 * Pre-iteration collector: records the client's Via and Connection 436 * headers on @a hr so the subsequent forwarding pass can append to 437 * Via (RFC 9110 §7.6.3) and honor the hop-by-hop names listed in 438 * Connection (RFC 9110 §7.6.1). Multiple values for the same 439 * header are joined with ", ", matching the list-header combining 440 * rule. 441 * 442 * @param cls our `struct HttpRequest *` 443 * @param kind value kind (unused) 444 * @param key header field name 445 * @param value header field value 446 * @return #MHD_YES to continue iteration 447 */ 448 static enum MHD_Result 449 collect_proxy_state (void *cls, 450 enum MHD_ValueKind kind, 451 const char *key, 452 const char *value) 453 { 454 struct HttpRequest *hr = cls; 455 char **target; 456 457 (void) kind; 458 if (NULL == value) 459 return MHD_YES; 460 if (0 == strcasecmp (MHD_HTTP_HEADER_VIA, 461 key)) 462 target = &hr->client_via; 463 else if (0 == strcasecmp (MHD_HTTP_HEADER_CONNECTION, 464 key)) 465 target = &hr->client_connection; 466 else 467 return MHD_YES; 468 if (NULL == *target) 469 { 470 *target = GNUNET_strdup (value); 471 } 472 else 473 { 474 char *combined; 475 476 GNUNET_asprintf (&combined, 477 "%s, %s", 478 *target, 479 value); 480 GNUNET_free (*target); 481 *target = combined; 482 } 483 return MHD_YES; 484 } 485 486 487 /* *************** HTTP handling with cURL ***************** */ 488 489 490 /** 491 * Transform _one_ CURL header (gotten from the request) into 492 * MHD format and put it into the response headers list; mostly 493 * copies the headers, but makes special adjustments based on 494 * control requests. 495 * 496 * @param buffer curl buffer with a single 497 * line of header data; not 0-terminated! 498 * @param size curl blocksize 499 * @param nmemb curl blocknumber 500 * @param cls our `struct HttpRequest *` 501 * @return size of processed bytes 502 */ 503 static size_t 504 curl_check_hdr (void *buffer, 505 size_t size, 506 size_t nmemb, 507 void *cls) 508 { 509 struct HttpRequest *hr = cls; 510 struct HttpResponseHeader *header; 511 size_t bytes = size * nmemb; 512 char *ndup; 513 const char *hdr_type; 514 char *hdr_val; 515 char *tok; 516 517 /* Raw line is not guaranteed to be null-terminated. */ 518 ndup = GNUNET_malloc (bytes + 1); 519 memcpy (ndup, 520 buffer, 521 bytes); 522 ndup[bytes] = '\0'; 523 hdr_type = strtok (ndup, ":"); 524 if (NULL == hdr_type) 525 { 526 GNUNET_free (ndup); 527 return bytes; 528 } 529 hdr_val = strtok (NULL, ""); 530 if (NULL == hdr_val) 531 { 532 GNUNET_free (ndup); 533 return bytes; 534 } 535 if (' ' == *hdr_val) 536 hdr_val++; 537 538 /* MHD does not allow certain characters in values, 539 * remove those, plus those could alter strings matching. */ 540 if (NULL != (tok = strchr (hdr_val, '\n'))) 541 *tok = '\0'; 542 if (NULL != (tok = strchr (hdr_val, '\r'))) 543 *tok = '\0'; 544 if (NULL != (tok = strchr (hdr_val, '\t'))) 545 *tok = '\0'; 546 PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n", 547 hdr_type, 548 hdr_val); 549 /* Skip "Content-length:" header as it will be wrong, given 550 that we are man-in-the-middling the connection */ 551 if (0 == strcasecmp (hdr_type, 552 MHD_HTTP_HEADER_CONTENT_LENGTH)) 553 { 554 GNUNET_free (ndup); 555 return bytes; 556 } 557 /* Skip hop-by-hop headers. In particular Transfer-Encoding 558 must not leak through: libcurl has already dechunked the 559 body for us and MHD will decide whether to re-chunk. */ 560 if (is_hop_by_hop_header (hdr_type)) 561 { 562 GNUNET_free (ndup); 563 return bytes; 564 } 565 if (0 != strlen (hdr_val)) /* Rely in MHD to set those */ 566 { 567 header = GNUNET_new (struct HttpResponseHeader); 568 header->type = GNUNET_strdup (hdr_type); 569 header->value = GNUNET_strdup (hdr_val); 570 GNUNET_CONTAINER_DLL_insert (hr->header_head, 571 hr->header_tail, 572 header); 573 } 574 GNUNET_free (ndup); 575 return bytes; 576 } 577 578 579 /** 580 * Create the MHD response with CURL's as starting base; 581 * mainly set the response code and parses the response into 582 * JSON, if it is such. 583 * 584 * @param hr pointer to where to store the new data. Despite 585 * its name, the struct contains response data as well. 586 * @return #GNUNET_OK if it succeeds. 587 */ 588 static enum GNUNET_GenericReturnValue 589 create_mhd_response_from_hr (struct HttpRequest *hr) 590 { 591 long resp_code; 592 593 if (NULL != hr->response) 594 { 595 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 596 "Response already set!\n"); 597 return GNUNET_SYSERR; 598 } 599 GNUNET_break (CURLE_OK == 600 curl_easy_getinfo (hr->curl, 601 CURLINFO_RESPONSE_CODE, 602 &resp_code)); 603 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 604 "Creating MHD response with code %u\n", 605 (unsigned int) resp_code); 606 hr->response_code = resp_code; 607 if (GNUNET_YES == hr->suspended) 608 { 609 MHD_resume_connection (hr->con); 610 hr->suspended = GNUNET_NO; 611 } 612 TALER_MHD_daemon_trigger (); 613 return GNUNET_OK; 614 } 615 616 617 /** 618 * Handle response payload data from cURL. 619 * Copies it into our `io_buf` to make it available to MHD. 620 * 621 * @param ptr pointer to the data 622 * @param size number of blocks of data 623 * @param nmemb blocksize 624 * @param ctx our `struct HttpRequest *` 625 * @return number of bytes handled 626 */ 627 static size_t 628 curl_download_cb (void *ptr, 629 size_t size, 630 size_t nmemb, 631 void *ctx) 632 { 633 struct HttpRequest *hr = ctx; 634 size_t total = size * nmemb; 635 636 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 637 "Curl download proceeding\n"); 638 639 if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) 640 { 641 /* Web server started with response before we finished 642 the upload. In this case, current libcurl decides 643 to NOT complete the upload, so we should jump in the 644 state machine to process the download, dropping the 645 rest of the upload. This should only really happen 646 with uploads without "Expect: 100 Continue" and 647 Web servers responding with an error (i.e. upload 648 not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 649 GNUNET_log 650 (GNUNET_ERROR_TYPE_INFO, 651 "Stopping %u byte upload: we are already downloading...\n", 652 (unsigned int) hr->io_len); 653 hr->io_len = 0; 654 } 655 656 if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state) 657 { 658 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 659 "Download callback goes to sleep\n"); 660 hr->curl_paused = GNUNET_YES; 661 return CURL_WRITEFUNC_PAUSE; 662 } 663 GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == 664 hr->state); 665 if (hr->io_size - hr->io_len < total) 666 { 667 GNUNET_assert (total + hr->io_size >= total); 668 GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); 669 GNUNET_array_grow (hr->io_buf, 670 hr->io_size, 671 GNUNET_MAX (total + hr->io_len, 672 hr->io_size * 2 + 1024)); 673 } 674 GNUNET_memcpy (&hr->io_buf[hr->io_len], 675 ptr, 676 total); 677 hr->io_len += total; 678 return total; 679 } 680 681 682 /** 683 * Ask cURL for the select() sets and schedule cURL operations. 684 */ 685 static void 686 curl_download_prepare (void); 687 688 689 /** 690 * cURL callback for uploaded (PUT/POST) data. 691 * Copies from our `io_buf` to make it available to cURL. 692 * 693 * @param buf where to write the data 694 * @param size number of bytes per member 695 * @param nmemb number of members available in @a buf 696 * @param cls our `struct HttpRequest` that generated the data 697 * @return number of bytes copied to @a buf 698 */ 699 static size_t 700 curl_upload_cb (void *buf, 701 size_t size, 702 size_t nmemb, 703 void *cls) 704 { 705 struct HttpRequest *hr = cls; 706 size_t len = size * nmemb; 707 size_t to_copy; 708 709 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 710 "Upload cb is working...\n"); 711 712 if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) || 713 (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) ) 714 { 715 GNUNET_log 716 (GNUNET_ERROR_TYPE_INFO, 717 "Upload cb aborts: we are already downloading...\n"); 718 return CURL_READFUNC_ABORT; 719 } 720 721 if ( (0 == hr->io_len) && 722 (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) ) 723 { 724 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 725 "Pausing CURL UPLOAD, need more data\n"); 726 return CURL_READFUNC_PAUSE; 727 } 728 729 /** 730 * We got rescheduled because the download callback was asleep. 731 * FIXME: can this block be eliminated and the unpausing being 732 * moved in the last block where we return zero as well? 733 */ 734 if ( (0 == hr->io_len) && 735 (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ) 736 { 737 if (GNUNET_YES == hr->curl_paused) 738 { 739 hr->curl_paused = GNUNET_NO; 740 curl_easy_pause (hr->curl, 741 CURLPAUSE_CONT); 742 } 743 curl_download_prepare (); 744 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 745 "Completed CURL UPLOAD\n"); 746 return 0; /* upload finished, can now download */ 747 } 748 to_copy = GNUNET_MIN (hr->io_len, 749 len); 750 GNUNET_memcpy (buf, 751 hr->io_buf, 752 to_copy); 753 /* shift remaining data back to the beginning of the buffer. */ 754 memmove (hr->io_buf, 755 &hr->io_buf[to_copy], 756 hr->io_len - to_copy); 757 hr->io_len -= to_copy; 758 if (0 == hr->io_len) 759 { 760 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 761 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 762 "Completed CURL UPLOAD\n"); 763 } 764 return to_copy; 765 } 766 767 768 /* ************** helper functions ************* */ 769 770 /** 771 * Extract the hostname from a complete URL. 772 * 773 * @param url full fledged URL 774 * @return pointer to the 0-terminated hostname, to be freed 775 * by the caller. 776 */ 777 static char * 778 build_host_header (const char *url) 779 { 780 #define MARKER "://" 781 782 char *header; 783 char *end; 784 char *hostname; 785 char *dup = GNUNET_strdup (url); 786 787 hostname = strstr (dup, 788 MARKER); 789 hostname += 3; 790 end = strchrnul (hostname, '/'); 791 *end = '\0'; 792 GNUNET_asprintf (&header, 793 "Host: %s", 794 hostname); 795 GNUNET_free (dup); 796 return header; 797 } 798 799 800 /* ************** main loop of cURL interaction ************* */ 801 802 803 /** 804 * Task that is run when we are ready to receive more data 805 * from curl 806 * 807 * @param cls closure 808 */ 809 static void 810 curl_task_download (void *cls); 811 812 813 /** 814 * Ask cURL for the select() sets and schedule cURL operations. 815 */ 816 static void 817 curl_download_prepare () 818 { 819 CURLMcode mret; 820 fd_set rs; 821 fd_set ws; 822 fd_set es; 823 int max; 824 struct GNUNET_NETWORK_FDSet *grs; 825 struct GNUNET_NETWORK_FDSet *gws; 826 long to; 827 struct GNUNET_TIME_Relative rtime; 828 829 if (NULL != curl_download_task) 830 { 831 GNUNET_SCHEDULER_cancel (curl_download_task); 832 curl_download_task = NULL; 833 } 834 max = -1; 835 FD_ZERO (&rs); 836 FD_ZERO (&ws); 837 FD_ZERO (&es); 838 if (CURLM_OK != (mret = curl_multi_fdset (curl_multi, 839 &rs, 840 &ws, 841 &es, 842 &max))) 843 { 844 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 845 "%s failed at %s:%d: `%s'\n", 846 "curl_multi_fdset", 847 __FILE__, 848 __LINE__, 849 curl_multi_strerror (mret)); 850 return; 851 } 852 to = -1; 853 GNUNET_break (CURLM_OK == 854 curl_multi_timeout (curl_multi, 855 &to)); 856 if (-1 == to) 857 rtime = GNUNET_TIME_UNIT_FOREVER_REL; 858 else 859 rtime = GNUNET_TIME_relative_multiply 860 (GNUNET_TIME_UNIT_MILLISECONDS, to); 861 if (-1 != max) 862 { 863 grs = GNUNET_NETWORK_fdset_create (); 864 gws = GNUNET_NETWORK_fdset_create (); 865 GNUNET_NETWORK_fdset_copy_native (grs, 866 &rs, 867 max + 1); 868 GNUNET_NETWORK_fdset_copy_native (gws, 869 &ws, 870 max + 1); 871 curl_download_task 872 = GNUNET_SCHEDULER_add_select ( 873 GNUNET_SCHEDULER_PRIORITY_DEFAULT, 874 rtime, 875 grs, gws, 876 &curl_task_download, 877 curl_multi); 878 GNUNET_NETWORK_fdset_destroy (gws); 879 GNUNET_NETWORK_fdset_destroy (grs); 880 } 881 else 882 { 883 curl_download_task = GNUNET_SCHEDULER_add_delayed (rtime, 884 &curl_task_download, 885 curl_multi); 886 } 887 } 888 889 890 /** 891 * "Filter" function that translates MHD request headers to 892 * cURL's. 893 * 894 * @param cls our `struct HttpRequest` 895 * @param kind value kind 896 * @param key field key 897 * @param value field value 898 * @return #MHD_YES to continue to iterate 899 */ 900 static enum MHD_Result 901 con_val_iter (void *cls, 902 enum MHD_ValueKind kind, 903 const char *key, 904 const char *value) 905 { 906 struct HttpRequest *hr = cls; 907 char *hdr; 908 char *new_value = NULL; 909 910 (void) kind; 911 if (0 == strcasecmp (MHD_HTTP_HEADER_HOST, 912 key)) 913 { 914 /* We don't take the host header as given in the request. 915 * We'll instead put the proxied service's hostname in it*/ 916 return MHD_YES; 917 } 918 if (0 == strcasecmp (MHD_HTTP_HEADER_CONTENT_LENGTH, 919 key)) 920 { 921 /* libcurl sets Content-Length itself from CURLOPT_POSTFIELDSIZE 922 / CURLOPT_INFILESIZE. */ 923 return MHD_YES; 924 } 925 if (0 == strcasecmp (MHD_HTTP_HEADER_EXPECT, 926 key)) 927 { 928 /* libcurl manages Expect: 100-continue on its own. */ 929 return MHD_YES; 930 } 931 if (is_hop_by_hop_header (key)) 932 return MHD_YES; 933 /* RFC 9110 §7.6.1: suppress any header named by the client's 934 Connection list (additional hop-by-hop headers). */ 935 if (connection_lists_header (hr->client_connection, 936 key)) 937 return MHD_YES; 938 if ( (0 == strncasecmp ("X-Forwarded-", 939 key, 940 strlen ("X-Forwarded-"))) || 941 (0 == strcasecmp (MHD_HTTP_HEADER_FORWARDED, 942 key)) ) 943 { 944 /* We will replace these with our own below. */ 945 return MHD_YES; 946 } 947 if (0 == strcasecmp (MHD_HTTP_HEADER_VIA, 948 key)) 949 { 950 /* The client's Via was captured in `collect_proxy_state` and 951 will be emitted below with our own entry appended (RFC 9110 952 §7.6.3). Drop the raw header here so we don't forward it 953 twice. */ 954 return MHD_YES; 955 } 956 GNUNET_asprintf (&hdr, 957 "%s: %s", 958 key, 959 value); 960 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 961 "Adding header `%s' to HTTP request\n", 962 hdr); 963 hr->headers = curl_slist_append (hr->headers, 964 hdr); 965 GNUNET_free (hdr); 966 GNUNET_free (new_value); 967 return MHD_YES; 968 } 969 970 971 /** 972 * Task that is run when we are ready to receive 973 * more data from curl. 974 * 975 * @param cls closure, usually NULL. 976 */ 977 static void 978 curl_task_download (void *cls) 979 { 980 int running; 981 int msgnum; 982 struct CURLMsg *msg; 983 CURLMcode mret; 984 struct HttpRequest *hr; 985 986 (void) cls; 987 curl_download_task = NULL; 988 do 989 { 990 running = 0; 991 mret = curl_multi_perform (curl_multi, 992 &running); 993 while (NULL != (msg = curl_multi_info_read (curl_multi, 994 &msgnum))) 995 { 996 GNUNET_break 997 (CURLE_OK == curl_easy_getinfo 998 (msg->easy_handle, 999 CURLINFO_PRIVATE, 1000 (char **) &hr)); 1001 1002 if (NULL == hr) 1003 { 1004 GNUNET_break (0); 1005 continue; 1006 } 1007 switch (msg->msg) 1008 { 1009 case CURLMSG_NONE: 1010 /* documentation says this is not used */ 1011 GNUNET_break (0); 1012 break; 1013 case CURLMSG_DONE: 1014 switch (msg->data.result) 1015 { 1016 case CURLE_OK: 1017 case CURLE_GOT_NOTHING: 1018 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1019 "CURL download completed.\n"); 1020 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; 1021 if (0 == hr->response_code) 1022 GNUNET_assert (GNUNET_OK == 1023 create_mhd_response_from_hr (hr)); 1024 break; 1025 default: 1026 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1027 "Download curl failed: %s\n", 1028 curl_easy_strerror (msg->data.result)); 1029 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; 1030 /* Surface upstream failures as 502 Bad Gateway. Drop 1031 any partial body we might have accumulated and let 1032 MHD return our pre-built failure page instead of 1033 half a truncated upstream response. */ 1034 hr->response_code = MHD_HTTP_BAD_GATEWAY; 1035 hr->io_len = 0; 1036 hr->response = curl_failure_response; 1037 if (GNUNET_YES == hr->suspended) 1038 { 1039 MHD_resume_connection (hr->con); 1040 hr->suspended = GNUNET_NO; 1041 } 1042 break; 1043 } /* end switch (msg->data.result) */ 1044 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1045 "Curl request for `%s' finished (got the response)\n", 1046 hr->url); 1047 TALER_MHD_daemon_trigger (); 1048 break; 1049 case CURLMSG_LAST: 1050 /* documentation says this is not used */ 1051 GNUNET_break (0); 1052 break; 1053 default: 1054 /* unexpected status code */ 1055 GNUNET_break (0); 1056 break; 1057 } /* end switch msg->msg */ 1058 } /* end while (curl_multi_info_read()) */ 1059 } while (mret == CURLM_CALL_MULTI_PERFORM); 1060 if (CURLM_OK != mret) 1061 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1062 "%s failed at %s:%d: `%s'\n", 1063 "curl_multi_perform", 1064 __FILE__, 1065 __LINE__, 1066 curl_multi_strerror (mret)); 1067 if (0 == running) 1068 { 1069 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1070 "Suspending cURL multi loop," 1071 " no more events pending\n"); 1072 return; /* nothing more in progress */ 1073 } 1074 curl_download_prepare (); 1075 } 1076 1077 1078 struct HttpRequest * 1079 PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection, 1080 const char *url) 1081 { 1082 struct HttpRequest *hr; 1083 1084 hr = GNUNET_new (struct HttpRequest); 1085 hr->con = connection; 1086 hr->url = GNUNET_strdup (url); 1087 GNUNET_CONTAINER_DLL_insert (hr_head, 1088 hr_tail, 1089 hr); 1090 return hr; 1091 } 1092 1093 1094 void 1095 PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr) 1096 { 1097 struct HttpResponseHeader *header; 1098 1099 if (NULL != hr->curl) 1100 { 1101 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1102 "Resetting cURL handle\n"); 1103 curl_multi_remove_handle (curl_multi, 1104 hr->curl); 1105 curl_easy_cleanup (hr->curl); 1106 hr->curl = NULL; 1107 hr->io_len = 0; 1108 } 1109 if (NULL != hr->headers) 1110 { 1111 curl_slist_free_all (hr->headers); 1112 hr->headers = NULL; 1113 } 1114 if ( (NULL != hr->response) && 1115 (curl_failure_response != hr->response) ) 1116 /* Destroy non-error responses... (?) */ 1117 MHD_destroy_response (hr->response); 1118 1119 for (header = hr->header_head; 1120 header != NULL; 1121 header = hr->header_head) 1122 { 1123 GNUNET_CONTAINER_DLL_remove (hr->header_head, 1124 hr->header_tail, 1125 header); 1126 GNUNET_free (header->type); 1127 GNUNET_free (header->value); 1128 GNUNET_free (header); 1129 } 1130 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1131 "Proxying of '%s' completely done\n", 1132 hr->url); 1133 1134 GNUNET_free (hr->url); 1135 GNUNET_free (hr->io_buf); 1136 GNUNET_free (hr->client_via); 1137 GNUNET_free (hr->client_connection); 1138 GNUNET_CONTAINER_DLL_remove (hr_head, 1139 hr_tail, 1140 hr); 1141 GNUNET_free (hr); 1142 } 1143 1144 1145 /** 1146 * Main MHD callback for reverse proxy. 1147 * 1148 * @param hr the HTTP request context 1149 * @param con MHD connection handle 1150 * @param url the url in the request 1151 * @param meth the HTTP method used ("GET", "PUT", etc.) 1152 * @param ver the HTTP version string (i.e. "HTTP/1.1") 1153 * @param upload_data the data being uploaded (excluding HEADERS, 1154 * for a POST that fits into memory and that is encoded 1155 * with a supported encoding, the POST data will NOT be 1156 * given in upload_data and is instead available as 1157 * part of MHD_get_connection_values; very large POST 1158 * data *will* be made available incrementally in 1159 * upload_data) 1160 * @param upload_data_size set initially to the size of the 1161 * @a upload_data provided; the method must update this 1162 * value to the number of bytes NOT processed; 1163 * @return #MHD_YES if the connection was handled successfully, 1164 * #MHD_NO if the socket must be closed due to a serious 1165 * error while handling the request 1166 */ 1167 enum MHD_Result 1168 PAIVANA_HTTPD_reverse (struct HttpRequest *hr, 1169 struct MHD_Connection *con, 1170 const char *url, 1171 const char *meth, 1172 const char *ver, 1173 const char *upload_data, 1174 size_t *upload_data_size) 1175 { 1176 /* MHD's "first" call (immediately after headers) arrives with 1177 `upload_data_size == 0` and no body. Just acknowledge it so 1178 MHD will proceed to deliver the request body (if any) and then 1179 make the "final" call. Setting up curl here would start the 1180 upstream request with an empty io_buf and leave body chunks 1181 nowhere to land. */ 1182 if (! hr->accepted) 1183 { 1184 /* If the client declared a Content-Length we already know is too 1185 large, reject now: MHD is in HEADERS_PROCESSED so we can queue 1186 a response, and rejecting here suppresses the implicit 100 1187 Continue (RFC 7231 §5.1.1) for clients that asked for one and 1188 avoids buffering the body just to throw it away. Chunked 1189 uploads (no Content-Length) and clients that ignore 100 1190 Continue and stream the body anyway still hit the 1191 drain-then-reject path further down. */ 1192 const char *cl_str 1193 = MHD_lookup_connection_value (con, 1194 MHD_HEADER_KIND, 1195 MHD_HTTP_HEADER_CONTENT_LENGTH); 1196 hr->accepted = true; 1197 if (NULL != cl_str) 1198 { 1199 char *endptr; 1200 unsigned long long cl; 1201 1202 errno = 0; 1203 cl = strtoull (cl_str, 1204 &endptr, 1205 10); 1206 if ( (0 == errno) && 1207 ('\0' == *endptr) && 1208 (cl > PH_request_buffer_max) ) 1209 { 1210 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1211 "Rejecting upload: Content-Length %llu exceeds %llu byte limit\n", 1212 cl, 1213 PH_request_buffer_max); 1214 hr->reject_upload = true; 1215 return MHD_queue_response (con, 1216 MHD_HTTP_CONTENT_TOO_LARGE, 1217 curl_failure_response); 1218 } 1219 } 1220 return MHD_YES; 1221 } 1222 /* On the "final" access-handler call after we drained an 1223 over-sized upload, queue the deferred 413 response now that 1224 MHD is back in a state where that is allowed. */ 1225 if (hr->reject_upload && 0 == *upload_data_size) 1226 { 1227 return MHD_queue_response (con, 1228 MHD_HTTP_CONTENT_TOO_LARGE, 1229 curl_failure_response); 1230 } 1231 /* FIXME: make state machine more explicit by 1232 switching on hr->state here! */ 1233 if (0 != *upload_data_size) 1234 { 1235 GNUNET_assert 1236 (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state); 1237 1238 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1239 "Processing %u bytes UPLOAD\n", 1240 (unsigned int) *upload_data_size); 1241 1242 /* Reject uploads that would exceed our buffering cap. The 1243 entire request body is currently buffered before forwarding, 1244 so this also bounds memory usage per request. 1245 MHD does not allow queuing a response while BODY_RECEIVING 1246 (MHD_queue_response returns MHD_NO outside of 1247 HEADERS_PROCESSED / FULL_REQ_RECEIVED), so we mark the 1248 request as over-limit, silently drain the remaining body, 1249 and queue the 413 on the "final" access-handler call. */ 1250 if (hr->reject_upload || 1251 hr->io_len + *upload_data_size > PH_request_buffer_max) 1252 { 1253 if (! hr->reject_upload) 1254 { 1255 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1256 "Upload exceeds %llu byte limit, rejecting\n", 1257 PH_request_buffer_max); 1258 hr->reject_upload = true; 1259 } 1260 *upload_data_size = 0; 1261 return MHD_YES; 1262 } 1263 1264 /* Grow the buffer if remaining space isn't enough. */ 1265 if (hr->io_size - hr->io_len < *upload_data_size) 1266 { 1267 /* How can this assertion be false? */ 1268 GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); 1269 /* This asserts that upload_data_size > 0, ? */ 1270 GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len); 1271 1272 GNUNET_array_grow (hr->io_buf, 1273 hr->io_size, 1274 GNUNET_MAX 1275 (hr->io_size * 2 + 1024, 1276 *upload_data_size + hr->io_len)); 1277 } 1278 1279 /* Finally copy upload data. */ 1280 GNUNET_memcpy (&hr->io_buf[hr->io_len], 1281 upload_data, 1282 *upload_data_size); 1283 1284 hr->io_len += *upload_data_size; 1285 *upload_data_size = 0; 1286 1287 return MHD_YES; 1288 } 1289 1290 /* Upload (*from the client*) finished or just a without-body 1291 * request. */ 1292 if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state) 1293 { 1294 hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE; 1295 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1296 "Finished processing UPLOAD\n"); 1297 } 1298 1299 /* generate curl request to the proxied service. */ 1300 if (NULL == hr->curl) 1301 { 1302 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1303 "Generating curl request\n"); 1304 hr->curl = curl_easy_init (); 1305 if (NULL == hr->curl) 1306 { 1307 PAIVANA_LOG_ERROR ("Could not init the curl handle\n"); 1308 return MHD_queue_response (con, 1309 MHD_HTTP_INTERNAL_SERVER_ERROR, 1310 curl_failure_response); 1311 } 1312 1313 /* No need to check whether we're POSTing or PUTting. 1314 * If not needed, one of the following values will be 1315 * ignored.*/ 1316 curl_easy_setopt (hr->curl, 1317 CURLOPT_POSTFIELDSIZE, 1318 hr->io_len); 1319 curl_easy_setopt (hr->curl, 1320 CURLOPT_INFILESIZE, 1321 hr->io_len); 1322 curl_easy_setopt (hr->curl, 1323 CURLOPT_HEADERFUNCTION, 1324 &curl_check_hdr); 1325 curl_easy_setopt (hr->curl, 1326 CURLOPT_HEADERDATA, 1327 hr); 1328 curl_easy_setopt (hr->curl, 1329 CURLOPT_FOLLOWLOCATION, 1330 0); 1331 curl_easy_setopt (hr->curl, 1332 CURLOPT_CONNECTTIMEOUT, 1333 60L); 1334 curl_easy_setopt (hr->curl, 1335 CURLOPT_TIMEOUT, 1336 60L); 1337 curl_easy_setopt (hr->curl, 1338 CURLOPT_NOSIGNAL, 1339 1L); 1340 curl_easy_setopt (hr->curl, 1341 CURLOPT_PRIVATE, 1342 hr); 1343 curl_easy_setopt (hr->curl, 1344 CURLOPT_VERBOSE, 1345 0); 1346 1347 curl_easy_setopt (hr->curl, 1348 CURLOPT_READFUNCTION, 1349 &curl_upload_cb); 1350 curl_easy_setopt (hr->curl, 1351 CURLOPT_READDATA, 1352 hr); 1353 1354 curl_easy_setopt (hr->curl, 1355 CURLOPT_WRITEFUNCTION, 1356 &curl_download_cb); 1357 curl_easy_setopt (hr->curl, 1358 CURLOPT_WRITEDATA, 1359 hr); 1360 { 1361 char *curlurl; 1362 char *host_hdr; 1363 1364 GNUNET_asprintf (&curlurl, 1365 "%s%s", 1366 PH_target_server_base_url, 1367 hr->url); 1368 curl_easy_setopt (hr->curl, 1369 CURLOPT_URL, 1370 curlurl); 1371 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1372 "Forwarding request to: %s\n", 1373 curlurl); 1374 GNUNET_free (curlurl); 1375 1376 host_hdr = build_host_header (PH_target_server_base_url); 1377 PAIVANA_LOG_DEBUG ("Faking the host header, %s\n", 1378 host_hdr); 1379 hr->headers = curl_slist_append (hr->headers, 1380 host_hdr); 1381 GNUNET_free (host_hdr); 1382 } 1383 1384 if (0 == strcasecmp (meth, 1385 MHD_HTTP_METHOD_PUT)) 1386 { 1387 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1388 "Crafting a CURL PUT request\n"); 1389 1390 curl_easy_setopt (hr->curl, 1391 CURLOPT_UPLOAD, 1392 1L); 1393 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1394 } 1395 else if (0 == strcasecmp (meth, 1396 MHD_HTTP_METHOD_POST)) 1397 { 1398 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1399 "Crafting a CURL POST request\n"); 1400 curl_easy_setopt (hr->curl, 1401 CURLOPT_POST, 1402 1L); 1403 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1404 } 1405 else if (0 == strcasecmp (meth, 1406 MHD_HTTP_METHOD_PATCH)) 1407 { 1408 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1409 "Crafting a CURL PATCH request\n"); 1410 /* CURLOPT_POST=1 turns on body upload via the read callback; 1411 CURLOPT_CUSTOMREQUEST then overrides the verb on the wire. */ 1412 curl_easy_setopt (hr->curl, 1413 CURLOPT_POST, 1414 1L); 1415 curl_easy_setopt (hr->curl, 1416 CURLOPT_CUSTOMREQUEST, 1417 "PATCH"); 1418 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1419 } 1420 else if (0 == strcasecmp (meth, 1421 MHD_HTTP_METHOD_DELETE)) 1422 { 1423 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1424 "Crafting a CURL DELETE request\n"); 1425 curl_easy_setopt (hr->curl, 1426 CURLOPT_CUSTOMREQUEST, 1427 "DELETE"); 1428 if (0 != hr->io_len) 1429 { 1430 /* DELETE with a request body is unusual but legal. */ 1431 curl_easy_setopt (hr->curl, 1432 CURLOPT_POST, 1433 1L); 1434 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1435 } 1436 else 1437 { 1438 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1439 } 1440 } 1441 else if (0 == strcasecmp (meth, 1442 MHD_HTTP_METHOD_HEAD)) 1443 { 1444 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1445 curl_easy_setopt (hr->curl, 1446 CURLOPT_NOBODY, 1447 1L); 1448 } 1449 else if (0 == strcasecmp (meth, 1450 MHD_HTTP_METHOD_OPTIONS)) 1451 { 1452 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1453 curl_easy_setopt (hr->curl, 1454 CURLOPT_CUSTOMREQUEST, 1455 "OPTIONS"); 1456 } 1457 else if (0 == strcasecmp (meth, 1458 MHD_HTTP_METHOD_GET)) 1459 { 1460 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1461 curl_easy_setopt (hr->curl, 1462 CURLOPT_HTTPGET, 1463 1L); 1464 } 1465 else 1466 { 1467 /* TRACE leaks headers back to the client; CONNECT is for 1468 TLS tunnelling and doesn't fit the reverse-proxy model. 1469 Reject anything else with a proper 405. */ 1470 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1471 "Unsupported HTTP method `%s'\n", 1472 meth); 1473 curl_easy_cleanup (hr->curl); 1474 hr->curl = NULL; 1475 return MHD_queue_response (con, 1476 MHD_HTTP_METHOD_NOT_ALLOWED, 1477 curl_failure_response); 1478 } 1479 1480 if (CURLM_OK != 1481 curl_multi_add_handle (curl_multi, 1482 hr->curl)) 1483 { 1484 GNUNET_break (0); 1485 curl_easy_cleanup (hr->curl); 1486 hr->curl = NULL; 1487 return MHD_queue_response (con, 1488 MHD_HTTP_BAD_GATEWAY, 1489 curl_failure_response); 1490 } 1491 1492 /* First pass: collect Via / Connection so `con_val_iter` can 1493 honor them (append to Via; drop headers named by Connection). */ 1494 MHD_get_connection_values (con, 1495 MHD_HEADER_KIND, 1496 &collect_proxy_state, 1497 hr); 1498 MHD_get_connection_values (con, 1499 MHD_HEADER_KIND, 1500 &con_val_iter, 1501 hr); 1502 1503 /* Add standard reverse-proxy forwarding headers. X-Forwarded-* 1504 and Forwarded are replaced with our view of the connection 1505 (filtered out in con_val_iter); Via is *appended to* the 1506 client's existing chain per RFC 9110 §7.6.3. */ 1507 { 1508 const union MHD_ConnectionInfo *ci; 1509 char *hdr; 1510 const char *proto; 1511 const char *fhost; 1512 1513 ci = MHD_get_connection_info (con, 1514 MHD_CONNECTION_INFO_CLIENT_ADDRESS); 1515 if ( (NULL != ci) && (NULL != ci->client_addr) ) 1516 { 1517 char ipbuf[INET6_ADDRSTRLEN]; 1518 const char *ip = NULL; 1519 1520 switch (ci->client_addr->sa_family) 1521 { 1522 case AF_INET: 1523 ip = inet_ntop ( 1524 AF_INET, 1525 &((const struct sockaddr_in *) ci->client_addr)->sin_addr, 1526 ipbuf, sizeof (ipbuf)); 1527 break; 1528 case AF_INET6: 1529 ip = inet_ntop ( 1530 AF_INET6, 1531 &((const struct sockaddr_in6 *) ci->client_addr)->sin6_addr, 1532 ipbuf, sizeof (ipbuf)); 1533 break; 1534 default: 1535 break; 1536 } 1537 if (NULL != ip) 1538 { 1539 GNUNET_asprintf (&hdr, 1540 "X-Forwarded-For: %s", 1541 ip); 1542 hr->headers = curl_slist_append (hr->headers, 1543 hdr); 1544 GNUNET_free (hdr); 1545 } 1546 } 1547 proto = (GNUNET_YES == TALER_mhd_is_https (con)) 1548 ? "https" : "http"; 1549 GNUNET_asprintf (&hdr, 1550 "X-Forwarded-Proto: %s", 1551 proto); 1552 hr->headers = curl_slist_append (hr->headers, 1553 hdr); 1554 GNUNET_free (hdr); 1555 fhost = MHD_lookup_connection_value (con, 1556 MHD_HEADER_KIND, 1557 MHD_HTTP_HEADER_HOST); 1558 if (NULL != fhost) 1559 { 1560 GNUNET_asprintf (&hdr, 1561 "X-Forwarded-Host: %s", 1562 fhost); 1563 hr->headers = curl_slist_append (hr->headers, 1564 hdr); 1565 GNUNET_free (hdr); 1566 } 1567 /* Via: pseudonym + protocol-version (RFC 9110 §7.6.3); 1568 MHD hands us e.g. "HTTP/1.1" but Via wants just "1.1". 1569 If the client already carried a Via chain, prepend it so 1570 the upstream sees the full trace of proxies. */ 1571 { 1572 const char *via_ver = "1.1"; 1573 1574 if ( (NULL != ver) && 1575 (0 == strncasecmp (ver, "HTTP/", 5)) ) 1576 via_ver = ver + 5; 1577 if (NULL != hr->client_via) 1578 GNUNET_asprintf (&hdr, 1579 "%s: %s, %s paivana", 1580 MHD_HTTP_HEADER_VIA, 1581 hr->client_via, 1582 via_ver); 1583 else 1584 GNUNET_asprintf (&hdr, 1585 "%s: %s paivana", 1586 MHD_HTTP_HEADER_VIA, 1587 via_ver); 1588 } 1589 hr->headers = curl_slist_append (hr->headers, 1590 hdr); 1591 GNUNET_free (hdr); 1592 } 1593 1594 curl_easy_setopt (hr->curl, 1595 CURLOPT_HTTPHEADER, 1596 hr->headers); 1597 curl_download_prepare (); 1598 1599 return MHD_YES; 1600 } 1601 1602 if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state) 1603 { 1604 GNUNET_assert (GNUNET_NO == hr->suspended); 1605 MHD_suspend_connection (con); 1606 hr->suspended = GNUNET_YES; 1607 return MHD_YES; /* wait for curl */ 1608 } 1609 1610 GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state); 1611 1612 /* Response may already be set to curl_failure_response by the 1613 curl task on upstream failure; in that case, don't build a 1614 buffer response and don't attach per-request headers to the 1615 shared failure response. */ 1616 if (NULL == hr->response) 1617 { 1618 hr->response 1619 = MHD_create_response_from_buffer_static (hr->io_len, 1620 hr->io_buf); 1621 if (NULL == hr->response) 1622 { 1623 GNUNET_break (0); 1624 hr->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 1625 hr->response = curl_failure_response; 1626 } 1627 else 1628 { 1629 for (struct HttpResponseHeader *header = hr->header_head; 1630 NULL != header; 1631 header = header->next) 1632 { 1633 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1634 "Adding MHD response header %s->%s\n", 1635 header->type, 1636 header->value); 1637 GNUNET_break (MHD_YES == 1638 MHD_add_response_header (hr->response, 1639 header->type, 1640 header->value)); 1641 } 1642 } 1643 } 1644 TALER_MHD_daemon_trigger (); 1645 1646 return MHD_queue_response (con, 1647 hr->response_code, 1648 hr->response); 1649 }