paivana

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

paivana-httpd_daemon.c (10319B)


      1 /*
      2      This file is part of GNUnet.
      3      Copyright (C) 2026 Taler Systems SA
      4 
      5      Paivana is free software; you can redistribute it and/or
      6      modify it under the terms of the GNU General Public License
      7      as published by the Free Software Foundation; either version
      8      3, or (at your option) any later version.
      9 
     10      Paivana is distributed in the hope that it will be useful,
     11      but WITHOUT ANY WARRANTY; without even the implied warranty
     12      of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
     13      the GNU General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public
     16      License along with Paivana; see the file COPYING.  If not,
     17      write to the Free Software Foundation, Inc., 51 Franklin
     18      Street, Fifth Floor, Boston, MA 02110-1301, USA.
     19 */
     20 
     21 /**
     22  * @author Christian Grothoff
     23  * @file paivana-httpd_daemon.c
     24  * @brief daemon functions
     25  */
     26 
     27 #include "platform.h"
     28 #include <curl/curl.h>
     29 #include <gnunet/gnunet_util_lib.h>
     30 #include <gnunet/gnunet_curl_lib.h>
     31 #include <taler/taler_mhd_lib.h>
     32 #include "paivana-httpd_cookie.h"
     33 #include "paivana-httpd_daemon.h"
     34 #include "paivana-httpd_helper.h"
     35 #include "paivana-httpd_pay.h"
     36 #include "paivana-httpd_reverse.h"
     37 #include "paivana-httpd_templates.h"
     38 
     39 
     40 struct RequestContext
     41 {
     42 
     43   /**
     44    * HTTP connection to the client.
     45    */
     46   struct MHD_Connection *connection;
     47 
     48   /**
     49    * Handle for request forwarding as reverse proxy.
     50    */
     51   struct HttpRequest *hr;
     52 
     53   /**
     54    * Handle for processing actual payment.
     55    */
     56   struct PayRequest *hp;
     57 
     58   /**
     59    * Full request URL.
     60    */
     61   char *url;
     62 
     63   /**
     64    * True if this is a POST to the .well-known/paivana endpoint.
     65    */
     66   bool is_paivana;
     67 
     68   /**
     69    * We are past the paywall, forward to client.
     70    */
     71   bool do_forward;
     72 };
     73 
     74 
     75 /**
     76  * Set to true if we started a daemon.
     77  */
     78 static bool have_daemons;
     79 
     80 
     81 /**
     82  * Main MHD callback for handling requests.
     83  *
     84  * @param cls unused
     85  * @param con MHD connection handle
     86  * @param url the url in the request
     87  * @param meth the HTTP method used ("GET", "PUT", etc.)
     88  * @param ver the HTTP version string (i.e. "HTTP/1.1")
     89  * @param upload_data the data being uploaded (excluding HEADERS,
     90  *        for a POST that fits into memory and that is encoded
     91  *        with a supported encoding, the POST data will NOT be
     92  *        given in upload_data and is instead available as
     93  *        part of MHD_get_connection_values; very large POST
     94  *        data *will* be made available incrementally in
     95  *        upload_data)
     96  * @param upload_data_size set initially to the size of the
     97  *        @a upload_data provided; the method must update this
     98  *        value to the number of bytes NOT processed;
     99  * @param con_cls pointer to location where we store the
    100  *        'struct Request'
    101  * @return #MHD_YES if the connection was handled successfully,
    102  *         #MHD_NO if the socket must be closed due to a serious
    103  *         error while handling the request
    104  */
    105 static enum MHD_Result
    106 create_response (void *cls,
    107                  struct MHD_Connection *con,
    108                  const char *url,
    109                  const char *meth,
    110                  const char *ver,
    111                  const char *upload_data,
    112                  size_t *upload_data_size,
    113                  void **con_cls)
    114 {
    115   struct RequestContext *rc = *con_cls;
    116   const char *cookie;
    117   bool ok = false;
    118   struct GNUNET_Buffer buf;
    119   char *website;
    120 
    121   (void) cls;
    122   memset (&buf,
    123           0,
    124           sizeof (buf));
    125   if ( (! rc->is_paivana) &&
    126        (0 == strcmp (url,
    127                      "/.well-known/paivana")) &&
    128        (0 == strcasecmp (meth,
    129                          MHD_HTTP_METHOD_POST)) )
    130   {
    131     rc->is_paivana = true;
    132   }
    133   if (rc->is_paivana)
    134   {
    135     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    136                 "Client POSTed payment, checking validity\n");
    137     if (NULL == rc->hp)
    138       rc->hp = PAIVANA_HTTPD_payment_create (rc->connection);
    139     return PAIVANA_HTTPD_payment_handle (rc->hp,
    140                                          upload_data,
    141                                          upload_data_size);
    142   }
    143 
    144   if (PH_have_whitelist_ex && (! rc->do_forward))
    145   {
    146     rc->do_forward = (0 ==
    147 		      regcomp (&PH_whitelist_ex,
    148 			       url,
    149 			       REG_NOSUB | REG_EXTENDED));
    150   }
    151   
    152   if (rc->do_forward)
    153     goto do_forward;
    154 
    155   if ( (0 == strncmp (url,
    156                       "/.well-known/paivana/templates/",
    157                       strlen ("/.well-known/paivana/templates/"))) &&
    158        (0 == strcasecmp (meth,
    159                          MHD_HTTP_METHOD_GET)) )
    160   {
    161     const char *id = &url[strlen ("/.well-known/paivana/templates/")];
    162 
    163     return PAIVANA_HTTPD_return_template (rc->connection,
    164                                           id);
    165   }
    166 
    167   if (! PAIVANA_HTTPD_get_base_url (con,
    168                                     &buf))
    169   {
    170     GNUNET_break (0);
    171     return TALER_MHD_reply_with_error (
    172       con,
    173       MHD_HTTP_BAD_REQUEST,
    174       TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
    175       "Host or X-Forwarded-Host required");
    176   }
    177   GNUNET_buffer_write_str (&buf,
    178                            url);
    179   website = GNUNET_buffer_reap_str (&buf);
    180   cookie = MHD_lookup_connection_value (con,
    181                                         MHD_COOKIE_KIND,
    182                                         "Paivana-Cookie");
    183   if (NULL != cookie)
    184   {
    185     void *ca;
    186     size_t ca_len;
    187 
    188     GNUNET_break (PAIVANA_HTTPD_get_client_address (con,
    189                                                     &ca,
    190                                                     &ca_len));
    191     ok = PAIVANA_HTTPD_check_cookie (cookie,
    192                                      website,
    193                                      ca_len,
    194                                      ca);
    195     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    196                 "Client sent cookie %s for %s: %s\n",
    197                 cookie,
    198                 website,
    199                 ok ? "good" : "invalid");
    200     GNUNET_free (ca);
    201   }
    202   if (! ok)
    203   {
    204     enum GNUNET_GenericReturnValue ret;
    205 
    206     ret = PAIVANA_HTTPD_search_templates (con,
    207                                           website);
    208     GNUNET_free (website);
    209     if (GNUNET_SYSERR != ret)
    210     {
    211       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    212                   "Payment required, sending paywall page %s\n",
    213                   (GNUNET_OK == ret) ? "ok" : "failed");
    214       return (GNUNET_OK == ret) ? MHD_YES : MHD_NO;
    215     }
    216   }
    217   GNUNET_free (website);
    218   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    219               "Request OK, no paywall applies!\n");
    220   rc->do_forward = true;
    221 do_forward:
    222   if (NULL == rc->hr)
    223     rc->hr = PAIVANA_HTTPD_reverse_create (rc->connection,
    224                                            rc->url);
    225   return PAIVANA_HTTPD_reverse (rc->hr,
    226                                 con,
    227                                 url,
    228                                 meth,
    229                                 ver,
    230                                 upload_data,
    231                                 upload_data_size);
    232 }
    233 
    234 
    235 /**
    236  * Function called when MHD decides that we
    237  * are done with a request.
    238  *
    239  * @param cls NULL
    240  * @param connection connection handle
    241  * @param con_cls value as set by the last call to
    242  *        the MHD_AccessHandlerCallback, should be
    243  *        our `struct RequestContext *` (created in `mhd_log_callback()`)
    244  * @param toe reason for request termination (ignored)
    245  */
    246 static void
    247 mhd_completed_cb (void *cls,
    248                   struct MHD_Connection *connection,
    249                   void **con_cls,
    250                   enum MHD_RequestTerminationCode toe)
    251 {
    252   struct RequestContext *rc = *con_cls;
    253 
    254   (void) cls;
    255   (void) connection;
    256   if (NULL == rc)
    257     return;
    258   if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe)
    259     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    260                 "MHD encountered error handling request to %s: %d\n",
    261                 rc->url,
    262                 toe);
    263   if (NULL != rc->hr)
    264     PAIVANA_HTTPD_reverse_cleanup (rc->hr);
    265   if (NULL != rc->hp)
    266     PAIVANA_HTTPD_payment_destroy (rc->hp);
    267   GNUNET_free (rc->url);
    268   GNUNET_free (rc);
    269   *con_cls = NULL;
    270 }
    271 
    272 
    273 /**
    274  * Function called when MHD first processes an incoming connection.
    275  * Gives us the respective URI information.
    276  *
    277  * We use this to associate the `struct MHD_Connection` with our
    278  * internal `struct HttpRequest` data structure (by checking
    279  * for matching sockets).
    280  *
    281  * @param cls the HTTP server handle (a `struct MhdHttpList`)
    282  * @param url the URL that is being requested
    283  * @param connection MHD connection object for the request
    284  * @return the `struct RequestContext` that this @a connection is for
    285  */
    286 static void *
    287 mhd_log_callback (void *cls,
    288                   const char *url,
    289                   struct MHD_Connection *connection)
    290 {
    291   struct RequestContext *rc;
    292 
    293   rc = GNUNET_new (struct RequestContext);
    294   rc->connection = connection;
    295   rc->url = GNUNET_strdup (url);
    296   rc->do_forward = (1 == PH_no_check);
    297   return rc;
    298 }
    299 
    300 
    301 /**
    302  * Callback invoked on every listen socket to start the
    303  * respective MHD HTTP daemon.
    304  *
    305  * @param cls unused
    306  * @param lsock the listen socket
    307  */
    308 static void
    309 start_daemon (void *cls,
    310               int lsock)
    311 {
    312   struct MHD_Daemon *mhd;
    313 
    314   (void) cls;
    315   GNUNET_assert (-1 != lsock);
    316   mhd = MHD_start_daemon (
    317     MHD_USE_DEBUG
    318     | MHD_ALLOW_SUSPEND_RESUME
    319     | MHD_USE_DUAL_STACK,
    320     0,
    321     NULL, NULL,
    322     &create_response, NULL,
    323     MHD_OPTION_LISTEN_SOCKET,
    324     lsock,
    325     MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
    326     MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL,
    327     MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL,
    328     MHD_OPTION_END);
    329 
    330   if (NULL == mhd)
    331   {
    332     GNUNET_break (0);
    333     GNUNET_SCHEDULER_shutdown ();
    334     return;
    335   }
    336   have_daemons = true;
    337   TALER_MHD_daemon_start (mhd);
    338 }
    339 
    340 
    341 void
    342 PAIVANA_HTTPD_serve_requests ()
    343 {
    344   enum GNUNET_GenericReturnValue ret;
    345 
    346   ret = TALER_MHD_listen_bind (PH_cfg,
    347                                "paivana",
    348                                &start_daemon,
    349                                NULL);
    350   switch (ret)
    351   {
    352   case GNUNET_SYSERR:
    353     PH_global_ret = EXIT_NOTCONFIGURED;
    354     GNUNET_SCHEDULER_shutdown ();
    355     return;
    356   case GNUNET_NO:
    357     if (! have_daemons)
    358     {
    359       PH_global_ret = EXIT_NOTCONFIGURED;
    360       GNUNET_SCHEDULER_shutdown ();
    361       return;
    362     }
    363     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    364                 "Could not open all configured listen sockets\n");
    365     break;
    366   case GNUNET_OK:
    367     break;
    368   }
    369 }