paivana-httpd_reverse.c (31619B)
1 /* 2 This file is part of GNU Taler 3 Copyright (C) 2012-2014 GNUnet e.V. 4 Copyright (C) 2018, 2025, 2026 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_reverse.c 27 * @brief Reverse proxy logic that just forwards the request 28 */ 29 #include "platform.h" 30 #include <curl/curl.h> 31 #include <gnunet/gnunet_util_lib.h> 32 #include <taler/taler_mhd_lib.h> 33 #include "paivana-httpd_reverse.h" 34 35 #define REQUEST_BUFFER_MAX (1024 * 1024) 36 37 38 /** 39 * Log curl error. 40 * 41 * @param level log level 42 * @param fun name of curl_easy-function that gave the error 43 * @param rc return code from curl 44 */ 45 #define LOG_CURL_EASY(level,fun,rc) \ 46 GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \ 47 __LINE__, \ 48 curl_easy_strerror (rc)) 49 50 51 /** 52 * State machine for HTTP requests (per request). 53 */ 54 enum RequestState 55 { 56 /** 57 * We've started receiving upload data from MHD. 58 * Initial state. 59 */ 60 REQUEST_STATE_CLIENT_UPLOAD_STARTED, 61 62 /** 63 * Wa have started uploading data to the proxied service. 64 */ 65 REQUEST_STATE_PROXY_UPLOAD_STARTED, 66 67 /** 68 * We're done with the upload from MHD. 69 */ 70 REQUEST_STATE_CLIENT_UPLOAD_DONE, 71 72 /** 73 * We're done uploading data to the proxied service. 74 */ 75 REQUEST_STATE_PROXY_UPLOAD_DONE, 76 77 /** 78 * We've finished uploading data via CURL and can now download. 79 */ 80 REQUEST_STATE_PROXY_DOWNLOAD_STARTED, 81 82 /** 83 * We've finished receiving download data from cURL. 84 */ 85 REQUEST_STATE_PROXY_DOWNLOAD_DONE 86 }; 87 88 89 /** 90 * A header list 91 */ 92 struct HttpResponseHeader 93 { 94 /** 95 * DLL 96 */ 97 struct HttpResponseHeader *next; 98 99 /** 100 * DLL 101 */ 102 struct HttpResponseHeader *prev; 103 104 /** 105 * Header type 106 */ 107 char *type; 108 109 /** 110 * Header value 111 */ 112 char *value; 113 }; 114 115 116 /** 117 * A structure for socks requests 118 */ 119 struct HttpRequest 120 { 121 122 /** 123 * Kept in DLL. 124 */ 125 struct HttpRequest *prev; 126 127 /** 128 * Kept in DLL. 129 */ 130 struct HttpRequest *next; 131 132 /** 133 * MHD request that triggered us. 134 */ 135 struct MHD_Connection *con; 136 137 /** 138 * Client socket read task 139 */ 140 struct GNUNET_SCHEDULER_Task *rtask; 141 142 /** 143 * Client socket write task 144 */ 145 struct GNUNET_SCHEDULER_Task *wtask; 146 147 /** 148 * Hold the response obtained by modifying the original one. 149 */ 150 struct MHD_Response *mod_response; 151 152 /** 153 * MHD response object for this request. 154 */ 155 struct MHD_Response *response; 156 157 /** 158 * The URL to fetch 159 */ 160 char *url; 161 162 /** 163 * Handle to cURL 164 */ 165 CURL *curl; 166 167 /** 168 * HTTP request headers for the curl request. 169 */ 170 struct curl_slist *headers; 171 172 /** 173 * Headers from response 174 */ 175 struct HttpResponseHeader *header_head; 176 177 /** 178 * Headers from response 179 */ 180 struct HttpResponseHeader *header_tail; 181 182 /** 183 * Buffer we use for moving data between MHD and 184 * curl (in both directions). 185 */ 186 char *io_buf; 187 188 /** 189 * Number of bytes already in the IO buffer. 190 */ 191 size_t io_len; 192 193 /** 194 * Number of bytes allocated for the IO buffer. 195 */ 196 unsigned int io_size; 197 198 /** 199 * HTTP response code to give to MHD for the response. 200 */ 201 unsigned int response_code; 202 203 /** 204 * Request processing state machine. 205 */ 206 enum RequestState state; 207 208 /** 209 * Did we suspend MHD processing? 210 */ 211 enum GNUNET_GenericReturnValue suspended; 212 213 /** 214 * Did we pause CURL processing? 215 */ 216 int curl_paused; 217 }; 218 219 220 /** 221 * DLL of active HTTP requests. 222 */ 223 static struct HttpRequest *hr_head; 224 225 /** 226 * DLL of active HTTP requests. 227 */ 228 static struct HttpRequest *hr_tail; 229 230 /** 231 * Response we return on cURL failures. 232 */ 233 static struct MHD_Response *curl_failure_response; 234 235 /** 236 * The cURL multi handle 237 */ 238 static CURLM *curl_multi; 239 240 /** 241 * The cURL download task (curl multi API). 242 */ 243 static struct GNUNET_SCHEDULER_Task *curl_download_task; 244 245 246 bool 247 PAIVANA_HTTPD_reverse_init (void) 248 { 249 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 250 { 251 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 252 "cURL global init failed!\n"); 253 return false; 254 } 255 if (NULL == (curl_multi = curl_multi_init ())) 256 { 257 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 258 "Failed to create cURL multi handle!\n"); 259 return false; 260 } 261 return true; 262 } 263 264 265 void 266 PAIVANA_HTTPD_reverse_shutdown (void) 267 { 268 for (struct HttpRequest *hr = hr_head; 269 NULL != hr; 270 hr = hr->next) 271 { 272 if (GNUNET_YES == hr->suspended) 273 { 274 hr->suspended = GNUNET_NO; 275 MHD_resume_connection (hr->con); 276 } 277 } 278 if (NULL != curl_multi) 279 { 280 curl_multi_cleanup (curl_multi); 281 curl_multi = NULL; 282 } 283 if (NULL != curl_download_task) 284 { 285 GNUNET_SCHEDULER_cancel (curl_download_task); 286 curl_download_task = NULL; 287 } 288 } 289 290 291 /* *************** HTTP handling with cURL ***************** */ 292 293 294 /** 295 * Transform _one_ CURL header (gotten from the request) into 296 * MHD format and put it into the response headers list; mostly 297 * copies the headers, but makes special adjustments based on 298 * control requests. 299 * 300 * @param buffer curl buffer with a single 301 * line of header data; not 0-terminated! 302 * @param size curl blocksize 303 * @param nmemb curl blocknumber 304 * @param cls our `struct HttpRequest *` 305 * @return size of processed bytes 306 */ 307 static size_t 308 curl_check_hdr (void *buffer, 309 size_t size, 310 size_t nmemb, 311 void *cls) 312 { 313 struct HttpRequest *hr = cls; 314 struct HttpResponseHeader *header; 315 size_t bytes = size * nmemb; 316 char *ndup; 317 const char *hdr_type; 318 char *hdr_val; 319 char *tok; 320 321 /* Raw line is not guaranteed to be null-terminated. */ 322 ndup = GNUNET_malloc (bytes + 1); 323 memcpy (ndup, 324 buffer, 325 bytes); 326 ndup[bytes] = '\0'; 327 hdr_type = strtok (ndup, ":"); 328 if (NULL == hdr_type) 329 { 330 GNUNET_free (ndup); 331 return bytes; 332 } 333 hdr_val = strtok (NULL, ""); 334 if (NULL == hdr_val) 335 { 336 GNUNET_free (ndup); 337 return bytes; 338 } 339 if (' ' == *hdr_val) 340 hdr_val++; 341 342 /* MHD does not allow certain characters in values, 343 * remove those, plus those could alter strings matching. */ 344 if (NULL != (tok = strchr (hdr_val, '\n'))) 345 *tok = '\0'; 346 if (NULL != (tok = strchr (hdr_val, '\r'))) 347 *tok = '\0'; 348 if (NULL != (tok = strchr (hdr_val, '\t'))) 349 *tok = '\0'; 350 PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n", 351 hdr_type, 352 hdr_val); 353 /* Skip "Content-length:" header as it will be wrong, given 354 that we are man-in-the-middling the connection */ 355 if (0 == strcasecmp (hdr_type, 356 MHD_HTTP_HEADER_CONTENT_LENGTH)) 357 { 358 GNUNET_free (ndup); 359 return bytes; 360 } 361 /* Skip "Connection: Keep-Alive" header, it will be 362 done by MHD if possible */ 363 if ( (0 == strcasecmp (hdr_type, 364 MHD_HTTP_HEADER_CONNECTION)) && 365 (0 == strcasecmp (hdr_val, 366 "Keep-Alive")) ) 367 { 368 GNUNET_free (ndup); 369 return bytes; 370 } 371 if (0 != strlen (hdr_val)) /* Rely in MHD to set those */ 372 { 373 header = GNUNET_new (struct HttpResponseHeader); 374 header->type = GNUNET_strdup (hdr_type); 375 header->value = GNUNET_strdup (hdr_val); 376 GNUNET_CONTAINER_DLL_insert (hr->header_head, 377 hr->header_tail, 378 header); 379 } 380 GNUNET_free (ndup); 381 return bytes; 382 } 383 384 385 /** 386 * Create the MHD response with CURL's as starting base; 387 * mainly set the response code and parses the response into 388 * JSON, if it is such. 389 * 390 * @param hr pointer to where to store the new data. Despite 391 * its name, the struct contains response data as well. 392 * @return #GNUNET_OK if it succeeds. 393 */ 394 static enum GNUNET_GenericReturnValue 395 create_mhd_response_from_hr (struct HttpRequest *hr) 396 { 397 long resp_code; 398 399 if (NULL != hr->response) 400 { 401 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 402 "Response already set!\n"); 403 return GNUNET_SYSERR; 404 } 405 GNUNET_break (CURLE_OK == 406 curl_easy_getinfo (hr->curl, 407 CURLINFO_RESPONSE_CODE, 408 &resp_code)); 409 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 410 "Creating MHD response with code %u\n", 411 (unsigned int) resp_code); 412 hr->response_code = resp_code; 413 if (GNUNET_YES == hr->suspended) 414 { 415 MHD_resume_connection (hr->con); 416 hr->suspended = GNUNET_NO; 417 } 418 TALER_MHD_daemon_trigger (); 419 return GNUNET_OK; 420 } 421 422 423 /** 424 * Handle response payload data from cURL. 425 * Copies it into our `io_buf` to make it available to MHD. 426 * 427 * @param ptr pointer to the data 428 * @param size number of blocks of data 429 * @param nmemb blocksize 430 * @param ctx our `struct HttpRequest *` 431 * @return number of bytes handled 432 */ 433 static size_t 434 curl_download_cb (void *ptr, 435 size_t size, 436 size_t nmemb, 437 void *ctx) 438 { 439 struct HttpRequest *hr = ctx; 440 size_t total = size * nmemb; 441 442 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 443 "Curl download proceeding\n"); 444 445 if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) 446 { 447 /* Web server started with response before we finished 448 the upload. In this case, current libcurl decides 449 to NOT complete the upload, so we should jump in the 450 state machine to process the download, dropping the 451 rest of the upload. This should only really happen 452 with uploads without "Expect: 100 Continue" and 453 Web servers responding with an error (i.e. upload 454 not allowed) */hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 455 GNUNET_log 456 (GNUNET_ERROR_TYPE_INFO, 457 "Stopping %u byte upload: we are already downloading...\n", 458 (unsigned int) hr->io_len); 459 hr->io_len = 0; 460 } 461 462 if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state) 463 { 464 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 465 "Download callback goes to sleep\n"); 466 hr->curl_paused = GNUNET_YES; 467 return CURL_WRITEFUNC_PAUSE; 468 } 469 GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == 470 hr->state); 471 if (hr->io_size - hr->io_len < total) 472 { 473 GNUNET_assert (total + hr->io_size >= total); 474 GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); 475 GNUNET_array_grow (hr->io_buf, 476 hr->io_size, 477 GNUNET_MAX (total + hr->io_len, 478 hr->io_size * 2 + 1024)); 479 } 480 GNUNET_memcpy (&hr->io_buf[hr->io_len], 481 ptr, 482 total); 483 hr->io_len += total; 484 return total; 485 } 486 487 488 /** 489 * Ask cURL for the select() sets and schedule cURL operations. 490 */ 491 static void 492 curl_download_prepare (void); 493 494 495 /** 496 * cURL callback for uploaded (PUT/POST) data. 497 * Copies from our `io_buf` to make it available to cURL. 498 * 499 * @param buf where to write the data 500 * @param size number of bytes per member 501 * @param nmemb number of members available in @a buf 502 * @param cls our `struct HttpRequest` that generated the data 503 * @return number of bytes copied to @a buf 504 */ 505 static size_t 506 curl_upload_cb (void *buf, 507 size_t size, 508 size_t nmemb, 509 void *cls) 510 { 511 struct HttpRequest *hr = cls; 512 size_t len = size * nmemb; 513 size_t to_copy; 514 515 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 516 "Upload cb is working...\n"); 517 518 if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) || 519 (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) ) 520 { 521 GNUNET_log 522 (GNUNET_ERROR_TYPE_INFO, 523 "Upload cb aborts: we are already downloading...\n"); 524 return CURL_READFUNC_ABORT; 525 } 526 527 if ( (0 == hr->io_len) && 528 (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) ) 529 { 530 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 531 "Pausing CURL UPLOAD, need more data\n"); 532 return CURL_READFUNC_PAUSE; 533 } 534 535 /** 536 * We got rescheduled because the download callback was asleep. 537 * FIXME: can this block be eliminated and the unpausing being 538 * moved in the last block where we return zero as well? 539 */ 540 if ( (0 == hr->io_len) && 541 (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ) 542 { 543 if (GNUNET_YES == hr->curl_paused) 544 { 545 hr->curl_paused = GNUNET_NO; 546 curl_easy_pause (hr->curl, 547 CURLPAUSE_CONT); 548 } 549 curl_download_prepare (); 550 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 551 "Completed CURL UPLOAD\n"); 552 return 0; /* upload finished, can now download */ 553 } 554 to_copy = GNUNET_MIN (hr->io_len, 555 len); 556 GNUNET_memcpy (buf, 557 hr->io_buf, 558 to_copy); 559 /* shift remaining data back to the beginning of the buffer. */ 560 memmove (hr->io_buf, 561 &hr->io_buf[to_copy], 562 hr->io_len - to_copy); 563 hr->io_len -= to_copy; 564 if (0 == hr->io_len) 565 { 566 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 567 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 568 "Completed CURL UPLOAD\n"); 569 } 570 return to_copy; 571 } 572 573 574 /* ************** helper functions ************* */ 575 576 /** 577 * Extract the hostname from a complete URL. 578 * 579 * @param url full fledged URL 580 * @return pointer to the 0-terminated hostname, to be freed 581 * by the caller. 582 */ 583 static char * 584 build_host_header (const char *url) 585 { 586 #define MARKER "://" 587 588 char *header; 589 char *end; 590 char *hostname; 591 char *dup = GNUNET_strdup (url); 592 593 hostname = strstr (dup, 594 MARKER); 595 hostname += 3; 596 end = strchrnul (hostname, '/'); 597 *end = '\0'; 598 GNUNET_asprintf (&header, 599 "Host: %s", 600 hostname); 601 GNUNET_free (dup); 602 return header; 603 } 604 605 606 /* ************** main loop of cURL interaction ************* */ 607 608 609 /** 610 * Task that is run when we are ready to receive more data 611 * from curl 612 * 613 * @param cls closure 614 */ 615 static void 616 curl_task_download (void *cls); 617 618 619 /** 620 * Ask cURL for the select() sets and schedule cURL operations. 621 */ 622 static void 623 curl_download_prepare () 624 { 625 CURLMcode mret; 626 fd_set rs; 627 fd_set ws; 628 fd_set es; 629 int max; 630 struct GNUNET_NETWORK_FDSet *grs; 631 struct GNUNET_NETWORK_FDSet *gws; 632 long to; 633 struct GNUNET_TIME_Relative rtime; 634 635 if (NULL != curl_download_task) 636 { 637 GNUNET_SCHEDULER_cancel (curl_download_task); 638 curl_download_task = NULL; 639 } 640 max = -1; 641 FD_ZERO (&rs); 642 FD_ZERO (&ws); 643 FD_ZERO (&es); 644 if (CURLM_OK != (mret = curl_multi_fdset (curl_multi, 645 &rs, 646 &ws, 647 &es, 648 &max))) 649 { 650 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 651 "%s failed at %s:%d: `%s'\n", 652 "curl_multi_fdset", 653 __FILE__, 654 __LINE__, 655 curl_multi_strerror (mret)); 656 return; 657 } 658 to = -1; 659 GNUNET_break (CURLM_OK == 660 curl_multi_timeout (curl_multi, 661 &to)); 662 if (-1 == to) 663 rtime = GNUNET_TIME_UNIT_FOREVER_REL; 664 else 665 rtime = GNUNET_TIME_relative_multiply 666 (GNUNET_TIME_UNIT_MILLISECONDS, to); 667 if (-1 != max) 668 { 669 grs = GNUNET_NETWORK_fdset_create (); 670 gws = GNUNET_NETWORK_fdset_create (); 671 GNUNET_NETWORK_fdset_copy_native (grs, 672 &rs, 673 max + 1); 674 GNUNET_NETWORK_fdset_copy_native (gws, 675 &ws, 676 max + 1); 677 curl_download_task 678 = GNUNET_SCHEDULER_add_select ( 679 GNUNET_SCHEDULER_PRIORITY_DEFAULT, 680 rtime, 681 grs, gws, 682 &curl_task_download, 683 curl_multi); 684 GNUNET_NETWORK_fdset_destroy (gws); 685 GNUNET_NETWORK_fdset_destroy (grs); 686 } 687 else 688 { 689 curl_download_task = GNUNET_SCHEDULER_add_delayed (rtime, 690 &curl_task_download, 691 curl_multi); 692 } 693 } 694 695 696 /** 697 * "Filter" function that translates MHD request headers to 698 * cURL's. 699 * 700 * @param cls our `struct HttpRequest` 701 * @param kind value kind 702 * @param key field key 703 * @param value field value 704 * @return #MHD_YES to continue to iterate 705 */ 706 static enum MHD_Result 707 con_val_iter (void *cls, 708 enum MHD_ValueKind kind, 709 const char *key, 710 const char *value) 711 { 712 struct HttpRequest *hr = cls; 713 char *hdr; 714 char *new_value = NULL; 715 716 (void) kind; 717 if (0 == strcmp (MHD_HTTP_HEADER_HOST, 718 key)) 719 { 720 /* We don't take the host header as given in the request. 721 * We'll instead put the proxied service's hostname in it*/ 722 return MHD_YES; 723 } 724 if ((0 == strcmp (MHD_HTTP_HEADER_CONTENT_LENGTH, 725 key))) 726 { 727 PAIVANA_LOG_INFO ( 728 "Do not set Content-Length for request\n"); 729 return MHD_YES; 730 } 731 GNUNET_asprintf (&hdr, 732 "%s: %s", 733 key, 734 value); 735 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 736 "Adding header `%s' to HTTP request\n", 737 hdr); 738 hr->headers = curl_slist_append (hr->headers, 739 hdr); 740 GNUNET_free (hdr); 741 GNUNET_free (new_value); 742 return MHD_YES; 743 } 744 745 746 /** 747 * Task that is run when we are ready to receive 748 * more data from curl. 749 * 750 * @param cls closure, usually NULL. 751 */ 752 static void 753 curl_task_download (void *cls) 754 { 755 int running; 756 int msgnum; 757 struct CURLMsg *msg; 758 CURLMcode mret; 759 struct HttpRequest *hr; 760 761 (void) cls; 762 curl_download_task = NULL; 763 do 764 { 765 running = 0; 766 mret = curl_multi_perform (curl_multi, 767 &running); 768 while (NULL != (msg = curl_multi_info_read (curl_multi, 769 &msgnum))) 770 { 771 GNUNET_break 772 (CURLE_OK == curl_easy_getinfo 773 (msg->easy_handle, 774 CURLINFO_PRIVATE, 775 (char **) &hr)); 776 777 if (NULL == hr) 778 { 779 GNUNET_break (0); 780 continue; 781 } 782 switch (msg->msg) 783 { 784 case CURLMSG_NONE: 785 /* documentation says this is not used */ 786 GNUNET_break (0); 787 break; 788 case CURLMSG_DONE: 789 switch (msg->data.result) 790 { 791 case CURLE_OK: 792 case CURLE_GOT_NOTHING: 793 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 794 "CURL download completed.\n"); 795 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; 796 if (NULL == hr->response) 797 GNUNET_assert (GNUNET_OK == 798 create_mhd_response_from_hr (hr)); 799 break; 800 default: 801 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 802 "Download curl failed: %s\n", 803 curl_easy_strerror (msg->data.result)); 804 /* FIXME: indicate error somehow? 805 * close MHD connection badly as well? */ 806 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE; 807 if (GNUNET_YES == hr->suspended) 808 { 809 MHD_resume_connection (hr->con); 810 hr->suspended = GNUNET_NO; 811 } 812 TALER_MHD_daemon_trigger (); 813 break; 814 } 815 if (NULL == hr->response) 816 hr->response = curl_failure_response; 817 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 818 "Curl request for `%s' finished (got the response)\n", 819 hr->url); 820 TALER_MHD_daemon_trigger (); 821 break; 822 case CURLMSG_LAST: 823 /* documentation says this is not used */ 824 GNUNET_break (0); 825 break; 826 default: 827 /* unexpected status code */ 828 GNUNET_break (0); 829 break; 830 } 831 } 832 } while (mret == CURLM_CALL_MULTI_PERFORM); 833 if (CURLM_OK != mret) 834 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 835 "%s failed at %s:%d: `%s'\n", 836 "curl_multi_perform", 837 __FILE__, 838 __LINE__, 839 curl_multi_strerror (mret)); 840 if (0 == running) 841 { 842 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 843 "Suspending cURL multi loop," 844 " no more events pending\n"); 845 return; /* nothing more in progress */ 846 } 847 curl_download_prepare (); 848 } 849 850 851 struct HttpRequest * 852 PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection, 853 const char *url) 854 { 855 struct HttpRequest *hr; 856 857 hr = GNUNET_new (struct HttpRequest); 858 hr->con = connection; 859 hr->url = GNUNET_strdup (url); 860 GNUNET_CONTAINER_DLL_insert (hr_head, 861 hr_tail, 862 hr); 863 return hr; 864 } 865 866 867 void 868 PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr) 869 { 870 struct HttpResponseHeader *header; 871 872 if (NULL != hr->curl) 873 { 874 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 875 "Resetting cURL handle\n"); 876 curl_multi_remove_handle (curl_multi, 877 hr->curl); 878 curl_easy_cleanup (hr->curl); 879 hr->curl = NULL; 880 hr->io_len = 0; 881 } 882 if (NULL != hr->headers) 883 { 884 curl_slist_free_all (hr->headers); 885 hr->headers = NULL; 886 } 887 if ( (NULL != hr->response) && 888 (curl_failure_response != hr->response) ) 889 /* Destroy non-error responses... (?) */ 890 MHD_destroy_response (hr->response); 891 892 for (header = hr->header_head; 893 header != NULL; 894 header = hr->header_head) 895 { 896 GNUNET_CONTAINER_DLL_remove (hr->header_head, 897 hr->header_tail, 898 header); 899 GNUNET_free (header->type); 900 GNUNET_free (header->value); 901 GNUNET_free (header); 902 } 903 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 904 "Proxying of '%s' completely done\n", 905 hr->url); 906 907 GNUNET_free (hr->url); 908 GNUNET_free (hr->io_buf); 909 GNUNET_CONTAINER_DLL_remove (hr_head, 910 hr_tail, 911 hr); 912 GNUNET_free (hr); 913 } 914 915 916 /** 917 * Main MHD callback for reverse proxy. 918 * 919 * @param hr the HTTP request context 920 * @param con MHD connection handle 921 * @param url the url in the request 922 * @param meth the HTTP method used ("GET", "PUT", etc.) 923 * @param ver the HTTP version string (i.e. "HTTP/1.1") 924 * @param upload_data the data being uploaded (excluding HEADERS, 925 * for a POST that fits into memory and that is encoded 926 * with a supported encoding, the POST data will NOT be 927 * given in upload_data and is instead available as 928 * part of MHD_get_connection_values; very large POST 929 * data *will* be made available incrementally in 930 * upload_data) 931 * @param upload_data_size set initially to the size of the 932 * @a upload_data provided; the method must update this 933 * value to the number of bytes NOT processed; 934 * @return #MHD_YES if the connection was handled successfully, 935 * #MHD_NO if the socket must be closed due to a serious 936 * error while handling the request 937 */ 938 enum MHD_Result 939 PAIVANA_HTTPD_reverse (struct HttpRequest *hr, 940 struct MHD_Connection *con, 941 const char *url, 942 const char *meth, 943 const char *ver, 944 const char *upload_data, 945 size_t *upload_data_size) 946 { 947 /* FIXME: make state machine more explicit by 948 switching on hr->state here! */ 949 if (0 != *upload_data_size) 950 { 951 GNUNET_assert 952 (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state); 953 954 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 955 "Processing %u bytes UPLOAD\n", 956 (unsigned int) *upload_data_size); 957 958 /* Grow the buffer if remaining space isn't enough. */ 959 if (hr->io_size - hr->io_len < *upload_data_size) 960 { 961 /* How can this assertion be false? */ 962 GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); 963 /* This asserts that upload_data_size > 0, ? */ 964 GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len); 965 966 GNUNET_array_grow (hr->io_buf, 967 hr->io_size, 968 GNUNET_MAX 969 (hr->io_size * 2 + 1024, 970 *upload_data_size + hr->io_len)); 971 } 972 973 /* Finally copy upload data. */ 974 GNUNET_memcpy (&hr->io_buf[hr->io_len], 975 upload_data, 976 *upload_data_size); 977 978 hr->io_len += *upload_data_size; 979 *upload_data_size = 0; 980 981 return MHD_YES; 982 } 983 984 /* Upload (*from the client*) finished or just a without-body 985 * request. */ 986 if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state) 987 { 988 hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE; 989 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 990 "Finished processing UPLOAD\n"); 991 } 992 993 /* generate curl request to the proxied service. */ 994 if (NULL == hr->curl) 995 { 996 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 997 "Generating curl request\n"); 998 hr->curl = curl_easy_init (); 999 if (NULL == hr->curl) 1000 { 1001 PAIVANA_LOG_ERROR ("Could not init the curl handle\n"); 1002 return MHD_queue_response (con, 1003 MHD_HTTP_INTERNAL_SERVER_ERROR, 1004 curl_failure_response); 1005 } 1006 1007 /* No need to check whether we're POSTing or PUTting. 1008 * If not needed, one of the following values will be 1009 * ignored.*/ 1010 curl_easy_setopt (hr->curl, 1011 CURLOPT_POSTFIELDSIZE, 1012 hr->io_len); 1013 curl_easy_setopt (hr->curl, 1014 CURLOPT_INFILESIZE, 1015 hr->io_len); 1016 curl_easy_setopt (hr->curl, 1017 CURLOPT_HEADERFUNCTION, 1018 &curl_check_hdr); 1019 curl_easy_setopt (hr->curl, 1020 CURLOPT_HEADERDATA, 1021 hr); 1022 curl_easy_setopt (hr->curl, 1023 CURLOPT_FOLLOWLOCATION, 1024 0); 1025 curl_easy_setopt (hr->curl, 1026 CURLOPT_CONNECTTIMEOUT, 1027 60L); 1028 curl_easy_setopt (hr->curl, 1029 CURLOPT_TIMEOUT, 1030 60L); 1031 curl_easy_setopt (hr->curl, 1032 CURLOPT_NOSIGNAL, 1033 1L); 1034 curl_easy_setopt (hr->curl, 1035 CURLOPT_PRIVATE, 1036 hr); 1037 curl_easy_setopt (hr->curl, 1038 CURLOPT_VERBOSE, 1039 0); 1040 1041 curl_easy_setopt (hr->curl, 1042 CURLOPT_READFUNCTION, 1043 &curl_upload_cb); 1044 curl_easy_setopt (hr->curl, 1045 CURLOPT_READDATA, 1046 hr); 1047 1048 curl_easy_setopt (hr->curl, 1049 CURLOPT_WRITEFUNCTION, 1050 &curl_download_cb); 1051 curl_easy_setopt (hr->curl, 1052 CURLOPT_WRITEDATA, 1053 hr); 1054 { 1055 char *curlurl; 1056 char *host_hdr; 1057 1058 GNUNET_asprintf (&curlurl, 1059 "%s%s", 1060 target_server_base_url, 1061 hr->url); 1062 curl_easy_setopt (hr->curl, 1063 CURLOPT_URL, 1064 curlurl); 1065 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1066 "Forwarding request to: %s\n", 1067 curlurl); 1068 GNUNET_free (curlurl); 1069 1070 host_hdr = build_host_header (target_server_base_url); 1071 PAIVANA_LOG_DEBUG ("Faking the host header, %s\n", 1072 host_hdr); 1073 hr->headers = curl_slist_append (hr->headers, 1074 host_hdr); 1075 GNUNET_free (host_hdr); 1076 } 1077 1078 // FIXME: support PATCH, etc. 1079 if (0 == strcasecmp (meth, 1080 MHD_HTTP_METHOD_PUT)) 1081 { 1082 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1083 "Crafting a CURL PUT request\n"); 1084 1085 curl_easy_setopt (hr->curl, 1086 CURLOPT_UPLOAD, 1087 1L); 1088 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1089 } 1090 else if (0 == strcasecmp (meth, 1091 MHD_HTTP_METHOD_POST)) 1092 { 1093 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1094 "Crafting a CURL POST request\n"); 1095 curl_easy_setopt (hr->curl, 1096 CURLOPT_POST, 1097 1L); 1098 curl_easy_setopt (hr->curl, 1099 CURLOPT_VERBOSE, 1100 1L); 1101 hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; 1102 } 1103 else if (0 == strcasecmp (meth, 1104 MHD_HTTP_METHOD_HEAD)) 1105 { 1106 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1107 curl_easy_setopt (hr->curl, 1108 CURLOPT_NOBODY, 1109 1L); 1110 } 1111 else if (0 == strcasecmp (meth, 1112 MHD_HTTP_METHOD_OPTIONS)) 1113 { 1114 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1115 curl_easy_setopt (hr->curl, 1116 CURLOPT_CUSTOMREQUEST, 1117 "OPTIONS"); 1118 } 1119 else if (0 == strcasecmp (meth, 1120 MHD_HTTP_METHOD_GET)) 1121 { 1122 hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; 1123 curl_easy_setopt (hr->curl, 1124 CURLOPT_HTTPGET, 1125 1L); 1126 } 1127 else 1128 { 1129 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1130 "Unsupported HTTP method `%s'\n", 1131 meth); 1132 curl_easy_cleanup (hr->curl); 1133 hr->curl = NULL; 1134 return MHD_NO; 1135 } 1136 1137 if (CURLM_OK != 1138 curl_multi_add_handle (curl_multi, 1139 hr->curl)) 1140 { 1141 GNUNET_break (0); 1142 curl_easy_cleanup (hr->curl); 1143 hr->curl = NULL; 1144 return MHD_NO; 1145 } 1146 1147 MHD_get_connection_values (con, 1148 MHD_HEADER_KIND, 1149 &con_val_iter, 1150 hr); 1151 1152 curl_easy_setopt (hr->curl, 1153 CURLOPT_HTTPHEADER, 1154 hr->headers); 1155 curl_download_prepare (); 1156 1157 return MHD_YES; 1158 } 1159 1160 if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state) 1161 { 1162 GNUNET_assert (GNUNET_NO == hr->suspended); 1163 MHD_suspend_connection (con); 1164 hr->suspended = GNUNET_YES; 1165 return MHD_YES; /* wait for curl */ 1166 } 1167 1168 GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state); 1169 1170 hr->response 1171 = MHD_create_response_from_buffer_copy (hr->io_len, 1172 hr->io_buf); 1173 for (struct HttpResponseHeader *header = hr->header_head; 1174 NULL != header; 1175 header = header->next) 1176 { 1177 const char *value = header->value; 1178 1179 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1180 "Adding MHD response header %s->%s\n", 1181 header->type, 1182 value); 1183 GNUNET_break (MHD_YES == 1184 MHD_add_response_header (hr->response, 1185 header->type, 1186 value)); 1187 } 1188 TALER_MHD_daemon_trigger (); 1189 1190 return MHD_queue_response (con, 1191 hr->response_code, 1192 hr->response); 1193 }