paivana

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

paivana-httpd_reverse.c (31619B)


      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_reverse.h"
     34 
     35 #define REQUEST_BUFFER_MAX (1024 * 1024)
     36 
     37 
     38 /**
     39  * Log curl error.
     40  *
     41  * @param level log level
     42  * @param fun name of curl_easy-function that gave the error
     43  * @param rc return code from curl
     44  */
     45 #define LOG_CURL_EASY(level,fun,rc) \
     46         GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \
     47                     __LINE__, \
     48                     curl_easy_strerror (rc))
     49 
     50 
     51 /**
     52  * State machine for HTTP requests (per request).
     53  */
     54 enum RequestState
     55 {
     56   /**
     57    * We've started receiving upload data from MHD.
     58    * Initial state.
     59    */
     60   REQUEST_STATE_CLIENT_UPLOAD_STARTED,
     61 
     62   /**
     63    * Wa have started uploading data to the proxied service.
     64    */
     65   REQUEST_STATE_PROXY_UPLOAD_STARTED,
     66 
     67   /**
     68    * We're done with the upload from MHD.
     69    */
     70   REQUEST_STATE_CLIENT_UPLOAD_DONE,
     71 
     72   /**
     73    * We're done uploading data to the proxied service.
     74    */
     75   REQUEST_STATE_PROXY_UPLOAD_DONE,
     76 
     77   /**
     78    * We've finished uploading data via CURL and can now download.
     79    */
     80   REQUEST_STATE_PROXY_DOWNLOAD_STARTED,
     81 
     82   /**
     83    * We've finished receiving download data from cURL.
     84    */
     85   REQUEST_STATE_PROXY_DOWNLOAD_DONE
     86 };
     87 
     88 
     89 /**
     90  * A header list
     91  */
     92 struct HttpResponseHeader
     93 {
     94   /**
     95    * DLL
     96    */
     97   struct HttpResponseHeader *next;
     98 
     99   /**
    100    * DLL
    101    */
    102   struct HttpResponseHeader *prev;
    103 
    104   /**
    105    * Header type
    106    */
    107   char *type;
    108 
    109   /**
    110    * Header value
    111    */
    112   char *value;
    113 };
    114 
    115 
    116 /**
    117  * A structure for socks requests
    118  */
    119 struct HttpRequest
    120 {
    121 
    122   /**
    123    * Kept in DLL.
    124    */
    125   struct HttpRequest *prev;
    126 
    127   /**
    128    * Kept in DLL.
    129    */
    130   struct HttpRequest *next;
    131 
    132   /**
    133    * MHD request that triggered us.
    134    */
    135   struct MHD_Connection *con;
    136 
    137   /**
    138    * Client socket read task
    139    */
    140   struct GNUNET_SCHEDULER_Task *rtask;
    141 
    142   /**
    143    * Client socket write task
    144    */
    145   struct GNUNET_SCHEDULER_Task *wtask;
    146 
    147   /**
    148    * Hold the response obtained by modifying the original one.
    149    */
    150   struct MHD_Response *mod_response;
    151 
    152   /**
    153    * MHD response object for this request.
    154    */
    155   struct MHD_Response *response;
    156 
    157   /**
    158    * The URL to fetch
    159    */
    160   char *url;
    161 
    162   /**
    163    * Handle to cURL
    164    */
    165   CURL *curl;
    166 
    167   /**
    168    * HTTP request headers for the curl request.
    169    */
    170   struct curl_slist *headers;
    171 
    172   /**
    173    * Headers from response
    174    */
    175   struct HttpResponseHeader *header_head;
    176 
    177   /**
    178    * Headers from response
    179    */
    180   struct HttpResponseHeader *header_tail;
    181 
    182   /**
    183    * Buffer we use for moving data between MHD and
    184    * curl (in both directions).
    185    */
    186   char *io_buf;
    187 
    188   /**
    189    * Number of bytes already in the IO buffer.
    190    */
    191   size_t io_len;
    192 
    193   /**
    194    * Number of bytes allocated for the IO buffer.
    195    */
    196   unsigned int io_size;
    197 
    198   /**
    199    * HTTP response code to give to MHD for the response.
    200    */
    201   unsigned int response_code;
    202 
    203   /**
    204    * Request processing state machine.
    205    */
    206   enum RequestState state;
    207 
    208   /**
    209    * Did we suspend MHD processing?
    210    */
    211   enum GNUNET_GenericReturnValue suspended;
    212 
    213   /**
    214    * Did we pause CURL processing?
    215    */
    216   int curl_paused;
    217 };
    218 
    219 
    220 /**
    221  * DLL of active HTTP requests.
    222  */
    223 static struct HttpRequest *hr_head;
    224 
    225 /**
    226  * DLL of active HTTP requests.
    227  */
    228 static struct HttpRequest *hr_tail;
    229 
    230 /**
    231  * Response we return on cURL failures.
    232  */
    233 static struct MHD_Response *curl_failure_response;
    234 
    235 /**
    236  * The cURL multi handle
    237  */
    238 static CURLM *curl_multi;
    239 
    240 /**
    241  * The cURL download task (curl multi API).
    242  */
    243 static struct GNUNET_SCHEDULER_Task *curl_download_task;
    244 
    245 
    246 bool
    247 PAIVANA_HTTPD_reverse_init (void)
    248 {
    249   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
    250   {
    251     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    252                 "cURL global init failed!\n");
    253     return false;
    254   }
    255   if (NULL == (curl_multi = curl_multi_init ()))
    256   {
    257     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    258                 "Failed to create cURL multi handle!\n");
    259     return false;
    260   }
    261   return true;
    262 }
    263 
    264 
    265 void
    266 PAIVANA_HTTPD_reverse_shutdown (void)
    267 {
    268   for (struct HttpRequest *hr = hr_head;
    269        NULL != hr;
    270        hr = hr->next)
    271   {
    272     if (GNUNET_YES == hr->suspended)
    273     {
    274       hr->suspended = GNUNET_NO;
    275       MHD_resume_connection (hr->con);
    276     }
    277   }
    278   if (NULL != curl_multi)
    279   {
    280     curl_multi_cleanup (curl_multi);
    281     curl_multi = NULL;
    282   }
    283   if (NULL != curl_download_task)
    284   {
    285     GNUNET_SCHEDULER_cancel (curl_download_task);
    286     curl_download_task = NULL;
    287   }
    288 }
    289 
    290 
    291 /* *************** HTTP handling with cURL ***************** */
    292 
    293 
    294 /**
    295  * Transform _one_ CURL header (gotten from the request) into
    296  * MHD format and put it into the response headers list; mostly
    297  * copies the headers, but makes special adjustments based on
    298  * control requests.
    299  *
    300  * @param buffer curl buffer with a single
    301  *        line of header data; not 0-terminated!
    302  * @param size curl blocksize
    303  * @param nmemb curl blocknumber
    304  * @param cls our `struct HttpRequest *`
    305  * @return size of processed bytes
    306  */
    307 static size_t
    308 curl_check_hdr (void *buffer,
    309                 size_t size,
    310                 size_t nmemb,
    311                 void *cls)
    312 {
    313   struct HttpRequest *hr = cls;
    314   struct HttpResponseHeader *header;
    315   size_t bytes = size * nmemb;
    316   char *ndup;
    317   const char *hdr_type;
    318   char *hdr_val;
    319   char *tok;
    320 
    321   /* Raw line is not guaranteed to be null-terminated.  */
    322   ndup = GNUNET_malloc (bytes + 1);
    323   memcpy (ndup,
    324           buffer,
    325           bytes);
    326   ndup[bytes] = '\0';
    327   hdr_type = strtok (ndup, ":");
    328   if (NULL == hdr_type)
    329   {
    330     GNUNET_free (ndup);
    331     return bytes;
    332   }
    333   hdr_val = strtok (NULL, "");
    334   if (NULL == hdr_val)
    335   {
    336     GNUNET_free (ndup);
    337     return bytes;
    338   }
    339   if (' ' == *hdr_val)
    340     hdr_val++;
    341 
    342   /* MHD does not allow certain characters in values,
    343    * remove those, plus those could alter strings matching.  */
    344   if (NULL != (tok = strchr (hdr_val, '\n')))
    345     *tok = '\0';
    346   if (NULL != (tok = strchr (hdr_val, '\r')))
    347     *tok = '\0';
    348   if (NULL != (tok = strchr (hdr_val, '\t')))
    349     *tok = '\0';
    350   PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n",
    351                      hdr_type,
    352                      hdr_val);
    353   /* Skip "Content-length:" header as it will be wrong, given
    354      that we are man-in-the-middling the connection */
    355   if (0 == strcasecmp (hdr_type,
    356                        MHD_HTTP_HEADER_CONTENT_LENGTH))
    357   {
    358     GNUNET_free (ndup);
    359     return bytes;
    360   }
    361   /* Skip "Connection: Keep-Alive" header, it will be
    362      done by MHD if possible */
    363   if ( (0 == strcasecmp (hdr_type,
    364                          MHD_HTTP_HEADER_CONNECTION)) &&
    365        (0 == strcasecmp (hdr_val,
    366                          "Keep-Alive")) )
    367   {
    368     GNUNET_free (ndup);
    369     return bytes;
    370   }
    371   if (0 != strlen (hdr_val)) /* Rely in MHD to set those */
    372   {
    373     header = GNUNET_new (struct HttpResponseHeader);
    374     header->type = GNUNET_strdup (hdr_type);
    375     header->value = GNUNET_strdup (hdr_val);
    376     GNUNET_CONTAINER_DLL_insert (hr->header_head,
    377                                  hr->header_tail,
    378                                  header);
    379   }
    380   GNUNET_free (ndup);
    381   return bytes;
    382 }
    383 
    384 
    385 /**
    386  * Create the MHD response with CURL's as starting base;
    387  * mainly set the response code and parses the response into
    388  * JSON, if it is such.
    389  *
    390  * @param hr pointer to where to store the new data.  Despite
    391  *        its name, the struct contains response data as well.
    392  * @return #GNUNET_OK if it succeeds.
    393  */
    394 static enum GNUNET_GenericReturnValue
    395 create_mhd_response_from_hr (struct HttpRequest *hr)
    396 {
    397   long resp_code;
    398 
    399   if (NULL != hr->response)
    400   {
    401     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    402                 "Response already set!\n");
    403     return GNUNET_SYSERR;
    404   }
    405   GNUNET_break (CURLE_OK ==
    406                 curl_easy_getinfo (hr->curl,
    407                                    CURLINFO_RESPONSE_CODE,
    408                                    &resp_code));
    409   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    410               "Creating MHD response with code %u\n",
    411               (unsigned int) resp_code);
    412   hr->response_code = resp_code;
    413   if (GNUNET_YES == hr->suspended)
    414   {
    415     MHD_resume_connection (hr->con);
    416     hr->suspended = GNUNET_NO;
    417   }
    418   TALER_MHD_daemon_trigger ();
    419   return GNUNET_OK;
    420 }
    421 
    422 
    423 /**
    424  * Handle response payload data from cURL.
    425  * Copies it into our `io_buf` to make it available to MHD.
    426  *
    427  * @param ptr pointer to the data
    428  * @param size number of blocks of data
    429  * @param nmemb blocksize
    430  * @param ctx our `struct HttpRequest *`
    431  * @return number of bytes handled
    432  */
    433 static size_t
    434 curl_download_cb (void *ptr,
    435                   size_t size,
    436                   size_t nmemb,
    437                   void *ctx)
    438 {
    439   struct HttpRequest *hr = ctx;
    440   size_t total = size * nmemb;
    441 
    442   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    443               "Curl download proceeding\n");
    444 
    445   if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state)
    446   {
    447     /* Web server started with response before we finished
    448        the upload.  In this case, current libcurl decides
    449        to NOT complete the upload, so we should jump in the
    450        state machine to process the download, dropping the
    451        rest of the upload.  This should only really happen
    452        with uploads without "Expect: 100 Continue" and
    453        Web servers responding with an error (i.e. upload
    454        not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
    455     GNUNET_log
    456       (GNUNET_ERROR_TYPE_INFO,
    457       "Stopping %u byte upload: we are already downloading...\n",
    458       (unsigned int) hr->io_len);
    459     hr->io_len = 0;
    460   }
    461 
    462   if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state)
    463   {
    464     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    465                 "Download callback goes to sleep\n");
    466     hr->curl_paused = GNUNET_YES;
    467     return CURL_WRITEFUNC_PAUSE;
    468   }
    469   GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED ==
    470                  hr->state);
    471   if (hr->io_size - hr->io_len < total)
    472   {
    473     GNUNET_assert (total + hr->io_size >= total);
    474     GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
    475     GNUNET_array_grow (hr->io_buf,
    476                        hr->io_size,
    477                        GNUNET_MAX (total + hr->io_len,
    478                                    hr->io_size * 2 + 1024));
    479   }
    480   GNUNET_memcpy (&hr->io_buf[hr->io_len],
    481                  ptr,
    482                  total);
    483   hr->io_len += total;
    484   return total;
    485 }
    486 
    487 
    488 /**
    489  * Ask cURL for the select() sets and schedule cURL operations.
    490  */
    491 static void
    492 curl_download_prepare (void);
    493 
    494 
    495 /**
    496  * cURL callback for uploaded (PUT/POST) data.
    497  * Copies from our `io_buf` to make it available to cURL.
    498  *
    499  * @param buf where to write the data
    500  * @param size number of bytes per member
    501  * @param nmemb number of members available in @a buf
    502  * @param cls our `struct HttpRequest` that generated the data
    503  * @return number of bytes copied to @a buf
    504  */
    505 static size_t
    506 curl_upload_cb (void *buf,
    507                 size_t size,
    508                 size_t nmemb,
    509                 void *cls)
    510 {
    511   struct HttpRequest *hr = cls;
    512   size_t len = size * nmemb;
    513   size_t to_copy;
    514 
    515   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    516               "Upload cb is working...\n");
    517 
    518   if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ||
    519        (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) )
    520   {
    521     GNUNET_log
    522       (GNUNET_ERROR_TYPE_INFO,
    523       "Upload cb aborts: we are already downloading...\n");
    524     return CURL_READFUNC_ABORT;
    525   }
    526 
    527   if ( (0 == hr->io_len) &&
    528        (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) )
    529   {
    530     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    531                 "Pausing CURL UPLOAD, need more data\n");
    532     return CURL_READFUNC_PAUSE;
    533   }
    534 
    535   /**
    536    * We got rescheduled because the download callback was asleep.
    537    * FIXME: can this block be eliminated and the unpausing being
    538    * moved in the last block where we return zero as well?
    539    */
    540   if ( (0 == hr->io_len) &&
    541        (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) )
    542   {
    543     if (GNUNET_YES == hr->curl_paused)
    544     {
    545       hr->curl_paused = GNUNET_NO;
    546       curl_easy_pause (hr->curl,
    547                        CURLPAUSE_CONT);
    548     }
    549     curl_download_prepare ();
    550     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    551                 "Completed CURL UPLOAD\n");
    552     return 0; /* upload finished, can now download */
    553   }
    554   to_copy = GNUNET_MIN (hr->io_len,
    555                         len);
    556   GNUNET_memcpy (buf,
    557                  hr->io_buf,
    558                  to_copy);
    559   /* shift remaining data back to the beginning of the buffer.  */
    560   memmove (hr->io_buf,
    561            &hr->io_buf[to_copy],
    562            hr->io_len - to_copy);
    563   hr->io_len -= to_copy;
    564   if (0 == hr->io_len)
    565   {
    566     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
    567     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    568                 "Completed CURL UPLOAD\n");
    569   }
    570   return to_copy;
    571 }
    572 
    573 
    574 /* ************** helper functions ************* */
    575 
    576 /**
    577  * Extract the hostname from a complete URL.
    578  *
    579  * @param url full fledged URL
    580  * @return pointer to the 0-terminated hostname, to be freed
    581  *         by the caller.
    582  */
    583 static char *
    584 build_host_header (const char *url)
    585 {
    586   #define MARKER "://"
    587 
    588   char *header;
    589   char *end;
    590   char *hostname;
    591   char *dup = GNUNET_strdup (url);
    592 
    593   hostname = strstr (dup,
    594                      MARKER);
    595   hostname += 3;
    596   end = strchrnul (hostname, '/');
    597   *end = '\0';
    598   GNUNET_asprintf (&header,
    599                    "Host: %s",
    600                    hostname);
    601   GNUNET_free (dup);
    602   return header;
    603 }
    604 
    605 
    606 /* ************** main loop of cURL interaction ************* */
    607 
    608 
    609 /**
    610  * Task that is run when we are ready to receive more data
    611  * from curl
    612  *
    613  * @param cls closure
    614  */
    615 static void
    616 curl_task_download (void *cls);
    617 
    618 
    619 /**
    620  * Ask cURL for the select() sets and schedule cURL operations.
    621  */
    622 static void
    623 curl_download_prepare ()
    624 {
    625   CURLMcode mret;
    626   fd_set rs;
    627   fd_set ws;
    628   fd_set es;
    629   int max;
    630   struct GNUNET_NETWORK_FDSet *grs;
    631   struct GNUNET_NETWORK_FDSet *gws;
    632   long to;
    633   struct GNUNET_TIME_Relative rtime;
    634 
    635   if (NULL != curl_download_task)
    636   {
    637     GNUNET_SCHEDULER_cancel (curl_download_task);
    638     curl_download_task = NULL;
    639   }
    640   max = -1;
    641   FD_ZERO (&rs);
    642   FD_ZERO (&ws);
    643   FD_ZERO (&es);
    644   if (CURLM_OK != (mret = curl_multi_fdset (curl_multi,
    645                                             &rs,
    646                                             &ws,
    647                                             &es,
    648                                             &max)))
    649   {
    650     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    651                 "%s failed at %s:%d: `%s'\n",
    652                 "curl_multi_fdset",
    653                 __FILE__,
    654                 __LINE__,
    655                 curl_multi_strerror (mret));
    656     return;
    657   }
    658   to = -1;
    659   GNUNET_break (CURLM_OK ==
    660                 curl_multi_timeout (curl_multi,
    661                                     &to));
    662   if (-1 == to)
    663     rtime = GNUNET_TIME_UNIT_FOREVER_REL;
    664   else
    665     rtime = GNUNET_TIME_relative_multiply
    666               (GNUNET_TIME_UNIT_MILLISECONDS, to);
    667   if (-1 != max)
    668   {
    669     grs = GNUNET_NETWORK_fdset_create ();
    670     gws = GNUNET_NETWORK_fdset_create ();
    671     GNUNET_NETWORK_fdset_copy_native (grs,
    672                                       &rs,
    673                                       max + 1);
    674     GNUNET_NETWORK_fdset_copy_native (gws,
    675                                       &ws,
    676                                       max + 1);
    677     curl_download_task
    678       = GNUNET_SCHEDULER_add_select (
    679           GNUNET_SCHEDULER_PRIORITY_DEFAULT,
    680           rtime,
    681           grs, gws,
    682           &curl_task_download,
    683           curl_multi);
    684     GNUNET_NETWORK_fdset_destroy (gws);
    685     GNUNET_NETWORK_fdset_destroy (grs);
    686   }
    687   else
    688   {
    689     curl_download_task = GNUNET_SCHEDULER_add_delayed (rtime,
    690                                                        &curl_task_download,
    691                                                        curl_multi);
    692   }
    693 }
    694 
    695 
    696 /**
    697  * "Filter" function that translates MHD request headers to
    698  * cURL's.
    699  *
    700  * @param cls our `struct HttpRequest`
    701  * @param kind value kind
    702  * @param key field key
    703  * @param value field value
    704  * @return #MHD_YES to continue to iterate
    705  */
    706 static enum MHD_Result
    707 con_val_iter (void *cls,
    708               enum MHD_ValueKind kind,
    709               const char *key,
    710               const char *value)
    711 {
    712   struct HttpRequest *hr = cls;
    713   char *hdr;
    714   char *new_value = NULL;
    715 
    716   (void) kind;
    717   if (0 == strcmp (MHD_HTTP_HEADER_HOST,
    718                    key))
    719   {
    720     /* We don't take the host header as given in the request.
    721      * We'll instead put the proxied service's hostname in it*/
    722     return MHD_YES;
    723   }
    724   if ((0 == strcmp (MHD_HTTP_HEADER_CONTENT_LENGTH,
    725                     key)))
    726   {
    727     PAIVANA_LOG_INFO (
    728       "Do not set Content-Length for request\n");
    729     return MHD_YES;
    730   }
    731   GNUNET_asprintf (&hdr,
    732                    "%s: %s",
    733                    key,
    734                    value);
    735   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    736               "Adding header `%s' to HTTP request\n",
    737               hdr);
    738   hr->headers = curl_slist_append (hr->headers,
    739                                    hdr);
    740   GNUNET_free (hdr);
    741   GNUNET_free (new_value);
    742   return MHD_YES;
    743 }
    744 
    745 
    746 /**
    747  * Task that is run when we are ready to receive
    748  * more data from curl.
    749  *
    750  * @param cls closure, usually NULL.
    751  */
    752 static void
    753 curl_task_download (void *cls)
    754 {
    755   int running;
    756   int msgnum;
    757   struct CURLMsg *msg;
    758   CURLMcode mret;
    759   struct HttpRequest *hr;
    760 
    761   (void) cls;
    762   curl_download_task = NULL;
    763   do
    764   {
    765     running = 0;
    766     mret = curl_multi_perform (curl_multi,
    767                                &running);
    768     while (NULL != (msg = curl_multi_info_read (curl_multi,
    769                                                 &msgnum)))
    770     {
    771       GNUNET_break
    772         (CURLE_OK == curl_easy_getinfo
    773           (msg->easy_handle,
    774           CURLINFO_PRIVATE,
    775           (char **) &hr));
    776 
    777       if (NULL == hr)
    778       {
    779         GNUNET_break (0);
    780         continue;
    781       }
    782       switch (msg->msg)
    783       {
    784       case CURLMSG_NONE:
    785         /* documentation says this is not used */
    786         GNUNET_break (0);
    787         break;
    788       case CURLMSG_DONE:
    789         switch (msg->data.result)
    790         {
    791         case CURLE_OK:
    792         case CURLE_GOT_NOTHING:
    793           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    794                       "CURL download completed.\n");
    795           hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
    796           if (NULL == hr->response)
    797             GNUNET_assert (GNUNET_OK ==
    798                            create_mhd_response_from_hr (hr));
    799           break;
    800         default:
    801           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    802                       "Download curl failed: %s\n",
    803                       curl_easy_strerror (msg->data.result));
    804           /* FIXME: indicate error somehow?
    805            * close MHD connection badly as well? */
    806           hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
    807           if (GNUNET_YES == hr->suspended)
    808           {
    809             MHD_resume_connection (hr->con);
    810             hr->suspended = GNUNET_NO;
    811           }
    812           TALER_MHD_daemon_trigger ();
    813           break;
    814         }
    815         if (NULL == hr->response)
    816           hr->response = curl_failure_response;
    817         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    818                     "Curl request for `%s' finished (got the response)\n",
    819                     hr->url);
    820         TALER_MHD_daemon_trigger ();
    821         break;
    822       case CURLMSG_LAST:
    823         /* documentation says this is not used */
    824         GNUNET_break (0);
    825         break;
    826       default:
    827         /* unexpected status code */
    828         GNUNET_break (0);
    829         break;
    830       }
    831     }
    832   } while (mret == CURLM_CALL_MULTI_PERFORM);
    833   if (CURLM_OK != mret)
    834     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    835                 "%s failed at %s:%d: `%s'\n",
    836                 "curl_multi_perform",
    837                 __FILE__,
    838                 __LINE__,
    839                 curl_multi_strerror (mret));
    840   if (0 == running)
    841   {
    842     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    843                 "Suspending cURL multi loop,"
    844                 " no more events pending\n");
    845     return; /* nothing more in progress */
    846   }
    847   curl_download_prepare ();
    848 }
    849 
    850 
    851 struct HttpRequest *
    852 PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection,
    853                               const char *url)
    854 {
    855   struct HttpRequest *hr;
    856 
    857   hr = GNUNET_new (struct HttpRequest);
    858   hr->con = connection;
    859   hr->url = GNUNET_strdup (url);
    860   GNUNET_CONTAINER_DLL_insert (hr_head,
    861                                hr_tail,
    862                                hr);
    863   return hr;
    864 }
    865 
    866 
    867 void
    868 PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr)
    869 {
    870   struct HttpResponseHeader *header;
    871 
    872   if (NULL != hr->curl)
    873   {
    874     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    875                 "Resetting cURL handle\n");
    876     curl_multi_remove_handle (curl_multi,
    877                               hr->curl);
    878     curl_easy_cleanup (hr->curl);
    879     hr->curl = NULL;
    880     hr->io_len = 0;
    881   }
    882   if (NULL != hr->headers)
    883   {
    884     curl_slist_free_all (hr->headers);
    885     hr->headers = NULL;
    886   }
    887   if ( (NULL != hr->response) &&
    888        (curl_failure_response != hr->response) )
    889     /* Destroy non-error responses... (?) */
    890     MHD_destroy_response (hr->response);
    891 
    892   for (header = hr->header_head;
    893        header != NULL;
    894        header = hr->header_head)
    895   {
    896     GNUNET_CONTAINER_DLL_remove (hr->header_head,
    897                                  hr->header_tail,
    898                                  header);
    899     GNUNET_free (header->type);
    900     GNUNET_free (header->value);
    901     GNUNET_free (header);
    902   }
    903   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    904               "Proxying of '%s' completely done\n",
    905               hr->url);
    906 
    907   GNUNET_free (hr->url);
    908   GNUNET_free (hr->io_buf);
    909   GNUNET_CONTAINER_DLL_remove (hr_head,
    910                                hr_tail,
    911                                hr);
    912   GNUNET_free (hr);
    913 }
    914 
    915 
    916 /**
    917  * Main MHD callback for reverse proxy.
    918  *
    919  * @param hr the HTTP request context
    920  * @param con MHD connection handle
    921  * @param url the url in the request
    922  * @param meth the HTTP method used ("GET", "PUT", etc.)
    923  * @param ver the HTTP version string (i.e. "HTTP/1.1")
    924  * @param upload_data the data being uploaded (excluding HEADERS,
    925  *        for a POST that fits into memory and that is encoded
    926  *        with a supported encoding, the POST data will NOT be
    927  *        given in upload_data and is instead available as
    928  *        part of MHD_get_connection_values; very large POST
    929  *        data *will* be made available incrementally in
    930  *        upload_data)
    931  * @param upload_data_size set initially to the size of the
    932  *        @a upload_data provided; the method must update this
    933  *        value to the number of bytes NOT processed;
    934  * @return #MHD_YES if the connection was handled successfully,
    935  *         #MHD_NO if the socket must be closed due to a serious
    936  *         error while handling the request
    937  */
    938 enum MHD_Result
    939 PAIVANA_HTTPD_reverse (struct HttpRequest *hr,
    940                        struct MHD_Connection *con,
    941                        const char *url,
    942                        const char *meth,
    943                        const char *ver,
    944                        const char *upload_data,
    945                        size_t *upload_data_size)
    946 {
    947   /* FIXME: make state machine more explicit by
    948      switching on hr->state here! */
    949   if (0 != *upload_data_size)
    950   {
    951     GNUNET_assert
    952       (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state);
    953 
    954     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    955                 "Processing %u bytes UPLOAD\n",
    956                 (unsigned int) *upload_data_size);
    957 
    958     /* Grow the buffer if remaining space isn't enough.  */
    959     if (hr->io_size - hr->io_len < *upload_data_size)
    960     {
    961       /* How can this assertion be false?  */
    962       GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
    963       /* This asserts that upload_data_size > 0, ?  */
    964       GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len);
    965 
    966       GNUNET_array_grow (hr->io_buf,
    967                          hr->io_size,
    968                          GNUNET_MAX
    969                            (hr->io_size * 2 + 1024,
    970                            *upload_data_size + hr->io_len));
    971     }
    972 
    973     /* Finally copy upload data.  */
    974     GNUNET_memcpy (&hr->io_buf[hr->io_len],
    975                    upload_data,
    976                    *upload_data_size);
    977 
    978     hr->io_len += *upload_data_size;
    979     *upload_data_size = 0;
    980 
    981     return MHD_YES;
    982   }
    983 
    984   /* Upload (*from the client*) finished or just a without-body
    985    * request.  */
    986   if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state)
    987   {
    988     hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE;
    989     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    990                 "Finished processing UPLOAD\n");
    991   }
    992 
    993   /* generate curl request to the proxied service. */
    994   if (NULL == hr->curl)
    995   {
    996     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    997                 "Generating curl request\n");
    998     hr->curl = curl_easy_init ();
    999     if (NULL == hr->curl)
   1000     {
   1001       PAIVANA_LOG_ERROR ("Could not init the curl handle\n");
   1002       return MHD_queue_response (con,
   1003                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
   1004                                  curl_failure_response);
   1005     }
   1006 
   1007     /* No need to check whether we're POSTing or PUTting.
   1008      * If not needed, one of the following values will be
   1009      * ignored.*/
   1010     curl_easy_setopt (hr->curl,
   1011                       CURLOPT_POSTFIELDSIZE,
   1012                       hr->io_len);
   1013     curl_easy_setopt (hr->curl,
   1014                       CURLOPT_INFILESIZE,
   1015                       hr->io_len);
   1016     curl_easy_setopt (hr->curl,
   1017                       CURLOPT_HEADERFUNCTION,
   1018                       &curl_check_hdr);
   1019     curl_easy_setopt (hr->curl,
   1020                       CURLOPT_HEADERDATA,
   1021                       hr);
   1022     curl_easy_setopt (hr->curl,
   1023                       CURLOPT_FOLLOWLOCATION,
   1024                       0);
   1025     curl_easy_setopt (hr->curl,
   1026                       CURLOPT_CONNECTTIMEOUT,
   1027                       60L);
   1028     curl_easy_setopt (hr->curl,
   1029                       CURLOPT_TIMEOUT,
   1030                       60L);
   1031     curl_easy_setopt (hr->curl,
   1032                       CURLOPT_NOSIGNAL,
   1033                       1L);
   1034     curl_easy_setopt (hr->curl,
   1035                       CURLOPT_PRIVATE,
   1036                       hr);
   1037     curl_easy_setopt (hr->curl,
   1038                       CURLOPT_VERBOSE,
   1039                       0);
   1040 
   1041     curl_easy_setopt (hr->curl,
   1042                       CURLOPT_READFUNCTION,
   1043                       &curl_upload_cb);
   1044     curl_easy_setopt (hr->curl,
   1045                       CURLOPT_READDATA,
   1046                       hr);
   1047 
   1048     curl_easy_setopt (hr->curl,
   1049                       CURLOPT_WRITEFUNCTION,
   1050                       &curl_download_cb);
   1051     curl_easy_setopt (hr->curl,
   1052                       CURLOPT_WRITEDATA,
   1053                       hr);
   1054     {
   1055       char *curlurl;
   1056       char *host_hdr;
   1057 
   1058       GNUNET_asprintf (&curlurl,
   1059                        "%s%s",
   1060                        target_server_base_url,
   1061                        hr->url);
   1062       curl_easy_setopt (hr->curl,
   1063                         CURLOPT_URL,
   1064                         curlurl);
   1065       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1066                   "Forwarding request to: %s\n",
   1067                   curlurl);
   1068       GNUNET_free (curlurl);
   1069 
   1070       host_hdr = build_host_header (target_server_base_url);
   1071       PAIVANA_LOG_DEBUG ("Faking the host header, %s\n",
   1072                          host_hdr);
   1073       hr->headers = curl_slist_append (hr->headers,
   1074                                        host_hdr);
   1075       GNUNET_free (host_hdr);
   1076     }
   1077 
   1078     // FIXME: support PATCH, etc.
   1079     if (0 == strcasecmp (meth,
   1080                          MHD_HTTP_METHOD_PUT))
   1081     {
   1082       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1083                   "Crafting a CURL PUT request\n");
   1084 
   1085       curl_easy_setopt (hr->curl,
   1086                         CURLOPT_UPLOAD,
   1087                         1L);
   1088       hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1089     }
   1090     else if (0 == strcasecmp (meth,
   1091                               MHD_HTTP_METHOD_POST))
   1092     {
   1093       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1094                   "Crafting a CURL POST request\n");
   1095       curl_easy_setopt (hr->curl,
   1096                         CURLOPT_POST,
   1097                         1L);
   1098       curl_easy_setopt (hr->curl,
   1099                         CURLOPT_VERBOSE,
   1100                         1L);
   1101       hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1102     }
   1103     else if (0 == strcasecmp (meth,
   1104                               MHD_HTTP_METHOD_HEAD))
   1105     {
   1106       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1107       curl_easy_setopt (hr->curl,
   1108                         CURLOPT_NOBODY,
   1109                         1L);
   1110     }
   1111     else if (0 == strcasecmp (meth,
   1112                               MHD_HTTP_METHOD_OPTIONS))
   1113     {
   1114       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1115       curl_easy_setopt (hr->curl,
   1116                         CURLOPT_CUSTOMREQUEST,
   1117                         "OPTIONS");
   1118     }
   1119     else if (0 == strcasecmp (meth,
   1120                               MHD_HTTP_METHOD_GET))
   1121     {
   1122       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1123       curl_easy_setopt (hr->curl,
   1124                         CURLOPT_HTTPGET,
   1125                         1L);
   1126     }
   1127     else
   1128     {
   1129       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1130                   "Unsupported HTTP method `%s'\n",
   1131                   meth);
   1132       curl_easy_cleanup (hr->curl);
   1133       hr->curl = NULL;
   1134       return MHD_NO;
   1135     }
   1136 
   1137     if (CURLM_OK !=
   1138         curl_multi_add_handle (curl_multi,
   1139                                hr->curl))
   1140     {
   1141       GNUNET_break (0);
   1142       curl_easy_cleanup (hr->curl);
   1143       hr->curl = NULL;
   1144       return MHD_NO;
   1145     }
   1146 
   1147     MHD_get_connection_values (con,
   1148                                MHD_HEADER_KIND,
   1149                                &con_val_iter,
   1150                                hr);
   1151 
   1152     curl_easy_setopt (hr->curl,
   1153                       CURLOPT_HTTPHEADER,
   1154                       hr->headers);
   1155     curl_download_prepare ();
   1156 
   1157     return MHD_YES;
   1158   }
   1159 
   1160   if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state)
   1161   {
   1162     GNUNET_assert (GNUNET_NO == hr->suspended);
   1163     MHD_suspend_connection (con);
   1164     hr->suspended = GNUNET_YES;
   1165     return MHD_YES; /* wait for curl */
   1166   }
   1167 
   1168   GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state);
   1169 
   1170   hr->response
   1171     = MHD_create_response_from_buffer_copy (hr->io_len,
   1172                                             hr->io_buf);
   1173   for (struct HttpResponseHeader *header = hr->header_head;
   1174        NULL != header;
   1175        header = header->next)
   1176   {
   1177     const char *value = header->value;
   1178 
   1179     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1180                 "Adding MHD response header %s->%s\n",
   1181                 header->type,
   1182                 value);
   1183     GNUNET_break (MHD_YES ==
   1184                   MHD_add_response_header (hr->response,
   1185                                            header->type,
   1186                                            value));
   1187   }
   1188   TALER_MHD_daemon_trigger ();
   1189 
   1190   return MHD_queue_response (con,
   1191                              hr->response_code,
   1192                              hr->response);
   1193 }