X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Fhttpd.c;h=39aad90a8eca29cfe8de72300696fdaa303baadc;hb=7a18b9502aedbd6a9201c7c7603ded997a401f53;hp=0356e4c1b274e0ff3a9e92a3a7d729bccba0cd11;hpb=0fa3e5f6f9ad55871d52bd10988fec66398f3d65;p=oweals%2Fbusybox.git diff --git a/networking/httpd.c b/networking/httpd.c index 0356e4c1b..39aad90a8 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -98,6 +98,128 @@ * */ /* TODO: use TCP_CORK, parse_config() */ +//config:config HTTPD +//config: bool "httpd" +//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 Basic 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 crypted passwords for 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]]" @@ -125,6 +247,7 @@ //usage: "\n -d STRING URL decode STRING" #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 */ @@ -307,7 +430,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 @@ -348,7 +472,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), }; @@ -368,8 +492,10 @@ enum { # 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 +822,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 +921,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 @@ -922,16 +1048,16 @@ static void log_and_exit(void) static void send_headers(int 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 */ const char *responseString = ""; const char *infoString = NULL; - const char *mime_type; #if ENABLE_FEATURE_HTTPD_ERROR_PAGES const char *error_page = NULL; #endif unsigned i; time_t timer = time(NULL); - char tmp_str[80]; int len; for (i = 0; i < ARRAY_SIZE(http_response_type); i++) { @@ -944,41 +1070,61 @@ 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(&timer)); 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 %d %s\r\n" + "Content-type: %s\r\n" + "Date: %s\r\n" + "Connection: close\r\n", + responseNum, responseString, + /* if it's error message, then it's HTML */ + (responseNum == HTTP_OK ? found_mime_type : "text/html"), + date_str + ); #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,13 +1133,15 @@ 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(&last_mod)); #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 @@ -1001,8 +1149,9 @@ static void send_headers(int responseNum) #if ENABLE_FEATURE_HTTPD_RANGES "Accept-Ranges: bytes\r\n" #endif - "Last-Modified: %s\r\n%s %"OFF_FMT"u\r\n", - tmp_str, + "Last-Modified: %s\r\n" + "%s %"OFF_FMT"u\r\n", + date_str, content_gzip ? "Transfer-length:" : "Content-length:", file_size ); @@ -1016,12 +1165,18 @@ static void send_headers(int responseNum) if (infoString) { len += sprintf(iobuf + len, "%d %s\n" - "

%d %s

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

%d %s

\n" + "%s\n" + "\n", + responseNum, responseString, responseNum, responseString, - responseNum, responseString, infoString); + 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"); @@ -1052,7 +1207,7 @@ static int get_line(void) alarm(HEADER_READ_TIMEOUT); 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; hdr_ptr = hdr_buf; @@ -1103,18 +1258,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 +1294,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) { @@ -1143,7 +1311,7 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post } 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 +1332,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; @@ -1208,12 +1376,12 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post out_cnt += count; count = 0; /* "Status" header format is: "Status: 302 Redirected\r\n" */ - if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { + if (out_cnt >= 7 && memcmp(rbuf, "Status:", 7) == 0) { /* send "HTTP/1.0 " */ if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) break; - rbuf += 8; /* skip "Status: " */ - count = out_cnt - 8; + rbuf += 7; /* skip "Status:" */ + count = out_cnt - 7; out_cnt = -1; /* buffering off */ } else if (out_cnt >= 4) { /* Did CGI add "HTTP"? */ @@ -1265,18 +1433,21 @@ 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; static void send_cgi_and_exit( const char *url, + const char *orig_uri, const char *request, int post_len, const char *cookie, @@ -1284,7 +1455,7 @@ static void send_cgi_and_exit( { struct fd_pair fromCgi; /* CGI -> httpd pipe */ struct fd_pair toCgi; /* httpd -> CGI pipe */ - char *script; + char *script, *last_slash; int pid; /* Make a copy. NB: caller guarantees: @@ -1298,22 +1469,25 @@ static void send_cgi_and_exit( */ /* Check for [dirs/]script.cgi/PATH_INFO */ - script = (char*)url; + last_slash = script = (char*)url; while ((script = strchr(script + 1, '/')) != NULL) { + int dir; *script = '\0'; - if (!is_directory(url + 1, 1, NULL)) { + dir = is_directory(url + 1, /*followlinks:*/ 1); + *script = '/'; + if (!dir) { /* not directory, found script.cgi/PATH_INFO */ - *script = '/'; break; } - *script = '/'; /* is directory, find next '/' */ + /* is directory, find next '/' */ + last_slash = script; } 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 */ @@ -1387,7 +1561,7 @@ static void send_cgi_and_exit( log_and_exit(); } - if (!pid) { + if (pid == 0) { /* Child process */ char *argv[3]; @@ -1403,11 +1577,11 @@ static void send_cgi_and_exit( /* dup2(1, 2); */ /* Chdiring to script's dir */ - script = strrchr(url, '/'); + script = last_slash; 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 = '/'; @@ -1583,18 +1757,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); @@ -1604,7 +1778,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) { @@ -1617,7 +1791,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(); } @@ -1634,7 +1808,7 @@ 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"); } @@ -1669,7 +1843,7 @@ static int checkPermIP(void) #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; @@ -1701,9 +1875,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); @@ -1770,6 +1944,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; @@ -1780,13 +1964,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; @@ -1822,31 +1999,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: @@ -1868,7 +2048,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; @@ -1951,7 +2131,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'; @@ -1969,7 +2151,8 @@ 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); @@ -1992,7 +2175,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) /* NB: urlcopy ptr is never changed after this */ /* Extract url args if present */ - g_query = NULL; + /* g_query = NULL; - already is */ tptr = strchr(urlcopy, '?'); if (tptr) { *tptr++ = '\0'; @@ -2012,34 +2195,40 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) /* Algorithm stolen from libbb bb_simplify_path(), * but don't strdup, retain trailing slash, protect root */ urlp = tptr = urlcopy; - do { + for (;;) { if (*urlp == '/') { /* skip duplicate (or initial) slash */ if (*tptr == '/') { - continue; + goto next_char; } if (*tptr == '.') { - /* skip extra "/./" */ - if (tptr[1] == '/' || !tptr[1]) { - continue; - } - /* "..": be careful */ - if (tptr[1] == '.' && (tptr[2] == '/' || !tptr[2])) { - ++tptr; - if (urlp == urlcopy) /* protect root */ + if (tptr[1] == '.' && (tptr[2] == '/' || tptr[2] == '\0')) { + /* "..": be careful */ + /* protect root */ + if (urlp == urlcopy) send_headers_and_exit(HTTP_BAD_REQUEST); - while (*--urlp != '/') /* omit previous dir */; + /* omit previous dir */ + while (*--urlp != '/') continue; + /* skip to "./" or "." */ + tptr++; + } + if (tptr[1] == '/' || tptr[1] == '\0') { + /* skip extra "/./" */ + goto next_char; } } } *++urlp = *tptr; - } while (*++tptr); - *++urlp = '\0'; /* terminate after last character */ + if (*urlp == '\0') + break; + next_char: + tptr++; + } /* 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; } } @@ -2053,7 +2242,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) while (ip_allowed && (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(); @@ -2148,15 +2337,15 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) 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=") == 0) { 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; } } } @@ -2207,12 +2396,12 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) 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); + 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); fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n", @@ -2234,17 +2423,25 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) tptr = urlcopy + 1; /* skip first '/' */ #if ENABLE_FEATURE_HTTPD_CGI - if (strncmp(tptr, "cgi-bin/", 8) == 0) { + if (is_prefixed_with(tptr, "cgi-bin/")) { if (tptr[8] == '\0') { /* protect listing "cgi-bin/" */ send_headers_and_exit(HTTP_FORBIDDEN); } - send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type); + send_cgi_and_exit(urlcopy, urlcopy, prequest, length, cookie, content_type); } #endif - if (urlp[-1] == '/') + 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, '.'); @@ -2252,7 +2449,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) 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); + send_cgi_and_exit(urlcopy, urlcopy, prequest, length, cookie, content_type); } } } @@ -2265,9 +2462,8 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) /* 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); + urlp[0] = '\0'; /* remove index_page */ + send_cgi_and_exit("/cgi-bin/index.cgi", urlcopy, prequest, length, cookie, content_type); } } /* else fall through to send_file, it errors out if open fails: */ @@ -2310,7 +2506,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,7 +2549,7 @@ static void mini_httpd_nommu(int server_socket, int argc, char **argv) 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 */