paivana

HTTP paywall reverse proxy
Log | Files | Refs | Submodules | README | LICENSE

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 }