paivana

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

paivana-httpd_pay.c (50030B)


      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_pay.c
     24  * @brief payment processing logic
     25  */
     26 #include "platform.h"
     27 #include <curl/curl.h>
     28 #include <microhttpd.h>
     29 #include <gnunet/gnunet_curl_lib.h>
     30 #include <gnunet/gnunet_util_lib.h>
     31 #include <taler/taler_mhd_lib.h>
     32 #include <taler/taler_json_lib.h>
     33 #include <taler/taler_error_codes.h>
     34 #include "paivana-httpd_cookie.h"
     35 #include "paivana-httpd_helper.h"
     36 #include "paivana-httpd_pay.h"
     37 
     38 struct PayRequest;
     39 #define TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE struct PayRequest
     40 #include "taler/merchant/get-private-orders-ORDER_ID.h"
     41 
     42 /**
     43  * How long we give the merchant backend to answer the
     44  * `GET /private/orders/$ORDER_ID' behind one client's redemption.
     45  *
     46  * A bound is needed at all because the endpoint is unauthenticated:
     47  * every syntactically valid POST suspends an MHD connection and issues
     48  * a backend query before any payment has been shown to exist, so
     49  * without one, a wedged backend pins a suspended connection per
     50  * request forever.  MHD_OPTION_CONNECTION_TIMEOUT does not apply to
     51  * suspended connections, so the deadline has to come from us.
     52  *
     53  * It is passed as TALER_MERCHANT_get_private_order_option_timeout(),
     54  * which does two things at once: it caps the request client-side
     55  * (CURLOPT_TIMEOUT_MS, so an unreachable or hung backend is bounded
     56  * here too) and it sets `timeout_ms' on the URL, asking the backend to
     57  * long-poll for that long before reporting an order as unpaid.
     58  *
     59  * The long poll is wanted, not merely tolerated.  By the time the
     60  * client posts here it has already seen the backend confirm the
     61  * payment, so an order that still reads "unpaid" means either a client
     62  * that is lying -- replaying an order ID it never paid -- or one that
     63  * raced a state change that is about to land.  Waiting a few seconds
     64  * settles the race in the honest client's favour, and the dishonest
     65  * one pays for it with a bounded wait and then a 409.  Keep it short:
     66  * this is the interval an attacker can pin a connection for.
     67  */
     68 #define MERCHANT_ORDER_TIMEOUT \
     69         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
     70 
     71 /**
     72  * Do not start a retry when too little of #MERCHANT_ORDER_TIMEOUT remains to
     73  * make another connection useful.
     74  */
     75 #define MERCHANT_RETRY_MIN_BUDGET \
     76         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 250)
     77 
     78 
     79 /**
     80  * Handle for processing actual payment.
     81  */
     82 struct PayRequest
     83 {
     84 
     85   /**
     86    * Kept in a DLL while suspended.
     87    */
     88   struct PayRequest *next;
     89 
     90   /**
     91    * Kept in a DLL while suspended.
     92    */
     93   struct PayRequest *prev;
     94 
     95   /**
     96    * Connection we are handling.
     97    */
     98   struct MHD_Connection *connection;
     99 
    100   /**
    101    * Buffer for TALER_MHD_parse_post_json().
    102    */
    103   void *buffer;
    104 
    105   /**
    106    * Uploaded JSON body, NULL if none yet.
    107    */
    108   json_t *body;
    109 
    110   /**
    111    * Handle for our request to the merchant backend. This
    112    * struct is in the #ph_head DLL as long as @e co is non-NULL.
    113    */
    114   struct TALER_MERCHANT_GetPrivateOrderHandle *co;
    115 
    116   /**
    117    * When @e co was started.  The merchant client API reports every
    118    * transport failure as HTTP status zero, without the CURLcode that
    119    * would distinguish a timeout from (for example) a refused stale
    120    * pooled connection.  The elapsed time still lets us distinguish a
    121    * request that reached our deadline from one that failed before it.
    122    */
    123   struct GNUNET_TIME_Absolute merchant_request_started;
    124 
    125   /**
    126    * Number of merchant lookup attempts started for this redemption.  A GET
    127    * that fails before receiving any HTTP response is retried once: the
    128    * merchant connection pool can otherwise turn one stale idle connection
    129    * into a user-visible payment failure.
    130    */
    131   unsigned int merchant_lookup_attempts;
    132 
    133   /**
    134    * Response to return, NULL if not yet determined.
    135    */
    136   struct MHD_Response *response;
    137 
    138   /**
    139    * ID of the order the client claims to have paid. Aliased
    140    * from @e body.
    141    */
    142   const char *order_id;
    143 
    144   /**
    145    * Website the order is supposed to have paid for. Aliased
    146    * from @e body.
    147    */
    148   const char *website;
    149 
    150   /**
    151    * Client-side nonce.
    152    */
    153   struct PAIVANA_Nonce nonce;
    154 
    155   /**
    156    * End of the access the client is redeeming: the expiration of the
    157    * cookie we mint, and one of the three inputs the client hashed into
    158    * the paivana_id the order was created under.  Chosen by the client
    159    * and bounded above by the contract's `max_pickup_time'.
    160    */
    161   struct GNUNET_TIME_Timestamp expiration;
    162 
    163   /**
    164    * HTTP status to return in combination with @e response to the
    165    * client.
    166    */
    167   unsigned int response_status;
    168 
    169 };
    170 
    171 
    172 /**
    173  * Head of DLL of suspended requests.
    174  */
    175 static struct PayRequest *ph_head;
    176 
    177 /**
    178  * Tail of DLL of suspended requests.
    179  */
    180 static struct PayRequest *ph_tail;
    181 
    182 /**
    183  * Number of consecutive merchant order lookups that ended without an
    184  * HTTP response.  This is diagnostic state only: a real response of
    185  * any status resets it.  In particular, a growing number here makes a
    186  * persistent resolver/connection-pool/backend problem visible in the
    187  * log instead of making each redemption look like an isolated timeout.
    188  */
    189 static unsigned int merchant_transport_failures;
    190 
    191 /**
    192  * Number of merchant order requests currently in #ph_head.  Maintaining the
    193  * counter with the DLL makes diagnostics O(1) during a mass outage instead of
    194  * walking every suspended request from every completion callback.
    195  */
    196 static unsigned int active_merchant_lookups;
    197 
    198 /**
    199  * One forced-fresh request started after a sampled early transport failure.
    200  * It is diagnostic only: the client's response is still determined by the
    201  * two regular merchant API attempts.  Keeping these in a DLL lets shutdown
    202  * cancel both a not-yet-started scheduler task and a live curl job safely.
    203  */
    204 struct MerchantTransportDiagnostic
    205 {
    206   struct MerchantTransportDiagnostic *next;
    207   struct MerchantTransportDiagnostic *prev;
    208   struct GNUNET_SCHEDULER_Task *task;
    209   struct GNUNET_CURL_Job *job;
    210   CURL *easy;
    211   char *url;
    212   char *order_id;
    213   char error[CURL_ERROR_SIZE];
    214 };
    215 
    216 static struct MerchantTransportDiagnostic *diagnostic_head;
    217 static struct MerchantTransportDiagnostic *diagnostic_tail;
    218 
    219 /**
    220  * Merchant transport diagnostics are useful immediately and then at most once
    221  * per minute per failure class.  A minute is short enough for an operator to
    222  * see a persistent outage in routine monitoring, while reducing 32 concurrent
    223  * five-second failures from hundreds of warnings per minute to two.
    224  */
    225 #define MERCHANT_FAILURE_LOG_INTERVAL \
    226         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 1)
    227 
    228 /**
    229  * Transport failures that need independently sampled explanations.
    230  */
    231 enum MerchantFailureClass
    232 {
    233   MFC_UNUSABLE_REPLY,
    234   MFC_TIMEOUT,
    235   MFC_EARLY_TRANSPORT,
    236   MFC_COUNT
    237 };
    238 
    239 /**
    240  * Sampling state for one #MerchantFailureClass.
    241  */
    242 struct MerchantFailureLogState
    243 {
    244   /** Next time a warning may be emitted. */
    245   struct GNUNET_TIME_Absolute next_log;
    246 
    247   /** Failures omitted since the previous emitted warning. */
    248   unsigned int suppressed;
    249 
    250   /** Whether this class has emitted its first warning. */
    251   bool logged;
    252 };
    253 
    254 /**
    255  * Per-class warning sampling state.
    256  */
    257 static struct MerchantFailureLogState failure_logs[MFC_COUNT];
    258 
    259 
    260 /**
    261  * Discard a diagnostic response body without retaining contract data.
    262  */
    263 static size_t
    264 discard_diagnostic_body (void *cls,
    265                          const void *data,
    266                          size_t data_size)
    267 {
    268   (void) cls;
    269   (void) data;
    270   return data_size;
    271 }
    272 
    273 
    274 /**
    275  * Make curl's fixed-size error buffer safe to embed in one log record.
    276  */
    277 static void
    278 sanitize_diagnostic_error (char *error)
    279 {
    280   for (char *p = error; '\0' != *p; p++)
    281     if (iscntrl ((unsigned char) *p) ||
    282         ('"' == *p) ||
    283         ('\\' == *p))
    284       *p = ' ';
    285 }
    286 
    287 
    288 /**
    289  * Release a completed diagnostic after GNUnet has removed and cleaned up its
    290  * easy handle.  In particular, CURLOPT_ERRORBUFFER requires @e md->error to
    291  * remain alive until that cleanup is over, which happens after the completion
    292  * callback returns.
    293  */
    294 static void
    295 free_completed_diagnostic (void *cls)
    296 {
    297   struct MerchantTransportDiagnostic *md = cls;
    298 
    299   md->task = NULL;
    300   GNUNET_CONTAINER_DLL_remove (diagnostic_head,
    301                                diagnostic_tail,
    302                                md);
    303   GNUNET_free (md->url);
    304   GNUNET_free (md->order_id);
    305   GNUNET_free (md);
    306 }
    307 
    308 
    309 /**
    310  * Report the result while the easy handle still exists, then release our
    311  * closure on the next scheduler turn.  GNUnet removes and destroys the curl
    312  * job after this callback returns.
    313  */
    314 static void
    315 fresh_diagnostic_finished (void *cls,
    316                            long completed_http_status,
    317                            const void *body,
    318                            size_t body_size)
    319 {
    320   struct MerchantTransportDiagnostic *md = cls;
    321   long observed_http_status = 0;
    322   long new_connections = -1;
    323   long os_errno = 0;
    324   long http_version = 0;
    325   const char *remote_ip = NULL;
    326   const char *local_ip = NULL;
    327   double dns_s = 0;
    328   double tcp_s = 0;
    329   double tls_s = 0;
    330   double first_byte_s = 0;
    331   double total_s = 0;
    332 
    333   (void) body;
    334   (void) body_size;
    335   md->job = NULL;
    336   (void) curl_easy_getinfo (md->easy,
    337                             CURLINFO_RESPONSE_CODE,
    338                             &observed_http_status);
    339   (void) curl_easy_getinfo (md->easy,
    340                             CURLINFO_NUM_CONNECTS,
    341                             &new_connections);
    342   (void) curl_easy_getinfo (md->easy,
    343                             CURLINFO_OS_ERRNO,
    344                             &os_errno);
    345   (void) curl_easy_getinfo (md->easy,
    346                             CURLINFO_HTTP_VERSION,
    347                             &http_version);
    348   (void) curl_easy_getinfo (md->easy,
    349                             CURLINFO_PRIMARY_IP,
    350                             &remote_ip);
    351   (void) curl_easy_getinfo (md->easy,
    352                             CURLINFO_LOCAL_IP,
    353                             &local_ip);
    354   (void) curl_easy_getinfo (md->easy,
    355                             CURLINFO_NAMELOOKUP_TIME,
    356                             &dns_s);
    357   (void) curl_easy_getinfo (md->easy,
    358                             CURLINFO_CONNECT_TIME,
    359                             &tcp_s);
    360   (void) curl_easy_getinfo (md->easy,
    361                             CURLINFO_APPCONNECT_TIME,
    362                             &tls_s);
    363   (void) curl_easy_getinfo (md->easy,
    364                             CURLINFO_STARTTRANSFER_TIME,
    365                             &first_byte_s);
    366   (void) curl_easy_getinfo (md->easy,
    367                             CURLINFO_TOTAL_TIME,
    368                             &total_s);
    369   sanitize_diagnostic_error (md->error);
    370   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    371               "Forced-fresh merchant diagnostic for order `%s': completed"
    372               " HTTP status %ld, observed HTTP status %ld, curl error"
    373               " \"%s\", new connections %ld, local address %s, remote"
    374               " address %s, OS errno %ld, HTTP version %ld; timings in"
    375               " seconds (TCP Fast Open enabled): DNS %.6f, TCP %.6f, TLS"
    376               " %.6f, first byte %.6f, total %.6f%s\n",
    377               md->order_id,
    378               completed_http_status,
    379               observed_http_status,
    380               md->error,
    381               new_connections,
    382               (NULL != local_ip) ? local_ip : "<none>",
    383               (NULL != remote_ip) ? remote_ip : "<none>",
    384               os_errno,
    385               http_version,
    386               dns_s,
    387               tcp_s,
    388               tls_s,
    389               first_byte_s,
    390               total_s,
    391               (0 != completed_http_status)
    392               ? "; a fresh connection reached the merchant after the shared"
    393                 " merchant context failed"
    394               : "");
    395   md->easy = NULL;
    396   md->task = GNUNET_SCHEDULER_add_now (&free_completed_diagnostic,
    397                                        md);
    398 }
    399 
    400 
    401 /**
    402  * Start a forced-fresh request after the failed merchant job has completely
    403  * left libcurl's multi handle.  This is the useful A/B comparison with the
    404  * two regular attempts, which share the long-lived connection pool.
    405  */
    406 static void
    407 start_fresh_diagnostic (void *cls)
    408 {
    409   struct MerchantTransportDiagnostic *md = cls;
    410   struct GNUNET_CURL_StreamHandlers sh = {
    411     .scb = &discard_diagnostic_body,
    412     .scb_cls = md,
    413     .jcc = &fresh_diagnostic_finished,
    414     .jcc_cls = md
    415   };
    416   CURLcode cc;
    417 
    418   md->task = NULL;
    419   md->easy = curl_easy_init ();
    420   if (NULL == md->easy)
    421     goto fail;
    422   md->error[0] = '\0';
    423 #define SET_DIAGNOSTIC_OPTION(opt,val) do { \
    424     cc = curl_easy_setopt (md->easy, opt, val); \
    425     if (CURLE_OK != cc) \
    426       goto setopt_fail; \
    427   } while (0)
    428   SET_DIAGNOSTIC_OPTION (CURLOPT_URL,
    429                          md->url);
    430   SET_DIAGNOSTIC_OPTION (CURLOPT_ERRORBUFFER,
    431                          md->error);
    432   SET_DIAGNOSTIC_OPTION (CURLOPT_CONNECTTIMEOUT_MS,
    433                          1500L);
    434   SET_DIAGNOSTIC_OPTION (CURLOPT_TIMEOUT_MS,
    435                          2000L);
    436   SET_DIAGNOSTIC_OPTION (CURLOPT_ACCEPT_ENCODING,
    437                          "");
    438   SET_DIAGNOSTIC_OPTION (CURLOPT_TCP_FASTOPEN,
    439                          1L);
    440   SET_DIAGNOSTIC_OPTION (CURLOPT_FRESH_CONNECT,
    441                          1L);
    442   SET_DIAGNOSTIC_OPTION (CURLOPT_FORBID_REUSE,
    443                          1L);
    444   if (NULL != PH_merchant_unixpath)
    445     SET_DIAGNOSTIC_OPTION (CURLOPT_UNIX_SOCKET_PATH,
    446                            PH_merchant_unixpath);
    447 #undef SET_DIAGNOSTIC_OPTION
    448   md->job = GNUNET_CURL_job_add_stream (PH_merchant_ctx,
    449                                         md->easy,
    450                                         NULL,
    451                                         &sh);
    452   if (NULL != md->job)
    453     return;
    454   md->easy = NULL; /* GNUNET_CURL_job_add_stream() released it. */
    455   goto fail;
    456 
    457 setopt_fail:
    458 #undef SET_DIAGNOSTIC_OPTION
    459   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    460               "Could not configure forced-fresh merchant diagnostic for"
    461               " order `%s': %s\n",
    462               md->order_id,
    463               curl_easy_strerror (cc));
    464   curl_easy_cleanup (md->easy);
    465   md->easy = NULL;
    466 fail:
    467   GNUNET_CONTAINER_DLL_remove (diagnostic_head,
    468                                diagnostic_tail,
    469                                md);
    470   GNUNET_free (md->url);
    471   GNUNET_free (md->order_id);
    472   GNUNET_free (md);
    473 }
    474 
    475 
    476 /**
    477  * Queue one credential-safe diagnostic of the exact failed order lookup.
    478  * The bearer token is attached by #PH_merchant_ctx and is never copied here.
    479  */
    480 static void
    481 queue_fresh_diagnostic (const struct PayRequest *ph)
    482 {
    483   struct MerchantTransportDiagnostic *md;
    484   char *path;
    485   char *paivana_id;
    486 
    487   GNUNET_asprintf (&path,
    488                    "private/orders/%s",
    489                    ph->order_id);
    490   paivana_id = PAIVANA_HTTPD_compute_paivana_id (ph->expiration,
    491                                                  ph->website,
    492                                                  &ph->nonce);
    493   md = GNUNET_new (struct MerchantTransportDiagnostic);
    494   md->url = TALER_url_join (PH_merchant_internal_url,
    495                             path,
    496                             "session_id",
    497                             paivana_id,
    498                             NULL);
    499   GNUNET_free (paivana_id);
    500   GNUNET_free (path);
    501   if (NULL == md->url)
    502   {
    503     GNUNET_free (md);
    504     return;
    505   }
    506   md->order_id = GNUNET_strdup (ph->order_id);
    507   GNUNET_CONTAINER_DLL_insert (diagnostic_head,
    508                                diagnostic_tail,
    509                                md);
    510   md->task = GNUNET_SCHEDULER_add_now (&start_fresh_diagnostic,
    511                                        md);
    512 }
    513 
    514 
    515 /**
    516  * Decide whether to emit a merchant failure warning now.
    517  *
    518  * @param fc failure class
    519  * @param[out] suppressed number suppressed since the prior warning
    520  * @return true if the caller should log
    521  */
    522 static bool
    523 merchant_failure_should_log (enum MerchantFailureClass fc,
    524                              unsigned int *suppressed)
    525 {
    526   struct MerchantFailureLogState *fl = &failure_logs[fc];
    527   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
    528 
    529   if ( (! fl->logged) ||
    530        GNUNET_TIME_absolute_cmp (now,
    531                                  >=,
    532                                  fl->next_log) )
    533   {
    534     *suppressed = fl->suppressed;
    535     fl->suppressed = 0;
    536     fl->logged = true;
    537     fl->next_log = GNUNET_TIME_absolute_add (
    538       now,
    539       MERCHANT_FAILURE_LOG_INTERVAL);
    540     return true;
    541   }
    542   if (UINT_MAX != fl->suppressed)
    543     fl->suppressed++;
    544   return false;
    545 }
    546 
    547 
    548 /**
    549  * Clear sampling state after the backend returns an HTTP response.
    550  *
    551  * @return failures suppressed since the most recent emitted warnings
    552  */
    553 static unsigned int
    554 reset_merchant_failure_logs (void)
    555 {
    556   unsigned int suppressed = 0;
    557 
    558   for (unsigned int i = 0; i < MFC_COUNT; i++)
    559   {
    560     if (UINT_MAX - suppressed < failure_logs[i].suppressed)
    561       suppressed = UINT_MAX;
    562     else
    563       suppressed += failure_logs[i].suppressed;
    564   }
    565   memset (failure_logs,
    566           0,
    567           sizeof (failure_logs));
    568   return suppressed;
    569 }
    570 
    571 
    572 /**
    573  * Log process descriptor usage while a merchant transport failure is
    574  * live.  Linux exposes this cheaply through /proc; elsewhere the
    575  * directory may not exist and this diagnostic quietly stays absent.
    576  * Failure to open it with EMFILE/ENFILE is itself useful evidence.
    577  */
    578 static void
    579 log_file_descriptor_usage (void)
    580 {
    581   DIR *dir;
    582   struct dirent *entry;
    583   unsigned int open_fds = 0;
    584 
    585   dir = opendir ("/proc/self/fd");
    586   if (NULL == dir)
    587   {
    588     int ec = errno;
    589 
    590     if ( (EMFILE == ec) ||
    591          (ENFILE == ec) )
    592       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    593                   "Could not inspect open descriptors after merchant"
    594                   " transport failure: %s; descriptor exhaustion is"
    595                   " likely\n",
    596                   strerror (ec));
    597     else
    598       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    599                   "Could not inspect /proc/self/fd after merchant"
    600                   " transport failure: %s\n",
    601                   strerror (ec));
    602     return;
    603   }
    604   while (NULL != (entry = readdir (dir)))
    605     if ( (0 != strcmp (entry->d_name,
    606                        ".")) &&
    607          (0 != strcmp (entry->d_name,
    608                        "..")) )
    609       open_fds++;
    610   GNUNET_break (0 == closedir (dir));
    611   /* The directory descriptor was present during the scan and is closed
    612      now, so report the number that remains after this function. */
    613   if (0 != open_fds)
    614     open_fds--;
    615 #if HAVE_SYS_RESOURCE_H
    616   {
    617     struct rlimit lim;
    618 
    619     if (0 == getrlimit (RLIMIT_NOFILE,
    620                         &lim))
    621     {
    622       if (RLIM_INFINITY == lim.rlim_cur)
    623         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    624                     "Process had %u open file descriptors at merchant"
    625                     " transport failure (soft limit is unlimited)\n",
    626                     open_fds);
    627       else
    628         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    629                     "Process had %u open file descriptors at merchant"
    630                     " transport failure (soft limit %llu)\n",
    631                     open_fds,
    632                     (unsigned long long) lim.rlim_cur);
    633       return;
    634     }
    635   }
    636 #endif
    637   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    638               "Process had %u open file descriptors at merchant"
    639               " transport failure (soft limit unavailable)\n",
    640               open_fds);
    641 }
    642 
    643 
    644 void
    645 PAIVANA_HTTPD_payment_shutdown ()
    646 {
    647   while (NULL != diagnostic_head)
    648   {
    649     struct MerchantTransportDiagnostic *md = diagnostic_head;
    650 
    651     if (NULL != md->task)
    652       GNUNET_SCHEDULER_cancel (md->task);
    653     if (NULL != md->job)
    654       GNUNET_CURL_job_cancel (md->job);
    655     GNUNET_CONTAINER_DLL_remove (diagnostic_head,
    656                                  diagnostic_tail,
    657                                  md);
    658     GNUNET_free (md->url);
    659     GNUNET_free (md->order_id);
    660     GNUNET_free (md);
    661   }
    662   while (NULL != ph_head)
    663   {
    664     struct PayRequest *ph = ph_head;
    665 
    666     if (NULL != ph->co)
    667     {
    668       TALER_MERCHANT_get_private_order_cancel (ph->co);
    669       ph->co = NULL;
    670     }
    671     GNUNET_CONTAINER_DLL_remove (ph_head,
    672                                  ph_tail,
    673                                  ph);
    674     GNUNET_assert (active_merchant_lookups > 0);
    675     active_merchant_lookups--;
    676     MHD_resume_connection (ph->connection);
    677     /* Note: PAIVANA_HTTPD_payment_destroy()
    678        will be called by the owner of 'ph',
    679        no need to do it here! */
    680   }
    681   GNUNET_assert (0 == active_merchant_lookups);
    682 }
    683 
    684 
    685 struct PayRequest *
    686 PAIVANA_HTTPD_payment_create (struct MHD_Connection *connection)
    687 {
    688   struct PayRequest *ph;
    689 
    690   ph = GNUNET_new (struct PayRequest);
    691   ph->connection = connection;
    692   return ph;
    693 }
    694 
    695 
    696 /**
    697  * Is @a website a URL below our own base URL?
    698  *
    699  * Used to bound where a client may send itself once it has paid for
    700  * an order that carries no fulfillment URL of its own: without this
    701  * the client picks the redirect target and the site the access cookie
    702  * is minted for.
    703  *
    704  * The comparison is on whole path segments.  A bare prefix test would
    705  * accept "https://example.com.evil.net/" for a base URL of
    706  * "https://example.com", because strip_trailing_slashes() has removed
    707  * the '/' that used to terminate it.
    708  *
    709  * @param website candidate URL, from the client
    710  * @return true if @a website is our base URL or something below it
    711  */
    712 static bool
    713 under_our_base_url (const char *website)
    714 {
    715   size_t blen;
    716 
    717   if (NULL == PH_base_url)
    718   {
    719     /* BASE_URL is optional; without it we have nothing to compare
    720        against and must not guess.  Note that dereferencing it here
    721        used to be an unconditional crash. */
    722     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    723                 "Cannot check the target of an order without a"
    724                 " fulfillment URL: BASE_URL is not configured\n");
    725     return false;
    726   }
    727   blen = strlen (PH_base_url);
    728   if (0 != strncmp (website,
    729                     PH_base_url,
    730                     blen))
    731     return false;
    732   /* PH_base_url has no trailing '/' (strip_trailing_slashes()), so
    733      require the boundary here rather than inheriting it. */
    734   return ('\0' == website[blen]) ||
    735          ('/' == website[blen]);
    736 }
    737 
    738 
    739 static void
    740 order_status_cb (struct PayRequest *ph,
    741                  const struct TALER_MERCHANT_GetPrivateOrderResponse *osr);
    742 
    743 
    744 /**
    745  * Start one attempt to retrieve the order from the merchant backend.
    746  *
    747  * The caller owns the request's DLL membership and suspended MHD connection.
    748  * On failure this function leaves @e ph->co NULL.
    749  *
    750  * @param ph payment request
    751  * @param timeout remaining overall timeout for this attempt
    752  * @return #TALER_EC_NONE on success, an error code otherwise
    753  */
    754 static enum TALER_ErrorCode
    755 start_merchant_order_lookup (struct PayRequest *ph,
    756                              struct GNUNET_TIME_Relative timeout)
    757 {
    758   char *paivana_id;
    759   enum GNUNET_GenericReturnValue ret;
    760   enum TALER_ErrorCode ec;
    761 
    762   GNUNET_assert (NULL == ph->co);
    763   ph->co = TALER_MERCHANT_get_private_order_create (PH_merchant_ctx,
    764                                                     PH_merchant_internal_url,
    765                                                     ph->order_id);
    766   if (NULL == ph->co)
    767     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    768   paivana_id = PAIVANA_HTTPD_compute_paivana_id (ph->expiration,
    769                                                  ph->website,
    770                                                  &ph->nonce);
    771   ret = TALER_MERCHANT_get_private_order_set_options (
    772     ph->co,
    773     TALER_MERCHANT_get_private_order_option_session_id (paivana_id),
    774     TALER_MERCHANT_get_private_order_option_timeout (timeout));
    775   GNUNET_free (paivana_id);
    776   if (GNUNET_OK != ret)
    777   {
    778     TALER_MERCHANT_get_private_order_cancel (ph->co);
    779     ph->co = NULL;
    780     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    781   }
    782   ec = TALER_MERCHANT_get_private_order_start (ph->co,
    783                                                &order_status_cb,
    784                                                ph);
    785   if (TALER_EC_NONE != ec)
    786   {
    787     TALER_MERCHANT_get_private_order_cancel (ph->co);
    788     ph->co = NULL;
    789     return ec;
    790   }
    791   ph->merchant_lookup_attempts++;
    792   return TALER_EC_NONE;
    793 }
    794 
    795 
    796 /**
    797  * Build a Paivana error response caused by a merchant order lookup.
    798  *
    799  * Keep the standard Taler error shape and its existing @e detail while adding
    800  * the status of the merchant request.  A zero status explicitly says that no
    801  * HTTP response was received.
    802  *
    803  * @param ec Taler error code
    804  * @param detail optional error detail
    805  * @param merchant_http_status HTTP status returned by the merchant, or zero
    806  * @return MHD response
    807  */
    808 static struct MHD_Response *
    809 make_merchant_error (enum TALER_ErrorCode ec,
    810                      const char *detail,
    811                      unsigned int merchant_http_status)
    812 {
    813   return TALER_MHD_MAKE_JSON_PACK (
    814     TALER_MHD_PACK_EC (ec),
    815     GNUNET_JSON_pack_conditional (
    816       NULL != detail,
    817       GNUNET_JSON_pack_string ("detail",
    818                                detail)),
    819     GNUNET_JSON_pack_uint64 ("merchant_http_status",
    820                              merchant_http_status));
    821 }
    822 
    823 
    824 /**
    825  * Queue a Paivana error caused before a merchant lookup returned.
    826  *
    827  * @param connection client connection
    828  * @param http_status status to return to the client
    829  * @param ec Taler error code
    830  * @param detail optional error detail
    831  * @return MHD result
    832  */
    833 static enum MHD_Result
    834 reply_with_merchant_error (struct MHD_Connection *connection,
    835                            unsigned int http_status,
    836                            enum TALER_ErrorCode ec,
    837                            const char *detail)
    838 {
    839   struct MHD_Response *response;
    840   enum MHD_Result ret;
    841 
    842   response = make_merchant_error (ec,
    843                                   detail,
    844                                   0);
    845   ret = MHD_queue_response (connection,
    846                             http_status,
    847                             response);
    848   MHD_destroy_response (response);
    849   return ret;
    850 }
    851 
    852 
    853 /**
    854  * Check that the @a contract that was paid is reasonable for the
    855  * request in @a ph, that is that we would indeed consider this
    856  * contract to apply for the website and duration indicated
    857  * in @a ph. If it does not apply, a response must be set in
    858  * @a ph.
    859  *
    860  * @param[in,out] ph request to check
    861  * @param contract contract to check
    862  * @return true if the contract is good for the request,
    863  *   false if not and thus a response object was created in @a ph
    864  */
    865 static bool
    866 check_contract (struct PayRequest *ph,
    867                 const json_t *contract)
    868 {
    869   struct GNUNET_TIME_Timestamp max_time
    870     = GNUNET_TIME_UNIT_FOREVER_TS;
    871   const char *target = NULL;
    872   struct GNUNET_JSON_Specification spec[] = {
    873     GNUNET_JSON_spec_mark_optional (
    874       TALER_JSON_spec_web_url ("fulfillment_url",
    875                                &target),
    876       NULL),
    877     GNUNET_JSON_spec_mark_optional (
    878       GNUNET_JSON_spec_timestamp ("max_pickup_time",
    879                                   &max_time),
    880       NULL),
    881     GNUNET_JSON_spec_end ()
    882   };
    883   enum GNUNET_GenericReturnValue ret;
    884   const char *ename;
    885   unsigned int eline;
    886 
    887   ret = GNUNET_JSON_parse (contract,
    888                            spec,
    889                            &ename,
    890                            &eline);
    891   if (GNUNET_OK != ret)
    892   {
    893     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    894                 "Encountered contract with unexpected fields: %s@%u\n",
    895                 ename,
    896                 eline);
    897     /* Fail closed: returning true here would skip every check below --
    898        the fulfillment_url binding, the base-URL containment test and
    899        the max_pickup_time deadline -- and mint an access cookie for
    900        whatever website the client named.  Tolerating unknown *extra*
    901        fields is already what GNUNET_JSON_parse() does; a failure here
    902        means a field we do look at was malformed. */
    903     GNUNET_break_op (0);
    904     ph->response = make_merchant_error (TALER_EC_PAIVANA_WRONG_ORDER,
    905                                         ph->order_id,
    906                                         MHD_HTTP_OK);
    907     ph->response_status = MHD_HTTP_CONFLICT;
    908     return false;
    909   }
    910   if ( (NULL != target) &&
    911        (0 != strcmp (target,
    912                      ph->website)) )
    913   {
    914     GNUNET_break_op (0);
    915     ph->response = make_merchant_error (TALER_EC_PAIVANA_WRONG_ORDER,
    916                                         ph->order_id,
    917                                         MHD_HTTP_OK);
    918     ph->response_status = MHD_HTTP_CONFLICT;
    919     return false;
    920   }
    921   if ( ( (NULL == target) &&
    922          (! under_our_base_url (ph->website)) ) ||
    923        (! TALER_is_web_url (ph->website)) )
    924   {
    925     /* Bad: the order has no fulfillment URL, and on top of that
    926        the target given is not from our domain or not a well-formed
    927        URL. Reject hard. */
    928     GNUNET_break_op (0);
    929     ph->response = make_merchant_error (TALER_EC_PAIVANA_INVALID_TARGET,
    930                                         ph->website,
    931                                         MHD_HTTP_OK);
    932     ph->response_status = MHD_HTTP_CONFLICT;
    933     return false;
    934   }
    935   if (GNUNET_TIME_timestamp_cmp (ph->expiration,
    936                                  >,
    937                                  max_time))
    938   {
    939     GNUNET_break_op (0);
    940     ph->response = make_merchant_error (TALER_EC_PAIVANA_TOO_LATE,
    941                                         ph->order_id,
    942                                         MHD_HTTP_OK);
    943     ph->response_status = MHD_HTTP_GONE;
    944     return false;
    945   }
    946   return true;
    947 }
    948 
    949 
    950 /**
    951  * Handle response from the GET /private/orders/$ORDER_ID request.
    952  *
    953  * @param ph the payment request we are processing
    954  * @param osr response details
    955  */
    956 static void
    957 order_status_cb (struct PayRequest *ph,
    958                  const struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
    959 {
    960   struct GNUNET_TIME_Relative elapsed;
    961   char *elapsed_s;
    962   char *timeout_s;
    963   unsigned int active_lookups;
    964 
    965   elapsed = GNUNET_TIME_absolute_get_duration (
    966     ph->merchant_request_started);
    967   /* GNUNET_STRINGS_relative_time_to_string() reuses one static buffer,
    968      so keep copies before putting both values in one log message. */
    969   elapsed_s = GNUNET_strdup (
    970     GNUNET_STRINGS_relative_time_to_string (elapsed,
    971                                             true));
    972   timeout_s = GNUNET_strdup (
    973     GNUNET_STRINGS_relative_time_to_string (MERCHANT_ORDER_TIMEOUT,
    974                                             true));
    975   active_lookups = active_merchant_lookups;
    976   ph->co = NULL;
    977   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    978               "Merchant order lookup attempt %u for `%s' completed with"
    979               " HTTP status %u after %s\n",
    980               ph->merchant_lookup_attempts,
    981               ph->order_id,
    982               osr->hr.http_status,
    983               elapsed_s);
    984   if ( (0 == osr->hr.http_status) &&
    985        (NULL == osr->hr.reply) &&
    986        (1 == ph->merchant_lookup_attempts) &&
    987        GNUNET_TIME_relative_cmp (elapsed,
    988                                  <,
    989                                  MERCHANT_ORDER_TIMEOUT) )
    990   {
    991     struct GNUNET_TIME_Relative remaining
    992       = GNUNET_TIME_relative_subtract (MERCHANT_ORDER_TIMEOUT,
    993                                        elapsed);
    994 
    995     if (GNUNET_TIME_relative_cmp (remaining,
    996                                   >=,
    997                                   MERCHANT_RETRY_MIN_BUDGET))
    998     {
    999       enum TALER_ErrorCode ec;
   1000 
   1001       ec = start_merchant_order_lookup (ph,
   1002                                         remaining);
   1003       if (TALER_EC_NONE == ec)
   1004       {
   1005         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1006                     "Merchant order lookup for `%s' received no HTTP"
   1007                     " response on its first attempt after %s; retrying once"
   1008                     " within the original %s deadline\n",
   1009                     ph->order_id,
   1010                     elapsed_s,
   1011                     timeout_s);
   1012         GNUNET_free (elapsed_s);
   1013         GNUNET_free (timeout_s);
   1014         return;
   1015       }
   1016       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1017                   "Could not start the retry of merchant order `%s': %d\n",
   1018                   ph->order_id,
   1019                   (int) ec);
   1020     }
   1021   }
   1022   GNUNET_CONTAINER_DLL_remove (ph_head,
   1023                                ph_tail,
   1024                                ph);
   1025   GNUNET_assert (active_merchant_lookups > 0);
   1026   active_merchant_lookups--;
   1027   MHD_resume_connection (ph->connection);
   1028   TALER_MHD_daemon_trigger ();
   1029   if (0 != osr->hr.http_status)
   1030   {
   1031     unsigned int suppressed = reset_merchant_failure_logs ();
   1032 
   1033     if (0 != merchant_transport_failures)
   1034       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1035                   "Merchant backend at `%s' answered order `%s' after %u"
   1036                   " consecutive lookup%s without an HTTP response (%u"
   1037                   " repetitive diagnostic%s suppressed)\n",
   1038                   PH_merchant_internal_url,
   1039                   ph->order_id,
   1040                   merchant_transport_failures,
   1041                   (1 == merchant_transport_failures) ? "" : "s",
   1042                   suppressed,
   1043                   (1 == suppressed) ? "" : "s");
   1044     merchant_transport_failures = 0;
   1045   }
   1046   switch (osr->hr.http_status)
   1047   {
   1048   case MHD_HTTP_OK:
   1049     /* "paid" survives a refund -- the merchant reports the refund in
   1050        separate fields (api-merchant.rst, CheckPaymentPaidResponse) --
   1051        so testing the status alone would hand a fresh cookie to someone
   1052        who has had their money back. */
   1053     if ( (TALER_MERCHANT_OSC_PAID != osr->details.ok.status) ||
   1054          (osr->details.ok.details.paid.refunded) ||
   1055          (osr->details.ok.details.paid.refund_pending) )
   1056     {
   1057       GNUNET_break_op (0);
   1058       ph->response = make_merchant_error (TALER_EC_PAIVANA_PAYMENT_MISSING,
   1059                                           ph->order_id,
   1060                                           MHD_HTTP_OK);
   1061       ph->response_status = MHD_HTTP_CONFLICT;
   1062     }
   1063     else
   1064     {
   1065       void *ca = NULL;
   1066       size_t ca_len = 0;
   1067       char *cookie;
   1068       struct MHD_Response *resp;
   1069 
   1070       if (! check_contract (ph,
   1071                             osr->details.ok.details.paid.contract_terms))
   1072       {
   1073         GNUNET_free (elapsed_s);
   1074         GNUNET_free (timeout_s);
   1075         return;
   1076       }
   1077       /* The client address is bound into the cookie MAC; computing
   1078          the cookie over an empty address would produce a cookie that
   1079          PAIVANA_HTTPD_check_cookie can never match, silently denying
   1080          the access the client just paid for.  Treat failure to obtain
   1081          it as a hard error instead.
   1082 
   1083          Note: This should become conditional once we add a
   1084          configuration option to not include the client address in the
   1085          cookie hash to allow one payment to be used from any IP
   1086          address. */
   1087       if (! PAIVANA_HTTPD_get_client_address (ph->connection,
   1088                                               &ca,
   1089                                               &ca_len))
   1090       {
   1091         GNUNET_break (0);
   1092         ph->response = make_merchant_error (
   1093           TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1094           ph->order_id,
   1095           MHD_HTTP_OK);
   1096         ph->response_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1097         break;
   1098       }
   1099       cookie = PAIVANA_HTTPD_compute_cookie (ph->expiration,
   1100                                              ph->website,
   1101                                              ca_len,
   1102                                              ca);
   1103       /* The cookie is the bearer credential proving payment; anyone
   1104          who can read the log could replay it from the same address. */
   1105       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1106                   "Client paid for `%s', setting access cookie\n",
   1107                   ph->website);
   1108       GNUNET_free (ca);
   1109       resp = MHD_create_response_from_buffer (0,
   1110                                               NULL,
   1111                                               MHD_RESPMEM_PERSISTENT);
   1112       GNUNET_assert (NULL != resp);
   1113       if ( (MHD_YES !=
   1114             MHD_add_response_header (resp,
   1115                                      MHD_HTTP_HEADER_SET_COOKIE,
   1116                                      cookie)) ||
   1117            (MHD_YES !=
   1118             MHD_add_response_header (resp,
   1119                                      MHD_HTTP_HEADER_LOCATION,
   1120                                      ph->website)) )
   1121       {
   1122         /* Neither header is optional: without the `Set-Cookie' the
   1123            client has paid and been sent back to a page that will
   1124            paywall it again, and without the `Location' the 303 has no
   1125            target at all.  Answering 500 at least says so, and leaves
   1126            the order paid and the redemption repeatable; sending the
   1127            303 anyway does not. */
   1128         GNUNET_break (0);
   1129         MHD_destroy_response (resp);
   1130         GNUNET_free (cookie);
   1131         ph->response = make_merchant_error (
   1132           TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1133           ph->website,
   1134           MHD_HTTP_OK);
   1135         ph->response_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1136         break;
   1137       }
   1138       GNUNET_free (cookie);
   1139       TALER_MHD_add_global_headers (resp,
   1140                                     false);
   1141       ph->response = resp;
   1142       ph->response_status = MHD_HTTP_SEE_OTHER;
   1143     }
   1144     break;
   1145   case MHD_HTTP_UNAUTHORIZED:
   1146   case MHD_HTTP_FORBIDDEN:
   1147     /* Our `MERCHANT_ACCESS_TOKEN' is wrong: the operator's problem, not
   1148        the client's, hence 500 and not a 4xx.  UNAUTHORIZED is the case
   1149        that actually fires -- taler-merchant-httpd_auth.c answers a bad
   1150        bearer token with 401 -- and without it this landed in the
   1151        default branch below, telling the operator that a protocol
   1152        incompatibility should be reported to us.
   1153 
   1154        Note that GANA has 9801 documented as a 502 and 9803 as a 500,
   1155        i.e. the two the other way round from what is sent here and
   1156        below.  The statuses are right: RFC 9110 section 15.6.3 gives
   1157        502 for "an invalid response from an inbound server", which is
   1158        the unexpected-status case (9803), while a bearer token of ours
   1159        that the backend will not take is our own misconfiguration and
   1160        not the upstream misbehaving (9801).  Fixing the registry is a
   1161        change in gana, a different repository. */
   1162     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1163                 "Merchant backend at `%s' rejected our credentials (HTTP"
   1164                 " %u); check MERCHANT_ACCESS_TOKEN\n",
   1165                 PH_merchant_internal_url,
   1166                 osr->hr.http_status);
   1167     ph->response = make_merchant_error (TALER_EC_PAIVANA_BACKEND_REFUSED,
   1168                                         NULL,
   1169                                         osr->hr.http_status);
   1170     ph->response_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1171     break;
   1172   case MHD_HTTP_NOT_FOUND:
   1173     ph->response = make_merchant_error (TALER_EC_PAIVANA_ORDER_UNKNOWN,
   1174                                         ph->order_id,
   1175                                         osr->hr.http_status);
   1176     ph->response_status = MHD_HTTP_NOT_FOUND;
   1177     break;
   1178   case 0:
   1179     /* No HTTP status at all.  The merchant client library reports this
   1180        both for a request that never completed and for one whose reply
   1181        it could not make sense of, with the same error code; @e reply is
   1182        what tells them apart, being NULL only in the former case. */
   1183     if (NULL != osr->hr.reply)
   1184     {
   1185       unsigned int suppressed;
   1186 
   1187       merchant_transport_failures = 0;
   1188       GNUNET_break_op (0);
   1189       if (merchant_failure_should_log (MFC_UNUSABLE_REPLY,
   1190                                        &suppressed))
   1191         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1192                     "Merchant backend at `%s' sent an unusable reply for"
   1193                     " order `%s' after %s (%u similar diagnostic%s"
   1194                     " suppressed)\n",
   1195                     PH_merchant_internal_url,
   1196                     ph->order_id,
   1197                     elapsed_s,
   1198                     suppressed,
   1199                     (1 == suppressed) ? "" : "s");
   1200       ph->response = make_merchant_error (TALER_EC_PAIVANA_BACKEND_ERROR,
   1201                                           ph->order_id,
   1202                                           osr->hr.http_status);
   1203       ph->response_status = MHD_HTTP_BAD_GATEWAY;
   1204       break;
   1205     }
   1206     if (UINT_MAX != merchant_transport_failures)
   1207       merchant_transport_failures++;
   1208     if (GNUNET_TIME_relative_cmp (elapsed,
   1209                                   >=,
   1210                                   MERCHANT_ORDER_TIMEOUT))
   1211     {
   1212       unsigned int suppressed;
   1213 
   1214       if (merchant_failure_should_log (MFC_TIMEOUT,
   1215                                        &suppressed))
   1216       {
   1217         log_file_descriptor_usage ();
   1218         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1219                     "Merchant backend at `%s' returned no HTTP response for"
   1220                     " order `%s' by the %s deadline (elapsed %s; %u"
   1221                     " concurrent merchant lookup%s including this one; %u"
   1222                     " consecutive transport failure%s; %u similar"
   1223                     " diagnostic%s suppressed)\n",
   1224                     PH_merchant_internal_url,
   1225                     ph->order_id,
   1226                     timeout_s,
   1227                     elapsed_s,
   1228                     active_lookups,
   1229                     (1 == active_lookups) ? "" : "s",
   1230                     merchant_transport_failures,
   1231                     (1 == merchant_transport_failures) ? "" : "s",
   1232                     suppressed,
   1233                     (1 == suppressed) ? "" : "s");
   1234       }
   1235       /* GENERIC_TIMEOUT's hint ("trying again might help") is the one
   1236          that is true once our own deadline was actually reached. */
   1237       ph->response = make_merchant_error (TALER_EC_GENERIC_TIMEOUT,
   1238                                           ph->order_id,
   1239                                           osr->hr.http_status);
   1240       ph->response_status = MHD_HTTP_GATEWAY_TIMEOUT;
   1241     }
   1242     else
   1243     {
   1244       unsigned int suppressed;
   1245 
   1246       /* The merchant API does not expose CURLcode, so DNS failure,
   1247          connection refusal, TLS failure and a dead reused connection
   1248          are indistinguishable here.  What they have in common is that
   1249          they failed before our timeout.  Calling that a timeout hid the
   1250          most useful fact from both the operator and the client. */
   1251       if (merchant_failure_should_log (MFC_EARLY_TRANSPORT,
   1252                                        &suppressed))
   1253       {
   1254         log_file_descriptor_usage ();
   1255         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1256                     "Merchant backend at `%s' returned no HTTP response for"
   1257                     " order `%s' after %s, before the %s deadline; this is"
   1258                     " an early transport failure (for example DNS, TCP, TLS"
   1259                     " or a stale reused connection), not a Paivana timeout"
   1260                     " (%u concurrent merchant lookup%s including this one;"
   1261                     " %u consecutive transport failure%s; %u similar"
   1262                     " diagnostic%s suppressed)\n",
   1263                     PH_merchant_internal_url,
   1264                     ph->order_id,
   1265                     elapsed_s,
   1266                     timeout_s,
   1267                     active_lookups,
   1268                     (1 == active_lookups) ? "" : "s",
   1269                     merchant_transport_failures,
   1270                     (1 == merchant_transport_failures) ? "" : "s",
   1271                     suppressed,
   1272                     (1 == suppressed) ? "" : "s");
   1273         queue_fresh_diagnostic (ph);
   1274       }
   1275       ph->response = make_merchant_error (TALER_EC_PAIVANA_BACKEND_REFUSED,
   1276                                           ph->order_id,
   1277                                           osr->hr.http_status);
   1278       ph->response_status = MHD_HTTP_BAD_GATEWAY;
   1279     }
   1280     break;
   1281   default:
   1282     {
   1283       char code[20];
   1284 
   1285       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1286                   "Unexpected status code %u from backend\n",
   1287                   osr->hr.http_status);
   1288       GNUNET_snprintf (code,
   1289                        sizeof (code),
   1290                        "%u",
   1291                        osr->hr.http_status);
   1292       ph->response = make_merchant_error (TALER_EC_PAIVANA_BACKEND_ERROR,
   1293                                           code,
   1294                                           osr->hr.http_status);
   1295       ph->response_status = MHD_HTTP_BAD_GATEWAY;
   1296     }
   1297     break;
   1298   }
   1299   GNUNET_free (elapsed_s);
   1300   GNUNET_free (timeout_s);
   1301 }
   1302 
   1303 
   1304 enum MHD_Result
   1305 PAIVANA_HTTPD_payment_handle (struct PayRequest *ph,
   1306                               const char *upload_data,
   1307                               size_t *upload_data_size)
   1308 {
   1309   if (NULL == ph->body)
   1310   {
   1311     enum GNUNET_GenericReturnValue ret;
   1312 
   1313     ret = TALER_MHD_parse_post_json (ph->connection,
   1314                                      &ph->buffer,
   1315                                      upload_data,
   1316                                      upload_data_size,
   1317                                      &ph->body);
   1318     if (GNUNET_OK != ret)
   1319       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
   1320     if (NULL == ph->body)
   1321       return MHD_YES;
   1322   }
   1323   if (NULL != ph->response)
   1324   {
   1325     return MHD_queue_response (ph->connection,
   1326                                ph->response_status,
   1327                                ph->response);
   1328   }
   1329   if (NULL == ph->order_id)
   1330   {
   1331     struct GNUNET_JSON_Specification spec[] = {
   1332       TALER_JSON_spec_slug ("order_id",
   1333                             &ph->order_id),
   1334       TALER_JSON_spec_web_url ("website",
   1335                                &ph->website),
   1336       GNUNET_JSON_spec_timestamp ("expiration",
   1337                                   &ph->expiration),
   1338       GNUNET_JSON_spec_fixed_auto ("nonce",
   1339                                    &ph->nonce),
   1340       GNUNET_JSON_spec_end ()
   1341     };
   1342     enum GNUNET_GenericReturnValue ret;
   1343 
   1344     ret = TALER_MHD_parse_json_data (ph->connection,
   1345                                      ph->body,
   1346                                      spec);
   1347     if (GNUNET_YES != ret)
   1348       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
   1349     /* `website' is a URL like any other paivana handles, and every
   1350        other one is refused past #PH_MAX_URL_LENGTH before anything
   1351        looks at it.  This one arrives in a JSON body instead of a
   1352        request line, which is the only reason it escaped that: from
   1353        here it goes into an HKDF, into a `Location' and into the
   1354        `Path' of a `Set-Cookie', where three bytes of header are spent
   1355        per byte of path.  Bound it in the same place and at the same
   1356        length. */
   1357     if (PH_MAX_URL_LENGTH < strlen (ph->website))
   1358     {
   1359       GNUNET_break_op (0);
   1360       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1361                   "Refusing %llu byte `website' in payment redemption\n",
   1362                   (unsigned long long) strlen (ph->website));
   1363       return TALER_MHD_reply_with_error (
   1364         ph->connection,
   1365         MHD_HTTP_BAD_REQUEST,
   1366         TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1367         "website");
   1368     }
   1369     /* `expiration' is the end of the access being bought, not a
   1370        statement about the client's clock: it is what the cookie's
   1371        Max-Age is computed from, what check_cookie() enforces, and what
   1372        is hashed into the paivana_id the order was created under, so we
   1373        cannot re-derive it here even if we wanted to.  An expiration in
   1374        the past would mint a cookie that is dead on arrival; the upper
   1375        bound is the contract's `max_pickup_time', enforced in
   1376        check_contract() once we have the contract to compare against. */
   1377     if (GNUNET_TIME_absolute_is_past (ph->expiration.abs_time))
   1378     {
   1379       GNUNET_break_op (0);
   1380       return TALER_MHD_reply_with_error (
   1381         ph->connection,
   1382         MHD_HTTP_BAD_REQUEST,
   1383         TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1384         "expiration");
   1385     }
   1386   }
   1387   GNUNET_CONTAINER_DLL_insert (ph_head,
   1388                                ph_tail,
   1389                                ph);
   1390   active_merchant_lookups++;
   1391   MHD_suspend_connection (ph->connection);
   1392   {
   1393     enum TALER_ErrorCode ec;
   1394 
   1395     ph->merchant_request_started = GNUNET_TIME_absolute_get ();
   1396     ec = start_merchant_order_lookup (ph,
   1397                                       MERCHANT_ORDER_TIMEOUT);
   1398     if (TALER_EC_NONE != ec)
   1399     {
   1400       /* Everything the callee can fail on here is a resource failure
   1401          it recovers from by telling us: curl_easy_init() or
   1402          curl_multi_add_handle() came back empty.  That is one client's
   1403          redemption going wrong, and asserting on it took the daemon
   1404          down with every other request in flight, paid ones included. */
   1405       GNUNET_break (0);
   1406       GNUNET_CONTAINER_DLL_remove (ph_head,
   1407                                    ph_tail,
   1408                                    ph);
   1409       GNUNET_assert (active_merchant_lookups > 0);
   1410       active_merchant_lookups--;
   1411       MHD_resume_connection (ph->connection);
   1412       return reply_with_merchant_error (ph->connection,
   1413                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
   1414                                         TALER_EC_PAIVANA_GET_ORDER_FAILED,
   1415                                         ph->order_id);
   1416     }
   1417   }
   1418   return MHD_YES;
   1419 }
   1420 
   1421 
   1422 void
   1423 PAIVANA_HTTPD_payment_destroy (struct PayRequest *ph)
   1424 {
   1425   TALER_MHD_parse_post_cleanup_callback (ph->buffer);
   1426   if (NULL != ph->co)
   1427   {
   1428     TALER_MERCHANT_get_private_order_cancel (ph->co);
   1429     GNUNET_CONTAINER_DLL_remove (ph_head,
   1430                                  ph_tail,
   1431                                  ph);
   1432     GNUNET_assert (active_merchant_lookups > 0);
   1433     active_merchant_lookups--;
   1434     ph->co = NULL;
   1435   }
   1436   if (NULL != ph->response)
   1437   {
   1438     MHD_destroy_response (ph->response);
   1439     ph->response = NULL;
   1440   }
   1441   json_decref (ph->body);
   1442   GNUNET_free (ph);
   1443 }