paivana

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

paivana-httpd_templates.c (45539B)


      1 /*
      2      This file is part of GNUnet.
      3      Copyright (C) 2026 Taler Systems SA
      4 
      5      Paivana is free software; you can redistribute it and/or
      6      modify it under the terms of the GNU Affero General Public License
      7      as published by the Free Software Foundation; either version
      8      3, or (at your option) any later version.
      9 
     10      Paivana is distributed in the hope that it will be useful,
     11      but WITHOUT ANY WARRANTY; without even the implied warranty
     12      of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
     13      the GNU Affero General Public License for more details.
     14 
     15      You should have received a copy of the GNU Affero General Public
     16      License along with Paivana; see the file COPYING.  If not,
     17      write to the Free Software Foundation, Inc., 51 Franklin
     18      Street, Fifth Floor, Boston, MA 02110-1301, USA.
     19 */
     20 
     21 /**
     22  * @author Christian Grothoff
     23  * @file paivana-httpd_templates.c
     24  * @brief template functions
     25  */
     26 #include "platform.h"
     27 #include <curl/curl.h>
     28 #include <gnunet/gnunet_util_lib.h>
     29 #include <gnunet/gnunet_uri_lib.h>
     30 #include <gnunet/gnunet_curl_lib.h>
     31 #include "paivana-httpd.h"
     32 #include "paivana-httpd_daemon.h"
     33 #include "paivana-httpd_helper.h"
     34 #include "paivana-httpd_templates.h"
     35 #include <taler/taler_mhd_lib.h>
     36 #include <taler/taler_templating_lib.h>
     37 #include "paivana_pd.h"
     38 #include <regex.h>
     39 
     40 
     41 struct Template;
     42 #define TALER_MERCHANT_GET_PRIVATE_TEMPLATE_RESULT_CLOSURE struct Template
     43 #include <taler/merchant/get-private-templates-TEMPLATE_ID.h>
     44 #include <taler/merchant/get-private-templates.h>
     45 
     46 
     47 /**
     48  * Maximum number of rendered paywall responses we cache per template.
     49  *
     50  * The key is derived from the client-supplied Accept-Language and
     51  * Accept-Encoding headers, so without a bound an attacker could send
     52  * unlimited distinct header values and grow the cache without limit
     53  * (memory-exhaustion DoS on the cheap pre-payment path).  The key is
     54  * normalised to what we can actually serve — see cache_key_language()
     55  * and TALER_MHD_can_compress() — which is what keeps every request
     56  * after the first few a *hit*; this cap only backstops that.  On
     57  * reaching it we evict the least recently used entry.
     58  */
     59 #define MAX_RESPONSE_CACHE_ENTRIES 128
     60 
     61 /**
     62  * How long we give the merchant backend to answer our template
     63  * queries before abandoning the startup sequence.
     64  *
     65  * Nothing is served until they are in: #PAIVANA_HTTPD_serve_requests()
     66  * — which binds the listen sockets — is only reached from the last of
     67  * these callbacks, and neither `TALER_MERCHANT_curl_easy_get_()' nor
     68  * anything above it arms CURLOPT_TIMEOUT.  A backend that accepts the
     69  * TCP connection and then never answers would otherwise leave paivana
     70  * neither serving nor exiting, with no log line after the startup
     71  * banner; under `SERVE = systemd' the listening socket already
     72  * exists, so clients connect successfully and then hang forever with
     73  * nothing accepting them.  Generous enough for the round-trips a load
     74  * takes (GET /private/templates, then one GET per template, issued in
     75  * parallel).
     76  */
     77 #define TEMPLATE_LOAD_TIMEOUT \
     78         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)
     79 
     80 
     81 /**
     82  * Entry in the cache of responses for a given template.
     83  */
     84 struct ResponseCacheEntry
     85 {
     86 
     87   /**
     88    * Kept in a DLL.
     89    */
     90   struct ResponseCacheEntry *next;
     91 
     92   /**
     93    * Kept in a DLL.
     94    */
     95   struct ResponseCacheEntry *prev;
     96 
     97   /**
     98    * Language of the response.
     99    */
    100   char *lang;
    101 
    102   /**
    103    * True if @e paywall carries a deflate-compressed body.
    104    */
    105   bool deflate;
    106 
    107   /**
    108    * Paywall response for these request parameters.
    109    */
    110   struct MHD_Response *paywall;
    111 
    112   /**
    113    * HTTP status to return with @e paywall.
    114    */
    115   unsigned int http_status;
    116 
    117 };
    118 
    119 
    120 /**
    121  * Information about a template in the merchant backend.
    122  */
    123 struct Template
    124 {
    125 
    126   /**
    127    * Kept in a DLL.
    128    */
    129   struct Template *next;
    130 
    131   /**
    132    * Kept in a DLL.
    133    */
    134   struct Template *prev;
    135 
    136   /**
    137    * ID of the template.
    138    */
    139   char *template_id;
    140 
    141   /**
    142    * Summary of the template, NULL if not given.
    143    */
    144   char *summary;
    145 
    146   /**
    147    * Maximum pickup delay for the pages.
    148    */
    149   struct GNUNET_TIME_Relative max_pickup_delay;
    150 
    151   /**
    152    * Ways how to pay for the template.
    153    */
    154   json_t *choices;
    155 
    156   /**
    157    * Regular expression of websites the template is for.
    158    */
    159   char *regex;
    160 
    161   /**
    162    * Pre-compiled regular expression @e regex.
    163    */
    164   regex_t ex;
    165 
    166   /**
    167    * Handle used to request more information about the template.
    168    */
    169   struct TALER_MERCHANT_GetPrivateTemplateHandle *gt;
    170 
    171   /**
    172    * Number of entries in the template cache starting at @e rce_head.
    173    */
    174   unsigned int rce_length;
    175 
    176   /**
    177    * Kept in a DLL.
    178    */
    179   struct ResponseCacheEntry *rce_head;
    180 
    181   /**
    182    * Kept in a DLL.
    183    */
    184   struct ResponseCacheEntry *rce_tail;
    185 
    186 };
    187 
    188 
    189 /**
    190  * Kept in a DLL.
    191  */
    192 static struct Template *t_head;
    193 
    194 /**
    195  * Kept in a DLL.
    196  */
    197 static struct Template *t_tail;
    198 
    199 /**
    200  * Handle to get all the templates.
    201  */
    202 static struct TALER_MERCHANT_GetPrivateTemplatesHandle *gpt;
    203 
    204 /**
    205  * Watchdog for #TEMPLATE_LOAD_TIMEOUT, NULL once the templates are in
    206  * (or once we have given up on them).
    207  */
    208 static struct GNUNET_SCHEDULER_Task *load_timeout_task;
    209 
    210 
    211 /**
    212  * Task run when the merchant backend did not answer our template
    213  * queries within #TEMPLATE_LOAD_TIMEOUT.
    214  *
    215  * Treated exactly like any other failure to load the templates (an
    216  * unauthorized or unexpected status from the backend): the daemon
    217  * exits with a diagnosis instead of stalling.  Whether that policy is
    218  * the right one is a separate question — this task only makes sure the
    219  * stall is not a third, silent outcome.
    220  *
    221  * @param cls NULL
    222  */
    223 static void
    224 load_timeout (void *cls)
    225 {
    226   (void) cls;
    227   load_timeout_task = NULL;
    228   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    229               "Merchant backend at `%s' did not answer our template queries"
    230               " within %s; giving up instead of never starting to serve\n",
    231               PH_merchant_base_url,
    232               GNUNET_STRINGS_relative_time_to_string (TEMPLATE_LOAD_TIMEOUT,
    233                                                       true));
    234   PH_global_ret = EXIT_FAILURE;
    235   GNUNET_SCHEDULER_shutdown ();
    236 }
    237 
    238 
    239 /**
    240  * The templates are in: stop the watchdog and open the listen sockets.
    241  *
    242  * Refuses to start if there is not a single one.  Only reachable with
    243  * the paywall enabled -- `-n' returns before
    244  * #PAIVANA_HTTPD_load_templates() is ever called -- and a paywall with
    245  * nothing to sell is not a paywall: PAIVANA_HTTPD_search_templates()
    246  * would answer "no template matched" for every URL, which create_response()
    247  * reads as "no paywall applies" and forwards.  The whole site would be
    248  * free, silently, which is exactly the outcome an operator running a
    249  * paywall did not ask for.  An operator who does want a plain reverse
    250  * proxy says so with `-n'.
    251  */
    252 static void
    253 templates_ready (void)
    254 {
    255   if (NULL == t_head)
    256   {
    257     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    258                 "The merchant backend at `%s' offers no paivana template;"
    259                 " refusing to start, as every request would then be"
    260                 " forwarded for free.  Pass -n if serving the site without"
    261                 " a paywall is what you want.\n",
    262                 PH_merchant_base_url);
    263     PH_global_ret = EXIT_NOTCONFIGURED;
    264     GNUNET_SCHEDULER_shutdown ();
    265     return;
    266   }
    267   if (NULL != load_timeout_task)
    268   {
    269     GNUNET_SCHEDULER_cancel (load_timeout_task);
    270     load_timeout_task = NULL;
    271   }
    272   PAIVANA_HTTPD_serve_requests ();
    273 }
    274 
    275 
    276 /**
    277  * Check if two strings are equal, including both being NULL
    278  *
    279  * @param s1 a string, possibly NULL
    280  * @param s2 a string. possibly NULL
    281  * @return true if both are equal
    282  */
    283 static bool
    284 eq (const char *s1,
    285     const char *s2)
    286 {
    287   if (s1 == s2)
    288     return true;
    289   if (NULL == s1)
    290     return false;
    291   if (NULL == s2)
    292     return false;
    293   return (0 == strcmp (s1,
    294                        s2));
    295 }
    296 
    297 
    298 /**
    299  * Create a taler://pay-template/ URI for the given @a con and @a template_id
    300  * and @a instance_id.
    301  *
    302  * @param merchant_base_url URL to take host and path from;
    303  *        we cannot take it from the MHD connection as a browser
    304  *        may have changed 'http' to 'https' and we MUST be consistent
    305  *        with what the merchant's frontend used initially
    306  * @param template_id the template id
    307  * @return corresponding taler://pay-template/ URI, or NULL on missing "host"
    308  */
    309 static char *
    310 make_taler_pay_template_uri (const char *merchant_base_url,
    311                              const char *template_id)
    312 {
    313   struct GNUNET_Buffer buf = { 0 };
    314   char *url;
    315   struct GNUNET_Uri uri;
    316 
    317   url = GNUNET_strdup (merchant_base_url);
    318   if (-1 == GNUNET_uri_parse (&uri,
    319                               url))
    320   {
    321     GNUNET_break (0);
    322     GNUNET_free (url);
    323     return NULL;
    324   }
    325   if ( (NULL == uri.scheme) ||
    326        (NULL == uri.host) )
    327   {
    328     GNUNET_break (0);
    329     GNUNET_free (url);
    330     return NULL;
    331   }
    332   GNUNET_assert (NULL != template_id);
    333   GNUNET_buffer_write_str (&buf,
    334                            "taler");
    335   if (0 == strcasecmp ("http",
    336                        uri.scheme))
    337     GNUNET_buffer_write_str (&buf,
    338                              "+http");
    339   GNUNET_buffer_write_str (&buf,
    340                            "://pay-template/");
    341   GNUNET_buffer_write_str (&buf,
    342                            uri.host);
    343   if (0 != uri.port)
    344     GNUNET_buffer_write_fstr (&buf,
    345                               ":%u",
    346                               (unsigned int) uri.port);
    347   if (NULL != uri.path)
    348     GNUNET_buffer_write_path (&buf,
    349                               uri.path);
    350   GNUNET_buffer_write_path (&buf,
    351                             template_id);
    352   GNUNET_free (url);
    353   return GNUNET_buffer_reap_str (&buf);
    354 }
    355 
    356 
    357 /**
    358  * Render @a s as a complete, double-quoted JavaScript string literal.
    359  *
    360  * The paywall page carries its context in `const' declarations inside a
    361  * <script> element, and mustache's default escaping is the wrong
    362  * escaping there twice over: it escapes exactly '<', '>', '&' and '"'
    363  * (mustach-wrap.c), which leaves the apostrophe free to close a
    364  * single-quoted literal and let arbitrary JavaScript follow, and the
    365  * entity references it does produce are never decoded, because the HTML
    366  * parser does not decode them inside a raw-text element -- an '&' in the
    367  * merchant base URL reached the script as the literal text "&amp;".
    368  *
    369  * So the value is escaped for the context it actually lands in, and
    370  * interpolated with the unescaped {{{ }}} since it arrives complete with
    371  * its quotes.  '<', '>' and '&' are still escaped, as \\uXXXX rather
    372  * than as entities: without that a value containing "</script>" would
    373  * end the element regardless of how well the string literal itself is
    374  * quoted.
    375  *
    376  * @param s string to render, must be valid UTF-8
    377  * @return JavaScript literal including the surrounding quotes,
    378  *         to be freed by the caller
    379  */
    380 static char *
    381 js_string_literal (const char *s)
    382 {
    383   struct GNUNET_Buffer buf = { 0 };
    384 
    385   GNUNET_buffer_write_str (&buf,
    386                            "\"");
    387   for (const unsigned char *p = (const unsigned char *) s;
    388        '\0' != *p;
    389        p++)
    390   {
    391     switch (*p)
    392     {
    393     case '"':
    394       GNUNET_buffer_write_str (&buf,
    395                                "\\\"");
    396       break;
    397     case '\\':
    398       GNUNET_buffer_write_str (&buf,
    399                                "\\\\");
    400       break;
    401     default:
    402       if ( (*p < 0x20) ||
    403            (0x7F == *p) ||
    404            ('<' == *p) ||
    405            ('>' == *p) ||
    406            ('&' == *p) )
    407         GNUNET_buffer_write_fstr (&buf,
    408                                   "\\u%04x",
    409                                   (unsigned int) *p);
    410       else
    411         GNUNET_buffer_write_fstr (&buf,
    412                                   "%c",
    413                                   (char) *p);
    414       break;
    415     }
    416   }
    417   GNUNET_buffer_write_str (&buf,
    418                            "\"");
    419   return GNUNET_buffer_reap_str (&buf);
    420 }
    421 
    422 
    423 /**
    424  * Number of installed `paywall.*.must' templates, counted once by
    425  * count_paywall_templates().  UINT_MAX until then.
    426  */
    427 static unsigned int paywall_template_count = UINT_MAX;
    428 
    429 
    430 /**
    431  * Count the installed paywall templates.
    432  *
    433  * TALER_TEMPLATING_build() picks among them by matching the client's
    434  * `Accept-Language' against the language tag in each file name, and
    435  * does not report which one it chose.  We do not need to know: if there
    436  * is only one to choose from — the shipped configuration, which ships
    437  * `paywall.en.must' and nothing else — then every `Accept-Language'
    438  * whatsoever produces the same body, and the header can be dropped from
    439  * the cache key entirely.
    440  *
    441  * @param cls unused
    442  * @param filename file found in the template directory
    443  * @return #GNUNET_OK to continue the scan
    444  */
    445 static enum GNUNET_GenericReturnValue
    446 count_paywall_template (void *cls,
    447                         const char *filename)
    448 {
    449   const char *base;
    450 
    451   (void) cls;
    452   base = strrchr (filename,
    453                   '/');
    454   base = (NULL == base) ? filename : base + 1;
    455   if (0 == strncmp (base,
    456                     "paywall.",
    457                     strlen ("paywall.")))
    458     paywall_template_count++;
    459   return GNUNET_OK;
    460 }
    461 
    462 
    463 /**
    464  * The `Content-Security-Policy' for the paywall page, built once from
    465  * #PH_merchant_base_url.  NULL until first needed.
    466  */
    467 static char *paywall_csp;
    468 
    469 
    470 /**
    471  * Return the `Content-Security-Policy' for the paywall page.
    472  *
    473  * The page is entirely self-contained -- every stylesheet, script and
    474  * image is inline -- so everything but the merchant backend it polls
    475  * can be denied outright.  That is the part worth having: with
    476  * `default-src' at 'none' and `connect-src' naming exactly two origins,
    477  * script that does run cannot reach an attacker's host to report what
    478  * it found, and `frame-ancestors' keeps the taler:// link from being
    479  * framed and clicked by proxy.
    480  *
    481  * `script-src' still has to permit inline script: the page carries three
    482  * inline <script> elements and one `onclick' attribute, and neither
    483  * hashes nor a nonce survive our response cache, which serves one
    484  * rendered body to every client for five minutes.  Removing
    485  * 'unsafe-inline' means moving that handler into paywall.js and hashing
    486  * each block after rendering; worth doing, but it is not what makes the
    487  * injection this policy backs up impossible -- js_string_literal() is.
    488  *
    489  * @return the policy, owned by this module, or NULL if the merchant
    490  *         base URL cannot be parsed
    491  */
    492 static const char *
    493 get_paywall_csp (void)
    494 {
    495   struct GNUNET_Buffer buf = { 0 };
    496   struct GNUNET_Uri uri;
    497   char *url;
    498 
    499   if (NULL != paywall_csp)
    500     return paywall_csp;
    501   url = GNUNET_strdup (PH_merchant_base_url);
    502   if ( (-1 == GNUNET_uri_parse (&uri,
    503                                 url)) ||
    504        (NULL == uri.scheme) ||
    505        (NULL == uri.host) )
    506   {
    507     /* Cannot name the backend, and a policy that omits it would break
    508        the polling the page exists to do.  Serve without one rather than
    509        with a broken one; the URL is checked at startup, so this is the
    510        unreachable arm. */
    511     GNUNET_break (0);
    512     GNUNET_free (url);
    513     return NULL;
    514   }
    515   GNUNET_buffer_write_str (&buf,
    516                            "default-src 'none'; "
    517                            "script-src 'unsafe-inline'; "
    518                            "style-src 'unsafe-inline'; "
    519                            /* the QR code is drawn to a canvas and
    520                               handed to an <img> as a data: URL */
    521                            "img-src data:; "
    522                            "connect-src 'self' ");
    523   GNUNET_buffer_write_str (&buf,
    524                            uri.scheme);
    525   GNUNET_buffer_write_str (&buf,
    526                            "://");
    527   GNUNET_buffer_write_str (&buf,
    528                            uri.host);
    529   if (0 != uri.port)
    530     GNUNET_buffer_write_fstr (&buf,
    531                               ":%u",
    532                               (unsigned int) uri.port);
    533   GNUNET_buffer_write_str (&buf,
    534                            "; frame-ancestors 'none'"
    535                            "; base-uri 'none'"
    536                            "; form-action 'none'");
    537   GNUNET_free (url);
    538   paywall_csp = GNUNET_buffer_reap_str (&buf);
    539   return paywall_csp;
    540 }
    541 
    542 
    543 /**
    544  * Return the language component of the render cache key for @a conn.
    545  *
    546  * With a single installed paywall template there is nothing to
    547  * negotiate, so the key does not depend on `Accept-Language' at all and
    548  * the whole header — an unauthenticated client's free choice, on the
    549  * pre-payment path — stops being able to force a fresh Mustache render
    550  * per distinct value.  With several installed we cannot tell which one
    551  * the templating library picked (it has no accessor for that; the
    552  * proper fix belongs there), so we fall back to keying on the raw
    553  * header and accept the amplification for that configuration.
    554  *
    555  * @param conn connection to derive the key component for
    556  * @return the key component, or NULL if `Accept-Language' does not
    557  *         affect the rendered body
    558  */
    559 static const char *
    560 cache_key_language (struct MHD_Connection *conn)
    561 {
    562   if (UINT_MAX == paywall_template_count)
    563   {
    564     char *dir;
    565     char *tdir;
    566 
    567     paywall_template_count = 0;
    568     dir = GNUNET_OS_installation_get_path (PAIVANA_project_data (),
    569                                            GNUNET_OS_IPK_DATADIR);
    570     GNUNET_asprintf (&tdir,
    571                      "%stemplates",
    572                      dir);
    573     GNUNET_free (dir);
    574     if (0 > GNUNET_DISK_directory_scan (tdir,
    575                                         &count_paywall_template,
    576                                         NULL))
    577     {
    578       /* Cannot tell; assume the worst and keep the old key. */
    579       GNUNET_break (0);
    580       paywall_template_count = 2;
    581     }
    582     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    583                 "%u paywall template(s) installed in `%s'\n",
    584                 paywall_template_count,
    585                 tdir);
    586     GNUNET_free (tdir);
    587   }
    588   if (2 > paywall_template_count)
    589     return NULL;
    590   return MHD_lookup_connection_value (conn,
    591                                       MHD_HEADER_KIND,
    592                                       MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
    593 }
    594 
    595 
    596 /**
    597  * Try to initialize the paywall response.
    598  *
    599  * @param conn connection to create the response for
    600  * @param t template to create the response for
    601  * @return MHD status code to return
    602  */
    603 static enum MHD_Result
    604 load_paywall (struct MHD_Connection *conn,
    605               struct Template *t)
    606 {
    607   struct MHD_Response *reply;
    608   const char *lang;
    609   bool deflate;
    610   unsigned int http_status = MHD_HTTP_PAYMENT_REQUIRED;
    611 
    612   lang = cache_key_language (conn);
    613   /* `Accept-Encoding' reaches the body through exactly this predicate
    614      (templating_api.c), so keying on its result rather than on the
    615      header text is not an approximation: it is the decision itself,
    616      and it has two outcomes instead of unboundedly many. */
    617   deflate = (TALER_MHD_CT_DEFLATE ==
    618              TALER_MHD_can_compress (conn,
    619                                      TALER_MHD_CT_DEFLATE));
    620   for (struct ResponseCacheEntry *pos = t->rce_head;
    621        NULL != pos;
    622        pos = pos->next)
    623   {
    624     if ( (eq (lang,
    625               pos->lang)) &&
    626          (deflate == pos->deflate) )
    627     {
    628       if (t->rce_head != pos)
    629       {
    630         /* Hit, move pos to head of DLL for proper LRU eviction */
    631         GNUNET_CONTAINER_DLL_remove (t->rce_head,
    632                                      t->rce_tail,
    633                                      pos);
    634         GNUNET_CONTAINER_DLL_insert (t->rce_head,
    635                                      t->rce_tail,
    636                                      pos);
    637       }
    638       return MHD_queue_response (conn,
    639                                  pos->http_status,
    640                                  pos->paywall);
    641     }
    642   }
    643 
    644   {
    645     enum GNUNET_GenericReturnValue ret;
    646     json_t *data;
    647     char *tid_js = js_string_literal (t->template_id);
    648     char *mb_js = js_string_literal (PH_merchant_base_url);
    649 
    650     data = GNUNET_JSON_PACK (
    651       GNUNET_JSON_pack_string (
    652         "template_id",
    653         t->template_id),
    654       /* The `_js' variants are complete JavaScript string literals,
    655          quotes included, for the <script> block; the plain ones are for
    656          the HTML body, where mustache's own escaping is correct. */
    657       GNUNET_JSON_pack_string (
    658         "template_id_js",
    659         tid_js),
    660       GNUNET_JSON_pack_string (
    661         "merchant_backend_js",
    662         mb_js),
    663       GNUNET_JSON_pack_allow_null (
    664         GNUNET_JSON_pack_string (
    665           "summary",
    666           t->summary)),
    667       GNUNET_JSON_pack_allow_null (
    668         GNUNET_JSON_pack_array_incref (
    669           "choices",
    670           t->choices)),
    671       GNUNET_JSON_pack_bool (
    672         "has_choices",
    673         1 < json_array_size (t->choices)),
    674       GNUNET_JSON_pack_allow_null (
    675         GNUNET_JSON_pack_object_incref (
    676           "default_choice",
    677           json_array_get (t->choices, 0))),
    678       GNUNET_JSON_pack_uint64 (
    679         "max_pickup_delay",
    680         /* Note: 'forever' will result in a very large number
    681            here, that is intentional, it is equivalent and avoids
    682            a special case on the client-side. */
    683         t->max_pickup_delay.rel_value_us / 1000LLU / 1000LLU),
    684       GNUNET_JSON_pack_string (
    685         "merchant_backend",
    686         PH_merchant_base_url));
    687     GNUNET_free (tid_js);
    688     GNUNET_free (mb_js);
    689     ret = TALER_TEMPLATING_build (
    690       conn,
    691       &http_status,
    692       "paywall",
    693       NULL /* no instance */,
    694       NULL /* no Taler URI (needs dynamic paivana_id!) */,
    695       data,
    696       &reply);
    697     json_decref (data);
    698     if (GNUNET_NO == ret)
    699     {
    700       enum MHD_Result mret;
    701 
    702       /* taler_templating_lib.h: #GNUNET_NO means an *error reply* was
    703          built — typically because the paywall template is not
    704          installed where TALER_TEMPLATING_init() looked.  It is a live
    705          MHD_Response and it is ours: returning #MHD_YES without
    706          queuing it leaked it once per unauthenticated request and left
    707          the client waiting for a status line that never came. */
    708       GNUNET_break (0);
    709       mret = MHD_queue_response (conn,
    710                                  http_status,
    711                                  reply);
    712       MHD_destroy_response (reply);
    713       return mret;
    714     }
    715     if (GNUNET_OK != ret)
    716     {
    717       /* #GNUNET_SYSERR: no reply was built, so there is nothing to
    718          queue and nothing to free; MHD must close the connection. */
    719       GNUNET_break (0);
    720       return MHD_NO;
    721     }
    722   }
    723 
    724 
    725   GNUNET_break (MHD_YES ==
    726                 MHD_add_response_header (reply,
    727                                          MHD_HTTP_HEADER_CONTENT_TYPE,
    728                                          "text/html"));
    729   /* The paywall body depends on the negotiated language and on
    730      whether we deflated it for the client; tell intermediaries to
    731      key their cache entries on both. */
    732   GNUNET_break (MHD_YES ==
    733                 MHD_add_response_header (reply,
    734                                          MHD_HTTP_HEADER_VARY,
    735                                          MHD_HTTP_HEADER_ACCEPT_LANGUAGE ", "
    736                                          MHD_HTTP_HEADER_ACCEPT_ENCODING ", "
    737                                          "Cookie"));
    738   GNUNET_break (MHD_YES ==
    739                 MHD_add_response_header (reply,
    740                                          MHD_HTTP_HEADER_CACHE_CONTROL,
    741                                          "public, max-age=300"));
    742   {
    743     const char *csp = get_paywall_csp ();
    744 
    745     if (NULL != csp)
    746       GNUNET_break (MHD_YES ==
    747                     MHD_add_response_header (reply,
    748                                              MHD_HTTP_HEADER_CONTENT_SECURITY_POLICY,
    749                                              csp));
    750   }
    751   /* frame-ancestors covers this for anything current; X-Frame-Options
    752      is for the user agents that do not implement it. */
    753   GNUNET_break (MHD_YES ==
    754                 MHD_add_response_header (reply,
    755                                          MHD_HTTP_HEADER_X_FRAME_OPTIONS,
    756                                          "DENY"));
    757   GNUNET_break (MHD_YES ==
    758                 MHD_add_response_header (reply,
    759                                          MHD_HTTP_HEADER_X_CONTENT_TYPE_OPTIONS,
    760                                          "nosniff"));
    761   {
    762     char *uri;
    763 
    764     uri = make_taler_pay_template_uri (PH_merchant_base_url,
    765                                        t->template_id);
    766     if (NULL != uri)
    767     {
    768       GNUNET_break (MHD_YES ==
    769                     MHD_add_response_header (reply,
    770                                              "Paivana",
    771                                              uri));
    772       GNUNET_free (uri);
    773     }
    774   }
    775 
    776   {
    777     struct ResponseCacheEntry *rce;
    778 
    779     /* '>=', not '>': the insert below is what takes us to the cap, so
    780        testing '>' left the steady state one entry above it. */
    781     while (t->rce_length >= MAX_RESPONSE_CACHE_ENTRIES)
    782     {
    783       /* Evict the least recently used entry; the hit path above
    784          promotes to the head, so the tail is the coldest. */
    785       struct ResponseCacheEntry *old = t->rce_tail;
    786 
    787       GNUNET_CONTAINER_DLL_remove (t->rce_head,
    788                                    t->rce_tail,
    789                                    old);
    790       GNUNET_assert (t->rce_length > 0);
    791       t->rce_length--;
    792       MHD_destroy_response (old->paywall);
    793       GNUNET_free (old->lang);
    794       GNUNET_free (old);
    795     }
    796     rce = GNUNET_new (struct ResponseCacheEntry);
    797     if (NULL != lang)
    798       rce->lang = GNUNET_strdup (lang);
    799     rce->deflate = deflate;
    800     rce->paywall = reply;
    801     rce->http_status = http_status;
    802     t->rce_length++;
    803     GNUNET_CONTAINER_DLL_insert (t->rce_head,
    804                                  t->rce_tail,
    805                                  rce);
    806     return MHD_queue_response (conn,
    807                                rce->http_status,
    808                                reply);
    809   }
    810 }
    811 
    812 
    813 /**
    814  * Parse template contract to (mostly) determine the
    815  * regex specifying which websites the template applies to.
    816  *
    817  * @param[in,out] t template to update
    818  * @param contract contract to parse
    819  * @return true on success, false on failure
    820  */
    821 static bool
    822 parse_template (struct Template *t,
    823                 const json_t *contract)
    824 {
    825   /* An absent website_regex means "every URL".  So does an empty one:
    826      it is what a merchant UI stores for a template that was never
    827      given a restriction, and anchoring it would yield "^()$", which
    828      matches only the empty string and therefore no URL at all -- a
    829      template that silently paywalls nothing. */
    830   const char *regex = NULL;
    831   const char *summary = NULL;
    832   const json_t *choices = NULL;
    833   struct GNUNET_JSON_Specification spec[] = {
    834     GNUNET_JSON_spec_mark_optional (
    835       GNUNET_JSON_spec_string ("website_regex",
    836                                &regex),
    837       NULL),
    838     GNUNET_JSON_spec_mark_optional (
    839       GNUNET_JSON_spec_string ("summary",
    840                                &summary),
    841       NULL),
    842     GNUNET_JSON_spec_array_const ("choices",
    843                                   &choices),
    844     GNUNET_JSON_spec_mark_optional (
    845       GNUNET_JSON_spec_relative_time ("max_pickup_duration",
    846                                       &t->max_pickup_delay),
    847       NULL),
    848     GNUNET_JSON_spec_end ()
    849   };
    850   const char *en;
    851 
    852   if (GNUNET_OK !=
    853       GNUNET_JSON_parse ((json_t *) contract,
    854                          spec,
    855                          &en,
    856                          NULL))
    857   {
    858     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    859                 "Invalid template %s at field %s\n",
    860                 t->template_id,
    861                 en);
    862     return false;
    863   }
    864   /* GNUNET_JSON_spec_array_const() only establishes that "choices" is
    865      an array; what is *in* it is whatever the backend sent.
    866      load_paywall() hands element 0 to
    867      GNUNET_JSON_pack_object_incref(), which aborts on anything that is
    868      not an object — and it does so from an unauthenticated GET of the
    869      paywall page, so a backend that disagrees with us about the schema
    870      would turn every visitor into a crash loop.  We cross a network
    871      trust boundary here and must not assert on what comes back. */
    872   {
    873     size_t idx;
    874     json_t *choice;
    875 
    876     json_array_foreach ((json_t *) choices, idx, choice)
    877     {
    878       if (json_is_object (choice))
    879         continue;
    880       GNUNET_break_op (0);
    881       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    882                   "Template %s has a non-object choice at index %u\n",
    883                   t->template_id,
    884                   (unsigned int) idx);
    885       return false;
    886     }
    887   }
    888   if ( (NULL != regex) &&
    889        ('\0' != regex[0]) )
    890   {
    891     char *anchored;
    892     regex_t bare;
    893     int rc;
    894 
    895     /* Compile the merchant's expression as it stands *before* wrapping
    896        it, and refuse the template if that fails.  Splicing into a
    897        group is not a syntactic no-op: "a)|(b" becomes "^(a)|(b)$",
    898        which is "^a" OR "b$" -- each anchored on one side only, so the
    899        anchoring below stops being the guarantee the manual states.
    900        Every such breakout needs a parenthesis that only balances
    901        against the ones we add, which is exactly what a bare regcomp()
    902        rejects.  The merchant backend compiles the bare expression on
    903        POST /private/templates for the same reason; this closes the gap
    904        for a template that reached the database some other way.  Note
    905        that no expression that compiles on its own changes meaning
    906        here. */
    907     rc = regcomp (&bare,
    908                   regex,
    909                   REG_NOSUB | REG_EXTENDED);
    910     if (0 != rc)
    911     {
    912       GNUNET_break_op (0);
    913       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    914                   "Invalid regex in template %s: %s\n",
    915                   t->template_id,
    916                   regex);
    917       return false;
    918     }
    919     regfree (&bare);
    920     /* Anchor the merchant's expression: regexec(3) is unanchored, so
    921        an expression like "/premium/" would otherwise put a paywall on
    922        every URL merely *containing* it.  Wrapping in a group keeps
    923        alternations ("a|b") from binding the anchors to only the first
    924        and last branch.  An expression that already anchors itself is
    925        unaffected, as ^ and $ inside still match at string
    926        start/end. */
    927     GNUNET_asprintf (&anchored,
    928                      "^(%s)$",
    929                      regex);
    930     rc = regcomp (&t->ex,
    931                   anchored,
    932                   REG_NOSUB | REG_EXTENDED);
    933     GNUNET_free (anchored);
    934     if (0 != rc)
    935     {
    936       GNUNET_break_op (0);
    937       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    938                   "Invalid regex in template %s: %s\n",
    939                   t->template_id,
    940                   regex);
    941       return false;
    942     }
    943     t->regex = GNUNET_strdup (regex);
    944   }
    945   if (NULL != summary)
    946     t->summary = GNUNET_strdup (summary);
    947   t->choices = json_incref ((json_t *) choices);
    948   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    949               "Using payment template %s for `%s'\n",
    950               t->template_id,
    951               (NULL != t->regex) ? t->regex : "(all URLs)");
    952   return true;
    953 }
    954 
    955 
    956 /**
    957  * Is @a contract a template that is ours to serve?
    958  *
    959  * The merchant's `template_type' discriminator; the literals are
    960  * TALER_MERCHANT_template_type_from_string(), which we would call if
    961  * it did not live in a library paivana does not otherwise need.  Note
    962  * that an absent `template_type' is `fixed-order' there, so it is not
    963  * ours either.
    964  *
    965  * @param contract template contract from the backend
    966  * @return true if this is a paivana template
    967  */
    968 static bool
    969 is_paivana_template (const json_t *contract)
    970 {
    971   const json_t *tt;
    972 
    973   tt = json_object_get (contract,
    974                         "template_type");
    975   return ( (NULL != tt) &&
    976            (json_is_string (tt)) &&
    977            (0 == strcmp ("paivana",
    978                          json_string_value (tt))) );
    979 }
    980 
    981 
    982 /**
    983  * Remove @a t from the list of templates and free it.
    984  *
    985  * Only for a template we decided not to use before anything was parsed
    986  * into it: @e gt must already be NULL and nothing but the ID
    987  * allocated.
    988  *
    989  * @param[in] t template to drop
    990  */
    991 static void
    992 drop_template (struct Template *t)
    993 {
    994   GNUNET_assert (NULL == t->gt);
    995   GNUNET_assert (NULL == t->rce_head);
    996   GNUNET_CONTAINER_DLL_remove (t_head,
    997                                t_tail,
    998                                t);
    999   GNUNET_free (t->template_id);
   1000   GNUNET_free (t);
   1001 }
   1002 
   1003 
   1004 /**
   1005  * Callback for a GET /private/templates/$TEMPLATE_ID request.
   1006  *
   1007  * @param t template the request was about
   1008  * @param tgr response details
   1009  */
   1010 static void
   1011 setup_template (
   1012   struct Template *t,
   1013   const struct TALER_MERCHANT_GetPrivateTemplateResponse *tgr)
   1014 {
   1015   t->gt = NULL;
   1016   switch (tgr->hr.http_status)
   1017   {
   1018   case MHD_HTTP_OK:
   1019     if (! is_paivana_template (tgr->details.ok.template_contract))
   1020     {
   1021       /* One merchant instance serves every kind of template, and
   1022          `template_type' is what says which of them are ours -- as the
   1023          manual promises.  Without this check a fixed-order template is
   1024          handed to parse_template(), which fails it for want of
   1025          `choices' and reports "Invalid template X at field choices":
   1026          an error about a template that is simply none of our business,
   1027          and (since a parse failure is fatal) one that keeps paivana
   1028          from starting at all next to a perfectly good paivana
   1029          template. */
   1030       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1031                   "Ignoring template %s: not a paivana template\n",
   1032                   t->template_id);
   1033       drop_template (t);
   1034       break;
   1035     }
   1036     if (! parse_template (t,
   1037                           tgr->details.ok.template_contract))
   1038     {
   1039       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1040                   "Failed to parse template %s, refusing to start\n",
   1041                   t->template_id);
   1042       PH_global_ret = EXIT_FAILURE;
   1043       GNUNET_SCHEDULER_shutdown ();
   1044       return;
   1045     }
   1046     break;
   1047   default:
   1048     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1049                 "Failed to load template %s from backend"
   1050                 " (HTTP status %u), refusing to start\n",
   1051                 t->template_id,
   1052                 tgr->hr.http_status);
   1053     PH_global_ret = EXIT_FAILURE;
   1054     GNUNET_SCHEDULER_shutdown ();
   1055     return;
   1056   }
   1057   for (struct Template *p = t_head; NULL != p; p = p->next)
   1058     if (NULL != p->gt)
   1059       return;
   1060   /* all templates done, continue with main logic */
   1061   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1062               "Templates loaded, starting to serve requests\n");
   1063   templates_ready ();
   1064 }
   1065 
   1066 
   1067 /**
   1068  * Callback for a GET /private/templates request.
   1069  *
   1070  * @param cls unused
   1071  * @param tgr response details
   1072  */
   1073 static void
   1074 check_templates (
   1075   void *cls,
   1076   const struct TALER_MERCHANT_GetPrivateTemplatesResponse *tgr)
   1077 {
   1078   (void) cls;
   1079   gpt = NULL;
   1080   switch (tgr->hr.http_status)
   1081   {
   1082   case MHD_HTTP_OK:
   1083     break;
   1084   case MHD_HTTP_UNAUTHORIZED:
   1085     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1086                 "Access to templates unauthorized: %s\n",
   1087                 TALER_ErrorCode_get_hint (tgr->hr.ec));
   1088     PH_global_ret = EXIT_FAILURE;
   1089     GNUNET_SCHEDULER_shutdown ();
   1090     return;
   1091   default:
   1092     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1093                 "Unexpected HTTP status code %u on GET /private/templates (%d)\n",
   1094                 tgr->hr.http_status,
   1095                 (int) tgr->hr.ec);
   1096     PH_global_ret = EXIT_FAILURE;
   1097     GNUNET_SCHEDULER_shutdown ();
   1098     return;
   1099   }
   1100   if (0 == tgr->details.ok.templates_length)
   1101   {
   1102     templates_ready ();
   1103     return;
   1104   }
   1105 
   1106   for (unsigned int i = 0; i<tgr->details.ok.templates_length; i++)
   1107   {
   1108     const struct TALER_MERCHANT_GetPrivateTemplatesTemplateEntry *te
   1109       = &tgr->details.ok.templates[i];
   1110     struct Template *t;
   1111     struct Template *before;
   1112 
   1113     t = GNUNET_new (struct Template);
   1114     t->template_id = GNUNET_strdup (te->template_id);
   1115     t->max_pickup_delay = GNUNET_TIME_UNIT_FOREVER_REL;
   1116     t->gt = TALER_MERCHANT_get_private_template_create (PH_merchant_ctx,
   1117                                                         PH_merchant_base_url,
   1118                                                         t->template_id);
   1119     /* Insert sorted by template ID.  Where two expressions both match a
   1120        URL, PAIVANA_HTTPD_search_templates() quotes the price of
   1121        whichever template it reaches first, so this list's order is a
   1122        pricing decision.  The backend's array arrives in whatever order
   1123        the database returned it -- the SELECT behind
   1124        GET /private/templates has no ORDER BY -- so taking it as given
   1125        (in either direction) makes that decision depend on nothing an
   1126        operator can see or set, and it can differ between two restarts
   1127        with no configuration change.  Sorting by ID is an arbitrary
   1128        rule, but it is a rule, and it is one an operator can act on. */
   1129     before = t_head;
   1130     while ( (NULL != before) &&
   1131             (0 > strcmp (before->template_id,
   1132                          t->template_id)) )
   1133       before = before->next;
   1134     GNUNET_CONTAINER_DLL_insert_before (t_head,
   1135                                         t_tail,
   1136                                         before,
   1137                                         t);
   1138     GNUNET_assert (
   1139       TALER_EC_NONE ==
   1140       TALER_MERCHANT_get_private_template_start (t->gt,
   1141                                                  &setup_template,
   1142                                                  t));
   1143   }
   1144 }
   1145 
   1146 
   1147 void
   1148 PAIVANA_HTTPD_load_templates ()
   1149 {
   1150   GNUNET_assert (NULL == load_timeout_task);
   1151   load_timeout_task
   1152     = GNUNET_SCHEDULER_add_delayed (TEMPLATE_LOAD_TIMEOUT,
   1153                                     &load_timeout,
   1154                                     NULL);
   1155   gpt = TALER_MERCHANT_get_private_templates_create (PH_merchant_ctx,
   1156                                                      PH_merchant_base_url);
   1157   GNUNET_assert (NULL != gpt);
   1158   GNUNET_assert (
   1159     TALER_EC_NONE ==
   1160     TALER_MERCHANT_get_private_templates_start (gpt,
   1161                                                 &check_templates,
   1162                                                 NULL));
   1163 }
   1164 
   1165 
   1166 enum GNUNET_GenericReturnValue
   1167 PAIVANA_HTTPD_search_templates (struct MHD_Connection *connection,
   1168                                 const char *website)
   1169 {
   1170   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1171               "Searching templates for `%s'\n",
   1172               website);
   1173   if (PH_MAX_URL_LENGTH < strlen (website))
   1174   {
   1175     enum MHD_Result ret;
   1176 
   1177     /* Refuse rather than returning #GNUNET_SYSERR: that would mean
   1178        "no paywall applies" and hand the request to the upstream for
   1179        free. */
   1180     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1181                 "Refusing to match templates against %llu byte URL\n",
   1182                 (unsigned long long) strlen (website));
   1183     ret = TALER_MHD_reply_with_error (
   1184       connection,
   1185       MHD_HTTP_URI_TOO_LONG,
   1186       TALER_EC_GENERIC_URI_TOO_LONG,
   1187       NULL);
   1188     return (MHD_YES == ret) ? GNUNET_OK : GNUNET_NO;
   1189   }
   1190   for (struct Template *t = t_head; NULL != t; t = t->next)
   1191   {
   1192     struct MHD_Response *redirect;
   1193     enum MHD_Result ret;
   1194     struct GNUNET_Buffer buf = { 0 };
   1195     char *enc = NULL;
   1196     char *url;
   1197 
   1198     if (NULL != t->regex)
   1199     {
   1200       int rc;
   1201 
   1202       rc = regexec (&t->ex,
   1203                     website,
   1204                     0, NULL,
   1205                     0);
   1206       if (REG_NOMATCH == rc)
   1207       {
   1208         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1209                     "Request for %s did not match template %s\n",
   1210                     website,
   1211                     t->template_id);
   1212         continue;
   1213       }
   1214       if (0 != rc)
   1215       {
   1216         char errbuf[128];
   1217 
   1218         /* Not "did not match": regexec(3) also reports REG_ESPACE,
   1219            whose likelihood is a function of the merchant's pattern and
   1220            the client's URL.  Taking the `continue' would drop this
   1221            template from consideration, and if it were the last one the
   1222            caller reads #GNUNET_SYSERR as "no paywall applies" and
   1223            serves the page for free.  Fail closed instead. */
   1224         GNUNET_break (0);
   1225         (void) regerror (rc,
   1226                          &t->ex,
   1227                          errbuf,
   1228                          sizeof (errbuf));
   1229         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1230                     "Failed to match template %s against %s: %s\n",
   1231                     t->template_id,
   1232                     website,
   1233                     errbuf);
   1234         ret = TALER_MHD_reply_with_error (
   1235           connection,
   1236           MHD_HTTP_INTERNAL_SERVER_ERROR,
   1237           TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1238           errbuf);
   1239         return (MHD_YES == ret) ? GNUNET_OK : GNUNET_NO;
   1240       }
   1241     }
   1242 
   1243     if (! PAIVANA_HTTPD_get_base_url (connection,
   1244                                       &buf))
   1245     {
   1246       GNUNET_break (0);
   1247       GNUNET_buffer_clear (&buf);
   1248       ret = TALER_MHD_reply_with_error (
   1249         connection,
   1250         MHD_HTTP_BAD_REQUEST,
   1251         TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
   1252         "Host or X-Forwarded-Host required");
   1253       return (MHD_YES == ret) ? GNUNET_OK : GNUNET_NO;
   1254     }
   1255     (void) GNUNET_STRINGS_base64url_encode (website,
   1256                                             strlen (website),
   1257                                             &enc);
   1258     GNUNET_buffer_write_str (&buf,
   1259                              "/.well-known/paivana/templates/");
   1260     GNUNET_buffer_write_str (&buf,
   1261                              t->template_id);
   1262     GNUNET_buffer_write_str (&buf,
   1263                              "#");
   1264     GNUNET_buffer_write_str (&buf,
   1265                              enc);
   1266     GNUNET_free (enc);
   1267     url = GNUNET_buffer_reap_str (&buf);
   1268     redirect = MHD_create_response_from_buffer_static (0,
   1269                                                        NULL);
   1270     GNUNET_assert (NULL != redirect);
   1271     GNUNET_break (MHD_YES ==
   1272                   MHD_add_response_header (redirect,
   1273                                            MHD_HTTP_HEADER_LOCATION,
   1274                                            url));
   1275     /* Both the Location and the fragment are built from the base URL,
   1276        which -- unless BASE_URL pins it -- comes out of the forwarding
   1277        headers.  A cache sitting between the terminating proxy and us
   1278        would otherwise serve one client's redirect to another virtual
   1279        host (RFC 9110 section 12.5.5). */
   1280     GNUNET_break (MHD_YES ==
   1281                   MHD_add_response_header (redirect,
   1282                                            MHD_HTTP_HEADER_VARY,
   1283                                            (NULL != PH_base_url)
   1284                                            ? "Cookie"
   1285                                            : "Cookie, "
   1286                                            MHD_HTTP_HEADER_FORWARDED ", "
   1287                                            PH_HEADER_X_FORWARDED_PROTO ", "
   1288                                            PH_HEADER_X_FORWARDED_HOST ", "
   1289                                            PH_HEADER_X_FORWARDED_PORT));
   1290     GNUNET_break (MHD_YES ==
   1291                   MHD_add_response_header (redirect,
   1292                                            MHD_HTTP_HEADER_CACHE_CONTROL,
   1293                                            "public, max-age=60"));
   1294     GNUNET_free (url);
   1295     ret = MHD_queue_response (connection,
   1296                               MHD_HTTP_FOUND,
   1297                               redirect);
   1298     MHD_destroy_response (redirect);
   1299     return (MHD_YES == ret) ? GNUNET_OK : GNUNET_NO;
   1300   }
   1301   return GNUNET_SYSERR;
   1302 }
   1303 
   1304 
   1305 /**
   1306  * Return the paywall page for the given @a template.
   1307  *
   1308  * @param connection request to search paywall response for
   1309  * @param template template to return paywall page for
   1310  * @return MHD status code
   1311  */
   1312 enum MHD_Result
   1313 PAIVANA_HTTPD_return_template (struct MHD_Connection *connection,
   1314                                const char *template)
   1315 {
   1316   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1317               "Searching template `%s'\n",
   1318               template);
   1319   for (struct Template *t = t_head; NULL != t; t = t->next)
   1320   {
   1321     if (0 == strcmp (template,
   1322                      t->template_id))
   1323       return load_paywall (connection,
   1324                            t);
   1325   }
   1326   /* No GNUNET_break_op() here: the ID is whatever the client put in the
   1327      path, this runs before any payment, and a stale bookmark or a
   1328      crawler would otherwise write an ERROR-level "Assertion failed" per
   1329      request.  A 404 is the whole answer. */
   1330   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1331               "No template `%s', returning 404\n",
   1332               template);
   1333   return TALER_MHD_reply_with_error (connection,
   1334                                      MHD_HTTP_NOT_FOUND,
   1335                                      TALER_EC_PAIVANA_TEMPLATE_UNKNOWN,
   1336                                      template);
   1337 }
   1338 
   1339 
   1340 /**
   1341  * Unload all of the template state.
   1342  */
   1343 void
   1344 PAIVANA_HTTPD_unload_templates ()
   1345 {
   1346   if (NULL != load_timeout_task)
   1347   {
   1348     GNUNET_SCHEDULER_cancel (load_timeout_task);
   1349     load_timeout_task = NULL;
   1350   }
   1351   while (NULL != t_head)
   1352   {
   1353     struct Template *t = t_head;
   1354 
   1355     while (NULL != t->rce_head)
   1356     {
   1357       struct ResponseCacheEntry *rce = t->rce_head;
   1358 
   1359       GNUNET_assert (t->rce_length > 0);
   1360       t->rce_length--;
   1361       GNUNET_CONTAINER_DLL_remove (t->rce_head,
   1362                                    t->rce_tail,
   1363                                    rce);
   1364       MHD_destroy_response (rce->paywall);
   1365       GNUNET_free (rce->lang);
   1366       GNUNET_free (rce);
   1367     }
   1368     GNUNET_CONTAINER_DLL_remove (t_head,
   1369                                  t_tail,
   1370                                  t);
   1371     if (NULL != t->gt)
   1372       TALER_MERCHANT_get_private_template_cancel (t->gt);
   1373     if (NULL != t->regex)
   1374     {
   1375       regfree (&t->ex);
   1376       GNUNET_free (t->regex);
   1377     }
   1378     GNUNET_free (t->template_id);
   1379     GNUNET_free (t->summary);
   1380     json_decref (t->choices);
   1381     GNUNET_free (t);
   1382   }
   1383   if (NULL != gpt)
   1384   {
   1385     TALER_MERCHANT_get_private_templates_cancel (gpt);
   1386     gpt = NULL;
   1387   }
   1388   GNUNET_free (paywall_csp);
   1389 }