paivana

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

paivana-httpd_daemon.c (9602B)


      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                          "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 (rc->do_forward)
    145   {
    146     if (NULL == rc->hr)
    147       rc->hr = PAIVANA_HTTPD_reverse_create (rc->connection,
    148                                              rc->url);
    149     return PAIVANA_HTTPD_reverse (rc->hr,
    150                                   con,
    151                                   url,
    152                                   meth,
    153                                   ver,
    154                                   upload_data,
    155                                   upload_data_size);
    156   }
    157 
    158   if (! PAIVANA_HTTPD_get_base_url (con,
    159                                     &buf))
    160   {
    161     GNUNET_break (0);
    162     return TALER_MHD_reply_with_error (
    163       con,
    164       MHD_HTTP_BAD_REQUEST,
    165       TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
    166       "Host or X-Forwarded-Host required");
    167   }
    168   GNUNET_buffer_write_str (&buf,
    169                            url);
    170   website = GNUNET_buffer_reap_str (&buf);
    171 
    172   cookie = MHD_lookup_connection_value (con,
    173                                         MHD_COOKIE_KIND,
    174                                         "Paivana-Cookie");
    175   if (NULL != cookie)
    176   {
    177     void *ca;
    178     size_t ca_len;
    179 
    180     GNUNET_break (PAIVANA_HTTPD_get_client_address (con,
    181                                                     &ca,
    182                                                     &ca_len));
    183     ok = PAIVANA_HTTPD_check_cookie (cookie,
    184                                      website,
    185                                      ca_len,
    186                                      ca);
    187     GNUNET_free (ca);
    188   }
    189   if (! ok)
    190   {
    191     enum GNUNET_GenericReturnValue ret;
    192 
    193     ret = PAIVANA_HTTPD_search_templates (con,
    194                                           website);
    195     GNUNET_free (website);
    196     if (GNUNET_SYSERR != ret)
    197     {
    198       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    199                   "Payment required, sending paywall page %s\n",
    200                   (GNUNET_OK == ret) ? "ok" : "failed");
    201       return (GNUNET_OK == ret) ? MHD_YES : MHD_NO;
    202     }
    203   }
    204   GNUNET_free (website);
    205   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    206               "Request OK, no paywall applies!\n");
    207   rc->do_forward = true;
    208   /* FIXME: code for 100 continue suppression should go here! */
    209   return MHD_YES;
    210 }
    211 
    212 
    213 /**
    214  * Function called when MHD decides that we
    215  * are done with a request.
    216  *
    217  * @param cls NULL
    218  * @param connection connection handle
    219  * @param con_cls value as set by the last call to
    220  *        the MHD_AccessHandlerCallback, should be
    221  *        our `struct RequestContext *` (created in `mhd_log_callback()`)
    222  * @param toe reason for request termination (ignored)
    223  */
    224 static void
    225 mhd_completed_cb (void *cls,
    226                   struct MHD_Connection *connection,
    227                   void **con_cls,
    228                   enum MHD_RequestTerminationCode toe)
    229 {
    230   struct RequestContext *rc = *con_cls;
    231 
    232   (void) cls;
    233   (void) connection;
    234   if (NULL == rc)
    235     return;
    236   if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe)
    237     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    238                 "MHD encountered error handling request to %s: %d\n",
    239                 rc->url,
    240                 toe);
    241   if (NULL != rc->hr)
    242     PAIVANA_HTTPD_reverse_cleanup (rc->hr);
    243   if (NULL != rc->hp)
    244     PAIVANA_HTTPD_payment_destroy (rc->hp);
    245   GNUNET_free (rc->url);
    246   GNUNET_free (rc);
    247   *con_cls = NULL;
    248 }
    249 
    250 
    251 /**
    252  * Function called when MHD first processes an incoming connection.
    253  * Gives us the respective URI information.
    254  *
    255  * We use this to associate the `struct MHD_Connection` with our
    256  * internal `struct HttpRequest` data structure (by checking
    257  * for matching sockets).
    258  *
    259  * @param cls the HTTP server handle (a `struct MhdHttpList`)
    260  * @param url the URL that is being requested
    261  * @param connection MHD connection object for the request
    262  * @return the `struct RequestContext` that this @a connection is for
    263  */
    264 static void *
    265 mhd_log_callback (void *cls,
    266                   const char *url,
    267                   struct MHD_Connection *connection)
    268 {
    269   struct RequestContext *rc;
    270 
    271   rc = GNUNET_new (struct RequestContext);
    272   rc->connection = connection;
    273   rc->url = GNUNET_strdup (url);
    274   rc->do_forward = (1 == PH_no_check);
    275   return rc;
    276 }
    277 
    278 
    279 /**
    280  * Callback invoked on every listen socket to start the
    281  * respective MHD HTTP daemon.
    282  *
    283  * @param cls unused
    284  * @param lsock the listen socket
    285  */
    286 static void
    287 start_daemon (void *cls,
    288               int lsock)
    289 {
    290   struct MHD_Daemon *mhd;
    291 
    292   (void) cls;
    293   GNUNET_assert (-1 != lsock);
    294   mhd = MHD_start_daemon (
    295     MHD_USE_DEBUG
    296     | MHD_ALLOW_SUSPEND_RESUME
    297     | MHD_USE_DUAL_STACK,
    298     0,
    299     NULL, NULL,
    300     &create_response, NULL,
    301     MHD_OPTION_LISTEN_SOCKET,
    302     lsock,
    303     MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
    304     MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL,
    305     MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL,
    306     MHD_OPTION_END);
    307 
    308   if (NULL == mhd)
    309   {
    310     GNUNET_break (0);
    311     GNUNET_SCHEDULER_shutdown ();
    312     return;
    313   }
    314   have_daemons = true;
    315   TALER_MHD_daemon_start (mhd);
    316 }
    317 
    318 
    319 void
    320 PAIVANA_HTTPD_serve_requests ()
    321 {
    322   enum GNUNET_GenericReturnValue ret;
    323 
    324   ret = TALER_MHD_listen_bind (PH_cfg,
    325                                "paivana",
    326                                &start_daemon,
    327                                NULL);
    328   switch (ret)
    329   {
    330   case GNUNET_SYSERR:
    331     PH_global_ret = EXIT_NOTCONFIGURED;
    332     GNUNET_SCHEDULER_shutdown ();
    333     return;
    334   case GNUNET_NO:
    335     if (! have_daemons)
    336     {
    337       PH_global_ret = EXIT_NOTCONFIGURED;
    338       GNUNET_SCHEDULER_shutdown ();
    339       return;
    340     }
    341     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    342                 "Could not open all configured listen sockets\n");
    343     break;
    344   case GNUNET_OK:
    345     break;
    346   }
    347 }