X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Fhttpd.c;h=cef9b8baf8de4c8e3717f2b60497308542b8cd47;hb=3b45801dc6e3484ffb2a86958fce587974842c6b;hp=c66e0f66b946ea89843426828f8d6e8947e9e1c7;hpb=7291755439ad2f400df51a74b4e9a31a48f484b1;p=oweals%2Fbusybox.git diff --git a/networking/httpd.c b/networking/httpd.c index c66e0f66b..cef9b8baf 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -348,7 +348,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), }; @@ -370,6 +370,7 @@ enum { #define INIT_G() do { \ 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; \ @@ -795,9 +796,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 @@ -1265,18 +1266,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 +1288,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 +1302,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 +1394,7 @@ static void send_cgi_and_exit( log_and_exit(); } - if (!pid) { + if (pid == 0) { /* Child process */ char *argv[3]; @@ -1403,11 +1410,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 +1590,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); @@ -1617,7 +1624,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(); } @@ -1669,7 +1676,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 +1708,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); @@ -1763,10 +1770,23 @@ static int check_user_passwd(const char *path, char *user_and_passwd) if (ENABLE_FEATURE_HTTPD_AUTH_MD5) { char *colon_after_user; const char *passwd; +# if ENABLE_FEATURE_SHADOWPASSWDS && !ENABLE_PAM + char sp_buf[256]; +# endif 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; @@ -1777,28 +1797,22 @@ 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; - r = pam_start("httpd", user_and_passwd, &conv_info, &pamh) != PAM_SUCCESS - || pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS - || pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS - ; - pam_end(pamh, PAM_SUCCESS); + r = pam_start("httpd", user_and_passwd, &conv_info, &pamh) != PAM_SUCCESS; + if (r == 0) { + r = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS + || pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS + ; + pam_end(pamh, PAM_SUCCESS); + } *colon_after_user = ':'; goto end_check_passwd; # else # if ENABLE_FEATURE_SHADOWPASSWDS /* Using _r function to avoid pulling in static buffers */ struct spwd spw; - char buffer[256]; # endif struct passwd *pw; @@ -1813,36 +1827,39 @@ static int check_user_passwd(const char *path, char *user_and_passwd) /* getspnam_r may return 0 yet set result to NULL. * At least glibc 2.4 does this. Be extra paranoid here. */ struct spwd *result = NULL; - r = getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result); + r = getspnam_r(pw->pw_name, &spw, sp_buf, sizeof(sp_buf), &result); if (r == 0 && result) 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: @@ -1947,7 +1964,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'; @@ -1965,7 +1984,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); @@ -1988,7 +2008,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'; @@ -2008,34 +2028,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; } } @@ -2049,7 +2075,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,11 +2174,11 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) 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; } } } @@ -2235,12 +2261,20 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) /* 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, '.'); @@ -2248,7 +2282,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); } } } @@ -2261,9 +2295,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: */