paivana

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

paivana-httpd_reverse.c (47480B)


      1 /*
      2   This file is part of GNU Taler
      3   Copyright (C) 2012-2014 GNUnet e.V.
      4   Copyright (C) 2018, 2025, 2026 Taler Systems SA
      5 
      6   GNU Taler is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU General Public License
      8   as published by the Free Software Foundation; either version
      9   3, or (at your option) any later version.
     10 
     11   GNU Taler is distributed in the hope that it will be useful, but
     12   WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14   GNU General Public License for more details.
     15 
     16   You should have received a copy of the GNU General Public
     17   License along with GNU Taler; see the file COPYING.  If not,
     18   write to the Free Software Foundation, Inc., 51 Franklin
     19   Street, Fifth Floor, Boston, MA 02110-1301, USA.
     20 */
     21 
     22 /**
     23  * @author Martin Schanzenbach
     24  * @author Christian Grothoff
     25  * @author Marcello Stanisci
     26  * @file src/backend/paivana-httpd_reverse.c
     27  * @brief Reverse proxy logic that just forwards the request
     28  */
     29 #include "platform.h"
     30 #include <curl/curl.h>
     31 #include <gnunet/gnunet_util_lib.h>
     32 #include <taler/taler_mhd_lib.h>
     33 #include "paivana-httpd.h"
     34 #include "paivana-httpd_reverse.h"
     35 
     36 
     37 /**
     38  * Log curl error.
     39  *
     40  * @param level log level
     41  * @param fun name of curl_easy-function that gave the error
     42  * @param rc return code from curl
     43  */
     44 #define LOG_CURL_EASY(level,fun,rc) \
     45         GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \
     46                     __LINE__, \
     47                     curl_easy_strerror (rc))
     48 
     49 
     50 /**
     51  * State machine for HTTP requests (per request).
     52  */
     53 enum RequestState
     54 {
     55   /**
     56    * We've started receiving upload data from MHD.
     57    * Initial state.
     58    */
     59   REQUEST_STATE_CLIENT_UPLOAD_STARTED,
     60 
     61   /**
     62    * Wa have started uploading data to the proxied service.
     63    */
     64   REQUEST_STATE_PROXY_UPLOAD_STARTED,
     65 
     66   /**
     67    * We're done with the upload from MHD.
     68    */
     69   REQUEST_STATE_CLIENT_UPLOAD_DONE,
     70 
     71   /**
     72    * We're done uploading data to the proxied service.
     73    */
     74   REQUEST_STATE_PROXY_UPLOAD_DONE,
     75 
     76   /**
     77    * We've finished uploading data via CURL and can now download.
     78    */
     79   REQUEST_STATE_PROXY_DOWNLOAD_STARTED,
     80 
     81   /**
     82    * We've finished receiving download data from cURL.
     83    */
     84   REQUEST_STATE_PROXY_DOWNLOAD_DONE
     85 };
     86 
     87 
     88 /**
     89  * A header list
     90  */
     91 struct HttpResponseHeader
     92 {
     93   /**
     94    * DLL
     95    */
     96   struct HttpResponseHeader *next;
     97 
     98   /**
     99    * DLL
    100    */
    101   struct HttpResponseHeader *prev;
    102 
    103   /**
    104    * Header type
    105    */
    106   char *type;
    107 
    108   /**
    109    * Header value
    110    */
    111   char *value;
    112 };
    113 
    114 
    115 /**
    116  * A structure for socks requests
    117  */
    118 struct HttpRequest
    119 {
    120 
    121   /**
    122    * Kept in DLL.
    123    */
    124   struct HttpRequest *prev;
    125 
    126   /**
    127    * Kept in DLL.
    128    */
    129   struct HttpRequest *next;
    130 
    131   /**
    132    * MHD request that triggered us.
    133    */
    134   struct MHD_Connection *con;
    135 
    136   /**
    137    * Client socket read task
    138    */
    139   struct GNUNET_SCHEDULER_Task *rtask;
    140 
    141   /**
    142    * Client socket write task
    143    */
    144   struct GNUNET_SCHEDULER_Task *wtask;
    145 
    146   /**
    147    * Hold the response obtained by modifying the original one.
    148    */
    149   struct MHD_Response *mod_response;
    150 
    151   /**
    152    * MHD response object for this request.
    153    */
    154   struct MHD_Response *response;
    155 
    156   /**
    157    * The URL to fetch
    158    */
    159   char *url;
    160 
    161   /**
    162    * Handle to cURL
    163    */
    164   CURL *curl;
    165 
    166   /**
    167    * HTTP request headers for the curl request.
    168    */
    169   struct curl_slist *headers;
    170 
    171   /**
    172    * Headers from response
    173    */
    174   struct HttpResponseHeader *header_head;
    175 
    176   /**
    177    * Headers from response
    178    */
    179   struct HttpResponseHeader *header_tail;
    180 
    181   /**
    182    * Buffer we use for moving data between MHD and
    183    * curl (in both directions).
    184    */
    185   char *io_buf;
    186 
    187   /**
    188    * Number of bytes already in the IO buffer.
    189    */
    190   size_t io_len;
    191 
    192   /**
    193    * Number of bytes allocated for the IO buffer.
    194    */
    195   unsigned int io_size;
    196 
    197   /**
    198    * HTTP response code to give to MHD for the response.
    199    */
    200   unsigned int response_code;
    201 
    202   /**
    203    * Request processing state machine.
    204    */
    205   enum RequestState state;
    206 
    207   /**
    208    * Did we suspend MHD processing?
    209    */
    210   enum GNUNET_GenericReturnValue suspended;
    211 
    212   /**
    213    * Did we pause CURL processing?
    214    */
    215   int curl_paused;
    216 
    217   /**
    218    * Have we observed the initial `HEADERS_PROCESSED` (a.k.a. "first")
    219    * access-handler call for this request yet?  MHD invokes the
    220    * handler once immediately after parsing the request headers with
    221    * `upload_data_size == 0` (and no body data yet), then again later
    222    * with body chunks (if any), and finally once more with
    223    * `upload_data_size == 0` at `FULL_REQ_RECEIVED`.  We must defer
    224    * curl setup to the "final" call so that request bodies are
    225    * available in `io_buf` by the time curl runs.
    226    */
    227   bool accepted;
    228 
    229   /**
    230    * Set when the request body exceeded `PH_request_buffer_max`.  We
    231    * must drain the rest of the upload silently and queue the 413
    232    * response on the "final" access-handler call.
    233    */
    234   bool reject_upload;
    235 
    236   /**
    237    * Concatenated value of the client's Via header(s), if any.  Per
    238    * RFC 9110 §7.6.3 a proxy must *append* its own entry to this
    239    * list, not replace it; we capture the inbound value here before
    240    * iterating over the other headers and emit our appended Via when
    241    * building the curl request.
    242    */
    243   char *client_via;
    244 
    245   /**
    246    * Concatenated value of the client's Connection header(s), if
    247    * any.  Per RFC 9110 §7.6.1 this lists additional header names
    248    * that are connection-specific and must not be forwarded; we
    249    * consult this list in `con_val_iter` to filter such headers out
    250    * of the upstream request.
    251    */
    252   char *client_connection;
    253 };
    254 
    255 
    256 /**
    257  * DLL of active HTTP requests.
    258  */
    259 static struct HttpRequest *hr_head;
    260 
    261 /**
    262  * DLL of active HTTP requests.
    263  */
    264 static struct HttpRequest *hr_tail;
    265 
    266 /**
    267  * Response we return on cURL failures.
    268  */
    269 static struct MHD_Response *curl_failure_response;
    270 
    271 /**
    272  * The cURL multi handle
    273  */
    274 static CURLM *curl_multi;
    275 
    276 /**
    277  * The cURL download task (curl multi API).
    278  */
    279 static struct GNUNET_SCHEDULER_Task *curl_download_task;
    280 
    281 
    282 bool
    283 PAIVANA_HTTPD_reverse_init (void)
    284 {
    285   static const char *failure_body =
    286     "<!DOCTYPE html>\n"
    287     "<html><head><title>Bad Gateway</title></head>"
    288     "<body><h1>502 Bad Gateway</h1>"
    289     "<p>The upstream server could not be reached.</p>"
    290     "</body></html>\n";
    291 
    292   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
    293   {
    294     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    295                 "cURL global init failed!\n");
    296     return false;
    297   }
    298   if (NULL == (curl_multi = curl_multi_init ()))
    299   {
    300     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    301                 "Failed to create cURL multi handle!\n");
    302     return false;
    303   }
    304   curl_failure_response
    305     = MHD_create_response_from_buffer_static (strlen (failure_body),
    306                                               failure_body);
    307   if (NULL == curl_failure_response)
    308   {
    309     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    310                 "Failed to create cURL failure response!\n");
    311     return false;
    312   }
    313   GNUNET_break (MHD_YES ==
    314                 MHD_add_response_header (curl_failure_response,
    315                                          MHD_HTTP_HEADER_CONTENT_TYPE,
    316                                          "text/html; charset=utf-8"));
    317   return true;
    318 }
    319 
    320 
    321 void
    322 PAIVANA_HTTPD_reverse_shutdown (void)
    323 {
    324   for (struct HttpRequest *hr = hr_head;
    325        NULL != hr;
    326        hr = hr->next)
    327   {
    328     if (GNUNET_YES == hr->suspended)
    329     {
    330       hr->suspended = GNUNET_NO;
    331       MHD_resume_connection (hr->con);
    332     }
    333   }
    334   if (NULL != curl_download_task)
    335   {
    336     GNUNET_SCHEDULER_cancel (curl_download_task);
    337     curl_download_task = NULL;
    338   }
    339   if (NULL != curl_multi)
    340   {
    341     curl_multi_cleanup (curl_multi);
    342     curl_multi = NULL;
    343   }
    344   if (NULL != curl_failure_response)
    345   {
    346     MHD_destroy_response (curl_failure_response);
    347     curl_failure_response = NULL;
    348   }
    349 }
    350 
    351 
    352 /**
    353  * Is @a name a hop-by-hop HTTP header name that a proxy must not
    354  * forward (RFC 9110 section 7.6.1 and RFC 7230, section 6.1).
    355  * The client may name *additional* hop-by-hop headers in its
    356  * Connection header; those are handled separately in
    357  * `connection_lists_header()`.
    358  *
    359  * @param name header field name
    360  * @return true if @a name is a hop-by-hop header
    361  */
    362 static bool
    363 is_hop_by_hop_header (const char *name)
    364 {
    365   static const char *const hop_headers[] = {
    366     MHD_HTTP_HEADER_CONNECTION,
    367     MHD_HTTP_HEADER_KEEP_ALIVE,
    368     MHD_HTTP_HEADER_PROXY_AUTHENTICATE,
    369     MHD_HTTP_HEADER_PROXY_AUTHORIZATION,
    370     MHD_HTTP_HEADER_TE,
    371     MHD_HTTP_HEADER_TRAILER,
    372     MHD_HTTP_HEADER_TRANSFER_ENCODING,
    373     MHD_HTTP_HEADER_UPGRADE,
    374     NULL
    375   };
    376 
    377   for (unsigned int i = 0; NULL != hop_headers[i]; i++)
    378     if (0 == strcasecmp (name,
    379                          hop_headers[i]))
    380       return true;
    381   return false;
    382 }
    383 
    384 
    385 /**
    386  * Return true if @a name appears (case-insensitively) as a
    387  * comma-separated element of @a list.  Used to honor RFC 9110
    388  * §7.6.1: the client's Connection header lists additional
    389  * hop-by-hop header names that must not be forwarded.
    390  *
    391  * @param list comma-separated list of header names, may be NULL
    392  * @param name header name to look for
    393  * @return true if @a list names @a name
    394  */
    395 static bool
    396 connection_lists_header (const char *list,
    397                          const char *name)
    398 {
    399   size_t namelen;
    400   const char *p;
    401 
    402   if (NULL == list)
    403     return false;
    404   namelen = strlen (name);
    405   p = list;
    406   while ('\0' != *p)
    407   {
    408     const char *comma;
    409     size_t len;
    410 
    411     while ( (' ' == *p) ||
    412             ('\t' == *p) ||
    413             (',' == *p) )
    414       p++;
    415     if ('\0' == *p)
    416       break;
    417     comma = strchr (p, ',');
    418     len = (NULL == comma) ? strlen (p) : (size_t) (comma - p);
    419     while ( (len > 0) &&
    420             ( (' ' == p[len - 1]) ||
    421               ('\t' == p[len - 1]) ) )
    422       len--;
    423     if ( (len == namelen) &&
    424          (0 == strncasecmp (p, name, namelen)) )
    425       return true;
    426     if (NULL == comma)
    427       break;
    428     p = comma + 1;
    429   }
    430   return false;
    431 }
    432 
    433 
    434 /**
    435  * Pre-iteration collector: records the client's Via and Connection
    436  * headers on @a hr so the subsequent forwarding pass can append to
    437  * Via (RFC 9110 §7.6.3) and honor the hop-by-hop names listed in
    438  * Connection (RFC 9110 §7.6.1).  Multiple values for the same
    439  * header are joined with ", ", matching the list-header combining
    440  * rule.
    441  *
    442  * @param cls our `struct HttpRequest *`
    443  * @param kind value kind (unused)
    444  * @param key header field name
    445  * @param value header field value
    446  * @return #MHD_YES to continue iteration
    447  */
    448 static enum MHD_Result
    449 collect_proxy_state (void *cls,
    450                      enum MHD_ValueKind kind,
    451                      const char *key,
    452                      const char *value)
    453 {
    454   struct HttpRequest *hr = cls;
    455   char **target;
    456 
    457   (void) kind;
    458   if (NULL == value)
    459     return MHD_YES;
    460   if (0 == strcasecmp (MHD_HTTP_HEADER_VIA,
    461                        key))
    462     target = &hr->client_via;
    463   else if (0 == strcasecmp (MHD_HTTP_HEADER_CONNECTION,
    464                             key))
    465     target = &hr->client_connection;
    466   else
    467     return MHD_YES;
    468   if (NULL == *target)
    469   {
    470     *target = GNUNET_strdup (value);
    471   }
    472   else
    473   {
    474     char *combined;
    475 
    476     GNUNET_asprintf (&combined,
    477                      "%s, %s",
    478                      *target,
    479                      value);
    480     GNUNET_free (*target);
    481     *target = combined;
    482   }
    483   return MHD_YES;
    484 }
    485 
    486 
    487 /* *************** HTTP handling with cURL ***************** */
    488 
    489 
    490 /**
    491  * Transform _one_ CURL header (gotten from the request) into
    492  * MHD format and put it into the response headers list; mostly
    493  * copies the headers, but makes special adjustments based on
    494  * control requests.
    495  *
    496  * @param buffer curl buffer with a single
    497  *        line of header data; not 0-terminated!
    498  * @param size curl blocksize
    499  * @param nmemb curl blocknumber
    500  * @param cls our `struct HttpRequest *`
    501  * @return size of processed bytes
    502  */
    503 static size_t
    504 curl_check_hdr (void *buffer,
    505                 size_t size,
    506                 size_t nmemb,
    507                 void *cls)
    508 {
    509   struct HttpRequest *hr = cls;
    510   struct HttpResponseHeader *header;
    511   size_t bytes = size * nmemb;
    512   char *ndup;
    513   const char *hdr_type;
    514   char *hdr_val;
    515   char *tok;
    516 
    517   /* Raw line is not guaranteed to be null-terminated.  */
    518   ndup = GNUNET_malloc (bytes + 1);
    519   memcpy (ndup,
    520           buffer,
    521           bytes);
    522   ndup[bytes] = '\0';
    523   hdr_type = strtok (ndup, ":");
    524   if (NULL == hdr_type)
    525   {
    526     GNUNET_free (ndup);
    527     return bytes;
    528   }
    529   hdr_val = strtok (NULL, "");
    530   if (NULL == hdr_val)
    531   {
    532     GNUNET_free (ndup);
    533     return bytes;
    534   }
    535   if (' ' == *hdr_val)
    536     hdr_val++;
    537 
    538   /* MHD does not allow certain characters in values,
    539    * remove those, plus those could alter strings matching.  */
    540   if (NULL != (tok = strchr (hdr_val, '\n')))
    541     *tok = '\0';
    542   if (NULL != (tok = strchr (hdr_val, '\r')))
    543     *tok = '\0';
    544   if (NULL != (tok = strchr (hdr_val, '\t')))
    545     *tok = '\0';
    546   PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n",
    547                      hdr_type,
    548                      hdr_val);
    549   /* Skip "Content-length:" header as it will be wrong, given
    550      that we are man-in-the-middling the connection */
    551   if (0 == strcasecmp (hdr_type,
    552                        MHD_HTTP_HEADER_CONTENT_LENGTH))
    553   {
    554     GNUNET_free (ndup);
    555     return bytes;
    556   }
    557   /* Skip hop-by-hop headers. In particular Transfer-Encoding
    558      must not leak through: libcurl has already dechunked the
    559      body for us and MHD will decide whether to re-chunk. */
    560   if (is_hop_by_hop_header (hdr_type))
    561   {
    562     GNUNET_free (ndup);
    563     return bytes;
    564   }
    565   if (0 != strlen (hdr_val)) /* Rely in MHD to set those */
    566   {
    567     header = GNUNET_new (struct HttpResponseHeader);
    568     header->type = GNUNET_strdup (hdr_type);
    569     header->value = GNUNET_strdup (hdr_val);
    570     GNUNET_CONTAINER_DLL_insert (hr->header_head,
    571                                  hr->header_tail,
    572                                  header);
    573   }
    574   GNUNET_free (ndup);
    575   return bytes;
    576 }
    577 
    578 
    579 /**
    580  * Create the MHD response with CURL's as starting base;
    581  * mainly set the response code and parses the response into
    582  * JSON, if it is such.
    583  *
    584  * @param hr pointer to where to store the new data.  Despite
    585  *        its name, the struct contains response data as well.
    586  * @return #GNUNET_OK if it succeeds.
    587  */
    588 static enum GNUNET_GenericReturnValue
    589 create_mhd_response_from_hr (struct HttpRequest *hr)
    590 {
    591   long resp_code;
    592 
    593   if (NULL != hr->response)
    594   {
    595     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    596                 "Response already set!\n");
    597     return GNUNET_SYSERR;
    598   }
    599   GNUNET_break (CURLE_OK ==
    600                 curl_easy_getinfo (hr->curl,
    601                                    CURLINFO_RESPONSE_CODE,
    602                                    &resp_code));
    603   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    604               "Creating MHD response with code %u\n",
    605               (unsigned int) resp_code);
    606   hr->response_code = resp_code;
    607   if (GNUNET_YES == hr->suspended)
    608   {
    609     MHD_resume_connection (hr->con);
    610     hr->suspended = GNUNET_NO;
    611   }
    612   TALER_MHD_daemon_trigger ();
    613   return GNUNET_OK;
    614 }
    615 
    616 
    617 /**
    618  * Handle response payload data from cURL.
    619  * Copies it into our `io_buf` to make it available to MHD.
    620  *
    621  * @param ptr pointer to the data
    622  * @param size number of blocks of data
    623  * @param nmemb blocksize
    624  * @param ctx our `struct HttpRequest *`
    625  * @return number of bytes handled
    626  */
    627 static size_t
    628 curl_download_cb (void *ptr,
    629                   size_t size,
    630                   size_t nmemb,
    631                   void *ctx)
    632 {
    633   struct HttpRequest *hr = ctx;
    634   size_t total = size * nmemb;
    635 
    636   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    637               "Curl download proceeding\n");
    638 
    639   if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state)
    640   {
    641     /* Web server started with response before we finished
    642        the upload.  In this case, current libcurl decides
    643        to NOT complete the upload, so we should jump in the
    644        state machine to process the download, dropping the
    645        rest of the upload.  This should only really happen
    646        with uploads without "Expect: 100 Continue" and
    647        Web servers responding with an error (i.e. upload
    648        not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
    649     GNUNET_log
    650       (GNUNET_ERROR_TYPE_INFO,
    651       "Stopping %u byte upload: we are already downloading...\n",
    652       (unsigned int) hr->io_len);
    653     hr->io_len = 0;
    654   }
    655 
    656   if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state)
    657   {
    658     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    659                 "Download callback goes to sleep\n");
    660     hr->curl_paused = GNUNET_YES;
    661     return CURL_WRITEFUNC_PAUSE;
    662   }
    663   GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED ==
    664                  hr->state);
    665   if (hr->io_size - hr->io_len < total)
    666   {
    667     GNUNET_assert (total + hr->io_size >= total);
    668     GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
    669     GNUNET_array_grow (hr->io_buf,
    670                        hr->io_size,
    671                        GNUNET_MAX (total + hr->io_len,
    672                                    hr->io_size * 2 + 1024));
    673   }
    674   GNUNET_memcpy (&hr->io_buf[hr->io_len],
    675                  ptr,
    676                  total);
    677   hr->io_len += total;
    678   return total;
    679 }
    680 
    681 
    682 /**
    683  * Ask cURL for the select() sets and schedule cURL operations.
    684  */
    685 static void
    686 curl_download_prepare (void);
    687 
    688 
    689 /**
    690  * cURL callback for uploaded (PUT/POST) data.
    691  * Copies from our `io_buf` to make it available to cURL.
    692  *
    693  * @param buf where to write the data
    694  * @param size number of bytes per member
    695  * @param nmemb number of members available in @a buf
    696  * @param cls our `struct HttpRequest` that generated the data
    697  * @return number of bytes copied to @a buf
    698  */
    699 static size_t
    700 curl_upload_cb (void *buf,
    701                 size_t size,
    702                 size_t nmemb,
    703                 void *cls)
    704 {
    705   struct HttpRequest *hr = cls;
    706   size_t len = size * nmemb;
    707   size_t to_copy;
    708 
    709   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    710               "Upload cb is working...\n");
    711 
    712   if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ||
    713        (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) )
    714   {
    715     GNUNET_log
    716       (GNUNET_ERROR_TYPE_INFO,
    717       "Upload cb aborts: we are already downloading...\n");
    718     return CURL_READFUNC_ABORT;
    719   }
    720 
    721   if ( (0 == hr->io_len) &&
    722        (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) )
    723   {
    724     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    725                 "Pausing CURL UPLOAD, need more data\n");
    726     return CURL_READFUNC_PAUSE;
    727   }
    728 
    729   /**
    730    * We got rescheduled because the download callback was asleep.
    731    * FIXME: can this block be eliminated and the unpausing being
    732    * moved in the last block where we return zero as well?
    733    */
    734   if ( (0 == hr->io_len) &&
    735        (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) )
    736   {
    737     if (GNUNET_YES == hr->curl_paused)
    738     {
    739       hr->curl_paused = GNUNET_NO;
    740       curl_easy_pause (hr->curl,
    741                        CURLPAUSE_CONT);
    742     }
    743     curl_download_prepare ();
    744     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    745                 "Completed CURL UPLOAD\n");
    746     return 0; /* upload finished, can now download */
    747   }
    748   to_copy = GNUNET_MIN (hr->io_len,
    749                         len);
    750   GNUNET_memcpy (buf,
    751                  hr->io_buf,
    752                  to_copy);
    753   /* shift remaining data back to the beginning of the buffer.  */
    754   memmove (hr->io_buf,
    755            &hr->io_buf[to_copy],
    756            hr->io_len - to_copy);
    757   hr->io_len -= to_copy;
    758   if (0 == hr->io_len)
    759   {
    760     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
    761     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    762                 "Completed CURL UPLOAD\n");
    763   }
    764   return to_copy;
    765 }
    766 
    767 
    768 /* ************** helper functions ************* */
    769 
    770 /**
    771  * Extract the hostname from a complete URL.
    772  *
    773  * @param url full fledged URL
    774  * @return pointer to the 0-terminated hostname, to be freed
    775  *         by the caller.
    776  */
    777 static char *
    778 build_host_header (const char *url)
    779 {
    780   #define MARKER "://"
    781 
    782   char *header;
    783   char *end;
    784   char *hostname;
    785   char *dup = GNUNET_strdup (url);
    786 
    787   hostname = strstr (dup,
    788                      MARKER);
    789   hostname += 3;
    790   end = strchrnul (hostname, '/');
    791   *end = '\0';
    792   GNUNET_asprintf (&header,
    793                    "Host: %s",
    794                    hostname);
    795   GNUNET_free (dup);
    796   return header;
    797 }
    798 
    799 
    800 /* ************** main loop of cURL interaction ************* */
    801 
    802 
    803 /**
    804  * Task that is run when we are ready to receive more data
    805  * from curl
    806  *
    807  * @param cls closure
    808  */
    809 static void
    810 curl_task_download (void *cls);
    811 
    812 
    813 /**
    814  * Ask cURL for the select() sets and schedule cURL operations.
    815  */
    816 static void
    817 curl_download_prepare ()
    818 {
    819   CURLMcode mret;
    820   fd_set rs;
    821   fd_set ws;
    822   fd_set es;
    823   int max;
    824   struct GNUNET_NETWORK_FDSet *grs;
    825   struct GNUNET_NETWORK_FDSet *gws;
    826   long to;
    827   struct GNUNET_TIME_Relative rtime;
    828 
    829   if (NULL != curl_download_task)
    830   {
    831     GNUNET_SCHEDULER_cancel (curl_download_task);
    832     curl_download_task = NULL;
    833   }
    834   max = -1;
    835   FD_ZERO (&rs);
    836   FD_ZERO (&ws);
    837   FD_ZERO (&es);
    838   if (CURLM_OK != (mret = curl_multi_fdset (curl_multi,
    839                                             &rs,
    840                                             &ws,
    841                                             &es,
    842                                             &max)))
    843   {
    844     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    845                 "%s failed at %s:%d: `%s'\n",
    846                 "curl_multi_fdset",
    847                 __FILE__,
    848                 __LINE__,
    849                 curl_multi_strerror (mret));
    850     return;
    851   }
    852   to = -1;
    853   GNUNET_break (CURLM_OK ==
    854                 curl_multi_timeout (curl_multi,
    855                                     &to));
    856   if (-1 == to)
    857     rtime = GNUNET_TIME_UNIT_FOREVER_REL;
    858   else
    859     rtime = GNUNET_TIME_relative_multiply
    860               (GNUNET_TIME_UNIT_MILLISECONDS, to);
    861   if (-1 != max)
    862   {
    863     grs = GNUNET_NETWORK_fdset_create ();
    864     gws = GNUNET_NETWORK_fdset_create ();
    865     GNUNET_NETWORK_fdset_copy_native (grs,
    866                                       &rs,
    867                                       max + 1);
    868     GNUNET_NETWORK_fdset_copy_native (gws,
    869                                       &ws,
    870                                       max + 1);
    871     curl_download_task
    872       = GNUNET_SCHEDULER_add_select (
    873           GNUNET_SCHEDULER_PRIORITY_DEFAULT,
    874           rtime,
    875           grs, gws,
    876           &curl_task_download,
    877           curl_multi);
    878     GNUNET_NETWORK_fdset_destroy (gws);
    879     GNUNET_NETWORK_fdset_destroy (grs);
    880   }
    881   else
    882   {
    883     curl_download_task = GNUNET_SCHEDULER_add_delayed (rtime,
    884                                                        &curl_task_download,
    885                                                        curl_multi);
    886   }
    887 }
    888 
    889 
    890 /**
    891  * "Filter" function that translates MHD request headers to
    892  * cURL's.
    893  *
    894  * @param cls our `struct HttpRequest`
    895  * @param kind value kind
    896  * @param key field key
    897  * @param value field value
    898  * @return #MHD_YES to continue to iterate
    899  */
    900 static enum MHD_Result
    901 con_val_iter (void *cls,
    902               enum MHD_ValueKind kind,
    903               const char *key,
    904               const char *value)
    905 {
    906   struct HttpRequest *hr = cls;
    907   char *hdr;
    908   char *new_value = NULL;
    909 
    910   (void) kind;
    911   if (0 == strcasecmp (MHD_HTTP_HEADER_HOST,
    912                        key))
    913   {
    914     /* We don't take the host header as given in the request.
    915      * We'll instead put the proxied service's hostname in it*/
    916     return MHD_YES;
    917   }
    918   if (0 == strcasecmp (MHD_HTTP_HEADER_CONTENT_LENGTH,
    919                        key))
    920   {
    921     /* libcurl sets Content-Length itself from CURLOPT_POSTFIELDSIZE
    922        / CURLOPT_INFILESIZE. */
    923     return MHD_YES;
    924   }
    925   if (0 == strcasecmp (MHD_HTTP_HEADER_EXPECT,
    926                        key))
    927   {
    928     /* libcurl manages Expect: 100-continue on its own. */
    929     return MHD_YES;
    930   }
    931   if (is_hop_by_hop_header (key))
    932     return MHD_YES;
    933   /* RFC 9110 §7.6.1: suppress any header named by the client's
    934      Connection list (additional hop-by-hop headers). */
    935   if (connection_lists_header (hr->client_connection,
    936                                key))
    937     return MHD_YES;
    938   if ( (0 == strncasecmp ("X-Forwarded-",
    939                           key,
    940                           strlen ("X-Forwarded-"))) ||
    941        (0 == strcasecmp (MHD_HTTP_HEADER_FORWARDED,
    942                          key)) )
    943   {
    944     /* We will replace these with our own below. */
    945     return MHD_YES;
    946   }
    947   if (0 == strcasecmp (MHD_HTTP_HEADER_VIA,
    948                        key))
    949   {
    950     /* The client's Via was captured in `collect_proxy_state` and
    951        will be emitted below with our own entry appended (RFC 9110
    952        §7.6.3).  Drop the raw header here so we don't forward it
    953        twice. */
    954     return MHD_YES;
    955   }
    956   GNUNET_asprintf (&hdr,
    957                    "%s: %s",
    958                    key,
    959                    value);
    960   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    961               "Adding header `%s' to HTTP request\n",
    962               hdr);
    963   hr->headers = curl_slist_append (hr->headers,
    964                                    hdr);
    965   GNUNET_free (hdr);
    966   GNUNET_free (new_value);
    967   return MHD_YES;
    968 }
    969 
    970 
    971 /**
    972  * Task that is run when we are ready to receive
    973  * more data from curl.
    974  *
    975  * @param cls closure, usually NULL.
    976  */
    977 static void
    978 curl_task_download (void *cls)
    979 {
    980   int running;
    981   int msgnum;
    982   struct CURLMsg *msg;
    983   CURLMcode mret;
    984   struct HttpRequest *hr;
    985 
    986   (void) cls;
    987   curl_download_task = NULL;
    988   do
    989   {
    990     running = 0;
    991     mret = curl_multi_perform (curl_multi,
    992                                &running);
    993     while (NULL != (msg = curl_multi_info_read (curl_multi,
    994                                                 &msgnum)))
    995     {
    996       GNUNET_break
    997         (CURLE_OK == curl_easy_getinfo
    998           (msg->easy_handle,
    999           CURLINFO_PRIVATE,
   1000           (char **) &hr));
   1001 
   1002       if (NULL == hr)
   1003       {
   1004         GNUNET_break (0);
   1005         continue;
   1006       }
   1007       switch (msg->msg)
   1008       {
   1009       case CURLMSG_NONE:
   1010         /* documentation says this is not used */
   1011         GNUNET_break (0);
   1012         break;
   1013       case CURLMSG_DONE:
   1014         switch (msg->data.result)
   1015         {
   1016         case CURLE_OK:
   1017         case CURLE_GOT_NOTHING:
   1018           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1019                       "CURL download completed.\n");
   1020           hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
   1021           if (0 == hr->response_code)
   1022             GNUNET_assert (GNUNET_OK ==
   1023                            create_mhd_response_from_hr (hr));
   1024           break;
   1025         default:
   1026           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1027                       "Download curl failed: %s\n",
   1028                       curl_easy_strerror (msg->data.result));
   1029           hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
   1030           /* Surface upstream failures as 502 Bad Gateway. Drop
   1031              any partial body we might have accumulated and let
   1032              MHD return our pre-built failure page instead of
   1033              half a truncated upstream response. */
   1034           hr->response_code = MHD_HTTP_BAD_GATEWAY;
   1035           hr->io_len = 0;
   1036           hr->response = curl_failure_response;
   1037           if (GNUNET_YES == hr->suspended)
   1038           {
   1039             MHD_resume_connection (hr->con);
   1040             hr->suspended = GNUNET_NO;
   1041           }
   1042           break;
   1043         }  /* end switch (msg->data.result) */
   1044         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1045                     "Curl request for `%s' finished (got the response)\n",
   1046                     hr->url);
   1047         TALER_MHD_daemon_trigger ();
   1048         break;
   1049       case CURLMSG_LAST:
   1050         /* documentation says this is not used */
   1051         GNUNET_break (0);
   1052         break;
   1053       default:
   1054         /* unexpected status code */
   1055         GNUNET_break (0);
   1056         break;
   1057       } /* end switch msg->msg */
   1058     } /* end while (curl_multi_info_read()) */
   1059   } while (mret == CURLM_CALL_MULTI_PERFORM);
   1060   if (CURLM_OK != mret)
   1061     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1062                 "%s failed at %s:%d: `%s'\n",
   1063                 "curl_multi_perform",
   1064                 __FILE__,
   1065                 __LINE__,
   1066                 curl_multi_strerror (mret));
   1067   if (0 == running)
   1068   {
   1069     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1070                 "Suspending cURL multi loop,"
   1071                 " no more events pending\n");
   1072     return; /* nothing more in progress */
   1073   }
   1074   curl_download_prepare ();
   1075 }
   1076 
   1077 
   1078 struct HttpRequest *
   1079 PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection,
   1080                               const char *url)
   1081 {
   1082   struct HttpRequest *hr;
   1083 
   1084   hr = GNUNET_new (struct HttpRequest);
   1085   hr->con = connection;
   1086   hr->url = GNUNET_strdup (url);
   1087   GNUNET_CONTAINER_DLL_insert (hr_head,
   1088                                hr_tail,
   1089                                hr);
   1090   return hr;
   1091 }
   1092 
   1093 
   1094 void
   1095 PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr)
   1096 {
   1097   struct HttpResponseHeader *header;
   1098 
   1099   if (NULL != hr->curl)
   1100   {
   1101     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1102                 "Resetting cURL handle\n");
   1103     curl_multi_remove_handle (curl_multi,
   1104                               hr->curl);
   1105     curl_easy_cleanup (hr->curl);
   1106     hr->curl = NULL;
   1107     hr->io_len = 0;
   1108   }
   1109   if (NULL != hr->headers)
   1110   {
   1111     curl_slist_free_all (hr->headers);
   1112     hr->headers = NULL;
   1113   }
   1114   if ( (NULL != hr->response) &&
   1115        (curl_failure_response != hr->response) )
   1116     /* Destroy non-error responses... (?) */
   1117     MHD_destroy_response (hr->response);
   1118 
   1119   for (header = hr->header_head;
   1120        header != NULL;
   1121        header = hr->header_head)
   1122   {
   1123     GNUNET_CONTAINER_DLL_remove (hr->header_head,
   1124                                  hr->header_tail,
   1125                                  header);
   1126     GNUNET_free (header->type);
   1127     GNUNET_free (header->value);
   1128     GNUNET_free (header);
   1129   }
   1130   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1131               "Proxying of '%s' completely done\n",
   1132               hr->url);
   1133 
   1134   GNUNET_free (hr->url);
   1135   GNUNET_free (hr->io_buf);
   1136   GNUNET_free (hr->client_via);
   1137   GNUNET_free (hr->client_connection);
   1138   GNUNET_CONTAINER_DLL_remove (hr_head,
   1139                                hr_tail,
   1140                                hr);
   1141   GNUNET_free (hr);
   1142 }
   1143 
   1144 
   1145 /**
   1146  * Main MHD callback for reverse proxy.
   1147  *
   1148  * @param hr the HTTP request context
   1149  * @param con MHD connection handle
   1150  * @param url the url in the request
   1151  * @param meth the HTTP method used ("GET", "PUT", etc.)
   1152  * @param ver the HTTP version string (i.e. "HTTP/1.1")
   1153  * @param upload_data the data being uploaded (excluding HEADERS,
   1154  *        for a POST that fits into memory and that is encoded
   1155  *        with a supported encoding, the POST data will NOT be
   1156  *        given in upload_data and is instead available as
   1157  *        part of MHD_get_connection_values; very large POST
   1158  *        data *will* be made available incrementally in
   1159  *        upload_data)
   1160  * @param upload_data_size set initially to the size of the
   1161  *        @a upload_data provided; the method must update this
   1162  *        value to the number of bytes NOT processed;
   1163  * @return #MHD_YES if the connection was handled successfully,
   1164  *         #MHD_NO if the socket must be closed due to a serious
   1165  *         error while handling the request
   1166  */
   1167 enum MHD_Result
   1168 PAIVANA_HTTPD_reverse (struct HttpRequest *hr,
   1169                        struct MHD_Connection *con,
   1170                        const char *url,
   1171                        const char *meth,
   1172                        const char *ver,
   1173                        const char *upload_data,
   1174                        size_t *upload_data_size)
   1175 {
   1176   /* MHD's "first" call (immediately after headers) arrives with
   1177      `upload_data_size == 0` and no body.  Just acknowledge it so
   1178      MHD will proceed to deliver the request body (if any) and then
   1179      make the "final" call.  Setting up curl here would start the
   1180      upstream request with an empty io_buf and leave body chunks
   1181      nowhere to land. */
   1182   if (! hr->accepted)
   1183   {
   1184     /* If the client declared a Content-Length we already know is too
   1185        large, reject now: MHD is in HEADERS_PROCESSED so we can queue
   1186        a response, and rejecting here suppresses the implicit 100
   1187        Continue (RFC 7231 §5.1.1) for clients that asked for one and
   1188        avoids buffering the body just to throw it away.  Chunked
   1189        uploads (no Content-Length) and clients that ignore 100
   1190        Continue and stream the body anyway still hit the
   1191        drain-then-reject path further down. */
   1192     const char *cl_str
   1193       = MHD_lookup_connection_value (con,
   1194                                      MHD_HEADER_KIND,
   1195                                      MHD_HTTP_HEADER_CONTENT_LENGTH);
   1196     hr->accepted = true;
   1197     if (NULL != cl_str)
   1198     {
   1199       char *endptr;
   1200       unsigned long long cl;
   1201 
   1202       errno = 0;
   1203       cl = strtoull (cl_str,
   1204                      &endptr,
   1205                      10);
   1206       if ( (0 == errno) &&
   1207            ('\0' == *endptr) &&
   1208            (cl > PH_request_buffer_max) )
   1209       {
   1210         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1211                     "Rejecting upload: Content-Length %llu exceeds %llu byte limit\n",
   1212                     cl,
   1213                     PH_request_buffer_max);
   1214         hr->reject_upload = true;
   1215         return MHD_queue_response (con,
   1216                                    MHD_HTTP_CONTENT_TOO_LARGE,
   1217                                    curl_failure_response);
   1218       }
   1219     }
   1220     return MHD_YES;
   1221   }
   1222   /* On the "final" access-handler call after we drained an
   1223      over-sized upload, queue the deferred 413 response now that
   1224      MHD is back in a state where that is allowed. */
   1225   if (hr->reject_upload && 0 == *upload_data_size)
   1226   {
   1227     return MHD_queue_response (con,
   1228                                MHD_HTTP_CONTENT_TOO_LARGE,
   1229                                curl_failure_response);
   1230   }
   1231   /* FIXME: make state machine more explicit by
   1232      switching on hr->state here! */
   1233   if (0 != *upload_data_size)
   1234   {
   1235     GNUNET_assert
   1236       (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state);
   1237 
   1238     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1239                 "Processing %u bytes UPLOAD\n",
   1240                 (unsigned int) *upload_data_size);
   1241 
   1242     /* Reject uploads that would exceed our buffering cap. The
   1243        entire request body is currently buffered before forwarding,
   1244        so this also bounds memory usage per request.
   1245        MHD does not allow queuing a response while BODY_RECEIVING
   1246        (MHD_queue_response returns MHD_NO outside of
   1247        HEADERS_PROCESSED / FULL_REQ_RECEIVED), so we mark the
   1248        request as over-limit, silently drain the remaining body,
   1249        and queue the 413 on the "final" access-handler call. */
   1250     if (hr->reject_upload ||
   1251         hr->io_len + *upload_data_size > PH_request_buffer_max)
   1252     {
   1253       if (! hr->reject_upload)
   1254       {
   1255         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1256                     "Upload exceeds %llu byte limit, rejecting\n",
   1257                     PH_request_buffer_max);
   1258         hr->reject_upload = true;
   1259       }
   1260       *upload_data_size = 0;
   1261       return MHD_YES;
   1262     }
   1263 
   1264     /* Grow the buffer if remaining space isn't enough.  */
   1265     if (hr->io_size - hr->io_len < *upload_data_size)
   1266     {
   1267       /* How can this assertion be false?  */
   1268       GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
   1269       /* This asserts that upload_data_size > 0, ?  */
   1270       GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len);
   1271 
   1272       GNUNET_array_grow (hr->io_buf,
   1273                          hr->io_size,
   1274                          GNUNET_MAX
   1275                            (hr->io_size * 2 + 1024,
   1276                            *upload_data_size + hr->io_len));
   1277     }
   1278 
   1279     /* Finally copy upload data.  */
   1280     GNUNET_memcpy (&hr->io_buf[hr->io_len],
   1281                    upload_data,
   1282                    *upload_data_size);
   1283 
   1284     hr->io_len += *upload_data_size;
   1285     *upload_data_size = 0;
   1286 
   1287     return MHD_YES;
   1288   }
   1289 
   1290   /* Upload (*from the client*) finished or just a without-body
   1291    * request.  */
   1292   if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state)
   1293   {
   1294     hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE;
   1295     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1296                 "Finished processing UPLOAD\n");
   1297   }
   1298 
   1299   /* generate curl request to the proxied service. */
   1300   if (NULL == hr->curl)
   1301   {
   1302     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1303                 "Generating curl request\n");
   1304     hr->curl = curl_easy_init ();
   1305     if (NULL == hr->curl)
   1306     {
   1307       PAIVANA_LOG_ERROR ("Could not init the curl handle\n");
   1308       return MHD_queue_response (con,
   1309                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
   1310                                  curl_failure_response);
   1311     }
   1312 
   1313     /* No need to check whether we're POSTing or PUTting.
   1314      * If not needed, one of the following values will be
   1315      * ignored.*/
   1316     curl_easy_setopt (hr->curl,
   1317                       CURLOPT_POSTFIELDSIZE,
   1318                       hr->io_len);
   1319     curl_easy_setopt (hr->curl,
   1320                       CURLOPT_INFILESIZE,
   1321                       hr->io_len);
   1322     curl_easy_setopt (hr->curl,
   1323                       CURLOPT_HEADERFUNCTION,
   1324                       &curl_check_hdr);
   1325     curl_easy_setopt (hr->curl,
   1326                       CURLOPT_HEADERDATA,
   1327                       hr);
   1328     curl_easy_setopt (hr->curl,
   1329                       CURLOPT_FOLLOWLOCATION,
   1330                       0);
   1331     curl_easy_setopt (hr->curl,
   1332                       CURLOPT_CONNECTTIMEOUT,
   1333                       60L);
   1334     curl_easy_setopt (hr->curl,
   1335                       CURLOPT_TIMEOUT,
   1336                       60L);
   1337     curl_easy_setopt (hr->curl,
   1338                       CURLOPT_NOSIGNAL,
   1339                       1L);
   1340     curl_easy_setopt (hr->curl,
   1341                       CURLOPT_PRIVATE,
   1342                       hr);
   1343     curl_easy_setopt (hr->curl,
   1344                       CURLOPT_VERBOSE,
   1345                       0);
   1346 
   1347     curl_easy_setopt (hr->curl,
   1348                       CURLOPT_READFUNCTION,
   1349                       &curl_upload_cb);
   1350     curl_easy_setopt (hr->curl,
   1351                       CURLOPT_READDATA,
   1352                       hr);
   1353 
   1354     curl_easy_setopt (hr->curl,
   1355                       CURLOPT_WRITEFUNCTION,
   1356                       &curl_download_cb);
   1357     curl_easy_setopt (hr->curl,
   1358                       CURLOPT_WRITEDATA,
   1359                       hr);
   1360     {
   1361       char *curlurl;
   1362       char *host_hdr;
   1363 
   1364       GNUNET_asprintf (&curlurl,
   1365                        "%s%s",
   1366                        PH_target_server_base_url,
   1367                        hr->url);
   1368       curl_easy_setopt (hr->curl,
   1369                         CURLOPT_URL,
   1370                         curlurl);
   1371       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1372                   "Forwarding request to: %s\n",
   1373                   curlurl);
   1374       GNUNET_free (curlurl);
   1375 
   1376       host_hdr = build_host_header (PH_target_server_base_url);
   1377       PAIVANA_LOG_DEBUG ("Faking the host header, %s\n",
   1378                          host_hdr);
   1379       hr->headers = curl_slist_append (hr->headers,
   1380                                        host_hdr);
   1381       GNUNET_free (host_hdr);
   1382     }
   1383 
   1384     if (0 == strcasecmp (meth,
   1385                          MHD_HTTP_METHOD_PUT))
   1386     {
   1387       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1388                   "Crafting a CURL PUT request\n");
   1389 
   1390       curl_easy_setopt (hr->curl,
   1391                         CURLOPT_UPLOAD,
   1392                         1L);
   1393       hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1394     }
   1395     else if (0 == strcasecmp (meth,
   1396                               MHD_HTTP_METHOD_POST))
   1397     {
   1398       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1399                   "Crafting a CURL POST request\n");
   1400       curl_easy_setopt (hr->curl,
   1401                         CURLOPT_POST,
   1402                         1L);
   1403       hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1404     }
   1405     else if (0 == strcasecmp (meth,
   1406                               MHD_HTTP_METHOD_PATCH))
   1407     {
   1408       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1409                   "Crafting a CURL PATCH request\n");
   1410       /* CURLOPT_POST=1 turns on body upload via the read callback;
   1411          CURLOPT_CUSTOMREQUEST then overrides the verb on the wire. */
   1412       curl_easy_setopt (hr->curl,
   1413                         CURLOPT_POST,
   1414                         1L);
   1415       curl_easy_setopt (hr->curl,
   1416                         CURLOPT_CUSTOMREQUEST,
   1417                         "PATCH");
   1418       hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1419     }
   1420     else if (0 == strcasecmp (meth,
   1421                               MHD_HTTP_METHOD_DELETE))
   1422     {
   1423       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1424                   "Crafting a CURL DELETE request\n");
   1425       curl_easy_setopt (hr->curl,
   1426                         CURLOPT_CUSTOMREQUEST,
   1427                         "DELETE");
   1428       if (0 != hr->io_len)
   1429       {
   1430         /* DELETE with a request body is unusual but legal. */
   1431         curl_easy_setopt (hr->curl,
   1432                           CURLOPT_POST,
   1433                           1L);
   1434         hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1435       }
   1436       else
   1437       {
   1438         hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1439       }
   1440     }
   1441     else if (0 == strcasecmp (meth,
   1442                               MHD_HTTP_METHOD_HEAD))
   1443     {
   1444       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1445       curl_easy_setopt (hr->curl,
   1446                         CURLOPT_NOBODY,
   1447                         1L);
   1448     }
   1449     else if (0 == strcasecmp (meth,
   1450                               MHD_HTTP_METHOD_OPTIONS))
   1451     {
   1452       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1453       curl_easy_setopt (hr->curl,
   1454                         CURLOPT_CUSTOMREQUEST,
   1455                         "OPTIONS");
   1456     }
   1457     else if (0 == strcasecmp (meth,
   1458                               MHD_HTTP_METHOD_GET))
   1459     {
   1460       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1461       curl_easy_setopt (hr->curl,
   1462                         CURLOPT_HTTPGET,
   1463                         1L);
   1464     }
   1465     else
   1466     {
   1467       /* TRACE leaks headers back to the client; CONNECT is for
   1468          TLS tunnelling and doesn't fit the reverse-proxy model.
   1469          Reject anything else with a proper 405. */
   1470       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1471                   "Unsupported HTTP method `%s'\n",
   1472                   meth);
   1473       curl_easy_cleanup (hr->curl);
   1474       hr->curl = NULL;
   1475       return MHD_queue_response (con,
   1476                                  MHD_HTTP_METHOD_NOT_ALLOWED,
   1477                                  curl_failure_response);
   1478     }
   1479 
   1480     if (CURLM_OK !=
   1481         curl_multi_add_handle (curl_multi,
   1482                                hr->curl))
   1483     {
   1484       GNUNET_break (0);
   1485       curl_easy_cleanup (hr->curl);
   1486       hr->curl = NULL;
   1487       return MHD_queue_response (con,
   1488                                  MHD_HTTP_BAD_GATEWAY,
   1489                                  curl_failure_response);
   1490     }
   1491 
   1492     /* First pass: collect Via / Connection so `con_val_iter` can
   1493        honor them (append to Via; drop headers named by Connection). */
   1494     MHD_get_connection_values (con,
   1495                                MHD_HEADER_KIND,
   1496                                &collect_proxy_state,
   1497                                hr);
   1498     MHD_get_connection_values (con,
   1499                                MHD_HEADER_KIND,
   1500                                &con_val_iter,
   1501                                hr);
   1502 
   1503     /* Add standard reverse-proxy forwarding headers.  X-Forwarded-*
   1504        and Forwarded are replaced with our view of the connection
   1505        (filtered out in con_val_iter); Via is *appended to* the
   1506        client's existing chain per RFC 9110 §7.6.3. */
   1507     {
   1508       const union MHD_ConnectionInfo *ci;
   1509       char *hdr;
   1510       const char *proto;
   1511       const char *fhost;
   1512 
   1513       ci = MHD_get_connection_info (con,
   1514                                     MHD_CONNECTION_INFO_CLIENT_ADDRESS);
   1515       if ( (NULL != ci) && (NULL != ci->client_addr) )
   1516       {
   1517         char ipbuf[INET6_ADDRSTRLEN];
   1518         const char *ip = NULL;
   1519 
   1520         switch (ci->client_addr->sa_family)
   1521         {
   1522         case AF_INET:
   1523           ip = inet_ntop (
   1524             AF_INET,
   1525             &((const struct sockaddr_in *) ci->client_addr)->sin_addr,
   1526             ipbuf, sizeof (ipbuf));
   1527           break;
   1528         case AF_INET6:
   1529           ip = inet_ntop (
   1530             AF_INET6,
   1531             &((const struct sockaddr_in6 *) ci->client_addr)->sin6_addr,
   1532             ipbuf, sizeof (ipbuf));
   1533           break;
   1534         default:
   1535           break;
   1536         }
   1537         if (NULL != ip)
   1538         {
   1539           GNUNET_asprintf (&hdr,
   1540                            "X-Forwarded-For: %s",
   1541                            ip);
   1542           hr->headers = curl_slist_append (hr->headers,
   1543                                            hdr);
   1544           GNUNET_free (hdr);
   1545         }
   1546       }
   1547       proto = (GNUNET_YES == TALER_mhd_is_https (con))
   1548               ? "https" : "http";
   1549       GNUNET_asprintf (&hdr,
   1550                        "X-Forwarded-Proto: %s",
   1551                        proto);
   1552       hr->headers = curl_slist_append (hr->headers,
   1553                                        hdr);
   1554       GNUNET_free (hdr);
   1555       fhost = MHD_lookup_connection_value (con,
   1556                                            MHD_HEADER_KIND,
   1557                                            MHD_HTTP_HEADER_HOST);
   1558       if (NULL != fhost)
   1559       {
   1560         GNUNET_asprintf (&hdr,
   1561                          "X-Forwarded-Host: %s",
   1562                          fhost);
   1563         hr->headers = curl_slist_append (hr->headers,
   1564                                          hdr);
   1565         GNUNET_free (hdr);
   1566       }
   1567       /* Via: pseudonym + protocol-version (RFC 9110 §7.6.3);
   1568          MHD hands us e.g. "HTTP/1.1" but Via wants just "1.1".
   1569          If the client already carried a Via chain, prepend it so
   1570          the upstream sees the full trace of proxies. */
   1571       {
   1572         const char *via_ver = "1.1";
   1573 
   1574         if ( (NULL != ver) &&
   1575              (0 == strncasecmp (ver, "HTTP/", 5)) )
   1576           via_ver = ver + 5;
   1577         if (NULL != hr->client_via)
   1578           GNUNET_asprintf (&hdr,
   1579                            "%s: %s, %s paivana",
   1580                            MHD_HTTP_HEADER_VIA,
   1581                            hr->client_via,
   1582                            via_ver);
   1583         else
   1584           GNUNET_asprintf (&hdr,
   1585                            "%s: %s paivana",
   1586                            MHD_HTTP_HEADER_VIA,
   1587                            via_ver);
   1588       }
   1589       hr->headers = curl_slist_append (hr->headers,
   1590                                        hdr);
   1591       GNUNET_free (hdr);
   1592     }
   1593 
   1594     curl_easy_setopt (hr->curl,
   1595                       CURLOPT_HTTPHEADER,
   1596                       hr->headers);
   1597     curl_download_prepare ();
   1598 
   1599     return MHD_YES;
   1600   }
   1601 
   1602   if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state)
   1603   {
   1604     GNUNET_assert (GNUNET_NO == hr->suspended);
   1605     MHD_suspend_connection (con);
   1606     hr->suspended = GNUNET_YES;
   1607     return MHD_YES; /* wait for curl */
   1608   }
   1609 
   1610   GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state);
   1611 
   1612   /* Response may already be set to curl_failure_response by the
   1613      curl task on upstream failure; in that case, don't build a
   1614      buffer response and don't attach per-request headers to the
   1615      shared failure response. */
   1616   if (NULL == hr->response)
   1617   {
   1618     hr->response
   1619       = MHD_create_response_from_buffer_static (hr->io_len,
   1620                                                 hr->io_buf);
   1621     if (NULL == hr->response)
   1622     {
   1623       GNUNET_break (0);
   1624       hr->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1625       hr->response = curl_failure_response;
   1626     }
   1627     else
   1628     {
   1629       for (struct HttpResponseHeader *header = hr->header_head;
   1630            NULL != header;
   1631            header = header->next)
   1632       {
   1633         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1634                     "Adding MHD response header %s->%s\n",
   1635                     header->type,
   1636                     header->value);
   1637         GNUNET_break (MHD_YES ==
   1638                       MHD_add_response_header (hr->response,
   1639                                                header->type,
   1640                                                header->value));
   1641       }
   1642     }
   1643   }
   1644   TALER_MHD_daemon_trigger ();
   1645 
   1646   return MHD_queue_response (con,
   1647                              hr->response_code,
   1648                              hr->response);
   1649 }