paivana

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

paivana-httpd.c (48017B)


      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 Affero 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 Affero General Public License for more details.
     15 
     16   You should have received a copy of the GNU Affero 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_merchant_internal_url;
     53 
     54 char *PH_merchant_unixpath;
     55 
     56 char *PH_base_url;
     57 
     58 struct GNUNET_CURL_Context *PH_merchant_ctx;
     59 
     60 struct GNUNET_CURL_Context *PH_proxy_ctx;
     61 
     62 int PH_no_check;
     63 
     64 int PH_respect_forwarded_headers;
     65 
     66 struct GNUNET_STRINGS_IPv4NetworkPolicy *PH_trusted_proxies4;
     67 
     68 struct GNUNET_STRINGS_IPv6NetworkPolicy *PH_trusted_proxies6;
     69 
     70 bool PH_have_trusted_proxies;
     71 
     72 /**
     73  * Compiled-in default for #PH_request_buffer_max.  Named because
     74  * `run()` has to be able to tell it apart from a value the operator
     75  * wrote, which is what decides whether #PH_max_request_size inherits
     76  * it.
     77  */
     78 #define PH_DEFAULT_REQUEST_BUFFER_MAX (256 * 1024)
     79 
     80 /**
     81  * File descriptors deliberately kept outside the client-connection budget.
     82  *
     83  * The current GNUnet/Taler event-loop integration uses native `fd_set`s, so
     84  * descriptors numbered #FD_SETSIZE or higher cannot be serviced even when
     85  * RLIMIT_NOFILE is larger.  256 descriptors leave one quarter of the usual
     86  * 1024-entry table for listen sockets, the scheduler, logs, resolver work,
     87  * libcurl's reusable connections and short-lived overlap while sockets are
     88  * being replaced.  This is deliberately a safety allowance rather than a
     89  * claim that those users always consume exactly 256 descriptors.
     90  */
     91 #define PH_DESCRIPTOR_RESERVE 256U
     92 
     93 /**
     94  * Worst-case sockets charged to one active client connection: the accepted
     95  * client socket and one simultaneous origin or merchant socket.
     96  */
     97 #define PH_DESCRIPTORS_PER_CONNECTION 2U
     98 
     99 /**
    100  * Largest connection budget that fits the select-based descriptor table
    101  * after #PH_DESCRIPTOR_RESERVE has been removed.
    102  */
    103 #define PH_MAX_CONNECTION_LIMIT \
    104         ((FD_SETSIZE - PH_DESCRIPTOR_RESERVE) \
    105          / PH_DESCRIPTORS_PER_CONNECTION)
    106 
    107 unsigned long long PH_request_buffer_max = PH_DEFAULT_REQUEST_BUFFER_MAX;
    108 
    109 unsigned long long PH_response_buffer_max = 256 * 1024;
    110 
    111 unsigned long long PH_max_request_size = 1024 * 1024;
    112 
    113 struct GNUNET_TIME_Relative PH_upstream_timeout;
    114 
    115 struct GNUNET_TIME_Relative PH_upstream_stall_timeout;
    116 
    117 unsigned int PH_connection_limit = PH_MAX_CONNECTION_LIMIT;
    118 
    119 /**
    120  * Thirty-two payment requests are enough to cover a burst while bounding a
    121  * malicious client's ability to pin suspended MHD connections for the
    122  * five-second merchant long poll.  The remaining 352 slots in the default
    123  * 384-connection budget stay available for ordinary proxy traffic.
    124  */
    125 unsigned int PH_payment_connection_limit = 32;
    126 
    127 unsigned int PH_per_ip_connection_limit = 32;
    128 
    129 /**
    130  * Aggregate budget for the two streaming rings of ordinary requests.
    131  * 256 MiB admits the defaults (352 * (256 KiB + 256 KiB) = 176 MiB) while
    132  * leaving memory for templates, cached responses, merchant replies, libcurl
    133  * and allocator overhead.  It is a validation budget, not a pre-allocation.
    134  */
    135 unsigned long long PH_relay_memory_limit = 256ULL * 1024 * 1024;
    136 
    137 /**
    138  * Time accepted requests may finish after SIGTERM.  Sixty seconds matches
    139  * the two upstream progress timeouts: a healthy request gets a useful chance
    140  * to complete, while systemd can still enforce a finite stop deadline.
    141  */
    142 struct GNUNET_TIME_Relative PH_shutdown_grace_period;
    143 
    144 int PH_global_ret;
    145 
    146 int PH_global_cookie;
    147 
    148 regex_t PH_whitelist_ex;
    149 
    150 bool PH_have_whitelist_ex;
    151 
    152 /**
    153  * Our configuration.
    154  */
    155 const struct GNUNET_CONFIGURATION_Handle *PH_cfg;
    156 
    157 
    158 /**
    159  * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule() of
    160  * #PH_merchant_ctx.
    161  */
    162 static struct GNUNET_CURL_RescheduleContext *merchant_ctx_rc;
    163 
    164 /**
    165  * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule() of
    166  * #PH_proxy_ctx.
    167  */
    168 static struct GNUNET_CURL_RescheduleContext *proxy_ctx_rc;
    169 
    170 /**
    171  * Wall-clock start of graceful shutdown, used to enforce
    172  * #PH_shutdown_grace_period.
    173  */
    174 static struct GNUNET_TIME_Absolute shutdown_started;
    175 
    176 /**
    177  * Poll task waiting for active MHD requests to drain.
    178  */
    179 static struct GNUNET_SCHEDULER_Task *shutdown_poll_task;
    180 
    181 /**
    182  * Guards the final cleanup against repeated shutdown signals or callbacks.
    183  */
    184 static bool shutdown_cleanup_done;
    185 
    186 
    187 /* *************** General / main code *************** */
    188 
    189 
    190 /**
    191  * Validate the descriptor and aggregate relay-memory budgets.
    192  *
    193  * These are startup errors rather than warnings.  A configuration that can
    194  * allocate an fd which `select()` cannot represent fails intermittently and
    195  * most visibly in merchant payment checks, where the client API reports the
    196  * transport failure as HTTP status zero.
    197  *
    198  * @return true if the configured budgets are safe
    199  */
    200 static bool
    201 check_resource_limits (void)
    202 {
    203   const unsigned int ordinary_limit
    204     = PH_connection_limit - PH_payment_connection_limit;
    205   const unsigned long long ring_bytes
    206     = PH_request_buffer_max + PH_response_buffer_max;
    207 
    208   if (PH_connection_limit > PH_MAX_CONNECTION_LIMIT)
    209   {
    210     GNUNET_log_config_invalid (
    211       GNUNET_ERROR_TYPE_ERROR,
    212       "paivana",
    213       "CONNECTION_LIMIT",
    214       "exceeds the select()-safe maximum: (FD_SETSIZE 1024 - 256"
    215       " reserved descriptors) / 2 descriptors per connection = 384");
    216     return false;
    217   }
    218   if (ring_bytes > PH_relay_memory_limit / ordinary_limit)
    219   {
    220     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    221                 "REQUEST_BUFFER_MAX (%llu) + RESPONSE_BUFFER_MAX (%llu),"
    222                 " multiplied by the %u ordinary request slots, exceeds"
    223                 " RELAY_MEMORY_LIMIT (%llu bytes)\n",
    224                 PH_request_buffer_max,
    225                 PH_response_buffer_max,
    226                 ordinary_limit,
    227                 PH_relay_memory_limit);
    228     return false;
    229   }
    230 #if HAVE_SYS_RESOURCE_H
    231   struct rlimit lim;
    232   const unsigned long long required
    233     = (unsigned long long) PH_DESCRIPTORS_PER_CONNECTION
    234       * PH_connection_limit
    235       + PH_DESCRIPTOR_RESERVE;
    236 
    237   if (0 != getrlimit (RLIMIT_NOFILE,
    238                       &lim))
    239   {
    240     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    241                          "getrlimit");
    242     return false;
    243   }
    244   if ( (RLIM_INFINITY != lim.rlim_cur) &&
    245        ((unsigned long long) lim.rlim_cur < required) )
    246   {
    247     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    248                 "Open-file soft limit %llu is below the required %llu"
    249                 " for CONNECTION_LIMIT=%u (two descriptors per connection"
    250                 " plus a 256-descriptor safety reserve)\n",
    251                 (unsigned long long) lim.rlim_cur,
    252                 required,
    253                 PH_connection_limit);
    254     return false;
    255   }
    256 #endif
    257   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    258               "Resource budgets: %u connections (%u ordinary, %u payment),"
    259               " %llu/%llu relay bytes, %u descriptor slots reserved\n",
    260               PH_connection_limit,
    261               ordinary_limit,
    262               PH_payment_connection_limit,
    263               ring_bytes * ordinary_limit,
    264               PH_relay_memory_limit,
    265               PH_DESCRIPTOR_RESERVE);
    266   return true;
    267 }
    268 
    269 
    270 /**
    271  * Load one of the `TRUSTED_PROXIES` options.
    272  *
    273  * The GNUnet policy parsers are lenient in ways that matter here, so
    274  * a non-NULL return is not on its own evidence that anything was
    275  * understood:
    276  *
    277  * - the list is terminated by an all-zero entry, so a network of
    278  *   0.0.0.0/0 or ::/0 *is* the terminator and silently truncates
    279  *   everything after it.  "Trust everyone" is therefore inexpressible
    280  *   — and would be a strange thing to write anyway;
    281  * - the v4 parser accepts a value whose last entry is not terminated
    282  *   by ';' -- the operator wrote the separators but not the
    283  *   terminator -- and drops that entry without a word, along with
    284  *   anything else after the final ';'.  (Its v6 counterpart refuses
    285  *   such a value outright, so this one is the v4 side's alone.)
    286  *
    287  * A genuinely malformed entry, including a v6 network handed to the
    288  * v4 parser, does come back as NULL rather than as a shorter list; it
    289  * is the only one of these that announces itself.
    290  *
    291  * The first comes out as "parsed, but nothing usable", which we reject
    292  * along with NULL: quietly trusting nobody would send every visitor to
    293  * the socket address, and the operator would have no hint why.  The
    294  * second is worse, because it succeeds: a single missing ';' would
    295  * leave the clients behind an unlisted proxy sharing that proxy's
    296  * address as their identity, i.e. sharing one paid cookie.  So the
    297  * entries that came back are counted against the ';' that went in, and
    298  * the value has to end in one.
    299  *
    300  * Not checked for here: an entry may also carry a port policy
    301  * ("10.0.0.0/8:80;"), which parses into a perfectly usable entry whose
    302  * port range nothing in paivana ever consults -- it reads as a
    303  * restriction on the trust and is not one.
    304  *
    305  * @param c configuration to read from
    306  * @param option name of the option
    307  * @param[out] count set to the number of usable entries
    308  * @return false if the option is present but unusable
    309  */
    310 static bool
    311 load_trusted_proxies (const struct GNUNET_CONFIGURATION_Handle *c,
    312                       const char *option,
    313                       unsigned int *count)
    314 {
    315   char *opt;
    316   bool v6 = (0 != strcmp (option,
    317                           "TRUSTED_PROXIES"));
    318   unsigned int want = 0;
    319   size_t len;
    320 
    321   *count = 0;
    322   if (GNUNET_OK !=
    323       GNUNET_CONFIGURATION_get_value_string (c,
    324                                              "paivana",
    325                                              option,
    326                                              &opt))
    327     return true; /* not configured at all: fine */
    328   len = strlen (opt);
    329   while ( (len > 0) &&
    330           ( (' ' == opt[len - 1]) ||
    331             ('\t' == opt[len - 1]) ) )
    332     opt[--len] = '\0';
    333   for (const char *p = strchr (opt, ';'); NULL != p; p = strchr (p + 1, ';'))
    334     want++;
    335   if (v6)
    336   {
    337     PH_trusted_proxies6 = GNUNET_STRINGS_parse_ipv6_policy (opt);
    338     if (NULL != PH_trusted_proxies6)
    339       while (! GNUNET_is_zero (&PH_trusted_proxies6[*count].network))
    340         (*count)++;
    341   }
    342   else
    343   {
    344     PH_trusted_proxies4 = GNUNET_STRINGS_parse_ipv4_policy (opt);
    345     if (NULL != PH_trusted_proxies4)
    346       while (0 != PH_trusted_proxies4[*count].network.s_addr)
    347         (*count)++;
    348   }
    349   if ( (0 == *count) ||
    350        (*count != want) ||
    351        (0 == len) ||
    352        (';' != opt[len - 1]) )
    353   {
    354     if ( (0 != *count) &&
    355          (*count != want) )
    356       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    357                   "Only %u of the %u entries of `%s' were understood;"
    358                   " refusing to trust a prefix of the list\n",
    359                   *count,
    360                   want,
    361                   option);
    362     else if (0 != *count)
    363       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    364                   "`%s' does not end in the ';' that terminates the last"
    365                   " entry; anything after the final one is dropped\n",
    366                   option);
    367     GNUNET_log_config_invalid (
    368       GNUNET_ERROR_TYPE_ERROR,
    369       "paivana",
    370       option,
    371       v6
    372       ? "not a usable IPv6 network list; entries are separated *and*"
    373       " terminated by ';' and must not contain spaces, e.g."
    374       " \"2001:db8::/32;fe80::/10;\" (note that ::/0 is indistinguishable"
    375       " from the end of the list and cannot be used)"
    376       : "not a usable IPv4 network list; entries are separated *and*"
    377       " terminated by ';', e.g. \"10.0.0.0/8;192.168.0.0/16;\""
    378       " (note that 0.0.0.0/0 is indistinguishable from the end of the"
    379       " list and cannot be used)");
    380     GNUNET_free (opt);
    381     return false;
    382   }
    383   GNUNET_free (opt);
    384   PH_have_trusted_proxies = true;
    385   return true;
    386 }
    387 
    388 
    389 /**
    390  * Final cleanup after graceful draining has completed or reached its deadline.
    391  */
    392 static void
    393 finish_shutdown (void)
    394 {
    395   if (shutdown_cleanup_done)
    396     return;
    397   shutdown_cleanup_done = true;
    398   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    399               "Finishing shutdown\n");
    400   /* MHD_stop_daemon() must never see a suspended connection.  Stop the
    401      scheduler integration first so that resumed requests cannot be dispatched
    402      between the following cleanup calls, then cancel/resume every suspended
    403      payment and reverse-proxy request before destroying the daemons.  This
    404      ordering matters specifically when the graceful-drain deadline expires:
    405      in that case active upstream operations still exist by definition. */
    406   TALER_MHD_daemons_halt ();
    407   PAIVANA_HTTPD_payment_shutdown ();
    408   PAIVANA_HTTPD_reverse_shutdown ();
    409   PAIVANA_HTTPD_daemons_destroy ();
    410   PAIVANA_HTTPD_unload_templates ();
    411   TALER_TEMPLATING_done ();
    412   GNUNET_free (PH_target_server_base_url);
    413   GNUNET_free (PH_target_server_unixpath);
    414   GNUNET_free (PH_trusted_proxies4);
    415   GNUNET_free (PH_trusted_proxies6);
    416   GNUNET_free (PH_merchant_base_url);
    417   GNUNET_free (PH_merchant_internal_url);
    418   GNUNET_free (PH_merchant_unixpath);
    419   GNUNET_free (PH_base_url);
    420   if (PH_have_whitelist_ex)
    421   {
    422     regfree (&PH_whitelist_ex);
    423     PH_have_whitelist_ex = false;
    424   }
    425   if (NULL != PH_merchant_ctx)
    426   {
    427     GNUNET_CURL_fini (PH_merchant_ctx);
    428     PH_merchant_ctx = NULL;
    429   }
    430   if (NULL != merchant_ctx_rc)
    431   {
    432     GNUNET_CURL_gnunet_rc_destroy (merchant_ctx_rc);
    433     merchant_ctx_rc = NULL;
    434   }
    435   if (NULL != PH_proxy_ctx)
    436   {
    437     GNUNET_CURL_fini (PH_proxy_ctx);
    438     PH_proxy_ctx = NULL;
    439   }
    440   if (NULL != proxy_ctx_rc)
    441   {
    442     GNUNET_CURL_gnunet_rc_destroy (proxy_ctx_rc);
    443     proxy_ctx_rc = NULL;
    444   }
    445 }
    446 
    447 
    448 /**
    449  * Check whether accepted requests have drained.
    450  *
    451  * A 100 ms poll interval bounds shutdown-completion latency without placing a
    452  * callback on every request-completion path.  At the 60-second default this
    453  * is at most 600 cheap counter reads and does not touch the request list.
    454  *
    455  * @param cls unused
    456  */
    457 static void
    458 poll_shutdown_drain (void *cls)
    459 {
    460   struct GNUNET_TIME_Relative elapsed;
    461   unsigned int active;
    462 
    463   (void) cls;
    464   shutdown_poll_task = NULL;
    465   active = PAIVANA_HTTPD_active_requests ();
    466   elapsed = GNUNET_TIME_absolute_get_duration (shutdown_started);
    467   if (0 == active)
    468   {
    469     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    470                 "Graceful shutdown drained all requests in %s\n",
    471                 GNUNET_STRINGS_relative_time_to_string (elapsed,
    472                                                         true));
    473     finish_shutdown ();
    474     return;
    475   }
    476   if (GNUNET_TIME_relative_cmp (elapsed,
    477                                 >=,
    478                                 PH_shutdown_grace_period))
    479   {
    480     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    481                 "Graceful shutdown deadline reached with %u active"
    482                 " request%s; terminating them now\n",
    483                 active,
    484                 (1 == active) ? "" : "s");
    485     finish_shutdown ();
    486     return;
    487   }
    488   shutdown_poll_task = GNUNET_SCHEDULER_add_delayed (
    489     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
    490                                    100),
    491     &poll_shutdown_drain,
    492     NULL);
    493 }
    494 
    495 
    496 /**
    497  * Task run on shutdown: close listeners, then allow accepted requests to
    498  * complete before final cleanup.
    499  *
    500  * @param cls closure
    501  */
    502 static void
    503 do_shutdown (void *cls)
    504 {
    505   unsigned int active;
    506 
    507   (void) cls;
    508   shutdown_started = GNUNET_TIME_absolute_get ();
    509   active = PAIVANA_HTTPD_begin_drain ();
    510   if ( (0 == active) ||
    511        (0 == PH_shutdown_grace_period.rel_value_us) )
    512   {
    513     finish_shutdown ();
    514     return;
    515   }
    516   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    517               "Shutdown quiesced listeners; allowing %u active request%s"
    518               " up to %s to finish\n",
    519               active,
    520               (1 == active) ? "" : "s",
    521               GNUNET_STRINGS_relative_time_to_string (
    522                 PH_shutdown_grace_period,
    523                 true));
    524   shutdown_poll_task = GNUNET_SCHEDULER_add_now (&poll_shutdown_drain,
    525                                                   NULL);
    526 }
    527 
    528 
    529 /**
    530  * Remove trailing slashes from the web URL @a url, in place.
    531  *
    532  * Our configuration syntax prefers base URLs to be written with a
    533  * trailing '/', while everything we append to one -- a request path,
    534  * a "/.well-known/..." endpoint -- brings a leading '/' of its own.
    535  * Dropping them here is what keeps the concatenation from yielding
    536  * "//", which would otherwise reach the upstream verbatim and, for
    537  * BASE_URL, end up in the string the access cookie is keyed on and in
    538  * the URL the templates' regular expressions are matched against.
    539  *
    540  * Never strips below "scheme://h", so that a URL consisting of
    541  * nothing but a scheme and a host keeps its host.
    542  *
    543  * Refuses a URL carrying a query or a fragment instead of mangling it:
    544  * the trailing '/' would then be part of the query ("http://h/?p=a/")
    545  * and stripping it changes what the query says.  Neither has a sensible
    546  * reading in a base URL we concatenate a request path onto anyway --
    547  * "http://h/?p=a" + "/x" is not a request for /x -- so the operator is
    548  * better told than quietly corrected.
    549  *
    550  * @param[in,out] url URL to canonicalize; must have passed
    551  *        TALER_is_web_url()
    552  * @return false if @a url carries a query or a fragment, in which case
    553  *        it is left untouched
    554  */
    555 static bool
    556 strip_trailing_slashes (char *url)
    557 {
    558   size_t len = strlen (url);
    559   const char *sep;
    560   size_t min_len;
    561 
    562   if ( (NULL != strchr (url,
    563                         '?')) ||
    564        (NULL != strchr (url,
    565                         '#')) )
    566     return false;
    567   sep = strstr (url,
    568                 "://");
    569   GNUNET_assert (NULL != sep); /* was a web URL after all! */
    570   min_len = (size_t) (sep - url) + strlen ("://") + 1;
    571   while ( (len > min_len) &&
    572           ('/' == url[len - 1]) )
    573     url[--len] = '\0';
    574   return true;
    575 }
    576 
    577 
    578 /**
    579  * Main function that will be run.  Main tasks are (1) init. the
    580  * curl infrastructure (curl_global_init() / curl_multi_init()),
    581  * then fetch the HTTP port where its Web service should listen at,
    582  * and finally start MHD on that port.
    583  *
    584  * @param cls closure
    585  * @param args remaining command-line arguments
    586  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    587  * @param c configuration
    588  */
    589 static void
    590 run (void *cls,
    591      char *const *args,
    592      const char *cfgfile,
    593      const struct GNUNET_CONFIGURATION_Handle *c)
    594 {
    595   char *secret;
    596   bool buffer_max_explicit;
    597 
    598   (void) cls;
    599   (void) args;
    600   (void) cfgfile;
    601   PH_cfg = c;
    602   PH_upstream_timeout
    603     = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
    604                                      60);
    605   PH_upstream_stall_timeout
    606     = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
    607                                      60);
    608   PH_shutdown_grace_period
    609     = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
    610                                      60);
    611   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    612                                  NULL);
    613   if ( (0 == PH_request_buffer_max) ||
    614        (PH_request_buffer_max > GNUNET_MAX_MALLOC_CHECKED) )
    615   {
    616     /* 0 would leave no room to move a body through at all; above the
    617        hard allocation cap the buffer simply cannot be made.  A negative
    618        argument ends up here too: GNUnet parses the option with
    619        sscanf("%llu"), which reads "-1" as ULLONG_MAX rather than
    620        complaining. */
    621     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    622                 "-u/--max-upload must be between 1 and %llu:"
    623                 " it sizes the buffer a request body is relayed through\n",
    624                 (unsigned long long) GNUNET_MAX_MALLOC_CHECKED);
    625     PH_global_ret = EXIT_INVALIDARGUMENT;
    626     GNUNET_SCHEDULER_shutdown ();
    627     return;
    628   }
    629   /* `-u' is applied by GNUNET_PROGRAM_run() before we are called, so a
    630      value differing from the compiled-in default is one the operator
    631      wrote.  See PH_max_request_size for why that has to be visible. */
    632   buffer_max_explicit = (PH_DEFAULT_REQUEST_BUFFER_MAX
    633                          != PH_request_buffer_max);
    634   if (! PH_no_check)
    635   {
    636     if (GNUNET_OK !=
    637         TALER_TEMPLATING_init (PAIVANA_project_data ()))
    638     {
    639       /* Almost always a missing or unreadable $PREFIX/share/paivana/
    640          templates/ -- an installation problem, not a bug, so say so
    641          instead of adding a second "Assertion failed" to the one
    642          TALER_TEMPLATING_init() already logged. */
    643       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    644                   "Failed to load the paywall templates; is paivana"
    645                   " installed, and does PAIVANA_PREFIX point at the"
    646                   " installation?\n");
    647       PH_global_ret = EXIT_NOTINSTALLED;
    648       GNUNET_SCHEDULER_shutdown ();
    649       return;
    650     }
    651     if (! PAIVANA_HTTPD_init_template_languages ())
    652     {
    653       PH_global_ret = EXIT_NOTINSTALLED;
    654       GNUNET_SCHEDULER_shutdown ();
    655       return;
    656     }
    657   }
    658   if (! PAIVANA_HTTPD_reverse_init ())
    659   {
    660     GNUNET_break (0);
    661     PH_global_ret = EXIT_FAILURE;
    662     GNUNET_SCHEDULER_shutdown ();
    663     return;
    664   }
    665 
    666   if (GNUNET_OK !=
    667       GNUNET_CONFIGURATION_get_value_string (
    668         c,
    669         "paivana",
    670         "DESTINATION_BASE_URL",
    671         &PH_target_server_base_url))
    672   {
    673     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    674                                "paivana",
    675                                "DESTINATION_BASE_URL");
    676     PH_global_ret = EXIT_NOTCONFIGURED;
    677     GNUNET_SCHEDULER_shutdown ();
    678     return;
    679   }
    680   if (! TALER_is_web_url (PH_target_server_base_url))
    681   {
    682     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    683                                "paivana",
    684                                "DESTINATION_BASE_URL",
    685                                "not a web url");
    686     PH_global_ret = EXIT_NOTCONFIGURED;
    687     GNUNET_SCHEDULER_shutdown ();
    688     return;
    689   }
    690   {
    691     unsigned long long v;
    692 
    693     if (GNUNET_OK ==
    694         GNUNET_CONFIGURATION_get_value_number (c,
    695                                                "paivana",
    696                                                "CONNECTION_LIMIT",
    697                                                &v))
    698     {
    699       if ( (0 == v) ||
    700            (v > UINT_MAX) )
    701       {
    702         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    703                                    "paivana",
    704                                    "CONNECTION_LIMIT",
    705                                    "must be between 1 and UINT_MAX");
    706         PH_global_ret = EXIT_NOTCONFIGURED;
    707         GNUNET_SCHEDULER_shutdown ();
    708         return;
    709       }
    710       PH_connection_limit = (unsigned int) v;
    711     }
    712     if (GNUNET_OK ==
    713         GNUNET_CONFIGURATION_get_value_number (c,
    714                                                "paivana",
    715                                                "PAYMENT_CONNECTION_LIMIT",
    716                                                &v))
    717     {
    718       if ( (0 == v) ||
    719            (v > UINT_MAX) )
    720       {
    721         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    722                                    "paivana",
    723                                    "PAYMENT_CONNECTION_LIMIT",
    724                                    "must be between 1 and UINT_MAX");
    725         PH_global_ret = EXIT_NOTCONFIGURED;
    726         GNUNET_SCHEDULER_shutdown ();
    727         return;
    728       }
    729       PH_payment_connection_limit = (unsigned int) v;
    730     }
    731     if (GNUNET_OK ==
    732         GNUNET_CONFIGURATION_get_value_number (c,
    733                                                "paivana",
    734                                                "PER_IP_CONNECTION_LIMIT",
    735                                                &v))
    736     {
    737       if (v > UINT_MAX)
    738       {
    739         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    740                                    "paivana",
    741                                    "PER_IP_CONNECTION_LIMIT",
    742                                    "must not exceed UINT_MAX");
    743         PH_global_ret = EXIT_NOTCONFIGURED;
    744         GNUNET_SCHEDULER_shutdown ();
    745         return;
    746       }
    747       PH_per_ip_connection_limit = (unsigned int) v;
    748     }
    749     if (GNUNET_OK ==
    750         GNUNET_CONFIGURATION_get_value_number (c,
    751                                                "paivana",
    752                                                "REQUEST_BUFFER_MAX",
    753                                                &v))
    754     {
    755       if ( (0 == v) ||
    756            (v > GNUNET_MAX_MALLOC_CHECKED) )
    757       {
    758         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    759                                    "paivana",
    760                                    "REQUEST_BUFFER_MAX",
    761                                    "must be between 1 and 40 MiB");
    762         PH_global_ret = EXIT_NOTCONFIGURED;
    763         GNUNET_SCHEDULER_shutdown ();
    764         return;
    765       }
    766       PH_request_buffer_max = v;
    767       buffer_max_explicit = true;
    768     }
    769     if (GNUNET_OK ==
    770         GNUNET_CONFIGURATION_get_value_number (c,
    771                                                "paivana",
    772                                                "RESPONSE_BUFFER_MAX",
    773                                                &v))
    774     {
    775       if ( (0 == v) ||
    776            (v > GNUNET_MAX_MALLOC_CHECKED) )
    777       {
    778         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    779                                    "paivana",
    780                                    "RESPONSE_BUFFER_MAX",
    781                                    "must be between 1 and 40 MiB");
    782         PH_global_ret = EXIT_NOTCONFIGURED;
    783         GNUNET_SCHEDULER_shutdown ();
    784         return;
    785       }
    786       PH_response_buffer_max = v;
    787     }
    788     if (GNUNET_OK ==
    789         GNUNET_CONFIGURATION_get_value_number (c,
    790                                                "paivana",
    791                                                "MAX_REQUEST_SIZE",
    792                                                &v))
    793     {
    794       PH_max_request_size = v;
    795     }
    796     else if (buffer_max_explicit)
    797     {
    798       /* Before streaming these were one number: the buffer a body was
    799          assembled in *was* the largest body we would accept.  An
    800          operator who raised it to permit large uploads meant the limit,
    801          so honour that reading rather than silently tightening their
    802          configuration back down to the 1 MiB default. */
    803       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    804                   "Taking MAX_REQUEST_SIZE from the configured request"
    805                   " buffer size (%llu bytes); set it explicitly to say"
    806                   " otherwise\n",
    807                   PH_request_buffer_max);
    808       PH_max_request_size = PH_request_buffer_max;
    809     }
    810     if (GNUNET_OK ==
    811         GNUNET_CONFIGURATION_get_value_number (c,
    812                                                "paivana",
    813                                                "RELAY_MEMORY_LIMIT",
    814                                                &v))
    815     {
    816       if (0 == v)
    817       {
    818         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    819                                    "paivana",
    820                                    "RELAY_MEMORY_LIMIT",
    821                                    "must be at least one byte");
    822         PH_global_ret = EXIT_NOTCONFIGURED;
    823         GNUNET_SCHEDULER_shutdown ();
    824         return;
    825       }
    826       PH_relay_memory_limit = v;
    827     }
    828   }
    829   if (PH_payment_connection_limit >= PH_connection_limit)
    830   {
    831     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    832                                "paivana",
    833                                "PAYMENT_CONNECTION_LIMIT",
    834                                "must be smaller than CONNECTION_LIMIT so"
    835                                " ordinary requests retain capacity");
    836     PH_global_ret = EXIT_NOTCONFIGURED;
    837     GNUNET_SCHEDULER_shutdown ();
    838     return;
    839   }
    840   {
    841     struct GNUNET_TIME_Relative st;
    842 
    843     if (GNUNET_OK ==
    844         GNUNET_CONFIGURATION_get_value_time (c,
    845                                              "paivana",
    846                                              "UPSTREAM_TIMEOUT",
    847                                              &st))
    848     {
    849       if (0 == st.rel_value_us)
    850       {
    851         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    852                                    "paivana",
    853                                    "UPSTREAM_TIMEOUT",
    854                                    "must not be zero");
    855         PH_global_ret = EXIT_NOTCONFIGURED;
    856         GNUNET_SCHEDULER_shutdown ();
    857         return;
    858       }
    859       PH_upstream_timeout = st;
    860     }
    861     if (GNUNET_OK ==
    862         GNUNET_CONFIGURATION_get_value_time (c,
    863                                              "paivana",
    864                                              "UPSTREAM_STALL_TIMEOUT",
    865                                              &st))
    866     {
    867       if (0 == st.rel_value_us)
    868       {
    869         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    870                                    "paivana",
    871                                    "UPSTREAM_STALL_TIMEOUT",
    872                                    "must not be zero");
    873         PH_global_ret = EXIT_NOTCONFIGURED;
    874         GNUNET_SCHEDULER_shutdown ();
    875         return;
    876       }
    877       PH_upstream_stall_timeout = st;
    878     }
    879     if (GNUNET_OK ==
    880         GNUNET_CONFIGURATION_get_value_time (c,
    881                                              "paivana",
    882                                              "SHUTDOWN_GRACE_PERIOD",
    883                                              &st))
    884       PH_shutdown_grace_period = st;
    885   }
    886   if (! check_resource_limits ())
    887   {
    888     PH_global_ret = EXIT_NOTCONFIGURED;
    889     GNUNET_SCHEDULER_shutdown ();
    890     return;
    891   }
    892   {
    893     unsigned int n4;
    894     unsigned int n6;
    895 
    896     if ( (! load_trusted_proxies (c,
    897                                   "TRUSTED_PROXIES",
    898                                   &n4)) ||
    899          (! load_trusted_proxies (c,
    900                                   "TRUSTED_PROXIES6",
    901                                   &n6)) )
    902     {
    903       PH_global_ret = EXIT_NOTCONFIGURED;
    904       GNUNET_SCHEDULER_shutdown ();
    905       return;
    906     }
    907     if (PH_have_trusted_proxies)
    908     {
    909       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    910                   "Trusting %u IPv4 and %u IPv6 network(s) as reverse proxies\n",
    911                   n4,
    912                   n6);
    913       if (! PH_respect_forwarded_headers)
    914       {
    915         /* The policy says which proxies may speak for a client; it is
    916            the -f flag that says we listen at all.  Configuring one
    917            without the other is a mistake in either direction, but only
    918            this one leaves the policy inert. */
    919         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    920                     "TRUSTED_PROXIES configured but -f/--respect-forwarded-headers"
    921                     " is not set; forwarded headers are ignored entirely\n");
    922       }
    923     }
    924     else if (PH_respect_forwarded_headers)
    925     {
    926       /* Not the same mistake in reverse: a single proxy in front, one
    927          that writes the forwarding headers itself, needs no policy at
    928          all.  PAIVANA_HTTPD_resolve_forwarding() walks the chain from
    929          the right and stops at the first hop it does not trust, so
    930          with no policy it stops on its first step -- at the element
    931          our peer wrote, which nothing the client prepends can displace.
    932          What the policy adds is the ability to keep stepping leftwards
    933          through hops that are named there, which is what a chain of
    934          more than one proxy needs.  The hazard worth warning about is
    935          narrower, and it is upstream of us. */
    936       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    937                   "-f/--respect-forwarded-headers is set without TRUSTED_PROXIES:"
    938                   " the client address is taken from the rightmost element of the"
    939                   " forwarding chain, which is the client's own address only if"
    940                   " the server in front sets or appends these headers itself."
    941                   "  One that passes the client's `%s' through unchanged -- as"
    942                   " nginx does, and `%s' is the header we prefer over `%s' --"
    943                   " leaves the client choosing the element we believe."
    944                   "  TRUSTED_PROXIES is needed only for a chain of more than one"
    945                   " proxy\n",
    946                   MHD_HTTP_HEADER_FORWARDED,
    947                   MHD_HTTP_HEADER_FORWARDED,
    948                   PH_HEADER_X_FORWARDED_FOR);
    949     }
    950   }
    951   /* No need to check the return value.  If given, we take it,
    952    * otherwise it stays NULL.  */
    953   GNUNET_CONFIGURATION_get_value_filename (
    954     c,
    955     "paivana",
    956     "DESTINATION_UNIXPATH",
    957     &PH_target_server_unixpath);
    958   if (! strip_trailing_slashes (PH_target_server_base_url))
    959   {
    960     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    961                                "paivana",
    962                                "DESTINATION_BASE_URL",
    963                                "must not carry a query or a fragment");
    964     PH_global_ret = EXIT_NOTCONFIGURED;
    965     GNUNET_SCHEDULER_shutdown ();
    966     return;
    967   }
    968   if (! PH_no_check)
    969   {
    970     if (GNUNET_OK !=
    971         GNUNET_CONFIGURATION_get_value_string (
    972           c,
    973           "paivana",
    974           "MERCHANT_BACKEND_URL",
    975           &PH_merchant_base_url))
    976     {
    977       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    978                                  "paivana",
    979                                  "MERCHANT_BACKEND_URL");
    980       PH_global_ret = EXIT_NOTCONFIGURED;
    981       GNUNET_SCHEDULER_shutdown ();
    982       return;
    983     }
    984     if (! TALER_is_web_url (PH_merchant_base_url))
    985     {
    986       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    987                                  "paivana",
    988                                  "MERCHANT_BACKEND_URL",
    989                                  "not a web url");
    990       PH_global_ret = EXIT_NOTCONFIGURED;
    991       GNUNET_SCHEDULER_shutdown ();
    992       return;
    993     }
    994     /* Deliberately *not* run through strip_trailing_slashes(): a Taler
    995        merchant API base URL is joined with relative paths, so it wants
    996        the '/' the other base URLs above shed.  TALER_url_join() insists
    997        on it and returns NULL without it, which surfaces much later as
    998        an assertion failure on the handle built from the joined URL --
    999        so check for it here, where we can still name the option that is
   1000        wrong. */
   1001     if ('/' != PH_merchant_base_url[strlen (PH_merchant_base_url) - 1])
   1002     {
   1003       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1004                                  "paivana",
   1005                                  "MERCHANT_BACKEND_URL",
   1006                                  "must end with a '/'");
   1007       PH_global_ret = EXIT_NOTCONFIGURED;
   1008       GNUNET_SCHEDULER_shutdown ();
   1009       return;
   1010     }
   1011     if (GNUNET_OK !=
   1012         GNUNET_CONFIGURATION_get_value_string (
   1013           c,
   1014           "paivana",
   1015           "MERCHANT_BACKEND_INTERNAL_URL",
   1016           &PH_merchant_internal_url))
   1017       PH_merchant_internal_url = GNUNET_strdup (PH_merchant_base_url);
   1018     if (! TALER_is_web_url (PH_merchant_internal_url))
   1019     {
   1020       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1021                                  "paivana",
   1022                                  "MERCHANT_BACKEND_INTERNAL_URL",
   1023                                  "not a web url");
   1024       PH_global_ret = EXIT_NOTCONFIGURED;
   1025       GNUNET_SCHEDULER_shutdown ();
   1026       return;
   1027     }
   1028     if ('/' != PH_merchant_internal_url[
   1029           strlen (PH_merchant_internal_url) - 1])
   1030     {
   1031       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1032                                  "paivana",
   1033                                  "MERCHANT_BACKEND_INTERNAL_URL",
   1034                                  "must end with a '/'");
   1035       PH_global_ret = EXIT_NOTCONFIGURED;
   1036       GNUNET_SCHEDULER_shutdown ();
   1037       return;
   1038     }
   1039   }
   1040   {
   1041     char *merchant_unix_path;
   1042 
   1043     if (GNUNET_OK ==
   1044         GNUNET_CONFIGURATION_get_value_string (
   1045           c,
   1046           "paivana",
   1047           "MERCHANT_BACKEND_UNIX_PATH",
   1048           &merchant_unix_path))
   1049     {
   1050       if (! TALER_MERCHANT_global_set_unixpath (merchant_unix_path))
   1051       {
   1052         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
   1053                                    "paivana",
   1054                                    "MERCHANT_BACKEND_UNIX_PATH",
   1055                                    "invalid path; ignoring the setting");
   1056       }
   1057       else
   1058       {
   1059         PH_merchant_unixpath = merchant_unix_path;
   1060         merchant_unix_path = NULL;
   1061       }
   1062       GNUNET_free (merchant_unix_path);
   1063     }
   1064   }
   1065   {
   1066     char *whitelist;
   1067 
   1068     if (GNUNET_OK ==
   1069         GNUNET_CONFIGURATION_get_value_string (
   1070           c,
   1071           "paivana",
   1072           "WHITELIST",
   1073           &whitelist))
   1074     {
   1075       regex_t bare;
   1076       int rc;
   1077 
   1078       /* Compile the expression exactly as written first, and refuse
   1079          the configuration if that fails.  The wrapping below is a
   1080          textual splice, and a value like "a)|(b" splices into
   1081          "^(a)|(b)$" -- which compiles, but means "^a" OR "b$", each
   1082          anchored on one side only.  Getting out of the group that way
   1083          needs parentheses that do not balance on their own, and that
   1084          is precisely what a bare regcomp() rejects; an expression that
   1085          is valid by itself is unaffected and keeps matching what it
   1086          always did.  (The merchant backend validates the website_regex
   1087          of a template the same way.) */
   1088       rc = regcomp (&bare,
   1089                     whitelist,
   1090                     REG_NOSUB | REG_EXTENDED);
   1091       if (0 == rc)
   1092       {
   1093         char *anchored;
   1094 
   1095         regfree (&bare);
   1096         /* Anchor the expression: regexec(3) is unanchored, so a
   1097            WHITELIST of "/free/" would otherwise waive payment for every
   1098            URL merely *containing* it -- including one an attacker
   1099            appends to a path they want for free.  Wrapping in a group
   1100            keeps alternations ("a|b") from binding the anchors to only
   1101            the first and last branch.  An expression that already
   1102            anchors itself is unaffected, as ^ and $ inside still match
   1103            at string start/end. */
   1104         GNUNET_asprintf (&anchored,
   1105                          "^(%s)$",
   1106                          whitelist);
   1107         rc = regcomp (&PH_whitelist_ex,
   1108                       anchored,
   1109                       REG_NOSUB | REG_EXTENDED);
   1110         GNUNET_free (anchored);
   1111       }
   1112       if (0 != rc)
   1113       {
   1114         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1115                                    "paivana",
   1116                                    "WHITELIST",
   1117                                    "Invalid regular expression");
   1118         GNUNET_free (whitelist);
   1119         PH_global_ret = EXIT_NOTCONFIGURED;
   1120         GNUNET_SCHEDULER_shutdown ();
   1121         return;
   1122       }
   1123       PH_have_whitelist_ex = true;
   1124       GNUNET_free (whitelist);
   1125     }
   1126   }
   1127 
   1128   if (GNUNET_OK !=
   1129       GNUNET_CONFIGURATION_get_value_string (
   1130         c,
   1131         "paivana",
   1132         "BASE_URL",
   1133         &PH_base_url))
   1134   {
   1135     /* Without BASE_URL we reconstruct our own URL from the request, and
   1136        the only thing that makes that safe is a reverse proxy in front
   1137        of us that enforced a correct Host: -- which is exactly what -f
   1138        asserts.  Talking to clients directly, Host: is whatever the
   1139        client typed, and it decides both the string the access cookie is
   1140        keyed on and the string the templates' website_regex is matched
   1141        against: a client sending "Host: anything.invalid" would match no
   1142        template and be served for free.  The scheme has the same
   1143        problem in reverse: direct connections have no X-Forwarded-Proto
   1144        to consult, so a site reached over https would generate http://
   1145        URLs and again match no template. */
   1146     if ( (! PH_respect_forwarded_headers) &&
   1147          (! PH_no_check) )
   1148     {
   1149       /* -n has no paywall, hence no access decision that the site's
   1150          own identity could be got wrong for; the base URL is then only
   1151          cosmetic and the "pure reverse proxy" mode stays configurable
   1152          with nothing but DESTINATION_BASE_URL. */
   1153       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1154                                  "paivana",
   1155                                  "BASE_URL");
   1156       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1157                   "BASE_URL is required unless -f is given: without a"
   1158                   " reverse proxy vouching for it, the client's Host"
   1159                   " header cannot be used to identify this site\n");
   1160       PH_global_ret = EXIT_NOTCONFIGURED;
   1161       GNUNET_SCHEDULER_shutdown ();
   1162       return;
   1163     }
   1164     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
   1165                                "paivana",
   1166                                "BASE_URL");
   1167   }
   1168   if (NULL != PH_base_url)
   1169   {
   1170     if (! TALER_is_web_url (PH_base_url))
   1171     {
   1172       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1173                                  "paivana",
   1174                                  "BASE_URL",
   1175                                  "not a web url");
   1176       PH_global_ret = EXIT_NOTCONFIGURED;
   1177       GNUNET_SCHEDULER_shutdown ();
   1178       return;
   1179     }
   1180     if (! strip_trailing_slashes (PH_base_url))
   1181     {
   1182       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1183                                  "paivana",
   1184                                  "BASE_URL",
   1185                                  "must not carry a query or a fragment");
   1186       PH_global_ret = EXIT_NOTCONFIGURED;
   1187       GNUNET_SCHEDULER_shutdown ();
   1188       return;
   1189     }
   1190   }
   1191 
   1192   if (GNUNET_OK !=
   1193       GNUNET_CONFIGURATION_get_value_string (
   1194         c,
   1195         "paivana",
   1196         "SECRET",
   1197         &secret))
   1198   {
   1199     if (! PH_no_check)
   1200     {
   1201       /* The key is the only input to the access-cookie MAC that the
   1202          client does not supply, so a fresh random one invalidates
   1203          every access anyone has paid for.  That used to be a warning
   1204          and a random key, which is survivable only if restarts are
   1205          rare -- and the shipped unit restarts hourly (RuntimeMaxSec),
   1206          so a customer paying at 10:59 was shown the paywall again at
   1207          11:01.  Refuse to start instead: the packaging generates one
   1208          at install time, and an operator configuring by hand needs to
   1209          be told rather than silently sold a paywall that forgets.  `-n'
   1210          mints no cookies at all and so needs no key. */
   1211       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1212                                  "paivana",
   1213                                  "SECRET");
   1214       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1215                   "Refusing to start without `SECRET': every restart"
   1216                   " would invalidate all access already paid for."
   1217                   "  Generate one with `gpg --gen-random 0 32 | base64'"
   1218                   " (or pass -n to serve without a paywall).\n");
   1219       PH_global_ret = EXIT_NOTCONFIGURED;
   1220       GNUNET_SCHEDULER_shutdown ();
   1221       return;
   1222     }
   1223     GNUNET_CRYPTO_random_block (&paivana_secret,
   1224                                 sizeof (paivana_secret));
   1225   }
   1226   else
   1227   {
   1228     GNUNET_CRYPTO_hash (secret,
   1229                         strlen (secret),
   1230                         &paivana_secret);
   1231     GNUNET_free (secret);
   1232   }
   1233   PH_proxy_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1234                                    &proxy_ctx_rc);
   1235   GNUNET_assert (NULL != PH_proxy_ctx);
   1236   proxy_ctx_rc = GNUNET_CURL_gnunet_rc_create (PH_proxy_ctx);
   1237   if (! PH_no_check)
   1238   {
   1239     char *merchant_access_token;
   1240     char *auth_header;
   1241 
   1242     if (GNUNET_OK !=
   1243         GNUNET_CONFIGURATION_get_value_string (
   1244           c,
   1245           "paivana",
   1246           "MERCHANT_ACCESS_TOKEN",
   1247           &merchant_access_token))
   1248     {
   1249       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1250                                  "paivana",
   1251                                  "MERCHANT_ACCESS_TOKEN");
   1252       PH_global_ret = EXIT_NOTCONFIGURED;
   1253       GNUNET_SCHEDULER_shutdown ();
   1254       return;
   1255     }
   1256     if ('\0' == merchant_access_token[0])
   1257     {
   1258       /* An empty value builds "Authorization: Bearer " and the backend
   1259          answers 401 to every request we make with it, i.e. no template
   1260          ever loads and no payment is ever confirmed -- and nothing
   1261          says why.  The option being present but blank is the shape a
   1262          half-finished install has. */
   1263       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1264                                  "paivana",
   1265                                  "MERCHANT_ACCESS_TOKEN",
   1266                                  "must not be empty");
   1267       GNUNET_free (merchant_access_token);
   1268       PH_global_ret = EXIT_NOTCONFIGURED;
   1269       GNUNET_SCHEDULER_shutdown ();
   1270       return;
   1271     }
   1272     /* A second context, because the credential below is appended to
   1273        *every* request the context makes: on a shared context we would
   1274        hand our merchant bearer token to the origin server (and to
   1275        whoever it redirects to) on each forwarded request. */
   1276     PH_merchant_ctx
   1277       = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1278                           &merchant_ctx_rc);
   1279     GNUNET_assert (NULL != PH_merchant_ctx);
   1280     merchant_ctx_rc = GNUNET_CURL_gnunet_rc_create (PH_merchant_ctx);
   1281     GNUNET_asprintf (&auth_header,
   1282                      "%s: Bearer %s",
   1283                      MHD_HTTP_HEADER_AUTHORIZATION,
   1284                      merchant_access_token);
   1285     GNUNET_free (merchant_access_token);
   1286     GNUNET_assert (GNUNET_OK ==
   1287                    GNUNET_CURL_append_header (PH_merchant_ctx,
   1288                                               auth_header));
   1289     GNUNET_free (auth_header);
   1290   }
   1291   /* Once templates are done loading, this will
   1292      start the daemon as well.  In -n (no-payment) mode we skip
   1293      the merchant round-trip entirely. */
   1294   if (PH_no_check)
   1295   {
   1296     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1297                 "Paywall disabled (-n), skipping template load\n");
   1298     PAIVANA_HTTPD_serve_requests ();
   1299     return;
   1300   }
   1301   PAIVANA_HTTPD_load_templates ();
   1302 }
   1303 
   1304 
   1305 /**
   1306  * Main function.
   1307  */
   1308 int
   1309 main (int argc,
   1310       char *const *argv)
   1311 {
   1312   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1313     GNUNET_GETOPT_option_flag (
   1314       'f',
   1315       "respect-forwarded-headers",
   1316       gettext_noop (
   1317         "trust X-Forwarded-For for the client address (only safe behind a trusted reverse proxy)"),
   1318       &PH_respect_forwarded_headers),
   1319     GNUNET_GETOPT_option_flag (
   1320       'g',
   1321       "global-payment",
   1322       gettext_noop (
   1323         "disables per-page payment, useful if a single payment should grant access to the entire site"),
   1324       &PH_global_cookie),
   1325     GNUNET_GETOPT_option_flag (
   1326       'n',
   1327       "no-payment",
   1328       gettext_noop (
   1329         "disables payment, useful for testing reverse-proxy only"),
   1330       &PH_no_check),
   1331     GNUNET_GETOPT_option_ulong (
   1332       'u',
   1333       "max-upload",
   1334       "BYTES",
   1335       gettext_noop (
   1336         "bytes of a request body to hold in memory while relaying it upstream (default: 262144); the largest body accepted is MAX_REQUEST_SIZE"),
   1337       &PH_request_buffer_max),
   1338     GNUNET_GETOPT_OPTION_END
   1339   };
   1340   enum GNUNET_GenericReturnValue ret;
   1341 
   1342   ret = GNUNET_PROGRAM_run (
   1343     PAIVANA_project_data (),
   1344     argc,
   1345     argv,
   1346     "paivana-httpd",
   1347     "reverse proxy requesting Taler payment",
   1348     options,
   1349     &run, NULL);
   1350   if (GNUNET_SYSERR == ret)
   1351     return EXIT_INVALIDARGUMENT;
   1352   if (GNUNET_NO == ret)
   1353     return EXIT_SUCCESS;
   1354   return PH_global_ret;
   1355 }
   1356 
   1357 
   1358 /* end of paivana-httpd.c */