paivana

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

paivana-httpd_helper.h (11696B)


      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.h
     24  * @brief helper functions
     25  */
     26 #ifndef PAIVANA_HTTPD_HELPER_H
     27 #define PAIVANA_HTTPD_HELPER_H
     28 
     29 #include <stdbool.h>
     30 #include <microhttpd.h>
     31 #include <gnunet/gnunet_util_lib.h>
     32 
     33 
     34 /**
     35  * Longest forwarding chain we are willing to look at.
     36  *
     37  * A real chain has single digits; MHD's default connection pool would
     38  * otherwise let a client hand us some 2500 elements per request, and
     39  * anything that touches each of them is work we do on the one thread
     40  * that also drives all outbound traffic.  A longer chain is treated
     41  * as malformed, i.e. as if no forwarding header had been sent.
     42  */
     43 #define PAIVANA_HTTPD_MAX_FORWARDED_ELEMENTS 32
     44 
     45 
     46 /**
     47  * Everything about a request that the forwarding layer may look at.
     48  *
     49  * All header members are arrays of the *field line* values MHD
     50  * reported for that field name, in the order they arrived, terminated
     51  * by a NULL entry (NULL for "field not present at all").  RFC 9110
     52  * §5.3 makes repeated field lines of a list-valued field one list, so
     53  * every line has to be considered, not just the first.
     54  *
     55  * `Forwarded` and `X-Forwarded-For` say the same thing in two
     56  * grammars, so only one of them is walked: `Forwarded` wherever it is
     57  * present at all, `X-Forwarded-For` otherwise.  The two are never
     58  * merged -- a proxy that emits both and disagrees with itself would
     59  * otherwise make the answer depend on the order we happened to read
     60  * them in.
     61  */
     62 struct PAIVANA_HTTPD_Forwarding
     63 {
     64 
     65   /**
     66    * Address of the peer we accepted the connection from, in the binary
     67    * form used throughout, or NULL for a peer that has none (AF_UNIX).
     68    */
     69   const void *peer;
     70 
     71   /**
     72    * Number of bytes in @e peer.
     73    */
     74   size_t peer_len;
     75 
     76   /**
     77    * Are we behind a trusted reverse proxy, i.e. was `-f` given?  If
     78    * not, none of the header members below is looked at at all.
     79    */
     80   bool respect_forwarded;
     81 
     82   /**
     83    * Field lines of the `Forwarded` header (RFC 7239).
     84    */
     85   const char *const *forwarded;
     86 
     87   /**
     88    * Field lines of the `X-Forwarded-For` header.
     89    */
     90   const char *const *xff;
     91 
     92   /**
     93    * Field lines of the `X-Forwarded-Proto` header.
     94    */
     95   const char *const *xfp;
     96 
     97   /**
     98    * Field lines of the `X-Forwarded-Host` header.
     99    */
    100   const char *const *xfh;
    101 
    102   /**
    103    * Field lines of the `X-Forwarded-Port` header.
    104    */
    105   const char *const *xfport;
    106 };
    107 
    108 
    109 /**
    110  * What the forwarding layer concluded about the client.
    111  */
    112 struct PAIVANA_HTTPD_Client
    113 {
    114 
    115   /**
    116    * Client address in binary form: 4 bytes (`struct in_addr`) for
    117    * IPv4, 16 bytes (`struct in6_addr`) for IPv6.  NULL only if the
    118    * peer had no address and no header supplied one.
    119    */
    120   void *ca;
    121 
    122   /**
    123    * Number of bytes in @e ca.
    124    */
    125   size_t ca_len;
    126 
    127   /**
    128    * Scheme the client itself used, "http" or "https", or NULL if
    129    * nothing we trust said.
    130    */
    131   char *proto;
    132 
    133   /**
    134    * Authority the client itself addressed (host, optionally with a
    135    * port), or NULL if nothing we trust said.
    136    */
    137   char *host;
    138 };
    139 
    140 
    141 /**
    142  * Determine who the client is, and what it thinks it connected to.
    143  *
    144  * This is the one walk over the forwarding chain; both the address the
    145  * access cookie is keyed on and the base URL we reconstruct come out
    146  * of it, so that the two can never disagree about which hop was
    147  * believed.
    148  *
    149  * Given `-f` we are behind a trusted reverse proxy, so the peer we
    150  * accepted from is trusted implicitly and `TRUSTED_PROXIES` names the
    151  * *additional* hops further out that may also speak for their
    152  * predecessor.  The chain is therefore walked from the right: we step
    153  * leftwards over a node for as long as that node is one we trust, and
    154  * the first node that is not is the client.  Nothing to the left of an
    155  * untrusted node is attributable to anyone, and nothing to the left of
    156  * a node that names no address (RFC 7239 §6.3 `unknown`, an obfuscated
    157  * identifier, a hostname) can be reached at all.
    158  *
    159  * The scheme and authority are taken from the same element the address
    160  * was: that element was written by a hop we trust and describes the
    161  * connection its predecessor -- the client -- made.  Where the chain
    162  * says nothing, the de-facto `X-Forwarded-Proto` / `-Host` / `-Port`
    163  * headers are consulted instead, and they too only when `-f` says
    164  * something in front of us is entitled to have set them.
    165  *
    166  * Anything the client controls that we cannot believe falls back to
    167  * the peer: a malformed, empty or over-long chain never removes the
    168  * one address we know for certain.
    169  *
    170  * @param fi what the request carried
    171  * @param[out] cl what we concluded; to be released with
    172  *   #PAIVANA_HTTPD_client_clear() whatever the return value
    173  * @return true if a client address was determined, false only if the
    174  *   peer has no address and no header supplied one
    175  */
    176 bool
    177 PAIVANA_HTTPD_resolve_forwarding (const struct PAIVANA_HTTPD_Forwarding *fi,
    178                                   struct PAIVANA_HTTPD_Client *cl);
    179 
    180 
    181 /**
    182  * Release the members of @a cl and zero it.
    183  *
    184  * @param[in,out] cl result of #PAIVANA_HTTPD_resolve_forwarding()
    185  */
    186 void
    187 PAIVANA_HTTPD_client_clear (struct PAIVANA_HTTPD_Client *cl);
    188 
    189 
    190 /**
    191  * Obtain the client address of @a connection.
    192  *
    193  * A thin adapter over #PAIVANA_HTTPD_resolve_forwarding(): it reads
    194  * the socket peer and the forwarding field lines off @a connection and
    195  * hands them to that function, which is where the policy lives.
    196  *
    197  * The address is returned in binary form: 4 bytes (`struct in_addr`)
    198  * for IPv4, 16 bytes (`struct in6_addr`) for IPv6.  That
    199  * representation is canonical and does not depend on where the
    200  * address was taken from -- a forwarding header or the socket -- which
    201  * matters because it feeds the access-cookie MAC: two requests from
    202  * the same host have to yield the same bytes or the cookie stops
    203  * verifying.
    204  *
    205  * @param connection HTTP client connection
    206  * @param[out] ca where to write the client address
    207  * @param[out] ca_len number of bytes in @a ca
    208  * @return true on success, false if no address could be determined,
    209  *   which for a request that arrived over IP cannot happen: only a
    210  *   non-IP socket with nothing to fall back on gets here
    211  */
    212 bool
    213 PAIVANA_HTTPD_get_client_address (struct MHD_Connection *connection,
    214                                   void **ca,
    215                                   size_t *ca_len);
    216 
    217 
    218 /**
    219  * Render the `for=` chain of a `Forwarded` header value in
    220  * `X-Forwarded-For` form, so that an origin which speaks only the
    221  * de-facto header still learns the client.
    222  *
    223  * Returns NULL unless *every* element carries a bare IP address:
    224  * `X-Forwarded-For` has no way to say "this hop had no address", so an
    225  * element whose `for` is RFC 7239's `unknown` or an obfuscated
    226  * identifier cannot be represented, and omitting it would silently
    227  * shift every position to its left.
    228  *
    229  * @param fwd value of the `Forwarded` header
    230  * @return the comma-separated chain, or NULL; to be freed by the
    231  *   caller
    232  */
    233 char *
    234 PAIVANA_HTTPD_forwarded_for_chain (const char *fwd);
    235 
    236 
    237 /**
    238  * Read a parameter of the leftmost element of a `Forwarded` header
    239  * value.  Only `proto` and `host` are supported.
    240  *
    241  * The leftmost element describes the connection the client itself
    242  * made, which is what those two are being asked about.
    243  *
    244  * The result is *validated*, not merely unquoted: `proto` is one of
    245  * the two literals "http" and "https", and `host` is an RFC 3986 §3.2
    246  * authority.  Neither can therefore contain a `;`, `,`, `"` or space,
    247  * which is what makes it safe to splice into a header we build.  A
    248  * value that does not pass is reported as absent.
    249  *
    250  * Note that a `host` may still contain `:` and `[`/`]`, which are not
    251  * `tchar` (RFC 9110 §5.6.2): pass it through
    252  * #PAIVANA_HTTPD_forwarded_value() before emitting it inside a
    253  * `Forwarded` element, though it may be used as-is as the value of an
    254  * `X-Forwarded-Host`.
    255  *
    256  * @param fwd value of the `Forwarded` header
    257  * @param name parameter to look for, "proto" or "host"
    258  * @return the value, or NULL if the header does not carry a usable
    259  *   one; to be freed by the caller
    260  */
    261 char *
    262 PAIVANA_HTTPD_forwarded_param (const char *fwd,
    263                                const char *name);
    264 
    265 
    266 /**
    267  * Render @a v as an RFC 7239 §4 `value`, i.e. as a bare token where
    268  * that is legal and as an escaped quoted-string otherwise.
    269  *
    270  * Splicing a value into a `Forwarded` element without this is how a
    271  * `;` or `,` in it becomes a parameter or an element of its own, and
    272  * an origin that trusts us reads what the client wrote as something we
    273  * said.
    274  *
    275  * @param v value to render
    276  * @return the token or quoted-string, or NULL if @a v contains bytes
    277  *   that no `Forwarded` value may carry (a control character other
    278  *   than HTAB); to be freed by the caller
    279  */
    280 char *
    281 PAIVANA_HTTPD_forwarded_value (const char *v);
    282 
    283 
    284 /**
    285  * Render @a ca as an RFC 7239 `for=` node identifier, quoting and
    286  * bracketing an IPv6 address as §6 requires.
    287  *
    288  * @param ca address in binary form, or NULL for a peer that has none
    289  * @param ca_len number of bytes in @a ca
    290  * @return the identifier, e.g. "203.0.113.7", "\"[2001:db8::1]\"" or
    291  *   "unknown"; to be freed by the caller
    292  */
    293 char *
    294 PAIVANA_HTTPD_forwarded_node (const void *ca,
    295                               size_t ca_len);
    296 
    297 
    298 /**
    299  * Is @a ca the address of a reverse proxy we trust to report the
    300  * client address truthfully, i.e. one covered by `TRUSTED_PROXIES` or
    301  * `TRUSTED_PROXIES6`?
    302  *
    303  * Always false if no policy was configured: "trust nothing in
    304  * particular" and "trust everything" are deliberately different
    305  * answers, and only the former can be stated safely by default.
    306  *
    307  * @param ca address in the binary form used throughout, i.e. 4 bytes
    308  *   for IPv4 and 16 for IPv6
    309  * @param ca_len number of bytes in @a ca
    310  * @return true if @a ca is within a configured trusted network
    311  */
    312 bool
    313 PAIVANA_HTTPD_is_trusted_proxy (const void *ca,
    314                                 size_t ca_len);
    315 
    316 /**
    317  * Determine the Base URL that the client made the HTTP request to.
    318  * The URL returned will be without the trailing '/'.
    319  *
    320  * `BASE_URL` wins if it is configured.  Otherwise the scheme and
    321  * authority come from #PAIVANA_HTTPD_resolve_forwarding(), i.e. from
    322  * the same element of the forwarding chain the client address came
    323  * from, so that the string the cookie is keyed on and the address it
    324  * is keyed on were reported by the same trusted hop.  Where that says
    325  * nothing we go by the transport and the `Host` header.
    326  *
    327  * @param connection client connection used
    328  * @param[out] buf where to write the base URL; buffer will be cleared
    329  *   and must be reaped by caller.
    330  * @return false on error (no suitable HTTP headers found and nothing
    331  *   configured)
    332  */
    333 bool
    334 PAIVANA_HTTPD_get_base_url (struct MHD_Connection *connection,
    335                             struct GNUNET_Buffer *buf);
    336 
    337 
    338 #endif