Bump to version 1.22.1
[platform/upstream/busybox.git] / networking / httpd.c
index e9cd213..621d9cd 100644 (file)
@@ -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
@@ -1103,18 +1104,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 +1140,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 +1157,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
@@ -1265,18 +1279,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,
@@ -1314,9 +1331,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 */
@@ -1410,7 +1427,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 = '/';
@@ -1586,18 +1603,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);
@@ -1620,7 +1637,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();
                }
@@ -1672,7 +1689,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;
@@ -1704,9 +1721,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 +1790,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 +1810,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 +1845,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:
@@ -1954,7 +1977,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,7 +1997,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);
 
@@ -2161,11 +2187,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;
                                        }
                                }
                        }
@@ -2248,12 +2274,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 <dir>/ 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, '.');
@@ -2261,7 +2295,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);
                                }
                        }
                }
@@ -2274,9 +2308,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: */