aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--archival/gzip.c3
-rw-r--r--editors/awk.c72
-rw-r--r--include/libbb.h15
-rw-r--r--include/platform.h2
-rw-r--r--networking/httpd.c709
-rwxr-xr-xnetworking/httpd_helpers.sh9
-rw-r--r--networking/httpd_ratelimit_cgi.c242
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 =
583static const uint16_t PRIMES[] ALIGN2 = { 251, 1021, 4093, 16381, 65521 }; 583static 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 */
591struct globals { 586struct 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
622struct 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 */
1169static uint32_t next_token(uint32_t expected) 1163static 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
335static 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
465struct globals { 500struct 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: */
721enum { 786enum {
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 */
1082static int openServer(void) 1147static 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 */
1096static void log_and_exit(void) NORETURN; 1165static void log_and_exit(void) NORETURN;
1097static void log_and_exit(void) 1166static 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}
1172static void send_EOF_and_exit(void) NORETURN;
1173static 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 */
1348static unsigned get_line(void) 1416static 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 */
1394static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN; 1473static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) NORETURN;
1395static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) 1474static 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 */
1641static void send_cgi_and_exit( 1746static 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;
1646static void send_cgi_and_exit( 1750static 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 */
2339static void send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM) 2465#if !ENABLE_PLATFORM_MINGW32
2466static void sigalrm_handler(int sig) NORETURN;
2467static 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
2490static 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
2980static 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
3001static 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)
2822static void mini_httpd(int server_socket) NORETURN; 3040static void mini_httpd(int server_socket) NORETURN;
2823static void mini_httpd(int server_socket) 3041static 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)
2866static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN; 3093static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN;
2867static void mini_httpd_nommu(int server_socket, int argc, char **argv) 3094static 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
2983static void sighup_handler(int sig UNUSED_PARAM) 3223static 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
3PREFIX="i486-linux-uclibc-" 3PREFIX="i686-linux-musl-"
4OPTS="-static -static-libgcc \ 4OPTS="-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 \
24httpd_ssi.c -o httpd_ssi && strip httpd_ssi 24httpd_ssi.c -o httpd_ssi && strip httpd_ssi
25
26${PREFIX}gcc \
27${OPTS} \
28-Wl,-Map -Wl,httpd_ratelimit_cgi.map \
29httpd_ratelimit_cgi.c -o httpd_ratelimit_cgi && strip httpd_ratelimit_cgi
30
31size 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
20i486-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 \
32httpd_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
42static 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
69static 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
77static void write_and_die(int fd, const char *msg)
78{
79 full_write(fd, msg, strlen(msg));
80 exit(0);
81}
82
83static void write_and_die2(int fd, const char *msg, const char *msg2)
84{
85 full_write2(fd, msg, msg2);
86 exit(0);
87}
88
89static 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
103static 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
114int 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
235exec:
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