diff options
| -rw-r--r-- | archival/gzip.c | 3 | ||||
| -rw-r--r-- | editors/awk.c | 72 | ||||
| -rw-r--r-- | include/libbb.h | 15 | ||||
| -rw-r--r-- | include/platform.h | 2 | ||||
| -rw-r--r-- | networking/httpd.c | 709 | ||||
| -rwxr-xr-x | networking/httpd_helpers.sh | 9 | ||||
| -rw-r--r-- | networking/httpd_ratelimit_cgi.c | 242 |
7 files changed, 792 insertions, 260 deletions
diff --git a/archival/gzip.c b/archival/gzip.c index 91bd4d09d..e8080892b 100644 --- a/archival/gzip.c +++ b/archival/gzip.c | |||
| @@ -2215,6 +2215,9 @@ int gzip_main(int argc UNUSED_PARAM, char **argv) | |||
| 2215 | }; | 2215 | }; |
| 2216 | #endif | 2216 | #endif |
| 2217 | 2217 | ||
| 2218 | // TODO: use less ugly "split-globals" trick via SET_OFFSET_PTR_TO_GLOBALS(). | ||
| 2219 | // The problem is, the current method strategically places G2.heap[] | ||
| 2220 | // (~24 references) so that it has zero offset. | ||
| 2218 | SET_PTR_TO_GLOBALS((char *)xzalloc(sizeof(struct globals)+sizeof(struct globals2)) | 2221 | SET_PTR_TO_GLOBALS((char *)xzalloc(sizeof(struct globals)+sizeof(struct globals2)) |
| 2219 | + sizeof(struct globals)); | 2222 | + sizeof(struct globals)); |
| 2220 | 2223 | ||
diff --git a/editors/awk.c b/editors/awk.c index 1feb49da1..b3ec99d3f 100644 --- a/editors/awk.c +++ b/editors/awk.c | |||
| @@ -583,14 +583,10 @@ static const char vValues[] ALIGN1 = | |||
| 583 | static const uint16_t PRIMES[] ALIGN2 = { 251, 1021, 4093, 16381, 65521 }; | 583 | static const uint16_t PRIMES[] ALIGN2 = { 251, 1021, 4093, 16381, 65521 }; |
| 584 | 584 | ||
| 585 | 585 | ||
| 586 | /* Globals. Split in two parts so that first one is addressed | ||
| 587 | * with (mostly short) negative offsets. | ||
| 588 | * NB: it's unsafe to put members of type "double" | ||
| 589 | * into globals2 (gcc may fail to align them). | ||
| 590 | */ | ||
| 591 | struct globals { | 586 | struct globals { |
| 592 | double t_double; | ||
| 593 | chain beginseq, mainseq, endseq; | 587 | chain beginseq, mainseq, endseq; |
| 588 | |||
| 589 | double t_double; | ||
| 594 | chain *seq; | 590 | chain *seq; |
| 595 | node *break_ptr, *continue_ptr; | 591 | node *break_ptr, *continue_ptr; |
| 596 | xhash *ahash; /* argument names, used only while parsing function bodies */ | 592 | xhash *ahash; /* argument names, used only while parsing function bodies */ |
| @@ -618,9 +614,8 @@ struct globals { | |||
| 618 | smallint next_token__concat_inserted; | 614 | smallint next_token__concat_inserted; |
| 619 | uint32_t next_token__save_tclass; | 615 | uint32_t next_token__save_tclass; |
| 620 | uint32_t next_token__save_info; | 616 | uint32_t next_token__save_info; |
| 621 | }; | 617 | |
| 622 | struct globals2 { | 618 | uint32_t t_info; |
| 623 | uint32_t t_info; /* often used */ | ||
| 624 | uint32_t t_tclass; | 619 | uint32_t t_tclass; |
| 625 | char *t_string; | 620 | char *t_string; |
| 626 | int t_lineno; | 621 | int t_lineno; |
| @@ -654,41 +649,40 @@ struct globals2 { | |||
| 654 | 649 | ||
| 655 | char g_buf[MAXVARFMT + 1]; | 650 | char g_buf[MAXVARFMT + 1]; |
| 656 | }; | 651 | }; |
| 657 | #define G1 (ptr_to_globals[-1]) | 652 | #define G (*OFFSET_PTR_TO_GLOBALS) |
| 658 | #define G (*(struct globals2 *)ptr_to_globals) | ||
| 659 | /* For debug. nm --size-sort awk.o | grep -vi ' [tr] ' */ | 653 | /* For debug. nm --size-sort awk.o | grep -vi ' [tr] ' */ |
| 660 | //char G1size[sizeof(G1)]; // 0x70 | 654 | //char G1size[sizeof(G1)]; // 0x70 |
| 661 | //char Gsize[sizeof(G)]; // 0x2f8 | 655 | //char Gsize[sizeof(G)]; // 0x2f8 |
| 662 | /* Trying to keep most of members accessible with short offsets: */ | 656 | /* Trying to keep most of members accessible with short offsets: */ |
| 663 | //char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; // 0x7c | 657 | //char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; // 0x7c |
| 664 | #define t_double (G1.t_double ) | 658 | #define t_double (G.t_double ) |
| 665 | #define beginseq (G1.beginseq ) | 659 | #define beginseq (G.beginseq ) |
| 666 | #define mainseq (G1.mainseq ) | 660 | #define mainseq (G.mainseq ) |
| 667 | #define endseq (G1.endseq ) | 661 | #define endseq (G.endseq ) |
| 668 | #define seq (G1.seq ) | 662 | #define seq (G.seq ) |
| 669 | #define break_ptr (G1.break_ptr ) | 663 | #define break_ptr (G.break_ptr ) |
| 670 | #define continue_ptr (G1.continue_ptr) | 664 | #define continue_ptr (G.continue_ptr) |
| 671 | #define ahash (G1.ahash ) | 665 | #define ahash (G.ahash ) |
| 672 | #define fnhash (G1.fnhash ) | 666 | #define fnhash (G.fnhash ) |
| 673 | #define vhash (G1.vhash ) | 667 | #define vhash (G.vhash ) |
| 674 | #define fdhash ahash | 668 | #define fdhash ahash |
| 675 | //^^^^^^^^^^^^^^^^^^ ahash is cleared after every function parsing, | 669 | //^^^^^^^^^^^^^^^^^^ ahash is cleared after every function parsing, |
| 676 | // and ends up empty after parsing phase. Thus, we can simply reuse it | 670 | // and ends up empty after parsing phase. Thus, we can simply reuse it |
| 677 | // for fdhash in execution stage. | 671 | // for fdhash in execution stage. |
| 678 | #define g_progname (G1.g_progname ) | 672 | #define g_progname (G.g_progname ) |
| 679 | #define g_lineno (G1.g_lineno ) | 673 | #define g_lineno (G.g_lineno ) |
| 680 | #define num_fields (G1.num_fields ) | 674 | #define num_fields (G.num_fields ) |
| 681 | #define num_alloc_fields (G1.num_alloc_fields) | 675 | #define num_alloc_fields (G.num_alloc_fields) |
| 682 | #define Fields (G1.Fields ) | 676 | #define Fields (G.Fields ) |
| 683 | #define g_pos (G1.g_pos ) | 677 | #define g_pos (G.g_pos ) |
| 684 | #define g_saved_ch (G1.g_saved_ch ) | 678 | #define g_saved_ch (G.g_saved_ch ) |
| 685 | #define got_program (G1.got_program ) | 679 | #define got_program (G.got_program ) |
| 686 | #define icase (G1.icase ) | 680 | #define icase (G.icase ) |
| 687 | #define exiting (G1.exiting ) | 681 | #define exiting (G.exiting ) |
| 688 | #define nextrec (G1.nextrec ) | 682 | #define nextrec (G.nextrec ) |
| 689 | #define nextfile (G1.nextfile ) | 683 | #define nextfile (G.nextfile ) |
| 690 | #define is_f0_split (G1.is_f0_split ) | 684 | #define is_f0_split (G.is_f0_split ) |
| 691 | #define t_rollback (G1.t_rollback ) | 685 | #define t_rollback (G.t_rollback ) |
| 692 | #define t_info (G.t_info ) | 686 | #define t_info (G.t_info ) |
| 693 | #define t_tclass (G.t_tclass ) | 687 | #define t_tclass (G.t_tclass ) |
| 694 | #define t_string (G.t_string ) | 688 | #define t_string (G.t_string ) |
| @@ -699,7 +693,7 @@ struct globals2 { | |||
| 699 | #define rsplitter (G.rsplitter ) | 693 | #define rsplitter (G.rsplitter ) |
| 700 | #define g_buf (G.g_buf ) | 694 | #define g_buf (G.g_buf ) |
| 701 | #define INIT_G() do { \ | 695 | #define INIT_G() do { \ |
| 702 | SET_PTR_TO_GLOBALS((char*)xzalloc(sizeof(G1)+sizeof(G)) + sizeof(G1)); \ | 696 | SET_OFFSET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ |
| 703 | t_tclass = TC_NEWLINE; \ | 697 | t_tclass = TC_NEWLINE; \ |
| 704 | G.evaluate__seed = 1; \ | 698 | G.evaluate__seed = 1; \ |
| 705 | } while (0) | 699 | } while (0) |
| @@ -1168,9 +1162,9 @@ static int istrue(var *v) | |||
| 1168 | */ | 1162 | */ |
| 1169 | static uint32_t next_token(uint32_t expected) | 1163 | static uint32_t next_token(uint32_t expected) |
| 1170 | { | 1164 | { |
| 1171 | #define concat_inserted (G1.next_token__concat_inserted) | 1165 | #define concat_inserted (G.next_token__concat_inserted) |
| 1172 | #define save_tclass (G1.next_token__save_tclass) | 1166 | #define save_tclass (G.next_token__save_tclass) |
| 1173 | #define save_info (G1.next_token__save_info) | 1167 | #define save_info (G.next_token__save_info) |
| 1174 | 1168 | ||
| 1175 | char *p; | 1169 | char *p; |
| 1176 | const char *tl; | 1170 | const char *tl; |
diff --git a/include/libbb.h b/include/libbb.h index 1c75df523..562860cf4 100644 --- a/include/libbb.h +++ b/include/libbb.h | |||
| @@ -506,6 +506,11 @@ void *xmmap_anon(size_t size) FAST_FUNC; | |||
| 506 | //sparc64,alpha,openrisc: fixed 8k pages | 506 | //sparc64,alpha,openrisc: fixed 8k pages |
| 507 | #endif | 507 | #endif |
| 508 | 508 | ||
| 509 | #if defined(__x86_64__) || defined(i386) | ||
| 510 | /* 0x7f would be better, but it causes alignment problems */ | ||
| 511 | # define ARCH_GLOBAL_PTR_OFF 0x80 | ||
| 512 | #endif | ||
| 513 | |||
| 509 | #if defined BB_ARCH_FIXED_PAGESIZE | 514 | #if defined BB_ARCH_FIXED_PAGESIZE |
| 510 | # define IF_VARIABLE_ARCH_PAGESIZE(...) /*nothing*/ | 515 | # define IF_VARIABLE_ARCH_PAGESIZE(...) /*nothing*/ |
| 511 | # define bb_getpagesize() BB_ARCH_FIXED_PAGESIZE | 516 | # define bb_getpagesize() BB_ARCH_FIXED_PAGESIZE |
| @@ -2680,6 +2685,16 @@ void XZALLOC_CONST_PTR(const void *pptr, size_t size) FAST_FUNC; | |||
| 2680 | } \ | 2685 | } \ |
| 2681 | } while (0) | 2686 | } while (0) |
| 2682 | 2687 | ||
| 2688 | #if defined(ARCH_GLOBAL_PTR_OFF) | ||
| 2689 | # define SET_OFFSET_PTR_TO_GLOBALS(x) \ | ||
| 2690 | ASSIGN_CONST_PTR(&ptr_to_globals, (char*)(x) + ARCH_GLOBAL_PTR_OFF) | ||
| 2691 | # define OFFSET_PTR_TO_GLOBALS \ | ||
| 2692 | ((struct globals*)((char*)ptr_to_globals - ARCH_GLOBAL_PTR_OFF)) | ||
| 2693 | #else | ||
| 2694 | # define SET_OFFSET_PTR_TO_GLOBALS(x) SET_PTR_TO_GLOBALS(x) | ||
| 2695 | # define OFFSET_PTR_TO_GLOBALS ptr_to_globals | ||
| 2696 | #endif | ||
| 2697 | |||
| 2683 | 2698 | ||
| 2684 | /* You can change LIBBB_DEFAULT_LOGIN_SHELL, but don't use it, | 2699 | /* You can change LIBBB_DEFAULT_LOGIN_SHELL, but don't use it, |
| 2685 | * use bb_default_login_shell and following defines. | 2700 | * use bb_default_login_shell and following defines. |
diff --git a/include/platform.h b/include/platform.h index 793639f1c..f46569f0f 100644 --- a/include/platform.h +++ b/include/platform.h | |||
| @@ -232,6 +232,7 @@ | |||
| 232 | # define IF_LITTLE_ENDIAN(...) | 232 | # define IF_LITTLE_ENDIAN(...) |
| 233 | /* How do bytes a,b,c,d (sequential in memory) look if fetched into uint32_t? */ | 233 | /* How do bytes a,b,c,d (sequential in memory) look if fetched into uint32_t? */ |
| 234 | # define PACK32_BYTES(a,b,c,d) (uint32_t)((d)+((c)<<8)+((b)<<16)+((a)<<24)) | 234 | # define PACK32_BYTES(a,b,c,d) (uint32_t)((d)+((c)<<8)+((b)<<16)+((a)<<24)) |
| 235 | # define PACK64_LITERAL_STR(s) (((uint64_t)PACK32_BYTES((s)[0],(s)[1],(s)[2],(s)[3])<<32) + PACK32_BYTES((s)[4],(s)[5],(s)[6],(s)[7])) | ||
| 235 | #else | 236 | #else |
| 236 | # define SWAP_BE16(x) bswap_16(x) | 237 | # define SWAP_BE16(x) bswap_16(x) |
| 237 | # define SWAP_BE32(x) bswap_32(x) | 238 | # define SWAP_BE32(x) bswap_32(x) |
| @@ -242,6 +243,7 @@ | |||
| 242 | # define IF_BIG_ENDIAN(...) | 243 | # define IF_BIG_ENDIAN(...) |
| 243 | # define IF_LITTLE_ENDIAN(...) __VA_ARGS__ | 244 | # define IF_LITTLE_ENDIAN(...) __VA_ARGS__ |
| 244 | # define PACK32_BYTES(a,b,c,d) (uint32_t)((a)+((b)<<8)+((c)<<16)+((d)<<24)) | 245 | # define PACK32_BYTES(a,b,c,d) (uint32_t)((a)+((b)<<8)+((c)<<16)+((d)<<24)) |
| 246 | # define PACK64_LITERAL_STR(s) (((uint64_t)PACK32_BYTES((s)[4],(s)[5],(s)[6],(s)[7])<<32) + PACK32_BYTES((s)[0],(s)[1],(s)[2],(s)[3])) | ||
| 245 | #endif | 247 | #endif |
| 246 | 248 | ||
| 247 | 249 | ||
diff --git a/networking/httpd.c b/networking/httpd.c index 50595104c..0005e781a 100644 --- a/networking/httpd.c +++ b/networking/httpd.c | |||
| @@ -272,6 +272,10 @@ | |||
| 272 | //usage: ) | 272 | //usage: ) |
| 273 | //usage: " [-c CONFFILE]" | 273 | //usage: " [-c CONFFILE]" |
| 274 | //usage: " [-p [IP:]PORT]" | 274 | //usage: " [-p [IP:]PORT]" |
| 275 | //usage: IF_NOT_PLATFORM_MINGW32( | ||
| 276 | //usage: " [-M MAXCONN]" | ||
| 277 | //usage: IF_FEATURE_HTTPD_CGI(" [-K KILLSEC]") | ||
| 278 | //usage: ) | ||
| 275 | //usage: IF_FEATURE_HTTPD_SETUID(" [-u USER[:GRP]]") | 279 | //usage: IF_FEATURE_HTTPD_SETUID(" [-u USER[:GRP]]") |
| 276 | //usage: IF_FEATURE_HTTPD_BASIC_AUTH(" [-r REALM]") | 280 | //usage: IF_FEATURE_HTTPD_BASIC_AUTH(" [-r REALM]") |
| 277 | //usage: " [-h HOME]\n" | 281 | //usage: " [-h HOME]\n" |
| @@ -284,6 +288,11 @@ | |||
| 284 | //usage: "\n -f Run in foreground" | 288 | //usage: "\n -f Run in foreground" |
| 285 | //usage: "\n -v[v] Verbose" | 289 | //usage: "\n -v[v] Verbose" |
| 286 | //usage: "\n -p [IP:]PORT Bind to IP:PORT (default *:"STR(CONFIG_FEATURE_HTTPD_PORT_DEFAULT)")" | 290 | //usage: "\n -p [IP:]PORT Bind to IP:PORT (default *:"STR(CONFIG_FEATURE_HTTPD_PORT_DEFAULT)")" |
| 291 | //usage: IF_NOT_PLATFORM_MINGW32( | ||
| 292 | //usage: "\n -M NUM Pause if NUM connections are open (default 256)" | ||
| 293 | //usage: IF_FEATURE_HTTPD_CGI( | ||
| 294 | //usage: "\n -K NUM Kill CGIs after NUM seconds") | ||
| 295 | //usage: ) | ||
| 287 | //usage: IF_FEATURE_HTTPD_SETUID( | 296 | //usage: IF_FEATURE_HTTPD_SETUID( |
| 288 | //usage: "\n -u USER[:GRP] Set uid/gid after binding to port") | 297 | //usage: "\n -u USER[:GRP] Set uid/gid after binding to port") |
| 289 | //usage: IF_FEATURE_HTTPD_BASIC_AUTH( | 298 | //usage: IF_FEATURE_HTTPD_BASIC_AUTH( |
| @@ -295,7 +304,37 @@ | |||
| 295 | //usage: "\n -e STRING HTML encode STRING" | 304 | //usage: "\n -e STRING HTML encode STRING" |
| 296 | //usage: "\n -d STRING URL decode STRING" | 305 | //usage: "\n -d STRING URL decode STRING" |
| 297 | 306 | ||
| 298 | /* TODO: use TCP_CORK, parse_config() */ | 307 | /* Robustness |
| 308 | * | ||
| 309 | * Even though the design is geared towards simplicity, it is meant to | ||
| 310 | * survive in a mildly adversarial environment: | ||
| 311 | * - it does not accept unlimited number of connections (-M MAXCONN) | ||
| 312 | * - it requires clients to send their incoming requests quickly | ||
| 313 | * (HEADER_READ_TIMEOUT) | ||
| 314 | * - if client stops consuming its result ("stalled receiver" attack), | ||
| 315 | * the server will drop the connection (DATA_WRITE_TIMEOUT) | ||
| 316 | * - POSTDATA for POST method to CGI must arrive at least once | ||
| 317 | * per DATA_READ_TIMEOUT | ||
| 318 | * - killing CGIs which are obviously stuck if -K 60: | ||
| 319 | * "if CGI isn't done in 1 minute, it's fishy. SIGTERM+SIGKILL" | ||
| 320 | * | ||
| 321 | * To limit the number of simultaneously running CGI children, | ||
| 322 | * you may use the helper tool, httpd_ratelimit_cgi.c: | ||
| 323 | * it replies with "429 Too Many Requests" and exits when limit is exceeded. | ||
| 324 | * The limit can be set per CGI type: "I accept up to 256 connections, | ||
| 325 | * but only up to 100 of them may be to /cgi-bin/simple_cgi, | ||
| 326 | * and 20 to /cgi-bin/HEAVY_cgi". | ||
| 327 | * | ||
| 328 | * TODO: | ||
| 329 | * - do not allow all MAXCONN connections be taken up by the same remote IP. | ||
| 330 | * - sanity: add a limit how big POSTDATA can be? (-P 1024: "megabyte+ of POSTDATA is insanity") | ||
| 331 | * currently we only do: if (POST_length > INT_MAX) HTTP_BAD_REQUEST | ||
| 332 | * - sanity: measure CGI memory consumption (how?), kill when way too big? | ||
| 333 | */ | ||
| 334 | #define HEADER_READ_TIMEOUT 30 | ||
| 335 | #define DATA_WRITE_TIMEOUT 60 | ||
| 336 | #define DATA_READ_TIMEOUT 60 | ||
| 337 | |||
| 299 | 338 | ||
| 300 | #include "libbb.h" | 339 | #include "libbb.h" |
| 301 | #include "common_bufsiz.h" | 340 | #include "common_bufsiz.h" |
| @@ -330,10 +369,6 @@ | |||
| 330 | #define IOBUF_SIZE 8192 | 369 | #define IOBUF_SIZE 8192 |
| 331 | #define MAX_HTTP_HEADERS_SIZE (32*1024) | 370 | #define MAX_HTTP_HEADERS_SIZE (32*1024) |
| 332 | 371 | ||
| 333 | #define HEADER_READ_TIMEOUT 60 | ||
| 334 | |||
| 335 | static void send_REQUEST_TIMEOUT_and_exit(int sig) NORETURN; | ||
| 336 | |||
| 337 | #define STR1(s) #s | 372 | #define STR1(s) #s |
| 338 | #define STR(s) STR1(s) | 373 | #define STR(s) STR1(s) |
| 339 | 374 | ||
| @@ -463,7 +498,15 @@ static const struct { | |||
| 463 | }; | 498 | }; |
| 464 | 499 | ||
| 465 | struct globals { | 500 | struct globals { |
| 466 | int verbose; /* must be int (used by getopt32) */ | 501 | smallint flg_deny_all; |
| 502 | #if ENABLE_FEATURE_HTTPD_GZIP | ||
| 503 | /* client can handle gzip / we are going to send gzip */ | ||
| 504 | smallint accept_gzip; | ||
| 505 | smallint content_gzip; | ||
| 506 | #endif | ||
| 507 | #if ENABLE_FEATURE_HTTPD_CGI | ||
| 508 | smallint cgi_output; | ||
| 509 | #endif | ||
| 467 | #if ENABLE_PLATFORM_MINGW32 | 510 | #if ENABLE_PLATFORM_MINGW32 |
| 468 | smallint foreground; | 511 | smallint foreground; |
| 469 | # if ENABLE_FEATURE_HTTPD_CGI | 512 | # if ENABLE_FEATURE_HTTPD_CGI |
| @@ -471,11 +514,7 @@ struct globals { | |||
| 471 | char **server_argv; | 514 | char **server_argv; |
| 472 | # endif | 515 | # endif |
| 473 | #endif | 516 | #endif |
| 474 | smallint flg_deny_all; | 517 | int verbose; /* must be int (used by getopt32) */ |
| 475 | #if ENABLE_FEATURE_HTTPD_GZIP | ||
| 476 | /* client can handle gzip / we are going to send gzip */ | ||
| 477 | smallint content_gzip; | ||
| 478 | #endif | ||
| 479 | time_t last_mod; | 518 | time_t last_mod; |
| 480 | #if ENABLE_FEATURE_HTTPD_ETAG | 519 | #if ENABLE_FEATURE_HTTPD_ETAG |
| 481 | char *if_none_match; | 520 | char *if_none_match; |
| @@ -494,8 +533,11 @@ struct globals { | |||
| 494 | Htaccess_IP *ip_a_d; /* config allow/deny lines */ | 533 | Htaccess_IP *ip_a_d; /* config allow/deny lines */ |
| 495 | #endif | 534 | #endif |
| 496 | 535 | ||
| 497 | IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;) | 536 | #if !ENABLE_PLATFORM_MINGW32 |
| 498 | IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) | 537 | pid_t parent_pid; |
| 538 | int children_fd; | ||
| 539 | int conn_limit; | ||
| 540 | #endif | ||
| 499 | 541 | ||
| 500 | off_t file_size; /* -1 - unknown */ | 542 | off_t file_size; /* -1 - unknown */ |
| 501 | #if ENABLE_FEATURE_HTTPD_RANGES | 543 | #if ENABLE_FEATURE_HTTPD_RANGES |
| @@ -505,19 +547,24 @@ struct globals { | |||
| 505 | #endif | 547 | #endif |
| 506 | 548 | ||
| 507 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 549 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
| 550 | const char *g_realm; | ||
| 551 | char *remoteuser; | ||
| 508 | Htaccess *g_auth; /* config user:password lines */ | 552 | Htaccess *g_auth; /* config user:password lines */ |
| 509 | #endif | 553 | #endif |
| 510 | Htaccess *mime_a; /* config mime types */ | 554 | Htaccess *mime_a; /* config mime types */ |
| 511 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR | 555 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
| 512 | Htaccess *script_i; /* config script interpreters */ | 556 | Htaccess *script_i; /* config script interpreters */ |
| 513 | #endif | 557 | #endif |
| 514 | char *iobuf; /* [IOBUF_SIZE] */ | ||
| 515 | #define hdr_buf bb_common_bufsiz1 | ||
| 516 | #define sizeof_hdr_buf COMMON_BUFSIZE | ||
| 517 | char *hdr_ptr; | 558 | char *hdr_ptr; |
| 518 | int hdr_cnt; | 559 | int hdr_cnt; |
| 519 | #if ENABLE_FEATURE_HTTPD_ETAG | 560 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY |
| 520 | char etag[sizeof("'%llx-%llx'") + 2 * sizeof(long long)*3]; | 561 | int POST_len; |
| 562 | #endif | ||
| 563 | #if !ENABLE_PLATFORM_MINGW32 | ||
| 564 | #if ENABLE_FEATURE_HTTPD_CGI | ||
| 565 | unsigned cgi_kill_timeout; | ||
| 566 | pid_t cgi_pid; | ||
| 567 | #endif | ||
| 521 | #endif | 568 | #endif |
| 522 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES | 569 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
| 523 | const char *http_error_page[ARRAY_SIZE(http_response_type)]; | 570 | const char *http_error_page[ARRAY_SIZE(http_response_type)]; |
| @@ -525,8 +572,19 @@ struct globals { | |||
| 525 | #if ENABLE_FEATURE_HTTPD_PROXY | 572 | #if ENABLE_FEATURE_HTTPD_PROXY |
| 526 | Htaccess_Proxy *proxy; | 573 | Htaccess_Proxy *proxy; |
| 527 | #endif | 574 | #endif |
| 575 | char iobuf[IOBUF_SIZE] ALIGN8; | ||
| 576 | |||
| 577 | /* We also use the common buffer as a global buffer: | ||
| 578 | * = as input buffer for request line, headers, and POSTDATA | ||
| 579 | * = when retrieving ordinary file (not CGI, proxy, etc), we generate and store file's etag | ||
| 580 | */ | ||
| 581 | #define hdr_buf bb_common_bufsiz1 | ||
| 582 | #define sizeof_hdr_buf COMMON_BUFSIZE | ||
| 583 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
| 584 | #define etag bb_common_bufsiz1 | ||
| 585 | #endif | ||
| 528 | }; | 586 | }; |
| 529 | #define G (*ptr_to_globals) | 587 | #define G (*OFFSET_PTR_TO_GLOBALS) |
| 530 | #define verbose (G.verbose ) | 588 | #define verbose (G.verbose ) |
| 531 | #if ENABLE_PLATFORM_MINGW32 | 589 | #if ENABLE_PLATFORM_MINGW32 |
| 532 | #define foreground (G.foreground ) | 590 | #define foreground (G.foreground ) |
| @@ -535,8 +593,10 @@ struct globals { | |||
| 535 | #endif | 593 | #endif |
| 536 | #define flg_deny_all (G.flg_deny_all ) | 594 | #define flg_deny_all (G.flg_deny_all ) |
| 537 | #if ENABLE_FEATURE_HTTPD_GZIP | 595 | #if ENABLE_FEATURE_HTTPD_GZIP |
| 596 | # define accept_gzip (G.accept_gzip ) | ||
| 538 | # define content_gzip (G.content_gzip ) | 597 | # define content_gzip (G.content_gzip ) |
| 539 | #else | 598 | #else |
| 599 | # define accept_gzip 0 | ||
| 540 | # define content_gzip 0 | 600 | # define content_gzip 0 |
| 541 | #endif | 601 | #endif |
| 542 | #define bind_addr_or_port (G.bind_addr_or_port) | 602 | #define bind_addr_or_port (G.bind_addr_or_port) |
| @@ -565,14 +625,14 @@ enum { | |||
| 565 | #define g_auth (G.g_auth ) | 625 | #define g_auth (G.g_auth ) |
| 566 | #define mime_a (G.mime_a ) | 626 | #define mime_a (G.mime_a ) |
| 567 | #define script_i (G.script_i ) | 627 | #define script_i (G.script_i ) |
| 568 | #define iobuf (G.iobuf ) | ||
| 569 | #define hdr_ptr (G.hdr_ptr ) | 628 | #define hdr_ptr (G.hdr_ptr ) |
| 570 | #define hdr_cnt (G.hdr_cnt ) | 629 | #define hdr_cnt (G.hdr_cnt ) |
| 571 | #define http_error_page (G.http_error_page ) | 630 | #define http_error_page (G.http_error_page ) |
| 572 | #define proxy (G.proxy ) | 631 | #define proxy (G.proxy ) |
| 632 | #define iobuf (G.iobuf ) | ||
| 573 | #define INIT_G() do { \ | 633 | #define INIT_G() do { \ |
| 574 | setup_common_bufsiz(); \ | 634 | setup_common_bufsiz(); \ |
| 575 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | 635 | SET_OFFSET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ |
| 576 | IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \ | 636 | IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \ |
| 577 | IF_FEATURE_HTTPD_RANGES(range_start = -1;) \ | 637 | IF_FEATURE_HTTPD_RANGES(range_start = -1;) \ |
| 578 | bind_addr_or_port = STR(CONFIG_FEATURE_HTTPD_PORT_DEFAULT); \ | 638 | bind_addr_or_port = STR(CONFIG_FEATURE_HTTPD_PORT_DEFAULT); \ |
| @@ -580,6 +640,10 @@ enum { | |||
| 580 | file_size = -1; \ | 640 | file_size = -1; \ |
| 581 | } while (0) | 641 | } while (0) |
| 582 | 642 | ||
| 643 | #define VERBOSE_1 (verbose) | ||
| 644 | #define VERBOSE_2 (verbose > 1) | ||
| 645 | /* TODO: make conditional on FEATURE_HTTPD_MAXVERBOSE */ | ||
| 646 | #define VERBOSE_3 (verbose > 2) | ||
| 583 | 647 | ||
| 584 | #if !ENABLE_PLATFORM_MINGW32 | 648 | #if !ENABLE_PLATFORM_MINGW32 |
| 585 | #define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) | 649 | #define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) |
| @@ -717,6 +781,7 @@ static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp) | |||
| 717 | * path Path where to look for httpd.conf (without filename). | 781 | * path Path where to look for httpd.conf (without filename). |
| 718 | * flag Type of the parse request. | 782 | * flag Type of the parse request. |
| 719 | */ | 783 | */ |
| 784 | //TODO: use parse_config() from libbb? | ||
| 720 | /* flag param: */ | 785 | /* flag param: */ |
| 721 | enum { | 786 | enum { |
| 722 | FIRST_PARSE = 0, /* path will be "/etc" */ | 787 | FIRST_PARSE = 0, /* path will be "/etc" */ |
| @@ -1081,7 +1146,8 @@ static void decodeBase64(char *data) | |||
| 1081 | */ | 1146 | */ |
| 1082 | static int openServer(void) | 1147 | static int openServer(void) |
| 1083 | { | 1148 | { |
| 1084 | unsigned n = bb_strtou(bind_addr_or_port, NULL, 10); | 1149 | unsigned n; |
| 1150 | n = bb_strtou(bind_addr_or_port, NULL, 10); | ||
| 1085 | if (!errno && n && n <= 0xffff) | 1151 | if (!errno && n && n <= 0xffff) |
| 1086 | n = create_and_bind_stream_or_die(NULL, n); | 1152 | n = create_and_bind_stream_or_die(NULL, n); |
| 1087 | else | 1153 | else |
| @@ -1092,23 +1158,23 @@ static int openServer(void) | |||
| 1092 | 1158 | ||
| 1093 | /* | 1159 | /* |
| 1094 | * Log the connection closure and exit. | 1160 | * Log the connection closure and exit. |
| 1161 | * Two variants: one signals EOF (clean termination), | ||
| 1162 | * the other might signal that connection is reset, not closed normally | ||
| 1163 | * (usually RST is sent if there is unsent buffered data in the socket buffer). | ||
| 1095 | */ | 1164 | */ |
| 1096 | static void log_and_exit(void) NORETURN; | 1165 | static void log_and_exit(void) NORETURN; |
| 1097 | static void log_and_exit(void) | 1166 | static void log_and_exit(void) |
| 1098 | { | 1167 | { |
| 1099 | /* Paranoia. IE said to be buggy. It may send some extra data | 1168 | if (VERBOSE_3) |
| 1100 | * or be confused by us just exiting without SHUT_WR. Oh well. */ | ||
| 1101 | shutdown(1, SHUT_WR); | ||
| 1102 | /* Why?? | ||
| 1103 | (this also messes up stdin when user runs httpd -i from terminal) | ||
| 1104 | ndelay_on(0); | ||
| 1105 | while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0) | ||
| 1106 | continue; | ||
| 1107 | */ | ||
| 1108 | |||
| 1109 | if (verbose > 2) | ||
| 1110 | bb_simple_error_msg("closed"); | 1169 | bb_simple_error_msg("closed"); |
| 1111 | _exit(xfunc_error_retval); | 1170 | _exit_SUCCESS(); |
| 1171 | } | ||
| 1172 | static void send_EOF_and_exit(void) NORETURN; | ||
| 1173 | static void send_EOF_and_exit(void) | ||
| 1174 | { | ||
| 1175 | /* This makes sure on TCP level, the connection is closed with FIN, not RST */ | ||
| 1176 | shutdown(STDOUT_FILENO, SHUT_WR); | ||
| 1177 | log_and_exit(); | ||
| 1112 | } | 1178 | } |
| 1113 | 1179 | ||
| 1114 | /* | 1180 | /* |
| @@ -1286,7 +1352,7 @@ static void send_headers(unsigned responseNum) | |||
| 1286 | date_str, | 1352 | date_str, |
| 1287 | #endif | 1353 | #endif |
| 1288 | #if ENABLE_FEATURE_HTTPD_ETAG | 1354 | #if ENABLE_FEATURE_HTTPD_ETAG |
| 1289 | G.etag, | 1355 | etag, |
| 1290 | #endif | 1356 | #endif |
| 1291 | file_size | 1357 | file_size |
| 1292 | ); | 1358 | ); |
| @@ -1322,8 +1388,8 @@ static void send_headers(unsigned responseNum) | |||
| 1322 | fprintf(stderr, "headers: '%s'\n", iobuf); | 1388 | fprintf(stderr, "headers: '%s'\n", iobuf); |
| 1323 | } | 1389 | } |
| 1324 | if (full_write(STDOUT_FILENO, iobuf, len) != len) { | 1390 | if (full_write(STDOUT_FILENO, iobuf, len) != len) { |
| 1325 | if (verbose > 1) | 1391 | if (VERBOSE_1) |
| 1326 | bb_simple_perror_msg("error"); | 1392 | bb_simple_perror_msg("write error"); |
| 1327 | log_and_exit(); | 1393 | log_and_exit(); |
| 1328 | } | 1394 | } |
| 1329 | } | 1395 | } |
| @@ -1334,24 +1400,27 @@ static void send_headers_and_exit(int responseNum) | |||
| 1334 | IF_FEATURE_HTTPD_GZIP(content_gzip = 0;) | 1400 | IF_FEATURE_HTTPD_GZIP(content_gzip = 0;) |
| 1335 | file_size = -1; /* no Last-Modified:, ETag:, Content-Length: */ | 1401 | file_size = -1; /* no Last-Modified:, ETag:, Content-Length: */ |
| 1336 | send_headers(responseNum); | 1402 | send_headers(responseNum); |
| 1337 | log_and_exit(); | 1403 | send_EOF_and_exit(); |
| 1338 | } | 1404 | } |
| 1339 | 1405 | ||
| 1340 | /* | 1406 | /* |
| 1341 | * Read from the socket until '\n' or EOF. | 1407 | * Read from the socket until '\n' or EOF. |
| 1408 | * Data is returned in iobuf[]. | ||
| 1342 | * '\r' chars are removed. | 1409 | * '\r' chars are removed. |
| 1343 | * '\n' is replaced with NUL. | 1410 | * '\n' is replaced with NUL. |
| 1411 | * Control chars and 0x7f cause HTTP_BAD_REQUEST abort. | ||
| 1412 | * iobuf[] overflow causes HTTP_BAD_REQUEST abort. | ||
| 1344 | * Return number of characters read or 0 if nothing is read | 1413 | * Return number of characters read or 0 if nothing is read |
| 1345 | * ('\r' and '\n' are not counted). | 1414 | * ('\r' and '\n' are not counted). |
| 1346 | * Data is returned in iobuf. | ||
| 1347 | */ | 1415 | */ |
| 1348 | static unsigned get_line(void) | 1416 | static unsigned get_line(void) |
| 1349 | { | 1417 | { |
| 1350 | unsigned count; | 1418 | unsigned count; |
| 1351 | char c; | ||
| 1352 | 1419 | ||
| 1353 | count = 0; | 1420 | count = 0; |
| 1354 | while (1) { | 1421 | while (1) { |
| 1422 | unsigned char c; | ||
| 1423 | |||
| 1355 | if (hdr_cnt <= 0) { | 1424 | if (hdr_cnt <= 0) { |
| 1356 | #if ENABLE_PLATFORM_MINGW32 | 1425 | #if ENABLE_PLATFORM_MINGW32 |
| 1357 | int nfds = 1; | 1426 | int nfds = 1; |
| @@ -1359,13 +1428,12 @@ static unsigned get_line(void) | |||
| 1359 | 1428 | ||
| 1360 | switch (poll(&fds, nfds, HEADER_READ_TIMEOUT*1000)) { | 1429 | switch (poll(&fds, nfds, HEADER_READ_TIMEOUT*1000)) { |
| 1361 | case 0: | 1430 | case 0: |
| 1362 | send_REQUEST_TIMEOUT_and_exit(0); | 1431 | send_headers_and_exit(HTTP_REQUEST_TIMEOUT); |
| 1363 | break; | 1432 | break; |
| 1364 | case -1: | 1433 | case -1: |
| 1365 | bb_simple_perror_msg_and_die("poll"); | 1434 | bb_simple_perror_msg_and_die("poll"); |
| 1366 | break; | 1435 | break; |
| 1367 | } | 1436 | } |
| 1368 | #else | ||
| 1369 | alarm(HEADER_READ_TIMEOUT); | 1437 | alarm(HEADER_READ_TIMEOUT); |
| 1370 | #endif | 1438 | #endif |
| 1371 | hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); | 1439 | hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); |
| @@ -1375,13 +1443,24 @@ static unsigned get_line(void) | |||
| 1375 | } | 1443 | } |
| 1376 | hdr_cnt--; | 1444 | hdr_cnt--; |
| 1377 | c = *hdr_ptr++; | 1445 | c = *hdr_ptr++; |
| 1446 | |||
| 1447 | /* We really should only accept \n (for people debugging via telnet) | ||
| 1448 | * and \r\n, but... \r\n can split across read(), harder to check. */ | ||
| 1378 | if (c == '\r') | 1449 | if (c == '\r') |
| 1379 | continue; | 1450 | continue; |
| 1380 | if (c == '\n') | 1451 | if (c == '\n') |
| 1381 | break; | 1452 | break; |
| 1382 | iobuf[count] = c; | 1453 | |
| 1383 | if (count < (IOBUF_SIZE - 1)) /* check overflow */ | 1454 | /* rfc7230 allows tabs for header line continuation and as whitespace in values */ |
| 1384 | count++; | 1455 | if (c != '\t') { |
| 1456 | /* Control chars aren't allowed in headers */ | ||
| 1457 | if (c < ' ' || c == 0x7f) | ||
| 1458 | send_headers_and_exit(HTTP_BAD_REQUEST); | ||
| 1459 | /* hign bytes above 0x7f are heavily discouraged, but historically allowed */ | ||
| 1460 | } | ||
| 1461 | iobuf[count++] = c; | ||
| 1462 | if (count >= IOBUF_SIZE) | ||
| 1463 | send_headers_and_exit(HTTP_BAD_REQUEST); | ||
| 1385 | } | 1464 | } |
| 1386 | ret: | 1465 | ret: |
| 1387 | iobuf[count] = '\0'; | 1466 | iobuf[count] = '\0'; |
| @@ -1391,8 +1470,8 @@ static unsigned get_line(void) | |||
| 1391 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY | 1470 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY |
| 1392 | 1471 | ||
| 1393 | /* gcc 4.2.1 fares better with NOINLINE */ | 1472 | /* gcc 4.2.1 fares better with NOINLINE */ |
| 1394 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN; | 1473 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) NORETURN; |
| 1395 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) | 1474 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) |
| 1396 | { | 1475 | { |
| 1397 | enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */ | 1476 | enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */ |
| 1398 | struct pollfd pfd[3]; | 1477 | struct pollfd pfd[3]; |
| @@ -1402,20 +1481,18 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
| 1402 | /* iobuf is used for CGI -> network data, | 1481 | /* iobuf is used for CGI -> network data, |
| 1403 | * hdr_buf is for network -> CGI data (POSTDATA) */ | 1482 | * hdr_buf is for network -> CGI data (POSTDATA) */ |
| 1404 | 1483 | ||
| 1405 | /* If CGI dies, we still want to correctly finish reading its output | ||
| 1406 | * and send it to the peer. So please no SIGPIPEs! */ | ||
| 1407 | signal(SIGPIPE, SIG_IGN); | ||
| 1408 | |||
| 1409 | // We inconsistently handle a case when more POSTDATA from network | 1484 | // We inconsistently handle a case when more POSTDATA from network |
| 1410 | // is coming than we expected. We may give *some part* of that | 1485 | // is coming than we expected. We may give *some part* of that |
| 1411 | // extra data to CGI. | 1486 | // extra data to CGI. |
| 1412 | 1487 | ||
| 1413 | //if (hdr_cnt > post_len) { | 1488 | /* We often have the start of POSTDATA buffered in hdr_buf already |
| 1489 | * by the header lines reading logic */ | ||
| 1490 | //if (hdr_cnt > G.POST_len) { | ||
| 1414 | // /* We got more POSTDATA from network than we expected */ | 1491 | // /* We got more POSTDATA from network than we expected */ |
| 1415 | // hdr_cnt = post_len; | 1492 | // hdr_cnt = G.POST_len; |
| 1416 | //} | 1493 | //} |
| 1417 | post_len -= hdr_cnt; | 1494 | G.POST_len -= hdr_cnt; |
| 1418 | /* post_len - number of POST bytes not yet read from network */ | 1495 | //bb_error_msg("G.POST_len:%d", G.POST_len); |
| 1419 | 1496 | ||
| 1420 | /* NB: breaking out of this loop jumps to log_and_exit() */ | 1497 | /* NB: breaking out of this loop jumps to log_and_exit() */ |
| 1421 | out_cnt = 0; | 1498 | out_cnt = 0; |
| @@ -1434,41 +1511,57 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
| 1434 | /*pfd[FROM_CGI].events = POLLIN; - moved out of loop */ | 1511 | /*pfd[FROM_CGI].events = POLLIN; - moved out of loop */ |
| 1435 | /*pfd[FROM_CGI].revents = 0; - not needed */ | 1512 | /*pfd[FROM_CGI].revents = 0; - not needed */ |
| 1436 | 1513 | ||
| 1437 | /* gcc-4.8.0 still doesnt fill two shorts with one insn :( */ | 1514 | /* gcc-4.8.0 still doesn't fill two shorts with one insn :( */ |
| 1438 | /* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47059 */ | 1515 | /* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47059 */ |
| 1439 | /* hopefully one day it will... */ | 1516 | /* hopefully one day it will... */ |
| 1440 | pfd[TO_CGI].events = POLLOUT; | 1517 | pfd[TO_CGI].events = POLLOUT; |
| 1441 | pfd[TO_CGI].revents = 0; /* needed! */ | 1518 | pfd[TO_CGI].revents = 0; /* needed! */ |
| 1442 | 1519 | ||
| 1443 | if (toCgi_wr && hdr_cnt <= 0) { | 1520 | if (toCgi_wr && hdr_cnt <= 0) { |
| 1444 | if (post_len > 0) { | 1521 | if (G.POST_len > 0) { |
| 1445 | /* Expect more POST data from network */ | 1522 | /* Expect more POST data from network */ |
| 1446 | pfd[0].fd = 0; | 1523 | pfd[0].fd = 0; |
| 1524 | #if !ENABLE_PLATFORM_MINGW32 | ||
| 1525 | /* Kill ourselves if no data arrives in N seconds */ | ||
| 1526 | alarm(DATA_READ_TIMEOUT); | ||
| 1527 | #endif | ||
| 1447 | } else { | 1528 | } else { |
| 1448 | /* post_len <= 0 && hdr_cnt <= 0: | 1529 | /* G.POST_len <= 0 && hdr_cnt <= 0: |
| 1449 | * no more POST data to CGI, | 1530 | * no more POST data to CGI, |
| 1450 | * let CGI see EOF on CGI's stdin */ | 1531 | * let CGI see EOF on CGI's stdin */ |
| 1451 | if (toCgi_wr != fromCgi_rd) | 1532 | if (!ENABLE_FEATURE_HTTPD_PROXY || (toCgi_wr != fromCgi_rd)) { |
| 1452 | close(toCgi_wr); | 1533 | close(toCgi_wr); |
| 1534 | #if !ENABLE_PLATFORM_MINGW32 | ||
| 1535 | alarm(G.cgi_kill_timeout); | ||
| 1536 | #endif | ||
| 1537 | } else { | ||
| 1538 | /* proxying a socket, there is no CGI */ | ||
| 1539 | alarm(0); | ||
| 1540 | } | ||
| 1453 | toCgi_wr = 0; | 1541 | toCgi_wr = 0; |
| 1454 | } | 1542 | } |
| 1455 | } | 1543 | } |
| 1456 | 1544 | ||
| 1457 | /* Now wait on the set of sockets */ | 1545 | /* Now wait on the set of sockets */ |
| 1546 | /* Poll whether TO_CGI is ready to accept writes *only* if we have some POSTDATA to give to it */ | ||
| 1458 | count = safe_poll(pfd, hdr_cnt > 0 ? TO_CGI+1 : FROM_CGI+1, -1); | 1547 | count = safe_poll(pfd, hdr_cnt > 0 ? TO_CGI+1 : FROM_CGI+1, -1); |
| 1548 | |||
| 1459 | if (count <= 0) { | 1549 | if (count <= 0) { |
| 1460 | #if 0 | 1550 | #if 0 /* This doesn't work since we SIG_IGNed SIGCHLD (which means kernel auto-reaps our children) */ |
| 1461 | if (safe_waitpid(pid, &status, WNOHANG) <= 0) { | 1551 | if (VERBOSE_3) { |
| 1462 | /* Weird. CGI didn't exit and no fd's | 1552 | int status; |
| 1463 | * are ready, yet poll returned?! */ | 1553 | if (safe_waitpid(-1, &status, WNOHANG) <= 0) { |
| 1464 | continue; | 1554 | /* Weird. CGI didn't exit and no fd's |
| 1555 | * are ready, yet poll returned?! */ | ||
| 1556 | continue; | ||
| 1557 | } | ||
| 1558 | if (DEBUG && WIFEXITED(status)) | ||
| 1559 | bb_error_msg("CGI exited, status=%u", WEXITSTATUS(status)); | ||
| 1560 | if (DEBUG && WIFSIGNALED(status)) | ||
| 1561 | bb_error_msg("CGI killed, signal=%u", WTERMSIG(status)); | ||
| 1465 | } | 1562 | } |
| 1466 | if (DEBUG && WIFEXITED(status)) | ||
| 1467 | bb_error_msg("CGI exited, status=%u", WEXITSTATUS(status)); | ||
| 1468 | if (DEBUG && WIFSIGNALED(status)) | ||
| 1469 | bb_error_msg("CGI killed, signal=%u", WTERMSIG(status)); | ||
| 1470 | #endif | 1563 | #endif |
| 1471 | break; | 1564 | send_EOF_and_exit(); |
| 1472 | } | 1565 | } |
| 1473 | 1566 | ||
| 1474 | if (pfd[TO_CGI].revents) { | 1567 | if (pfd[TO_CGI].revents) { |
| @@ -1484,80 +1577,92 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
| 1484 | hdr_cnt -= count; | 1577 | hdr_cnt -= count; |
| 1485 | } else { | 1578 | } else { |
| 1486 | /* EOF/broken pipe to CGI, stop piping POST data */ | 1579 | /* EOF/broken pipe to CGI, stop piping POST data */ |
| 1487 | hdr_cnt = post_len = 0; | 1580 | hdr_cnt = G.POST_len = 0; |
| 1488 | } | 1581 | } |
| 1489 | } | 1582 | } |
| 1490 | 1583 | else /* After one poll(), we either write to CGI, or read from network. Never both */ | |
| 1491 | if (pfd[0].revents) { | 1584 | if (pfd[0].revents) { |
| 1492 | /* post_len > 0 && hdr_cnt == 0 here */ | 1585 | /* G.POST_len > 0 && hdr_cnt == 0 here */ |
| 1493 | /* We expect data, prev data portion is eaten by CGI | 1586 | /* We expect data, prev data portion is eaten by CGI |
| 1494 | * and there *is* data to read from the peer | 1587 | * and there *is* POSTDATA to read from the peer |
| 1495 | * (POSTDATA) */ | 1588 | */ |
| 1496 | //count = post_len > (int)sizeof_hdr_buf ? (int)sizeof_hdr_buf : post_len; | 1589 | //count = G.POST_len > (int)sizeof_hdr_buf ? (int)sizeof_hdr_buf : G.POST_len; |
| 1497 | //count = safe_read(STDIN_FILENO, hdr_buf, count); | 1590 | //count = safe_read(STDIN_FILENO, hdr_buf, count); |
| 1498 | count = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); | 1591 | count = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); |
| 1499 | if (count > 0) { | 1592 | if (count > 0) { |
| 1500 | hdr_cnt = count; | 1593 | hdr_cnt = count; |
| 1501 | hdr_ptr = hdr_buf; | 1594 | hdr_ptr = hdr_buf; |
| 1502 | post_len -= count; | 1595 | G.POST_len -= count; /* can go negative (peer wrote more than expected POSTDATA) */ |
| 1503 | } else { | 1596 | } else { |
| 1504 | /* no more POST data can be read */ | 1597 | /* no more POST data can be read */ |
| 1505 | post_len = 0; | 1598 | G.POST_len = 0; |
| 1506 | } | 1599 | } |
| 1507 | } | 1600 | } |
| 1508 | 1601 | ||
| 1509 | if (pfd[FROM_CGI].revents) { | 1602 | if (pfd[FROM_CGI].revents) { |
| 1510 | /* There is something to read from CGI */ | 1603 | /* There is something to read from CGI */ |
| 1511 | char *rbuf = iobuf; | 1604 | /* Still buffering CGI output? */ |
| 1512 | |||
| 1513 | /* Are we still buffering CGI output? */ | ||
| 1514 | if (out_cnt >= 0) { | 1605 | if (out_cnt >= 0) { |
| 1515 | /* HTTP_200[] has single "\r\n" at the end. | 1606 | /* HTTP_200[] has single "\r\n" at the end. |
| 1516 | * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html, | 1607 | * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html, |
| 1517 | * CGI scripts MUST send their own header terminated by | 1608 | * CGI scripts MUST send their own header terminated by |
| 1518 | * empty line, then data. That's why we have only one | 1609 | * empty line, then data. That's why we have only one |
| 1519 | * <cr><lf> pair here. We will output "200 OK" line | 1610 | * <cr><lf> pair here. We will output "200 OK" line |
| 1520 | * if needed, but CGI still has to provide blank line | 1611 | * if needed, but CGI still has to provide the blank line |
| 1521 | * between header and body */ | 1612 | * between header and body */ |
| 1522 | 1613 | ||
| 1523 | /* Must use safe_read, not full_read, because | 1614 | /* Must use safe_read, not full_read, because |
| 1524 | * CGI may output a few first bytes and then wait | 1615 | * CGI may output a few first bytes and then wait |
| 1525 | * for POSTDATA without closing stdout. | 1616 | * for POSTDATA without closing stdout. |
| 1526 | * With full_read we may wait here forever. */ | 1617 | * With full_read we may wait here forever. */ |
| 1527 | count = safe_read(fromCgi_rd, rbuf + out_cnt, IOBUF_SIZE - 8); | 1618 | count = safe_read(fromCgi_rd, iobuf + out_cnt, IOBUF_SIZE - 8); |
| 1619 | // "- 8" is important, out_cnt can be up to 7 | ||
| 1528 | if (count <= 0) { | 1620 | if (count <= 0) { |
| 1529 | /* eof (or error) and there was no "HTTP", | 1621 | /* EOF (or error, and out_cnt=0..7 |
| 1530 | * send "HTTP/1.1 200 OK\r\n", then send received data */ | 1622 | * send "HTTP/1.1 200 OK\r\n", then send received 0..7 bytes */ |
| 1531 | if (out_cnt) { | 1623 | goto write_HTTP_200_OK; |
| 1532 | full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1); | 1624 | /* we'll read once more later, in "no longer buffering" |
| 1533 | full_write(STDOUT_FILENO, rbuf, out_cnt); | 1625 | * code path, get another EOF there and exit */ |
| 1534 | } | ||
| 1535 | break; /* CGI stdout is closed, exiting */ | ||
| 1536 | } | 1626 | } |
| 1537 | out_cnt += count; | 1627 | out_cnt += count; |
| 1538 | count = 0; | 1628 | count = 0; |
| 1539 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ | 1629 | if (out_cnt >= 8) { |
| 1540 | if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { | 1630 | //FIXME: "Status: " is not required to be the first header! It can be anywhere! |
| 1541 | /* send "HTTP/1.1 " */ | 1631 | //FIXME: many servers also check "Location: ". If it exists but "Status: " does _not_, "302 Found" is assumed instead of "200 OK". |
| 1542 | if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) | 1632 | uint64_t str8 = *(uint64_t*)iobuf; |
| 1543 | break; | 1633 | //bb_error_msg("from cgi:'%.*s'", out_cnt, iobuf); |
| 1544 | /* skip "Status: " (including space, sending "HTTP/1.1 NNN" is wrong) */ | 1634 | |
| 1545 | rbuf += 8; | 1635 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ |
| 1546 | count = out_cnt - 8; | 1636 | //if (memcmp(iobuf, "Status: ", 8) == 0) |
| 1547 | out_cnt = -1; /* buffering off */ | 1637 | if (str8 == PACK64_LITERAL_STR("Status: ")) { |
| 1548 | } else if (out_cnt >= 4) { | 1638 | /* Replace "Status: " */ |
| 1549 | /* Did CGI add "HTTP"? */ | 1639 | /* with "HTTP/1.1 " */ |
| 1550 | if (memcmp(rbuf, HTTP_200, 4) != 0) { | 1640 | memmove(iobuf + 1, iobuf, out_cnt); |
| 1551 | /* there is no "HTTP", do it ourself */ | 1641 | out_cnt += 1; |
| 1642 | memcpy(iobuf, HTTP_200, 9); | ||
| 1643 | if (verbose) { | ||
| 1644 | char *end = iobuf + 9; | ||
| 1645 | int cnt = out_cnt - 9; | ||
| 1646 | while ((unsigned char)*end >= ' ' && (unsigned char)*end < 0x7f && cnt > 0) | ||
| 1647 | end++, cnt--; | ||
| 1648 | bb_error_msg("cgi response:'%.*s'", (int)(end - (iobuf + 9)), iobuf + 9); | ||
| 1649 | } | ||
| 1650 | } | ||
| 1651 | //NB: Apache has no such autodetection. It always adds its own HTTP/1.x header, | ||
| 1652 | //unless the CGI name starts with "nph-", in which case it passes its output verbatim to network. | ||
| 1653 | else /* Did CGI send "HTTP/1.1"? */ | ||
| 1654 | //if (memcmp(iobuf, HTTP_200, 8) != 0) | ||
| 1655 | if (str8 != PACK64_LITERAL_STR(HTTP_200)) { | ||
| 1656 | write_HTTP_200_OK: | ||
| 1657 | /* no, send "HTTP/1.1 200 OK\r\n" ourself */ | ||
| 1552 | if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1) | 1658 | if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1) |
| 1553 | break; | 1659 | break; |
| 1660 | if (verbose) | ||
| 1661 | bb_error_msg("cgi response:%u", 200); | ||
| 1554 | } | 1662 | } |
| 1555 | /* Commented out: | 1663 | /* Used to add "Content-type: text/plain\r\n\r\n" here if CGI didn't, |
| 1556 | if (!strstr(rbuf, "ontent-")) { | 1664 | * but it's wrong. Counter-example of valid CGI without Content-type: |
| 1557 | full_write(s, "Content-type: text/plain\r\n\r\n", 28); | 1665 | * echo -en "Status: 302 Found\r\n" |
| 1558 | } | ||
| 1559 | * Counter-example of valid CGI without Content-type: | ||
| 1560 | * echo -en "HTTP/1.1 302 Found\r\n" | ||
| 1561 | * echo -en "Location: http://www.busybox.net\r\n" | 1666 | * echo -en "Location: http://www.busybox.net\r\n" |
| 1562 | * echo -en "\r\n" | 1667 | * echo -en "\r\n" |
| 1563 | */ | 1668 | */ |
| @@ -1565,13 +1670,15 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
| 1565 | out_cnt = -1; /* buffering off */ | 1670 | out_cnt = -1; /* buffering off */ |
| 1566 | } | 1671 | } |
| 1567 | } else { | 1672 | } else { |
| 1568 | count = safe_read(fromCgi_rd, rbuf, IOBUF_SIZE); | 1673 | count = safe_read(fromCgi_rd, iobuf, IOBUF_SIZE); |
| 1569 | if (count <= 0) | 1674 | if (count <= 0) |
| 1570 | break; /* eof (or error) */ | 1675 | send_EOF_and_exit(); /* EOF (or error) */ |
| 1571 | } | 1676 | } |
| 1572 | if (full_write(STDOUT_FILENO, rbuf, count) != count) | 1677 | IF_FEATURE_HTTPD_CGI(G.cgi_output = 1;) |
| 1678 | //FIXME: many (most?) servers translate bare "\n" to "\r\n", only in the headers, not body (the part after empty line) | ||
| 1679 | if (full_write(STDOUT_FILENO, iobuf, count) != count) | ||
| 1573 | break; | 1680 | break; |
| 1574 | dbg("cgi read %d bytes: '%.*s'\n", count, count, rbuf); | 1681 | dbg("cgi read %d bytes: '%.*s'\n", count, count, iobuf); |
| 1575 | } /* if (pfd[FROM_CGI].revents) */ | 1682 | } /* if (pfd[FROM_CGI].revents) */ |
| 1576 | } /* while (1) */ | 1683 | } /* while (1) */ |
| 1577 | log_and_exit(); | 1684 | log_and_exit(); |
| @@ -1616,7 +1723,6 @@ static void cgi_handler(char **argv) | |||
| 1616 | bb_perror_msg("can't execute '%s'", argv[0]); | 1723 | bb_perror_msg("can't execute '%s'", argv[0]); |
| 1617 | error_execing_cgi: | 1724 | error_execing_cgi: |
| 1618 | /* send to stdout */ | 1725 | /* send to stdout */ |
| 1619 | iobuf = xmalloc(IOBUF_SIZE); | ||
| 1620 | send_headers_and_exit(HTTP_NOT_FOUND); | 1726 | send_headers_and_exit(HTTP_NOT_FOUND); |
| 1621 | } | 1727 | } |
| 1622 | # endif | 1728 | # endif |
| @@ -1636,24 +1742,21 @@ static void setenv1(const char *name, const char *value) | |||
| 1636 | * Parameters: | 1742 | * Parameters: |
| 1637 | * const char *url The requested URL (with leading /). | 1743 | * const char *url The requested URL (with leading /). |
| 1638 | * const char *orig_uri The original URI before rewriting (if any) | 1744 | * const char *orig_uri The original URI before rewriting (if any) |
| 1639 | * int post_len Length of the POST body. | ||
| 1640 | */ | 1745 | */ |
| 1641 | static void send_cgi_and_exit( | 1746 | static void send_cgi_and_exit( |
| 1642 | const char *url, | 1747 | const char *url, |
| 1643 | const char *orig_uri, | 1748 | const char *orig_uri, |
| 1644 | const char *request, | 1749 | const char *request) NORETURN; |
| 1645 | int post_len) NORETURN; | ||
| 1646 | static void send_cgi_and_exit( | 1750 | static void send_cgi_and_exit( |
| 1647 | const char *url, | 1751 | const char *url, |
| 1648 | const char *orig_uri, | 1752 | const char *orig_uri, |
| 1649 | const char *request, | 1753 | const char *request) |
| 1650 | int post_len) | ||
| 1651 | { | 1754 | { |
| 1652 | struct fd_pair fromCgi; /* CGI -> httpd pipe */ | 1755 | struct fd_pair fromCgi; /* CGI -> httpd pipe */ |
| 1653 | struct fd_pair toCgi; /* httpd -> CGI pipe */ | 1756 | struct fd_pair toCgi; /* httpd -> CGI pipe */ |
| 1654 | char *script, *last_slash; | 1757 | char *script, *last_slash; |
| 1655 | int pid; | ||
| 1656 | #if ENABLE_PLATFORM_MINGW32 | 1758 | #if ENABLE_PLATFORM_MINGW32 |
| 1759 | int pid; | ||
| 1657 | const char *script_dir; | 1760 | const char *script_dir; |
| 1658 | char **argv; | 1761 | char **argv; |
| 1659 | #endif | 1762 | #endif |
| @@ -1728,19 +1831,19 @@ static void send_cgi_and_exit( | |||
| 1728 | setenv1("REMOTE_ADDR", p); | 1831 | setenv1("REMOTE_ADDR", p); |
| 1729 | if (cp) { | 1832 | if (cp) { |
| 1730 | *cp = ':'; | 1833 | *cp = ':'; |
| 1731 | #if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV | 1834 | # if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV |
| 1732 | setenv1("REMOTE_PORT", cp + 1); | 1835 | setenv1("REMOTE_PORT", cp + 1); |
| 1733 | #endif | 1836 | # endif |
| 1734 | } | 1837 | } |
| 1735 | } | 1838 | } |
| 1736 | if (post_len) | 1839 | if (G.POST_len > 0) |
| 1737 | putenv(xasprintf("CONTENT_LENGTH=%u", post_len)); | 1840 | putenv(xasprintf("CONTENT_LENGTH=%u", G.POST_len)); |
| 1738 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 1841 | # if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
| 1739 | if (remoteuser) { | 1842 | if (remoteuser) { |
| 1740 | setenv1("REMOTE_USER", remoteuser); | 1843 | setenv1("REMOTE_USER", remoteuser); |
| 1741 | putenv((char*)"AUTH_TYPE=Basic"); | 1844 | putenv((char*)"AUTH_TYPE=Basic"); |
| 1742 | } | 1845 | } |
| 1743 | #endif | 1846 | # endif |
| 1744 | /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this, | 1847 | /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this, |
| 1745 | * just run "env SERVER_NAME=xyz httpd ..." instead */ | 1848 | * just run "env SERVER_NAME=xyz httpd ..." instead */ |
| 1746 | 1849 | ||
| @@ -1791,31 +1894,32 @@ static void send_cgi_and_exit( | |||
| 1791 | if (pid == -1) | 1894 | if (pid == -1) |
| 1792 | log_and_exit(); | 1895 | log_and_exit(); |
| 1793 | #else | 1896 | #else |
| 1794 | pid = vfork(); | 1897 | G.cgi_pid = vfork(); |
| 1795 | if (pid < 0) { | 1898 | if (G.cgi_pid < 0) { |
| 1796 | /* TODO: log perror? */ | 1899 | if (VERBOSE_1) |
| 1900 | bb_simple_perror_msg("vfork"); | ||
| 1797 | log_and_exit(); | 1901 | log_and_exit(); |
| 1798 | } | 1902 | } |
| 1799 | 1903 | ||
| 1800 | if (pid == 0) { | 1904 | if (G.cgi_pid == 0) { |
| 1801 | /* Child process */ | 1905 | /* Child process */ |
| 1802 | char *argv[3]; | 1906 | char *argv[3]; |
| 1803 | 1907 | ||
| 1804 | xfunc_error_retval = 242; | 1908 | /* -K SECS kills entire process group: set up one */ |
| 1909 | bb_setpgrp(); | ||
| 1805 | 1910 | ||
| 1806 | /* NB: close _first_, then move fds! */ | 1911 | /* NB: close _first_, then move fds! */ |
| 1807 | close(toCgi.wr); | 1912 | close(toCgi.wr); |
| 1808 | close(fromCgi.rd); | 1913 | close(fromCgi.rd); |
| 1809 | xmove_fd(toCgi.rd, 0); /* replace stdin with the pipe */ | 1914 | xmove_fd(toCgi.rd, 0); /* replace stdin with the pipe */ |
| 1810 | xmove_fd(fromCgi.wr, 1); /* replace stdout with the pipe */ | 1915 | xmove_fd(fromCgi.wr, 1); /* replace stdout with the pipe */ |
| 1811 | /* User seeing stderr output can be a security problem. | ||
| 1812 | * If CGI really wants that, it can always do dup itself. */ | ||
| 1813 | /* dup2(1, 2); */ | ||
| 1814 | 1916 | ||
| 1815 | /* Chdiring to script's dir */ | 1917 | /* Chdiring to script's dir */ |
| 1816 | script = last_slash; | 1918 | script = last_slash; |
| 1817 | if (script != url) { /* paranoia */ | 1919 | if (script != url) { /* paranoia */ |
| 1818 | *script = '\0'; | 1920 | *script = '\0'; |
| 1921 | if (VERBOSE_3) | ||
| 1922 | bb_error_msg("cd:%s", url + 1); | ||
| 1819 | if (chdir_or_warn(url + 1) != 0) { | 1923 | if (chdir_or_warn(url + 1) != 0) { |
| 1820 | goto error_execing_cgi; | 1924 | goto error_execing_cgi; |
| 1821 | } | 1925 | } |
| @@ -1827,7 +1931,7 @@ static void send_cgi_and_exit( | |||
| 1827 | argv[0] = script; | 1931 | argv[0] = script; |
| 1828 | argv[1] = NULL; | 1932 | argv[1] = NULL; |
| 1829 | 1933 | ||
| 1830 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR | 1934 | # if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
| 1831 | { | 1935 | { |
| 1832 | char *suffix = strrchr(script, '.'); | 1936 | char *suffix = strrchr(script, '.'); |
| 1833 | 1937 | ||
| @@ -1844,14 +1948,21 @@ static void send_cgi_and_exit( | |||
| 1844 | } | 1948 | } |
| 1845 | } | 1949 | } |
| 1846 | } | 1950 | } |
| 1847 | #endif | 1951 | # endif |
| 1848 | /* restore default signal dispositions for CGI process */ | 1952 | if (VERBOSE_2) |
| 1953 | bb_error_msg("exec:%s"IF_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR(" %s"), | ||
| 1954 | argv[0] | ||
| 1955 | IF_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR(, argv[1]) | ||
| 1956 | ); | ||
| 1957 | /* Restore default signal dispositions for CGI process */ | ||
| 1849 | bb_signals(0 | 1958 | bb_signals(0 |
| 1850 | | (1 << SIGCHLD) | 1959 | | (1 << SIGCHLD) |
| 1851 | | (1 << SIGPIPE) | 1960 | | (1 << SIGPIPE) |
| 1852 | | (1 << SIGHUP) | 1961 | /*| (1 << SIGHUP) - not needed, we have a handler, and exec resets signals with handlers to DFL */ |
| 1853 | , SIG_DFL); | 1962 | , SIG_DFL); |
| 1854 | 1963 | /* User seeing stderr output can be a security problem. | |
| 1964 | * If CGI really wants that, it can always do dup itself. */ | ||
| 1965 | /* dup2(1, 2); */ | ||
| 1855 | /* _NOT_ execvp. We do not search PATH. argv[0] is a filename | 1966 | /* _NOT_ execvp. We do not search PATH. argv[0] is a filename |
| 1856 | * without any dir components and will only match a file | 1967 | * without any dir components and will only match a file |
| 1857 | * in the current directory */ | 1968 | * in the current directory */ |
| @@ -1861,19 +1972,19 @@ static void send_cgi_and_exit( | |||
| 1861 | error_execing_cgi: | 1972 | error_execing_cgi: |
| 1862 | /* send to stdout | 1973 | /* send to stdout |
| 1863 | * (we are CGI here, our stdout is pumped to the net) */ | 1974 | * (we are CGI here, our stdout is pumped to the net) */ |
| 1864 | send_headers_and_exit(HTTP_NOT_FOUND); | 1975 | //send_headers_and_exit(HTTP_NOT_FOUND); |
| 1976 | //^^^ WRONG, this logs "closed", then the parent logs it again | ||
| 1977 | send_headers(HTTP_NOT_FOUND); | ||
| 1978 | _exit_FAILURE(); | ||
| 1865 | } /* end child */ | 1979 | } /* end child */ |
| 1980 | #endif | ||
| 1866 | 1981 | ||
| 1867 | /* Parent process */ | 1982 | /* Parent process */ |
| 1868 | 1983 | ||
| 1869 | /* Restore variables possibly changed by child */ | ||
| 1870 | xfunc_error_retval = 0; | ||
| 1871 | #endif | ||
| 1872 | |||
| 1873 | /* Pump data */ | 1984 | /* Pump data */ |
| 1874 | close(fromCgi.wr); | 1985 | close(fromCgi.wr); |
| 1875 | close(toCgi.rd); | 1986 | close(toCgi.rd); |
| 1876 | cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len); | 1987 | cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr); |
| 1877 | } | 1988 | } |
| 1878 | 1989 | ||
| 1879 | #endif /* FEATURE_HTTPD_CGI */ | 1990 | #endif /* FEATURE_HTTPD_CGI */ |
| @@ -1891,7 +2002,8 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
| 1891 | int fd; | 2002 | int fd; |
| 1892 | ssize_t count; | 2003 | ssize_t count; |
| 1893 | 2004 | ||
| 1894 | if (content_gzip) { | 2005 | #if ENABLE_FEATURE_HTTPD_GZIP |
| 2006 | if (accept_gzip) { | ||
| 1895 | /* does <url>.gz exist? Then use it instead */ | 2007 | /* does <url>.gz exist? Then use it instead */ |
| 1896 | char *gzurl = xasprintf("%s.gz", url); | 2008 | char *gzurl = xasprintf("%s.gz", url); |
| 1897 | fd = open(gzurl, O_RDONLY); | 2009 | fd = open(gzurl, O_RDONLY); |
| @@ -1901,11 +2013,13 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
| 1901 | fstat(fd, &sb); | 2013 | fstat(fd, &sb); |
| 1902 | file_size = sb.st_size; | 2014 | file_size = sb.st_size; |
| 1903 | last_mod = sb.st_mtime; | 2015 | last_mod = sb.st_mtime; |
| 2016 | content_gzip = 1; | ||
| 1904 | } else { | 2017 | } else { |
| 1905 | IF_FEATURE_HTTPD_GZIP(content_gzip = 0;) | ||
| 1906 | fd = open(url, O_RDONLY); | 2018 | fd = open(url, O_RDONLY); |
| 1907 | } | 2019 | } |
| 1908 | } else { | 2020 | } else |
| 2021 | #endif | ||
| 2022 | { | ||
| 1909 | fd = open(url, O_RDONLY); | 2023 | fd = open(url, O_RDONLY); |
| 1910 | /* file_size and last_mod are already populated */ | 2024 | /* file_size and last_mod are already populated */ |
| 1911 | } | 2025 | } |
| @@ -1913,26 +2027,23 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
| 1913 | dbg("can't open '%s'\n", url); | 2027 | dbg("can't open '%s'\n", url); |
| 1914 | /* Error pages are sent by using send_file_and_exit(SEND_BODY). | 2028 | /* Error pages are sent by using send_file_and_exit(SEND_BODY). |
| 1915 | * IOW: it is unsafe to call send_headers_and_exit | 2029 | * IOW: it is unsafe to call send_headers_and_exit |
| 1916 | * if what is SEND_BODY! Can recurse! */ | 2030 | * if "what" is SEND_BODY! Can recurse! */ |
| 1917 | if (what != SEND_BODY) | 2031 | if (what != SEND_BODY) |
| 1918 | send_headers_and_exit(HTTP_NOT_FOUND); | 2032 | send_headers_and_exit(HTTP_NOT_FOUND); |
| 1919 | log_and_exit(); | 2033 | send_EOF_and_exit(); |
| 1920 | } | 2034 | } |
| 1921 | #if ENABLE_FEATURE_HTTPD_ETAG | 2035 | #if ENABLE_FEATURE_HTTPD_ETAG |
| 1922 | /* ETag is "hex(last_mod)-hex(file_size)" e.g. "5e132e20-417" */ | 2036 | /* ETag is "hex(last_mod)-hex(file_size)" e.g. "5e132e20-417" */ |
| 1923 | sprintf(G.etag, "\"%"LL_FMT"x-%"LL_FMT"x\"", (unsigned long long)last_mod, (unsigned long long)file_size); | 2037 | sprintf(etag, "\"%"LL_FMT"x-%"LL_FMT"x\"", (unsigned long long)last_mod, (unsigned long long)file_size); |
| 1924 | 2038 | ||
| 1925 | if (G.if_none_match) { | 2039 | if (G.if_none_match) { |
| 1926 | dbg("If-None-Match:'%s' file's ETag:'%s'\n", G.if_none_match, G.etag); | 2040 | dbg("If-None-Match:'%s' file's ETag:'%s'\n", G.if_none_match, etag); |
| 1927 | /* Weak ETag comparision. | 2041 | /* Weak ETag comparision. |
| 1928 | * If-None-Match may have many ETags but they are quoted so we can use simple substring search */ | 2042 | * If-None-Match may have many ETags but they are quoted so we can use simple substring search */ |
| 1929 | if (strstr(G.if_none_match, G.etag)) | 2043 | if (strstr(G.if_none_match, etag)) |
| 1930 | send_headers_and_exit(HTTP_NOT_MODIFIED); | 2044 | send_headers_and_exit(HTTP_NOT_MODIFIED); |
| 1931 | } | 2045 | } |
| 1932 | #endif | 2046 | #endif |
| 1933 | /* If you want to know about EPIPE below | ||
| 1934 | * (happens if you abort downloads from local httpd): */ | ||
| 1935 | signal(SIGPIPE, SIG_IGN); | ||
| 1936 | 2047 | ||
| 1937 | /* If not found, default is to not send "Content-type:" */ | 2048 | /* If not found, default is to not send "Content-type:" */ |
| 1938 | /*found_mime_type = NULL; - already is */ | 2049 | /*found_mime_type = NULL; - already is */ |
| @@ -2026,6 +2137,8 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
| 2026 | #endif | 2137 | #endif |
| 2027 | if (what & SEND_HEADERS) | 2138 | if (what & SEND_HEADERS) |
| 2028 | send_headers(HTTP_OK); | 2139 | send_headers(HTTP_OK); |
| 2140 | |||
| 2141 | /* Sending BODY */ | ||
| 2029 | #if ENABLE_FEATURE_USE_SENDFILE | 2142 | #if ENABLE_FEATURE_USE_SENDFILE |
| 2030 | { | 2143 | { |
| 2031 | off_t offset; | 2144 | off_t offset; |
| @@ -2044,11 +2157,18 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
| 2044 | if (count < 0) { | 2157 | if (count < 0) { |
| 2045 | if (offset == range_start) /* was it the very 1st sendfile? */ | 2158 | if (offset == range_start) /* was it the very 1st sendfile? */ |
| 2046 | break; /* fall back to read/write loop */ | 2159 | break; /* fall back to read/write loop */ |
| 2047 | goto fin; | 2160 | if (VERBOSE_1) { |
| 2161 | // SO_SNDTIME on our socket causes write timeouts manifest as EAGAIN "Resource temporarily unavailable". | ||
| 2162 | // Not the best error message (when reading the log: "Er... what resource?!") | ||
| 2163 | if (errno == EAGAIN) | ||
| 2164 | errno = ETIMEDOUT; | ||
| 2165 | bb_simple_perror_msg("sendfile error"); | ||
| 2166 | } | ||
| 2167 | log_and_exit(); | ||
| 2048 | } | 2168 | } |
| 2049 | IF_FEATURE_HTTPD_RANGES(range_len -= count;) | 2169 | IF_FEATURE_HTTPD_RANGES(range_len -= count;) |
| 2050 | if (count == 0 || range_len == 0) | 2170 | if (count == 0 || range_len == 0) |
| 2051 | log_and_exit(); | 2171 | send_EOF_and_exit(); |
| 2052 | } | 2172 | } |
| 2053 | } | 2173 | } |
| 2054 | #endif | 2174 | #endif |
| @@ -2056,18 +2176,24 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
| 2056 | ssize_t n; | 2176 | ssize_t n; |
| 2057 | IF_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;) | 2177 | IF_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;) |
| 2058 | n = full_write(STDOUT_FILENO, iobuf, count); | 2178 | n = full_write(STDOUT_FILENO, iobuf, count); |
| 2059 | if (count != n) | 2179 | if (count != n) { |
| 2180 | if (VERBOSE_1 && n < 0) { | ||
| 2181 | if (errno == EAGAIN) | ||
| 2182 | errno = ETIMEDOUT; | ||
| 2183 | bb_simple_perror_msg("write error"); | ||
| 2184 | log_and_exit(); | ||
| 2185 | } | ||
| 2060 | break; | 2186 | break; |
| 2187 | } | ||
| 2061 | IF_FEATURE_HTTPD_RANGES(range_len -= count;) | 2188 | IF_FEATURE_HTTPD_RANGES(range_len -= count;) |
| 2062 | if (range_len == 0) | 2189 | if (range_len == 0) |
| 2063 | break; | 2190 | break; |
| 2064 | } | 2191 | } |
| 2065 | if (count < 0) { | 2192 | if (count < 0) { |
| 2066 | IF_FEATURE_USE_SENDFILE(fin:) | 2193 | if (VERBOSE_1) |
| 2067 | if (verbose > 1) | 2194 | bb_simple_perror_msg("read error"); |
| 2068 | bb_simple_perror_msg("error"); | ||
| 2069 | } | 2195 | } |
| 2070 | log_and_exit(); | 2196 | send_EOF_and_exit(); |
| 2071 | } | 2197 | } |
| 2072 | 2198 | ||
| 2073 | #if ENABLE_FEATURE_HTTPD_ACL_IP | 2199 | #if ENABLE_FEATURE_HTTPD_ACL_IP |
| @@ -2336,9 +2462,45 @@ static Htaccess_Proxy *find_proxy_entry(const char *url) | |||
| 2336 | /* | 2462 | /* |
| 2337 | * Handle timeouts | 2463 | * Handle timeouts |
| 2338 | */ | 2464 | */ |
| 2339 | static void send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM) | 2465 | #if !ENABLE_PLATFORM_MINGW32 |
| 2466 | static void sigalrm_handler(int sig) NORETURN; | ||
| 2467 | static void sigalrm_handler(int sig UNUSED_PARAM) | ||
| 2468 | { | ||
| 2469 | /* timed out reading headers, POSTDATA, or CGI runs too long */ | ||
| 2470 | int response = HTTP_REQUEST_TIMEOUT; | ||
| 2471 | #if ENABLE_FEATURE_HTTPD_CGI | ||
| 2472 | if (G.cgi_pid > 0) { | ||
| 2473 | if (kill(-G.cgi_pid, SIGTERM) == 0) { | ||
| 2474 | bb_error_msg("kill cgi:%d", G.cgi_pid); | ||
| 2475 | sleep1(); | ||
| 2476 | kill(-G.cgi_pid, SIGKILL); | ||
| 2477 | } | ||
| 2478 | /* Browsers were seen retrying if got HTTP_REQUEST_TIMEOUT, | ||
| 2479 | * we don't want that for the case of stuck CGI. */ | ||
| 2480 | response = HTTP_INTERNAL_SERVER_ERROR; | ||
| 2481 | } | ||
| 2482 | #endif | ||
| 2483 | IF_FEATURE_HTTPD_CGI(if (!G.cgi_output)) | ||
| 2484 | send_headers_and_exit(response); | ||
| 2485 | /* else: we already have some output, do not garble it with HTTP response */ | ||
| 2486 | log_and_exit(); | ||
| 2487 | } | ||
| 2488 | #endif | ||
| 2489 | |||
| 2490 | static void prepare_write_timeout(void) | ||
| 2340 | { | 2491 | { |
| 2341 | send_headers_and_exit(HTTP_REQUEST_TIMEOUT); | 2492 | #if ENABLE_PLATFORM_MINGW32 |
| 2493 | static DWORD tv = DATA_WRITE_TIMEOUT * 1000; | ||
| 2494 | #else | ||
| 2495 | static const struct timeval tv = { .tv_sec = DATA_WRITE_TIMEOUT, .tv_usec = 0 }; | ||
| 2496 | |||
| 2497 | /* stop header read timeout */ | ||
| 2498 | alarm(0); | ||
| 2499 | #endif | ||
| 2500 | |||
| 2501 | /* make write() calls exit with error after DATA_WRITE_TIMEOUT */ | ||
| 2502 | setsockopt(STDOUT_FILENO, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); | ||
| 2503 | /* this is less expensive than arming alarm() before every write */ | ||
| 2342 | } | 2504 | } |
| 2343 | 2505 | ||
| 2344 | /* | 2506 | /* |
| @@ -2356,13 +2518,13 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2356 | #endif | 2518 | #endif |
| 2357 | #if ENABLE_FEATURE_HTTPD_CGI | 2519 | #if ENABLE_FEATURE_HTTPD_CGI |
| 2358 | unsigned total_headers_len; | 2520 | unsigned total_headers_len; |
| 2521 | unsigned un; | ||
| 2359 | #endif | 2522 | #endif |
| 2360 | const char *prequest; | 2523 | const char *prequest; |
| 2361 | static const char request_GET[] ALIGN1 = "GET"; | 2524 | static const char request_GET[] ALIGN1 = "GET"; |
| 2362 | static const char request_HEAD[] ALIGN1 = "HEAD"; | 2525 | static const char request_HEAD[] ALIGN1 = "HEAD"; |
| 2363 | #if ENABLE_FEATURE_HTTPD_CGI | 2526 | #if ENABLE_FEATURE_HTTPD_CGI |
| 2364 | static const char request_POST[] ALIGN1 = "POST"; | 2527 | static const char request_POST[] ALIGN1 = "POST"; |
| 2365 | unsigned long POST_length; | ||
| 2366 | enum CGI_type { | 2528 | enum CGI_type { |
| 2367 | CGI_NONE = 0, | 2529 | CGI_NONE = 0, |
| 2368 | CGI_NORMAL, | 2530 | CGI_NORMAL, |
| @@ -2378,10 +2540,6 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2378 | #endif | 2540 | #endif |
| 2379 | char *HTTP_slash; | 2541 | char *HTTP_slash; |
| 2380 | 2542 | ||
| 2381 | /* Allocation of iobuf is postponed until now | ||
| 2382 | * (IOW, server process doesn't need to waste 8k) */ | ||
| 2383 | iobuf = xmalloc(IOBUF_SIZE); | ||
| 2384 | |||
| 2385 | if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { | 2543 | if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { |
| 2386 | /* NB: can be NULL (user runs httpd -i by hand?) */ | 2544 | /* NB: can be NULL (user runs httpd -i by hand?) */ |
| 2387 | rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa); | 2545 | rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa); |
| @@ -2390,7 +2548,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2390 | /* this trick makes -v logging much simpler */ | 2548 | /* this trick makes -v logging much simpler */ |
| 2391 | if (rmt_ip_str) | 2549 | if (rmt_ip_str) |
| 2392 | applet_name = rmt_ip_str; | 2550 | applet_name = rmt_ip_str; |
| 2393 | if (verbose > 2) | 2551 | if (VERBOSE_3) |
| 2394 | bb_simple_error_msg("connected"); | 2552 | bb_simple_error_msg("connected"); |
| 2395 | } | 2553 | } |
| 2396 | #if ENABLE_FEATURE_HTTPD_ACL_IP | 2554 | #if ENABLE_FEATURE_HTTPD_ACL_IP |
| @@ -2418,9 +2576,9 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2418 | if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip); | 2576 | if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip); |
| 2419 | #endif | 2577 | #endif |
| 2420 | 2578 | ||
| 2421 | #ifdef SIGALRM | 2579 | #if !ENABLE_PLATFORM_MINGW32 |
| 2422 | /* Install timeout handler. get_line() needs it. */ | 2580 | /* Limit how long we expect clients to be sending headers */ |
| 2423 | signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit); | 2581 | alarm(HEADER_READ_TIMEOUT); |
| 2424 | #endif | 2582 | #endif |
| 2425 | 2583 | ||
| 2426 | if (!get_line()) { /* EOF or error or empty line */ | 2584 | if (!get_line()) { /* EOF or error or empty line */ |
| @@ -2430,26 +2588,24 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2430 | * being sent at all. | 2588 | * being sent at all. |
| 2431 | * (Presumably it's a method to decrease latency?) | 2589 | * (Presumably it's a method to decrease latency?) |
| 2432 | */ | 2590 | */ |
| 2433 | if (verbose > 2) | 2591 | if (VERBOSE_3) |
| 2434 | bb_simple_error_msg("eof on read, closing"); | 2592 | bb_simple_error_msg("eof on read, closing"); |
| 2435 | /* Don't bother generating error page in this case, | 2593 | /* Don't bother generating error page in this case, |
| 2436 | * just close the socket. | 2594 | * just close the socket. |
| 2437 | */ | 2595 | */ |
| 2438 | //send_headers_and_exit(HTTP_BAD_REQUEST); | 2596 | //send_headers_and_exit(HTTP_BAD_REQUEST); |
| 2439 | _exit(xfunc_error_retval); | 2597 | _exit_SUCCESS(); |
| 2440 | } | 2598 | } |
| 2441 | dbg("Request:'%s'\n", iobuf); | 2599 | dbg("Request:'%s'\n", iobuf); |
| 2442 | 2600 | ||
| 2443 | /* Find URL */ | 2601 | /* Find URL */ |
| 2444 | // rfc2616: method and URI is separated by exactly one space | 2602 | // rfc2616: method and URI is separated by exactly one space |
| 2445 | //urlp = strpbrk(iobuf, " \t"); - no, tab isn't allowed | 2603 | // no tabs, no double spaces, no empty methods, URIs, etc: |
| 2604 | // should be "METHOD /URI HTTP/xyz" ("\r\n" stripped by get_line) | ||
| 2446 | urlp = strchr(iobuf, ' '); | 2605 | urlp = strchr(iobuf, ' '); |
| 2447 | if (urlp == NULL) | 2606 | if (urlp == NULL) |
| 2448 | send_headers_and_exit(HTTP_BAD_REQUEST); | 2607 | send_headers_and_exit(HTTP_BAD_REQUEST); |
| 2449 | *urlp++ = '\0'; | 2608 | *urlp++ = '\0'; |
| 2450 | //urlp = skip_whitespace(urlp); - should not be necessary | ||
| 2451 | if (urlp[0] != '/') | ||
| 2452 | send_headers_and_exit(HTTP_BAD_REQUEST); | ||
| 2453 | /* Find end of URL */ | 2609 | /* Find end of URL */ |
| 2454 | HTTP_slash = strchr(urlp, ' '); | 2610 | HTTP_slash = strchr(urlp, ' '); |
| 2455 | /* Is it " HTTP/"? */ | 2611 | /* Is it " HTTP/"? */ |
| @@ -2463,7 +2619,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2463 | int proxy_fd; | 2619 | int proxy_fd; |
| 2464 | len_and_sockaddr *lsa; | 2620 | len_and_sockaddr *lsa; |
| 2465 | 2621 | ||
| 2466 | if (verbose > 1) | 2622 | if (VERBOSE_2) |
| 2467 | bb_error_msg("proxy:%s", urlp); | 2623 | bb_error_msg("proxy:%s", urlp); |
| 2468 | lsa = host2sockaddr(proxy_entry->host_port, 80); | 2624 | lsa = host2sockaddr(proxy_entry->host_port, 80); |
| 2469 | if (!lsa) | 2625 | if (!lsa) |
| @@ -2473,8 +2629,10 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2473 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); | 2629 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); |
| 2474 | if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0) | 2630 | if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0) |
| 2475 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); | 2631 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); |
| 2476 | /* Disable peer header reading timeout */ | 2632 | |
| 2477 | alarm(0); | 2633 | /* Disable header reading timeout */ |
| 2634 | prepare_write_timeout(); | ||
| 2635 | |||
| 2478 | /* Config directive was of the form: | 2636 | /* Config directive was of the form: |
| 2479 | * P:/url:[http://]hostname[:port]/new/path | 2637 | * P:/url:[http://]hostname[:port]/new/path |
| 2480 | * When /urlSFX is requested, reverse proxy it | 2638 | * When /urlSFX is requested, reverse proxy it |
| @@ -2486,26 +2644,35 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2486 | urlp + strlen(proxy_entry->url_from), /* "SFX" */ | 2644 | urlp + strlen(proxy_entry->url_from), /* "SFX" */ |
| 2487 | HTTP_slash /* "HTTP/xyz" */ | 2645 | HTTP_slash /* "HTTP/xyz" */ |
| 2488 | ); | 2646 | ); |
| 2489 | cgi_io_loop_and_exit(proxy_fd, proxy_fd, /*max POST length:*/ INT_MAX); | 2647 | /* The above also allows http2 which starts with a fixed |
| 2648 | * "PRI * HTTP/2.0" line | ||
| 2649 | */ | ||
| 2650 | G.POST_len = INT_MAX; /* hack */ | ||
| 2651 | cgi_io_loop_and_exit(proxy_fd, proxy_fd); | ||
| 2490 | } | 2652 | } |
| 2491 | #endif | 2653 | #endif |
| 2654 | /* We don't support http2 "*" URI, enforce "/URI" form */ | ||
| 2655 | if (urlp[0] != '/') | ||
| 2656 | send_headers_and_exit(HTTP_BAD_REQUEST); | ||
| 2492 | 2657 | ||
| 2493 | /* Determine type of request (GET/POST/...) */ | 2658 | /* Determine METHOD of request (GET/POST/...). Case-sensitive (rfc7230,rfc9110) */ |
| 2494 | prequest = request_GET; | 2659 | prequest = request_GET; |
| 2495 | if (strcasecmp(iobuf, prequest) == 0) | 2660 | if (strcmp(iobuf, prequest) == 0) |
| 2496 | goto found; | 2661 | goto found; |
| 2497 | prequest = request_HEAD; | 2662 | prequest = request_HEAD; |
| 2498 | if (strcasecmp(iobuf, prequest) == 0) | 2663 | if (strcmp(iobuf, prequest) == 0) |
| 2499 | goto found; | 2664 | goto found; |
| 2500 | #if !ENABLE_FEATURE_HTTPD_CGI | 2665 | #if !ENABLE_FEATURE_HTTPD_CGI |
| 2501 | send_headers_and_exit(HTTP_NOT_IMPLEMENTED); | 2666 | send_headers_and_exit(HTTP_NOT_IMPLEMENTED); |
| 2502 | #else | 2667 | #else |
| 2503 | prequest = request_POST; | 2668 | prequest = request_POST; |
| 2504 | if (strcasecmp(iobuf, prequest) == 0) | 2669 | if (strcmp(iobuf, prequest) == 0) |
| 2505 | goto found; | 2670 | goto found; |
| 2506 | /* For CGI, allow DELETE, PUT, OPTIONS, etc too */ | 2671 | /* For CGI, allow DELETE, PUT, OPTIONS, etc too */ |
| 2507 | prequest = alloca(16); | 2672 | prequest = alloca(16); |
| 2508 | safe_strncpy((char*)prequest, iobuf, 16); | 2673 | un = safe_strncpy((char*)prequest, iobuf, 16) - prequest; |
| 2674 | if (un < 1 || un >= 15) | ||
| 2675 | send_headers_and_exit(HTTP_BAD_REQUEST); | ||
| 2509 | #endif | 2676 | #endif |
| 2510 | found: | 2677 | found: |
| 2511 | /* Copy URL to stack-allocated char[] */ | 2678 | /* Copy URL to stack-allocated char[] */ |
| @@ -2563,8 +2730,8 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2563 | } | 2730 | } |
| 2564 | 2731 | ||
| 2565 | /* Log it */ | 2732 | /* Log it */ |
| 2566 | if (verbose > 1) | 2733 | if (VERBOSE_2) |
| 2567 | bb_error_msg("url:%s", urlcopy); | 2734 | bb_error_msg("%s %s", prequest, urlcopy); |
| 2568 | 2735 | ||
| 2569 | tptr = urlcopy; | 2736 | tptr = urlcopy; |
| 2570 | while ((tptr = strchr(tptr + 1, '/')) != NULL) { | 2737 | while ((tptr = strchr(tptr + 1, '/')) != NULL) { |
| @@ -2639,7 +2806,6 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2639 | 2806 | ||
| 2640 | #if ENABLE_FEATURE_HTTPD_CGI | 2807 | #if ENABLE_FEATURE_HTTPD_CGI |
| 2641 | total_headers_len = 0; | 2808 | total_headers_len = 0; |
| 2642 | POST_length = 0; | ||
| 2643 | #endif | 2809 | #endif |
| 2644 | 2810 | ||
| 2645 | /* Read until blank line */ | 2811 | /* Read until blank line */ |
| @@ -2661,9 +2827,9 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2661 | if (!tptr[0]) | 2827 | if (!tptr[0]) |
| 2662 | send_headers_and_exit(HTTP_BAD_REQUEST); | 2828 | send_headers_and_exit(HTTP_BAD_REQUEST); |
| 2663 | /* not using strtoul: it ignores leading minus! */ | 2829 | /* not using strtoul: it ignores leading minus! */ |
| 2664 | POST_length = bb_strtou(tptr, NULL, 10); | 2830 | G.POST_len = (int)bb_strtou(tptr, NULL, 10); |
| 2665 | /* length is "ulong", but we need to pass it to int later */ | 2831 | /* we need to pass it to int later */ |
| 2666 | if (errno || POST_length > INT_MAX) | 2832 | if (errno || G.POST_len < 0) |
| 2667 | send_headers_and_exit(HTTP_BAD_REQUEST); | 2833 | send_headers_and_exit(HTTP_BAD_REQUEST); |
| 2668 | continue; | 2834 | continue; |
| 2669 | } | 2835 | } |
| @@ -2714,7 +2880,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2714 | // || s[-1] == ',' | 2880 | // || s[-1] == ',' |
| 2715 | // || s[-1] == ':' | 2881 | // || s[-1] == ':' |
| 2716 | //) { | 2882 | //) { |
| 2717 | content_gzip = 1; | 2883 | accept_gzip = 1; |
| 2718 | //} | 2884 | //} |
| 2719 | } | 2885 | } |
| 2720 | continue; | 2886 | continue; |
| @@ -2757,8 +2923,8 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2757 | #endif | 2923 | #endif |
| 2758 | } /* while extra header reading */ | 2924 | } /* while extra header reading */ |
| 2759 | 2925 | ||
| 2760 | /* We are done reading headers, disable peer timeout */ | 2926 | /* We are done reading headers, disable header timeout */ |
| 2761 | alarm(0); | 2927 | prepare_write_timeout(); |
| 2762 | 2928 | ||
| 2763 | #if !ENABLE_PLATFORM_MINGW32 | 2929 | #if !ENABLE_PLATFORM_MINGW32 |
| 2764 | if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0) { | 2930 | if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0) { |
| @@ -2786,7 +2952,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2786 | send_cgi_and_exit( | 2952 | send_cgi_and_exit( |
| 2787 | (cgi_type == CGI_INDEX) ? "/cgi-bin/index.cgi" | 2953 | (cgi_type == CGI_INDEX) ? "/cgi-bin/index.cgi" |
| 2788 | /*CGI_NORMAL or CGI_INTERPRETER*/ : urlcopy, | 2954 | /*CGI_NORMAL or CGI_INTERPRETER*/ : urlcopy, |
| 2789 | urlcopy, prequest, POST_length | 2955 | urlcopy, prequest |
| 2790 | ); | 2956 | ); |
| 2791 | } | 2957 | } |
| 2792 | #endif | 2958 | #endif |
| @@ -2810,6 +2976,58 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2810 | ); | 2976 | ); |
| 2811 | } | 2977 | } |
| 2812 | 2978 | ||
| 2979 | #if !ENABLE_PLATFORM_MINGW32 | ||
| 2980 | static int count_children(void) | ||
| 2981 | { | ||
| 2982 | int count; | ||
| 2983 | int sz = pread(G.children_fd, iobuf, IOBUF_SIZE - 1, 0); | ||
| 2984 | if (sz < 0) | ||
| 2985 | return -1; | ||
| 2986 | /* The actual format is "NUM NUM ", but future-proof for lack of last space, and for '\n' */ | ||
| 2987 | count = 0; | ||
| 2988 | if (sz > 0) { | ||
| 2989 | char *p = iobuf; | ||
| 2990 | iobuf[sz] = '\n'; | ||
| 2991 | do { | ||
| 2992 | if (*++p == ' ') | ||
| 2993 | count++, p++; | ||
| 2994 | } while (*p != '\n'); | ||
| 2995 | if (p[-1] != ' ') /* it was "NUM NUM\n" (not "NUM NUM \n")? */ | ||
| 2996 | count++; /* there were (NUMSPACES + 1) pids */ | ||
| 2997 | } | ||
| 2998 | return count; | ||
| 2999 | } | ||
| 3000 | |||
| 3001 | static int throttle_if_conn_limit(void) | ||
| 3002 | { | ||
| 3003 | unsigned usec = 0xffff; /* 0.065535 seconds */ | ||
| 3004 | int countdown, c; | ||
| 3005 | for (;;) { | ||
| 3006 | countdown = G.conn_limit; | ||
| 3007 | c = count_children(); | ||
| 3008 | if (c < 0) | ||
| 3009 | break; /* can't count them */ | ||
| 3010 | countdown -= c; | ||
| 3011 | if (countdown <= 0) { /* we are at MAXCONN, pause until we are below */ | ||
| 3012 | bb_error_msg("pausing, children:%u", c); | ||
| 3013 | //bb_error_msg("pausing %ums, children:%u", usec/1000, c); | ||
| 3014 | usleep(usec); | ||
| 3015 | usec = ((usec << 1) + 1) & 0x1fffff; /* x2, up to 2.097151 seconds */ | ||
| 3016 | continue; /* loop: count them again */ | ||
| 3017 | } | ||
| 3018 | if (VERBOSE_3) /* -vvv periodically shows # of children */ | ||
| 3019 | bb_error_msg("children:%u", c ? c - 1 : c); | ||
| 3020 | // "Why minus one?!" you ask. We _just now_ forked (see the caller) | ||
| 3021 | // and immediately checked the # of children. | ||
| 3022 | // Of course, the just-forked child did not have time to complete. | ||
| 3023 | // Without "minus one" hack, this causes above statement to always show | ||
| 3024 | // at least one child, gives wrong impression to the log's reader | ||
| 3025 | // (looks like one child is stuck). | ||
| 3026 | break; | ||
| 3027 | } | ||
| 3028 | return countdown; | ||
| 3029 | } | ||
| 3030 | #endif | ||
| 2813 | 3031 | ||
| 2814 | /* | 3032 | /* |
| 2815 | * The main http server function. | 3033 | * The main http server function. |
| @@ -2822,18 +3040,27 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2822 | static void mini_httpd(int server_socket) NORETURN; | 3040 | static void mini_httpd(int server_socket) NORETURN; |
| 2823 | static void mini_httpd(int server_socket) | 3041 | static void mini_httpd(int server_socket) |
| 2824 | { | 3042 | { |
| 3043 | int countdown; | ||
| 3044 | |||
| 3045 | signal(SIGALRM, sigalrm_handler); | ||
| 3046 | |||
| 3047 | xmove_fd(server_socket, 0); | ||
| 2825 | /* NB: it's best to not use xfuncs in this loop before fork(). | 3048 | /* NB: it's best to not use xfuncs in this loop before fork(). |
| 2826 | * Otherwise server may die on transient errors (temporary | 3049 | * Otherwise server may die on transient errors (temporary |
| 2827 | * out-of-memory condition, etc), which is Bad(tm). | 3050 | * out-of-memory condition, etc), which is Bad(tm). |
| 2828 | * Try to do any dangerous calls after fork. | 3051 | * Try to do any dangerous calls after fork. |
| 2829 | */ | 3052 | */ |
| 3053 | countdown = G.conn_limit; | ||
| 2830 | while (1) { | 3054 | while (1) { |
| 2831 | int n; | 3055 | int n; |
| 2832 | len_and_sockaddr fromAddr; | 3056 | len_and_sockaddr fromAddr; |
| 2833 | 3057 | ||
| 3058 | if (G.children_fd > 0 && --countdown < 0) | ||
| 3059 | countdown = throttle_if_conn_limit(); | ||
| 3060 | |||
| 2834 | /* Wait for connections... */ | 3061 | /* Wait for connections... */ |
| 2835 | fromAddr.len = LSA_SIZEOF_SA; | 3062 | fromAddr.len = LSA_SIZEOF_SA; |
| 2836 | n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len); | 3063 | n = accept(0, &fromAddr.u.sa, &fromAddr.len); |
| 2837 | if (n < 0) | 3064 | if (n < 0) |
| 2838 | continue; | 3065 | continue; |
| 2839 | //TODO: we can reject connects from denied IPs right away; | 3066 | //TODO: we can reject connects from denied IPs right away; |
| @@ -2850,8 +3077,8 @@ static void mini_httpd(int server_socket) | |||
| 2850 | if (fork() == 0) { | 3077 | if (fork() == 0) { |
| 2851 | /* child */ | 3078 | /* child */ |
| 2852 | /* Do not reload config on HUP */ | 3079 | /* Do not reload config on HUP */ |
| 2853 | signal(SIGHUP, SIG_IGN); | 3080 | /*signal(SIGHUP, SIG_IGN); - not needed, handler is a NOP in children (checks pid) */ |
| 2854 | close(server_socket); | 3081 | /* close(0); - server socket. The next line does this for free */ |
| 2855 | xmove_fd(n, 0); | 3082 | xmove_fd(n, 0); |
| 2856 | xdup2(0, 1); | 3083 | xdup2(0, 1); |
| 2857 | 3084 | ||
| @@ -2866,22 +3093,31 @@ static void mini_httpd(int server_socket) | |||
| 2866 | static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN; | 3093 | static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN; |
| 2867 | static void mini_httpd_nommu(int server_socket, int argc, char **argv) | 3094 | static void mini_httpd_nommu(int server_socket, int argc, char **argv) |
| 2868 | { | 3095 | { |
| 3096 | int countdown; | ||
| 2869 | char *argv_copy[argc + 2]; | 3097 | char *argv_copy[argc + 2]; |
| 2870 | 3098 | ||
| 2871 | argv_copy[0] = argv[0]; | 3099 | argv_copy[0] = argv[0]; |
| 2872 | argv_copy[1] = (char*)"-i"; | 3100 | argv_copy[1] = (char*)"-i"; |
| 2873 | memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0])); | 3101 | memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0])); |
| 2874 | 3102 | ||
| 3103 | /*signal(SIGALRM, sigalrm_handler);*/ | ||
| 3104 | /* ^^^ WRONG. mini_httpd_inetd() does this */ | ||
| 3105 | |||
| 3106 | xmove_fd(server_socket, 0); | ||
| 2875 | /* NB: it's best to not use xfuncs in this loop before vfork(). | 3107 | /* NB: it's best to not use xfuncs in this loop before vfork(). |
| 2876 | * Otherwise server may die on transient errors (temporary | 3108 | * Otherwise server may die on transient errors (temporary |
| 2877 | * out-of-memory condition, etc), which is Bad(tm). | 3109 | * out-of-memory condition, etc), which is Bad(tm). |
| 2878 | * Try to do any dangerous calls after fork. | 3110 | * Try to do any dangerous calls after fork. |
| 2879 | */ | 3111 | */ |
| 3112 | countdown = G.conn_limit; | ||
| 2880 | while (1) { | 3113 | while (1) { |
| 2881 | int n; | 3114 | int n; |
| 2882 | 3115 | ||
| 3116 | if (G.children_fd > 0 && --countdown < 0) | ||
| 3117 | countdown = throttle_if_conn_limit(); | ||
| 3118 | |||
| 2883 | /* Wait for connections... */ | 3119 | /* Wait for connections... */ |
| 2884 | n = accept(server_socket, NULL, NULL); | 3120 | n = accept(0, NULL, NULL); |
| 2885 | if (n < 0) | 3121 | if (n < 0) |
| 2886 | continue; | 3122 | continue; |
| 2887 | 3123 | ||
| @@ -2891,8 +3127,8 @@ static void mini_httpd_nommu(int server_socket, int argc, char **argv) | |||
| 2891 | if (vfork() == 0) { | 3127 | if (vfork() == 0) { |
| 2892 | /* child */ | 3128 | /* child */ |
| 2893 | /* Do not reload config on HUP */ | 3129 | /* Do not reload config on HUP */ |
| 2894 | signal(SIGHUP, SIG_IGN); | 3130 | /*signal(SIGHUP, SIG_IGN); - not needed, handler is a NOP in children (checks pid) */ |
| 2895 | close(server_socket); | 3131 | /* close(0); - server socket. The next line does this for free */ |
| 2896 | xmove_fd(n, 0); | 3132 | xmove_fd(n, 0); |
| 2897 | xdup2(0, 1); | 3133 | xdup2(0, 1); |
| 2898 | 3134 | ||
| @@ -2950,6 +3186,10 @@ static void mini_httpd_inetd(void) | |||
| 2950 | { | 3186 | { |
| 2951 | len_and_sockaddr fromAddr; | 3187 | len_and_sockaddr fromAddr; |
| 2952 | 3188 | ||
| 3189 | #if !ENABLE_PLATFORM_MINGW32 | ||
| 3190 | signal(SIGALRM, sigalrm_handler); | ||
| 3191 | #endif | ||
| 3192 | |||
| 2953 | memset(&fromAddr, 0, sizeof(fromAddr)); | 3193 | memset(&fromAddr, 0, sizeof(fromAddr)); |
| 2954 | fromAddr.len = LSA_SIZEOF_SA; | 3194 | fromAddr.len = LSA_SIZEOF_SA; |
| 2955 | /* NB: can fail if user runs it by hand and types in http cmds */ | 3195 | /* NB: can fail if user runs it by hand and types in http cmds */ |
| @@ -2982,9 +3222,11 @@ static void mingw_daemonize(char **argv) | |||
| 2982 | #if !ENABLE_PLATFORM_MINGW32 | 3222 | #if !ENABLE_PLATFORM_MINGW32 |
| 2983 | static void sighup_handler(int sig UNUSED_PARAM) | 3223 | static void sighup_handler(int sig UNUSED_PARAM) |
| 2984 | { | 3224 | { |
| 2985 | int sv = errno; | 3225 | if (G.parent_pid == getpid()) { |
| 2986 | parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE); | 3226 | int sv = errno; |
| 2987 | errno = sv; | 3227 | parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE); |
| 3228 | errno = sv; | ||
| 3229 | } | ||
| 2988 | } | 3230 | } |
| 2989 | #endif | 3231 | #endif |
| 2990 | 3232 | ||
| @@ -2997,9 +3239,11 @@ enum { | |||
| 2997 | IF_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,) | 3239 | IF_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,) |
| 2998 | IF_FEATURE_HTTPD_SETUID( u_opt_setuid ,) | 3240 | IF_FEATURE_HTTPD_SETUID( u_opt_setuid ,) |
| 2999 | p_opt_port , | 3241 | p_opt_port , |
| 3000 | p_opt_inetd , | 3242 | IF_NOT_PLATFORM_MINGW32( M_opt_maxconn ,) |
| 3001 | p_opt_foreground, | 3243 | IF_NOT_PLATFORM_MINGW32( K_opt_killcgi ,) |
| 3002 | p_opt_verbose , | 3244 | i_opt_inetd , |
| 3245 | f_opt_foreground, | ||
| 3246 | v_opt_verbose , | ||
| 3003 | OPT_CONFIG_FILE = 1 << c_opt_config_file, | 3247 | OPT_CONFIG_FILE = 1 << c_opt_config_file, |
| 3004 | OPT_DECODE_URL = 1 << d_opt_decode_url, | 3248 | OPT_DECODE_URL = 1 << d_opt_decode_url, |
| 3005 | OPT_HOME_HTTPD = 1 << h_opt_home_httpd, | 3249 | OPT_HOME_HTTPD = 1 << h_opt_home_httpd, |
| @@ -3008,9 +3252,9 @@ enum { | |||
| 3008 | OPT_MD5 = IF_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0, | 3252 | OPT_MD5 = IF_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0, |
| 3009 | OPT_SETUID = IF_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0, | 3253 | OPT_SETUID = IF_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0, |
| 3010 | OPT_PORT = 1 << p_opt_port, | 3254 | OPT_PORT = 1 << p_opt_port, |
| 3011 | OPT_INETD = 1 << p_opt_inetd, | 3255 | OPT_INETD = 1 << i_opt_inetd, |
| 3012 | OPT_FOREGROUND = 1 << p_opt_foreground, | 3256 | OPT_FOREGROUND = 1 << f_opt_foreground, |
| 3013 | OPT_VERBOSE = 1 << p_opt_verbose, | 3257 | OPT_VERBOSE = 1 << v_opt_verbose, |
| 3014 | }; | 3258 | }; |
| 3015 | 3259 | ||
| 3016 | 3260 | ||
| @@ -3025,7 +3269,12 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) | |||
| 3025 | IF_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;) | 3269 | IF_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;) |
| 3026 | IF_FEATURE_HTTPD_AUTH_MD5(const char *pass;) | 3270 | IF_FEATURE_HTTPD_AUTH_MD5(const char *pass;) |
| 3027 | IF_PLATFORM_MINGW32(int fd;) | 3271 | IF_PLATFORM_MINGW32(int fd;) |
| 3028 | 3272 | #if 0 // PACK64_LITERAL_STR test | |
| 3273 | char testing[16] = "Status: "; | ||
| 3274 | printf("0x%08llx\n", PACK64_LITERAL_STR("Status: ")); | ||
| 3275 | printf("0x%08llx\n", *(uint64_t*)testing); | ||
| 3276 | exit(1); | ||
| 3277 | #endif | ||
| 3029 | INIT_G(); | 3278 | INIT_G(); |
| 3030 | 3279 | ||
| 3031 | #if ENABLE_LOCALE_SUPPORT | 3280 | #if ENABLE_LOCALE_SUPPORT |
| @@ -3033,6 +3282,9 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) | |||
| 3033 | setlocale(LC_TIME, "C"); | 3282 | setlocale(LC_TIME, "C"); |
| 3034 | #endif | 3283 | #endif |
| 3035 | 3284 | ||
| 3285 | #if !ENABLE_PLATFORM_MINGW32 | ||
| 3286 | G.conn_limit = 256; | ||
| 3287 | #endif | ||
| 3036 | home_httpd = xrealloc_getcwd_or_warn(NULL); | 3288 | home_httpd = xrealloc_getcwd_or_warn(NULL); |
| 3037 | /* We do not "absolutize" path given by -h (home) opt. | 3289 | /* We do not "absolutize" path given by -h (home) opt. |
| 3038 | * If user gives relative path in -h, | 3290 | * If user gives relative path in -h, |
| @@ -3043,7 +3295,7 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) | |||
| 3043 | IF_FEATURE_HTTPD_BASIC_AUTH("r:") | 3295 | IF_FEATURE_HTTPD_BASIC_AUTH("r:") |
| 3044 | IF_FEATURE_HTTPD_AUTH_MD5("m:") | 3296 | IF_FEATURE_HTTPD_AUTH_MD5("m:") |
| 3045 | IF_FEATURE_HTTPD_SETUID("u:") | 3297 | IF_FEATURE_HTTPD_SETUID("u:") |
| 3046 | IF_NOT_PLATFORM_MINGW32("p:ifv") | 3298 | IF_NOT_PLATFORM_MINGW32("p:M:+K:+ifv") |
| 3047 | IF_PLATFORM_MINGW32("p:I:+fv") | 3299 | IF_PLATFORM_MINGW32("p:I:+fv") |
| 3048 | "\0" | 3300 | "\0" |
| 3049 | /* -v counts, -i implies -f */ | 3301 | /* -v counts, -i implies -f */ |
| @@ -3056,6 +3308,10 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) | |||
| 3056 | IF_FEATURE_HTTPD_SETUID(, &s_ugid) | 3308 | IF_FEATURE_HTTPD_SETUID(, &s_ugid) |
| 3057 | , &bind_addr_or_port | 3309 | , &bind_addr_or_port |
| 3058 | IF_PLATFORM_MINGW32(, &fd) | 3310 | IF_PLATFORM_MINGW32(, &fd) |
| 3311 | IF_NOT_PLATFORM_MINGW32(, &G.conn_limit) | ||
| 3312 | IF_NOT_PLATFORM_MINGW32( | ||
| 3313 | , IF_FEATURE_HTTPD_CGI(&G.cgi_kill_timeout) IF_NOT_FEATURE_HTTPD_CGI(NULL) | ||
| 3314 | ) | ||
| 3059 | , &verbose | 3315 | , &verbose |
| 3060 | ); | 3316 | ); |
| 3061 | if (opt & OPT_DECODE_URL) { | 3317 | if (opt & OPT_DECODE_URL) { |
| @@ -3109,9 +3365,15 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) | |||
| 3109 | xchdir(home_httpd); | 3365 | xchdir(home_httpd); |
| 3110 | 3366 | ||
| 3111 | if (!(opt & OPT_INETD)) { | 3367 | if (!(opt & OPT_INETD)) { |
| 3112 | #ifdef SIGCHLD | 3368 | #if !ENABLE_PLATFORM_MINGW32 |
| 3369 | G.parent_pid = getpid(); | ||
| 3370 | sprintf(iobuf, "/proc/self/task/%u/children", (unsigned)G.parent_pid); | ||
| 3371 | G.children_fd = open(iobuf, O_RDONLY | O_CLOEXEC); | ||
| 3372 | |||
| 3373 | /* Make it unnecessary to wait for children */ | ||
| 3113 | signal(SIGCHLD, SIG_IGN); | 3374 | signal(SIGCHLD, SIG_IGN); |
| 3114 | #endif | 3375 | #endif |
| 3376 | |||
| 3115 | server_socket = openServer(); | 3377 | server_socket = openServer(); |
| 3116 | #if ENABLE_FEATURE_HTTPD_SETUID | 3378 | #if ENABLE_FEATURE_HTTPD_SETUID |
| 3117 | /* drop privileges */ | 3379 | /* drop privileges */ |
| @@ -3143,7 +3405,6 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) | |||
| 3143 | // setenv_long("SERVER_PORT", ???); | 3405 | // setenv_long("SERVER_PORT", ???); |
| 3144 | } | 3406 | } |
| 3145 | #endif | 3407 | #endif |
| 3146 | |||
| 3147 | parse_conf(DEFAULT_PATH_HTTPD_CONF, FIRST_PARSE); | 3408 | parse_conf(DEFAULT_PATH_HTTPD_CONF, FIRST_PARSE); |
| 3148 | #if ENABLE_PLATFORM_MINGW32 | 3409 | #if ENABLE_PLATFORM_MINGW32 |
| 3149 | # if ENABLE_FEATURE_HTTPD_CGI | 3410 | # if ENABLE_FEATURE_HTTPD_CGI |
| @@ -3152,11 +3413,17 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) | |||
| 3152 | return 0; | 3413 | return 0; |
| 3153 | } | 3414 | } |
| 3154 | # endif | 3415 | # endif |
| 3155 | #else | ||
| 3156 | if (!(opt & OPT_INETD)) | ||
| 3157 | signal(SIGHUP, sighup_handler); | ||
| 3158 | #endif | 3416 | #endif |
| 3159 | 3417 | ||
| 3418 | /* If CGI dies, we still want to correctly finish reading its output | ||
| 3419 | * and send it to the peer. So please no SIGPIPEs! */ | ||
| 3420 | signal(SIGPIPE, SIG_IGN); | ||
| 3421 | /* If a _local_ simple file download (not CGI) from local httpd | ||
| 3422 | * is aborted, you also can get SIGPIPE. | ||
| 3423 | * Disabling it converts SIGPIPE into EPIPE error from sendfile/write. | ||
| 3424 | * We handle that correctly. Hopefully. Maybe. | ||
| 3425 | */ | ||
| 3426 | |||
| 3160 | xfunc_error_retval = 0; | 3427 | xfunc_error_retval = 0; |
| 3161 | #if ENABLE_PLATFORM_MINGW32 | 3428 | #if ENABLE_PLATFORM_MINGW32 |
| 3162 | if (opt & OPT_INETD) { | 3429 | if (opt & OPT_INETD) { |
| @@ -3173,7 +3440,9 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) | |||
| 3173 | #endif | 3440 | #endif |
| 3174 | if (opt & OPT_INETD) | 3441 | if (opt & OPT_INETD) |
| 3175 | mini_httpd_inetd(); /* never returns */ | 3442 | mini_httpd_inetd(); /* never returns */ |
| 3443 | |||
| 3176 | #if !ENABLE_PLATFORM_MINGW32 | 3444 | #if !ENABLE_PLATFORM_MINGW32 |
| 3445 | signal(SIGHUP, sighup_handler); | ||
| 3177 | #if BB_MMU | 3446 | #if BB_MMU |
| 3178 | if (!(opt & OPT_FOREGROUND)) | 3447 | if (!(opt & OPT_FOREGROUND)) |
| 3179 | bb_daemonize(0); /* don't change current directory */ | 3448 | bb_daemonize(0); /* don't change current directory */ |
diff --git a/networking/httpd_helpers.sh b/networking/httpd_helpers.sh index 8eaa2d456..038d2d27f 100755 --- a/networking/httpd_helpers.sh +++ b/networking/httpd_helpers.sh | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | 2 | ||
| 3 | PREFIX="i486-linux-uclibc-" | 3 | PREFIX="i686-linux-musl-" |
| 4 | OPTS="-static -static-libgcc \ | 4 | OPTS="-static -static-libgcc \ |
| 5 | -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \ | 5 | -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \ |
| 6 | -Wall -Wshadow -Wwrite-strings -Wundef -Wstrict-prototypes -Werror \ | 6 | -Wall -Wshadow -Wwrite-strings -Wundef -Wstrict-prototypes -Werror \ |
| @@ -22,3 +22,10 @@ ${PREFIX}gcc \ | |||
| 22 | ${OPTS} \ | 22 | ${OPTS} \ |
| 23 | -Wl,-Map -Wl,httpd_ssi.map \ | 23 | -Wl,-Map -Wl,httpd_ssi.map \ |
| 24 | httpd_ssi.c -o httpd_ssi && strip httpd_ssi | 24 | httpd_ssi.c -o httpd_ssi && strip httpd_ssi |
| 25 | |||
| 26 | ${PREFIX}gcc \ | ||
| 27 | ${OPTS} \ | ||
| 28 | -Wl,-Map -Wl,httpd_ratelimit_cgi.map \ | ||
| 29 | httpd_ratelimit_cgi.c -o httpd_ratelimit_cgi && strip httpd_ratelimit_cgi | ||
| 30 | |||
| 31 | size index.cgi httpd_ssi httpd_ratelimit_cgi | ||
diff --git a/networking/httpd_ratelimit_cgi.c b/networking/httpd_ratelimit_cgi.c new file mode 100644 index 000000000..96702131e --- /dev/null +++ b/networking/httpd_ratelimit_cgi.c | |||
| @@ -0,0 +1,242 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2026 Denys Vlasenko <vda.linux@googlemail.com> | ||
| 3 | * | ||
| 4 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
| 5 | */ | ||
| 6 | |||
| 7 | /* | ||
| 8 | * This program is a CGI application. It is intended to rate-limit | ||
| 9 | * invocations of another, presumably resource-intensive CGI | ||
| 10 | * which you want to only allow less than N instances at any one time. | ||
| 11 | * | ||
| 12 | * Any extra clients who try to run the CGI will get the | ||
| 13 | * "429 Too Many Requests" HTTP response. | ||
| 14 | * | ||
| 15 | * The most efficient way to do so is to use a shebang-style executable file: | ||
| 16 | * #!/path/to/httpd_ratelimit_cgi /tmp/lockdir 99 /path/to/expensive_cgi | ||
| 17 | */ | ||
| 18 | |||
| 19 | /* Build a-la | ||
| 20 | i486-linux-uclibc-gcc \ | ||
| 21 | -static -static-libgcc \ | ||
| 22 | -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \ | ||
| 23 | -Wall -Wshadow -Wwrite-strings -Wundef -Wstrict-prototypes -Werror \ | ||
| 24 | -Wold-style-definition -Wdeclaration-after-statement -Wno-pointer-sign \ | ||
| 25 | -Wmissing-prototypes -Wmissing-declarations \ | ||
| 26 | -Os -fno-builtin-strlen -finline-limit=0 -fomit-frame-pointer \ | ||
| 27 | -ffunction-sections -fdata-sections -fno-guess-branch-probability \ | ||
| 28 | -funsigned-char \ | ||
| 29 | -falign-functions=1 -falign-jumps=1 -falign-labels=1 -falign-loops=1 \ | ||
| 30 | -march=i386 -mpreferred-stack-boundary=2 \ | ||
| 31 | -Wl,-Map -Wl,link.map -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \ | ||
| 32 | httpd_ratelimit_cgi.c -o httpd_ratelimit_cgi | ||
| 33 | */ | ||
| 34 | #include <stdlib.h> | ||
| 35 | #include <string.h> | ||
| 36 | #include <unistd.h> | ||
| 37 | #include <errno.h> | ||
| 38 | #include <signal.h> | ||
| 39 | #include <sys/stat.h> /* mkdir */ | ||
| 40 | #include <limits.h> | ||
| 41 | |||
| 42 | static ssize_t full_write(int fd, const void *buf, size_t len) | ||
| 43 | { | ||
| 44 | ssize_t cc; | ||
| 45 | ssize_t total; | ||
| 46 | |||
| 47 | total = 0; | ||
| 48 | |||
| 49 | while (len) { | ||
| 50 | cc = write(fd, buf, len); | ||
| 51 | |||
| 52 | if (cc < 0) { | ||
| 53 | if (total) { | ||
| 54 | /* we already wrote some! */ | ||
| 55 | /* user can do another write to know the error code */ | ||
| 56 | return total; | ||
| 57 | } | ||
| 58 | return cc; /* write() returns -1 on failure. */ | ||
| 59 | } | ||
| 60 | |||
| 61 | total += cc; | ||
| 62 | buf = ((const char *)buf) + cc; | ||
| 63 | len -= cc; | ||
| 64 | } | ||
| 65 | |||
| 66 | return total; | ||
| 67 | } | ||
| 68 | |||
| 69 | static void full_write2(int fd, const char *msg, const char *msg2) | ||
| 70 | { | ||
| 71 | full_write(fd, msg, strlen(msg)); | ||
| 72 | full_write(fd, " '", 2); | ||
| 73 | full_write(fd, msg2, strlen(msg2)); | ||
| 74 | full_write(fd, "'\n", 2); | ||
| 75 | } | ||
| 76 | |||
| 77 | static void write_and_die(int fd, const char *msg) | ||
| 78 | { | ||
| 79 | full_write(fd, msg, strlen(msg)); | ||
| 80 | exit(0); | ||
| 81 | } | ||
| 82 | |||
| 83 | static void write_and_die2(int fd, const char *msg, const char *msg2) | ||
| 84 | { | ||
| 85 | full_write2(fd, msg, msg2); | ||
| 86 | exit(0); | ||
| 87 | } | ||
| 88 | |||
| 89 | static void fmt_ul(char *dst, unsigned long n) | ||
| 90 | { | ||
| 91 | char buf[sizeof(n)*3 + 2]; | ||
| 92 | char *p; | ||
| 93 | |||
| 94 | p = buf + sizeof(buf) - 1; | ||
| 95 | *p = '\0'; | ||
| 96 | do { | ||
| 97 | *--p = (n % 10) + '0'; | ||
| 98 | n /= 10; | ||
| 99 | } while (n); | ||
| 100 | strcpy(dst, p); | ||
| 101 | } | ||
| 102 | |||
| 103 | static long get_no(const char *s) | ||
| 104 | { | ||
| 105 | const char *start = s; | ||
| 106 | long v = 0; | ||
| 107 | while (*s >= '0' && *s <= '9') | ||
| 108 | v = v * 10 + (*s++ - '0'); | ||
| 109 | if (start == s || *s != '\0' /*|| v < 0*/) | ||
| 110 | return -1; | ||
| 111 | return v; | ||
| 112 | } | ||
| 113 | |||
| 114 | int main(int argc, char **argv) | ||
| 115 | { | ||
| 116 | const char *lock_dir = "."; | ||
| 117 | unsigned long max_slots; | ||
| 118 | char *sp; | ||
| 119 | char *symno; | ||
| 120 | unsigned slot_num; | ||
| 121 | pid_t my_pid; | ||
| 122 | char my_pid_str[sizeof(long)*3]; | ||
| 123 | |||
| 124 | argv++; | ||
| 125 | if (!argv[0] || !argv[1]) | ||
| 126 | write_and_die(2, "Usage: ratelimit [LOCKDIR] MAX_PROCS PROG [ARGS]\n"); | ||
| 127 | |||
| 128 | /* ratelimit "[LOCKDIR] MAX_PROCS PROG" SHEBANG [ARGS] syntax? | ||
| 129 | * This happens if we are running as shebang file | ||
| 130 | * of the form "!#/path/to/ratelimit [/tmp/cgit] 10 CGI_BINARY" | ||
| 131 | * (in this case argv[1] is the shebang's filename) */ | ||
| 132 | sp = strchr(argv[0], ' '); | ||
| 133 | if (sp) { | ||
| 134 | *sp++ = '\0'; | ||
| 135 | /* convert to ratelimit "SOME\0THING" SHEBANG [ARGS] form */ | ||
| 136 | /* argv1 ^ */ | ||
| 137 | argv[1] = sp; | ||
| 138 | sp = strchr(sp, ' '); | ||
| 139 | if (sp) { /* "THING" also has a space? There is a LOCKDIR! */ | ||
| 140 | *sp++ = '\0'; | ||
| 141 | /* convert to ratelimit "SOME\0THI\0G" SHEBANG [ARGS] form */ | ||
| 142 | /* argv0^ ^argv1 */ | ||
| 143 | lock_dir = argv[0]; | ||
| 144 | argv[0] = argv[1]; | ||
| 145 | argv[1] = sp; | ||
| 146 | goto get_max; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | max_slots = get_no(argv[0]); | ||
| 151 | if (max_slots > 9999) { | ||
| 152 | /* ratelimit LOCKDIR MAX_PROCS PROG [ARGS] */ | ||
| 153 | lock_dir = argv[0]; | ||
| 154 | if (!lock_dir[0]) | ||
| 155 | write_and_die2(2, "Bad LOCKDIR", argv[0]); | ||
| 156 | argv++; | ||
| 157 | get_max: | ||
| 158 | max_slots = get_no(argv[0]); | ||
| 159 | if (max_slots > 9999) | ||
| 160 | write_and_die2(2, "Bad MAX_PROCS", argv[0]); | ||
| 161 | } | ||
| 162 | argv++; /* points to PROG [ARGS] */ | ||
| 163 | |||
| 164 | { | ||
| 165 | char slot_path[strlen(lock_dir) + 16]; | ||
| 166 | symno = stpcpy(stpcpy(slot_path, lock_dir), "/lock."); | ||
| 167 | |||
| 168 | my_pid = getpid(); | ||
| 169 | fmt_ul(my_pid_str, my_pid); | ||
| 170 | |||
| 171 | /* Ensure lock directory exists (idempotent, ignores errors) */ | ||
| 172 | if (lock_dir[0] != '.' || lock_dir[1]) /* Don't bother with "." */ | ||
| 173 | mkdir(lock_dir, 0755); | ||
| 174 | |||
| 175 | /* Starting slot varies per process */ | ||
| 176 | slot_num = my_pid; | ||
| 177 | |||
| 178 | /* max_slots = 0 is allowed for testing */ | ||
| 179 | if (max_slots != 0) for (int i = 0; i < max_slots; i++) { | ||
| 180 | slot_num = (slot_num + 1) % max_slots; | ||
| 181 | fmt_ul(symno, slot_num); | ||
| 182 | |||
| 183 | while (1) { | ||
| 184 | char buf[32]; | ||
| 185 | ssize_t len; | ||
| 186 | long old_pid; | ||
| 187 | |||
| 188 | /* Try to claim atomically */ | ||
| 189 | if (symlink(my_pid_str, slot_path) == 0) | ||
| 190 | goto exec; | ||
| 191 | |||
| 192 | /* Only handle EEXIST - other errors skip to next slot */ | ||
| 193 | if (errno != EEXIST) | ||
| 194 | break; | ||
| 195 | |||
| 196 | /* Read existing target PID */ | ||
| 197 | len = readlink(slot_path, buf, sizeof(buf) - 1); | ||
| 198 | if (len < 1) { | ||
| 199 | /* Broken/empty - clean up and retry */ | ||
| 200 | unlink(slot_path); | ||
| 201 | continue; | ||
| 202 | } | ||
| 203 | buf[len] = '\0'; | ||
| 204 | |||
| 205 | /* Parse PID */ | ||
| 206 | old_pid = get_no(buf); | ||
| 207 | if (old_pid <= 0 || old_pid > INT_MAX) { | ||
| 208 | /* Invalid PID string - clean up and retry */ | ||
| 209 | unlink(slot_path); | ||
| 210 | continue; | ||
| 211 | } | ||
| 212 | |||
| 213 | /* Check if old process is alive */ | ||
| 214 | if (kill(old_pid, 0) == 0 || errno != ESRCH) { | ||
| 215 | /* Alive (or unexpected error): slot in use, try next */ | ||
| 216 | break; | ||
| 217 | } | ||
| 218 | |||
| 219 | /* Dead: clean up and retry this slot */ | ||
| 220 | unlink(slot_path); | ||
| 221 | /* Loop continues to retry symlink() */ | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | /* No slot available, return 429 */ | ||
| 226 | write_and_die(1, "Status: 429 Too Many Requests\r\n" | ||
| 227 | "Content-Type: text/plain\r\n" | ||
| 228 | "Retry-After: 60\r\n" | ||
| 229 | "Connection: close\r\n\r\n" | ||
| 230 | "Too many concurrent requests\n" | ||
| 231 | ); | ||
| 232 | return 0; | ||
| 233 | } | ||
| 234 | |||
| 235 | exec: | ||
| 236 | execv(argv[0], argv); | ||
| 237 | full_write2(2, "can't execute", argv[0]); | ||
| 238 | write_and_die(1, "Status: 500 Internal Server Error\r\n" | ||
| 239 | "Content-Type: text/plain\r\n\r\n" | ||
| 240 | "Failed to execute binary\n"); | ||
| 241 | return 1; | ||
| 242 | } \ No newline at end of file | ||
