test_client_address.c (51824B)
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 * @file test_client_address.c 23 * @brief tests the forwarding walk: who the client is, and what it 24 * thinks it connected to 25 * 26 * Two things come out of #PAIVANA_HTTPD_resolve_forwarding(), and both 27 * are load-bearing: 28 * 29 * - The client address. The access cookie is an HMAC over 30 * (expiration, website, client address). If a host yields one byte 31 * string when it reaches paivana through a proxy and a different one 32 * when it reaches paivana directly -- or when two proxies spell its 33 * address differently -- then the cookie it was issued stops 34 * verifying, with no error anywhere: it just looks unpaid again. So 35 * the representation has to be canonical, which is what this test 36 * pins down. And if a client can *choose* that address, one paid 37 * cookie admits everybody, which is what the trust walk prevents. 38 * 39 * - The scheme and authority the base URL is rebuilt from, which the 40 * very same walk has to decide, or the cookie ends up keyed on a 41 * website string one hop reported and an address another one did. 42 * 43 * The integration suite cannot cover any of this: it runs paivana with 44 * -n, where the cookie path is never reached at all, and it has no way 45 * to present a forwarding chain from a chosen peer. 46 */ 47 #include "platform.h" 48 #include <gnunet/gnunet_util_lib.h> 49 #include "paivana-httpd_helper.h" 50 #include "paivana-httpd_cookie.h" 51 52 /** 53 * Globals that paivana-httpd.c normally defines; the helper and 54 * cookie compilation units reference them. 55 */ 56 int PH_respect_forwarded_headers; 57 char *PH_base_url; 58 int PH_global_cookie; 59 struct GNUNET_STRINGS_IPv4NetworkPolicy *PH_trusted_proxies4; 60 struct GNUNET_STRINGS_IPv6NetworkPolicy *PH_trusted_proxies6; 61 bool PH_have_trusted_proxies; 62 63 /** 64 * Cookie key, normally set up by paivana-httpd.c from the SECRET 65 * configuration value. 66 */ 67 extern struct GNUNET_HashCode paivana_secret; 68 69 /** 70 * Website the test cookies are minted for. 71 */ 72 #define WEBSITE "http://example.com/page" 73 74 /** 75 * Number of checks that did not hold. 76 */ 77 static unsigned int failures; 78 79 80 /** 81 * One row of the table-driven forwarding tests. 82 * 83 * The header members are the *field lines* of that header, in the 84 * order they arrived; leaving one empty means the header was absent. 85 */ 86 struct Case 87 { 88 89 /** 90 * What this row is about, for the log. 91 */ 92 const char *name; 93 94 /** 95 * Socket peer, in presentation form, or NULL for a peer without an 96 * address (AF_UNIX). 97 */ 98 const char *peer; 99 100 /** 101 * `TRUSTED_PROXIES`, or NULL. 102 */ 103 const char *v4; 104 105 /** 106 * `TRUSTED_PROXIES6`, or NULL. 107 */ 108 const char *v6; 109 110 /** 111 * `Forwarded` field lines. 112 */ 113 const char *fwd[4]; 114 115 /** 116 * `X-Forwarded-For` field lines. 117 */ 118 const char *xff[4]; 119 120 /** 121 * `X-Forwarded-Proto` field lines. 122 */ 123 const char *xfp[2]; 124 125 /** 126 * `X-Forwarded-Host` field lines. 127 */ 128 const char *xfh[2]; 129 130 /** 131 * `X-Forwarded-Port` field lines. 132 */ 133 const char *xfport[2]; 134 135 /** 136 * Expected client address in presentation form, or NULL if none 137 * should be found. 138 */ 139 const char *want_ca; 140 141 /** 142 * Expected scheme, or NULL for "nothing we trust said". 143 */ 144 const char *want_proto; 145 146 /** 147 * Expected authority, or NULL for "nothing we trust said". 148 */ 149 const char *want_host; 150 151 /** 152 * Was `-f` given? 153 */ 154 bool respect; 155 }; 156 157 158 /** 159 * Print @a len bytes of @a p as hex into @a out. 160 * 161 * @param p bytes to render 162 * @param len number of bytes in @a p 163 * @param[out] out buffer of at least 2 * @a len + 1 bytes 164 */ 165 static void 166 tohex (const void *p, 167 size_t len, 168 char *out) 169 { 170 const unsigned char *b = p; 171 172 for (size_t i = 0; i < len; i++) 173 sprintf (&out[2 * i], 174 "%02x", 175 b[i]); 176 out[2 * len] = '\0'; 177 } 178 179 180 /** 181 * The binary address the socket branch of the resolver produces for a 182 * peer whose address is @a literal. Mirrors that branch rather than 183 * calling it, as calling it would need a live MHD connection. 184 * 185 * @param literal address of the peer, in presentation form 186 * @param[out] ca where to write the allocated address 187 * @param[out] ca_len set to the number of bytes in @a ca 188 */ 189 static void 190 socket_address (const char *literal, 191 void **ca, 192 size_t *ca_len) 193 { 194 struct in_addr a4; 195 struct in6_addr a6; 196 197 if (1 == inet_pton (AF_INET, 198 literal, 199 &a4)) 200 { 201 *ca = GNUNET_memdup (&a4, 202 sizeof (a4)); 203 *ca_len = sizeof (a4); 204 return; 205 } 206 GNUNET_assert (1 == inet_pton (AF_INET6, 207 literal, 208 &a6)); 209 if (IN6_IS_ADDR_V4MAPPED (&a6)) 210 { 211 *ca = GNUNET_memdup (&a6.s6_addr[12], 212 sizeof (struct in_addr)); 213 *ca_len = sizeof (struct in_addr); 214 return; 215 } 216 *ca = GNUNET_memdup (&a6, 217 sizeof (a6)); 218 *ca_len = sizeof (a6); 219 } 220 221 222 /** 223 * Mint an access cookie for @a mint_ca and check it against 224 * @a check_ca. 225 * 226 * @param mint_ca client address the cookie is issued for 227 * @param mint_len number of bytes in @a mint_ca 228 * @param check_ca client address presented on the later request 229 * @param check_len number of bytes in @a check_ca 230 * @return true if the cookie verified 231 */ 232 static bool 233 cookie_survives (const void *mint_ca, 234 size_t mint_len, 235 const void *check_ca, 236 size_t check_len) 237 { 238 struct GNUNET_TIME_Timestamp ts; 239 char *setcookie; 240 char *val; 241 char *semi; 242 bool ok; 243 244 ts = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS); 245 setcookie = PAIVANA_HTTPD_compute_cookie (ts, 246 WEBSITE, 247 mint_len, 248 mint_ca); 249 /* Reduce the Set-Cookie line to the bare cookie value, which is 250 what check_cookie() is given on the next request. */ 251 val = strchr (setcookie, 252 '='); 253 GNUNET_assert (NULL != val); 254 val++; 255 semi = strchr (val, 256 ';'); 257 if (NULL != semi) 258 *semi = '\0'; 259 ok = PAIVANA_HTTPD_check_cookie (val, 260 WEBSITE, 261 check_len, 262 check_ca); 263 GNUNET_free (setcookie); 264 return ok; 265 } 266 267 268 /** 269 * Install a trusted-proxy policy for the checks that follow, or clear 270 * it when both arguments are NULL. 271 * 272 * The GNUnet policy parsers are lenient enough that "returned 273 * non-NULL" is not the same as "understood something" -- see 274 * load_trusted_proxies() in paivana-httpd.c -- so this asserts that 275 * usable entries actually came back. 276 * 277 * @param v4 IPv4 policy string, or NULL 278 * @param v6 IPv6 policy string, or NULL 279 */ 280 static void 281 set_policy (const char *v4, 282 const char *v6) 283 { 284 GNUNET_free (PH_trusted_proxies4); 285 GNUNET_free (PH_trusted_proxies6); 286 PH_trusted_proxies4 = NULL; 287 PH_trusted_proxies6 = NULL; 288 PH_have_trusted_proxies = false; 289 if (NULL != v4) 290 { 291 PH_trusted_proxies4 = GNUNET_STRINGS_parse_ipv4_policy (v4); 292 GNUNET_assert (NULL != PH_trusted_proxies4); 293 GNUNET_assert (0 != PH_trusted_proxies4[0].network.s_addr); 294 PH_have_trusted_proxies = true; 295 } 296 if (NULL != v6) 297 { 298 PH_trusted_proxies6 = GNUNET_STRINGS_parse_ipv6_policy (v6); 299 GNUNET_assert (NULL != PH_trusted_proxies6); 300 GNUNET_assert (! GNUNET_is_zero (&PH_trusted_proxies6[0].network)); 301 PH_have_trusted_proxies = true; 302 } 303 } 304 305 306 /** 307 * Resolve @a c and check the outcome against what it expects. 308 * 309 * @param c case to run 310 */ 311 static void 312 run_case (const struct Case *c) 313 { 314 struct PAIVANA_HTTPD_Forwarding fi = { 0 }; 315 struct PAIVANA_HTTPD_Client cl; 316 void *peer = NULL; 317 void *want = NULL; 318 size_t peer_len = 0; 319 size_t want_len = 0; 320 bool ok = true; 321 322 set_policy (c->v4, 323 c->v6); 324 PH_respect_forwarded_headers = c->respect ? 1 : 0; 325 if (NULL != c->peer) 326 socket_address (c->peer, 327 &peer, 328 &peer_len); 329 fi.peer = peer; 330 fi.peer_len = peer_len; 331 fi.respect_forwarded = c->respect; 332 fi.forwarded = c->fwd; 333 fi.xff = c->xff; 334 fi.xfp = c->xfp; 335 fi.xfh = c->xfh; 336 fi.xfport = c->xfport; 337 if (PAIVANA_HTTPD_resolve_forwarding (&fi, 338 &cl) != (NULL != c->want_ca)) 339 ok = false; 340 if (NULL != c->want_ca) 341 { 342 socket_address (c->want_ca, 343 &want, 344 &want_len); 345 if ( (want_len != cl.ca_len) || 346 (NULL == cl.ca) || 347 (0 != memcmp (want, 348 cl.ca, 349 want_len)) ) 350 ok = false; 351 /* The identity has to be the same bytes however it was learned, 352 or a cookie minted on one path stops verifying on the next. */ 353 else if (! cookie_survives (cl.ca, 354 cl.ca_len, 355 want, 356 want_len)) 357 ok = false; 358 } 359 else if (NULL != cl.ca) 360 { 361 ok = false; 362 } 363 if ( ( (NULL == cl.proto) != (NULL == c->want_proto) ) || 364 ( (NULL != cl.proto) && 365 (0 != strcmp (cl.proto, 366 c->want_proto)) ) ) 367 ok = false; 368 if ( ( (NULL == cl.host) != (NULL == c->want_host) ) || 369 ( (NULL != cl.host) && 370 (0 != strcmp (cl.host, 371 c->want_host)) ) ) 372 ok = false; 373 if (! ok) 374 { 375 char ghex[2 * sizeof (struct in6_addr) + 1]; 376 char ehex[2 * sizeof (struct in6_addr) + 1]; 377 378 tohex (cl.ca, 379 cl.ca_len, 380 ghex); 381 tohex (want, 382 want_len, 383 ehex); 384 fprintf (stderr, 385 "FAIL: %s\n" 386 " address %s, want %s (%s)\n" 387 " proto %s, want %s\n" 388 " host %s, want %s\n", 389 c->name, 390 ghex, 391 ehex, 392 (NULL != c->want_ca) ? c->want_ca : "(none)", 393 (NULL != cl.proto) ? cl.proto : "(none)", 394 (NULL != c->want_proto) ? c->want_proto : "(none)", 395 (NULL != cl.host) ? cl.host : "(none)", 396 (NULL != c->want_host) ? c->want_host : "(none)"); 397 failures++; 398 } 399 else 400 { 401 fprintf (stderr, 402 " ok: %s\n", 403 c->name); 404 } 405 PAIVANA_HTTPD_client_clear (&cl); 406 GNUNET_free (peer); 407 GNUNET_free (want); 408 set_policy (NULL, 409 NULL); 410 } 411 412 413 /** 414 * Run every case in the NULL-name-terminated table @a tab. 415 * 416 * @param tab cases to run 417 */ 418 static void 419 run_cases (const struct Case *tab) 420 { 421 for (unsigned int i = 0; NULL != tab[i].name; i++) 422 run_case (&tab[i]); 423 } 424 425 426 /** 427 * Resolve @a xff as the only `X-Forwarded-For` field line of a request 428 * from a peer with no address of its own, so that whatever comes out 429 * came out of the header. 430 * 431 * @param xff header value 432 * @param[out] ca where to write the allocated address 433 * @param[out] ca_len set to the number of bytes in @a ca 434 * @return true if an address was found 435 */ 436 static bool 437 resolve_xff (const char *xff, 438 void **ca, 439 size_t *ca_len) 440 { 441 struct PAIVANA_HTTPD_Forwarding fi = { 0 }; 442 struct PAIVANA_HTTPD_Client cl; 443 const char *lines[] = { 444 xff, 445 NULL 446 }; 447 bool ret; 448 449 PH_respect_forwarded_headers = 1; 450 fi.respect_forwarded = true; 451 fi.xff = lines; 452 ret = PAIVANA_HTTPD_resolve_forwarding (&fi, 453 &cl); 454 *ca = cl.ca; 455 *ca_len = cl.ca_len; 456 cl.ca = NULL; 457 cl.ca_len = 0; 458 PAIVANA_HTTPD_client_clear (&cl); 459 return ret; 460 } 461 462 463 /** 464 * Check that @a xff yields exactly the address a direct connection 465 * from @a peer would, and that a cookie issued on one path is 466 * accepted on the other. 467 * 468 * @param xff `X-Forwarded-For` value the proxied request carries 469 * @param peer socket address of the same host connecting directly 470 */ 471 static void 472 same_host (const char *xff, 473 const char *peer) 474 { 475 void *fa; 476 void *sa; 477 size_t fa_len; 478 size_t sa_len; 479 char fhex[2 * sizeof (struct in6_addr) + 1]; 480 char shex[2 * sizeof (struct in6_addr) + 1]; 481 482 socket_address (peer, 483 &sa, 484 &sa_len); 485 if (! resolve_xff (xff, 486 &fa, 487 &fa_len)) 488 { 489 fprintf (stderr, 490 "FAIL: X-Forwarded-For `%s' rejected, want the address of %s\n", 491 xff, 492 peer); 493 failures++; 494 GNUNET_free (sa); 495 return; 496 } 497 tohex (fa, 498 fa_len, 499 fhex); 500 tohex (sa, 501 sa_len, 502 shex); 503 if ( (fa_len != sa_len) || 504 (0 != memcmp (fa, 505 sa, 506 fa_len)) ) 507 { 508 fprintf (stderr, 509 "FAIL: X-Forwarded-For `%s' gives %s, socket %s gives %s\n", 510 xff, 511 fhex, 512 peer, 513 shex); 514 failures++; 515 } 516 else if (! cookie_survives (fa, 517 fa_len, 518 sa, 519 sa_len)) 520 { 521 fprintf (stderr, 522 "FAIL: cookie issued via X-Forwarded-For `%s' rejected for %s\n", 523 xff, 524 peer); 525 failures++; 526 } 527 else 528 { 529 fprintf (stderr, 530 " ok: `%s' == socket %s == %s\n", 531 xff, 532 peer, 533 fhex); 534 } 535 GNUNET_free (fa); 536 GNUNET_free (sa); 537 } 538 539 540 /** 541 * Check that @a xff names no address: it is not a bare IP address, so 542 * there is no canonical form for it and it must not become an 543 * identity. With no socket address to fall back on either, nothing 544 * comes out at all. 545 * 546 * @param xff `X-Forwarded-For` value to reject 547 */ 548 static void 549 refused (const char *xff) 550 { 551 void *ca; 552 size_t ca_len; 553 554 if (resolve_xff (xff, 555 &ca, 556 &ca_len)) 557 { 558 char hex[2 * sizeof (struct in6_addr) + 1]; 559 560 tohex (ca, 561 ca_len, 562 hex); 563 fprintf (stderr, 564 "FAIL: X-Forwarded-For `%s' accepted as %s, want refusal\n", 565 xff, 566 hex); 567 failures++; 568 GNUNET_free (ca); 569 return; 570 } 571 if ( (NULL != ca) || 572 (0 != ca_len) ) 573 { 574 fprintf (stderr, 575 "FAIL: X-Forwarded-For `%s' refused but left ca=%p len=%u\n", 576 xff, 577 ca, 578 (unsigned int) ca_len); 579 failures++; 580 return; 581 } 582 fprintf (stderr, 583 " ok: `%s' refused\n", 584 xff); 585 } 586 587 588 /** 589 * Check that @a a and @a b, two spellings of one host, are one 590 * identity. 591 * 592 * @param a first spelling 593 * @param b second spelling 594 */ 595 static void 596 same_identity (const char *a, 597 const char *b) 598 { 599 void *ca; 600 void *cb; 601 size_t ca_len; 602 size_t cb_len; 603 604 /* Not GNUNET_assert: these are the code under test, not this 605 test's own inputs. A regression that makes a spelling 606 unresolvable would abort here and print one backtrace, where the 607 whole point of this file is to run every check and show the 608 shape of the damage. */ 609 if (! resolve_xff (a, 610 &ca, 611 &ca_len)) 612 { 613 fprintf (stderr, 614 "FAIL: `%s' does not resolve at all\n", 615 a); 616 failures++; 617 return; 618 } 619 if (! resolve_xff (b, 620 &cb, 621 &cb_len)) 622 { 623 fprintf (stderr, 624 "FAIL: `%s' does not resolve at all\n", 625 b); 626 failures++; 627 GNUNET_free (ca); 628 return; 629 } 630 if ( (ca_len != cb_len) || 631 (0 != memcmp (ca, 632 cb, 633 ca_len)) || 634 (! cookie_survives (ca, 635 ca_len, 636 cb, 637 cb_len)) ) 638 { 639 fprintf (stderr, 640 "FAIL: `%s' and `%s' are one host but not one identity\n", 641 a, 642 b); 643 failures++; 644 } 645 else 646 { 647 fprintf (stderr, 648 " ok: `%s' == `%s'\n", 649 a, 650 b); 651 } 652 GNUNET_free (ca); 653 GNUNET_free (cb); 654 } 655 656 657 /** 658 * Check that @a addr is (or is not) inside the installed policy. 659 * 660 * @param addr address in presentation form 661 * @param want expected verdict 662 */ 663 static void 664 trusted_is (const char *addr, 665 bool want) 666 { 667 void *ca; 668 size_t ca_len; 669 bool got; 670 671 socket_address (addr, 672 &ca, 673 &ca_len); 674 got = PAIVANA_HTTPD_is_trusted_proxy (ca, 675 ca_len); 676 if (got != want) 677 { 678 fprintf (stderr, 679 "FAIL: %s trusted=%s, want %s\n", 680 addr, 681 got ? "true" : "false", 682 want ? "true" : "false"); 683 failures++; 684 } 685 else 686 { 687 fprintf (stderr, 688 " ok: %s trusted=%s\n", 689 addr, 690 got ? "true" : "false"); 691 } 692 GNUNET_free (ca); 693 } 694 695 696 /** 697 * Check that parameter @a name of @a fwd reads as @a want (NULL for 698 * "not present, or not usable"). 699 * 700 * @param fwd `Forwarded` value 701 * @param name parameter to read 702 * @param want expected value, or NULL 703 */ 704 static void 705 fwd_param_is (const char *fwd, 706 const char *name, 707 const char *want) 708 { 709 char *got; 710 711 got = PAIVANA_HTTPD_forwarded_param (fwd, 712 name); 713 if ( ( (NULL == got) != (NULL == want) ) || 714 ( (NULL != got) && 715 (0 != strcmp (got, 716 want)) ) ) 717 { 718 fprintf (stderr, 719 "FAIL: `%s' %s=%s, want %s\n", 720 fwd, 721 name, 722 (NULL != got) ? got : "(none)", 723 (NULL != want) ? want : "(none)"); 724 failures++; 725 } 726 else 727 { 728 fprintf (stderr, 729 " ok: `%s' %s=%s\n", 730 fwd, 731 name, 732 (NULL != got) ? got : "(none)"); 733 } 734 GNUNET_free (got); 735 } 736 737 738 /** 739 * Check that @a v renders as the RFC 7239 §4 value @a want (NULL if 740 * it cannot be rendered at all). 741 * 742 * @param v value to render 743 * @param want expected rendering, or NULL 744 */ 745 static void 746 value_is (const char *v, 747 const char *want) 748 { 749 char *got; 750 751 got = PAIVANA_HTTPD_forwarded_value (v); 752 if ( ( (NULL == got) != (NULL == want) ) || 753 ( (NULL != got) && 754 (0 != strcmp (got, 755 want)) ) ) 756 { 757 fprintf (stderr, 758 "FAIL: value of `%s' is `%s', want `%s'\n", 759 v, 760 (NULL != got) ? got : "(none)", 761 (NULL != want) ? want : "(none)"); 762 failures++; 763 } 764 else 765 { 766 fprintf (stderr, 767 " ok: value of `%s' is `%s'\n", 768 v, 769 (NULL != got) ? got : "(none)"); 770 } 771 GNUNET_free (got); 772 } 773 774 775 /** 776 * Check that @a literal renders as the RFC 7239 node identifier 777 * @a want. 778 * 779 * @param literal address in presentation form, or NULL for a peer 780 * that has none 781 * @param want expected identifier 782 */ 783 static void 784 node_is (const char *literal, 785 const char *want) 786 { 787 void *ca = NULL; 788 size_t ca_len = 0; 789 char *got; 790 791 if (NULL != literal) 792 socket_address (literal, 793 &ca, 794 &ca_len); 795 got = PAIVANA_HTTPD_forwarded_node (ca, 796 ca_len); 797 if (0 != strcmp (got, 798 want)) 799 { 800 fprintf (stderr, 801 "FAIL: node for %s is `%s', want `%s'\n", 802 (NULL != literal) ? literal : "(no address)", 803 got, 804 want); 805 failures++; 806 } 807 else 808 { 809 fprintf (stderr, 810 " ok: node for %s is `%s'\n", 811 (NULL != literal) ? literal : "(no address)", 812 got); 813 } 814 GNUNET_free (got); 815 GNUNET_free (ca); 816 } 817 818 819 /** 820 * Check that @a fwd renders as the X-Forwarded-For chain @a want 821 * (NULL if it cannot be represented). 822 * 823 * @param fwd `Forwarded` value 824 * @param want expected chain, or NULL 825 */ 826 static void 827 chain_is (const char *fwd, 828 const char *want) 829 { 830 char *got; 831 832 got = PAIVANA_HTTPD_forwarded_for_chain (fwd); 833 if ( ( (NULL == got) != (NULL == want) ) || 834 ( (NULL != got) && 835 (0 != strcmp (got, 836 want)) ) ) 837 { 838 fprintf (stderr, 839 "FAIL: chain of `%s' is `%s', want `%s'\n", 840 fwd, 841 (NULL != got) ? got : "(none)", 842 (NULL != want) ? want : "(none)"); 843 failures++; 844 } 845 else 846 { 847 fprintf (stderr, 848 " ok: chain of `%s' is `%s'\n", 849 fwd, 850 (NULL != got) ? got : "(none)"); 851 } 852 GNUNET_free (got); 853 } 854 855 856 /** 857 * Which hop the walk stops at, in every shape of chain we can be 858 * handed. The socket peer is trusted implicitly whenever -f is set -- 859 * that is what the flag means -- and TRUSTED_PROXIES names the 860 * additional hops further out that may speak for their predecessor. 861 */ 862 static void 863 test_walk (void) 864 { 865 static const struct Case tab[] = { 866 { 867 .name = "without -f the socket peer wins, headers or not", 868 .peer = "203.0.113.7", 869 .respect = false, 870 .fwd = { "for=1.2.3.4;proto=https;host=evil.example.com" }, 871 .xff = { "5.6.7.8" }, 872 .xfp = { "https" }, 873 .xfh = { "evil.example.com" }, 874 .want_ca = "203.0.113.7" 875 }, 876 { 877 .name = "without -f not even a truthful header is read", 878 .peer = "198.51.100.9", 879 .respect = false, 880 .xff = { "203.0.113.7" }, 881 .want_ca = "198.51.100.9" 882 }, 883 { 884 .name = "-f, one proxy (the peer), one element: the client", 885 .peer = "10.0.0.1", 886 .respect = true, 887 .xff = { "203.0.113.7" }, 888 .want_ca = "203.0.113.7" 889 }, 890 { 891 .name = "-f, one element, Forwarded spelling", 892 .peer = "10.0.0.1", 893 .respect = true, 894 .fwd = { "for=203.0.113.7" }, 895 .want_ca = "203.0.113.7" 896 }, 897 { 898 .name = "-f, no TRUSTED_PROXIES: only the peer's own report" 899 " counts, so a client cannot prepend itself an identity", 900 .peer = "10.0.0.1", 901 .respect = true, 902 .xff = { "1.2.3.4, 203.0.113.7" }, 903 .want_ca = "203.0.113.7" 904 }, 905 { 906 .name = "-f, two proxies, the outer one trusted: the walk steps" 907 " over it and stops at the client", 908 .peer = "10.0.0.1", 909 .v4 = "10.0.0.0/8;", 910 .respect = true, 911 .xff = { "203.0.113.7, 10.0.0.2" }, 912 .want_ca = "203.0.113.7" 913 }, 914 { 915 .name = "-f, three proxies, all trusted", 916 .peer = "10.0.0.1", 917 .v4 = "10.0.0.0/8;", 918 .respect = true, 919 .xff = { "203.0.113.7, 10.0.0.2, 10.0.0.3" }, 920 .want_ca = "203.0.113.7" 921 }, 922 { 923 .name = "-f, untrusted node in the middle: the walk stops there", 924 .peer = "10.0.0.1", 925 .v4 = "10.0.0.0/8;", 926 .respect = true, 927 .xff = { "1.2.3.4, 203.0.113.7, 10.0.0.2" }, 928 .want_ca = "203.0.113.7" 929 }, 930 { 931 .name = "-f, the peer is trusted implicitly even where" 932 " TRUSTED_PROXIES does not cover it", 933 .peer = "192.0.2.5", 934 .v4 = "10.0.0.0/8;", 935 .respect = true, 936 .xff = { "203.0.113.7" }, 937 .want_ca = "203.0.113.7" 938 }, 939 { 940 .name = "-f, every element is one of ours: the leftmost is all" 941 " we have", 942 .peer = "10.0.0.1", 943 .v4 = "10.0.0.0/8;", 944 .respect = true, 945 .xff = { "10.0.0.9, 10.0.0.2" }, 946 .want_ca = "10.0.0.9" 947 }, 948 { 949 .name = "-f, IPv6 client, bracketed node", 950 .peer = "10.0.0.1", 951 .respect = true, 952 .fwd = { "for=\"[2001:db8::1]\"" }, 953 .want_ca = "2001:db8::1" 954 }, 955 { 956 .name = "-f, IPv6 client with a port: the port is not part of" 957 " the identity", 958 .peer = "10.0.0.1", 959 .respect = true, 960 .fwd = { "for=\"[2001:0db8:0000:0000:0000:0000:0000:0001]:47011\"" }, 961 .want_ca = "2001:db8::1" 962 }, 963 { 964 .name = "-f, IPv4 node with a port", 965 .peer = "10.0.0.1", 966 .respect = true, 967 .fwd = { "for=\"203.0.113.7:8080\"" }, 968 .want_ca = "203.0.113.7" 969 }, 970 { 971 .name = "-f, IPv6 trusted proxy, IPv6 policy", 972 .peer = "2001:db8::2", 973 .v6 = "2001:db8::/32;", 974 .respect = true, 975 .fwd = { "for=\"[2001:db9::1]\", for=\"[2001:db8::3]\"" }, 976 .want_ca = "2001:db9::1" 977 }, 978 { 979 .name = "-f, for=unknown (RFC 7239 §6.3): back to the peer", 980 .peer = "10.0.0.1", 981 .respect = true, 982 .fwd = { "for=unknown" }, 983 .want_ca = "10.0.0.1" 984 }, 985 { 986 .name = "-f, obfuscated node: back to the peer", 987 .peer = "10.0.0.1", 988 .respect = true, 989 .fwd = { "for=_hidden" }, 990 .want_ca = "10.0.0.1" 991 }, 992 { 993 .name = "-f, unknown to the left of a trusted proxy stops the" 994 " walk without losing the peer", 995 .peer = "10.0.0.1", 996 .v4 = "10.0.0.0/8;", 997 .respect = true, 998 .fwd = { "for=203.0.113.7, for=unknown, for=10.0.0.2" }, 999 .want_ca = "10.0.0.1" 1000 }, 1001 { 1002 .name = "-f, element without a for= at all", 1003 .peer = "10.0.0.1", 1004 .respect = true, 1005 .fwd = { "proto=https;host=e.com" }, 1006 .want_ca = "10.0.0.1", 1007 .want_proto = "https", 1008 .want_host = "e.com" 1009 }, 1010 /* RFC 9110 §5.3: repeated field lines of a list-based field are 1011 one list. A proxy adding a line of its own instead of extending 1012 the last one is explicitly permitted by RFC 7239 §4, and reading 1013 only the first would let the client's element outrank it. */ 1014 { 1015 .name = "-f, two Forwarded field lines: both are the header", 1016 .peer = "10.0.0.1", 1017 .respect = true, 1018 .fwd = { "for=1.2.3.4", "for=203.0.113.7" }, 1019 .want_ca = "203.0.113.7" 1020 }, 1021 { 1022 .name = "-f, three X-Forwarded-For field lines", 1023 .peer = "10.0.0.1", 1024 .v4 = "10.0.0.0/8;", 1025 .respect = true, 1026 .xff = { "1.2.3.4", "203.0.113.7", "10.0.0.2" }, 1027 .want_ca = "203.0.113.7" 1028 }, 1029 { 1030 .name = "-f, a field line that is itself a list", 1031 .peer = "10.0.0.1", 1032 .v4 = "10.0.0.0/8;", 1033 .respect = true, 1034 .fwd = { "for=1.2.3.4, for=203.0.113.7", "for=10.0.0.2" }, 1035 .want_ca = "203.0.113.7" 1036 }, 1037 { 1038 .name = "-f, Forwarded wins where both headers are present", 1039 .peer = "10.0.0.1", 1040 .respect = true, 1041 .fwd = { "for=203.0.113.7" }, 1042 .xff = { "1.2.3.4" }, 1043 .want_ca = "203.0.113.7" 1044 }, 1045 { 1046 .name = "-f, X-Forwarded-For is read when Forwarded is absent", 1047 .peer = "10.0.0.1", 1048 .respect = true, 1049 .xff = { "203.0.113.7" }, 1050 .want_ca = "203.0.113.7" 1051 }, 1052 /* A quoted-string is data: a ',' or ';' inside one does not end an 1053 element or a parameter (RFC 7239 §4 via RFC 9110 §5.6.4). */ 1054 { 1055 .name = "-f, comma inside a quoted string is not a delimiter", 1056 .peer = "10.0.0.1", 1057 .respect = true, 1058 .fwd = { "host=\"a,b\";for=203.0.113.7" }, 1059 .want_ca = "203.0.113.7" 1060 }, 1061 { 1062 .name = "-f, quoted-pair escapes are unescaped, not counted", 1063 .peer = "10.0.0.1", 1064 .respect = true, 1065 .fwd = { "by=\"x\\\"y;for=1.2.3.4\";for=203.0.113.7" }, 1066 .want_ca = "203.0.113.7" 1067 }, 1068 /* An unterminated quoted-string used to be read past the end of 1069 the header. It is malformed, and malformed means the peer. */ 1070 { 1071 .name = "-f, unterminated quoted string: malformed, back to the" 1072 " peer", 1073 .peer = "10.0.0.1", 1074 .respect = true, 1075 .fwd = { "for=\"unterminated" }, 1076 .want_ca = "10.0.0.1" 1077 }, 1078 { 1079 .name = "-f, unterminated quoted string in a later element", 1080 .peer = "10.0.0.1", 1081 .respect = true, 1082 .fwd = { "for=203.0.113.7, for=\"1.2.3.4" }, 1083 .want_ca = "10.0.0.1" 1084 }, 1085 { 1086 .name = "-f, backslash at the very end of the header", 1087 .peer = "10.0.0.1", 1088 .respect = true, 1089 .fwd = { "for=\"1.2.3.4\\" }, 1090 .want_ca = "10.0.0.1" 1091 }, 1092 { 1093 .name = "-f, empty Forwarded field line: back to the peer", 1094 .peer = "10.0.0.1", 1095 .respect = true, 1096 .fwd = { "" }, 1097 .want_ca = "10.0.0.1" 1098 }, 1099 { 1100 .name = "-f, whitespace-only field line", 1101 .peer = "10.0.0.1", 1102 .respect = true, 1103 .fwd = { " \t " }, 1104 .want_ca = "10.0.0.1" 1105 }, 1106 { 1107 .name = "-f, whitespace-only X-Forwarded-For", 1108 .peer = "10.0.0.1", 1109 .respect = true, 1110 .xff = { " " }, 1111 .want_ca = "10.0.0.1" 1112 }, 1113 { 1114 .name = "-f, a parameter without a value", 1115 .peer = "10.0.0.1", 1116 .respect = true, 1117 .fwd = { "for=" }, 1118 .want_ca = "10.0.0.1" 1119 }, 1120 { 1121 .name = "-f, a duplicate parameter (RFC 7239 §4 forbids it)", 1122 .peer = "10.0.0.1", 1123 .respect = true, 1124 .fwd = { "for=203.0.113.7;for=1.2.3.4" }, 1125 .want_ca = "10.0.0.1" 1126 }, 1127 { 1128 .name = "-f, garbage where a forwarded-pair should be", 1129 .peer = "10.0.0.1", 1130 .respect = true, 1131 .fwd = { "for=1.2.3.4 nonsense" }, 1132 .want_ca = "10.0.0.1" 1133 }, 1134 { 1135 .name = "-f, X-Forwarded-For element that is not an address", 1136 .peer = "10.0.0.1", 1137 .respect = true, 1138 .xff = { "garbage" }, 1139 .want_ca = "10.0.0.1" 1140 }, 1141 { 1142 .name = "-f over a Unix socket: the header is all there is", 1143 .peer = NULL, 1144 .respect = true, 1145 .fwd = { "for=203.0.113.7" }, 1146 .want_ca = "203.0.113.7" 1147 }, 1148 { 1149 .name = "-f over a Unix socket, unusable header: nothing at all", 1150 .peer = NULL, 1151 .respect = true, 1152 .fwd = { "for=unknown" }, 1153 .want_ca = NULL 1154 }, 1155 { 1156 .name = "a dual-stack listener folds ::ffff:a.b.c.d to IPv4", 1157 .peer = "::ffff:203.0.113.7", 1158 .respect = false, 1159 .want_ca = "203.0.113.7" 1160 }, 1161 { .name = NULL } 1162 }; 1163 1164 run_cases (tab); 1165 } 1166 1167 1168 /** 1169 * Where the scheme and authority of the base URL come from. They are 1170 * decided by the same walk as the address, so that the string the 1171 * cookie is keyed on and the address it is keyed on were reported by 1172 * the same hop. 1173 */ 1174 static void 1175 test_base (void) 1176 { 1177 static const struct Case tab[] = { 1178 { 1179 .name = "Forwarded proto=/host= are read, not only the" 1180 " X-Forwarded-* spellings", 1181 .peer = "10.0.0.1", 1182 .respect = true, 1183 .fwd = { "for=203.0.113.7;proto=https;host=example.com" }, 1184 .want_ca = "203.0.113.7", 1185 .want_proto = "https", 1186 .want_host = "example.com" 1187 }, 1188 /* The element the Apache snippet in debian/ builds: an unquoted 1189 authority (`:` is not a tchar, but that is what mod_headers 1190 emits) and an unbracketed IPv6 node. */ 1191 { 1192 .name = "the shipped Apache element, with a port in the host", 1193 .peer = "10.0.0.1", 1194 .respect = true, 1195 .fwd = { "for=203.0.113.7;proto=https;host=example.com:8443" }, 1196 .want_ca = "203.0.113.7", 1197 .want_proto = "https", 1198 .want_host = "example.com:8443" 1199 }, 1200 { 1201 .name = "the shipped Apache element, IPv6 client, unbracketed", 1202 .peer = "10.0.0.1", 1203 .respect = true, 1204 .fwd = { "for=2001:db8::1;proto=https;host=example.com" }, 1205 .want_ca = "2001:db8::1", 1206 .want_proto = "https", 1207 .want_host = "example.com" 1208 }, 1209 { 1210 .name = "proto= and host= come from the element the address" 1211 " came from", 1212 .peer = "10.0.0.1", 1213 .v4 = "10.0.0.0/8;", 1214 .respect = true, 1215 .fwd = { "for=203.0.113.7;proto=https;host=example.com," 1216 " for=10.0.0.2;proto=http;host=inner.example.net" }, 1217 .want_ca = "203.0.113.7", 1218 .want_proto = "https", 1219 .want_host = "example.com" 1220 }, 1221 { 1222 .name = "X-Forwarded-Proto/-Host fill in where Forwarded is" 1223 " silent", 1224 .peer = "10.0.0.1", 1225 .respect = true, 1226 .fwd = { "for=203.0.113.7" }, 1227 .xfp = { "https" }, 1228 .xfh = { "example.com" }, 1229 .want_ca = "203.0.113.7", 1230 .want_proto = "https", 1231 .want_host = "example.com" 1232 }, 1233 { 1234 .name = "without -f neither is believed", 1235 .peer = "10.0.0.1", 1236 .respect = false, 1237 .xfp = { "https" }, 1238 .xfh = { "evil.example.com" }, 1239 .want_ca = "10.0.0.1" 1240 }, 1241 { 1242 .name = "X-Forwarded-Port is appended when the host has none", 1243 .peer = "10.0.0.1", 1244 .respect = true, 1245 .xff = { "203.0.113.7" }, 1246 .xfp = { "https" }, 1247 .xfh = { "example.com" }, 1248 .xfport = { "8443" }, 1249 .want_ca = "203.0.113.7", 1250 .want_proto = "https", 1251 .want_host = "example.com:8443" 1252 }, 1253 { 1254 .name = "X-Forwarded-Host with a port plus X-Forwarded-Port does" 1255 " not yield example.com:8443:8443", 1256 .peer = "10.0.0.1", 1257 .respect = true, 1258 .xff = { "203.0.113.7" }, 1259 .xfp = { "https" }, 1260 .xfh = { "example.com:8443" }, 1261 .xfport = { "8443" }, 1262 .want_ca = "203.0.113.7", 1263 .want_proto = "https", 1264 .want_host = "example.com:8443" 1265 }, 1266 { 1267 .name = "the default port for the scheme is left off", 1268 .peer = "10.0.0.1", 1269 .respect = true, 1270 .xff = { "203.0.113.7" }, 1271 .xfp = { "https" }, 1272 .xfh = { "example.com" }, 1273 .xfport = { "443" }, 1274 .want_ca = "203.0.113.7", 1275 .want_proto = "https", 1276 .want_host = "example.com" 1277 }, 1278 { 1279 .name = "a port is re-rendered, not echoed: 0443 is 443", 1280 .peer = "10.0.0.1", 1281 .respect = true, 1282 .xff = { "203.0.113.7" }, 1283 .xfp = { "https" }, 1284 .xfh = { "example.com" }, 1285 .xfport = { "0443" }, 1286 .want_ca = "203.0.113.7", 1287 .want_proto = "https", 1288 .want_host = "example.com" 1289 }, 1290 { 1291 .name = "a port that is not a number is ignored", 1292 .peer = "10.0.0.1", 1293 .respect = true, 1294 .xff = { "203.0.113.7" }, 1295 .xfh = { "example.com" }, 1296 .xfport = { "+80" }, 1297 .want_ca = "203.0.113.7", 1298 .want_host = "example.com" 1299 }, 1300 { 1301 .name = "an out-of-range port is ignored", 1302 .peer = "10.0.0.1", 1303 .respect = true, 1304 .xff = { "203.0.113.7" }, 1305 .xfh = { "example.com" }, 1306 .xfport = { "70000" }, 1307 .want_ca = "203.0.113.7", 1308 .want_host = "example.com" 1309 }, 1310 { 1311 .name = "an IPv6 X-Forwarded-Host keeps its brackets and takes" 1312 " the port outside them", 1313 .peer = "10.0.0.1", 1314 .respect = true, 1315 .xff = { "203.0.113.7" }, 1316 .xfh = { "[2001:db8::1]" }, 1317 .xfport = { "8080" }, 1318 .want_ca = "203.0.113.7", 1319 .want_host = "[2001:db8::1]:8080" 1320 }, 1321 { 1322 .name = "a host that is not an authority is refused", 1323 .peer = "10.0.0.1", 1324 .respect = true, 1325 .fwd = { "for=203.0.113.7;host=\"a, for=1.2.3.4\"" }, 1326 .want_ca = "203.0.113.7" 1327 }, 1328 { 1329 .name = "a host with a path is refused", 1330 .peer = "10.0.0.1", 1331 .respect = true, 1332 .xff = { "203.0.113.7" }, 1333 .xfh = { "example.com/evil" }, 1334 .want_ca = "203.0.113.7" 1335 }, 1336 { 1337 .name = "a scheme we cannot serve is refused", 1338 .peer = "10.0.0.1", 1339 .respect = true, 1340 .fwd = { "for=203.0.113.7;proto=javascript" }, 1341 .want_ca = "203.0.113.7" 1342 }, 1343 { 1344 .name = "the leftmost element of a listed X-Forwarded-Proto is" 1345 " the client's", 1346 .peer = "10.0.0.1", 1347 .respect = true, 1348 .xff = { "203.0.113.7" }, 1349 .xfp = { "https, http" }, 1350 .want_ca = "203.0.113.7", 1351 .want_proto = "https" 1352 }, 1353 { .name = NULL } 1354 }; 1355 1356 run_cases (tab); 1357 } 1358 1359 1360 /** 1361 * A chain long enough to hurt. Every element used to be found by 1362 * rescanning the header from byte 0, so a client could buy Θ(n·len) of 1363 * the daemon's only thread for the price of one request. The walk is 1364 * a single pass now, and declines outright to look at more elements 1365 * than any real deployment has. 1366 */ 1367 static void 1368 test_long_chain (void) 1369 { 1370 struct PAIVANA_HTTPD_Forwarding fi = { 0 }; 1371 struct PAIVANA_HTTPD_Client cl; 1372 struct GNUNET_Buffer buf = { 0 }; 1373 struct GNUNET_TIME_Absolute start; 1374 struct GNUNET_TIME_Relative dur; 1375 void *peer; 1376 size_t peer_len; 1377 char *big; 1378 const char *lines[2] = { 1379 NULL, 1380 NULL 1381 }; 1382 unsigned int rounds = 200; 1383 1384 socket_address ("10.0.0.1", 1385 &peer, 1386 &peer_len); 1387 set_policy ("10.0.0.0/8;", 1388 NULL); 1389 PH_respect_forwarded_headers = 1; 1390 fi.peer = peer; 1391 fi.peer_len = peer_len; 1392 fi.respect_forwarded = true; 1393 1394 /* Exactly the cap: still a chain, still walked. */ 1395 for (unsigned int i = 0; 1396 i < PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS; 1397 i++) 1398 { 1399 char elem[64]; 1400 1401 if (0 != i) 1402 GNUNET_buffer_write_str (&buf, 1403 ", "); 1404 GNUNET_snprintf (elem, 1405 sizeof (elem), 1406 "for=10.0.0.%u", 1407 1 + i); 1408 GNUNET_buffer_write_str (&buf, 1409 elem); 1410 } 1411 big = GNUNET_buffer_reap_str (&buf); 1412 lines[0] = big; 1413 fi.forwarded = lines; 1414 (void) PAIVANA_HTTPD_resolve_forwarding (&fi, 1415 &cl); 1416 if ( (sizeof (struct in_addr) != cl.ca_len) || 1417 (0 != memcmp (cl.ca, 1418 "\x0a\x00\x00\x01", 1419 4)) ) 1420 { 1421 fprintf (stderr, 1422 "FAIL: a chain of exactly %u trusted hops should resolve to" 1423 " its leftmost element\n", 1424 (unsigned int) PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS); 1425 failures++; 1426 } 1427 else 1428 { 1429 fprintf (stderr, 1430 " ok: a chain of exactly %u elements is walked\n", 1431 (unsigned int) PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS); 1432 } 1433 PAIVANA_HTTPD_client_clear (&cl); 1434 GNUNET_free (big); 1435 1436 /* Everything an MHD connection pool can hold. The answer is the 1437 peer -- we decline to look -- and saying so has to stay linear in 1438 the length of the header. */ 1439 for (unsigned int i = 0; i < 2500; i++) 1440 { 1441 if (0 != i) 1442 GNUNET_buffer_write_str (&buf, 1443 ", "); 1444 GNUNET_buffer_write_str (&buf, 1445 "for=10.0.0.1"); 1446 } 1447 big = GNUNET_buffer_reap_str (&buf); 1448 lines[0] = big; 1449 fi.forwarded = lines; 1450 start = GNUNET_TIME_absolute_get (); 1451 for (unsigned int r = 0; r < rounds; r++) 1452 { 1453 (void) PAIVANA_HTTPD_resolve_forwarding (&fi, 1454 &cl); 1455 if ( (peer_len != cl.ca_len) || 1456 (NULL == cl.ca) || 1457 (0 != memcmp (cl.ca, 1458 peer, 1459 peer_len)) ) 1460 { 1461 fprintf (stderr, 1462 "FAIL: an over-long chain must fall back to the socket" 1463 " peer\n"); 1464 failures++; 1465 PAIVANA_HTTPD_client_clear (&cl); 1466 break; 1467 } 1468 PAIVANA_HTTPD_client_clear (&cl); 1469 } 1470 dur = GNUNET_TIME_absolute_get_duration (start); 1471 fprintf (stderr, 1472 " ok: 2500 elements over %u bytes, %u times, refused in %s\n", 1473 (unsigned int) strlen (big), 1474 rounds, 1475 GNUNET_STRINGS_relative_time_to_string (dur, 1476 GNUNET_YES)); 1477 GNUNET_free (big); 1478 GNUNET_free (peer); 1479 set_policy (NULL, 1480 NULL); 1481 } 1482 1483 1484 /** 1485 * Pin the behaviour of the GNUnet policy parsers that 1486 * load_trusted_proxies() has to compensate for. These are not our 1487 * functions, and their edge cases are what the configuration loader 1488 * rejects on the operator's behalf; if upstream ever changes them, 1489 * this is where it shows up. 1490 */ 1491 static void 1492 test_policy_parser (void) 1493 { 1494 struct GNUNET_STRINGS_IPv4NetworkPolicy *p4; 1495 struct GNUNET_STRINGS_IPv6NetworkPolicy *p6; 1496 unsigned int n; 1497 1498 /* The trailing ';' is a terminator, not a separator: without it 1499 nothing parses at all. */ 1500 p4 = GNUNET_STRINGS_parse_ipv4_policy ("10.0.0.0/8"); 1501 if (NULL != p4) 1502 { 1503 fprintf (stderr, 1504 "FAIL: unterminated IPv4 policy accepted\n"); 1505 failures++; 1506 GNUNET_free (p4); 1507 } 1508 else 1509 { 1510 fprintf (stderr, 1511 " ok: unterminated IPv4 policy refused\n"); 1512 } 1513 p6 = GNUNET_STRINGS_parse_ipv6_policy ("2001:db8::/32"); 1514 if (NULL != p6) 1515 { 1516 fprintf (stderr, 1517 "FAIL: unterminated IPv6 policy accepted\n"); 1518 failures++; 1519 GNUNET_free (p6); 1520 } 1521 else 1522 { 1523 fprintf (stderr, 1524 " ok: unterminated IPv6 policy refused\n"); 1525 } 1526 /* A missing terminator on the LAST entry drops only that entry, and 1527 the parser reports success for the prefix it did understand -- 1528 which is why load_trusted_proxies() counts the ';' itself. */ 1529 p4 = GNUNET_STRINGS_parse_ipv4_policy ("10.0.0.0/8;192.168.0.0/16"); 1530 n = 0; 1531 if (NULL != p4) 1532 while (0 != p4[n].network.s_addr) 1533 n++; 1534 if ( (NULL == p4) || 1535 (1 != n) ) 1536 { 1537 fprintf (stderr, 1538 "FAIL: a partially terminated IPv4 policy no longer parses" 1539 " to its first entry -- load_trusted_proxies() can stop" 1540 " counting separators\n"); 1541 failures++; 1542 } 1543 else 1544 { 1545 fprintf (stderr, 1546 " ok: `10.0.0.0/8;192.168.0.0/16' silently yields 1 of 2" 1547 " entries (rejected at startup)\n"); 1548 } 1549 GNUNET_free (p4); 1550 /* Likewise trailing garbage after the last ';'. */ 1551 p4 = GNUNET_STRINGS_parse_ipv4_policy ("10.0.0.0/8;garbage"); 1552 n = 0; 1553 if (NULL != p4) 1554 while (0 != p4[n].network.s_addr) 1555 n++; 1556 if ( (NULL == p4) || 1557 (1 != n) ) 1558 { 1559 fprintf (stderr, 1560 "FAIL: trailing garbage in an IPv4 policy no longer slips" 1561 " through the parser\n"); 1562 failures++; 1563 } 1564 else 1565 { 1566 fprintf (stderr, 1567 " ok: trailing garbage is silently dropped (rejected at" 1568 " startup)\n"); 1569 } 1570 GNUNET_free (p4); 1571 /* The list is terminated by an all-zero entry, so a /0 network is 1572 indistinguishable from the end of it: "trust everyone" parses to 1573 "trust no one", taking any later entries with it. */ 1574 p4 = GNUNET_STRINGS_parse_ipv4_policy ("0.0.0.0/0;10.0.0.0/8;"); 1575 if ( (NULL == p4) || 1576 (0 != p4[0].network.s_addr) ) 1577 { 1578 fprintf (stderr, 1579 "FAIL: 0.0.0.0/0 no longer swallows the IPv4 list --" 1580 " load_trusted_proxies() can stop apologising for it\n"); 1581 failures++; 1582 } 1583 else 1584 { 1585 fprintf (stderr, 1586 " ok: 0.0.0.0/0 yields an empty list (rejected at startup)\n"); 1587 } 1588 GNUNET_free (p4); 1589 p6 = GNUNET_STRINGS_parse_ipv6_policy ("::/0;2001:db8::/32;"); 1590 if ( (NULL == p6) || 1591 (! GNUNET_is_zero (&p6[0].network)) ) 1592 { 1593 fprintf (stderr, 1594 "FAIL: ::/0 no longer swallows the IPv6 list\n"); 1595 failures++; 1596 } 1597 else 1598 { 1599 fprintf (stderr, 1600 " ok: ::/0 yields an empty list (rejected at startup)\n"); 1601 } 1602 GNUNET_free (p6); 1603 /* An IPv6 address in the IPv4 key returns a valid pointer to an 1604 empty list rather than an error, which is why the loader counts 1605 entries instead of just checking for NULL. */ 1606 p4 = GNUNET_STRINGS_parse_ipv4_policy ("::1;"); 1607 if ( (NULL == p4) || 1608 (0 != p4[0].network.s_addr) ) 1609 { 1610 fprintf (stderr, 1611 "FAIL: an IPv6 address in the IPv4 key no longer parses to" 1612 " an empty list\n"); 1613 failures++; 1614 } 1615 else 1616 { 1617 fprintf (stderr, 1618 " ok: IPv6 in the IPv4 key yields an empty list\n"); 1619 } 1620 GNUNET_free (p4); 1621 /* Whitespace between entries: tolerated by the v4 parser, fatal to 1622 the v6 one. Documented in the loader's error message. */ 1623 p4 = GNUNET_STRINGS_parse_ipv4_policy ("10.0.0.0/8; 192.168.0.0/16;"); 1624 if (NULL == p4) 1625 { 1626 fprintf (stderr, 1627 "FAIL: IPv4 policy with spaces refused\n"); 1628 failures++; 1629 } 1630 else 1631 { 1632 fprintf (stderr, 1633 " ok: IPv4 policy tolerates spaces between entries\n"); 1634 } 1635 GNUNET_free (p4); 1636 p6 = GNUNET_STRINGS_parse_ipv6_policy ("2001:db8::/32; fe80::/10;"); 1637 if (NULL != p6) 1638 { 1639 fprintf (stderr, 1640 "FAIL: IPv6 policy with spaces accepted -- the loader's" 1641 " error message says otherwise\n"); 1642 failures++; 1643 GNUNET_free (p6); 1644 } 1645 else 1646 { 1647 fprintf (stderr, 1648 " ok: IPv6 policy refuses spaces between entries\n"); 1649 } 1650 /* Out-of-range prefix lengths are caught. */ 1651 p4 = GNUNET_STRINGS_parse_ipv4_policy ("10.0.0.0/33;"); 1652 if (NULL != p4) 1653 { 1654 fprintf (stderr, 1655 "FAIL: IPv4 /33 accepted\n"); 1656 failures++; 1657 GNUNET_free (p4); 1658 } 1659 else 1660 { 1661 fprintf (stderr, 1662 " ok: IPv4 /33 refused\n"); 1663 } 1664 p6 = GNUNET_STRINGS_parse_ipv6_policy ("2001:db8::/129;"); 1665 if (NULL != p6) 1666 { 1667 fprintf (stderr, 1668 "FAIL: IPv6 /129 accepted\n"); 1669 failures++; 1670 GNUNET_free (p6); 1671 } 1672 else 1673 { 1674 fprintf (stderr, 1675 " ok: IPv6 /129 refused\n"); 1676 } 1677 } 1678 1679 1680 int 1681 main (int argc, 1682 char *const *argv) 1683 { 1684 (void) argc; 1685 (void) argv; 1686 /* Quiet: the refusal cases trip GNUNET_break_op() by design. */ 1687 GNUNET_assert (GNUNET_OK == 1688 GNUNET_log_setup ("test-client-address", 1689 "ERROR", 1690 NULL)); 1691 GNUNET_CRYPTO_hash ("test-client-address", 1692 strlen ("test-client-address"), 1693 &paivana_secret); 1694 1695 fprintf (stderr, 1696 "-- a host reaching paivana through a proxy and directly --\n"); 1697 same_host ("203.0.113.7", 1698 "203.0.113.7"); 1699 same_host ("2001:db8::1", 1700 "2001:db8::1"); 1701 same_host ("::1", 1702 "::1"); 1703 same_host ("127.0.0.1", 1704 "127.0.0.1"); 1705 /* A dual-stack listener reports an IPv4 peer as ::ffff:a.b.c.d; 1706 a proxy in front of it reports the plain IPv4 address. */ 1707 same_host ("203.0.113.7", 1708 "::ffff:203.0.113.7"); 1709 same_host (" 203.0.113.7 ", 1710 "203.0.113.7"); 1711 same_host ("\t2001:db8::1\t", 1712 "2001:db8::1"); 1713 1714 fprintf (stderr, 1715 "-- one host, several spellings --\n"); 1716 same_identity ("::1", 1717 "0:0:0:0:0:0:0:1"); 1718 same_identity ("2001:db8::1", 1719 "2001:0db8:0000:0000:0000:0000:0000:0001"); 1720 same_identity ("2001:DB8::1", 1721 "2001:db8::1"); 1722 same_identity ("203.0.113.7", 1723 "::ffff:203.0.113.7"); 1724 1725 fprintf (stderr, 1726 "-- values that are not bare addresses --\n"); 1727 refused (""); 1728 refused (" "); 1729 refused (","); 1730 refused ("203.0.113.7:4711"); /* port suffix */ 1731 refused ("[2001:db8::1]"); /* bracketed */ 1732 refused ("[2001:db8::1]:443"); 1733 refused ("unknown"); /* RFC 7239 */ 1734 refused ("_hidden"); 1735 refused ("client.example.com"); /* hostname */ 1736 refused ("203.0.113.7 198.51.100.9"); /* no comma */ 1737 refused ("fe80::1%eth0"); /* zone id: RFC 6874 */ 1738 refused ("999.1.1.1"); 1739 refused ("203.0.113"); 1740 refused ("::gggg"); 1741 /* Longer than any address; must not overrun the parse buffer. */ 1742 refused ( 1743 "2001:0db8:0000:0000:0000:0000:0000:0001:0002:0003:0004:0005:0006"); 1744 1745 fprintf (stderr, 1746 "-- the walk --\n"); 1747 test_walk (); 1748 1749 fprintf (stderr, 1750 "-- the scheme and authority the base URL is built from --\n"); 1751 test_base (); 1752 1753 fprintf (stderr, 1754 "-- a chain long enough to hurt --\n"); 1755 test_long_chain (); 1756 1757 fprintf (stderr, 1758 "-- Forwarded parameters and node rendering --\n"); 1759 fwd_param_is ("for=203.0.113.7;proto=https;host=e.com", 1760 "proto", 1761 "https"); 1762 fwd_param_is ("for=203.0.113.7;proto=https;host=e.com", 1763 "host", 1764 "e.com"); 1765 fwd_param_is ("for=203.0.113.7;host=\"e.com:8443\"", 1766 "host", 1767 "e.com:8443"); 1768 /* Unquoted, as mod_headers writes it. */ 1769 fwd_param_is ("for=203.0.113.7;host=e.com:8443", 1770 "host", 1771 "e.com:8443"); 1772 fwd_param_is ("for=203.0.113.7", 1773 "proto", 1774 NULL); 1775 /* The leftmost element describes the client's own connection. */ 1776 fwd_param_is ("proto=https, proto=http", 1777 "proto", 1778 "https"); 1779 /* What comes back is validated, so that a caller splicing it into a 1780 header it builds cannot be made to emit a second 1781 forwarded-element on the client's behalf. */ 1782 fwd_param_is ("for=10.0.0.1;host=\"a, for=1.2.3.4\"", 1783 "host", 1784 NULL); 1785 fwd_param_is ("for=10.0.0.1;proto=\"https;by=x\"", 1786 "proto", 1787 NULL); 1788 fwd_param_is ("for=10.0.0.1;host=\"e.com/../evil\"", 1789 "host", 1790 NULL); 1791 value_is ("203.0.113.7", 1792 "203.0.113.7"); 1793 value_is ("[2001:db8::1]", 1794 "\"[2001:db8::1]\""); 1795 value_is ("e.com:8443", 1796 "\"e.com:8443\""); 1797 value_is ("a\"b", 1798 "\"a\\\"b\""); 1799 value_is ("a\\b", 1800 "\"a\\\\b\""); 1801 value_is ("a, for=1.2.3.4", 1802 "\"a, for=1.2.3.4\""); 1803 value_is ("a\nb", 1804 NULL); 1805 node_is ("203.0.113.7", 1806 "203.0.113.7"); 1807 node_is ("2001:db8::1", 1808 "\"[2001:db8::1]\""); 1809 node_is (NULL, 1810 "unknown"); 1811 chain_is ("for=203.0.113.7, for=198.51.100.9", 1812 "203.0.113.7, 198.51.100.9"); 1813 chain_is ("for=\"[2001:db8::1]:443\", for=10.0.0.1", 1814 "2001:db8::1, 10.0.0.1"); 1815 /* Not every element is an address, so no chain can be rendered. */ 1816 chain_is ("for=unknown, for=10.0.0.1", 1817 NULL); 1818 chain_is ("for=\"unterminated", 1819 NULL); 1820 1821 fprintf (stderr, 1822 "-- the GNUnet policy parsers behave as the loader assumes --\n"); 1823 test_policy_parser (); 1824 1825 fprintf (stderr, 1826 "-- matching against a trusted-proxy policy --\n"); 1827 set_policy ("10.0.0.0/8;192.168.0.0/16;", 1828 "2001:db8::/32;"); 1829 trusted_is ("10.0.0.1", true); 1830 trusted_is ("10.255.255.255", true); 1831 trusted_is ("11.0.0.1", false); 1832 trusted_is ("192.168.0.1", true); 1833 trusted_is ("192.169.0.1", false); 1834 trusted_is ("2001:db8::1", true); 1835 trusted_is ("2001:db9::1", false); 1836 trusted_is ("203.0.113.7", false); 1837 /* An IPv4-mapped address is folded to 4 bytes before it gets here, 1838 so it is matched against the IPv4 list -- which is why IPv4 1839 proxies belong in TRUSTED_PROXIES and not in TRUSTED_PROXIES6 as 1840 ::ffff:10.0.0.1. */ 1841 trusted_is ("::ffff:10.0.0.1", true); 1842 set_policy (NULL, NULL); 1843 trusted_is ("10.0.0.1", false); /* no policy: nothing is trusted */ 1844 1845 fprintf (stderr, 1846 "-- distinct hosts stay distinct --\n"); 1847 { 1848 void *a = NULL; 1849 void *b = NULL; 1850 size_t al = 0; 1851 size_t bl = 0; 1852 1853 /* Again: a resolver that stopped understanding dotted quads is a 1854 regression to report, not a reason to abort the run. */ 1855 if ( (! resolve_xff ("203.0.113.7", 1856 &a, 1857 &al)) || 1858 (! resolve_xff ("203.0.113.8", 1859 &b, 1860 &bl)) ) 1861 { 1862 fprintf (stderr, 1863 "FAIL: 203.0.113.7 / 203.0.113.8 do not resolve at all\n"); 1864 failures++; 1865 } 1866 else if (cookie_survives (a, 1867 al, 1868 b, 1869 bl)) 1870 { 1871 fprintf (stderr, 1872 "FAIL: cookie for 203.0.113.7 accepted for 203.0.113.8\n"); 1873 failures++; 1874 } 1875 else 1876 { 1877 fprintf (stderr, 1878 " ok: cookie for 203.0.113.7 rejected for 203.0.113.8\n"); 1879 } 1880 GNUNET_free (a); 1881 GNUNET_free (b); 1882 } 1883 1884 set_policy (NULL, 1885 NULL); 1886 if (0 != failures) 1887 { 1888 fprintf (stderr, 1889 "%u check(s) failed\n", 1890 failures); 1891 return 1; 1892 } 1893 fprintf (stderr, 1894 "all checks passed\n"); 1895 return 0; 1896 }