paivana

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

paivana-httpd.c (11285B)


      1 /*
      2   This file is part of GNU Taler
      3   Copyright (C) 2012-2014 GNUnet e.V.
      4   Copyright (C) 2018, 2025, 2026 Taler Systems SA
      5 
      6   GNU Taler is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU General Public License
      8   as published by the Free Software Foundation; either version
      9   3, or (at your option) any later version.
     10 
     11   GNU Taler is distributed in the hope that it will be useful, but
     12   WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14   GNU General Public License for more details.
     15 
     16   You should have received a copy of the GNU General Public
     17   License along with GNU Taler; see the file COPYING.  If not,
     18   write to the Free Software Foundation, Inc., 51 Franklin
     19   Street, Fifth Floor, Boston, MA 02110-1301, USA.
     20 */
     21 
     22 /**
     23  * @author Martin Schanzenbach
     24  * @author Christian Grothoff
     25  * @author Marcello Stanisci
     26  * @file src/backend/paivana-httpd.c
     27  * @brief HTTP proxy that acts as a GNU Taler paywall
     28  */
     29 #include "platform.h"
     30 #include <curl/curl.h>
     31 #include <gnunet/gnunet_util_lib.h>
     32 #include <gnunet/gnunet_curl_lib.h>
     33 #include <taler/taler_mhd_lib.h>
     34 #include <taler/taler_templating_lib.h>
     35 #include <taler/merchant/common.h>
     36 #include "paivana-httpd.h"
     37 #include "paivana-httpd_cookie.h"
     38 #include "paivana-httpd_daemon.h"
     39 #include "paivana-httpd_helper.h"
     40 #include "paivana-httpd_pay.h"
     41 #include "paivana-httpd_reverse.h"
     42 #include "paivana-httpd_templates.h"
     43 #include "paivana_pd.h"
     44 
     45 
     46 char *PH_target_server_base_url;
     47 
     48 char *PH_target_server_unixpath;
     49 
     50 char *PH_merchant_base_url;
     51 
     52 char *PH_base_url;
     53 
     54 struct GNUNET_CURL_Context *PH_ctx;
     55 
     56 int PH_no_check;
     57 
     58 int PH_respect_forwarded_headers;
     59 
     60 unsigned long long PH_request_buffer_max = 1024 * 1024;
     61 
     62 int PH_global_ret;
     63 
     64 int PH_global_cookie;
     65 
     66 regex_t PH_whitelist_ex;
     67 
     68 bool PH_have_whitelist_ex;
     69 
     70 /**
     71  * Our configuration.
     72  */
     73 const struct GNUNET_CONFIGURATION_Handle *PH_cfg;
     74 
     75 
     76 /**
     77  * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule().
     78  */
     79 static struct GNUNET_CURL_RescheduleContext *ctx_rc;
     80 
     81 
     82 /* *************** General / main code *************** */
     83 
     84 
     85 /**
     86  * Task run on shutdown
     87  *
     88  * @param cls closure
     89  */
     90 static void
     91 do_shutdown (void *cls)
     92 {
     93   (void) cls;
     94   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     95               "Shutting down...\n");
     96   TALER_MHD_daemons_halt ();
     97   PAIVANA_HTTPD_payment_shutdown ();
     98   PAIVANA_HTTPD_reverse_shutdown ();
     99   TALER_MHD_daemons_destroy ();
    100   PAIVANA_HTTPD_unload_templates ();
    101   TALER_TEMPLATING_done ();
    102   GNUNET_free (PH_target_server_base_url);
    103   GNUNET_free (PH_target_server_unixpath);
    104   GNUNET_free (PH_merchant_base_url);
    105   GNUNET_free (PH_base_url);
    106   if (PH_have_whitelist_ex)
    107   {
    108     regfree (&PH_whitelist_ex);
    109     PH_have_whitelist_ex = false;
    110   }
    111   if (NULL != PH_ctx)
    112   {
    113     GNUNET_CURL_fini (PH_ctx);
    114     PH_ctx = NULL;
    115   }
    116   if (NULL != ctx_rc)
    117   {
    118     GNUNET_CURL_gnunet_rc_destroy (ctx_rc);
    119     ctx_rc = NULL;
    120   }
    121 }
    122 
    123 
    124 /**
    125  * Main function that will be run.  Main tasks are (1) init. the
    126  * curl infrastructure (curl_global_init() / curl_multi_init()),
    127  * then fetch the HTTP port where its Web service should listen at,
    128  * and finally start MHD on that port.
    129  *
    130  * @param cls closure
    131  * @param args remaining command-line arguments
    132  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    133  * @param c configuration
    134  */
    135 static void
    136 run (void *cls,
    137      char *const *args,
    138      const char *cfgfile,
    139      const struct GNUNET_CONFIGURATION_Handle *c)
    140 {
    141   char *secret;
    142 
    143   (void) cls;
    144   (void) args;
    145   (void) cfgfile;
    146   PH_cfg = c;
    147 
    148   if (! PH_no_check)
    149   {
    150     if (GNUNET_OK !=
    151         TALER_TEMPLATING_init (PAIVANA_project_data ()))
    152     {
    153       GNUNET_break (0);
    154       GNUNET_SCHEDULER_shutdown ();
    155       return;
    156     }
    157   }
    158   if (! PAIVANA_HTTPD_reverse_init ())
    159   {
    160     GNUNET_break (0);
    161     GNUNET_SCHEDULER_shutdown ();
    162     return;
    163   }
    164 
    165   /* No need to check return value.  If given, we take,
    166    * otherwise it stays zero.  */
    167   if (GNUNET_OK !=
    168       GNUNET_CONFIGURATION_get_value_string (
    169         c,
    170         "paivana",
    171         "DESTINATION_BASE_URL",
    172         &PH_target_server_base_url))
    173   {
    174     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    175                                "paivana",
    176                                "DESTINATION_BASE_URL");
    177     GNUNET_SCHEDULER_shutdown ();
    178     return;
    179   }
    180   if (! TALER_is_web_url (PH_target_server_base_url))
    181   {
    182     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    183                                "paivana",
    184                                "DESTINATION_BASE_URL",
    185                                "not a web url");
    186     GNUNET_SCHEDULER_shutdown ();
    187     return;
    188   }
    189   GNUNET_CONFIGURATION_get_value_filename (
    190     c,
    191     "paivana",
    192     "DESTINATION_UNIXPATH",
    193     &PH_target_server_unixpath);
    194   {
    195     size_t tlen = strlen (PH_target_server_base_url);
    196 
    197     if ( (tlen > 0) &&
    198          ('/' == PH_target_server_base_url[tlen - 1]) )
    199       PH_target_server_base_url[tlen - 1] = '\0';
    200   }
    201   if (! PH_no_check)
    202   {
    203     if (GNUNET_OK !=
    204         GNUNET_CONFIGURATION_get_value_string (
    205           c,
    206           "paivana",
    207           "MERCHANT_BACKEND_URL",
    208           &PH_merchant_base_url))
    209     {
    210       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    211                                  "paivana",
    212                                  "MERCHANT_BACKEND_URL");
    213       GNUNET_SCHEDULER_shutdown ();
    214       return;
    215     }
    216     if (! TALER_is_web_url (PH_merchant_base_url))
    217     {
    218       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    219                                  "paivana",
    220                                  "MERCHANT_BACKEND_URL",
    221                                  "not a web url");
    222       GNUNET_SCHEDULER_shutdown ();
    223       return;
    224     }
    225   }
    226   {
    227     char *merchant_unix_path;
    228 
    229     if (GNUNET_OK ==
    230         GNUNET_CONFIGURATION_get_value_string (
    231           c,
    232           "paivana",
    233           "MERCHANT_BACKEND_UNIX_PATH",
    234           &merchant_unix_path))
    235     {
    236       if (! TALER_MERCHANT_global_set_unixpath (merchant_unix_path))
    237       {
    238         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
    239                                    "paivana",
    240                                    "MERCHANT_BACKEND_UNIX_PATH",
    241                                    "invalid path; ignoring the setting");
    242       }
    243       GNUNET_free (merchant_unix_path);
    244     }
    245   }
    246   {
    247     char *whitelist;
    248 
    249     if (GNUNET_OK ==
    250         GNUNET_CONFIGURATION_get_value_string (
    251           c,
    252           "paivana",
    253           "WHITELIST",
    254           &whitelist))
    255     {
    256       if (0 != regcomp (&PH_whitelist_ex,
    257                         whitelist,
    258                         REG_NOSUB | REG_EXTENDED))
    259       {
    260         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    261                                    "paivana",
    262                                    "WHITELIST",
    263                                    "Invalid regular expression");
    264         GNUNET_free (whitelist);
    265         GNUNET_SCHEDULER_shutdown ();
    266         return;
    267       }
    268       PH_have_whitelist_ex = true;
    269       GNUNET_free (whitelist);
    270     }
    271   }
    272 
    273   if (GNUNET_OK !=
    274       GNUNET_CONFIGURATION_get_value_string (
    275         c,
    276         "paivana",
    277         "BASE_URL",
    278         &PH_base_url))
    279   {
    280     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
    281                                "paivana",
    282                                "BASE_URL");
    283   }
    284   if (NULL != PH_base_url)
    285   {
    286     if (! TALER_is_web_url (PH_base_url))
    287     {
    288       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    289                                  "paivana",
    290                                  "BASE_URL",
    291                                  "not a web url");
    292       GNUNET_SCHEDULER_shutdown ();
    293       return;
    294     }
    295     if ('/' == PH_base_url[strlen (PH_base_url) - 1])
    296       PH_base_url[strlen (PH_base_url) - 1] = '\0';
    297   }
    298 
    299   if (GNUNET_OK !=
    300       GNUNET_CONFIGURATION_get_value_string (
    301         c,
    302         "paivana",
    303         "SECRET",
    304         &secret))
    305   {
    306     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    307                                "paivana",
    308                                "SECRET");
    309     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
    310                                 &paivana_secret,
    311                                 sizeof (paivana_secret));
    312   }
    313   else
    314   {
    315     GNUNET_CRYPTO_hash (secret,
    316                         strlen (secret),
    317                         &paivana_secret);
    318     GNUNET_free (secret);
    319   }
    320   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    321                                  NULL);
    322   PH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    323                              &ctx_rc);
    324   GNUNET_assert (NULL != PH_ctx);
    325   if (! PH_no_check)
    326   {
    327     char *merchant_access_token;
    328     char *auth_header;
    329 
    330     if (GNUNET_OK !=
    331         GNUNET_CONFIGURATION_get_value_string (
    332           c,
    333           "paivana",
    334           "MERCHANT_ACCESS_TOKEN",
    335           &merchant_access_token))
    336     {
    337       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    338                                  "paivana",
    339                                  "MERCHANT_ACCESS_TOKEN");
    340       GNUNET_SCHEDULER_shutdown ();
    341       return;
    342     }
    343     GNUNET_asprintf (&auth_header,
    344                      "%s: Bearer %s",
    345                      MHD_HTTP_HEADER_AUTHORIZATION,
    346                      merchant_access_token);
    347     GNUNET_free (merchant_access_token);
    348     GNUNET_assert (GNUNET_OK ==
    349                    GNUNET_CURL_append_header (PH_ctx,
    350                                               auth_header));
    351     GNUNET_free (auth_header);
    352   }
    353   ctx_rc = GNUNET_CURL_gnunet_rc_create (PH_ctx);
    354   /* Once templates are done loading, this will
    355      start the daemon as well.  In -n (no-payment) mode we skip
    356      the merchant round-trip entirely. */
    357   if (PH_no_check)
    358   {
    359     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    360                 "Paywall disabled (-n), skipping template load\n");
    361     PAIVANA_HTTPD_serve_requests ();
    362     return;
    363   }
    364   PAIVANA_HTTPD_load_templates ();
    365 }
    366 
    367 
    368 /**
    369  * Main function.
    370  */
    371 int
    372 main (int argc,
    373       char *const *argv)
    374 {
    375   struct GNUNET_GETOPT_CommandLineOption options[] = {
    376     GNUNET_GETOPT_option_flag (
    377       'f',
    378       "respect-forwarded-headers",
    379       gettext_noop (
    380         "trust X-Forwarded-For for the client address (only safe behind a trusted reverse proxy)"),
    381       &PH_respect_forwarded_headers),
    382     GNUNET_GETOPT_option_flag (
    383       'g',
    384       "global-payment",
    385       gettext_noop (
    386         "disables per-page payment, useful if a single payment should grant access to the entire site"),
    387       &PH_global_cookie),
    388     GNUNET_GETOPT_option_flag (
    389       'n',
    390       "no-payment",
    391       gettext_noop (
    392         "disables payment, useful for testing reverse-proxy only"),
    393       &PH_no_check),
    394     GNUNET_GETOPT_option_ulong (
    395       'u',
    396       "max-upload",
    397       "BYTES",
    398       gettext_noop (
    399         "maximum request body size to buffer before forwarding (default: 1048576)"),
    400       &PH_request_buffer_max),
    401     GNUNET_GETOPT_OPTION_END
    402   };
    403   enum GNUNET_GenericReturnValue ret;
    404 
    405   ret = GNUNET_PROGRAM_run (
    406     PAIVANA_project_data (),
    407     argc,
    408     argv,
    409     "paivana-httpd",
    410     "reverse proxy requesting Taler payment",
    411     options,
    412     &run, NULL);
    413   if (GNUNET_SYSERR == ret)
    414     return EXIT_INVALIDARGUMENT;
    415   if (GNUNET_NO == ret)
    416     return EXIT_SUCCESS;
    417   return PH_global_ret;
    418 }
    419 
    420 
    421 /* end of paivana-httpd.c */