paivana-httpd_helper.c (45524B)
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_helper.c 24 * @brief helper functions 25 */ 26 #include "platform.h" 27 #include "paivana-httpd.h" 28 #include "paivana-httpd_helper.h" 29 #include <taler/taler_mhd_lib.h> 30 31 /** 32 * Longest node identifier we keep. RFC 7239 §6 has 33 * `node = nodename [ ":" node-port ]` with 34 * `nodename = ... / "[" IPv6address "]"`, so the longest one that can 35 * name an address is `[` + 45 + `]` + `:` + 5 = 53 bytes -- more than 36 * INET6_ADDRSTRLEN, which is why the port cannot be stripped after a 37 * length check against that. 38 */ 39 #define PH_NODE_MAX 64 40 41 /** 42 * Longest authority we accept in a `host` parameter or an 43 * `X-Forwarded-Host`. A DNS name is at most 253 bytes and an IPv6 44 * literal with brackets and port 53; anything longer is not an 45 * authority. 46 */ 47 #define PH_HOST_MAX 256 48 49 /** 50 * Longest `Forwarded` parameter value we parse. The values we care 51 * about are a node, a scheme or an authority; the rest (`by`, 52 * extensions) only has to fit for the element to be understood at all. 53 */ 54 #define PH_VALUE_MAX 512 55 56 57 /** 58 * One hop of a forwarding chain, as reported by the hop to its right. 59 */ 60 struct Element 61 { 62 63 /** 64 * Node identifier as it appeared in the header, for logging; "" if 65 * it did not fit. 66 */ 67 char node[PH_NODE_MAX]; 68 69 /** 70 * Authority from the element's `host` parameter, or "" if it had 71 * none we could use. 72 */ 73 char host[PH_HOST_MAX]; 74 75 /** 76 * Binary form of @e node, in the representation the socket branch 77 * produces for the same host. 78 */ 79 unsigned char addr[sizeof (struct in6_addr)]; 80 81 /** 82 * Number of bytes in @e addr; 0 if @e node names no address. 83 */ 84 size_t addr_len; 85 86 /** 87 * Did the element carry a `proto` we understood? 88 */ 89 bool have_proto; 90 91 /** 92 * Was that `proto` https? 93 */ 94 bool https; 95 }; 96 97 98 /** 99 * Store IPv6 address @a a6 in @a out / @a out_len. 100 * 101 * An IPv4-mapped address (::ffff:a.b.c.d) is unwrapped to the four 102 * bytes a plain IPv4 peer would have yielded: it names the same host, 103 * and the two spellings must not produce two identities. 104 * 105 * @param a6 address to store 106 * @param[out] out buffer of at least `sizeof (struct in6_addr)` bytes 107 * @param[out] out_len set to the number of bytes written to @a out 108 */ 109 static void 110 pack_v6 (const struct in6_addr *a6, 111 unsigned char *out, 112 size_t *out_len) 113 { 114 if (IN6_IS_ADDR_V4MAPPED (a6)) 115 { 116 memcpy (out, 117 &a6->s6_addr[12], 118 sizeof (struct in_addr)); 119 *out_len = sizeof (struct in_addr); 120 return; 121 } 122 memcpy (out, 123 a6, 124 sizeof (*a6)); 125 *out_len = sizeof (*a6); 126 } 127 128 129 /** 130 * Is @a c a `tchar`, i.e. legal in a token (RFC 9110 §5.6.2)? 131 * 132 * @param c character to test 133 * @return true if @a c may appear in a token 134 */ 135 static bool 136 is_tchar (char c) 137 { 138 if ('\0' == c) 139 return false; 140 return ( (c >= 'a') && (c <= 'z') ) || 141 ( (c >= 'A') && (c <= 'Z') ) || 142 ( (c >= '0') && (c <= '9') ) || 143 (NULL != strchr ("!#$%&'*+-.^_`|~", 144 c)); 145 } 146 147 148 /** 149 * May @a c appear in an unquoted RFC 7239 §4 `value`? 150 * 151 * The grammar says `token`, but proxies in the field -- the Apache 152 * snippet we ship among them -- emit an unquoted authority, and `:` 153 * is not a `tchar`. Refusing those outright would throw away the 154 * whole chain a correctly configured front server sent, so anything 155 * that is not a delimiter, whitespace or a control character is read 156 * as part of the value. That keeps the element boundaries exactly 157 * where the grammar puts them, which is the part that matters; whether 158 * the value is one we can *use* is then decided per parameter. 159 * 160 * @param c character to test 161 * @return true if @a c continues an unquoted value 162 */ 163 static bool 164 is_value_char (char c) 165 { 166 unsigned char u = (unsigned char) c; 167 168 return (u > 0x20) && 169 (0x7F != u) && 170 (';' != u) && 171 (',' != u) && 172 ('"' != u); 173 } 174 175 176 /** 177 * Is @a c a `qdtext`, i.e. legal unescaped inside a quoted-string 178 * (RFC 9110 §5.6.4)? 179 * 180 * @param c character to test 181 * @return true if @a c may appear unescaped 182 */ 183 static bool 184 is_qdtext (char c) 185 { 186 unsigned char u = (unsigned char) c; 187 188 return ('\t' == u) || 189 (' ' == u) || 190 (0x21 == u) || 191 ( (u >= 0x23) && (u <= 0x5B) ) || 192 ( (u >= 0x5D) && (u <= 0x7E) ) || 193 (u >= 0x80); 194 } 195 196 197 /** 198 * May @a c follow a backslash in a quoted-pair (RFC 9110 §5.6.4)? 199 * 200 * @param c character to test 201 * @return true if @a c may be escaped 202 */ 203 static bool 204 is_escapable (char c) 205 { 206 unsigned char u = (unsigned char) c; 207 208 return ('\t' == u) || 209 ( (u >= 0x20) && (u <= 0x7E) ) || 210 (u >= 0x80); 211 } 212 213 214 /** 215 * Skip optional whitespace (RFC 9110 §5.6.3). 216 * 217 * @param p position to start at 218 * @return first position that is neither SP nor HTAB 219 */ 220 static const char * 221 skip_ows (const char *p) 222 { 223 while ( (' ' == *p) || 224 ('\t' == *p) ) 225 p++; 226 return p; 227 } 228 229 230 /** 231 * Read one RFC 7239 §4 `value` -- a token or a quoted-string -- from 232 * @a pp into @a out, advancing @a pp past it. 233 * 234 * The quoted-string is unescaped here, which is the only place that 235 * may happen: a caller handed the raw bytes could not tell a `,` that 236 * was data from one that was a delimiter. 237 * 238 * @param[in,out] pp position to read from; advanced past the value 239 * @param[out] out buffer for the unescaped value 240 * @param out_size number of bytes in @a out 241 * @return true if a well-formed value was read 242 */ 243 static bool 244 parse_value (const char **pp, 245 char *out, 246 size_t out_size) 247 { 248 const char *p = *pp; 249 size_t len = 0; 250 251 if ('"' == *p) 252 { 253 p++; 254 while ('"' != *p) 255 { 256 char c = *p; 257 258 if ('\0' == c) 259 { 260 /* An unterminated quoted-string is not a value at all, and 261 reading on would run off the end of the header. */ 262 GNUNET_break_op (0); 263 return false; 264 } 265 if ('\\' == c) 266 { 267 c = p[1]; 268 if (! is_escapable (c)) 269 { 270 GNUNET_break_op (0); 271 return false; 272 } 273 p++; 274 } 275 else if (! is_qdtext (c)) 276 { 277 GNUNET_break_op (0); 278 return false; 279 } 280 if (len + 1 >= out_size) 281 { 282 GNUNET_break_op (0); 283 return false; 284 } 285 out[len++] = c; 286 p++; 287 } 288 p++; /* closing DQUOTE */ 289 } 290 else 291 { 292 while (is_value_char (*p)) 293 { 294 if (len + 1 >= out_size) 295 { 296 GNUNET_break_op (0); 297 return false; 298 } 299 out[len++] = *p++; 300 } 301 if (0 == len) 302 { 303 /* `for=` with nothing after it is not a `value`. */ 304 GNUNET_break_op (0); 305 return false; 306 } 307 } 308 out[len] = '\0'; 309 *pp = p; 310 return true; 311 } 312 313 314 /** 315 * Parse the RFC 7239 §6 node identifier @a node into binary form. 316 * 317 * What we return has to be the *same bytes* the socket branch would 318 * have produced for this client, or the cookie MAC -- which covers the 319 * client address -- silently stops matching as soon as a request 320 * arrives without the header, or through a proxy that spells the 321 * address differently ("::1" and "0:0:0:0:0:0:0:1" are one host). 322 * Hence the address is parsed into its binary form rather than carried 323 * around as text. 324 * 325 * Brackets and any port are removed first: a port is not part of the 326 * identity of a host and the cookie must not depend on it. A node 327 * that is not an address at all -- the `unknown` of §6.3, an 328 * obfuscated identifier, a hostname -- has no such form and is 329 * reported as naming none. 330 * 331 * @param node identifier to parse 332 * @param[out] addr buffer of at least `sizeof (struct in6_addr)` bytes 333 * @param[out] addr_len set to the number of bytes written, 0 if 334 * @a node names no address 335 */ 336 static void 337 node_to_addr (const char *node, 338 unsigned char *addr, 339 size_t *addr_len) 340 { 341 char tmp[PH_NODE_MAX]; 342 const char *a; 343 size_t len = strlen (node); 344 struct in_addr a4; 345 struct in6_addr a6; 346 347 *addr_len = 0; 348 if ( (0 == len) || 349 (len >= sizeof (tmp)) ) 350 return; 351 memcpy (tmp, 352 node, 353 len + 1); 354 if ('[' == tmp[0]) 355 { 356 char *close = strchr (tmp, 357 ']'); 358 359 if (NULL == close) 360 { 361 /* RFC 7239 §6 requires the closing bracket. */ 362 GNUNET_break_op (0); 363 return; 364 } 365 *close = '\0'; 366 if ( ('\0' != close[1]) && 367 (':' != close[1]) ) 368 { 369 GNUNET_break_op (0); 370 return; 371 } 372 a = &tmp[1]; 373 } 374 else 375 { 376 char *colon = strchr (tmp, 377 ':'); 378 379 /* Only a *single* colon can be a port separator; more than one 380 means this is a bare IPv6 address, which RFC 7239 requires to 381 be bracketed but which we accept anyway. */ 382 if ( (NULL != colon) && 383 (NULL == strchr (colon + 1, 384 ':')) ) 385 *colon = '\0'; 386 a = tmp; 387 } 388 if (1 == inet_pton (AF_INET, 389 a, 390 &a4)) 391 { 392 memcpy (addr, 393 &a4, 394 sizeof (a4)); 395 *addr_len = sizeof (a4); 396 return; 397 } 398 if (1 == inet_pton (AF_INET6, 399 a, 400 &a6)) 401 pack_v6 (&a6, 402 addr, 403 addr_len); 404 } 405 406 407 /** 408 * Is @a c legal in an RFC 3986 §3.2.2 `reg-name` as we accept them? 409 * 410 * Deliberately narrower than the grammar: percent-encoding and the 411 * sub-delims have no business in a host we are about to concatenate 412 * into the string the access cookie is keyed on and the templates' 413 * regular expressions are matched against. 414 * 415 * @param c character to test 416 * @return true if @a c may appear in a host name 417 */ 418 static bool 419 is_host_char (char c) 420 { 421 return ( (c >= 'a') && (c <= 'z') ) || 422 ( (c >= 'A') && (c <= 'Z') ) || 423 ( (c >= '0') && (c <= '9') ) || 424 ('-' == c) || 425 ('.' == c) || 426 ('_' == c); 427 } 428 429 430 /** 431 * Is @a p an RFC 3986 §3.2.3 port naming one that can be connected to? 432 * 433 * @param p text to check 434 * @return true if @a p is a usable port number 435 */ 436 static bool 437 valid_port_text (const char *p) 438 { 439 unsigned long v = 0; 440 size_t len = strlen (p); 441 442 if ( (0 == len) || 443 (len > 5) ) 444 return false; 445 for (size_t i = 0; i < len; i++) 446 { 447 if ( (p[i] < '0') || 448 (p[i] > '9') ) 449 return false; 450 v = v * 10 + (unsigned long) (p[i] - '0'); 451 } 452 return ( (v > 0) && 453 (v < 65536) ); 454 } 455 456 457 /** 458 * Is @a h an authority we are willing to build a base URL from, i.e. 459 * an RFC 3986 §3.2 `host [ ":" port ]`? 460 * 461 * @param h text to check 462 * @return true if @a h is such an authority 463 */ 464 static bool 465 valid_host (const char *h) 466 { 467 size_t len = strlen (h); 468 const char *port; 469 470 if ( (0 == len) || 471 (len >= PH_HOST_MAX) ) 472 return false; 473 if ('[' == h[0]) 474 { 475 /* RFC 3986 §3.2.2: an IPv6 literal is bracketed, and RFC 5952 §6 476 asks for exactly that spelling in a URI. */ 477 char tmp[INET6_ADDRSTRLEN]; 478 const char *close = strchr (h, 479 ']'); 480 struct in6_addr a6; 481 size_t hl; 482 483 if (NULL == close) 484 return false; 485 hl = (size_t) (close - h) - 1; 486 if ( (0 == hl) || 487 (hl >= sizeof (tmp)) ) 488 return false; 489 memcpy (tmp, 490 h + 1, 491 hl); 492 tmp[hl] = '\0'; 493 if (1 != inet_pton (AF_INET6, 494 tmp, 495 &a6)) 496 return false; 497 if ('\0' == close[1]) 498 return true; 499 if (':' != close[1]) 500 return false; 501 port = close + 2; 502 } 503 else 504 { 505 const char *colon = strchr (h, 506 ':'); 507 size_t hl = (NULL != colon) 508 ? (size_t) (colon - h) 509 : len; 510 511 if (0 == hl) 512 return false; 513 for (size_t i = 0; i < hl; i++) 514 if (! is_host_char (h[i])) 515 return false; 516 if (NULL == colon) 517 return true; 518 port = colon + 1; 519 } 520 return valid_port_text (port); 521 } 522 523 524 /** 525 * Does the authority @a h already carry a port? 526 * 527 * @param h authority that passed #valid_host() 528 * @return true if a port is present 529 */ 530 static bool 531 host_has_port (const char *h) 532 { 533 const char *p = ('[' == h[0]) 534 ? strchr (h, 535 ']') 536 : h; 537 538 if (NULL == p) 539 return false; 540 return (NULL != strchr (p, 541 ':')); 542 } 543 544 545 /** 546 * Parse one RFC 7239 §4 `forwarded-element` from @a pp into @a e, 547 * advancing @a pp to the `,` or NUL that ended it. 548 * 549 * @param[in,out] pp position to read from 550 * @param[out] e element to fill in 551 * @return true if the element was well-formed 552 */ 553 static bool 554 parse_forwarded_element (const char **pp, 555 struct Element *e) 556 { 557 const char *p = *pp; 558 bool have_for = false; 559 bool have_host = false; 560 bool have_proto = false; 561 562 memset (e, 563 0, 564 sizeof (*e)); 565 while (true) 566 { 567 char name[32]; 568 char value[PH_VALUE_MAX]; 569 size_t nlen = 0; 570 571 p = skip_ows (p); 572 if (';' == *p) 573 { 574 /* RFC 7239 §4 permits an empty forwarded-pair. */ 575 p++; 576 continue; 577 } 578 if ( (',' == *p) || 579 ('\0' == *p) ) 580 break; 581 while (is_tchar (*p)) 582 { 583 if (nlen + 1 >= sizeof (name)) 584 { 585 GNUNET_break_op (0); 586 return false; 587 } 588 name[nlen++] = *p++; 589 } 590 if (0 == nlen) 591 { 592 GNUNET_break_op (0); 593 return false; 594 } 595 name[nlen] = '\0'; 596 p = skip_ows (p); 597 if ('=' != *p) 598 { 599 GNUNET_break_op (0); 600 return false; 601 } 602 p++; 603 p = skip_ows (p); 604 if (! parse_value (&p, 605 value, 606 sizeof (value))) 607 return false; 608 /* RFC 7239 §4: "Each parameter MUST NOT occur more than once per 609 field-value." A second one would leave which of the two we 610 believe up to the direction we happen to scan in. */ 611 if (0 == strcasecmp (name, 612 "for")) 613 { 614 if (have_for) 615 { 616 GNUNET_break_op (0); 617 return false; 618 } 619 have_for = true; 620 if (strlen (value) < sizeof (e->node)) 621 memcpy (e->node, 622 value, 623 strlen (value) + 1); 624 node_to_addr (value, 625 e->addr, 626 &e->addr_len); 627 } 628 else if (0 == strcasecmp (name, 629 "host")) 630 { 631 if (have_host) 632 { 633 GNUNET_break_op (0); 634 return false; 635 } 636 have_host = true; 637 if (valid_host (value)) 638 memcpy (e->host, 639 value, 640 strlen (value) + 1); 641 else 642 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 643 "Ignoring unusable Forwarded host `%s'\n", 644 value); 645 } 646 else if (0 == strcasecmp (name, 647 "proto")) 648 { 649 if (have_proto) 650 { 651 GNUNET_break_op (0); 652 return false; 653 } 654 have_proto = true; 655 /* Anything but the two schemes we can serve would end up in a 656 Location header and in the string the cookie is keyed on. */ 657 if (0 == strcasecmp (value, 658 "https")) 659 { 660 e->have_proto = true; 661 e->https = true; 662 } 663 else if (0 == strcasecmp (value, 664 "http")) 665 { 666 e->have_proto = true; 667 } 668 else 669 { 670 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 671 "Ignoring unusable Forwarded proto `%s'\n", 672 value); 673 } 674 } 675 /* `by` and extension parameters are none of our business, but had 676 to be parsed to find the end of the element. */ 677 p = skip_ows (p); 678 if (';' != *p) 679 break; 680 p++; 681 } 682 if ( (',' != *p) && 683 ('\0' != *p) ) 684 { 685 GNUNET_break_op (0); 686 return false; 687 } 688 *pp = p; 689 return true; 690 } 691 692 693 /** 694 * Was this header present at all? 695 * 696 * @param lines NULL-terminated array of field line values, or NULL 697 * @return true if there is at least one field line 698 */ 699 static bool 700 have_lines (const char *const *lines) 701 { 702 return ( (NULL != lines) && 703 (NULL != lines[0]) ); 704 } 705 706 707 /** 708 * Parse every `Forwarded` field line of @a lines into @a el, in order. 709 * 710 * RFC 9110 §5.3 makes repeated field lines of a list-based field one 711 * list, and RFC 7239 §4 explicitly blesses a proxy adding a field line 712 * of its own instead of extending the last one -- so reading only the 713 * first line would let a client's element outrank the trusted proxy's. 714 * 715 * @param lines NULL-terminated array of field line values 716 * @param[out] el array of #PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS 717 * elements to fill in 718 * @return number of elements parsed, 0 if the header is unusable 719 */ 720 static unsigned int 721 parse_forwarded_lines (const char *const *lines, 722 struct Element *el) 723 { 724 unsigned int n = 0; 725 726 for (unsigned int i = 0; NULL != lines[i]; i++) 727 { 728 const char *p = lines[i]; 729 730 while (true) 731 { 732 p = skip_ows (p); 733 if ('\0' == *p) 734 break; 735 if (',' == *p) 736 { 737 /* RFC 9110 §5.6.1.2: an empty list element is to be ignored. */ 738 p++; 739 continue; 740 } 741 if (PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS == n) 742 { 743 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 744 "Forwarded chain longer than %u elements; ignoring it\n", 745 (unsigned int) PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS); 746 GNUNET_break_op (0); 747 return 0; 748 } 749 if (! parse_forwarded_element (&p, 750 &el[n])) 751 { 752 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 753 "Malformed Forwarded header; ignoring it\n"); 754 return 0; 755 } 756 n++; 757 if (',' == *p) 758 p++; 759 } 760 } 761 return n; 762 } 763 764 765 /** 766 * Parse every `X-Forwarded-For` field line of @a lines into @a el, in 767 * order. 768 * 769 * The de-facto header has no grammar beyond "comma-separated 770 * addresses"; an element that is not a bare address has no binary form 771 * and is recorded as naming none. 772 * 773 * @param lines NULL-terminated array of field line values 774 * @param[out] el array of #PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS 775 * elements to fill in 776 * @return number of elements parsed, 0 if the header is unusable 777 */ 778 static unsigned int 779 parse_xff_lines (const char *const *lines, 780 struct Element *el) 781 { 782 unsigned int n = 0; 783 784 for (unsigned int i = 0; NULL != lines[i]; i++) 785 { 786 const char *p = lines[i]; 787 788 while (true) 789 { 790 const char *comma; 791 struct Element *e; 792 size_t len; 793 794 p = skip_ows (p); 795 comma = strchr (p, 796 ','); 797 len = (NULL != comma) 798 ? (size_t) (comma - p) 799 : strlen (p); 800 while ( (len > 0) && 801 ( (' ' == p[len - 1]) || 802 ('\t' == p[len - 1]) ) ) 803 len--; 804 if (0 == len) 805 { 806 if (NULL == comma) 807 break; 808 p = comma + 1; 809 continue; 810 } 811 if (PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS == n) 812 { 813 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 814 "X-Forwarded-For chain longer than %u elements;" 815 " ignoring it\n", 816 (unsigned int) PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS); 817 GNUNET_break_op (0); 818 return 0; 819 } 820 e = &el[n++]; 821 memset (e, 822 0, 823 sizeof (*e)); 824 if (len < sizeof (e->node)) 825 { 826 struct in_addr a4; 827 struct in6_addr a6; 828 829 memcpy (e->node, 830 p, 831 len); 832 e->node[len] = '\0'; 833 if (1 == inet_pton (AF_INET, 834 e->node, 835 &a4)) 836 { 837 memcpy (e->addr, 838 &a4, 839 sizeof (a4)); 840 e->addr_len = sizeof (a4); 841 } 842 else if (1 == inet_pton (AF_INET6, 843 e->node, 844 &a6)) 845 { 846 pack_v6 (&a6, 847 e->addr, 848 &e->addr_len); 849 } 850 } 851 if (0 == e->addr_len) 852 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 853 "X-Forwarded-For element `%.*s' is not a bare address\n", 854 (int) len, 855 p); 856 if (NULL == comma) 857 break; 858 p = comma + 1; 859 } 860 } 861 return n; 862 } 863 864 865 bool 866 PAIVANA_HTTPD_is_trusted_proxy (const void *ca, 867 size_t ca_len) 868 { 869 if (! PH_have_trusted_proxies) 870 return false; 871 if (sizeof (struct in_addr) == ca_len) 872 { 873 const struct in_addr *a4 = ca; 874 875 if (NULL == PH_trusted_proxies4) 876 return false; 877 /* The list is terminated by an all-zero entry; GNUnet does not 878 hand out a count. See load_trusted_proxies(). */ 879 for (unsigned int i = 0; 880 0 != PH_trusted_proxies4[i].network.s_addr; 881 i++) 882 if ( (a4->s_addr & PH_trusted_proxies4[i].netmask.s_addr) == 883 (PH_trusted_proxies4[i].network.s_addr & 884 PH_trusted_proxies4[i].netmask.s_addr) ) 885 return true; 886 return false; 887 } 888 if (sizeof (struct in6_addr) == ca_len) 889 { 890 const struct in6_addr *a6 = ca; 891 892 if (NULL == PH_trusted_proxies6) 893 return false; 894 for (unsigned int i = 0; 895 ! GNUNET_is_zero (&PH_trusted_proxies6[i].network); 896 i++) 897 { 898 const struct in6_addr *net = &PH_trusted_proxies6[i].network; 899 const struct in6_addr *mask = &PH_trusted_proxies6[i].netmask; 900 bool match = true; 901 902 for (unsigned int j = 0; j < sizeof (struct in6_addr); j++) 903 if ( (a6->s6_addr[j] & mask->s6_addr[j]) != 904 (net->s6_addr[j] & mask->s6_addr[j]) ) 905 { 906 match = false; 907 break; 908 } 909 if (match) 910 return true; 911 } 912 return false; 913 } 914 GNUNET_break (0); 915 return false; 916 } 917 918 919 /** 920 * Walk the chain @a el from the right and return the index of the 921 * element that speaks for the client. 922 * 923 * We arrive here already standing on the peer we accepted from, which 924 * is trusted because `-f` says something in front of us is. Each step 925 * leftwards is permitted only by the node we are stepping over: that 926 * node wrote the element to its left, so unless *it* is one of ours, 927 * that element is hearsay. The first node we may not step over is 928 * therefore as far back as the chain can be believed, and is the 929 * client. 930 * 931 * @param el chain, leftmost element first 932 * @param n number of elements in @a el, at least 1 933 * @return index into @a el 934 */ 935 static unsigned int 936 select_element (const struct Element *el, 937 unsigned int n) 938 { 939 for (unsigned int i = n; i > 1; i--) 940 { 941 const struct Element *e = &el[i - 1]; 942 943 if (0 == e->addr_len) 944 return i - 1; /* nothing further left is reachable */ 945 if (! PAIVANA_HTTPD_is_trusted_proxy (e->addr, 946 e->addr_len)) 947 return i - 1; /* the client */ 948 } 949 /* Every hop is a proxy we trust; the leftmost is all we have. */ 950 return 0; 951 } 952 953 954 /** 955 * Copy the leftmost element of the first field line of @a lines into 956 * @a out, with whitespace stripped. 957 * 958 * The de-facto `X-Forwarded-*` headers other than `-For` are 959 * single-valued in practice, but a chain of proxies may still have 960 * turned one into a list; its leftmost element is the one describing 961 * the client's own request. 962 * 963 * @param lines NULL-terminated array of field line values, or NULL 964 * @param[out] out buffer for the value 965 * @param out_size number of bytes in @a out 966 * @return true if a non-empty value was found 967 */ 968 static bool 969 first_value (const char *const *lines, 970 char *out, 971 size_t out_size) 972 { 973 const char *p; 974 const char *comma; 975 size_t len; 976 977 if (! have_lines (lines)) 978 return false; 979 p = skip_ows (lines[0]); 980 comma = strchr (p, 981 ','); 982 len = (NULL != comma) 983 ? (size_t) (comma - p) 984 : strlen (p); 985 while ( (len > 0) && 986 ( (' ' == p[len - 1]) || 987 ('\t' == p[len - 1]) ) ) 988 len--; 989 if ( (0 == len) || 990 (len >= out_size) ) 991 return false; 992 memcpy (out, 993 p, 994 len); 995 out[len] = '\0'; 996 return true; 997 } 998 999 1000 void 1001 PAIVANA_HTTPD_client_clear (struct PAIVANA_HTTPD_Client *cl) 1002 { 1003 GNUNET_free (cl->ca); 1004 GNUNET_free (cl->proto); 1005 GNUNET_free (cl->host); 1006 memset (cl, 1007 0, 1008 sizeof (*cl)); 1009 } 1010 1011 1012 bool 1013 PAIVANA_HTTPD_resolve_forwarding (const struct PAIVANA_HTTPD_Forwarding *fi, 1014 struct PAIVANA_HTTPD_Client *cl) 1015 { 1016 struct Element el[PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS]; 1017 unsigned int n = 0; 1018 bool from_forwarded = false; 1019 1020 memset (cl, 1021 0, 1022 sizeof (*cl)); 1023 if (fi->respect_forwarded) 1024 { 1025 /* RFC 7239 is the standardized form and says more than the 1026 de-facto headers do, so it wins where both are present -- and 1027 the reverse-proxy configurations we ship set it. A proxy that 1028 emits both should agree with itself; if it does not, we would 1029 rather be predictable than clever. */ 1030 if (have_lines (fi->forwarded)) 1031 { 1032 if (have_lines (fi->xff)) 1033 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1034 "Both Forwarded and X-Forwarded-For present; using" 1035 " Forwarded\n"); 1036 from_forwarded = true; 1037 n = parse_forwarded_lines (fi->forwarded, 1038 el); 1039 } 1040 else if (have_lines (fi->xff)) 1041 { 1042 n = parse_xff_lines (fi->xff, 1043 el); 1044 } 1045 } 1046 if (0 != n) 1047 { 1048 unsigned int sel = select_element (el, 1049 n); 1050 1051 if (0 != el[sel].addr_len) 1052 { 1053 cl->ca = GNUNET_memdup (el[sel].addr, 1054 el[sel].addr_len); 1055 cl->ca_len = el[sel].addr_len; 1056 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1057 "Client address is based on %s: `%s'\n", 1058 from_forwarded 1059 ? MHD_HTTP_HEADER_FORWARDED 1060 : PH_HEADER_X_FORWARDED_FOR, 1061 el[sel].node); 1062 } 1063 else 1064 { 1065 /* RFC 7239 §6.3 `unknown`, an obfuscated identifier, a name we 1066 cannot turn into bytes: legal, but nothing to bind a cookie 1067 to. The peer is what we know for certain, and a header the 1068 client controls must never be able to take that away. */ 1069 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1070 "Forwarding chain names no client address at `%s';" 1071 " falling back to the socket peer\n", 1072 el[sel].node); 1073 } 1074 if (from_forwarded) 1075 { 1076 /* Same element, same author: the hop that reported this node 1077 also reported the connection its predecessor made to it. */ 1078 if (el[sel].have_proto) 1079 cl->proto = GNUNET_strdup (el[sel].https 1080 ? "https" 1081 : "http"); 1082 if ('\0' != el[sel].host[0]) 1083 cl->host = GNUNET_strdup (el[sel].host); 1084 } 1085 } 1086 if ( (NULL == cl->ca) && 1087 (NULL != fi->peer) && 1088 (0 != fi->peer_len) ) 1089 { 1090 cl->ca = GNUNET_memdup (fi->peer, 1091 fi->peer_len); 1092 cl->ca_len = fi->peer_len; 1093 } 1094 if (fi->respect_forwarded) 1095 { 1096 char buf[PH_HOST_MAX]; 1097 1098 if ( (NULL == cl->proto) && 1099 (first_value (fi->xfp, 1100 buf, 1101 sizeof (buf))) ) 1102 { 1103 if (0 == strcasecmp (buf, 1104 "https")) 1105 cl->proto = GNUNET_strdup ("https"); 1106 else if (0 == strcasecmp (buf, 1107 "http")) 1108 cl->proto = GNUNET_strdup ("http"); 1109 else 1110 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1111 "Ignoring unusable %s value `%s'\n", 1112 PH_HEADER_X_FORWARDED_PROTO, 1113 buf); 1114 } 1115 if ( (NULL == cl->host) && 1116 (first_value (fi->xfh, 1117 buf, 1118 sizeof (buf))) ) 1119 { 1120 if (valid_host (buf)) 1121 cl->host = GNUNET_strdup (buf); 1122 else 1123 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1124 "Ignoring unusable %s value `%s'\n", 1125 PH_HEADER_X_FORWARDED_HOST, 1126 buf); 1127 } 1128 /* An authority carries at most one port. nginx's 1129 `X-Forwarded-Host $http_host` includes one and its 1130 `X-Forwarded-Port` repeats it; appending regardless would yield 1131 "example.com:8443:8443", which is no authority at all. */ 1132 if ( (NULL != cl->host) && 1133 (! host_has_port (cl->host)) && 1134 (first_value (fi->xfport, 1135 buf, 1136 sizeof (buf))) ) 1137 { 1138 if (! valid_port_text (buf)) 1139 { 1140 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1141 "Ignoring unusable %s value `%s'\n", 1142 PH_HEADER_X_FORWARDED_PORT, 1143 buf); 1144 } 1145 else 1146 { 1147 unsigned int port = (unsigned int) strtoul (buf, 1148 NULL, 1149 10); 1150 bool https = ( (NULL != cl->proto) && 1151 (0 == strcmp (cl->proto, 1152 "https")) ); 1153 1154 /* Re-rendered rather than echoed: "0443" is a valid spelling 1155 of 443 and must not reach the URL as itself. The default 1156 port for the scheme is left off, as RFC 3986 §6.2.3 asks. */ 1157 if (port != (https ? 443U : 80U)) 1158 { 1159 char *hp; 1160 1161 GNUNET_asprintf (&hp, 1162 "%s:%u", 1163 cl->host, 1164 port); 1165 GNUNET_free (cl->host); 1166 cl->host = hp; 1167 } 1168 } 1169 } 1170 } 1171 return (NULL != cl->ca); 1172 } 1173 1174 1175 char * 1176 PAIVANA_HTTPD_forwarded_param (const char *fwd, 1177 const char *name) 1178 { 1179 struct Element el[PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS]; 1180 const char *lines[] = { 1181 fwd, 1182 NULL 1183 }; 1184 1185 if (0 == parse_forwarded_lines (lines, 1186 el)) 1187 return NULL; 1188 /* The leftmost element describes the connection the client itself 1189 made, which is what `proto` and `host` are being asked about. 1190 With a single proxy in front -- the ordinary case -- there is only 1191 one element and the question does not arise. */ 1192 if (0 == strcasecmp (name, 1193 "proto")) 1194 return el[0].have_proto 1195 ? GNUNET_strdup (el[0].https 1196 ? "https" 1197 : "http") 1198 : NULL; 1199 if (0 == strcasecmp (name, 1200 "host")) 1201 return ('\0' != el[0].host[0]) 1202 ? GNUNET_strdup (el[0].host) 1203 : NULL; 1204 GNUNET_break (0); /* no other parameter is validated here */ 1205 return NULL; 1206 } 1207 1208 1209 char * 1210 PAIVANA_HTTPD_forwarded_value (const char *v) 1211 { 1212 struct GNUNET_Buffer buf = { 0 }; 1213 bool token = ('\0' != v[0]); 1214 1215 for (const char *p = v; '\0' != *p; p++) 1216 if (! is_tchar (*p)) 1217 { 1218 token = false; 1219 break; 1220 } 1221 if (token) 1222 return GNUNET_strdup (v); 1223 GNUNET_buffer_write (&buf, 1224 "\"", 1225 1); 1226 for (const char *p = v; '\0' != *p; p++) 1227 { 1228 if ( ('"' == *p) || 1229 ('\\' == *p) ) 1230 { 1231 GNUNET_buffer_write (&buf, 1232 "\\", 1233 1); 1234 } 1235 else if (! is_qdtext (*p)) 1236 { 1237 /* A control character has no quoted-pair either, so there is no 1238 way to say this at all. */ 1239 GNUNET_break_op (0); 1240 GNUNET_buffer_clear (&buf); 1241 return NULL; 1242 } 1243 GNUNET_buffer_write (&buf, 1244 p, 1245 1); 1246 } 1247 GNUNET_buffer_write (&buf, 1248 "\"", 1249 1); 1250 return GNUNET_buffer_reap_str (&buf); 1251 } 1252 1253 1254 char * 1255 PAIVANA_HTTPD_forwarded_node (const void *ca, 1256 size_t ca_len) 1257 { 1258 char buf[INET6_ADDRSTRLEN]; 1259 char bracketed[INET6_ADDRSTRLEN + 2]; 1260 1261 if ( (NULL == ca) || 1262 (0 == ca_len) ) 1263 { 1264 /* RFC 7239 §6.3 provides exactly this for a hop whose predecessor 1265 has no address we can name -- a Unix-domain peer, here. */ 1266 return GNUNET_strdup ("unknown"); 1267 } 1268 if (sizeof (struct in_addr) == ca_len) 1269 { 1270 GNUNET_assert (NULL != inet_ntop (AF_INET, 1271 ca, 1272 buf, 1273 sizeof (buf))); 1274 return PAIVANA_HTTPD_forwarded_value (buf); 1275 } 1276 GNUNET_assert (sizeof (struct in6_addr) == ca_len); 1277 GNUNET_assert (NULL != inet_ntop (AF_INET6, 1278 ca, 1279 buf, 1280 sizeof (buf))); 1281 /* RFC 7239 §6: an IPv6 identifier is bracketed, and the brackets 1282 force the whole thing to be a quoted-string. */ 1283 GNUNET_snprintf (bracketed, 1284 sizeof (bracketed), 1285 "[%s]", 1286 buf); 1287 return PAIVANA_HTTPD_forwarded_value (bracketed); 1288 } 1289 1290 1291 char * 1292 PAIVANA_HTTPD_forwarded_for_chain (const char *fwd) 1293 { 1294 struct Element el[PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS]; 1295 struct GNUNET_Buffer buf = { 0 }; 1296 const char *lines[] = { 1297 fwd, 1298 NULL 1299 }; 1300 unsigned int n; 1301 1302 n = parse_forwarded_lines (lines, 1303 el); 1304 if (0 == n) 1305 return NULL; 1306 for (unsigned int i = 0; i < n; i++) 1307 { 1308 char node[INET6_ADDRSTRLEN]; 1309 1310 /* All or nothing: X-Forwarded-For has no way to say "this hop had 1311 no address", so an element whose `for` is RFC 7239's "unknown", 1312 an obfuscated identifier, or anything else that is not an 1313 address cannot be represented. Dropping it would silently 1314 shift every position to its left, which is worse than declining 1315 to translate the header at all. */ 1316 if (0 == el[i].addr_len) 1317 { 1318 GNUNET_buffer_clear (&buf); 1319 return NULL; 1320 } 1321 GNUNET_assert (NULL != 1322 inet_ntop ( (sizeof (struct in_addr) == el[i].addr_len) 1323 ? AF_INET 1324 : AF_INET6, 1325 el[i].addr, 1326 node, 1327 sizeof (node))); 1328 if (0 != i) 1329 GNUNET_buffer_write_str (&buf, 1330 ", "); 1331 GNUNET_buffer_write_str (&buf, 1332 node); 1333 } 1334 return GNUNET_buffer_reap_str (&buf); 1335 } 1336 1337 1338 /** 1339 * Store the address of the peer we accepted @a connection from. 1340 * 1341 * @param connection HTTP client connection 1342 * @param[out] addr buffer of at least `sizeof (struct in6_addr)` bytes 1343 * @param[out] addr_len set to the number of bytes written, 0 for a 1344 * peer that has no IP address 1345 */ 1346 static void 1347 socket_address (struct MHD_Connection *connection, 1348 unsigned char *addr, 1349 size_t *addr_len) 1350 { 1351 const union MHD_ConnectionInfo *ci; 1352 const struct sockaddr *sa; 1353 1354 *addr_len = 0; 1355 ci = MHD_get_connection_info (connection, 1356 MHD_CONNECTION_INFO_CLIENT_ADDRESS); 1357 if ( (NULL == ci) || 1358 (NULL == ci->client_addr) ) 1359 { 1360 /* MHD documents this as NULL-able -- it returns NULL when the 1361 connection carries no address at all -- and no reachable case 1362 was found. Still: "a peer with no address" is a case every 1363 caller of ours already handles (it is the AF_UNIX case), so 1364 aborting the single-process daemon over it would turn a 1365 hypothetical into an outage. */ 1366 GNUNET_break (0); 1367 return; 1368 } 1369 sa = ci->client_addr; 1370 switch (sa->sa_family) 1371 { 1372 case AF_INET: 1373 memcpy (addr, 1374 &((const struct sockaddr_in *) sa)->sin_addr, 1375 sizeof (struct in_addr)); 1376 *addr_len = sizeof (struct in_addr); 1377 return; 1378 case AF_INET6: 1379 /* A dual-stack listener hands us ::ffff:a.b.c.d for an IPv4 1380 peer; pack_v6() folds that back to the IPv4 form. */ 1381 pack_v6 (&((const struct sockaddr_in6 *) sa)->sin6_addr, 1382 addr, 1383 addr_len); 1384 return; 1385 default: 1386 /* AF_UNIX: no address exists. */ 1387 return; 1388 } 1389 } 1390 1391 1392 /** 1393 * The field lines of the forwarding headers of one request, as 1394 * collected from MHD. 1395 */ 1396 struct HeaderLines 1397 { 1398 1399 /** 1400 * `Forwarded` field lines, NULL-terminated once collected. 1401 */ 1402 const char **forwarded; 1403 1404 /** 1405 * `X-Forwarded-For` field lines. 1406 */ 1407 const char **xff; 1408 1409 /** 1410 * `X-Forwarded-Proto` field lines. 1411 */ 1412 const char **xfp; 1413 1414 /** 1415 * `X-Forwarded-Host` field lines. 1416 */ 1417 const char **xfh; 1418 1419 /** 1420 * `X-Forwarded-Port` field lines. 1421 */ 1422 const char **xfport; 1423 1424 /** 1425 * Number of entries in @e forwarded. 1426 */ 1427 unsigned int forwarded_len; 1428 1429 /** 1430 * Number of entries in @e xff. 1431 */ 1432 unsigned int xff_len; 1433 1434 /** 1435 * Number of entries in @e xfp. 1436 */ 1437 unsigned int xfp_len; 1438 1439 /** 1440 * Number of entries in @e xfh. 1441 */ 1442 unsigned int xfh_len; 1443 1444 /** 1445 * Number of entries in @e xfport. 1446 */ 1447 unsigned int xfport_len; 1448 }; 1449 1450 1451 /** 1452 * Collect one header field line into the arrays of @a cls. 1453 * 1454 * @param cls a `struct HeaderLines *` 1455 * @param kind header kind, always #MHD_HEADER_KIND here 1456 * @param key field name 1457 * @param value field line value 1458 * @return #MHD_YES to keep iterating 1459 */ 1460 static enum MHD_Result 1461 collect_header (void *cls, 1462 enum MHD_ValueKind kind, 1463 const char *key, 1464 const char *value) 1465 { 1466 struct HeaderLines *hl = cls; 1467 1468 (void) kind; 1469 if ( (NULL == key) || 1470 (NULL == value) ) 1471 return MHD_YES; 1472 if (0 == strcasecmp (key, 1473 MHD_HTTP_HEADER_FORWARDED)) 1474 GNUNET_array_append (hl->forwarded, 1475 hl->forwarded_len, 1476 value); 1477 else if (0 == strcasecmp (key, 1478 PH_HEADER_X_FORWARDED_FOR)) 1479 GNUNET_array_append (hl->xff, 1480 hl->xff_len, 1481 value); 1482 else if (0 == strcasecmp (key, 1483 PH_HEADER_X_FORWARDED_PROTO)) 1484 GNUNET_array_append (hl->xfp, 1485 hl->xfp_len, 1486 value); 1487 else if (0 == strcasecmp (key, 1488 PH_HEADER_X_FORWARDED_HOST)) 1489 GNUNET_array_append (hl->xfh, 1490 hl->xfh_len, 1491 value); 1492 else if (0 == strcasecmp (key, 1493 PH_HEADER_X_FORWARDED_PORT)) 1494 GNUNET_array_append (hl->xfport, 1495 hl->xfport_len, 1496 value); 1497 return MHD_YES; 1498 } 1499 1500 1501 /** 1502 * Collect the forwarding header field lines of @a connection. 1503 * 1504 * MHD stores repeated field lines separately and 1505 * MHD_lookup_connection_value() returns only one of them, so iterating 1506 * is the only way to see them all -- and RFC 9110 §5.3 says all of 1507 * them together are the field value. The values are borrowed from MHD 1508 * and live as long as the connection; only the arrays are ours. 1509 * 1510 * @param connection connection to read from 1511 * @param[out] hl where to collect; release with #free_headers() 1512 */ 1513 static void 1514 collect_headers (struct MHD_Connection *connection, 1515 struct HeaderLines *hl) 1516 { 1517 memset (hl, 1518 0, 1519 sizeof (*hl)); 1520 MHD_get_connection_values (connection, 1521 MHD_HEADER_KIND, 1522 &collect_header, 1523 hl); 1524 if (0 != hl->forwarded_len) 1525 GNUNET_array_append (hl->forwarded, 1526 hl->forwarded_len, 1527 NULL); 1528 if (0 != hl->xff_len) 1529 GNUNET_array_append (hl->xff, 1530 hl->xff_len, 1531 NULL); 1532 if (0 != hl->xfp_len) 1533 GNUNET_array_append (hl->xfp, 1534 hl->xfp_len, 1535 NULL); 1536 if (0 != hl->xfh_len) 1537 GNUNET_array_append (hl->xfh, 1538 hl->xfh_len, 1539 NULL); 1540 if (0 != hl->xfport_len) 1541 GNUNET_array_append (hl->xfport, 1542 hl->xfport_len, 1543 NULL); 1544 } 1545 1546 1547 /** 1548 * Release the arrays of @a hl. 1549 * 1550 * @param[in,out] hl what #collect_headers() filled in 1551 */ 1552 static void 1553 free_headers (struct HeaderLines *hl) 1554 { 1555 GNUNET_array_grow (hl->forwarded, 1556 hl->forwarded_len, 1557 0); 1558 GNUNET_array_grow (hl->xff, 1559 hl->xff_len, 1560 0); 1561 GNUNET_array_grow (hl->xfp, 1562 hl->xfp_len, 1563 0); 1564 GNUNET_array_grow (hl->xfh, 1565 hl->xfh_len, 1566 0); 1567 GNUNET_array_grow (hl->xfport, 1568 hl->xfport_len, 1569 0); 1570 } 1571 1572 1573 /** 1574 * Run the forwarding walk for @a connection. 1575 * 1576 * @param connection connection to resolve 1577 * @param[out] cl what we concluded; to be released with 1578 * #PAIVANA_HTTPD_client_clear() 1579 * @return true if a client address was determined 1580 */ 1581 static bool 1582 resolve_connection (struct MHD_Connection *connection, 1583 struct PAIVANA_HTTPD_Client *cl) 1584 { 1585 struct HeaderLines hl; 1586 struct PAIVANA_HTTPD_Forwarding fi = { 0 }; 1587 unsigned char peer[sizeof (struct in6_addr)]; 1588 size_t peer_len; 1589 bool ret; 1590 1591 socket_address (connection, 1592 peer, 1593 &peer_len); 1594 collect_headers (connection, 1595 &hl); 1596 fi.peer = (0 != peer_len) 1597 ? peer 1598 : NULL; 1599 fi.peer_len = peer_len; 1600 fi.respect_forwarded = (0 != PH_respect_forwarded_headers); 1601 fi.forwarded = hl.forwarded; 1602 fi.xff = hl.xff; 1603 fi.xfp = hl.xfp; 1604 fi.xfh = hl.xfh; 1605 fi.xfport = hl.xfport; 1606 ret = PAIVANA_HTTPD_resolve_forwarding (&fi, 1607 cl); 1608 free_headers (&hl); 1609 return ret; 1610 } 1611 1612 1613 bool 1614 PAIVANA_HTTPD_get_client_address (struct MHD_Connection *connection, 1615 void **ca, 1616 size_t *ca_len) 1617 { 1618 struct PAIVANA_HTTPD_Client cl; 1619 bool ret; 1620 1621 ret = resolve_connection (connection, 1622 &cl); 1623 *ca = cl.ca; 1624 *ca_len = cl.ca_len; 1625 cl.ca = NULL; 1626 cl.ca_len = 0; 1627 PAIVANA_HTTPD_client_clear (&cl); 1628 if (! ret) 1629 { 1630 /* Only a peer that has no address at all gets here, i.e. AF_UNIX 1631 with no forwarding header to stand in for it. The shipped 1632 packaging serves over a Unix socket and passes -f for exactly 1633 this reason. */ 1634 GNUNET_break (0); 1635 } 1636 return ret; 1637 } 1638 1639 1640 bool 1641 PAIVANA_HTTPD_get_base_url (struct MHD_Connection *connection, 1642 struct GNUNET_Buffer *buf) 1643 { 1644 struct PAIVANA_HTTPD_Client cl; 1645 const char *host; 1646 1647 GNUNET_buffer_clear (buf); 1648 if (NULL != PH_base_url) 1649 { 1650 GNUNET_buffer_write_str (buf, 1651 PH_base_url); 1652 return true; 1653 } 1654 /* run() refuses to start without BASE_URL unless -f was given: with 1655 no proxy in front of us the Host header is unverified client 1656 input and reconstructing a URL from it is not something we may 1657 do. So everything below is the behind-a-trusted-proxy case. */ 1658 GNUNET_assert (0 != PH_respect_forwarded_headers); 1659 (void) resolve_connection (connection, 1660 &cl); 1661 if (NULL != cl.proto) 1662 { 1663 GNUNET_buffer_write_str (buf, 1664 cl.proto); 1665 GNUNET_buffer_write_str (buf, 1666 "://"); 1667 } 1668 else 1669 { 1670 /* The proxy said nothing about the scheme, so go by the transport: 1671 MHD reports a TLS session or it does not. (TALER_mhd_is_https() 1672 would consult X-Forwarded-Proto again, without applying the 1673 walk's verdict on whether it may be believed.) */ 1674 GNUNET_buffer_write_str (buf, 1675 (NULL != 1676 MHD_get_connection_info ( 1677 connection, 1678 MHD_CONNECTION_INFO_PROTOCOL)) 1679 ? "https://" 1680 : "http://"); 1681 } 1682 host = cl.host; 1683 if (NULL == host) 1684 { 1685 /* No `Forwarded` host and no X-Forwarded-Host: the proxy passed 1686 the client's Host through, and -f says it vouches for it. */ 1687 host = MHD_lookup_connection_value (connection, 1688 MHD_HEADER_KIND, 1689 MHD_HTTP_HEADER_HOST); 1690 if (NULL == host) 1691 { 1692 /* RFC 9112 §3.2 requires a Host on every HTTP/1.1 request. */ 1693 GNUNET_break_op (0); 1694 PAIVANA_HTTPD_client_clear (&cl); 1695 return false; 1696 } 1697 if (! valid_host (host)) 1698 { 1699 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1700 "Refusing to build a base URL from Host `%s'\n", 1701 host); 1702 GNUNET_break_op (0); 1703 PAIVANA_HTTPD_client_clear (&cl); 1704 return false; 1705 } 1706 } 1707 GNUNET_buffer_write_str (buf, 1708 host); 1709 PAIVANA_HTTPD_client_clear (&cl); 1710 return true; 1711 }