paivana

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

paivana-httpd.c (11224B)


      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 (&paivana_secret,
    310                                 sizeof (paivana_secret));
    311   }
    312   else
    313   {
    314     GNUNET_CRYPTO_hash (secret,
    315                         strlen (secret),
    316                         &paivana_secret);
    317     GNUNET_free (secret);
    318   }
    319   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    320                                  NULL);
    321   PH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    322                              &ctx_rc);
    323   GNUNET_assert (NULL != PH_ctx);
    324   if (! PH_no_check)
    325   {
    326     char *merchant_access_token;
    327     char *auth_header;
    328 
    329     if (GNUNET_OK !=
    330         GNUNET_CONFIGURATION_get_value_string (
    331           c,
    332           "paivana",
    333           "MERCHANT_ACCESS_TOKEN",
    334           &merchant_access_token))
    335     {
    336       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    337                                  "paivana",
    338                                  "MERCHANT_ACCESS_TOKEN");
    339       GNUNET_SCHEDULER_shutdown ();
    340       return;
    341     }
    342     GNUNET_asprintf (&auth_header,
    343                      "%s: Bearer %s",
    344                      MHD_HTTP_HEADER_AUTHORIZATION,
    345                      merchant_access_token);
    346     GNUNET_free (merchant_access_token);
    347     GNUNET_assert (GNUNET_OK ==
    348                    GNUNET_CURL_append_header (PH_ctx,
    349                                               auth_header));
    350     GNUNET_free (auth_header);
    351   }
    352   ctx_rc = GNUNET_CURL_gnunet_rc_create (PH_ctx);
    353   /* Once templates are done loading, this will
    354      start the daemon as well.  In -n (no-payment) mode we skip
    355      the merchant round-trip entirely. */
    356   if (PH_no_check)
    357   {
    358     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    359                 "Paywall disabled (-n), skipping template load\n");
    360     PAIVANA_HTTPD_serve_requests ();
    361     return;
    362   }
    363   PAIVANA_HTTPD_load_templates ();
    364 }
    365 
    366 
    367 /**
    368  * Main function.
    369  */
    370 int
    371 main (int argc,
    372       char *const *argv)
    373 {
    374   struct GNUNET_GETOPT_CommandLineOption options[] = {
    375     GNUNET_GETOPT_option_flag (
    376       'f',
    377       "respect-forwarded-headers",
    378       gettext_noop (
    379         "trust X-Forwarded-For for the client address (only safe behind a trusted reverse proxy)"),
    380       &PH_respect_forwarded_headers),
    381     GNUNET_GETOPT_option_flag (
    382       'g',
    383       "global-payment",
    384       gettext_noop (
    385         "disables per-page payment, useful if a single payment should grant access to the entire site"),
    386       &PH_global_cookie),
    387     GNUNET_GETOPT_option_flag (
    388       'n',
    389       "no-payment",
    390       gettext_noop (
    391         "disables payment, useful for testing reverse-proxy only"),
    392       &PH_no_check),
    393     GNUNET_GETOPT_option_ulong (
    394       'u',
    395       "max-upload",
    396       "BYTES",
    397       gettext_noop (
    398         "maximum request body size to buffer before forwarding (default: 1048576)"),
    399       &PH_request_buffer_max),
    400     GNUNET_GETOPT_OPTION_END
    401   };
    402   enum GNUNET_GenericReturnValue ret;
    403 
    404   ret = GNUNET_PROGRAM_run (
    405     PAIVANA_project_data (),
    406     argc,
    407     argv,
    408     "paivana-httpd",
    409     "reverse proxy requesting Taler payment",
    410     options,
    411     &run, NULL);
    412   if (GNUNET_SYSERR == ret)
    413     return EXIT_INVALIDARGUMENT;
    414   if (GNUNET_NO == ret)
    415     return EXIT_SUCCESS;
    416   return PH_global_ret;
    417 }
    418 
    419 
    420 /* end of paivana-httpd.c */