paivana

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

paivana-httpd.c (10995B)


      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 (NULL != PH_ctx)
    107   {
    108     GNUNET_CURL_fini (PH_ctx);
    109     PH_ctx = NULL;
    110   }
    111   if (NULL != ctx_rc)
    112   {
    113     GNUNET_CURL_gnunet_rc_destroy (ctx_rc);
    114     ctx_rc = NULL;
    115   }
    116 }
    117 
    118 
    119 /**
    120  * Main function that will be run.  Main tasks are (1) init. the
    121  * curl infrastructure (curl_global_init() / curl_multi_init()),
    122  * then fetch the HTTP port where its Web service should listen at,
    123  * and finally start MHD on that port.
    124  *
    125  * @param cls closure
    126  * @param args remaining command-line arguments
    127  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    128  * @param c configuration
    129  */
    130 static void
    131 run (void *cls,
    132      char *const *args,
    133      const char *cfgfile,
    134      const struct GNUNET_CONFIGURATION_Handle *c)
    135 {
    136   char *secret;
    137 
    138   (void) cls;
    139   (void) args;
    140   (void) cfgfile;
    141   PH_cfg = c;
    142 
    143   if (! PH_no_check)
    144   {
    145     if (GNUNET_OK !=
    146         TALER_TEMPLATING_init (PAIVANA_project_data ()))
    147     {
    148       GNUNET_break (0);
    149       GNUNET_SCHEDULER_shutdown ();
    150       return;
    151     }
    152   }
    153   if (! PAIVANA_HTTPD_reverse_init ())
    154   {
    155     GNUNET_break (0);
    156     GNUNET_SCHEDULER_shutdown ();
    157     return;
    158   }
    159 
    160   /* No need to check return value.  If given, we take,
    161    * otherwise it stays zero.  */
    162   if (GNUNET_OK !=
    163       GNUNET_CONFIGURATION_get_value_string (
    164         c,
    165         "paivana",
    166         "DESTINATION_BASE_URL",
    167         &PH_target_server_base_url))
    168   {
    169     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    170                                "paivana",
    171                                "DESTINATION_BASE_URL");
    172     GNUNET_SCHEDULER_shutdown ();
    173     return;
    174   }
    175   if (! TALER_is_web_url (PH_target_server_base_url))
    176   {
    177     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    178                                "paivana",
    179                                "DESTINATION_BASE_URL",
    180                                "not a web url");
    181     GNUNET_SCHEDULER_shutdown ();
    182     return;
    183   }
    184   GNUNET_CONFIGURATION_get_value_filename (
    185     c,
    186     "paivana",
    187     "DESTINATION_UNIXPATH",
    188     &PH_target_server_unixpath);
    189   {
    190     size_t tlen = strlen (PH_target_server_base_url);
    191 
    192     if ( (tlen > 0) &&
    193          ('/' == PH_target_server_base_url[tlen - 1]) )
    194       PH_target_server_base_url[tlen - 1] = '\0';
    195   }
    196   if (! PH_no_check)
    197   {
    198     if (GNUNET_OK !=
    199         GNUNET_CONFIGURATION_get_value_string (
    200           c,
    201           "paivana",
    202           "MERCHANT_BACKEND_URL",
    203           &PH_merchant_base_url))
    204     {
    205       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    206                                  "paivana",
    207                                  "MERCHANT_BACKEND_URL");
    208       GNUNET_SCHEDULER_shutdown ();
    209       return;
    210     }
    211     if (! TALER_is_web_url (PH_merchant_base_url))
    212     {
    213       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    214                                  "paivana",
    215                                  "MERCHANT_BACKEND_URL",
    216                                  "not a web url");
    217       GNUNET_SCHEDULER_shutdown ();
    218       return;
    219     }
    220   }
    221   {
    222     char *merchant_unix_path;
    223 
    224     if (GNUNET_OK ==
    225         GNUNET_CONFIGURATION_get_value_string (
    226           c,
    227           "paivana",
    228           "MERCHANT_BACKEND_UNIX_PATH",
    229           &merchant_unix_path))
    230     {
    231       if (! TALER_MERCHANT_global_set_unixpath (merchant_unix_path))
    232       {
    233         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
    234                                    "paivana",
    235                                    "MERCHANT_BACKEND_UNIX_PATH",
    236                                    "invalid path; ignoring the setting");
    237       }
    238       GNUNET_free (merchant_unix_path);
    239     }
    240   }
    241   {
    242     char *whitelist;
    243 
    244     if (GNUNET_OK ==
    245         GNUNET_CONFIGURATION_get_value_string (
    246           c,
    247           "paivana",
    248           "WHITELIST",
    249           &whitelist))
    250     {
    251       if (0 != regcomp (&PH_whitelist_ex,
    252 			whitelist,
    253 			REG_NOSUB | REG_EXTENDED))
    254       {
    255 	GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    256 				   "paivana",
    257 				   "WHITELIST",
    258 				   "Invalid regular expression");
    259 	GNUNET_free (whitelist);
    260 	GNUNET_SCHEDULER_shutdown ();
    261 	return;
    262       }
    263       PH_have_whitelist_ex = true;
    264       GNUNET_free (whitelist);
    265     }
    266   }
    267 
    268   if (GNUNET_OK !=
    269       GNUNET_CONFIGURATION_get_value_string (
    270         c,
    271         "paivana",
    272         "BASE_URL",
    273         &PH_base_url))
    274   {
    275     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
    276                                "paivana",
    277                                "BASE_URL");
    278   }
    279   if (NULL != PH_base_url)
    280   {
    281     if (! TALER_is_web_url (PH_base_url))
    282     {
    283       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    284                                  "paivana",
    285                                  "BASE_URL",
    286                                  "not a web url");
    287       GNUNET_SCHEDULER_shutdown ();
    288       return;
    289     }
    290     if ('/' == PH_base_url[strlen (PH_base_url) - 1])
    291       PH_base_url[strlen (PH_base_url) - 1] = '\0';
    292   }
    293 
    294   if (GNUNET_OK !=
    295       GNUNET_CONFIGURATION_get_value_string (
    296         c,
    297         "paivana",
    298         "SECRET",
    299         &secret))
    300   {
    301     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    302                                "paivana",
    303                                "SECRET");
    304     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
    305                                 &paivana_secret,
    306                                 sizeof (paivana_secret));
    307   }
    308   else
    309   {
    310     GNUNET_CRYPTO_hash (secret,
    311                         strlen (secret),
    312                         &paivana_secret);
    313     GNUNET_free (secret);
    314   }
    315   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    316                                  NULL);
    317   PH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    318                              &ctx_rc);
    319   if (! PH_no_check)
    320   {
    321     char *merchant_access_token;
    322     char *auth_header;
    323 
    324     if (GNUNET_OK !=
    325         GNUNET_CONFIGURATION_get_value_string (
    326           c,
    327           "paivana",
    328           "MERCHANT_ACCESS_TOKEN",
    329           &merchant_access_token))
    330     {
    331       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    332                                  "paivana",
    333                                  "MERCHANT_ACCESS_TOKEN");
    334       GNUNET_SCHEDULER_shutdown ();
    335       return;
    336     }
    337     GNUNET_asprintf (&auth_header,
    338                      "%s: Bearer %s",
    339                      MHD_HTTP_HEADER_AUTHORIZATION,
    340                      merchant_access_token);
    341     GNUNET_free (merchant_access_token);
    342     GNUNET_assert (GNUNET_OK ==
    343                    GNUNET_CURL_append_header (PH_ctx,
    344                                               auth_header));
    345     GNUNET_free (auth_header);
    346   }
    347   ctx_rc = GNUNET_CURL_gnunet_rc_create (PH_ctx);
    348   /* Once templates are done loading, this will
    349      start the daemon as well.  In -n (no-payment) mode we skip
    350      the merchant round-trip entirely. */
    351   if (PH_no_check)
    352   {
    353     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    354                 "Paywall disabled (-n), skipping template load\n");
    355     PAIVANA_HTTPD_serve_requests ();
    356     return;
    357   }
    358   PAIVANA_HTTPD_load_templates ();
    359 }
    360 
    361 
    362 /**
    363  * Main function.
    364  */
    365 int
    366 main (int argc,
    367       char *const *argv)
    368 {
    369   struct GNUNET_GETOPT_CommandLineOption options[] = {
    370     GNUNET_GETOPT_option_flag (
    371       'f',
    372       "respect-forwarded-headers",
    373       gettext_noop (
    374         "trust X-Forwarded-For for the client address (only safe behind a trusted reverse proxy)"),
    375       &PH_respect_forwarded_headers),
    376     GNUNET_GETOPT_option_flag (
    377       'g',
    378       "global-payment",
    379       gettext_noop (
    380         "disables per-page payment, useful if a single payment should grant access to the entire site"),
    381       &PH_global_cookie),
    382     GNUNET_GETOPT_option_flag (
    383       'n',
    384       "no-payment",
    385       gettext_noop (
    386         "disables payment, useful for testing reverse-proxy only"),
    387       &PH_no_check),
    388     GNUNET_GETOPT_option_ulong (
    389       'u',
    390       "max-upload",
    391       "BYTES",
    392       gettext_noop (
    393         "maximum request body size to buffer before forwarding (default: 1048576)"),
    394       &PH_request_buffer_max),
    395     GNUNET_GETOPT_OPTION_END
    396   };
    397   enum GNUNET_GenericReturnValue ret;
    398 
    399   ret = GNUNET_PROGRAM_run (
    400     PAIVANA_project_data (),
    401     argc,
    402     argv,
    403     "paivana-httpd",
    404     "reverse proxy requesting Taler payment",
    405     options,
    406     &run, NULL);
    407   if (GNUNET_SYSERR == ret)
    408     return EXIT_INVALIDARGUMENT;
    409   if (GNUNET_NO == ret)
    410     return EXIT_SUCCESS;
    411   return PH_global_ret;
    412 }
    413 
    414 
    415 /* end of paivana-httpd.c */