X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Fhttpd.c;h=0b5d2b481df8f4f47dfc3a3e4437d2f2b2cc4485;hb=049670fbbe8022e0e38909aa3de189c06e34ad7d;hp=cda7cc7af528e3ea5000d625f3c5e38065e4e0b4;hpb=f85bd1a7a7e712c4dd2dfd86daa9ab01a708b7b4;p=oweals%2Fbusybox.git diff --git a/networking/httpd.c b/networking/httpd.c index cda7cc7af..0b5d2b481 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -95,9 +95,129 @@ * If -c is not set, an attempt will be made to open the default * root configuration file. If -c is set and the file is not found, the * server exits with an error. - * */ - /* TODO: use TCP_CORK, parse_config() */ +//config:config HTTPD +//config: bool "httpd (32 kb)" +//config: default y +//config: help +//config: HTTP server. +//config: +//config:config FEATURE_HTTPD_RANGES +//config: bool "Support 'Ranges:' header" +//config: default y +//config: depends on HTTPD +//config: help +//config: Makes httpd emit "Accept-Ranges: bytes" header and understand +//config: "Range: bytes=NNN-[MMM]" header. Allows for resuming interrupted +//config: downloads, seeking in multimedia players etc. +//config: +//config:config FEATURE_HTTPD_SETUID +//config: bool "Enable -u option" +//config: default y +//config: depends on HTTPD +//config: help +//config: This option allows the server to run as a specific user +//config: rather than defaulting to the user that starts the server. +//config: Use of this option requires special privileges to change to a +//config: different user. +//config: +//config:config FEATURE_HTTPD_BASIC_AUTH +//config: bool "Enable HTTP authentication" +//config: default y +//config: depends on HTTPD +//config: help +//config: Utilizes password settings from /etc/httpd.conf for basic +//config: authentication on a per url basis. +//config: Example for httpd.conf file: +//config: /adm:toor:PaSsWd +//config: +//config:config FEATURE_HTTPD_AUTH_MD5 +//config: bool "Support MD5-encrypted passwords in HTTP authentication" +//config: default y +//config: depends on FEATURE_HTTPD_BASIC_AUTH +//config: help +//config: Enables encrypted passwords, and wildcard user/passwords +//config: in httpd.conf file. +//config: User '*' means 'any system user name is ok', +//config: password of '*' means 'use system password for this user' +//config: Examples: +//config: /adm:toor:$1$P/eKnWXS$aI1aPGxT.dJD5SzqAKWrF0 +//config: /adm:root:* +//config: /wiki:*:* +//config: +//config:config FEATURE_HTTPD_CGI +//config: bool "Support Common Gateway Interface (CGI)" +//config: default y +//config: depends on HTTPD +//config: help +//config: This option allows scripts and executables to be invoked +//config: when specific URLs are requested. +//config: +//config:config FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR +//config: bool "Support running scripts through an interpreter" +//config: default y +//config: depends on FEATURE_HTTPD_CGI +//config: help +//config: This option enables support for running scripts through an +//config: interpreter. Turn this on if you want PHP scripts to work +//config: properly. You need to supply an additional line in your +//config: httpd.conf file: +//config: *.php:/path/to/your/php +//config: +//config:config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV +//config: bool "Set REMOTE_PORT environment variable for CGI" +//config: default y +//config: depends on FEATURE_HTTPD_CGI +//config: help +//config: Use of this option can assist scripts in generating +//config: references that contain a unique port number. +//config: +//config:config FEATURE_HTTPD_ENCODE_URL_STR +//config: bool "Enable -e option (useful for CGIs written as shell scripts)" +//config: default y +//config: depends on HTTPD +//config: help +//config: This option allows html encoding of arbitrary strings for display +//config: by the browser. Output goes to stdout. +//config: For example, httpd -e "" produces +//config: "<Hello World>". +//config: +//config:config FEATURE_HTTPD_ERROR_PAGES +//config: bool "Support custom error pages" +//config: default y +//config: depends on HTTPD +//config: help +//config: This option allows you to define custom error pages in +//config: the configuration file instead of the default HTTP status +//config: error pages. For instance, if you add the line: +//config: E404:/path/e404.html +//config: in the config file, the server will respond the specified +//config: '/path/e404.html' file instead of the terse '404 NOT FOUND' +//config: message. +//config: +//config:config FEATURE_HTTPD_PROXY +//config: bool "Support reverse proxy" +//config: default y +//config: depends on HTTPD +//config: help +//config: This option allows you to define URLs that will be forwarded +//config: to another HTTP server. To setup add the following line to the +//config: configuration file +//config: P:/url/:http://hostname[:port]/new/path/ +//config: Then a request to /url/myfile will be forwarded to +//config: http://hostname[:port]/new/path/myfile. +//config: +//config:config FEATURE_HTTPD_GZIP +//config: bool "Support GZIP content encoding" +//config: default y +//config: depends on HTTPD +//config: help +//config: Makes httpd send files using GZIP content encoding if the +//config: client supports it and a pre-compressed .gz exists. + +//applet:IF_HTTPD(APPLET(httpd, BB_DIR_USR_SBIN, BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_HTTPD) += httpd.o //usage:#define httpd_trivial_usage //usage: "[-ifv[v]]" @@ -124,7 +244,10 @@ //usage: "\n -e STRING HTML encode STRING" //usage: "\n -d STRING URL decode STRING" +/* TODO: use TCP_CORK, parse_config() */ + #include "libbb.h" +#include "common_bufsiz.h" #if ENABLE_PAM /* PAM may include . We may need to undefine bbox's stub define: */ # undef setlocale @@ -133,7 +256,7 @@ # include # include #endif -#if ENABLE_FEATURE_HTTPD_USE_SENDFILE +#if ENABLE_FEATURE_USE_SENDFILE # include #endif /* amount of buffering in a pipe */ @@ -144,6 +267,7 @@ #define DEBUG 0 #define IOBUF_SIZE 8192 +#define MAX_HTTP_HEADERS_SIZE ((8*1024) - 16) #if PIPE_BUF >= IOBUF_SIZE # error "PIPE_BUF >= IOBUF_SIZE" #endif @@ -182,6 +306,13 @@ typedef struct Htaccess_Proxy { char *url_to; } Htaccess_Proxy; +typedef enum CGI_type { + CGI_NONE = 0, + CGI_NORMAL, + CGI_INDEX, + CGI_INTERPRETER, +} CGI_type; + enum { HTTP_OK = 200, HTTP_PARTIAL_CONTENT = 206, @@ -193,6 +324,7 @@ enum { HTTP_REQUEST_TIMEOUT = 408, HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ HTTP_INTERNAL_SERVER_ERROR = 500, + HTTP_ENTITY_TOO_LARGE = 413, HTTP_CONTINUE = 100, #if 0 /* future use */ HTTP_SWITCHING_PROTOCOLS = 101, @@ -224,6 +356,7 @@ static const uint16_t http_response_type[] ALIGN2 = { HTTP_BAD_REQUEST, HTTP_FORBIDDEN, HTTP_INTERNAL_SERVER_ERROR, + HTTP_ENTITY_TOO_LARGE, #if 0 /* not implemented */ HTTP_CREATED, HTTP_ACCEPTED, @@ -254,6 +387,7 @@ static const struct { { "Bad Request", "Unsupported method" }, { "Forbidden", "" }, { "Internal Server Error", "Internal Server Error" }, + { "Entity Too Large", "Entity Too Large" }, #if 0 /* not implemented */ { "Created" }, { "Accepted" }, @@ -269,13 +403,15 @@ static const struct { struct globals { int verbose; /* must be int (used by getopt32) */ smallint flg_deny_all; - - unsigned rmt_ip; /* used for IP-based allow/deny rules */ +#if ENABLE_FEATURE_HTTPD_GZIP + /* client can handle gzip / we are going to send gzip */ + smallint content_gzip; +#endif time_t last_mod; char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ const char *bind_addr_or_port; - const char *g_query; + char *g_query; const char *opt_c_configFile; const char *home_httpd; const char *index_page; @@ -286,11 +422,6 @@ struct globals { IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;) IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) - IF_FEATURE_HTTPD_CGI(char *referer;) - IF_FEATURE_HTTPD_CGI(char *user_agent;) - IF_FEATURE_HTTPD_CGI(char *host;) - IF_FEATURE_HTTPD_CGI(char *http_accept;) - IF_FEATURE_HTTPD_CGI(char *http_accept_language;) off_t file_size; /* -1 - unknown */ #if ENABLE_FEATURE_HTTPD_RANGES @@ -307,7 +438,8 @@ struct globals { Htaccess *script_i; /* config script interpreters */ #endif char *iobuf; /* [IOBUF_SIZE] */ -#define hdr_buf bb_common_bufsiz1 +#define hdr_buf bb_common_bufsiz1 +#define sizeof_hdr_buf COMMON_BUFSIZE char *hdr_ptr; int hdr_cnt; #if ENABLE_FEATURE_HTTPD_ERROR_PAGES @@ -316,15 +448,15 @@ struct globals { #if ENABLE_FEATURE_HTTPD_PROXY Htaccess_Proxy *proxy; #endif -#if ENABLE_FEATURE_HTTPD_GZIP - /* client can handle gzip / we are going to send gzip */ - smallint content_gzip; -#endif }; #define G (*ptr_to_globals) #define verbose (G.verbose ) #define flg_deny_all (G.flg_deny_all ) -#define rmt_ip (G.rmt_ip ) +#if ENABLE_FEATURE_HTTPD_GZIP +# define content_gzip (G.content_gzip ) +#else +# define content_gzip 0 +#endif #define bind_addr_or_port (G.bind_addr_or_port) #define g_query (G.g_query ) #define opt_c_configFile (G.opt_c_configFile ) @@ -336,11 +468,6 @@ struct globals { #define ip_a_d (G.ip_a_d ) #define g_realm (G.g_realm ) #define remoteuser (G.remoteuser ) -#define referer (G.referer ) -#define user_agent (G.user_agent ) -#define host (G.host ) -#define http_accept (G.http_accept ) -#define http_accept_language (G.http_accept_language) #define file_size (G.file_size ) #if ENABLE_FEATURE_HTTPD_RANGES #define range_start (G.range_start ) @@ -348,7 +475,7 @@ struct globals { #define range_len (G.range_len ) #else enum { - range_start = 0, + range_start = -1, range_end = MAXINT(off_t) - 1, range_len = MAXINT(off_t), }; @@ -362,14 +489,11 @@ enum { #define hdr_cnt (G.hdr_cnt ) #define http_error_page (G.http_error_page ) #define proxy (G.proxy ) -#if ENABLE_FEATURE_HTTPD_GZIP -# define content_gzip (G.content_gzip ) -#else -# define content_gzip 0 -#endif #define INIT_G() do { \ + setup_common_bufsiz(); \ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \ + IF_FEATURE_HTTPD_RANGES(range_start = -1;) \ bind_addr_or_port = "80"; \ index_page = index_html; \ file_size = -1; \ @@ -696,7 +820,7 @@ static void parse_conf(const char *path, int flag) goto config_error; } *host_port++ = '\0'; - if (strncmp(host_port, "http://", 7) == 0) + if (is_prefixed_with(host_port, "http://")) host_port += 7; if (*host_port == '\0') { goto config_error; @@ -795,9 +919,9 @@ static void parse_conf(const char *path, int flag) /* the line is not recognized */ config_error: bb_error_msg("config error '%s' in '%s'", buf, filename); - } /* while (fgets) */ + } /* while (fgets) */ - fclose(f); + fclose(f); } #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR @@ -823,7 +947,7 @@ static char *encodeString(const char *string) if (isalnum(ch)) *p++ = ch; else - p += sprintf(p, "&#%d;", (unsigned char) ch); + p += sprintf(p, "&#%u;", (unsigned char) ch); } *p = '\0'; return out; @@ -919,20 +1043,21 @@ static void log_and_exit(void) * second packet is delayed for any reason. * responseNum - the result code to send. */ -static void send_headers(int responseNum) +static void send_headers(unsigned responseNum) { static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT"; + /* Fixed size 29-byte string. Example: Sun, 06 Nov 1994 08:49:37 GMT */ + char date_str[40]; /* using a bit larger buffer to paranoia reasons */ + struct tm tm; const char *responseString = ""; const char *infoString = NULL; - const char *mime_type; #if ENABLE_FEATURE_HTTPD_ERROR_PAGES const char *error_page = NULL; #endif + unsigned len; unsigned i; time_t timer = time(NULL); - char tmp_str[80]; - int len; for (i = 0; i < ARRAY_SIZE(http_response_type); i++) { if (http_response_type[i] == responseNum) { @@ -944,41 +1069,67 @@ static void send_headers(int responseNum) break; } } - /* error message is HTML */ - mime_type = responseNum == HTTP_OK ? - found_mime_type : "text/html"; if (verbose) bb_error_msg("response:%u", responseNum); - /* emit the current date */ - strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&timer)); + /* We use sprintf, not snprintf (it's less code). + * iobuf[] is several kbytes long and all headers we generate + * always fit into those kbytes. + */ + + strftime(date_str, sizeof(date_str), RFC1123FMT, gmtime_r(&timer, &tm)); + /* ^^^ using gmtime_r() instead of gmtime() to not use static data */ len = sprintf(iobuf, - "HTTP/1.0 %d %s\r\nContent-type: %s\r\n" - "Date: %s\r\nConnection: close\r\n", - responseNum, responseString, mime_type, tmp_str); + "HTTP/1.0 %u %s\r\n" + "Date: %s\r\n" + "Connection: close\r\n", + responseNum, responseString, + date_str + ); + + if (responseNum != HTTP_OK || found_mime_type) { + len += sprintf(iobuf + len, + "Content-type: %s\r\n", + /* if it's error message, then it's HTML */ + (responseNum != HTTP_OK ? "text/html" : found_mime_type) + ); + } #if ENABLE_FEATURE_HTTPD_BASIC_AUTH if (responseNum == HTTP_UNAUTHORIZED) { len += sprintf(iobuf + len, - "WWW-Authenticate: Basic realm=\"%s\"\r\n", - g_realm); + "WWW-Authenticate: Basic realm=\"%.999s\"\r\n", + g_realm /* %.999s protects from overflowing iobuf[] */ + ); } #endif if (responseNum == HTTP_MOVED_TEMPORARILY) { - len += sprintf(iobuf + len, "Location: %s/%s%s\r\n", + /* Responding to "GET /dir" with + * "HTTP/1.0 302 Found" "Location: /dir/" + * - IOW, asking them to repeat with a slash. + * Here, overflow IS possible, can't use sprintf: + * mkdir test + * python -c 'print("get /test?" + ("x" * 8192))' | busybox httpd -i -h . + */ + len += snprintf(iobuf + len, IOBUF_SIZE-3 - len, + "Location: %s/%s%s\r\n", found_moved_temporarily, (g_query ? "?" : ""), - (g_query ? g_query : "")); + (g_query ? g_query : "") + ); + if (len > IOBUF_SIZE-3) + len = IOBUF_SIZE-3; } #if ENABLE_FEATURE_HTTPD_ERROR_PAGES if (error_page && access(error_page, R_OK) == 0) { - strcat(iobuf, "\r\n"); - len += 2; - - if (DEBUG) + iobuf[len++] = '\r'; + iobuf[len++] = '\n'; + if (DEBUG) { + iobuf[len] = '\0'; fprintf(stderr, "headers: '%s'\n", iobuf); + } full_write(STDOUT_FILENO, iobuf, len); if (DEBUG) fprintf(stderr, "writing error page: '%s'\n", error_page); @@ -987,27 +1138,73 @@ static void send_headers(int responseNum) #endif if (file_size != -1) { /* file */ - strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod)); + strftime(date_str, sizeof(date_str), RFC1123FMT, gmtime_r(&last_mod, &tm)); #if ENABLE_FEATURE_HTTPD_RANGES if (responseNum == HTTP_PARTIAL_CONTENT) { - len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"u-%"OFF_FMT"u/%"OFF_FMT"u\r\n", + len += sprintf(iobuf + len, + "Content-Range: bytes %"OFF_FMT"u-%"OFF_FMT"u/%"OFF_FMT"u\r\n", range_start, range_end, - file_size); + file_size + ); file_size = range_end - range_start + 1; } #endif + +//RFC 2616 4.4 Message Length +// The transfer-length of a message is the length of the message-body as +// it appears in the message; that is, after any transfer-codings have +// been applied. When a message-body is included with a message, the +// transfer-length of that body is determined by one of the following +// (in order of precedence): +// 1.Any response message which "MUST NOT" include a message-body (such +// as the 1xx, 204, and 304 responses and any response to a HEAD +// request) is always terminated by the first empty line after the +// header fields, regardless of the entity-header fields present in +// the message. +// 2.If a Transfer-Encoding header field (section 14.41) is present and +// has any value other than "identity", then the transfer-length is +// defined by use of the "chunked" transfer-coding (section 3.6), +// unless the message is terminated by closing the connection. +// 3.If a Content-Length header field (section 14.13) is present, its +// decimal value in OCTETs represents both the entity-length and the +// transfer-length. The Content-Length header field MUST NOT be sent +// if these two lengths are different (i.e., if a Transfer-Encoding +// header field is present). If a message is received with both a +// Transfer-Encoding header field and a Content-Length header field, +// the latter MUST be ignored. +// 4.If the message uses the media type "multipart/byteranges" ... +// 5.By the server closing the connection. +// +// (NB: standards do not define "Transfer-Length:" _header_, +// transfer-length above is just a concept). + len += sprintf(iobuf + len, #if ENABLE_FEATURE_HTTPD_RANGES "Accept-Ranges: bytes\r\n" #endif - "Last-Modified: %s\r\n%s %"OFF_FMT"u\r\n", - tmp_str, - content_gzip ? "Transfer-length:" : "Content-length:", + "Last-Modified: %s\r\n" + /* Because of 4.4 (5), we can forgo sending of "Content-Length" + * since we close connection afterwards, but it helps clients + * to e.g. estimate download times, show progress bars etc. + * Theoretically we should not send it if page is compressed, + * but de-facto standard is to send it (see comment below). + */ + "Content-Length: %"OFF_FMT"u\r\n", + date_str, file_size ); } + /* This should be "Transfer-Encoding", not "Content-Encoding": + * "data is compressed for transfer", not "data is an archive". + * But many clients were not handling "Transfer-Encoding" correctly + * (they were not uncompressing gzipped pages, tried to show + * raw compressed data), and servers worked around it by using + * "Content-Encoding" instead... and this become de-facto standard. + * https://bugzilla.mozilla.org/show_bug.cgi?id=68517 + * https://bugs.chromium.org/p/chromium/issues/detail?id=94730 + */ if (content_gzip) len += sprintf(iobuf + len, "Content-Encoding: gzip\r\n"); @@ -1015,13 +1212,19 @@ static void send_headers(int responseNum) iobuf[len++] = '\n'; if (infoString) { len += sprintf(iobuf + len, - "%d %s\n" - "

%d %s

\n%s\n\n", + "%u %s\n" + "

%u %s

\n" + "%s\n" + "\n", responseNum, responseString, - responseNum, responseString, infoString); + responseNum, responseString, + infoString + ); } - if (DEBUG) + if (DEBUG) { + iobuf[len] = '\0'; fprintf(stderr, "headers: '%s'\n", iobuf); + } if (full_write(STDOUT_FILENO, iobuf, len) != len) { if (verbose > 1) bb_perror_msg("error"); @@ -1038,37 +1241,39 @@ static void send_headers_and_exit(int responseNum) } /* - * Read from the socket until '\n' or EOF. '\r' chars are removed. + * Read from the socket until '\n' or EOF. + * '\r' chars are removed. * '\n' is replaced with NUL. * Return number of characters read or 0 if nothing is read * ('\r' and '\n' are not counted). * Data is returned in iobuf. */ -static int get_line(void) +static unsigned get_line(void) { - int count = 0; + unsigned count; char c; alarm(HEADER_READ_TIMEOUT); + count = 0; while (1) { if (hdr_cnt <= 0) { - hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf)); + hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); if (hdr_cnt <= 0) - break; + goto ret; hdr_ptr = hdr_buf; } - iobuf[count] = c = *hdr_ptr++; hdr_cnt--; - + c = *hdr_ptr++; if (c == '\r') continue; - if (c == '\n') { - iobuf[count] = '\0'; + if (c == '\n') break; - } + iobuf[count] = c; if (count < (IOBUF_SIZE - 1)) /* check overflow */ count++; } + ret: + iobuf[count] = '\0'; return count; } @@ -1103,18 +1308,31 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post /* NB: breaking out of this loop jumps to log_and_exit() */ out_cnt = 0; + pfd[FROM_CGI].fd = fromCgi_rd; + pfd[FROM_CGI].events = POLLIN; + pfd[TO_CGI].fd = toCgi_wr; while (1) { - memset(pfd, 0, sizeof(pfd)); - - pfd[FROM_CGI].fd = fromCgi_rd; - pfd[FROM_CGI].events = POLLIN; - - if (toCgi_wr) { - pfd[TO_CGI].fd = toCgi_wr; - if (hdr_cnt > 0) { - pfd[TO_CGI].events = POLLOUT; - } else if (post_len > 0) { - pfd[0].events = POLLIN; + /* Note: even pfd[0].events == 0 won't prevent + * revents == POLLHUP|POLLERR reports from closed stdin. + * Setting fd to -1 works: */ + pfd[0].fd = -1; + pfd[0].events = POLLIN; + pfd[0].revents = 0; /* probably not needed, paranoia */ + + /* We always poll this fd, thus kernel always sets revents: */ + /*pfd[FROM_CGI].events = POLLIN; - moved out of loop */ + /*pfd[FROM_CGI].revents = 0; - not needed */ + + /* gcc-4.8.0 still doesnt fill two shorts with one insn :( */ + /* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47059 */ + /* hopefully one day it will... */ + pfd[TO_CGI].events = POLLOUT; + pfd[TO_CGI].revents = 0; /* needed! */ + + if (toCgi_wr && hdr_cnt <= 0) { + if (post_len > 0) { + /* Expect more POST data from network */ + pfd[0].fd = 0; } else { /* post_len <= 0 && hdr_cnt <= 0: * no more POST data to CGI, @@ -1126,7 +1344,7 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post } /* Now wait on the set of sockets */ - count = safe_poll(pfd, toCgi_wr ? TO_CGI+1 : FROM_CGI+1, -1); + count = safe_poll(pfd, hdr_cnt > 0 ? TO_CGI+1 : FROM_CGI+1, -1); if (count <= 0) { #if 0 if (safe_waitpid(pid, &status, WNOHANG) <= 0) { @@ -1135,15 +1353,15 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post continue; } if (DEBUG && WIFEXITED(status)) - bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status)); + bb_error_msg("CGI exited, status=%u", WEXITSTATUS(status)); if (DEBUG && WIFSIGNALED(status)) - bb_error_msg("CGI killed, signal=%d", WTERMSIG(status)); + bb_error_msg("CGI killed, signal=%u", WTERMSIG(status)); #endif break; } if (pfd[TO_CGI].revents) { - /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */ + /* hdr_cnt > 0 here due to the way poll() called */ /* Have data from peer and can write to CGI */ count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt); /* Doesn't happen, we dont use nonblocking IO here @@ -1164,9 +1382,9 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post /* We expect data, prev data portion is eaten by CGI * and there *is* data to read from the peer * (POSTDATA) */ - //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len; + //count = post_len > (int)sizeof_hdr_buf ? (int)sizeof_hdr_buf : post_len; //count = safe_read(STDIN_FILENO, hdr_buf, count); - count = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf)); + count = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); if (count > 0) { hdr_cnt = count; hdr_ptr = hdr_buf; @@ -1212,7 +1430,8 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post /* send "HTTP/1.0 " */ if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) break; - rbuf += 8; /* skip "Status: " */ + /* skip "Status: " (including space, sending "HTTP/1.0 NNN" is wrong) */ + rbuf += 8; count = out_cnt - 8; out_cnt = -1; /* buffering off */ } else if (out_cnt >= 4) { @@ -1265,22 +1484,19 @@ static void setenv1(const char *name, const char *value) * * Parameters: * const char *url The requested URL (with leading /). + * const char *orig_uri The original URI before rewriting (if any) * int post_len Length of the POST body. - * const char *cookie For set HTTP_COOKIE. - * const char *content_type For set CONTENT_TYPE. */ static void send_cgi_and_exit( const char *url, + const char *orig_uri, const char *request, - int post_len, - const char *cookie, - const char *content_type) NORETURN; + int post_len) NORETURN; static void send_cgi_and_exit( const char *url, + const char *orig_uri, const char *request, - int post_len, - const char *cookie, - const char *content_type) + int post_len) { struct fd_pair fromCgi; /* CGI -> httpd pipe */ struct fd_pair toCgi; /* httpd -> CGI pipe */ @@ -1302,7 +1518,7 @@ static void send_cgi_and_exit( while ((script = strchr(script + 1, '/')) != NULL) { int dir; *script = '\0'; - dir = is_directory(url + 1, /*followlinks:*/ 1, NULL); + dir = is_directory(url + 1, /*followlinks:*/ 1); *script = '/'; if (!dir) { /* not directory, found script.cgi/PATH_INFO */ @@ -1314,9 +1530,9 @@ static void send_cgi_and_exit( setenv1("PATH_INFO", script); /* set to /PATH_INFO or "" */ setenv1("REQUEST_METHOD", request); if (g_query) { - putenv(xasprintf("%s=%s?%s", "REQUEST_URI", url, g_query)); + putenv(xasprintf("%s=%s?%s", "REQUEST_URI", orig_uri, g_query)); } else { - setenv1("REQUEST_URI", url); + setenv1("REQUEST_URI", orig_uri); } if (script != NULL) *script = '\0'; /* cut off /PATH_INFO */ @@ -1358,26 +1574,14 @@ static void send_cgi_and_exit( #endif } } - setenv1("HTTP_USER_AGENT", user_agent); - if (http_accept) - setenv1("HTTP_ACCEPT", http_accept); - if (http_accept_language) - setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language); if (post_len) - putenv(xasprintf("CONTENT_LENGTH=%d", post_len)); - if (cookie) - setenv1("HTTP_COOKIE", cookie); - if (content_type) - setenv1("CONTENT_TYPE", content_type); + putenv(xasprintf("CONTENT_LENGTH=%u", post_len)); #if ENABLE_FEATURE_HTTPD_BASIC_AUTH if (remoteuser) { setenv1("REMOTE_USER", remoteuser); putenv((char*)"AUTH_TYPE=Basic"); } #endif - if (referer) - setenv1("HTTP_REFERER", referer); - setenv1("HTTP_HOST", host); /* set to "" if NULL */ /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this, * just run "env SERVER_NAME=xyz httpd ..." instead */ @@ -1410,7 +1614,7 @@ static void send_cgi_and_exit( if (script != url) { /* paranoia */ *script = '\0'; if (chdir(url + 1) != 0) { - bb_perror_msg("chdir(%s)", url + 1); + bb_perror_msg("can't change directory to '%s'", url + 1); goto error_execing_cgi; } // not needed: *script = '/'; @@ -1515,8 +1719,8 @@ static NOINLINE void send_file_and_exit(const char *url, int what) * (happens if you abort downloads from local httpd): */ signal(SIGPIPE, SIG_IGN); - /* If not found, default is "application/octet-stream" */ - found_mime_type = "application/octet-stream"; + /* If not found, default is to not send "Content-type:" */ + /*found_mime_type = NULL; - already is */ suffix = strrchr(url, '.'); if (suffix) { static const char suffixTable[] ALIGN1 = @@ -1586,18 +1790,18 @@ static NOINLINE void send_file_and_exit(const char *url, int what) if (what == SEND_BODY /* err pages and ranges don't mix */ || content_gzip /* we are sending compressed page: can't do ranges */ ///why? ) { - range_start = 0; + range_start = -1; } range_len = MAXINT(off_t); - if (range_start) { - if (!range_end) { + if (range_start >= 0) { + if (!range_end || range_end > file_size - 1) { range_end = file_size - 1; } if (range_end < range_start || lseek(fd, range_start, SEEK_SET) != range_start ) { lseek(fd, 0, SEEK_SET); - range_start = 0; + range_start = -1; } else { range_len = range_end - range_start + 1; send_headers(HTTP_PARTIAL_CONTENT); @@ -1607,7 +1811,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what) #endif if (what & SEND_HEADERS) send_headers(HTTP_OK); -#if ENABLE_FEATURE_HTTPD_USE_SENDFILE +#if ENABLE_FEATURE_USE_SENDFILE { off_t offset = range_start; while (1) { @@ -1620,7 +1824,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what) break; /* fall back to read/write loop */ goto fin; } - IF_FEATURE_HTTPD_RANGES(range_len -= sz;) + IF_FEATURE_HTTPD_RANGES(range_len -= count;) if (count == 0 || range_len == 0) log_and_exit(); } @@ -1637,14 +1841,14 @@ static NOINLINE void send_file_and_exit(const char *url, int what) break; } if (count < 0) { - IF_FEATURE_HTTPD_USE_SENDFILE(fin:) + IF_FEATURE_USE_SENDFILE(fin:) if (verbose > 1) bb_perror_msg("error"); } log_and_exit(); } -static int checkPermIP(void) +static void if_ip_denied_send_HTTP_FORBIDDEN_and_exit(unsigned remote_ip) { Htaccess_IP *cur; @@ -1663,16 +1867,20 @@ static int checkPermIP(void) (unsigned char)(cur->mask) ); #endif - if ((rmt_ip & cur->mask) == cur->ip) - return (cur->allow_deny == 'A'); /* A -> 1 */ + if ((remote_ip & cur->mask) == cur->ip) { + if (cur->allow_deny == 'A') + return; + send_headers_and_exit(HTTP_FORBIDDEN); + } } - return !flg_deny_all; /* depends on whether we saw "D:*" */ + if (flg_deny_all) /* depends on whether we saw "D:*" */ + send_headers_and_exit(HTTP_FORBIDDEN); } #if ENABLE_FEATURE_HTTPD_BASIC_AUTH -# if ENABLE_FEATURE_HTTPD_AUTH_MD5 && ENABLE_PAM +# if ENABLE_PAM struct pam_userinfo { const char *name; const char *pw; @@ -1704,9 +1912,9 @@ static int pam_talker(int num_msg, case PAM_PROMPT_ECHO_OFF: s = userinfo->pw; break; - case PAM_ERROR_MSG: - case PAM_TEXT_INFO: - s = ""; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + s = ""; break; default: free(response); @@ -1773,6 +1981,16 @@ static int check_user_passwd(const char *path, char *user_and_passwd) colon_after_user = strchr(user_and_passwd, ':'); if (!colon_after_user) goto bad_input; + + /* compare "user:" */ + if (cur->after_colon[0] != '*' + && strncmp(cur->after_colon, user_and_passwd, + colon_after_user - user_and_passwd + 1) != 0 + ) { + continue; + } + /* this cfg entry is '*' or matches username from peer */ + passwd = strchr(cur->after_colon, ':'); if (!passwd) goto bad_input; @@ -1783,13 +2001,6 @@ static int check_user_passwd(const char *path, char *user_and_passwd) struct pam_conv conv_info = { &pam_talker, (void *) &userinfo }; pam_handle_t *pamh; - /* compare "user:" */ - if (cur->after_colon[0] != '*' - && strncmp(cur->after_colon, user_and_passwd, colon_after_user - user_and_passwd + 1) != 0 - ) { - continue; - } - /* this cfg entry is '*' or matches username from peer */ *colon_after_user = '\0'; userinfo.name = user_and_passwd; userinfo.pw = colon_after_user + 1; @@ -1825,31 +2036,34 @@ static int check_user_passwd(const char *path, char *user_and_passwd) passwd = result->sp_pwdp; } # endif + /* In this case, passwd is ALWAYS encrypted: + * it came from /etc/passwd or /etc/shadow! + */ + goto check_encrypted; # endif /* ENABLE_PAM */ } + /* Else: passwd is from httpd.conf, it is either plaintext or encrypted */ - /* compare "user:" */ - if (cur->after_colon[0] != '*' - && strncmp(cur->after_colon, user_and_passwd, colon_after_user - user_and_passwd + 1) != 0 - ) { - continue; - } - /* this cfg entry is '*' or matches username from peer */ - - /* encrypt pwd from peer and check match with local one */ - { - char *encrypted = pw_encrypt( - /* pwd: */ colon_after_user + 1, + if (passwd[0] == '$' && isdigit(passwd[1])) { + char *encrypted; +# if !ENABLE_PAM + check_encrypted: +# endif + /* encrypt pwd from peer and check match with local one */ + encrypted = pw_encrypt( + /* pwd (from peer): */ colon_after_user + 1, /* salt: */ passwd, /* cleanup: */ 0 ); r = strcmp(encrypted, passwd); free(encrypted); - goto end_check_passwd; + } else { + /* local passwd is from httpd.conf and it's plaintext */ + r = strcmp(colon_after_user + 1, passwd); } - bad_input: ; + goto end_check_passwd; } - + bad_input: /* Comparing plaintext "user:pass" in one go */ r = strcmp(cur->after_colon, user_and_passwd); end_check_passwd: @@ -1871,7 +2085,7 @@ static Htaccess_Proxy *find_proxy_entry(const char *url) { Htaccess_Proxy *p; for (p = proxy; p; p = p->next) { - if (strncmp(url, p->url_from, strlen(p->url_from)) == 0) + if (is_prefixed_with(url, p->url_from)) return p; } return NULL; @@ -1898,12 +2112,15 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) char *urlcopy; char *urlp; char *tptr; + unsigned remote_ip; +#if ENABLE_FEATURE_HTTPD_CGI + unsigned total_headers_len; +#endif #if ENABLE_FEATURE_HTTPD_CGI static const char request_HEAD[] ALIGN1 = "HEAD"; const char *prequest; - char *cookie = NULL; - char *content_type = NULL; unsigned long length = 0; + enum CGI_type cgi_type = CGI_NONE; #elif ENABLE_FEATURE_HTTPD_PROXY #define prequest request_GET unsigned long length = 0; @@ -1911,29 +2128,23 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) #if ENABLE_FEATURE_HTTPD_BASIC_AUTH smallint authorized = -1; #endif - smallint ip_allowed; char http_major_version; -#if ENABLE_FEATURE_HTTPD_PROXY - char http_minor_version; - char *header_buf = header_buf; /* for gcc */ - char *header_ptr = header_ptr; - Htaccess_Proxy *proxy_entry; -#endif + char *HTTP_slash; /* Allocation of iobuf is postponed until now * (IOW, server process doesn't need to waste 8k) */ iobuf = xmalloc(IOBUF_SIZE); - rmt_ip = 0; + remote_ip = 0; if (fromAddr->u.sa.sa_family == AF_INET) { - rmt_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr); + remote_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr); } #if ENABLE_FEATURE_IPV6 if (fromAddr->u.sa.sa_family == AF_INET6 && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0 && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0 && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff) - rmt_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]); + remote_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]); #endif if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { /* NB: can be NULL (user runs httpd -i by hand?) */ @@ -1946,6 +2157,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) if (verbose > 2) bb_error_msg("connected"); } + if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip); /* Install timeout handler. get_line() needs it. */ signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit); @@ -1954,7 +2166,9 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) send_headers_and_exit(HTTP_BAD_REQUEST); /* Determine type of request (GET/POST) */ - urlp = strpbrk(iobuf, " \t"); + // rfc2616: method and URI is separated by exactly one space + //urlp = strpbrk(iobuf, " \t"); - no, tab isn't allowed + urlp = strchr(iobuf, ' '); if (urlp == NULL) send_headers_and_exit(HTTP_BAD_REQUEST); *urlp++ = '\0'; @@ -1972,35 +2186,64 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) if (strcasecmp(iobuf, request_GET) != 0) send_headers_and_exit(HTTP_NOT_IMPLEMENTED); #endif - urlp = skip_whitespace(urlp); + // rfc2616: method and URI is separated by exactly one space + //urlp = skip_whitespace(urlp); - should not be necessary if (urlp[0] != '/') send_headers_and_exit(HTTP_BAD_REQUEST); /* Find end of URL and parse HTTP version, if any */ - http_major_version = '0'; - IF_FEATURE_HTTPD_PROXY(http_minor_version = '0';) - tptr = strchrnul(urlp, ' '); +//TODO: maybe just reject all queries which have no " HTTP/xyz" suffix? +//Then 'http_major_version' can be deleted + http_major_version = ('0' - 1); /* "less than 0th" version */ + HTTP_slash = strchrnul(urlp, ' '); /* Is it " HTTP/"? */ - if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) { - http_major_version = tptr[6]; - IF_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];) + if (HTTP_slash[0] && strncmp(HTTP_slash + 1, HTTP_200, 5) == 0) { + http_major_version = HTTP_slash[6]; + *HTTP_slash++ = '\0'; } - *tptr = '\0'; /* Copy URL from after "GET "/"POST " to stack-allocated char[] */ - urlcopy = alloca((tptr - urlp) + 2 + strlen(index_page)); + urlcopy = alloca((HTTP_slash - urlp) + 2 + strlen(index_page)); /*if (urlcopy == NULL) * send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/ strcpy(urlcopy, urlp); /* NB: urlcopy ptr is never changed after this */ - /* Extract url args if present */ - /* g_query = NULL; - already is */ - tptr = strchr(urlcopy, '?'); - if (tptr) { - *tptr++ = '\0'; - g_query = tptr; +#if ENABLE_FEATURE_HTTPD_PROXY + { + int proxy_fd; + len_and_sockaddr *lsa; + Htaccess_Proxy *proxy_entry = find_proxy_entry(urlcopy); + + if (proxy_entry) { + lsa = host2sockaddr(proxy_entry->host_port, 80); + if (!lsa) + send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); + proxy_fd = socket(lsa->u.sa.sa_family, SOCK_STREAM, 0); + if (proxy_fd < 0) + send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); + if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0) + send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); + /* Config directive was of the form: + * P:/url:[http://]hostname[:port]/new/path + * When /urlSFX is requested, reverse proxy it + * to http://hostname[:port]/new/pathSFX + */ + fdprintf(proxy_fd, "%s %s%s %s\r\n", + prequest, /* "GET" or "POST" */ + proxy_entry->url_to, /* "/new/path" */ + urlcopy + strlen(proxy_entry->url_from), /* "SFX" */ + HTTP_slash /* HTTP/xyz" or "" */ + ); + cgi_io_loop_and_exit(proxy_fd, proxy_fd, /*max POST length:*/ INT_MAX); + } } +#endif + + /* Extract url args if present */ + g_query = strchr(urlcopy, '?'); + if (g_query) + *g_query++ = '\0'; /* Decode URL escape sequences */ tptr = percent_decode_in_place(urlcopy, /*strict:*/ 1); @@ -2040,7 +2283,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) } } *++urlp = *tptr; - if (*urlp == '\0') + if (*tptr == '\0') break; next_char: tptr++; @@ -2048,7 +2291,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) /* If URL is a directory, add '/' */ if (urlp[-1] != '/') { - if (is_directory(urlcopy + 1, 1, NULL)) { + if (is_directory(urlcopy + 1, /*followlinks:*/ 1)) { found_moved_temporarily = urlcopy; } } @@ -2058,59 +2301,97 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) bb_error_msg("url:%s", urlcopy); tptr = urlcopy; - ip_allowed = checkPermIP(); - while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) { + while ((tptr = strchr(tptr + 1, '/')) != NULL) { /* have path1/path2 */ *tptr = '\0'; - if (is_directory(urlcopy + 1, 1, NULL)) { + if (is_directory(urlcopy + 1, /*followlinks:*/ 1)) { /* may have subdir config */ parse_conf(urlcopy + 1, SUBDIR_PARSE); - ip_allowed = checkPermIP(); + if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip); } *tptr = '/'; } -#if ENABLE_FEATURE_HTTPD_PROXY - proxy_entry = find_proxy_entry(urlcopy); - if (proxy_entry) - header_buf = header_ptr = xmalloc(IOBUF_SIZE); + tptr = urlcopy + 1; /* skip first '/' */ + +#if ENABLE_FEATURE_HTTPD_CGI + if (is_prefixed_with(tptr, "cgi-bin/")) { + if (tptr[8] == '\0') { + /* protect listing "cgi-bin/" */ + send_headers_and_exit(HTTP_FORBIDDEN); + } + cgi_type = CGI_NORMAL; + } #endif + if (urlp[-1] == '/') { + /* When index_page string is appended to / URL, it overwrites + * the query string. If we fall back to call /cgi-bin/index.cgi, + * query string would be lost and not available to the CGI. + * Work around it by making a deep copy. + */ + if (ENABLE_FEATURE_HTTPD_CGI) + g_query = xstrdup(g_query); /* ok for NULL too */ + strcpy(urlp, index_page); + } + if (stat(tptr, &sb) == 0) { +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + char *suffix = strrchr(tptr, '.'); + if (suffix) { + Htaccess *cur; + for (cur = script_i; cur; cur = cur->next) { + if (strcmp(cur->before_colon + 1, suffix) == 0) { + cgi_type = CGI_INTERPRETER; + break; + } + } + } +#endif + if (!found_moved_temporarily) { + file_size = sb.st_size; + last_mod = sb.st_mtime; + } + } +#if ENABLE_FEATURE_HTTPD_CGI + else if (urlp[-1] == '/') { + /* It's a dir URL and there is no index.html + * Try cgi-bin/index.cgi */ + if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) { + cgi_type = CGI_INDEX; + } + } +#endif + urlp[0] = '\0'; + +#if ENABLE_FEATURE_HTTPD_CGI + total_headers_len = 0; +#endif if (http_major_version >= '0') { /* Request was with "... HTTP/nXXX", and n >= 0 */ /* Read until blank line */ while (1) { - if (!get_line()) + unsigned iobuf_len = get_line(); + if (!iobuf_len) break; /* EOF or error or empty line */ +#if ENABLE_FEATURE_HTTPD_CGI + /* Prevent unlimited growth of HTTP_xyz envvars */ + total_headers_len += iobuf_len; + if (total_headers_len >= MAX_HTTP_HEADERS_SIZE) + send_headers_and_exit(HTTP_ENTITY_TOO_LARGE); +#endif if (DEBUG) bb_error_msg("header: '%s'", iobuf); - -#if ENABLE_FEATURE_HTTPD_PROXY - /* We need 2 more bytes for yet another "\r\n" - - * see near fdprintf(proxy_fd...) further below */ - if (proxy_entry && (header_ptr - header_buf) < IOBUF_SIZE - 2) { - int len = strlen(iobuf); - if (len > IOBUF_SIZE - (header_ptr - header_buf) - 4) - len = IOBUF_SIZE - (header_ptr - header_buf) - 4; - memcpy(header_ptr, iobuf, len); - header_ptr += len; - header_ptr[0] = '\r'; - header_ptr[1] = '\n'; - header_ptr += 2; - } -#endif - #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY /* Try and do our best to parse more lines */ - if ((STRNCASECMP(iobuf, "Content-length:") == 0)) { + if (STRNCASECMP(iobuf, "Content-Length:") == 0) { /* extra read only for POST */ if (prequest != request_GET # if ENABLE_FEATURE_HTTPD_CGI && prequest != request_HEAD # endif ) { - tptr = skip_whitespace(iobuf + sizeof("Content-length:") - 1); + tptr = skip_whitespace(iobuf + sizeof("Content-Length:") - 1); if (!tptr[0]) send_headers_and_exit(HTTP_BAD_REQUEST); /* not using strtoul: it ignores leading minus! */ @@ -2119,23 +2400,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) if (errno || length > INT_MAX) send_headers_and_exit(HTTP_BAD_REQUEST); } - } -#endif -#if ENABLE_FEATURE_HTTPD_CGI - else if (STRNCASECMP(iobuf, "Cookie:") == 0) { - cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1)); - } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) { - content_type = xstrdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1)); - } else if (STRNCASECMP(iobuf, "Referer:") == 0) { - referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1)); - } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) { - user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1)); - } else if (STRNCASECMP(iobuf, "Host:") == 0) { - host = xstrdup(skip_whitespace(iobuf + sizeof("Host:")-1)); - } else if (STRNCASECMP(iobuf, "Accept:") == 0) { - http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1)); - } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) { - http_accept_language = xstrdup(skip_whitespace(iobuf + sizeof("Accept-Language:")-1)); + continue; } #endif #if ENABLE_FEATURE_HTTPD_BASIC_AUTH @@ -2145,29 +2410,31 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) * ":" is base64 encoded. */ tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1); - if (STRNCASECMP(tptr, "Basic") != 0) + if (STRNCASECMP(tptr, "Basic") == 0) { + tptr += sizeof("Basic")-1; + /* decodeBase64() skips whitespace itself */ + decodeBase64(tptr); + authorized = check_user_passwd(urlcopy, tptr); continue; - tptr += sizeof("Basic")-1; - /* decodeBase64() skips whitespace itself */ - decodeBase64(tptr); - authorized = check_user_passwd(urlcopy, tptr); + } } #endif #if ENABLE_FEATURE_HTTPD_RANGES if (STRNCASECMP(iobuf, "Range:") == 0) { /* We know only bytes=NNN-[MMM] */ char *s = skip_whitespace(iobuf + sizeof("Range:")-1); - if (strncmp(s, "bytes=", 6) == 0) { + if (is_prefixed_with(s, "bytes=")) { s += sizeof("bytes=")-1; range_start = BB_STRTOOFF(s, &s, 10); if (s[0] != '-' || range_start < 0) { - range_start = 0; + range_start = -1; } else if (s[1]) { range_end = BB_STRTOOFF(s+1, NULL, 10); if (errno || range_end < range_start) - range_start = 0; + range_start = -1; } } + continue; } #endif #if ENABLE_FEATURE_HTTPD_GZIP @@ -2185,6 +2452,35 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) content_gzip = 1; //} } + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_CGI + if (cgi_type != CGI_NONE) { + bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0); + char *cp; + char *colon = strchr(iobuf, ':'); + + if (!colon) + continue; + cp = iobuf; + while (cp < colon) { + /* a-z => A-Z, not-alnum => _ */ + char c = (*cp & ~0x20); /* toupper for A-Za-z, undef for others */ + if ((unsigned)(c - 'A') <= ('Z' - 'A')) { + *cp++ = c; + continue; + } + if (!isdigit(*cp)) + *cp = '_'; + cp++; + } + /* "Content-Type:" gets no HTTP_ prefix, all others do */ + cp = xasprintf(ct ? "HTTP_%.*s=%s" + 5 : "HTTP_%.*s=%s", + (int)(colon - iobuf), iobuf, + skip_whitespace(colon + 1) + ); + putenv(cp); } #endif } /* while extra header reading */ @@ -2193,7 +2489,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) /* We are done reading headers, disable peer timeout */ alarm(0); - if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0 || !ip_allowed) { + if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0) { /* protect listing [/path]/httpd.conf or IP deny */ send_headers_and_exit(HTTP_FORBIDDEN); } @@ -2211,76 +2507,23 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) send_headers_and_exit(HTTP_MOVED_TEMPORARILY); } -#if ENABLE_FEATURE_HTTPD_PROXY - if (proxy_entry != NULL) { - int proxy_fd; - len_and_sockaddr *lsa; - - proxy_fd = socket(AF_INET, SOCK_STREAM, 0); - if (proxy_fd < 0) - send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); - lsa = host2sockaddr(proxy_entry->host_port, 80); - if (lsa == NULL) - send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); - if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0) - send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); - fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n", - prequest, /* GET or POST */ - proxy_entry->url_to, /* url part 1 */ - urlcopy + strlen(proxy_entry->url_from), /* url part 2 */ - (g_query ? "?" : ""), /* "?" (maybe) */ - (g_query ? g_query : ""), /* query string (maybe) */ - http_major_version, http_minor_version); - header_ptr[0] = '\r'; - header_ptr[1] = '\n'; - header_ptr += 2; - write(proxy_fd, header_buf, header_ptr - header_buf); - free(header_buf); /* on the order of 8k, free it */ - cgi_io_loop_and_exit(proxy_fd, proxy_fd, length); - } -#endif - tptr = urlcopy + 1; /* skip first '/' */ #if ENABLE_FEATURE_HTTPD_CGI - if (strncmp(tptr, "cgi-bin/", 8) == 0) { - if (tptr[8] == '\0') { - /* protect listing "cgi-bin/" */ - send_headers_and_exit(HTTP_FORBIDDEN); - } - send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type); + if (cgi_type != CGI_NONE) { + send_cgi_and_exit( + (cgi_type == CGI_INDEX) ? "/cgi-bin/index.cgi" + /*CGI_NORMAL or CGI_INTERPRETER*/ : urlcopy, + urlcopy, prequest, length + ); } #endif - if (urlp[-1] == '/') + if (urlp[-1] == '/') { strcpy(urlp, index_page); - if (stat(tptr, &sb) == 0) { -#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR - char *suffix = strrchr(tptr, '.'); - if (suffix) { - Htaccess *cur; - for (cur = script_i; cur; cur = cur->next) { - if (strcmp(cur->before_colon + 1, suffix) == 0) { - send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type); - } - } - } -#endif - file_size = sb.st_size; - last_mod = sb.st_mtime; } -#if ENABLE_FEATURE_HTTPD_CGI - else if (urlp[-1] == '/') { - /* It's a dir URL and there is no index.html - * Try cgi-bin/index.cgi */ - if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) { - urlp[0] = '\0'; - g_query = urlcopy; - send_cgi_and_exit("/cgi-bin/index.cgi", prequest, length, cookie, content_type); - } - } - /* else fall through to send_file, it errors out if open fails: */ +#if ENABLE_FEATURE_HTTPD_CGI if (prequest != request_GET && prequest != request_HEAD) { /* POST for files does not make sense */ send_headers_and_exit(HTTP_NOT_IMPLEMENTED); @@ -2319,7 +2562,7 @@ static void mini_httpd(int server_socket) continue; /* set the KEEPALIVE option to cull dead connections */ - setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); + setsockopt_keepalive(n); if (fork() == 0) { /* child */ @@ -2353,16 +2596,14 @@ static void mini_httpd_nommu(int server_socket, int argc, char **argv) */ while (1) { int n; - len_and_sockaddr fromAddr; /* Wait for connections... */ - fromAddr.len = LSA_SIZEOF_SA; - n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len); + n = accept(server_socket, NULL, NULL); if (n < 0) continue; /* set the KEEPALIVE option to cull dead connections */ - setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); + setsockopt_keepalive(n); if (vfork() == 0) { /* child */ @@ -2401,7 +2642,9 @@ static void mini_httpd_inetd(void) static void sighup_handler(int sig UNUSED_PARAM) { + int sv = errno; parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE); + errno = sv; } enum { @@ -2449,17 +2692,19 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) #endif home_httpd = xrealloc_getcwd_or_warn(NULL); - /* -v counts, -i implies -f */ - opt_complementary = "vv:if"; /* We do not "absolutize" path given by -h (home) opt. * If user gives relative path in -h, * $SCRIPT_FILENAME will not be set. */ - opt = getopt32(argv, "c:d:h:" + opt = getopt32(argv, "^" + "c:d:h:" IF_FEATURE_HTTPD_ENCODE_URL_STR("e:") IF_FEATURE_HTTPD_BASIC_AUTH("r:") IF_FEATURE_HTTPD_AUTH_MD5("m:") IF_FEATURE_HTTPD_SETUID("u:") - "p:ifv", + "p:ifv" + "\0" + /* -v counts, -i implies -f */ + "vv:if", &opt_c_configFile, &url_for_decode, &home_httpd IF_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode) IF_FEATURE_HTTPD_BASIC_AUTH(, &g_realm) @@ -2542,7 +2787,7 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) xfunc_error_retval = 0; if (opt & OPT_INETD) - mini_httpd_inetd(); + mini_httpd_inetd(); /* never returns */ #if BB_MMU if (!(opt & OPT_FOREGROUND)) bb_daemonize(0); /* don't change current directory */