paivana

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

paivana-httpd.c (53885B)


      1 /*
      2   This file is part of GNU Taler
      3   Copyright (C) 2012-2014 GNUnet e.V.
      4   Copyright (C) 2018, 2025 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.c
     27  * @brief HTTP proxy that acts as a GNU Taler paywall
     28  */
     29 #include "platform.h"
     30 #include <microhttpd.h>
     31 #include <curl/curl.h>
     32 #include <gnunet/gnunet_util_lib.h>
     33 #include <microhttpd.h>
     34 #include "paivana_pd.h"
     35 
     36 typedef enum MHD_Result MHD_RESULT;
     37 
     38 #define PAIVANA_LOG_INFO(...)                                  \
     39         GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__)
     40 #define PAIVANA_LOG_DEBUG(...)                                  \
     41         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
     42 #define PAIVANA_LOG_WARNING(...)                                  \
     43         GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__)
     44 #define PAIVANA_LOG_ERROR(...)                                  \
     45         GNUNET_log (GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__)
     46 
     47 #define REQUEST_BUFFER_MAX (1024 * 1024)
     48 #define UNIX_BACKLOG 500
     49 
     50 /**
     51  * Log curl error.
     52  *
     53  * @param level log level
     54  * @param fun name of curl_easy-function that gave the error
     55  * @param rc return code from curl
     56  */
     57 #define LOG_CURL_EASY(level,fun,rc) \
     58         GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \
     59                     __LINE__, \
     60                     curl_easy_strerror (rc))
     61 
     62 /* ******** Datastructures for HTTP handling ********** */
     63 
     64 
     65 /**
     66  * State machine for HTTP requests (per request).
     67  */
     68 enum RequestState
     69 {
     70   /**
     71    * Starting state.
     72    */
     73   REQUEST_STATE_WITH_MHD = 0,
     74 
     75   /**
     76    * We've started receiving upload data from MHD.
     77    */
     78   REQUEST_STATE_CLIENT_UPLOAD_STARTED,
     79 
     80   /**
     81    * Wa have started uploading data to the proxied service.
     82    */
     83   REQUEST_STATE_PROXY_UPLOAD_STARTED,
     84 
     85   /**
     86    * We're done with the upload from MHD.
     87    */
     88   REQUEST_STATE_CLIENT_UPLOAD_DONE,
     89 
     90   /**
     91    * We're done uploading data to the proxied service.
     92    */
     93   REQUEST_STATE_PROXY_UPLOAD_DONE,
     94 
     95   /**
     96    * We've finished uploading data via CURL and can now download.
     97    */
     98   REQUEST_STATE_PROXY_DOWNLOAD_STARTED,
     99 
    100   /**
    101    * We've finished receiving download data from cURL.
    102    */
    103   REQUEST_STATE_PROXY_DOWNLOAD_DONE
    104 };
    105 
    106 
    107 /**
    108  * A header list
    109  */
    110 struct HttpResponseHeader
    111 {
    112   /**
    113    * DLL
    114    */
    115   struct HttpResponseHeader *next;
    116 
    117   /**
    118    * DLL
    119    */
    120   struct HttpResponseHeader *prev;
    121 
    122   /**
    123    * Header type
    124    */
    125   char *type;
    126 
    127   /**
    128    * Header value
    129    */
    130   char *value;
    131 };
    132 
    133 
    134 /**
    135  * A structure for socks requests
    136  */
    137 struct HttpRequest
    138 {
    139 
    140   /**
    141    * Kept in DLL.
    142    */
    143   struct HttpRequest *prev;
    144 
    145   /**
    146    * Kept in DLL.
    147    */
    148   struct HttpRequest *next;
    149 
    150   /**
    151    * MHD request that triggered us.
    152    */
    153   struct MHD_Connection *con;
    154 
    155   /**
    156    * Client socket read task
    157    */
    158   struct GNUNET_SCHEDULER_Task *rtask;
    159 
    160   /**
    161    * Client socket write task
    162    */
    163   struct GNUNET_SCHEDULER_Task *wtask;
    164 
    165   /**
    166    * Hold the response obtained by modifying the original one.
    167    */
    168   struct MHD_Response *mod_response;
    169 
    170   /**
    171    * MHD response object for this request.
    172    */
    173   struct MHD_Response *response;
    174 
    175   /**
    176    * The URL to fetch
    177    */
    178   char *url;
    179 
    180   /**
    181    * Handle to cURL
    182    */
    183   CURL *curl;
    184 
    185   /**
    186    * HTTP request headers for the curl request.
    187    */
    188   struct curl_slist *headers;
    189 
    190   /**
    191    * Headers from response
    192    */
    193   struct HttpResponseHeader *header_head;
    194 
    195   /**
    196    * Headers from response
    197    */
    198   struct HttpResponseHeader *header_tail;
    199 
    200   /**
    201    * Buffer we use for moving data between MHD and
    202    * curl (in both directions).
    203    */
    204   char *io_buf;
    205 
    206   /**
    207    * Number of bytes already in the IO buffer.
    208    */
    209   size_t io_len;
    210 
    211   /**
    212    * Number of bytes allocated for the IO buffer.
    213    */
    214   unsigned int io_size;
    215 
    216   /**
    217    * HTTP response code to give to MHD for the response.
    218    */
    219   unsigned int response_code;
    220 
    221   /**
    222    * Request processing state machine.
    223    */
    224   enum RequestState state;
    225 
    226   /**
    227    * Did we suspend MHD processing?
    228    */
    229   enum GNUNET_GenericReturnValue suspended;
    230 
    231   /**
    232    * Did we pause CURL processing?
    233    */
    234   int curl_paused;
    235 };
    236 
    237 
    238 /* *********************** Globals **************************** */
    239 
    240 /**
    241  * The cURL download task (curl multi API).
    242  */
    243 static struct GNUNET_SCHEDULER_Task *curl_download_task;
    244 
    245 /**
    246  * DLL of active HTTP requests.
    247  */
    248 static struct HttpRequest *hr_head;
    249 
    250 /**
    251  * DLL of active HTTP requests.
    252  */
    253 static struct HttpRequest *hr_tail;
    254 
    255 /**
    256  * The cURL multi handle
    257  */
    258 static CURLM *curl_multi;
    259 
    260 /**
    261  * The daemon handle
    262  */
    263 static struct MHD_Daemon *mhd_daemon;
    264 
    265 /**
    266  * Static paywall response.
    267  */
    268 static struct MHD_Response *paywall;
    269 
    270 /**
    271  * The task ID
    272  */
    273 static struct GNUNET_SCHEDULER_Task *httpd_task;
    274 
    275 /**
    276  * Response we return on cURL failures.
    277  */
    278 static struct MHD_Response *curl_failure_response;
    279 
    280 /**
    281  * Our configuration.
    282  */
    283 static const struct GNUNET_CONFIGURATION_Handle *cfg;
    284 
    285 /**
    286  * Disable paywall check.
    287  */
    288 static int no_check;
    289 
    290 /**
    291  * Destination to which HTTP server we forward requests to.
    292  * Of the format "http://servername:PORT"
    293  */
    294 static char *target_server_base_url;
    295 
    296 /**
    297  * Merchant backend base URL.
    298  */
    299 static char *merchant_base_url;
    300 
    301 /**
    302  * Merchant backend access token.
    303  */
    304 static char *merchant_access_token;
    305 
    306 /**
    307  * Secret for the cookie generation.
    308  */
    309 static struct GNUNET_HashCode paivana_secret;
    310 
    311 
    312 /* ********************* Cookie handling ****************** */
    313 
    314 /**
    315  * Compute access cookie hash for the given @a expiration and @a ca.
    316  *
    317  * @param expiration expiration time of the cookie
    318  * @param ca_len number of bytes in @a ca
    319  * @param ca client address
    320  * @param[out] c set to the cookie hash
    321  */
    322 static void
    323 compute_cookie_hash (struct GNUNET_TIME_Absolute expiration,
    324                      size_t ca_len,
    325                      const void *ca,
    326                      struct GNUNET_HashCode *c)
    327 {
    328   struct GNUNET_TIME_AbsoluteNBO e;
    329 
    330   e = GNUNET_TIME_absolute_hton (expiration);
    331   GNUNET_assert (GNUNET_YES ==
    332                  GNUNET_CRYPTO_kdf (c,
    333                                     sizeof (c),
    334                                     &e,
    335                                     sizeof (e),
    336                                     &paivana_secret,
    337                                     sizeof (paivana_secret),
    338                                     ca,
    339                                     ca_len,
    340                                     NULL,
    341                                     0));
    342 }
    343 
    344 
    345 /**
    346  * Check if the given cookie currently grants access.
    347  *
    348  * @param cookie the cookie
    349  * @param ca_len number of bytes in @a ca
    350  * @param ca client address
    351  * @return true if the cookie is OK
    352  */
    353 static bool
    354 check_cookie (const char *cookie,
    355               size_t ca_len,
    356               const void *ca)
    357 {
    358   const char *dash;
    359   unsigned long long u;
    360   struct GNUNET_HashCode h;
    361   struct GNUNET_HashCode c;
    362   struct GNUNET_TIME_Absolute a;
    363 
    364   dash = strchr (cookie,
    365                  '-');
    366   if (NULL == dash)
    367     return false;
    368   dash++;
    369   if (1 !=
    370       sscanf (cookie,
    371               "%llu-",
    372               &u))
    373     return false;
    374   a.abs_value_us = u;
    375   if (GNUNET_TIME_absolute_is_past (a))
    376     return false;
    377   if (GNUNET_OK !=
    378       GNUNET_STRINGS_string_to_data (dash,
    379                                      strlen (dash),
    380                                      &c,
    381                                      sizeof (c)))
    382     return false;
    383   compute_cookie_hash (a,
    384                        ca_len,
    385                        ca,
    386                        &h);
    387   return (0 ==
    388           GNUNET_memcmp (&c,
    389                          &h));
    390 }
    391 
    392 
    393 /**
    394  * Compute access cookie hash for the given @a expiration and @a ca.
    395  *
    396  * @param expiration expiration time of the cookie
    397  * @param ca_len number of bytes in @a ca
    398  * @param ca client address
    399  * @param[out] c set to the cookie hash
    400  */
    401 static char *
    402 compute_cookie (struct GNUNET_TIME_Absolute expiration,
    403                 size_t ca_len,
    404                 const void *ca)
    405 {
    406   struct GNUNET_HashCode h;
    407   char *end;
    408   char cstr[128];
    409   char *res;
    410 
    411   compute_cookie_hash (expiration,
    412                        ca_len,
    413                        ca,
    414                        &h);
    415   end = GNUNET_STRINGS_data_to_string (&h,
    416                                        sizeof (h),
    417                                        cstr,
    418                                        sizeof (cstr));
    419   *end = '\0';
    420   GNUNET_asprintf (&res,
    421                    "%llu-%s",
    422                    (unsigned long long) expiration.abs_value_us,
    423                    cstr);
    424   return res;
    425 }
    426 
    427 
    428 /* ********************* Global helpers ****************** */
    429 
    430 /**
    431  * Run MHD now, we have extra data ready for the callback.
    432  */
    433 static void
    434 run_mhd_now (void);
    435 
    436 
    437 /* *************** HTTP handling with cURL ***************** */
    438 
    439 
    440 /**
    441  * Transform _one_ CURL header (gotten from the request) into
    442  * MHD format and put it into the response headers list; mostly
    443  * copies the headers, but makes special adjustments based on
    444  * control requests.
    445  *
    446  * @param buffer curl buffer with a single
    447  *        line of header data; not 0-terminated!
    448  * @param size curl blocksize
    449  * @param nmemb curl blocknumber
    450  * @param cls our `struct HttpRequest *`
    451  * @return size of processed bytes
    452  */
    453 static size_t
    454 curl_check_hdr (void *buffer,
    455                 size_t size,
    456                 size_t nmemb,
    457                 void *cls)
    458 {
    459   struct HttpRequest *hr = cls;
    460   struct HttpResponseHeader *header;
    461   size_t bytes = size * nmemb;
    462   char *ndup;
    463   const char *hdr_type;
    464   char *hdr_val;
    465   char *tok;
    466 
    467   /* Raw line is not guaranteed to be null-terminated.  */
    468   ndup = GNUNET_malloc (bytes + 1);
    469   memcpy (ndup,
    470           buffer,
    471           bytes);
    472   ndup[bytes] = '\0';
    473   hdr_type = strtok (ndup, ":");
    474   if (NULL == hdr_type)
    475   {
    476     GNUNET_free (ndup);
    477     return bytes;
    478   }
    479   hdr_val = strtok (NULL, "");
    480   if (NULL == hdr_val)
    481   {
    482     GNUNET_free (ndup);
    483     return bytes;
    484   }
    485   if (' ' == *hdr_val)
    486     hdr_val++;
    487 
    488   /* MHD does not allow certain characters in values,
    489    * remove those, plus those could alter strings matching.  */
    490   if (NULL != (tok = strchr (hdr_val, '\n')))
    491     *tok = '\0';
    492   if (NULL != (tok = strchr (hdr_val, '\r')))
    493     *tok = '\0';
    494   if (NULL != (tok = strchr (hdr_val, '\t')))
    495     *tok = '\0';
    496   PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n",
    497                      hdr_type,
    498                      hdr_val);
    499   /* Skip "Content-length:" header as it will be wrong, given
    500      that we are man-in-the-middling the connection */
    501   if (0 == strcasecmp (hdr_type,
    502                        MHD_HTTP_HEADER_CONTENT_LENGTH))
    503   {
    504     GNUNET_free (ndup);
    505     return bytes;
    506   }
    507   /* Skip "Connection: Keep-Alive" header, it will be
    508      done by MHD if possible */
    509   if ( (0 == strcasecmp (hdr_type,
    510                          MHD_HTTP_HEADER_CONNECTION)) &&
    511        (0 == strcasecmp (hdr_val,
    512                          "Keep-Alive")) )
    513   {
    514     GNUNET_free (ndup);
    515     return bytes;
    516   }
    517   if (0 != strlen (hdr_val)) /* Rely in MHD to set those */
    518   {
    519     header = GNUNET_new (struct HttpResponseHeader);
    520     header->type = GNUNET_strdup (hdr_type);
    521     header->value = GNUNET_strdup (hdr_val);
    522     GNUNET_CONTAINER_DLL_insert (hr->header_head,
    523                                  hr->header_tail,
    524                                  header);
    525   }
    526   GNUNET_free (ndup);
    527   return bytes;
    528 }
    529 
    530 
    531 /**
    532  * Create the MHD response with CURL's as starting base;
    533  * mainly set the response code and parses the response into
    534  * JSON, if it is such.
    535  *
    536  * @param hr pointer to where to store the new data.  Despite
    537  *        its name, the struct contains response data as well.
    538  * @return #GNUNET_OK if it succeeds.
    539  */
    540 static enum GNUNET_GenericReturnValue
    541 create_mhd_response_from_hr (struct HttpRequest *hr)
    542 {
    543   long resp_code;
    544 
    545   if (NULL != hr->response)
    546   {
    547     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    548                 "Response already set!\n");
    549     return GNUNET_SYSERR;
    550   }
    551   GNUNET_break (CURLE_OK ==
    552                 curl_easy_getinfo (hr->curl,
    553                                    CURLINFO_RESPONSE_CODE,
    554                                    &resp_code));
    555   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    556               "Creating MHD response with code %u\n",
    557               (unsigned int) resp_code);
    558   hr->response_code = resp_code;
    559   if (GNUNET_YES == hr->suspended)
    560   {
    561     MHD_resume_connection (hr->con);
    562     hr->suspended = GNUNET_NO;
    563   }
    564   run_mhd_now ();
    565   return GNUNET_OK;
    566 }
    567 
    568 
    569 /**
    570  * Handle response payload data from cURL.
    571  * Copies it into our `io_buf` to make it available to MHD.
    572  *
    573  * @param ptr pointer to the data
    574  * @param size number of blocks of data
    575  * @param nmemb blocksize
    576  * @param ctx our `struct HttpRequest *`
    577  * @return number of bytes handled
    578  */
    579 static size_t
    580 curl_download_cb (void *ptr,
    581                   size_t size,
    582                   size_t nmemb,
    583                   void *ctx)
    584 {
    585   struct HttpRequest *hr = ctx;
    586   size_t total = size * nmemb;
    587 
    588   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    589               "Curl download proceeding\n");
    590 
    591   if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state)
    592   {
    593     /* Web server started with response before we finished
    594        the upload.  In this case, current libcurl decides
    595        to NOT complete the upload, so we should jump in the
    596        state machine to process the download, dropping the
    597        rest of the upload.  This should only really happen
    598        with uploads without "Expect: 100 Continue" and
    599        Web servers responding with an error (i.e. upload
    600        not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
    601     GNUNET_log
    602       (GNUNET_ERROR_TYPE_INFO,
    603       "Stopping %u byte upload: we are already downloading...\n",
    604       (unsigned int) hr->io_len);
    605     hr->io_len = 0;
    606   }
    607 
    608   if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state)
    609   {
    610     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    611                 "Download callback goes to sleep\n");
    612     hr->curl_paused = GNUNET_YES;
    613     return CURL_WRITEFUNC_PAUSE;
    614   }
    615   GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED ==
    616                  hr->state);
    617   if (hr->io_size - hr->io_len < total)
    618   {
    619     GNUNET_assert (total + hr->io_size >= total);
    620     GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
    621     GNUNET_array_grow (hr->io_buf,
    622                        hr->io_size,
    623                        GNUNET_MAX (total + hr->io_len,
    624                                    hr->io_size * 2 + 1024));
    625   }
    626   GNUNET_memcpy (&hr->io_buf[hr->io_len],
    627                  ptr,
    628                  total);
    629   hr->io_len += total;
    630   return total;
    631 }
    632 
    633 
    634 /**
    635  * Ask cURL for the select() sets and schedule cURL operations.
    636  */
    637 static void
    638 curl_download_prepare (void);
    639 
    640 
    641 /**
    642  * cURL callback for uploaded (PUT/POST) data.
    643  * Copies from our `io_buf` to make it available to cURL.
    644  *
    645  * @param buf where to write the data
    646  * @param size number of bytes per member
    647  * @param nmemb number of members available in @a buf
    648  * @param cls our `struct HttpRequest` that generated the data
    649  * @return number of bytes copied to @a buf
    650  */
    651 static size_t
    652 curl_upload_cb (void *buf,
    653                 size_t size,
    654                 size_t nmemb,
    655                 void *cls)
    656 {
    657   struct HttpRequest *hr = cls;
    658   size_t len = size * nmemb;
    659   size_t to_copy;
    660 
    661   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    662               "Upload cb is working...\n");
    663 
    664   if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ||
    665        (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) )
    666   {
    667     GNUNET_log
    668       (GNUNET_ERROR_TYPE_INFO,
    669       "Upload cb aborts: we are already downloading...\n");
    670     return CURL_READFUNC_ABORT;
    671   }
    672 
    673   if ( (0 == hr->io_len) &&
    674        (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) )
    675   {
    676     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    677                 "Pausing CURL UPLOAD, need more data\n");
    678     return CURL_READFUNC_PAUSE;
    679   }
    680 
    681   /**
    682    * We got rescheduled because the download callback was asleep.
    683    * FIXME: can this block be eliminated and the unpausing being
    684    * moved in the last block where we return zero as well?
    685    */
    686   if ( (0 == hr->io_len) &&
    687        (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) )
    688   {
    689     if (GNUNET_YES == hr->curl_paused)
    690     {
    691       hr->curl_paused = GNUNET_NO;
    692       curl_easy_pause (hr->curl,
    693                        CURLPAUSE_CONT);
    694     }
    695     curl_download_prepare ();
    696     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    697                 "Completed CURL UPLOAD\n");
    698     return 0; /* upload finished, can now download */
    699   }
    700   to_copy = GNUNET_MIN (hr->io_len,
    701                         len);
    702   GNUNET_memcpy (buf,
    703                  hr->io_buf,
    704                  to_copy);
    705   /* shift remaining data back to the beginning of the buffer.  */
    706   memmove (hr->io_buf,
    707            &hr->io_buf[to_copy],
    708            hr->io_len - to_copy);
    709   hr->io_len -= to_copy;
    710   if (0 == hr->io_len)
    711   {
    712     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
    713     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    714                 "Completed CURL UPLOAD\n");
    715   }
    716   return to_copy;
    717 }
    718 
    719 
    720 /* ************** helper functions ************* */
    721 
    722 /**
    723  * Extract the hostname from a complete URL.
    724  *
    725  * @param url full fledged URL
    726  * @return pointer to the 0-terminated hostname, to be freed
    727  *         by the caller.
    728  */
    729 static char *
    730 build_host_header (const char *url)
    731 {
    732   #define MARKER "://"
    733 
    734   char *header;
    735   char *end;
    736   char *hostname;
    737   char *dup = GNUNET_strdup (url);
    738 
    739   hostname = strstr (dup,
    740                      MARKER);
    741   hostname += 3;
    742   end = strchrnul (hostname, '/');
    743   *end = '\0';
    744   GNUNET_asprintf (&header,
    745                    "Host: %s",
    746                    hostname);
    747   GNUNET_free (dup);
    748   return header;
    749 }
    750 
    751 
    752 /* ************** main loop of cURL interaction ************* */
    753 
    754 
    755 /**
    756  * Task that is run when we are ready to receive more data
    757  * from curl
    758  *
    759  * @param cls closure
    760  */
    761 static void
    762 curl_task_download (void *cls);
    763 
    764 
    765 /**
    766  * Ask cURL for the select() sets and schedule cURL operations.
    767  */
    768 static void
    769 curl_download_prepare ()
    770 {
    771   CURLMcode mret;
    772   fd_set rs;
    773   fd_set ws;
    774   fd_set es;
    775   int max;
    776   struct GNUNET_NETWORK_FDSet *grs;
    777   struct GNUNET_NETWORK_FDSet *gws;
    778   long to;
    779   struct GNUNET_TIME_Relative rtime;
    780 
    781   if (NULL != curl_download_task)
    782   {
    783     GNUNET_SCHEDULER_cancel (curl_download_task);
    784     curl_download_task = NULL;
    785   }
    786   max = -1;
    787   FD_ZERO (&rs);
    788   FD_ZERO (&ws);
    789   FD_ZERO (&es);
    790   if (CURLM_OK != (mret = curl_multi_fdset (curl_multi,
    791                                             &rs,
    792                                             &ws,
    793                                             &es,
    794                                             &max)))
    795   {
    796     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    797                 "%s failed at %s:%d: `%s'\n",
    798                 "curl_multi_fdset",
    799                 __FILE__,
    800                 __LINE__,
    801                 curl_multi_strerror (mret));
    802     return;
    803   }
    804   to = -1;
    805   GNUNET_break (CURLM_OK ==
    806                 curl_multi_timeout (curl_multi,
    807                                     &to));
    808   if (-1 == to)
    809     rtime = GNUNET_TIME_UNIT_FOREVER_REL;
    810   else
    811     rtime = GNUNET_TIME_relative_multiply
    812               (GNUNET_TIME_UNIT_MILLISECONDS, to);
    813   if (-1 != max)
    814   {
    815     grs = GNUNET_NETWORK_fdset_create ();
    816     gws = GNUNET_NETWORK_fdset_create ();
    817     GNUNET_NETWORK_fdset_copy_native (grs,
    818                                       &rs,
    819                                       max + 1);
    820     GNUNET_NETWORK_fdset_copy_native (gws,
    821                                       &ws,
    822                                       max + 1);
    823     curl_download_task
    824       = GNUNET_SCHEDULER_add_select (
    825           GNUNET_SCHEDULER_PRIORITY_DEFAULT,
    826           rtime,
    827           grs, gws,
    828           &curl_task_download,
    829           curl_multi);
    830     GNUNET_NETWORK_fdset_destroy (gws);
    831     GNUNET_NETWORK_fdset_destroy (grs);
    832   }
    833   else
    834   {
    835     curl_download_task = GNUNET_SCHEDULER_add_delayed
    836                            (rtime,
    837                            &curl_task_download,
    838                            curl_multi);
    839   }
    840 }
    841 
    842 
    843 /**
    844  * "Filter" function that translates MHD request headers to
    845  * cURL's.
    846  *
    847  * @param cls our `struct HttpRequest`
    848  * @param kind value kind
    849  * @param key field key
    850  * @param value field value
    851  * @return #MHD_YES to continue to iterate
    852  */
    853 static MHD_RESULT
    854 con_val_iter (void *cls,
    855               enum MHD_ValueKind kind,
    856               const char *key,
    857               const char *value)
    858 {
    859   struct HttpRequest *hr = cls;
    860   char *hdr;
    861   char *new_value = NULL;
    862 
    863   (void) kind;
    864   if (0 == strcmp (MHD_HTTP_HEADER_HOST,
    865                    key))
    866   {
    867     /* We don't take the host header as given in the request.
    868      * We'll instead put the proxied service's hostname in it*/
    869     return MHD_YES;
    870   }
    871   if ((0 == strcmp (MHD_HTTP_HEADER_CONTENT_LENGTH,
    872                     key)))
    873   {
    874     PAIVANA_LOG_INFO (
    875       "Do not set Content-Length for request\n");
    876     return MHD_YES;
    877   }
    878   GNUNET_asprintf (&hdr,
    879                    "%s: %s",
    880                    key,
    881                    value);
    882   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    883               "Adding header `%s' to HTTP request\n",
    884               hdr);
    885   hr->headers = curl_slist_append (hr->headers,
    886                                    hdr);
    887   GNUNET_free (hdr);
    888   GNUNET_free (new_value);
    889   return MHD_YES;
    890 }
    891 
    892 
    893 /**
    894  * Task that is run when we are ready to receive
    895  * more data from curl.
    896  *
    897  * @param cls closure, usually NULL.
    898  */
    899 static void
    900 curl_task_download (void *cls)
    901 {
    902   int running;
    903   int msgnum;
    904   struct CURLMsg *msg;
    905   CURLMcode mret;
    906   struct HttpRequest *hr;
    907 
    908   (void) cls;
    909   curl_download_task = NULL;
    910   do
    911   {
    912     running = 0;
    913     mret = curl_multi_perform (curl_multi,
    914                                &running);
    915     while (NULL != (msg = curl_multi_info_read (curl_multi,
    916                                                 &msgnum)))
    917     {
    918       GNUNET_break
    919         (CURLE_OK == curl_easy_getinfo
    920           (msg->easy_handle,
    921           CURLINFO_PRIVATE,
    922           (char **) &hr));
    923 
    924       if (NULL == hr)
    925       {
    926         GNUNET_break (0);
    927         continue;
    928       }
    929       switch (msg->msg)
    930       {
    931       case CURLMSG_NONE:
    932         /* documentation says this is not used */
    933         GNUNET_break (0);
    934         break;
    935       case CURLMSG_DONE:
    936         switch (msg->data.result)
    937         {
    938         case CURLE_OK:
    939         case CURLE_GOT_NOTHING:
    940           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    941                       "CURL download completed.\n");
    942           hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
    943           if (NULL == hr->response)
    944             GNUNET_assert (GNUNET_OK ==
    945                            create_mhd_response_from_hr (hr));
    946           break;
    947         default:
    948           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    949                       "Download curl failed: %s\n",
    950                       curl_easy_strerror (msg->data.result));
    951           /* FIXME: indicate error somehow?
    952            * close MHD connection badly as well? */
    953           hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
    954           if (GNUNET_YES == hr->suspended)
    955           {
    956             MHD_resume_connection (hr->con);
    957             hr->suspended = GNUNET_NO;
    958           }
    959           run_mhd_now ();
    960           break;
    961         }
    962         if (NULL == hr->response)
    963           hr->response = curl_failure_response;
    964         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    965                     "Curl request for `%s' finished (got the response)\n",
    966                     hr->url);
    967         run_mhd_now ();
    968         break;
    969       case CURLMSG_LAST:
    970         /* documentation says this is not used */
    971         GNUNET_break (0);
    972         break;
    973       default:
    974         /* unexpected status code */
    975         GNUNET_break (0);
    976         break;
    977       }
    978     }
    979   } while (mret == CURLM_CALL_MULTI_PERFORM);
    980   if (CURLM_OK != mret)
    981     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    982                 "%s failed at %s:%d: `%s'\n",
    983                 "curl_multi_perform",
    984                 __FILE__,
    985                 __LINE__,
    986                 curl_multi_strerror (mret));
    987   if (0 == running)
    988   {
    989     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    990                 "Suspending cURL multi loop,"
    991                 " no more events pending\n");
    992     return; /* nothing more in progress */
    993   }
    994   curl_download_prepare ();
    995 }
    996 
    997 
    998 /* *************** MHD response generation ***************** */
    999 
   1000 
   1001 /**
   1002  * Main MHD callback for handling requests.
   1003  *
   1004  * @param cls unused
   1005  * @param con MHD connection handle
   1006  * @param url the url in the request
   1007  * @param meth the HTTP method used ("GET", "PUT", etc.)
   1008  * @param ver the HTTP version string (i.e. "HTTP/1.1")
   1009  * @param upload_data the data being uploaded (excluding HEADERS,
   1010  *        for a POST that fits into memory and that is encoded
   1011  *        with a supported encoding, the POST data will NOT be
   1012  *        given in upload_data and is instead available as
   1013  *        part of MHD_get_connection_values; very large POST
   1014  *        data *will* be made available incrementally in
   1015  *        upload_data)
   1016  * @param upload_data_size set initially to the size of the
   1017  *        @a upload_data provided; the method must update this
   1018  *        value to the number of bytes NOT processed;
   1019  * @param con_cls pointer to location where we store the
   1020  *        'struct Request'
   1021  * @return #MHD_YES if the connection was handled successfully,
   1022  *         #MHD_NO if the socket must be closed due to a serious
   1023  *         error while handling the request
   1024  */
   1025 static MHD_RESULT
   1026 create_response (void *cls,
   1027                  struct MHD_Connection *con,
   1028                  const char *url,
   1029                  const char *meth,
   1030                  const char *ver,
   1031                  const char *upload_data,
   1032                  size_t *upload_data_size,
   1033                  void **con_cls)
   1034 {
   1035   struct HttpRequest *hr = *con_cls;
   1036 
   1037   (void) cls;
   1038   // FIXME: check if url is one that we reverse proxy!
   1039 
   1040   (void) url;
   1041 
   1042   if (NULL == hr)
   1043   {
   1044     GNUNET_break (0);
   1045     return MHD_NO;
   1046   }
   1047 
   1048   if (REQUEST_STATE_WITH_MHD == hr->state)
   1049   {
   1050     const char *cookie;
   1051     bool ok = (0 != no_check);
   1052 
   1053     cookie = MHD_lookup_connection_value (con,
   1054                                           MHD_COOKIE_KIND,
   1055                                           "Paivana-Cookie");
   1056     if (NULL != cookie)
   1057     {
   1058       const union MHD_ConnectionInfo *ci;
   1059       const struct sockaddr *ca;
   1060       socklen_t ca_len;
   1061 
   1062       ci = MHD_get_connection_info (con,
   1063                                     MHD_CONNECTION_INFO_CLIENT_ADDRESS);
   1064       GNUNET_assert (NULL != ci);
   1065       ca = ci->client_addr;
   1066       switch (ca->sa_family)
   1067       {
   1068       case AF_INET:
   1069         ca_len = sizeof (struct sockaddr_in);
   1070         break;
   1071       case AF_INET6:
   1072         ca_len = sizeof (struct sockaddr_in6);
   1073         break;
   1074       default:
   1075         GNUNET_break (0);
   1076         ca_len = 0;
   1077         break;
   1078       }
   1079       ok = check_cookie (cookie,
   1080                          ca_len,
   1081                          ca);
   1082     }
   1083     if (! ok)
   1084     {
   1085       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1086                   "Request denied\n");
   1087       return MHD_queue_response (con,
   1088                                  MHD_HTTP_PAYMENT_REQUIRED,
   1089                                  paywall);
   1090     }
   1091     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1092                 "Request ok!\n");
   1093     hr->state = REQUEST_STATE_CLIENT_UPLOAD_STARTED;
   1094     /* TODO: hacks for 100 continue suppression would go here! */
   1095     return MHD_YES;
   1096   }
   1097 
   1098   // FIXME: move vanilla reverse proxy logic to another file!
   1099 
   1100   /* continuing to process request */
   1101   if (0 != *upload_data_size)
   1102   {
   1103     GNUNET_assert
   1104       (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state);
   1105 
   1106     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1107                 "Processing %u bytes UPLOAD\n",
   1108                 (unsigned int) *upload_data_size);
   1109 
   1110     /* Grow the buffer if remaining space isn't enough.  */
   1111     if (hr->io_size - hr->io_len < *upload_data_size)
   1112     {
   1113       /* How can this assertion be false?  */
   1114       GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size);
   1115       /* This asserts that upload_data_size > 0, ?  */
   1116       GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len);
   1117 
   1118       GNUNET_array_grow (hr->io_buf,
   1119                          hr->io_size,
   1120                          GNUNET_MAX
   1121                            (hr->io_size * 2 + 1024,
   1122                            *upload_data_size + hr->io_len));
   1123     }
   1124 
   1125     /* Finally copy upload data.  */
   1126     GNUNET_memcpy (&hr->io_buf[hr->io_len],
   1127                    upload_data,
   1128                    *upload_data_size);
   1129 
   1130     hr->io_len += *upload_data_size;
   1131     *upload_data_size = 0;
   1132 
   1133     return MHD_YES;
   1134   }
   1135 
   1136   /* Upload (*from the client*) finished or just a without-body
   1137    * request.  */
   1138   if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state)
   1139   {
   1140     hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE;
   1141     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1142                 "Finished processing UPLOAD\n");
   1143   }
   1144 
   1145   /* generate curl request to the proxied service. */
   1146   if (NULL == hr->curl)
   1147   {
   1148     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1149                 "Generating curl request\n");
   1150     hr->curl = curl_easy_init ();
   1151     if (NULL == hr->curl)
   1152     {
   1153       PAIVANA_LOG_ERROR ("Could not init the curl handle\n");
   1154       return MHD_queue_response (con,
   1155                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
   1156                                  curl_failure_response);
   1157     }
   1158 
   1159     /* No need to check whether we're POSTing or PUTting.
   1160      * If not needed, one of the following values will be
   1161      * ignored.*/
   1162     curl_easy_setopt (hr->curl,
   1163                       CURLOPT_POSTFIELDSIZE,
   1164                       hr->io_len);
   1165     curl_easy_setopt (hr->curl,
   1166                       CURLOPT_INFILESIZE,
   1167                       hr->io_len);
   1168     curl_easy_setopt (hr->curl,
   1169                       CURLOPT_HEADERFUNCTION,
   1170                       &curl_check_hdr);
   1171     curl_easy_setopt (hr->curl,
   1172                       CURLOPT_HEADERDATA,
   1173                       hr);
   1174     curl_easy_setopt (hr->curl,
   1175                       CURLOPT_FOLLOWLOCATION,
   1176                       0);
   1177     curl_easy_setopt (hr->curl,
   1178                       CURLOPT_CONNECTTIMEOUT,
   1179                       60L);
   1180     curl_easy_setopt (hr->curl,
   1181                       CURLOPT_TIMEOUT,
   1182                       60L);
   1183     curl_easy_setopt (hr->curl,
   1184                       CURLOPT_NOSIGNAL,
   1185                       1L);
   1186     curl_easy_setopt (hr->curl,
   1187                       CURLOPT_PRIVATE,
   1188                       hr);
   1189     curl_easy_setopt (hr->curl,
   1190                       CURLOPT_VERBOSE,
   1191                       0);
   1192 
   1193     curl_easy_setopt (hr->curl,
   1194                       CURLOPT_READFUNCTION,
   1195                       &curl_upload_cb);
   1196     curl_easy_setopt (hr->curl,
   1197                       CURLOPT_READDATA,
   1198                       hr);
   1199 
   1200     curl_easy_setopt (hr->curl,
   1201                       CURLOPT_WRITEFUNCTION,
   1202                       &curl_download_cb);
   1203     curl_easy_setopt (hr->curl,
   1204                       CURLOPT_WRITEDATA,
   1205                       hr);
   1206     {
   1207       char *curlurl;
   1208       char *host_hdr;
   1209 
   1210       GNUNET_asprintf (&curlurl,
   1211                        "%s%s",
   1212                        target_server_base_url,
   1213                        hr->url);
   1214       curl_easy_setopt (hr->curl,
   1215                         CURLOPT_URL,
   1216                         curlurl);
   1217       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1218                   "Forwarding request to: %s\n",
   1219                   curlurl);
   1220       GNUNET_free (curlurl);
   1221 
   1222       host_hdr = build_host_header (target_server_base_url);
   1223       PAIVANA_LOG_DEBUG ("Faking the host header, %s\n",
   1224                          host_hdr);
   1225       hr->headers = curl_slist_append (hr->headers,
   1226                                        host_hdr);
   1227       GNUNET_free (host_hdr);
   1228     }
   1229 
   1230     // FIXME: support PATCH, etc.
   1231     if (0 == strcasecmp (meth,
   1232                          MHD_HTTP_METHOD_PUT))
   1233     {
   1234       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1235                   "Crafting a CURL PUT request\n");
   1236 
   1237       curl_easy_setopt (hr->curl,
   1238                         CURLOPT_UPLOAD,
   1239                         1L);
   1240       hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1241     }
   1242     else if (0 == strcasecmp (meth,
   1243                               MHD_HTTP_METHOD_POST))
   1244     {
   1245       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1246                   "Crafting a CURL POST request\n");
   1247       curl_easy_setopt (hr->curl,
   1248                         CURLOPT_POST,
   1249                         1L);
   1250       curl_easy_setopt (hr->curl,
   1251                         CURLOPT_VERBOSE,
   1252                         1L);
   1253       hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1254     }
   1255     else if (0 == strcasecmp (meth,
   1256                               MHD_HTTP_METHOD_HEAD))
   1257     {
   1258       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1259       curl_easy_setopt (hr->curl,
   1260                         CURLOPT_NOBODY,
   1261                         1L);
   1262     }
   1263     else if (0 == strcasecmp (meth,
   1264                               MHD_HTTP_METHOD_OPTIONS))
   1265     {
   1266       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1267       curl_easy_setopt (hr->curl,
   1268                         CURLOPT_CUSTOMREQUEST,
   1269                         "OPTIONS");
   1270     }
   1271     else if (0 == strcasecmp (meth,
   1272                               MHD_HTTP_METHOD_GET))
   1273     {
   1274       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1275       curl_easy_setopt (hr->curl,
   1276                         CURLOPT_HTTPGET,
   1277                         1L);
   1278     }
   1279     else
   1280     {
   1281       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1282                   "Unsupported HTTP method `%s'\n",
   1283                   meth);
   1284       curl_easy_cleanup (hr->curl);
   1285       hr->curl = NULL;
   1286       return MHD_NO;
   1287     }
   1288 
   1289     if (CURLM_OK !=
   1290         curl_multi_add_handle (curl_multi,
   1291                                hr->curl))
   1292     {
   1293       GNUNET_break (0);
   1294       curl_easy_cleanup (hr->curl);
   1295       hr->curl = NULL;
   1296       return MHD_NO;
   1297     }
   1298 
   1299     MHD_get_connection_values (con,
   1300                                MHD_HEADER_KIND,
   1301                                &con_val_iter,
   1302                                hr);
   1303 
   1304     curl_easy_setopt (hr->curl,
   1305                       CURLOPT_HTTPHEADER,
   1306                       hr->headers);
   1307     curl_download_prepare ();
   1308 
   1309     return MHD_YES;
   1310   }
   1311 
   1312   if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state)
   1313   {
   1314     GNUNET_assert (GNUNET_NO == hr->suspended);
   1315     MHD_suspend_connection (con);
   1316     hr->suspended = GNUNET_YES;
   1317     return MHD_YES; /* wait for curl */
   1318   }
   1319 
   1320   GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state);
   1321 
   1322   hr->response
   1323     = MHD_create_response_from_buffer_copy (hr->io_len,
   1324                                             hr->io_buf);
   1325   for (struct HttpResponseHeader *header = hr->header_head;
   1326        NULL != header;
   1327        header = header->next)
   1328   {
   1329     const char *value = header->value;
   1330 
   1331     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1332                 "Adding MHD response header %s->%s\n",
   1333                 header->type,
   1334                 value);
   1335     GNUNET_break (MHD_YES ==
   1336                   MHD_add_response_header (hr->response,
   1337                                            header->type,
   1338                                            value));
   1339   }
   1340   run_mhd_now ();
   1341 
   1342   return MHD_queue_response (con,
   1343                              hr->response_code,
   1344                              hr->response);
   1345 }
   1346 
   1347 
   1348 /* ************ MHD HTTP setup and event loop *************** */
   1349 
   1350 
   1351 /**
   1352  * Function called when MHD decides that we
   1353  * are done with a request.
   1354  *
   1355  * @param cls NULL
   1356  * @param connection connection handle
   1357  * @param con_cls value as set by the last call to
   1358  *        the MHD_AccessHandlerCallback, should be
   1359  *        our `struct HttpRequest *` (set by `create_response()`)
   1360  * @param toe reason for request termination (ignored)
   1361  */
   1362 static void
   1363 mhd_completed_cb (void *cls,
   1364                   struct MHD_Connection *connection,
   1365                   void **con_cls,
   1366                   enum MHD_RequestTerminationCode toe)
   1367 {
   1368   struct HttpRequest *hr = *con_cls;
   1369   struct HttpResponseHeader *header;
   1370 
   1371   (void) cls;
   1372   (void) connection;
   1373   if (NULL == hr)
   1374     return;
   1375   if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe)
   1376     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1377                 "MHD encountered error handling request: %d\n",
   1378                 toe);
   1379   if (NULL != hr->curl)
   1380   {
   1381     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1382                 "Resetting cURL handle\n");
   1383     curl_multi_remove_handle (curl_multi,
   1384                               hr->curl);
   1385     curl_easy_cleanup (hr->curl);
   1386     hr->curl = NULL;
   1387     hr->io_len = 0;
   1388   }
   1389   if (NULL != hr->headers)
   1390   {
   1391     curl_slist_free_all (hr->headers);
   1392     hr->headers = NULL;
   1393   }
   1394   if ( (NULL != hr->response) &&
   1395        (curl_failure_response != hr->response) )
   1396     /* Destroy non-error responses... (?) */
   1397     MHD_destroy_response (hr->response);
   1398 
   1399   for (header = hr->header_head;
   1400        header != NULL;
   1401        header = hr->header_head)
   1402   {
   1403     GNUNET_CONTAINER_DLL_remove (hr->header_head,
   1404                                  hr->header_tail,
   1405                                  header);
   1406     GNUNET_free (header->type);
   1407     GNUNET_free (header->value);
   1408     GNUNET_free (header);
   1409   }
   1410 
   1411   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1412               "Proxying of '%s' completely done\n",
   1413               hr->url);
   1414 
   1415   GNUNET_free (hr->url);
   1416   GNUNET_free (hr->io_buf);
   1417   GNUNET_CONTAINER_DLL_remove (hr_head,
   1418                                hr_tail,
   1419                                hr);
   1420   GNUNET_free (hr);
   1421   *con_cls = NULL;
   1422 }
   1423 
   1424 
   1425 /**
   1426  * Function called when MHD first processes an incoming connection.
   1427  * Gives us the respective URI information.
   1428  *
   1429  * We use this to associate the `struct MHD_Connection` with our
   1430  * internal `struct HttpRequest` data structure (by checking
   1431  * for matching sockets).
   1432  *
   1433  * @param cls the HTTP server handle (a `struct MhdHttpList`)
   1434  * @param url the URL that is being requested
   1435  * @param connection MHD connection object for the request
   1436  * @return the `struct HttpRequest` that this @a connection is for
   1437  */
   1438 static void *
   1439 mhd_log_callback (void *cls,
   1440                   const char *url,
   1441                   struct MHD_Connection *connection)
   1442 {
   1443   struct HttpRequest *hr;
   1444   const union MHD_ConnectionInfo *ci;
   1445 
   1446   (void) cls;
   1447   ci = MHD_get_connection_info (connection,
   1448                                 MHD_CONNECTION_INFO_SOCKET_CONTEXT);
   1449   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1450               "Processing %s\n",
   1451               url);
   1452   if (NULL == ci)
   1453   {
   1454     GNUNET_break (0);
   1455     return NULL;
   1456   }
   1457 
   1458   hr = GNUNET_new (struct HttpRequest);
   1459   hr->con = connection;
   1460   hr->url = GNUNET_strdup (url);
   1461   GNUNET_CONTAINER_DLL_insert (hr_head,
   1462                                hr_tail,
   1463                                hr);
   1464   return hr;
   1465 }
   1466 
   1467 
   1468 /**
   1469  * Kill the MHD daemon.
   1470  */
   1471 static void
   1472 kill_httpd (void)
   1473 {
   1474   MHD_stop_daemon (mhd_daemon);
   1475   mhd_daemon = NULL;
   1476   if (NULL != httpd_task)
   1477   {
   1478     GNUNET_SCHEDULER_cancel (httpd_task);
   1479     httpd_task = NULL;
   1480   }
   1481 }
   1482 
   1483 
   1484 /**
   1485  * Task run whenever HTTP server operations are pending.
   1486  *
   1487  * @param cls the `struct MhdHttpList *`
   1488  *        of the daemon that is being run
   1489  */
   1490 static void
   1491 do_httpd (void *cls);
   1492 
   1493 
   1494 /**
   1495  * Schedule MHD.  This function should be called initially when an
   1496  * MHD is first getting its client socket, and will then
   1497  * automatically always be called later whenever there is work to
   1498  * be done.
   1499  */
   1500 static void
   1501 schedule_httpd (void)
   1502 {
   1503   fd_set rs;
   1504   fd_set ws;
   1505   fd_set es;
   1506   struct GNUNET_NETWORK_FDSet *wrs;
   1507   struct GNUNET_NETWORK_FDSet *wws;
   1508   int max;
   1509   int haveto;
   1510   MHD_UNSIGNED_LONG_LONG timeout;
   1511   struct GNUNET_TIME_Relative tv;
   1512 
   1513   FD_ZERO (&rs);
   1514   FD_ZERO (&ws);
   1515   FD_ZERO (&es);
   1516   max = -1;
   1517   if (MHD_YES !=
   1518       MHD_get_fdset (mhd_daemon,
   1519                      &rs,
   1520                      &ws,
   1521                      &es,
   1522                      &max))
   1523   {
   1524     kill_httpd ();
   1525     return;
   1526   }
   1527   haveto = MHD_get_timeout (mhd_daemon,
   1528                             &timeout);
   1529   if (MHD_YES == haveto)
   1530     tv.rel_value_us = (uint64_t) timeout * 1000LL;
   1531   else
   1532     tv = GNUNET_TIME_UNIT_FOREVER_REL;
   1533   if (-1 != max)
   1534   {
   1535     wrs = GNUNET_NETWORK_fdset_create ();
   1536     wws = GNUNET_NETWORK_fdset_create ();
   1537     GNUNET_NETWORK_fdset_copy_native (wrs,
   1538                                       &rs,
   1539                                       max + 1);
   1540     GNUNET_NETWORK_fdset_copy_native (wws,
   1541                                       &ws,
   1542                                       max + 1);
   1543   }
   1544   else
   1545   {
   1546     wrs = NULL;
   1547     wws = NULL;
   1548   }
   1549   if (NULL != httpd_task)
   1550   {
   1551     GNUNET_SCHEDULER_cancel (httpd_task);
   1552     httpd_task = NULL;
   1553   }
   1554   httpd_task =
   1555     GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
   1556                                  tv,
   1557                                  wrs,
   1558                                  wws,
   1559                                  &do_httpd,
   1560                                  NULL);
   1561   if (NULL != wrs)
   1562     GNUNET_NETWORK_fdset_destroy (wrs);
   1563   if (NULL != wws)
   1564     GNUNET_NETWORK_fdset_destroy (wws);
   1565 }
   1566 
   1567 
   1568 /**
   1569  * Task run whenever HTTP server operations are pending.
   1570  *
   1571  * @param cls NULL
   1572  */
   1573 static void
   1574 do_httpd (void *cls)
   1575 {
   1576   (void) cls;
   1577   httpd_task = NULL;
   1578   MHD_run (mhd_daemon);
   1579   schedule_httpd ();
   1580 }
   1581 
   1582 
   1583 /**
   1584  * Run MHD now, we have extra data ready for the callback.
   1585  */
   1586 static void
   1587 run_mhd_now (void)
   1588 {
   1589   if (NULL != httpd_task)
   1590     GNUNET_SCHEDULER_cancel (httpd_task);
   1591   httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd,
   1592                                          NULL);
   1593 }
   1594 
   1595 
   1596 /* *************** General / main code *************** */
   1597 
   1598 
   1599 /**
   1600  * Task run on shutdown
   1601  *
   1602  * @param cls closure
   1603  */
   1604 static void
   1605 do_shutdown (void *cls)
   1606 {
   1607   (void) cls;
   1608   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1609               "Shutting down...\n");
   1610   /* MHD requires resuming before destroying the daemons */
   1611   for (struct HttpRequest *hr = hr_head;
   1612        NULL != hr;
   1613        hr = hr->next)
   1614   {
   1615     if (GNUNET_YES == hr->suspended)
   1616     {
   1617       hr->suspended = GNUNET_NO;
   1618       MHD_resume_connection (hr->con);
   1619     }
   1620   }
   1621   kill_httpd ();
   1622   if (NULL != curl_multi)
   1623   {
   1624     curl_multi_cleanup (curl_multi);
   1625     curl_multi = NULL;
   1626   }
   1627   if (NULL != curl_download_task)
   1628   {
   1629     GNUNET_SCHEDULER_cancel (curl_download_task);
   1630     curl_download_task = NULL;
   1631   }
   1632   GNUNET_free (target_server_base_url);
   1633 }
   1634 
   1635 
   1636 /**
   1637  * Connect to a unix domain socket.
   1638  *
   1639  * @param path the IPC path
   1640  * @param mode the IPC path mode
   1641  * @return the file descriptor of the connection.
   1642  */
   1643 static int
   1644 open_unix_path (const char *path,
   1645                 mode_t mode)
   1646 {
   1647 
   1648   struct GNUNET_NETWORK_Handle *nh;
   1649   struct sockaddr_un *un;
   1650   int fd;
   1651 
   1652   if (sizeof (un->sun_path) <= strlen (path))
   1653   {
   1654     fprintf (stderr,
   1655              "path `%s' too long\n",
   1656              path);
   1657     return -1;
   1658   }
   1659   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1660               "Creating listen socket '%s' with mode %o\n",
   1661               path,
   1662               mode);
   1663 
   1664   if (GNUNET_OK !=
   1665       GNUNET_DISK_directory_create_for_file (path))
   1666   {
   1667     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
   1668                               "mkdir",
   1669                               path);
   1670   }
   1671 
   1672   un = GNUNET_new (struct sockaddr_un);
   1673   un->sun_family = AF_UNIX;
   1674 
   1675   strncpy (un->sun_path,
   1676            path,
   1677            sizeof (un->sun_path) - 1);
   1678   GNUNET_NETWORK_unix_precheck (un);
   1679 
   1680   if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX,
   1681                                                   SOCK_STREAM,
   1682                                                   0)))
   1683   {
   1684     fprintf (stderr,
   1685              "create failed for AF_UNIX\n");
   1686     GNUNET_free (un);
   1687     return -1;
   1688   }
   1689   if (GNUNET_OK !=
   1690       GNUNET_NETWORK_socket_bind (nh,
   1691                                   (void *) un,
   1692                                   sizeof (struct sockaddr_un)))
   1693   {
   1694     fprintf (stderr,
   1695              "bind failed for AF_UNIX\n");
   1696     GNUNET_free (un);
   1697     GNUNET_NETWORK_socket_close (nh);
   1698     return -1;
   1699   }
   1700   GNUNET_free (un);
   1701   if (GNUNET_OK !=
   1702       GNUNET_NETWORK_socket_listen (nh,
   1703                                     UNIX_BACKLOG))
   1704   {
   1705     fprintf (stderr,
   1706              "listen failed for AF_UNIX\n");
   1707     GNUNET_NETWORK_socket_close (nh);
   1708     return -1;
   1709   }
   1710 
   1711   if (0 != chmod (path,
   1712                   mode))
   1713   {
   1714     fprintf (stderr,
   1715              "chmod failed: %s\n",
   1716              strerror (errno));
   1717     GNUNET_NETWORK_socket_close (nh);
   1718     return -1;
   1719   }
   1720   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1721               "set socket '%s' to mode %o\n",
   1722               path,
   1723               mode);
   1724   fd = GNUNET_NETWORK_get_fd (nh);
   1725   GNUNET_NETWORK_socket_free_memory_only_ (nh);
   1726   return fd;
   1727 }
   1728 
   1729 
   1730 /**
   1731  * Crawl the configuration file and extracts the serving
   1732  * method, TCP vs IPC, and the respective details (port/path/mode)
   1733  *
   1734  * @param ccfg configuration handle.
   1735  * @param port[out] port number to use
   1736  * @param path[out] unix path for IPC.
   1737  * @param mode[out] mode string for @a path.
   1738  * @return #GNUNET_SYSERR if the parsing didn't succeed.
   1739  */
   1740 // FIXME: replace by helper function of Taler?
   1741 static enum GNUNET_GenericReturnValue
   1742 parse_serving_mean (const struct GNUNET_CONFIGURATION_Handle *ccfg,
   1743                     uint16_t *port,
   1744                     char **path,
   1745                     mode_t *mode)
   1746 {
   1747 
   1748   const char *serve;
   1749   const char *choices[] = {"tcp", "unix", NULL};
   1750   char *modestring;
   1751   unsigned long long port_ull;
   1752 
   1753   if (GNUNET_OK !=
   1754       GNUNET_CONFIGURATION_get_value_choice (ccfg,
   1755                                              "paivana",
   1756                                              "SERVE",
   1757                                              choices,
   1758                                              &serve))
   1759   {
   1760     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1761                                "paivana",
   1762                                "SERVE");
   1763     return GNUNET_SYSERR;
   1764   }
   1765 
   1766   if (0 == strcmp ("tcp", serve))
   1767   {
   1768     *path = NULL;
   1769 
   1770     if (GNUNET_OK !=
   1771         GNUNET_CONFIGURATION_get_value_number (ccfg,
   1772                                                "paivana",
   1773                                                "HTTP_PORT",
   1774                                                &port_ull))
   1775     {
   1776       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1777                                  "paivana",
   1778                                  "HTTP_PORT");
   1779       return GNUNET_SYSERR;
   1780     }
   1781     *port = (uint16_t) port_ull;
   1782     return GNUNET_OK;
   1783   }
   1784 
   1785   /* serving via unix */
   1786 
   1787   if (GNUNET_OK !=
   1788       GNUNET_CONFIGURATION_get_value_filename (ccfg,
   1789                                                "paivana",
   1790                                                "SERVE_UNIXPATH",
   1791                                                path))
   1792   {
   1793     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1794                                "paivana",
   1795                                "SERVE_UNIXPATH");
   1796     return GNUNET_SYSERR;
   1797   }
   1798 
   1799   if (GNUNET_OK !=
   1800       GNUNET_CONFIGURATION_get_value_string (ccfg,
   1801                                              "paivana",
   1802                                              "SERVE_UNIXMODE",
   1803                                              &modestring))
   1804   {
   1805     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1806                                "paivana",
   1807                                "SERVE_UNIXMODE");
   1808     return GNUNET_SYSERR;
   1809   }
   1810 
   1811   errno = 0;
   1812   *mode = (mode_t) strtoul (modestring, NULL, 8);
   1813 
   1814   if (0 != errno)
   1815   {
   1816     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1817                                "paivana",
   1818                                "SERVE_UNIXMODE",
   1819                                "must be octal number");
   1820     GNUNET_free (modestring);
   1821     return GNUNET_SYSERR;
   1822   }
   1823   GNUNET_free (modestring);
   1824   return GNUNET_OK;
   1825 }
   1826 
   1827 
   1828 /**
   1829  * Try to initialize the paywall response.
   1830  */
   1831 static bool
   1832 load_paywall ()
   1833 {
   1834   char *tpath;
   1835   char *fn;
   1836   int fd;
   1837   struct stat sb;
   1838 
   1839   tpath = GNUNET_OS_installation_get_path (PAIVANA_project_data (),
   1840                                            GNUNET_OS_IPK_DATADIR);
   1841   GNUNET_asprintf (&fn,
   1842                    "%s/paywall.html",
   1843                    tpath);
   1844   GNUNET_free (tpath);
   1845   fd = open (fn,
   1846              O_RDONLY);
   1847   if (-1 == fd)
   1848   {
   1849     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
   1850                               "open",
   1851                               fn);
   1852     GNUNET_free (fn);
   1853     return false;
   1854   }
   1855   if (0 !=
   1856       fstat (fd,
   1857              &sb))
   1858   {
   1859     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
   1860                               "stat",
   1861                               fn);
   1862     GNUNET_free (fn);
   1863     GNUNET_break (0 == close (fd));
   1864     return false;
   1865   }
   1866   GNUNET_free (fn);
   1867   paywall = MHD_create_response_from_fd (sb.st_size,
   1868                                          fd);
   1869   if (NULL == paywall)
   1870     return false;
   1871   GNUNET_break (MHD_YES ==
   1872                 MHD_add_response_header (paywall,
   1873                                          MHD_HTTP_HEADER_CONTENT_TYPE,
   1874                                          "text/html"));
   1875   return true;
   1876 }
   1877 
   1878 
   1879 /**
   1880  * Main function that will be run.  Main tasks are (1) init. the
   1881  * curl infrastructure (curl_global_init() / curl_multi_init()),
   1882  * then fetch the HTTP port where its Web service should listen at,
   1883  * and finally start MHD on that port.
   1884  *
   1885  * @param cls closure
   1886  * @param args remaining command-line arguments
   1887  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1888  * @param c configuration
   1889  */
   1890 static void
   1891 run (void *cls,
   1892      char *const *args,
   1893      const char *cfgfile,
   1894      const struct GNUNET_CONFIGURATION_Handle *c)
   1895 {
   1896   uint16_t port = 0;
   1897   int fh = -1;
   1898   char *serve_unixpath = NULL;
   1899   mode_t serve_unixmode = 0;
   1900   char *secret;
   1901 
   1902   (void) cls;
   1903   (void) args;
   1904   (void) cfgfile;
   1905   cfg = c;
   1906   // FIXME: initialize database logic!
   1907 
   1908   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
   1909   {
   1910     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1911                 "cURL global init failed!\n");
   1912     GNUNET_SCHEDULER_shutdown ();
   1913     return;
   1914   }
   1915   if (! load_paywall ())
   1916   {
   1917     GNUNET_SCHEDULER_shutdown ();
   1918     return;
   1919   }
   1920   if (NULL == (curl_multi = curl_multi_init ()))
   1921   {
   1922     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1923                 "Failed to create cURL multi handle!\n");
   1924     return;
   1925   }
   1926 
   1927   /* No need to check return value.  If given, we take,
   1928    * otherwise it stays zero.  */
   1929   if (GNUNET_OK !=
   1930       GNUNET_CONFIGURATION_get_value_string (
   1931         c,
   1932         "paivana",
   1933         "DESTINATION_BASE_URL",
   1934         &target_server_base_url))
   1935   {
   1936     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1937                                "paivana",
   1938                                "DESTINATION_BASE_URL");
   1939     GNUNET_SCHEDULER_shutdown ();
   1940     return;
   1941   }
   1942   if (GNUNET_OK !=
   1943       GNUNET_CONFIGURATION_get_value_string (
   1944         c,
   1945         "paivana",
   1946         "MERCHANT_BACKEND_URL",
   1947         &merchant_base_url))
   1948   {
   1949     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1950                                "paivana",
   1951                                "MERCHANT_BACKEND_URL");
   1952     GNUNET_SCHEDULER_shutdown ();
   1953     return;
   1954   }
   1955   if (GNUNET_OK !=
   1956       GNUNET_CONFIGURATION_get_value_string (
   1957         c,
   1958         "paivana",
   1959         "MERCHANT_ACCESS_TOKEN",
   1960         &merchant_access_token))
   1961   {
   1962     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1963                                "paivana",
   1964                                "MERCHANT_ACCESS_TOKEN");
   1965     GNUNET_SCHEDULER_shutdown ();
   1966     return;
   1967   }
   1968   if (GNUNET_OK !=
   1969       GNUNET_CONFIGURATION_get_value_string (
   1970         c,
   1971         "paivana",
   1972         "SECRET",
   1973         &secret))
   1974   {
   1975     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
   1976                                "paivana",
   1977                                "SECRET");
   1978     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
   1979                                 &paivana_secret,
   1980                                 sizeof (paivana_secret));
   1981   }
   1982   else
   1983   {
   1984     GNUNET_CRYPTO_hash (secret,
   1985                         strlen (secret),
   1986                         &paivana_secret);
   1987     GNUNET_free (secret);
   1988   }
   1989 
   1990   if (GNUNET_SYSERR ==
   1991       parse_serving_mean (c,
   1992                           &port,
   1993                           &serve_unixpath,
   1994                           &serve_unixmode))
   1995   {
   1996     GNUNET_break (0);
   1997     GNUNET_SCHEDULER_shutdown ();
   1998     return;
   1999   }
   2000 
   2001   if (NULL != serve_unixpath)
   2002   {
   2003     /* Connect the 'fh' socket.  */
   2004     fh = open_unix_path (serve_unixpath,
   2005                          serve_unixmode);
   2006 
   2007     GNUNET_assert (-1 != fh);
   2008   }
   2009 
   2010   /* start MHD daemon for HTTP */
   2011   mhd_daemon = MHD_start_daemon
   2012                  (MHD_USE_DEBUG | MHD_ALLOW_SUSPEND_RESUME | MHD_USE_DUAL_STACK,
   2013                  (-1 == fh) ? (uint16_t) port : 0,
   2014                  NULL, NULL,
   2015                  &create_response, NULL,
   2016                  MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
   2017                  MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL,
   2018                  MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL,
   2019                  MHD_OPTION_LISTEN_SOCKET, fh,
   2020                  MHD_OPTION_END);
   2021 
   2022   if (NULL == mhd_daemon)
   2023   {
   2024     GNUNET_break (0);
   2025     GNUNET_SCHEDULER_shutdown ();
   2026     return;
   2027   }
   2028   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   2029                                  NULL);
   2030   run_mhd_now ();
   2031 }
   2032 
   2033 
   2034 /**
   2035  * Main function.
   2036  */
   2037 int
   2038 main (int argc,
   2039       char *const *argv)
   2040 {
   2041   struct GNUNET_GETOPT_CommandLineOption options[] = {
   2042     GNUNET_GETOPT_option_flag (
   2043       'n',
   2044       "no-payment",
   2045       gettext_noop (
   2046         "disables payment, useful for testing reverse-proxy only"),
   2047       &no_check),
   2048     GNUNET_GETOPT_OPTION_END
   2049   };
   2050 
   2051   return GNUNET_PROGRAM_run (PAIVANA_project_data (),
   2052                              argc,
   2053                              argv,
   2054                              "paivana-httpd",
   2055                              "reverse proxy requesting Taler payment",
   2056                              options,
   2057                              &run,
   2058                              NULL);
   2059 }
   2060 
   2061 
   2062 /* end of paivana-httpd.c */