paivana

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

paivana-httpd_helper.c (45524B)


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