paivana

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

paivana-httpd_daemon.c (26248B)


      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_daemon.c
     24  * @brief daemon functions
     25  */
     26 
     27 #include "platform.h"
     28 #include <curl/curl.h>
     29 #include <gnunet/gnunet_util_lib.h>
     30 #include <gnunet/gnunet_curl_lib.h>
     31 #include <taler/taler_mhd_lib.h>
     32 #include "paivana-httpd_cookie.h"
     33 #include "paivana-httpd_daemon.h"
     34 #include "paivana-httpd_helper.h"
     35 #include "paivana-httpd_pay.h"
     36 #include "paivana-httpd_reverse.h"
     37 #include "paivana-httpd_templates.h"
     38 
     39 
     40 struct RequestContext
     41 {
     42 
     43   /**
     44    * HTTP connection to the client.
     45    */
     46   struct MHD_Connection *connection;
     47 
     48   /**
     49    * Handle for request forwarding as reverse proxy.
     50    */
     51   struct HttpRequest *hr;
     52 
     53   /**
     54    * Handle for processing actual payment.
     55    */
     56   struct PayRequest *hp;
     57 
     58   /**
     59    * Full request URL.
     60    */
     61   char *url;
     62 
     63   /**
     64    * True if this is a POST to the .well-known/paivana endpoint.
     65    */
     66   bool is_paivana;
     67 
     68   /**
     69    * We are past the paywall, forward to client.
     70    */
     71   bool do_forward;
     72 
     73   /**
     74    * Admission class charged to this request.  Classification waits until the
     75    * access handler has both the decoded path and method; the URI callback
     76    * alone cannot distinguish a payment POST from a GET to the same path.
     77    */
     78   enum
     79   {
     80     AC_UNDECIDED,
     81     AC_ORDINARY,
     82     AC_PAYMENT,
     83     AC_REJECTED
     84   } admission_class;
     85 };
     86 
     87 
     88 /**
     89  * How many seconds MHD lets a client connection sit idle before it
     90  * closes it.
     91  *
     92  * This is *not* a per-request ceiling: MHD takes a suspended
     93  * connection off its timeout lists, and every request that waits for
     94  * the upstream or the merchant backend is suspended for exactly that
     95  * time.  What it bounds is the phases MHD itself owns -- reading the
     96  * request line and headers, reading an upload, writing the response --
     97  * which is what keeps a client that opens a connection and says
     98  * nothing from holding one of the #PH_connection_limit slots.  The
     99  * time a request may spend waiting is bounded by the libcurl timeouts
    100  * in paivana-httpd_reverse.c instead.
    101  */
    102 #define CLIENT_CONNECTION_TIMEOUT 16
    103 
    104 
    105 /**
    106  * Set to true if we started a daemon.
    107  */
    108 static bool have_daemons;
    109 
    110 /**
    111  * MHD daemons retained so shutdown can quiesce only their listen sockets
    112  * while the Taler scheduler adapter continues driving accepted requests.
    113  */
    114 static struct MHD_Daemon **mhd_daemons;
    115 
    116 /**
    117  * Length of #mhd_daemons.
    118  */
    119 static unsigned int mhd_daemons_length;
    120 
    121 /**
    122  * Number of requests whose URI callback has run and whose completion
    123  * callback has not.  This is the drain condition; half-written request lines
    124  * are connections rather than requests and are closed at final cleanup.
    125  */
    126 static unsigned int active_requests;
    127 
    128 /**
    129  * Ordinary requests admitted against the non-payment part of the connection
    130  * budget.
    131  */
    132 static unsigned int ordinary_requests;
    133 
    134 /**
    135  * Payment redemption requests admitted against the reserved part of the
    136  * connection budget.
    137  */
    138 static unsigned int payment_requests;
    139 
    140 /**
    141  * True after listeners have been quiesced for graceful shutdown.
    142  */
    143 static bool draining;
    144 
    145 
    146 /**
    147  * Queue a controlled-overload response and force the connection closed.
    148  *
    149  * Payment callers receive the machine-readable Taler error used for local
    150  * resource exhaustion.  Other requests receive a small static HTML body so
    151  * overload itself does not allocate or render a template.  `Retry-After: 1'
    152  * discourages a hot retry loop without claiming a longer outage.
    153  *
    154  * @param connection client connection
    155  * @param payment true for the payment endpoint
    156  * @return MHD result
    157  */
    158 static enum MHD_Result
    159 reply_overloaded (struct MHD_Connection *connection,
    160                   bool payment)
    161 {
    162   static const char body[] =
    163     "<!doctype html><title>Service unavailable</title>"
    164     "<p>Paivana is temporarily at capacity. Please retry.</p>";
    165   struct MHD_Response *response;
    166   enum MHD_Result ret;
    167 
    168   if (payment)
    169     response = TALER_MHD_make_error (
    170       TALER_EC_GENERIC_OS_RESOURCE_ALLOCATION_FAILURE,
    171       "Paivana payment-check capacity is exhausted");
    172   else
    173     response = MHD_create_response_from_buffer_static (sizeof (body) - 1,
    174                                                        body);
    175   if (NULL == response)
    176     return MHD_NO;
    177   if ( (! payment) &&
    178        (MHD_YES !=
    179         MHD_add_response_header (response,
    180                                  MHD_HTTP_HEADER_CONTENT_TYPE,
    181                                  "text/html; charset=utf-8")) )
    182     goto fail;
    183   if ( (MHD_YES !=
    184         MHD_add_response_header (response,
    185                                  MHD_HTTP_HEADER_CONNECTION,
    186                                  "close")) ||
    187        (MHD_YES !=
    188         MHD_add_response_header (response,
    189                                  MHD_HTTP_HEADER_RETRY_AFTER,
    190                                  "1")) )
    191     goto fail;
    192   ret = MHD_queue_response (connection,
    193                             MHD_HTTP_SERVICE_UNAVAILABLE,
    194                             response);
    195   MHD_destroy_response (response);
    196   return ret;
    197 fail:
    198   MHD_destroy_response (response);
    199   return MHD_NO;
    200 }
    201 
    202 
    203 /**
    204  * Decide whether a request may consume one of the process-wide slots.
    205  *
    206  * MHD's connection limit remains the hard transport ceiling.  This second
    207  * request-level gate stops ordinary requests at CONNECTION_LIMIT minus
    208  * PAYMENT_CONNECTION_LIMIT, leaving the remainder available for payment
    209  * POSTs.  It cannot reserve against clients that connect but never finish a
    210  * request line; CLIENT_CONNECTION_TIMEOUT bounds that separate slowloris
    211  * case.
    212  *
    213  * @param rc request context
    214  * @param payment whether this is a payment POST
    215  * @return true if admitted
    216  */
    217 static bool
    218 admit_request (struct RequestContext *rc,
    219                bool payment)
    220 {
    221   GNUNET_assert (AC_UNDECIDED == rc->admission_class);
    222   if (draining)
    223   {
    224     rc->admission_class = AC_REJECTED;
    225     return false;
    226   }
    227   if (payment)
    228   {
    229     if (payment_requests >= PH_payment_connection_limit)
    230     {
    231       rc->admission_class = AC_REJECTED;
    232       return false;
    233     }
    234     payment_requests++;
    235     rc->admission_class = AC_PAYMENT;
    236     return true;
    237   }
    238   if (ordinary_requests >=
    239       PH_connection_limit - PH_payment_connection_limit)
    240   {
    241     rc->admission_class = AC_REJECTED;
    242     return false;
    243   }
    244   ordinary_requests++;
    245   rc->admission_class = AC_ORDINARY;
    246   return true;
    247 }
    248 
    249 
    250 /**
    251  * Is the request target something we may decide about and then forward
    252  * unchanged?
    253  *
    254  * Two strings describe one request here, and they are not the same:
    255  * @a raw is the target exactly as it arrived on the request line --
    256  * that is what we hand to libcurl -- while @a url is MHD's
    257  * percent-decoded, query-stripped path, and that is what the WHITELIST
    258  * expression and the templates' expressions are matched against.
    259  * Neither is normalized, but libcurl normalizes the URL we give it
    260  * (CURLOPT_PATH_AS_IS is not set), so "/assets/../premium/x" matches a
    261  * WHITELIST of "/assets/.*" and is then fetched as "/premium/x".
    262  * Rather than decide about one resource and fetch another, refuse
    263  * anything not already in normal form; browsers normalize before
    264  * sending, so only hand-built requests are affected.
    265  *
    266  * Non-origin-form targets are refused for a second reason: such a
    267  * target -- an absolute-form one, or merely one starting with "@" --
    268  * is concatenated onto DESTINATION_BASE_URL, where it can re-parse as
    269  * userinfo (RFC 3986 section 3.2.1) and let the client choose the host
    270  * we connect to.
    271  *
    272  * @param raw request target as it arrived on the request line
    273  * @param url percent-decoded path MHD gives the handler
    274  * @return true if the request may proceed
    275  */
    276 static bool
    277 canonical_request_target (const char *raw,
    278                           const char *url)
    279 {
    280   const char *p;
    281 
    282   if ( ('/' != raw[0]) ||
    283        ('/' != url[0]) )
    284     return false; /* not origin-form; RFC 9112 section 3.2.1 */
    285   /* Reject ".", ".." and empty segments: RFC 3986 section 5.2.4's
    286      remove_dot_segments is exactly what libcurl would apply behind our
    287      back, after we have already decided. */
    288   for (p = url; NULL != p; p = strchr (p + 1, '/'))
    289   {
    290     const char *seg = p + 1;
    291     const char *end = strchr (seg, '/');
    292     size_t len = (NULL == end) ? strlen (seg) : (size_t) (end - seg);
    293 
    294     if (0 == len)
    295       return ('\0' == *seg); /* a trailing '/' is fine, "//" is not */
    296     if ( ( (1 == len) &&
    297            ('.' == seg[0]) ) ||
    298          ( (2 == len) &&
    299            ('.' == seg[0]) &&
    300            ('.' == seg[1]) ) )
    301       return false;
    302   }
    303   return true;
    304 }
    305 
    306 
    307 /**
    308  * Main MHD callback for handling requests.
    309  *
    310  * @param cls unused
    311  * @param con MHD connection handle
    312  * @param url the url in the request
    313  * @param meth the HTTP method used ("GET", "PUT", etc.)
    314  * @param ver the HTTP version string (i.e. "HTTP/1.1")
    315  * @param upload_data the data being uploaded (excluding HEADERS,
    316  *        for a POST that fits into memory and that is encoded
    317  *        with a supported encoding, the POST data will NOT be
    318  *        given in upload_data and is instead available as
    319  *        part of MHD_get_connection_values; very large POST
    320  *        data *will* be made available incrementally in
    321  *        upload_data)
    322  * @param upload_data_size set initially to the size of the
    323  *        @a upload_data provided; the method must update this
    324  *        value to the number of bytes NOT processed;
    325  * @param con_cls pointer to the `struct RequestContext` that
    326  *        mhd_log_callback() made for this request
    327  * @return #MHD_YES if the connection was handled successfully,
    328  *         #MHD_NO if the socket must be closed due to a serious
    329  *         error while handling the request
    330  */
    331 static enum MHD_Result
    332 create_response (void *cls,
    333                  struct MHD_Connection *con,
    334                  const char *url,
    335                  const char *meth,
    336                  const char *ver,
    337                  const char *upload_data,
    338                  size_t *upload_data_size,
    339                  void **con_cls)
    340 {
    341   struct RequestContext *rc = *con_cls;
    342   const char *cookie;
    343   bool ok = false;
    344   struct GNUNET_Buffer buf;
    345   char *website;
    346   const bool payment
    347     = ( (0 == strcmp (url,
    348                       "/.well-known/paivana")) &&
    349         (0 == strcasecmp (meth,
    350                           MHD_HTTP_METHOD_POST)) );
    351 
    352   (void) cls;
    353   memset (&buf,
    354           0,
    355           sizeof (buf));
    356   if (AC_UNDECIDED == rc->admission_class)
    357     (void) admit_request (rc,
    358                           payment);
    359   if (AC_REJECTED == rc->admission_class)
    360     return reply_overloaded (con,
    361                              payment);
    362   if (! canonical_request_target (rc->url,
    363                                   url))
    364   {
    365     GNUNET_break_op (0);
    366     return TALER_MHD_reply_with_error (
    367       rc->connection,
    368       MHD_HTTP_BAD_REQUEST,
    369       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    370       "request-target must be in origin-form and free of '.', '..'"
    371       " and empty path segments");
    372   }
    373   if ( (! rc->is_paivana) &&
    374        payment )
    375   {
    376     rc->is_paivana = true;
    377   }
    378   if (rc->is_paivana)
    379   {
    380     if (PH_no_check)
    381     {
    382       /* paywall disabled, 501 */
    383       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    384                   "Paywall disabled, refusing to respond to %s\n",
    385                   url);
    386       return TALER_MHD_reply_with_error (rc->connection,
    387                                          MHD_HTTP_NOT_IMPLEMENTED,
    388                                          TALER_EC_PAIVANA_PAYWALL_DISABLED,
    389                                          NULL);
    390     }
    391     if (NULL == rc->hp)
    392     {
    393       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    394                   "Client POSTed payment, checking validity\n");
    395       rc->hp = PAIVANA_HTTPD_payment_create (rc->connection);
    396     }
    397     return PAIVANA_HTTPD_payment_handle (rc->hp,
    398                                          upload_data,
    399                                          upload_data_size);
    400   }
    401 
    402   if ( (! rc->do_forward) &&
    403        (PH_MAX_URL_LENGTH < strlen (url)) )
    404   {
    405     /* Refuse before either regexec() -- the whitelist just below and
    406        the templates' expressions further down both run on the
    407        pre-payment path with a client-controlled subject.  Refusing
    408        outright (rather than skipping the whitelist) also keeps this
    409        from being read as "not whitelisted, so charge for it": there
    410        is nothing to charge for at this length.  Requests already
    411        destined to be forwarded reach no expression at all, and are
    412        left alone -- notably every request under -n, where paivana is
    413        a plain reverse proxy. */
    414     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    415                 "Refusing %llu byte URL\n",
    416                 (unsigned long long) strlen (url));
    417     return TALER_MHD_reply_with_error (rc->connection,
    418                                        MHD_HTTP_URI_TOO_LONG,
    419                                        TALER_EC_GENERIC_URI_TOO_LONG,
    420                                        NULL);
    421   }
    422 
    423   if (PH_have_whitelist_ex && (! rc->do_forward))
    424   {
    425     rc->do_forward = (0 ==
    426                       regexec (&PH_whitelist_ex,
    427                                url,
    428                                0,
    429                                NULL,
    430                                0));
    431   }
    432 
    433   if (rc->do_forward)
    434     goto do_forward;
    435 
    436   if ( (0 == strncmp (url,
    437                       "/.well-known/paivana/templates/",
    438                       strlen ("/.well-known/paivana/templates/"))) &&
    439        ( (0 == strcasecmp (meth,
    440                            MHD_HTTP_METHOD_GET)) ||
    441          /* HEAD must be answered like GET (MHD suppresses the body);
    442             otherwise it falls through to the paywall check below,
    443             which redirects to this very URL again -- forever. */
    444          (0 == strcasecmp (meth,
    445                            MHD_HTTP_METHOD_HEAD)) ) )
    446   {
    447     const char *id = &url[strlen ("/.well-known/paivana/templates/")];
    448 
    449     return PAIVANA_HTTPD_return_template (rc->connection,
    450                                           id);
    451   }
    452 
    453   if (! PAIVANA_HTTPD_get_base_url (con,
    454                                     &buf))
    455   {
    456     GNUNET_break (0);
    457     GNUNET_buffer_clear (&buf);
    458     return TALER_MHD_reply_with_error (
    459       con,
    460       MHD_HTTP_BAD_REQUEST,
    461       TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
    462       "Host or X-Forwarded-Host required");
    463   }
    464   GNUNET_buffer_write_str (&buf,
    465                            url);
    466   website = GNUNET_buffer_reap_str (&buf);
    467   cookie = MHD_lookup_connection_value (con,
    468                                         MHD_COOKIE_KIND,
    469                                         PAIVANA_COOKIE_NAME);
    470   if (NULL != cookie)
    471   {
    472     void *ca = NULL;
    473     size_t ca_len = 0;
    474 
    475     /* If we cannot get the client address, we just
    476        use 0/NULL and log an error. */
    477     GNUNET_break (PAIVANA_HTTPD_get_client_address (con,
    478                                                     &ca,
    479                                                     &ca_len));
    480     ok = PAIVANA_HTTPD_check_cookie (cookie,
    481                                      website,
    482                                      ca_len,
    483                                      ca);
    484     /* The cookie value itself is deliberately not logged: it is the
    485        bearer credential the client paid for, it outlives its own
    486        Max-Age in the log file, and anyone sharing the client's address
    487        -- everyone behind one NAT -- could replay it. */
    488     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    489                 "Client sent an access cookie for %s: %s\n",
    490                 website,
    491                 ok ? "good" : "invalid");
    492     GNUNET_free (ca);
    493   }
    494   if (! ok)
    495   {
    496     enum GNUNET_GenericReturnValue ret;
    497 
    498     ret = PAIVANA_HTTPD_search_templates (con,
    499                                           website);
    500     if (GNUNET_SYSERR != ret)
    501     {
    502       GNUNET_free (website);
    503       /* #GNUNET_OK only says a response was queued; besides the
    504          redirect to the paywall that may be a 414 or a 400. */
    505       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    506                   "A paywall applies, response queued: %s\n",
    507                   (GNUNET_OK == ret) ? "ok" : "failed");
    508       return (GNUNET_OK == ret) ? MHD_YES : MHD_NO;
    509     }
    510   }
    511   GNUNET_free (website);
    512   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    513               "Request OK, no paywall applies!\n");
    514   rc->do_forward = true;
    515 do_forward:
    516   if (NULL == rc->hr)
    517     rc->hr = PAIVANA_HTTPD_reverse_create (rc->connection,
    518                                            rc->url);
    519   return PAIVANA_HTTPD_reverse (rc->hr,
    520                                 con,
    521                                 url,
    522                                 meth,
    523                                 ver,
    524                                 upload_data,
    525                                 upload_data_size);
    526 }
    527 
    528 
    529 /**
    530  * Function called when MHD decides that we
    531  * are done with a request.
    532  *
    533  * @param cls NULL
    534  * @param connection connection handle
    535  * @param con_cls value as set by the last call to
    536  *        the MHD_AccessHandlerCallback, should be
    537  *        our `struct RequestContext *` (created in `mhd_log_callback()`)
    538  * @param toe reason for request termination (ignored)
    539  */
    540 static void
    541 mhd_completed_cb (void *cls,
    542                   struct MHD_Connection *connection,
    543                   void **con_cls,
    544                   enum MHD_RequestTerminationCode toe)
    545 {
    546   struct RequestContext *rc = *con_cls;
    547 
    548   (void) cls;
    549   (void) connection;
    550   if (NULL == rc)
    551     return;
    552   if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe)
    553     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    554                 "MHD encountered error handling request to %s: %d\n",
    555                 rc->url,
    556                 toe);
    557   if (NULL != rc->hr)
    558     PAIVANA_HTTPD_reverse_cleanup (rc->hr);
    559   if (NULL != rc->hp)
    560     PAIVANA_HTTPD_payment_destroy (rc->hp);
    561   switch (rc->admission_class)
    562   {
    563   case AC_ORDINARY:
    564     GNUNET_assert (ordinary_requests > 0);
    565     ordinary_requests--;
    566     break;
    567   case AC_PAYMENT:
    568     GNUNET_assert (payment_requests > 0);
    569     payment_requests--;
    570     break;
    571   case AC_UNDECIDED:
    572   case AC_REJECTED:
    573     break;
    574   }
    575   GNUNET_assert (active_requests > 0);
    576   active_requests--;
    577   GNUNET_free (rc->url);
    578   GNUNET_free (rc);
    579   *con_cls = NULL;
    580 }
    581 
    582 
    583 /**
    584  * Function called when MHD first processes an incoming connection.
    585  * Gives us the respective URI information.
    586  *
    587  * We use this to associate the `struct MHD_Connection` with our
    588  * internal `struct HttpRequest` data structure (by checking
    589  * for matching sockets).
    590  *
    591  * @param cls unused
    592  * @param url the request target exactly as it arrived on the request
    593  *        line: RAW, i.e. neither percent-decoded nor stripped of its
    594  *        query, unlike the @a url create_response() is given.  That is
    595  *        the string we forward, and the difference between the two is
    596  *        why canonical_request_target() has to see both.
    597  * @param connection MHD connection object for the request
    598  * @return the `struct RequestContext` that this @a connection is for
    599  */
    600 static void *
    601 mhd_log_callback (void *cls,
    602                   const char *url,
    603                   struct MHD_Connection *connection)
    604 {
    605   struct RequestContext *rc;
    606 
    607   (void) cls;
    608   rc = GNUNET_new (struct RequestContext);
    609   active_requests++;
    610   rc->connection = connection;
    611   rc->url = GNUNET_strdup (url);
    612   rc->do_forward = (1 == PH_no_check);
    613   return rc;
    614 }
    615 
    616 
    617 /**
    618  * Listen sockets handed to us by TALER_MHD_listen_bind(), collected
    619  * before any daemon is started; see #collect_socket().
    620  */
    621 static int *lsocks;
    622 
    623 /**
    624  * Length of #lsocks.
    625  */
    626 static unsigned int lsocks_length;
    627 
    628 
    629 /**
    630  * Callback invoked on every listen socket: remember it.
    631  *
    632  * The daemons are not started here because
    633  * MHD_OPTION_CONNECTION_LIMIT is per daemon and cannot be changed
    634  * afterwards, while the budget it has to divide is process-wide --
    635  * TALER_MHD_listen_bind() starts one daemon per getaddrinfo() result,
    636  * so an empty BIND_TO yields two, and giving each the whole budget
    637  * would double it.  We cannot know the divisor until the last socket
    638  * has been handed over.
    639  *
    640  * @param cls unused
    641  * @param lsock the listen socket
    642  */
    643 static void
    644 collect_socket (void *cls,
    645                 int lsock)
    646 {
    647   (void) cls;
    648   GNUNET_assert (-1 != lsock);
    649   GNUNET_array_append (lsocks,
    650                        lsocks_length,
    651                        lsock);
    652 }
    653 
    654 
    655 /**
    656  * Start one MHD daemon on @a lsock, entitled to @a climit concurrent
    657  * connections.
    658  *
    659  * @param lsock the listen socket, ownership passes to MHD on success
    660  * @param climit connection limit for this daemon
    661  */
    662 static void
    663 start_daemon (int lsock,
    664               unsigned int climit)
    665 {
    666   struct MHD_Daemon *mhd;
    667 
    668   mhd = MHD_start_daemon (
    669     MHD_USE_DEBUG
    670     | MHD_ALLOW_SUSPEND_RESUME,
    671     0,
    672     NULL, NULL,
    673     &create_response, NULL,
    674     /* First, and MHD says so itself: it logs about the options it is
    675        still parsing, and anything before this one would be printed by
    676        its built-in logger.  Without the option at all -- MHD_USE_DEBUG
    677        above is the same bit as MHD_USE_ERROR_LOG -- MHD fprintf()s to
    678        stderr for the lifetime of the daemon, outside GNUnet's level
    679        and logfile handling entirely.  Malformed request lines are
    680        remotely triggerable, so that is client-influenced output
    681        arriving on stderr regardless of -L ERROR. */
    682     MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs, NULL,
    683     MHD_OPTION_LISTEN_SOCKET,
    684     lsock,
    685     MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) CLIENT_CONNECTION_TIMEOUT,
    686     MHD_OPTION_CONNECTION_LIMIT, climit,
    687     MHD_OPTION_PER_IP_CONNECTION_LIMIT, PH_per_ip_connection_limit,
    688     MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL,
    689     MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL,
    690     MHD_OPTION_END);
    691 
    692   if (NULL == mhd)
    693   {
    694     /* Not fatal on its own: TALER_MHD_listen_bind() starts one daemon
    695        per getaddrinfo() result, so an empty BIND_TO yields a v4 and a
    696        v6 socket, and losing the whole process because MHD would not
    697        take the v6 one is the same over-reaction the GNUNET_NO arm in
    698        PAIVANA_HTTPD_serve_requests() already refuses to make for a
    699        socket that would not bind.  Serving on nothing at all is still
    700        fatal; that is decided once, by the caller. */
    701     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    702                 "Failed to start HTTP daemon on one of the listen sockets\n");
    703     /* MHD did not take ownership of the socket; close it ourselves. */
    704     GNUNET_break (0 == close (lsock));
    705     return;
    706   }
    707   have_daemons = true;
    708   GNUNET_array_append (mhd_daemons,
    709                        mhd_daemons_length,
    710                        mhd);
    711   TALER_MHD_daemon_start (mhd);
    712 }
    713 
    714 
    715 unsigned int
    716 PAIVANA_HTTPD_begin_drain (void)
    717 {
    718   if (! draining)
    719   {
    720     draining = true;
    721     for (unsigned int i = 0; i < mhd_daemons_length; i++)
    722     {
    723       MHD_socket fd;
    724 
    725       fd = MHD_quiesce_daemon (mhd_daemons[i]);
    726       if (MHD_INVALID_SOCKET == fd)
    727       {
    728         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    729                     "HTTP listen socket was already quiesced\n");
    730         continue;
    731       }
    732       /* TALER_MHD_daemon_start() has a GNUnet select task whose fdset was
    733          built before quiescing and therefore still contains @a fd.  Triggering
    734          cancels that pending task and schedules a fresh MHD_run(), which will
    735          rebuild the set without the listener.  Closing first leaves select()
    736          holding an invalid descriptor and makes the scheduler abort with
    737          EBADF.  The trigger task cannot run until this callback returns, so
    738          the returned socket is no longer referenced when close() follows. */
    739       TALER_MHD_daemon_trigger ();
    740       GNUNET_break (0 == close (fd));
    741     }
    742   }
    743   return active_requests;
    744 }
    745 
    746 
    747 unsigned int
    748 PAIVANA_HTTPD_active_requests (void)
    749 {
    750   return active_requests;
    751 }
    752 
    753 
    754 void
    755 PAIVANA_HTTPD_daemons_destroy (void)
    756 {
    757   TALER_MHD_daemons_halt ();
    758   TALER_MHD_daemons_destroy ();
    759   GNUNET_array_grow (mhd_daemons,
    760                      mhd_daemons_length,
    761                      0);
    762   have_daemons = false;
    763 }
    764 
    765 
    766 void
    767 PAIVANA_HTTPD_serve_requests ()
    768 {
    769   enum GNUNET_GenericReturnValue ret;
    770 
    771   ret = TALER_MHD_listen_bind (PH_cfg,
    772                                "paivana",
    773                                &collect_socket,
    774                                NULL);
    775   if (0 != lsocks_length)
    776   {
    777     unsigned int climit
    778       = GNUNET_MAX (1,
    779                     PH_connection_limit / lsocks_length);
    780 
    781     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    782                 "Serving on %u listen socket(s), %u connection(s) each"
    783                 " (%u per client address, 0 = unlimited)\n",
    784                 lsocks_length,
    785                 climit,
    786                 PH_per_ip_connection_limit);
    787     for (unsigned int i = 0; i < lsocks_length; i++)
    788       start_daemon (lsocks[i],
    789                     climit);
    790     GNUNET_array_grow (lsocks,
    791                        lsocks_length,
    792                        0);
    793   }
    794   switch (ret)
    795   {
    796   case GNUNET_SYSERR:
    797     /* Configuration error; TALER_MHD_listen_bind() has diagnosed it. */
    798     PH_global_ret = EXIT_NOTCONFIGURED;
    799     GNUNET_SCHEDULER_shutdown ();
    800     return;
    801   case GNUNET_NO:
    802     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    803                 "Could not open all configured listen sockets\n");
    804     break;
    805   case GNUNET_OK:
    806     break;
    807   }
    808   if (! have_daemons)
    809   {
    810     /* One policy for both ways a listener can fail to come up: a
    811        socket that would not bind, and a socket MHD would not serve
    812        on.  Some is enough; none is not. */
    813     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    814                 "Not a single listen socket came up, refusing to run\n");
    815     PH_global_ret = EXIT_NOTCONFIGURED;
    816     GNUNET_SCHEDULER_shutdown ();
    817     return;
    818   }
    819 }