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 }