paivana-httpd.c (53885B)
1 /* 2 This file is part of GNU Taler 3 Copyright (C) 2012-2014 GNUnet e.V. 4 Copyright (C) 2018, 2025 Taler Systems SA 5 6 GNU Taler is free software; you can redistribute it and/or 7 modify it under the terms of the GNU General Public License 8 as published by the Free Software Foundation; either version 9 3, or (at your option) any later version. 10 11 GNU Taler is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public 17 License along with GNU Taler; see the file COPYING. If not, 18 write to the Free Software Foundation, Inc., 51 Franklin 19 Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @author Martin Schanzenbach 24 * @author Christian Grothoff 25 * @author Marcello Stanisci 26 * @file src/backend/paivana-httpd.c 27 * @brief HTTP proxy that acts as a GNU Taler paywall 28 */ 29 #include "platform.h" 30 #include <microhttpd.h> 31 #include <curl/curl.h> 32 #include <gnunet/gnunet_util_lib.h> 33 #include <microhttpd.h> 34 #include "paivana_pd.h" 35 36 typedef enum MHD_Result MHD_RESULT; 37 38 #define PAIVANA_LOG_INFO(...) \ 39 GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__) 40 #define PAIVANA_LOG_DEBUG(...) \ 41 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) 42 #define PAIVANA_LOG_WARNING(...) \ 43 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__) 44 #define PAIVANA_LOG_ERROR(...) \ 45 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__) 46 47 #define REQUEST_BUFFER_MAX (1024 * 1024) 48 #define UNIX_BACKLOG 500 49 50 /** 51 * Log curl error. 52 * 53 * @param level log level 54 * @param fun name of curl_easy-function that gave the error 55 * @param rc return code from curl 56 */ 57 #define LOG_CURL_EASY(level,fun,rc) \ 58 GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \ 59 __LINE__, \ 60 curl_easy_strerror (rc)) 61 62 /* ******** Datastructures for HTTP handling ********** */ 63 64 65 /** 66 * State machine for HTTP requests (per request). 67 */ 68 enum RequestState 69 { 70 /** 71 * Starting state. 72 */ 73 REQUEST_STATE_WITH_MHD = 0, 74 75 /** 76 * We've started receiving upload data from MHD. 77 */ 78 REQUEST_STATE_CLIENT_UPLOAD_STARTED, 79 80 /** 81 * Wa have started uploading data to the proxied service. 82 */ 83 REQUEST_STATE_PROXY_UPLOAD_STARTED, 84 85 /** 86 * We're done with the upload from MHD. 87 */ 88 REQUEST_STATE_CLIENT_UPLOAD_DONE, 89 90 /** 91 * We're done uploading data to the proxied service. 92 */ 93 REQUEST_STATE_PROXY_UPLOAD_DONE, 94 95 /** 96 * We've finished uploading data via CURL and can now download. 97 */ 98 REQUEST_STATE_PROXY_DOWNLOAD_STARTED, 99 100 /** 101 * We've finished receiving download data from cURL. 102 */ 103 REQUEST_STATE_PROXY_DOWNLOAD_DONE 104 }; 105 106 107 /** 108 * A header list 109 */ 110 struct HttpResponseHeader 111 { 112 /** 113 * DLL 114 */ 115 struct HttpResponseHeader *next; 116 117 /** 118 * DLL 119 */ 120 struct HttpResponseHeader *prev; 121 122 /** 123 * Header type 124 */ 125 char *type; 126 127 /** 128 * Header value 129 */ 130 char *value; 131 }; 132 133 134 /** 135 * A structure for socks requests 136 */ 137 struct HttpRequest 138 { 139 140 /** 141 * Kept in DLL. 142 */ 143 struct HttpRequest *prev; 144 145 /** 146 * Kept in DLL. 147 */ 148 struct HttpRequest *next; 149 150 /** 151 * MHD request that triggered us. 152 */ 153 struct MHD_Connection *con; 154 155 /** 156 * Client socket read task 157 */ 158 struct GNUNET_SCHEDULER_Task *rtask; 159 160 /** 161 * Client socket write task 162 */ 163 struct GNUNET_SCHEDULER_Task *wtask; 164 165 /** 166 * Hold the response obtained by modifying the original one. 167 */ 168 struct MHD_Response *mod_response; 169 170 /** 171 * MHD response object for this request. 172 */ 173 struct MHD_Response *response; 174 175 /** 176 * The URL to fetch 177 */ 178 char *url; 179 180 /** 181 * Handle to cURL 182 */ 183 CURL *curl; 184 185 /** 186 * HTTP request headers for the curl request. 187 */ 188 struct curl_slist *headers; 189 190 /** 191 * Headers from response 192 */ 193 struct HttpResponseHeader *header_head; 194 195 /** 196 * Headers from response 197 */ 198 struct HttpResponseHeader *header_tail; 199 200 /** 201 * Buffer we use for moving data between MHD and 202 * curl (in both directions). 203 */ 204 char *io_buf; 205 206 /** 207 * Number of bytes already in the IO buffer. 208 */ 209 size_t io_len; 210 211 /** 212 * Number of bytes allocated for the IO buffer. 213 */ 214 unsigned int io_size; 215 216 /** 217 * HTTP response code to give to MHD for the response. 218 */ 219 unsigned int response_code; 220 221 /** 222 * Request processing state machine. 223 */ 224 enum RequestState state; 225 226 /** 227 * Did we suspend MHD processing? 228 */ 229 enum GNUNET_GenericReturnValue suspended; 230 231 /** 232 * Did we pause CURL processing? 233 */ 234 int curl_paused; 235 }; 236 237 238 /* *********************** Globals **************************** */ 239 240 /** 241 * The cURL download task (curl multi API). 242 */ 243 static struct GNUNET_SCHEDULER_Task *curl_download_task; 244 245 /** 246 * DLL of active HTTP requests. 247 */ 248 static struct HttpRequest *hr_head; 249 250 /** 251 * DLL of active HTTP requests. 252 */ 253 static struct HttpRequest *hr_tail; 254 255 /** 256 * The cURL multi handle 257 */ 258 static CURLM *curl_multi; 259 260 /** 261 * The daemon handle 262 */ 263 static struct MHD_Daemon *mhd_daemon; 264 265 /** 266 * Static paywall response. 267 */ 268 static struct MHD_Response *paywall; 269 270 /** 271 * The task ID 272 */ 273 static struct GNUNET_SCHEDULER_Task *httpd_task; 274 275 /** 276 * Response we return on cURL failures. 277 */ 278 static struct MHD_Response *curl_failure_response; 279 280 /** 281 * Our configuration. 282 */ 283 static const struct GNUNET_CONFIGURATION_Handle *cfg; 284 285 /** 286 * Disable paywall check. 287 */ 288 static int no_check; 289 290 /** 291 * Destination to which HTTP server we forward requests to. 292 * Of the format "http://servername:PORT" 293 */ 294 static char *target_server_base_url; 295 296 /** 297 * Merchant backend base URL. 298 */ 299 static char *merchant_base_url; 300 301 /** 302 * Merchant backend access token. 303 */ 304 static char *merchant_access_token; 305 306 /** 307 * Secret for the cookie generation. 308 */ 309 static struct GNUNET_HashCode paivana_secret; 310 311 312 /* ********************* Cookie handling ****************** */ 313 314 /** 315 * Compute access cookie hash for the given @a expiration and @a ca. 316 * 317 * @param expiration expiration time of the cookie 318 * @param ca_len number of bytes in @a ca 319 * @param ca client address 320 * @param[out] c set to the cookie hash 321 */ 322 static void 323 compute_cookie_hash (struct GNUNET_TIME_Absolute expiration, 324 size_t ca_len, 325 const void *ca, 326 struct GNUNET_HashCode *c) 327 { 328 struct GNUNET_TIME_AbsoluteNBO e; 329 330 e = GNUNET_TIME_absolute_hton (expiration); 331 GNUNET_assert (GNUNET_YES == 332 GNUNET_CRYPTO_kdf (c, 333 sizeof (c), 334 &e, 335 sizeof (e), 336 &paivana_secret, 337 sizeof (paivana_secret), 338 ca, 339 ca_len, 340 NULL, 341 0)); 342 } 343 344 345 /** 346 * Check if the given cookie currently grants access. 347 * 348 * @param cookie the cookie 349 * @param ca_len number of bytes in @a ca 350 * @param ca client address 351 * @return true if the cookie is OK 352 */ 353 static bool 354 check_cookie (const char *cookie, 355 size_t ca_len, 356 const void *ca) 357 { 358 const char *dash; 359 unsigned long long u; 360 struct GNUNET_HashCode h; 361 struct GNUNET_HashCode c; 362 struct GNUNET_TIME_Absolute a; 363 364 dash = strchr (cookie, 365 '-'); 366 if (NULL == dash) 367 return false; 368 dash++; 369 if (1 != 370 sscanf (cookie, 371 "%llu-", 372 &u)) 373 return false; 374 a.abs_value_us = u; 375 if (GNUNET_TIME_absolute_is_past (a)) 376 return false; 377 if (GNUNET_OK != 378 GNUNET_STRINGS_string_to_data (dash, 379 strlen (dash), 380 &c, 381 sizeof (c))) 382 return false; 383 compute_cookie_hash (a, 384 ca_len, 385 ca, 386 &h); 387 return (0 == 388 GNUNET_memcmp (&c, 389 &h)); 390 } 391 392 393 /** 394 * Compute access cookie hash for the given @a expiration and @a ca. 395 * 396 * @param expiration expiration time of the cookie 397 * @param ca_len number of bytes in @a ca 398 * @param ca client address 399 * @param[out] c set to the cookie hash 400 */ 401 static char * 402 compute_cookie (struct GNUNET_TIME_Absolute expiration, 403 size_t ca_len, 404 const void *ca) 405 { 406 struct GNUNET_HashCode h; 407 char *end; 408 char cstr[128]; 409 char *res; 410 411 compute_cookie_hash (expiration, 412 ca_len, 413 ca, 414 &h); 415 end = GNUNET_STRINGS_data_to_string (&h, 416 sizeof (h), 417 cstr, 418 sizeof (cstr)); 419 *end = '\0'; 420 GNUNET_asprintf (&res, 421 "%llu-%s", 422 (unsigned long long) expiration.abs_value_us, 423 cstr); 424 return res; 425 } 426 427 428 /* ********************* Global helpers ****************** */ 429 430 /** 431 * Run MHD now, we have extra data ready for the callback. 432 */ 433 static void 434 run_mhd_now (void); 435 436 437 /* *************** HTTP handling with cURL ***************** */ 438 439 440 /** 441 * Transform _one_ CURL header (gotten from the request) into 442 * MHD format and put it into the response headers list; mostly 443 * copies the headers, but makes special adjustments based on 444 * control requests. 445 * 446 * @param buffer curl buffer with a single 447 * line of header data; not 0-terminated! 448 * @param size curl blocksize 449 * @param nmemb curl blocknumber 450 * @param cls our `struct HttpRequest *` 451 * @return size of processed bytes 452 */ 453 static size_t 454 curl_check_hdr (void *buffer, 455 size_t size, 456 size_t nmemb, 457 void *cls) 458 { 459 struct HttpRequest *hr = cls; 460 struct HttpResponseHeader *header; 461 size_t bytes = size * nmemb; 462 char *ndup; 463 const char *hdr_type; 464 char *hdr_val; 465 char *tok; 466 467 /* Raw line is not guaranteed to be null-terminated. */ 468 ndup = GNUNET_malloc (bytes + 1); 469 memcpy (ndup, 470 buffer, 471 bytes); 472 ndup[bytes] = '\0'; 473 hdr_type = strtok (ndup, ":"); 474 if (NULL == hdr_type) 475 { 476 GNUNET_free (ndup); 477 return bytes; 478 } 479 hdr_val = strtok (NULL, ""); 480 if (NULL == hdr_val) 481 { 482 GNUNET_free (ndup); 483 return bytes; 484 } 485 if (' ' == *hdr_val) 486 hdr_val++; 487 488 /* MHD does not allow certain characters in values, 489 * remove those, plus those could alter strings matching. */ 490 if (NULL != (tok = strchr (hdr_val, '\n'))) 491 *tok = '\0'; 492 if (NULL != (tok = strchr (hdr_val, '\r'))) 493 *tok = '\0'; 494 if (NULL != (tok = strchr (hdr_val, '\t'))) 495 *tok = '\0'; 496 PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n", 497 hdr_type, 498 hdr_val); 499 /* Skip "Content-length:" header as it will be wrong, given 500 that we are man-in-the-middling the connection */ 501 if (0 == strcasecmp (hdr_type, 502 MHD_HTTP_HEADER_CONTENT_LENGTH)) 503 { 504 GNUNET_free (ndup); 505 return bytes; 506 } 507 /* Skip "Connection: Keep-Alive" header, it will be 508 done by MHD if possible */ 509 if ( (0 == strcasecmp (hdr_type, 510 MHD_HTTP_HEADER_CONNECTION)) && 511 (0 == strcasecmp (hdr_val, 512 "Keep-Alive")) ) 513 { 514 GNUNET_free (ndup); 515 return bytes; 516 } 517 if (0 != strlen (hdr_val)) /* Rely in MHD to set those */ 518 { 519 header = GNUNET_new (struct HttpResponseHeader); 520 header->type = GNUNET_strdup (hdr_type); 521 header->value = GNUNET_strdup (hdr_val); 522 GNUNET_CONTAINER_DLL_insert (hr->header_head, 523 hr->header_tail, 524 header); 525 } 526 GNUNET_free (ndup); 527 return bytes; 528 } 529 530 531 /** 532 * Create the MHD response with CURL's as starting base; 533 * mainly set the response code and parses the response into 534 * JSON, if it is such. 535 * 536 * @param hr pointer to where to store the new data. Despite 537 * its name, the struct contains response data as well. 538 * @return #GNUNET_OK if it succeeds. 539 */ 540 static enum GNUNET_GenericReturnValue 541 create_mhd_response_from_hr (struct HttpRequest *hr) 542 { 543 long resp_code; 544 545 if (NULL != hr->response) 546 { 547 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 548 "Response already set!\n"); 549 return GNUNET_SYSERR; 550 } 551 GNUNET_break (CURLE_OK == 552 curl_easy_getinfo (hr->curl, 553 CURLINFO_RESPONSE_CODE, 554 &resp_code)); 555 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 556 "Creating MHD response with code %u\n", 557 (unsigned int) resp_code); 558 hr->response_code = resp_code; 559 if (GNUNET_YES == hr->suspended) 560 { 561 MHD_resume_connection (hr->con); 562 hr->suspended = GNUNET_NO; 563 } 564 run_mhd_now (); 565 return GNUNET_OK; 566 } 567 568 569 /** 570 * Handle response payload data from cURL. 571 * Copies it into our `io_buf` to make it available to MHD. 572 * 573 * @param ptr pointer to the data 574 * @param size number of blocks of data 575 * @param nmemb blocksize 576 * @param ctx our `struct HttpRequest *` 577 * @return number of bytes handled 578 */ 579 static size_t 580 curl_download_cb (void *ptr, 581 size_t size, 582 size_t nmemb, 583 void *ctx) 584 { 585 struct HttpRequest *hr = ctx; 586 size_t total = size * nmemb; 587 588 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 589 "Curl download proceeding\n"); 590 591 if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) 592 { 593 /* Web server started with response before we finished 594 the upload. In this case, current libcurl decides 595 to NOT complete the upload, so we should jump in the 596 state machine to process the download, dropping the 597 rest of the upload. This should only really happen 598 with uploads without "Expect: 100 Continue" and 599 Web servers responding with an error (i.e. upload 600 not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 601 GNUNET_log 602 (GNUNET_ERROR_TYPE_INFO, 603 "Stopping %u byte upload: we are already downloading...\n", 604 (unsigned int) hr->io_len); 605 hr->io_len = 0; 606 } 607 608 if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state) 609 { 610 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 611 "Download callback goes to sleep\n"); 612 hr->curl_paused = GNUNET_YES; 613 return CURL_WRITEFUNC_PAUSE; 614 } 615 GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == 616 hr->state); 617 if (hr->io_size - hr->io_len < total) 618 { 619 GNUNET_assert (total + hr->io_size >= total); 620 GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); 621 GNUNET_array_grow (hr->io_buf, 622 hr->io_size, 623 GNUNET_MAX (total + hr->io_len, 624 hr->io_size * 2 + 1024)); 625 } 626 GNUNET_memcpy (&hr->io_buf[hr->io_len], 627 ptr, 628 total); 629 hr->io_len += total; 630 return total; 631 } 632 633 634 /** 635 * Ask cURL for the select() sets and schedule cURL operations. 636 */ 637 static void 638 curl_download_prepare (void); 639 640 641 /** 642 * cURL callback for uploaded (PUT/POST) data. 643 * Copies from our `io_buf` to make it available to cURL. 644 * 645 * @param buf where to write the data 646 * @param size number of bytes per member 647 * @param nmemb number of members available in @a buf 648 * @param cls our `struct HttpRequest` that generated the data 649 * @return number of bytes copied to @a buf 650 */ 651 static size_t 652 curl_upload_cb (void *buf, 653 size_t size, 654 size_t nmemb, 655 void *cls) 656 { 657 struct HttpRequest *hr = cls; 658 size_t len = size * nmemb; 659 size_t to_copy; 660 661 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 662 "Upload cb is working...\n"); 663 664 if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) || 665 (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) ) 666 { 667 GNUNET_log 668 (GNUNET_ERROR_TYPE_INFO, 669 "Upload cb aborts: we are already downloading...\n"); 670 return CURL_READFUNC_ABORT; 671 } 672 673 if ( (0 == hr->io_len) && 674 (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) ) 675 { 676 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 677 "Pausing CURL UPLOAD, need more data\n"); 678 return CURL_READFUNC_PAUSE; 679 } 680 681 /** 682 * We got rescheduled because the download callback was asleep. 683 * FIXME: can this block be eliminated and the unpausing being 684 * moved in the last block where we return zero as well? 685 */ 686 if ( (0 == hr->io_len) && 687 (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ) 688 { 689 if (GNUNET_YES == hr->curl_paused) 690 { 691 hr->curl_paused = GNUNET_NO; 692 curl_easy_pause (hr->curl, 693 CURLPAUSE_CONT); 694 } 695 curl_download_prepare (); 696 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 697 "Completed CURL UPLOAD\n"); 698 return 0; /* upload finished, can now download */ 699 } 700 to_copy = GNUNET_MIN (hr->io_len, 701 len); 702 GNUNET_memcpy (buf, 703 hr->io_buf, 704 to_copy); 705 /* shift remaining data back to the beginning of the buffer. */ 706 memmove (hr->io_buf, 707 &hr->io_buf[to_copy], 708 hr->io_len - to_copy); 709 hr->io_len -= to_copy; 710 if (0 == hr->io_len) 711 { 712 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 713 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 714 "Completed CURL UPLOAD\n"); 715 } 716 return to_copy; 717 } 718 719 720 /* ************** helper functions ************* */ 721 722 /** 723 * Extract the hostname from a complete URL. 724 * 725 * @param url full fledged URL 726 * @return pointer to the 0-terminated hostname, to be freed 727 * by the caller. 728 */ 729 static char * 730 build_host_header (const char *url) 731 { 732 #define MARKER "://" 733 734 char *header; 735 char *end; 736 char *hostname; 737 char *dup = GNUNET_strdup (url); 738 739 hostname = strstr (dup, 740 MARKER); 741 hostname += 3; 742 end = strchrnul (hostname, '/'); 743 *end = '\0'; 744 GNUNET_asprintf (&header, 745 "Host: %s", 746 hostname); 747 GNUNET_free (dup); 748 return header; 749 } 750 751 752 /* ************** main loop of cURL interaction ************* */ 753 754 755 /** 756 * Task that is run when we are ready to receive more data 757 * from curl 758 * 759 * @param cls closure 760 */ 761 static void 762 curl_task_download (void *cls); 763 764 765 /** 766 * Ask cURL for the select() sets and schedule cURL operations. 767 */ 768 static void 769 curl_download_prepare () 770 { 771 CURLMcode mret; 772 fd_set rs; 773 fd_set ws; 774 fd_set es; 775 int max; 776 struct GNUNET_NETWORK_FDSet *grs; 777 struct GNUNET_NETWORK_FDSet *gws; 778 long to; 779 struct GNUNET_TIME_Relative rtime; 780 781 if (NULL != curl_download_task) 782 { 783 GNUNET_SCHEDULER_cancel (curl_download_task); 784 curl_download_task = NULL; 785 } 786 max = -1; 787 FD_ZERO (&rs); 788 FD_ZERO (&ws); 789 FD_ZERO (&es); 790 if (CURLM_OK != (mret = curl_multi_fdset (curl_multi, 791 &rs, 792 &ws, 793 &es, 794 &max))) 795 { 796 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 797 "%s failed at %s:%d: `%s'\n", 798 "curl_multi_fdset", 799 __FILE__, 800 __LINE__, 801 curl_multi_strerror (mret)); 802 return; 803 } 804 to = -1; 805 GNUNET_break (CURLM_OK == 806 curl_multi_timeout (curl_multi, 807 &to)); 808 if (-1 == to) 809 rtime = GNUNET_TIME_UNIT_FOREVER_REL; 810 else 811 rtime = GNUNET_TIME_relative_multiply 812 (GNUNET_TIME_UNIT_MILLISECONDS, to); 813 if (-1 != max) 814 { 815 grs = GNUNET_NETWORK_fdset_create (); 816 gws = GNUNET_NETWORK_fdset_create (); 817 GNUNET_NETWORK_fdset_copy_native (grs, 818 &rs, 819 max + 1); 820 GNUNET_NETWORK_fdset_copy_native (gws, 821 &ws, 822 max + 1); 823 curl_download_task 824 = GNUNET_SCHEDULER_add_select ( 825 GNUNET_SCHEDULER_PRIORITY_DEFAULT, 826 rtime, 827 grs, gws, 828 &curl_task_download, 829 curl_multi); 830 GNUNET_NETWORK_fdset_destroy (gws); 831 GNUNET_NETWORK_fdset_destroy (grs); 832 } 833 else 834 { 835 curl_download_task = GNUNET_SCHEDULER_add_delayed 836 (rtime, 837 &curl_task_download, 838 curl_multi); 839 } 840 } 841 842 843 /** 844 * "Filter" function that translates MHD request headers to 845 * cURL's. 846 * 847 * @param cls our `struct HttpRequest` 848 * @param kind value kind 849 * @param key field key 850 * @param value field value 851 * @return #MHD_YES to continue to iterate 852 */ 853 static MHD_RESULT 854 con_val_iter (void *cls, 855 enum MHD_ValueKind kind, 856 const char *key, 857 const char *value) 858 { 859 struct HttpRequest *hr = cls; 860 char *hdr; 861 char *new_value = NULL; 862 863 (void) kind; 864 if (0 == strcmp (MHD_HTTP_HEADER_HOST, 865 key)) 866 { 867 /* We don't take the host header as given in the request. 868 * We'll instead put the proxied service's hostname in it*/ 869 return MHD_YES; 870 } 871 if ((0 == strcmp (MHD_HTTP_HEADER_CONTENT_LENGTH, 872 key))) 873 { 874 PAIVANA_LOG_INFO ( 875 "Do not set Content-Length for request\n"); 876 return MHD_YES; 877 } 878 GNUNET_asprintf (&hdr, 879 "%s: %s", 880 key, 881 value); 882 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 883 "Adding header `%s' to HTTP request\n", 884 hdr); 885 hr->headers = curl_slist_append (hr->headers, 886 hdr); 887 GNUNET_free (hdr); 888 GNUNET_free (new_value); 889 return MHD_YES; 890 } 891 892 893 /** 894 * Task that is run when we are ready to receive 895 * more data from curl. 896 * 897 * @param cls closure, usually NULL. 898 */ 899 static void 900 curl_task_download (void *cls) 901 { 902 int running; 903 int msgnum; 904 struct CURLMsg *msg; 905 CURLMcode mret; 906 struct HttpRequest *hr; 907 908 (void) cls; 909 curl_download_task = NULL; 910 do 911 { 912 running = 0; 913 mret = curl_multi_perform (curl_multi, 914 &running); 915 while (NULL != (msg = curl_multi_info_read (curl_multi, 916 &msgnum))) 917 { 918 GNUNET_break 919 (CURLE_OK == curl_easy_getinfo 920 (msg->easy_handle, 921 CURLINFO_PRIVATE, 922 (char **) &hr)); 923 924 if (NULL == hr) 925 { 926 GNUNET_break (0); 927 continue; 928 } 929 switch (msg->msg) 930 { 931 case CURLMSG_NONE: 932 /* documentation says this is not used */ 933 GNUNET_break (0); 934 break; 935 case CURLMSG_DONE: 936 switch (msg->data.result) 937 { 938 case CURLE_OK: 939 case CURLE_GOT_NOTHING: 940 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 941 "CURL download completed.\n"); 942 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; 943 if (NULL == hr->response) 944 GNUNET_assert (GNUNET_OK == 945 create_mhd_response_from_hr (hr)); 946 break; 947 default: 948 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 949 "Download curl failed: %s\n", 950 curl_easy_strerror (msg->data.result)); 951 /* FIXME: indicate error somehow? 952 * close MHD connection badly as well? */ 953 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; 954 if (GNUNET_YES == hr->suspended) 955 { 956 MHD_resume_connection (hr->con); 957 hr->suspended = GNUNET_NO; 958 } 959 run_mhd_now (); 960 break; 961 } 962 if (NULL == hr->response) 963 hr->response = curl_failure_response; 964 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 965 "Curl request for `%s' finished (got the response)\n", 966 hr->url); 967 run_mhd_now (); 968 break; 969 case CURLMSG_LAST: 970 /* documentation says this is not used */ 971 GNUNET_break (0); 972 break; 973 default: 974 /* unexpected status code */ 975 GNUNET_break (0); 976 break; 977 } 978 } 979 } while (mret == CURLM_CALL_MULTI_PERFORM); 980 if (CURLM_OK != mret) 981 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 982 "%s failed at %s:%d: `%s'\n", 983 "curl_multi_perform", 984 __FILE__, 985 __LINE__, 986 curl_multi_strerror (mret)); 987 if (0 == running) 988 { 989 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 990 "Suspending cURL multi loop," 991 " no more events pending\n"); 992 return; /* nothing more in progress */ 993 } 994 curl_download_prepare (); 995 } 996 997 998 /* *************** MHD response generation ***************** */ 999 1000 1001 /** 1002 * Main MHD callback for handling requests. 1003 * 1004 * @param cls unused 1005 * @param con MHD connection handle 1006 * @param url the url in the request 1007 * @param meth the HTTP method used ("GET", "PUT", etc.) 1008 * @param ver the HTTP version string (i.e. "HTTP/1.1") 1009 * @param upload_data the data being uploaded (excluding HEADERS, 1010 * for a POST that fits into memory and that is encoded 1011 * with a supported encoding, the POST data will NOT be 1012 * given in upload_data and is instead available as 1013 * part of MHD_get_connection_values; very large POST 1014 * data *will* be made available incrementally in 1015 * upload_data) 1016 * @param upload_data_size set initially to the size of the 1017 * @a upload_data provided; the method must update this 1018 * value to the number of bytes NOT processed; 1019 * @param con_cls pointer to location where we store the 1020 * 'struct Request' 1021 * @return #MHD_YES if the connection was handled successfully, 1022 * #MHD_NO if the socket must be closed due to a serious 1023 * error while handling the request 1024 */ 1025 static MHD_RESULT 1026 create_response (void *cls, 1027 struct MHD_Connection *con, 1028 const char *url, 1029 const char *meth, 1030 const char *ver, 1031 const char *upload_data, 1032 size_t *upload_data_size, 1033 void **con_cls) 1034 { 1035 struct HttpRequest *hr = *con_cls; 1036 1037 (void) cls; 1038 // FIXME: check if url is one that we reverse proxy! 1039 1040 (void) url; 1041 1042 if (NULL == hr) 1043 { 1044 GNUNET_break (0); 1045 return MHD_NO; 1046 } 1047 1048 if (REQUEST_STATE_WITH_MHD == hr->state) 1049 { 1050 const char *cookie; 1051 bool ok = (0 != no_check); 1052 1053 cookie = MHD_lookup_connection_value (con, 1054 MHD_COOKIE_KIND, 1055 "Paivana-Cookie"); 1056 if (NULL != cookie) 1057 { 1058 const union MHD_ConnectionInfo *ci; 1059 const struct sockaddr *ca; 1060 socklen_t ca_len; 1061 1062 ci = MHD_get_connection_info (con, 1063 MHD_CONNECTION_INFO_CLIENT_ADDRESS); 1064 GNUNET_assert (NULL != ci); 1065 ca = ci->client_addr; 1066 switch (ca->sa_family) 1067 { 1068 case AF_INET: 1069 ca_len = sizeof (struct sockaddr_in); 1070 break; 1071 case AF_INET6: 1072 ca_len = sizeof (struct sockaddr_in6); 1073 break; 1074 default: 1075 GNUNET_break (0); 1076 ca_len = 0; 1077 break; 1078 } 1079 ok = check_cookie (cookie, 1080 ca_len, 1081 ca); 1082 } 1083 if (! ok) 1084 { 1085 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1086 "Request denied\n"); 1087 return MHD_queue_response (con, 1088 MHD_HTTP_PAYMENT_REQUIRED, 1089 paywall); 1090 } 1091 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1092 "Request ok!\n"); 1093 hr->state = REQUEST_STATE_CLIENT_UPLOAD_STARTED; 1094 /* TODO: hacks for 100 continue suppression would go here! */ 1095 return MHD_YES; 1096 } 1097 1098 // FIXME: move vanilla reverse proxy logic to another file! 1099 1100 /* continuing to process request */ 1101 if (0 != *upload_data_size) 1102 { 1103 GNUNET_assert 1104 (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state); 1105 1106 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1107 "Processing %u bytes UPLOAD\n", 1108 (unsigned int) *upload_data_size); 1109 1110 /* Grow the buffer if remaining space isn't enough. */ 1111 if (hr->io_size - hr->io_len < *upload_data_size) 1112 { 1113 /* How can this assertion be false? */ 1114 GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); 1115 /* This asserts that upload_data_size > 0, ? */ 1116 GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len); 1117 1118 GNUNET_array_grow (hr->io_buf, 1119 hr->io_size, 1120 GNUNET_MAX 1121 (hr->io_size * 2 + 1024, 1122 *upload_data_size + hr->io_len)); 1123 } 1124 1125 /* Finally copy upload data. */ 1126 GNUNET_memcpy (&hr->io_buf[hr->io_len], 1127 upload_data, 1128 *upload_data_size); 1129 1130 hr->io_len += *upload_data_size; 1131 *upload_data_size = 0; 1132 1133 return MHD_YES; 1134 } 1135 1136 /* Upload (*from the client*) finished or just a without-body 1137 * request. */ 1138 if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state) 1139 { 1140 hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE; 1141 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1142 "Finished processing UPLOAD\n"); 1143 } 1144 1145 /* generate curl request to the proxied service. */ 1146 if (NULL == hr->curl) 1147 { 1148 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1149 "Generating curl request\n"); 1150 hr->curl = curl_easy_init (); 1151 if (NULL == hr->curl) 1152 { 1153 PAIVANA_LOG_ERROR ("Could not init the curl handle\n"); 1154 return MHD_queue_response (con, 1155 MHD_HTTP_INTERNAL_SERVER_ERROR, 1156 curl_failure_response); 1157 } 1158 1159 /* No need to check whether we're POSTing or PUTting. 1160 * If not needed, one of the following values will be 1161 * ignored.*/ 1162 curl_easy_setopt (hr->curl, 1163 CURLOPT_POSTFIELDSIZE, 1164 hr->io_len); 1165 curl_easy_setopt (hr->curl, 1166 CURLOPT_INFILESIZE, 1167 hr->io_len); 1168 curl_easy_setopt (hr->curl, 1169 CURLOPT_HEADERFUNCTION, 1170 &curl_check_hdr); 1171 curl_easy_setopt (hr->curl, 1172 CURLOPT_HEADERDATA, 1173 hr); 1174 curl_easy_setopt (hr->curl, 1175 CURLOPT_FOLLOWLOCATION, 1176 0); 1177 curl_easy_setopt (hr->curl, 1178 CURLOPT_CONNECTTIMEOUT, 1179 60L); 1180 curl_easy_setopt (hr->curl, 1181 CURLOPT_TIMEOUT, 1182 60L); 1183 curl_easy_setopt (hr->curl, 1184 CURLOPT_NOSIGNAL, 1185 1L); 1186 curl_easy_setopt (hr->curl, 1187 CURLOPT_PRIVATE, 1188 hr); 1189 curl_easy_setopt (hr->curl, 1190 CURLOPT_VERBOSE, 1191 0); 1192 1193 curl_easy_setopt (hr->curl, 1194 CURLOPT_READFUNCTION, 1195 &curl_upload_cb); 1196 curl_easy_setopt (hr->curl, 1197 CURLOPT_READDATA, 1198 hr); 1199 1200 curl_easy_setopt (hr->curl, 1201 CURLOPT_WRITEFUNCTION, 1202 &curl_download_cb); 1203 curl_easy_setopt (hr->curl, 1204 CURLOPT_WRITEDATA, 1205 hr); 1206 { 1207 char *curlurl; 1208 char *host_hdr; 1209 1210 GNUNET_asprintf (&curlurl, 1211 "%s%s", 1212 target_server_base_url, 1213 hr->url); 1214 curl_easy_setopt (hr->curl, 1215 CURLOPT_URL, 1216 curlurl); 1217 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1218 "Forwarding request to: %s\n", 1219 curlurl); 1220 GNUNET_free (curlurl); 1221 1222 host_hdr = build_host_header (target_server_base_url); 1223 PAIVANA_LOG_DEBUG ("Faking the host header, %s\n", 1224 host_hdr); 1225 hr->headers = curl_slist_append (hr->headers, 1226 host_hdr); 1227 GNUNET_free (host_hdr); 1228 } 1229 1230 // FIXME: support PATCH, etc. 1231 if (0 == strcasecmp (meth, 1232 MHD_HTTP_METHOD_PUT)) 1233 { 1234 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1235 "Crafting a CURL PUT request\n"); 1236 1237 curl_easy_setopt (hr->curl, 1238 CURLOPT_UPLOAD, 1239 1L); 1240 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1241 } 1242 else if (0 == strcasecmp (meth, 1243 MHD_HTTP_METHOD_POST)) 1244 { 1245 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1246 "Crafting a CURL POST request\n"); 1247 curl_easy_setopt (hr->curl, 1248 CURLOPT_POST, 1249 1L); 1250 curl_easy_setopt (hr->curl, 1251 CURLOPT_VERBOSE, 1252 1L); 1253 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1254 } 1255 else if (0 == strcasecmp (meth, 1256 MHD_HTTP_METHOD_HEAD)) 1257 { 1258 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1259 curl_easy_setopt (hr->curl, 1260 CURLOPT_NOBODY, 1261 1L); 1262 } 1263 else if (0 == strcasecmp (meth, 1264 MHD_HTTP_METHOD_OPTIONS)) 1265 { 1266 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1267 curl_easy_setopt (hr->curl, 1268 CURLOPT_CUSTOMREQUEST, 1269 "OPTIONS"); 1270 } 1271 else if (0 == strcasecmp (meth, 1272 MHD_HTTP_METHOD_GET)) 1273 { 1274 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1275 curl_easy_setopt (hr->curl, 1276 CURLOPT_HTTPGET, 1277 1L); 1278 } 1279 else 1280 { 1281 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1282 "Unsupported HTTP method `%s'\n", 1283 meth); 1284 curl_easy_cleanup (hr->curl); 1285 hr->curl = NULL; 1286 return MHD_NO; 1287 } 1288 1289 if (CURLM_OK != 1290 curl_multi_add_handle (curl_multi, 1291 hr->curl)) 1292 { 1293 GNUNET_break (0); 1294 curl_easy_cleanup (hr->curl); 1295 hr->curl = NULL; 1296 return MHD_NO; 1297 } 1298 1299 MHD_get_connection_values (con, 1300 MHD_HEADER_KIND, 1301 &con_val_iter, 1302 hr); 1303 1304 curl_easy_setopt (hr->curl, 1305 CURLOPT_HTTPHEADER, 1306 hr->headers); 1307 curl_download_prepare (); 1308 1309 return MHD_YES; 1310 } 1311 1312 if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state) 1313 { 1314 GNUNET_assert (GNUNET_NO == hr->suspended); 1315 MHD_suspend_connection (con); 1316 hr->suspended = GNUNET_YES; 1317 return MHD_YES; /* wait for curl */ 1318 } 1319 1320 GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state); 1321 1322 hr->response 1323 = MHD_create_response_from_buffer_copy (hr->io_len, 1324 hr->io_buf); 1325 for (struct HttpResponseHeader *header = hr->header_head; 1326 NULL != header; 1327 header = header->next) 1328 { 1329 const char *value = header->value; 1330 1331 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1332 "Adding MHD response header %s->%s\n", 1333 header->type, 1334 value); 1335 GNUNET_break (MHD_YES == 1336 MHD_add_response_header (hr->response, 1337 header->type, 1338 value)); 1339 } 1340 run_mhd_now (); 1341 1342 return MHD_queue_response (con, 1343 hr->response_code, 1344 hr->response); 1345 } 1346 1347 1348 /* ************ MHD HTTP setup and event loop *************** */ 1349 1350 1351 /** 1352 * Function called when MHD decides that we 1353 * are done with a request. 1354 * 1355 * @param cls NULL 1356 * @param connection connection handle 1357 * @param con_cls value as set by the last call to 1358 * the MHD_AccessHandlerCallback, should be 1359 * our `struct HttpRequest *` (set by `create_response()`) 1360 * @param toe reason for request termination (ignored) 1361 */ 1362 static void 1363 mhd_completed_cb (void *cls, 1364 struct MHD_Connection *connection, 1365 void **con_cls, 1366 enum MHD_RequestTerminationCode toe) 1367 { 1368 struct HttpRequest *hr = *con_cls; 1369 struct HttpResponseHeader *header; 1370 1371 (void) cls; 1372 (void) connection; 1373 if (NULL == hr) 1374 return; 1375 if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) 1376 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1377 "MHD encountered error handling request: %d\n", 1378 toe); 1379 if (NULL != hr->curl) 1380 { 1381 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1382 "Resetting cURL handle\n"); 1383 curl_multi_remove_handle (curl_multi, 1384 hr->curl); 1385 curl_easy_cleanup (hr->curl); 1386 hr->curl = NULL; 1387 hr->io_len = 0; 1388 } 1389 if (NULL != hr->headers) 1390 { 1391 curl_slist_free_all (hr->headers); 1392 hr->headers = NULL; 1393 } 1394 if ( (NULL != hr->response) && 1395 (curl_failure_response != hr->response) ) 1396 /* Destroy non-error responses... (?) */ 1397 MHD_destroy_response (hr->response); 1398 1399 for (header = hr->header_head; 1400 header != NULL; 1401 header = hr->header_head) 1402 { 1403 GNUNET_CONTAINER_DLL_remove (hr->header_head, 1404 hr->header_tail, 1405 header); 1406 GNUNET_free (header->type); 1407 GNUNET_free (header->value); 1408 GNUNET_free (header); 1409 } 1410 1411 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1412 "Proxying of '%s' completely done\n", 1413 hr->url); 1414 1415 GNUNET_free (hr->url); 1416 GNUNET_free (hr->io_buf); 1417 GNUNET_CONTAINER_DLL_remove (hr_head, 1418 hr_tail, 1419 hr); 1420 GNUNET_free (hr); 1421 *con_cls = NULL; 1422 } 1423 1424 1425 /** 1426 * Function called when MHD first processes an incoming connection. 1427 * Gives us the respective URI information. 1428 * 1429 * We use this to associate the `struct MHD_Connection` with our 1430 * internal `struct HttpRequest` data structure (by checking 1431 * for matching sockets). 1432 * 1433 * @param cls the HTTP server handle (a `struct MhdHttpList`) 1434 * @param url the URL that is being requested 1435 * @param connection MHD connection object for the request 1436 * @return the `struct HttpRequest` that this @a connection is for 1437 */ 1438 static void * 1439 mhd_log_callback (void *cls, 1440 const char *url, 1441 struct MHD_Connection *connection) 1442 { 1443 struct HttpRequest *hr; 1444 const union MHD_ConnectionInfo *ci; 1445 1446 (void) cls; 1447 ci = MHD_get_connection_info (connection, 1448 MHD_CONNECTION_INFO_SOCKET_CONTEXT); 1449 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1450 "Processing %s\n", 1451 url); 1452 if (NULL == ci) 1453 { 1454 GNUNET_break (0); 1455 return NULL; 1456 } 1457 1458 hr = GNUNET_new (struct HttpRequest); 1459 hr->con = connection; 1460 hr->url = GNUNET_strdup (url); 1461 GNUNET_CONTAINER_DLL_insert (hr_head, 1462 hr_tail, 1463 hr); 1464 return hr; 1465 } 1466 1467 1468 /** 1469 * Kill the MHD daemon. 1470 */ 1471 static void 1472 kill_httpd (void) 1473 { 1474 MHD_stop_daemon (mhd_daemon); 1475 mhd_daemon = NULL; 1476 if (NULL != httpd_task) 1477 { 1478 GNUNET_SCHEDULER_cancel (httpd_task); 1479 httpd_task = NULL; 1480 } 1481 } 1482 1483 1484 /** 1485 * Task run whenever HTTP server operations are pending. 1486 * 1487 * @param cls the `struct MhdHttpList *` 1488 * of the daemon that is being run 1489 */ 1490 static void 1491 do_httpd (void *cls); 1492 1493 1494 /** 1495 * Schedule MHD. This function should be called initially when an 1496 * MHD is first getting its client socket, and will then 1497 * automatically always be called later whenever there is work to 1498 * be done. 1499 */ 1500 static void 1501 schedule_httpd (void) 1502 { 1503 fd_set rs; 1504 fd_set ws; 1505 fd_set es; 1506 struct GNUNET_NETWORK_FDSet *wrs; 1507 struct GNUNET_NETWORK_FDSet *wws; 1508 int max; 1509 int haveto; 1510 MHD_UNSIGNED_LONG_LONG timeout; 1511 struct GNUNET_TIME_Relative tv; 1512 1513 FD_ZERO (&rs); 1514 FD_ZERO (&ws); 1515 FD_ZERO (&es); 1516 max = -1; 1517 if (MHD_YES != 1518 MHD_get_fdset (mhd_daemon, 1519 &rs, 1520 &ws, 1521 &es, 1522 &max)) 1523 { 1524 kill_httpd (); 1525 return; 1526 } 1527 haveto = MHD_get_timeout (mhd_daemon, 1528 &timeout); 1529 if (MHD_YES == haveto) 1530 tv.rel_value_us = (uint64_t) timeout * 1000LL; 1531 else 1532 tv = GNUNET_TIME_UNIT_FOREVER_REL; 1533 if (-1 != max) 1534 { 1535 wrs = GNUNET_NETWORK_fdset_create (); 1536 wws = GNUNET_NETWORK_fdset_create (); 1537 GNUNET_NETWORK_fdset_copy_native (wrs, 1538 &rs, 1539 max + 1); 1540 GNUNET_NETWORK_fdset_copy_native (wws, 1541 &ws, 1542 max + 1); 1543 } 1544 else 1545 { 1546 wrs = NULL; 1547 wws = NULL; 1548 } 1549 if (NULL != httpd_task) 1550 { 1551 GNUNET_SCHEDULER_cancel (httpd_task); 1552 httpd_task = NULL; 1553 } 1554 httpd_task = 1555 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, 1556 tv, 1557 wrs, 1558 wws, 1559 &do_httpd, 1560 NULL); 1561 if (NULL != wrs) 1562 GNUNET_NETWORK_fdset_destroy (wrs); 1563 if (NULL != wws) 1564 GNUNET_NETWORK_fdset_destroy (wws); 1565 } 1566 1567 1568 /** 1569 * Task run whenever HTTP server operations are pending. 1570 * 1571 * @param cls NULL 1572 */ 1573 static void 1574 do_httpd (void *cls) 1575 { 1576 (void) cls; 1577 httpd_task = NULL; 1578 MHD_run (mhd_daemon); 1579 schedule_httpd (); 1580 } 1581 1582 1583 /** 1584 * Run MHD now, we have extra data ready for the callback. 1585 */ 1586 static void 1587 run_mhd_now (void) 1588 { 1589 if (NULL != httpd_task) 1590 GNUNET_SCHEDULER_cancel (httpd_task); 1591 httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, 1592 NULL); 1593 } 1594 1595 1596 /* *************** General / main code *************** */ 1597 1598 1599 /** 1600 * Task run on shutdown 1601 * 1602 * @param cls closure 1603 */ 1604 static void 1605 do_shutdown (void *cls) 1606 { 1607 (void) cls; 1608 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1609 "Shutting down...\n"); 1610 /* MHD requires resuming before destroying the daemons */ 1611 for (struct HttpRequest *hr = hr_head; 1612 NULL != hr; 1613 hr = hr->next) 1614 { 1615 if (GNUNET_YES == hr->suspended) 1616 { 1617 hr->suspended = GNUNET_NO; 1618 MHD_resume_connection (hr->con); 1619 } 1620 } 1621 kill_httpd (); 1622 if (NULL != curl_multi) 1623 { 1624 curl_multi_cleanup (curl_multi); 1625 curl_multi = NULL; 1626 } 1627 if (NULL != curl_download_task) 1628 { 1629 GNUNET_SCHEDULER_cancel (curl_download_task); 1630 curl_download_task = NULL; 1631 } 1632 GNUNET_free (target_server_base_url); 1633 } 1634 1635 1636 /** 1637 * Connect to a unix domain socket. 1638 * 1639 * @param path the IPC path 1640 * @param mode the IPC path mode 1641 * @return the file descriptor of the connection. 1642 */ 1643 static int 1644 open_unix_path (const char *path, 1645 mode_t mode) 1646 { 1647 1648 struct GNUNET_NETWORK_Handle *nh; 1649 struct sockaddr_un *un; 1650 int fd; 1651 1652 if (sizeof (un->sun_path) <= strlen (path)) 1653 { 1654 fprintf (stderr, 1655 "path `%s' too long\n", 1656 path); 1657 return -1; 1658 } 1659 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1660 "Creating listen socket '%s' with mode %o\n", 1661 path, 1662 mode); 1663 1664 if (GNUNET_OK != 1665 GNUNET_DISK_directory_create_for_file (path)) 1666 { 1667 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 1668 "mkdir", 1669 path); 1670 } 1671 1672 un = GNUNET_new (struct sockaddr_un); 1673 un->sun_family = AF_UNIX; 1674 1675 strncpy (un->sun_path, 1676 path, 1677 sizeof (un->sun_path) - 1); 1678 GNUNET_NETWORK_unix_precheck (un); 1679 1680 if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX, 1681 SOCK_STREAM, 1682 0))) 1683 { 1684 fprintf (stderr, 1685 "create failed for AF_UNIX\n"); 1686 GNUNET_free (un); 1687 return -1; 1688 } 1689 if (GNUNET_OK != 1690 GNUNET_NETWORK_socket_bind (nh, 1691 (void *) un, 1692 sizeof (struct sockaddr_un))) 1693 { 1694 fprintf (stderr, 1695 "bind failed for AF_UNIX\n"); 1696 GNUNET_free (un); 1697 GNUNET_NETWORK_socket_close (nh); 1698 return -1; 1699 } 1700 GNUNET_free (un); 1701 if (GNUNET_OK != 1702 GNUNET_NETWORK_socket_listen (nh, 1703 UNIX_BACKLOG)) 1704 { 1705 fprintf (stderr, 1706 "listen failed for AF_UNIX\n"); 1707 GNUNET_NETWORK_socket_close (nh); 1708 return -1; 1709 } 1710 1711 if (0 != chmod (path, 1712 mode)) 1713 { 1714 fprintf (stderr, 1715 "chmod failed: %s\n", 1716 strerror (errno)); 1717 GNUNET_NETWORK_socket_close (nh); 1718 return -1; 1719 } 1720 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1721 "set socket '%s' to mode %o\n", 1722 path, 1723 mode); 1724 fd = GNUNET_NETWORK_get_fd (nh); 1725 GNUNET_NETWORK_socket_free_memory_only_ (nh); 1726 return fd; 1727 } 1728 1729 1730 /** 1731 * Crawl the configuration file and extracts the serving 1732 * method, TCP vs IPC, and the respective details (port/path/mode) 1733 * 1734 * @param ccfg configuration handle. 1735 * @param port[out] port number to use 1736 * @param path[out] unix path for IPC. 1737 * @param mode[out] mode string for @a path. 1738 * @return #GNUNET_SYSERR if the parsing didn't succeed. 1739 */ 1740 // FIXME: replace by helper function of Taler? 1741 static enum GNUNET_GenericReturnValue 1742 parse_serving_mean (const struct GNUNET_CONFIGURATION_Handle *ccfg, 1743 uint16_t *port, 1744 char **path, 1745 mode_t *mode) 1746 { 1747 1748 const char *serve; 1749 const char *choices[] = {"tcp", "unix", NULL}; 1750 char *modestring; 1751 unsigned long long port_ull; 1752 1753 if (GNUNET_OK != 1754 GNUNET_CONFIGURATION_get_value_choice (ccfg, 1755 "paivana", 1756 "SERVE", 1757 choices, 1758 &serve)) 1759 { 1760 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1761 "paivana", 1762 "SERVE"); 1763 return GNUNET_SYSERR; 1764 } 1765 1766 if (0 == strcmp ("tcp", serve)) 1767 { 1768 *path = NULL; 1769 1770 if (GNUNET_OK != 1771 GNUNET_CONFIGURATION_get_value_number (ccfg, 1772 "paivana", 1773 "HTTP_PORT", 1774 &port_ull)) 1775 { 1776 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1777 "paivana", 1778 "HTTP_PORT"); 1779 return GNUNET_SYSERR; 1780 } 1781 *port = (uint16_t) port_ull; 1782 return GNUNET_OK; 1783 } 1784 1785 /* serving via unix */ 1786 1787 if (GNUNET_OK != 1788 GNUNET_CONFIGURATION_get_value_filename (ccfg, 1789 "paivana", 1790 "SERVE_UNIXPATH", 1791 path)) 1792 { 1793 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1794 "paivana", 1795 "SERVE_UNIXPATH"); 1796 return GNUNET_SYSERR; 1797 } 1798 1799 if (GNUNET_OK != 1800 GNUNET_CONFIGURATION_get_value_string (ccfg, 1801 "paivana", 1802 "SERVE_UNIXMODE", 1803 &modestring)) 1804 { 1805 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1806 "paivana", 1807 "SERVE_UNIXMODE"); 1808 return GNUNET_SYSERR; 1809 } 1810 1811 errno = 0; 1812 *mode = (mode_t) strtoul (modestring, NULL, 8); 1813 1814 if (0 != errno) 1815 { 1816 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1817 "paivana", 1818 "SERVE_UNIXMODE", 1819 "must be octal number"); 1820 GNUNET_free (modestring); 1821 return GNUNET_SYSERR; 1822 } 1823 GNUNET_free (modestring); 1824 return GNUNET_OK; 1825 } 1826 1827 1828 /** 1829 * Try to initialize the paywall response. 1830 */ 1831 static bool 1832 load_paywall () 1833 { 1834 char *tpath; 1835 char *fn; 1836 int fd; 1837 struct stat sb; 1838 1839 tpath = GNUNET_OS_installation_get_path (PAIVANA_project_data (), 1840 GNUNET_OS_IPK_DATADIR); 1841 GNUNET_asprintf (&fn, 1842 "%s/paywall.html", 1843 tpath); 1844 GNUNET_free (tpath); 1845 fd = open (fn, 1846 O_RDONLY); 1847 if (-1 == fd) 1848 { 1849 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 1850 "open", 1851 fn); 1852 GNUNET_free (fn); 1853 return false; 1854 } 1855 if (0 != 1856 fstat (fd, 1857 &sb)) 1858 { 1859 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 1860 "stat", 1861 fn); 1862 GNUNET_free (fn); 1863 GNUNET_break (0 == close (fd)); 1864 return false; 1865 } 1866 GNUNET_free (fn); 1867 paywall = MHD_create_response_from_fd (sb.st_size, 1868 fd); 1869 if (NULL == paywall) 1870 return false; 1871 GNUNET_break (MHD_YES == 1872 MHD_add_response_header (paywall, 1873 MHD_HTTP_HEADER_CONTENT_TYPE, 1874 "text/html")); 1875 return true; 1876 } 1877 1878 1879 /** 1880 * Main function that will be run. Main tasks are (1) init. the 1881 * curl infrastructure (curl_global_init() / curl_multi_init()), 1882 * then fetch the HTTP port where its Web service should listen at, 1883 * and finally start MHD on that port. 1884 * 1885 * @param cls closure 1886 * @param args remaining command-line arguments 1887 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1888 * @param c configuration 1889 */ 1890 static void 1891 run (void *cls, 1892 char *const *args, 1893 const char *cfgfile, 1894 const struct GNUNET_CONFIGURATION_Handle *c) 1895 { 1896 uint16_t port = 0; 1897 int fh = -1; 1898 char *serve_unixpath = NULL; 1899 mode_t serve_unixmode = 0; 1900 char *secret; 1901 1902 (void) cls; 1903 (void) args; 1904 (void) cfgfile; 1905 cfg = c; 1906 // FIXME: initialize database logic! 1907 1908 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 1909 { 1910 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1911 "cURL global init failed!\n"); 1912 GNUNET_SCHEDULER_shutdown (); 1913 return; 1914 } 1915 if (! load_paywall ()) 1916 { 1917 GNUNET_SCHEDULER_shutdown (); 1918 return; 1919 } 1920 if (NULL == (curl_multi = curl_multi_init ())) 1921 { 1922 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1923 "Failed to create cURL multi handle!\n"); 1924 return; 1925 } 1926 1927 /* No need to check return value. If given, we take, 1928 * otherwise it stays zero. */ 1929 if (GNUNET_OK != 1930 GNUNET_CONFIGURATION_get_value_string ( 1931 c, 1932 "paivana", 1933 "DESTINATION_BASE_URL", 1934 &target_server_base_url)) 1935 { 1936 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1937 "paivana", 1938 "DESTINATION_BASE_URL"); 1939 GNUNET_SCHEDULER_shutdown (); 1940 return; 1941 } 1942 if (GNUNET_OK != 1943 GNUNET_CONFIGURATION_get_value_string ( 1944 c, 1945 "paivana", 1946 "MERCHANT_BACKEND_URL", 1947 &merchant_base_url)) 1948 { 1949 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1950 "paivana", 1951 "MERCHANT_BACKEND_URL"); 1952 GNUNET_SCHEDULER_shutdown (); 1953 return; 1954 } 1955 if (GNUNET_OK != 1956 GNUNET_CONFIGURATION_get_value_string ( 1957 c, 1958 "paivana", 1959 "MERCHANT_ACCESS_TOKEN", 1960 &merchant_access_token)) 1961 { 1962 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1963 "paivana", 1964 "MERCHANT_ACCESS_TOKEN"); 1965 GNUNET_SCHEDULER_shutdown (); 1966 return; 1967 } 1968 if (GNUNET_OK != 1969 GNUNET_CONFIGURATION_get_value_string ( 1970 c, 1971 "paivana", 1972 "SECRET", 1973 &secret)) 1974 { 1975 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 1976 "paivana", 1977 "SECRET"); 1978 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 1979 &paivana_secret, 1980 sizeof (paivana_secret)); 1981 } 1982 else 1983 { 1984 GNUNET_CRYPTO_hash (secret, 1985 strlen (secret), 1986 &paivana_secret); 1987 GNUNET_free (secret); 1988 } 1989 1990 if (GNUNET_SYSERR == 1991 parse_serving_mean (c, 1992 &port, 1993 &serve_unixpath, 1994 &serve_unixmode)) 1995 { 1996 GNUNET_break (0); 1997 GNUNET_SCHEDULER_shutdown (); 1998 return; 1999 } 2000 2001 if (NULL != serve_unixpath) 2002 { 2003 /* Connect the 'fh' socket. */ 2004 fh = open_unix_path (serve_unixpath, 2005 serve_unixmode); 2006 2007 GNUNET_assert (-1 != fh); 2008 } 2009 2010 /* start MHD daemon for HTTP */ 2011 mhd_daemon = MHD_start_daemon 2012 (MHD_USE_DEBUG | MHD_ALLOW_SUSPEND_RESUME | MHD_USE_DUAL_STACK, 2013 (-1 == fh) ? (uint16_t) port : 0, 2014 NULL, NULL, 2015 &create_response, NULL, 2016 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16, 2017 MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL, 2018 MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL, 2019 MHD_OPTION_LISTEN_SOCKET, fh, 2020 MHD_OPTION_END); 2021 2022 if (NULL == mhd_daemon) 2023 { 2024 GNUNET_break (0); 2025 GNUNET_SCHEDULER_shutdown (); 2026 return; 2027 } 2028 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 2029 NULL); 2030 run_mhd_now (); 2031 } 2032 2033 2034 /** 2035 * Main function. 2036 */ 2037 int 2038 main (int argc, 2039 char *const *argv) 2040 { 2041 struct GNUNET_GETOPT_CommandLineOption options[] = { 2042 GNUNET_GETOPT_option_flag ( 2043 'n', 2044 "no-payment", 2045 gettext_noop ( 2046 "disables payment, useful for testing reverse-proxy only"), 2047 &no_check), 2048 GNUNET_GETOPT_OPTION_END 2049 }; 2050 2051 return GNUNET_PROGRAM_run (PAIVANA_project_data (), 2052 argc, 2053 argv, 2054 "paivana-httpd", 2055 "reverse proxy requesting Taler payment", 2056 options, 2057 &run, 2058 NULL); 2059 } 2060 2061 2062 /* end of paivana-httpd.c */