ntpd: remove some code which is at best unneeded and at worst wrong
[oweals/busybox.git] / networking / httpd.c
index 794a39ea05608b0b0ac92accacd3905b3017171c..227803abf03a5ff2ff195fc97df69f36b63d6e00 100644 (file)
  * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication"
  *
  *
- * When a url starts by "/cgi-bin/" it is assumed to be a cgi script.  The
+ * When an url starts by "/cgi-bin/" it is assumed to be a cgi script.  The
  * server changes directory to the location of the script and executes it
  * after setting QUERY_STRING and other environment variables.
  *
  * Doc:
  * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
  *
- * The applet can also be invoked as a url arg decoder and html text encoder
+ * The applet can also be invoked as an url arg decoder and html text encoder
  * as follows:
  *  foo=`httpd -d $foo`           # decode "Hello%20World" as "Hello World"
  *  bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
  * Note that url encoding for arguments is not the same as html encoding for
- * presentation.  -d decodes a url-encoded argument while -e encodes in html
+ * presentation.  -d decodes an url-encoded argument while -e encodes in html
  * for page display.
  *
  * httpd.conf has the following format:
  * server exits with an error.
  *
  */
+ /* TODO: use TCP_CORK, parse_config() */
 
 #include "libbb.h"
 #if ENABLE_FEATURE_HTTPD_USE_SENDFILE
 # include <sys/sendfile.h>
 #endif
 
-//#define DEBUG 1
 #define DEBUG 0
 
 #define IOBUF_SIZE 8192    /* IO buffer */
@@ -231,6 +231,8 @@ static const struct {
 #endif
 };
 
+static const char index_html[] ALIGN1 = "index.html";
+
 
 struct globals {
        int verbose;            /* must be int (used by getopt32) */
@@ -328,7 +330,7 @@ enum {
        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
        IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
        bind_addr_or_port = "80"; \
-       index_page = "index.html"; \
+       index_page = index_html; \
        file_size = -1; \
 } while (0)
 
@@ -531,32 +533,46 @@ static void parse_conf(const char *path, int flag)
        while (fgets(buf, sizeof(buf), f) != NULL) {
                unsigned strlen_buf;
                unsigned char ch;
-               char *after_colon = NULL;
+               char *after_colon;
 
                { /* remove all whitespace, and # comments */
                        char *p, *p0;
 
-                       p = p0 = buf;
-                       while ((ch = *p0++) != '\0' && ch != '#') {
-                               if (!isspace(ch)) {
+                       p0 = buf;
+                       /* skip non-whitespace beginning. Often the whole line
+                        * is non-whitespace. We want this case to work fast,
+                        * without needless copying, therefore we don't merge
+                        * this operation into next while loop. */
+                       while ((ch = *p0) != '\0' && ch != '\n' && ch != '#'
+                        && ch != ' ' && ch != '\t'
+                       ) {
+                               p0++;
+                       }
+                       p = p0;
+                       /* if we enter this loop, we have some whitespace.
+                        * discard it */
+                       while (ch != '\0' && ch != '\n' && ch != '#') {
+                               if (ch != ' ' && ch != '\t') {
                                        *p++ = ch;
-                                       if (ch == ':' && after_colon == NULL)
-                                               after_colon = p;
                                }
+                               ch = *++p0;
                        }
                        *p = '\0';
                        strlen_buf = p - buf;
                        if (strlen_buf == 0)
-                               continue;
+                               continue; /* empty line */
                }
 
+               after_colon = strchr(buf, ':');
                /* strange line? */
-               if (after_colon == NULL || *after_colon == '\0')
+               if (after_colon == NULL || *++after_colon == '\0')
                        goto config_error;
 
                ch = (buf[0] & ~0x20); /* toupper if it's a letter */
 
                if (ch == 'I') {
+                       if (index_page != index_html)
+                               free((char*)index_page);
                        index_page = xstrdup(after_colon);
                        continue;
                }
@@ -698,15 +714,15 @@ static void parse_conf(const char *path, int flag)
                        unsigned file_len;
 
                        /* note: path is "" unless we are in SUBDIR parse,
-                        * otherwise it always starts with "/" */
+                        * otherwise it does NOT start with "/" */
                        cur = xzalloc(sizeof(*cur) /* includes space for NUL */
-                               + strlen(path)
+                               + 1 + strlen(path)
                                + strlen_buf
                                );
                        /* form "/path/file" */
-                       sprintf(cur->before_colon, "%s%.*s",
+                       sprintf(cur->before_colon, "/%s%.*s",
                                path,
-                               after_colon - buf - 1, /* includes "/", but not ":" */
+                               (int) (after_colon - buf - 1), /* includes "/", but not ":" */
                                buf);
                        /* canonicalize it */
                        p = bb_simplify_abs_path_inplace(cur->before_colon);
@@ -1005,7 +1021,7 @@ static void send_headers(int responseNum)
                strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod));
 #if ENABLE_FEATURE_HTTPD_RANGES
                if (responseNum == HTTP_PARTIAL_CONTENT) {
-                       len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"d-%"OFF_FMT"d/%"OFF_FMT"d\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);
@@ -1016,7 +1032,7 @@ 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"d\r\n",
+                       "Last-Modified: %s\r\n%s %"OFF_FMT"u\r\n",
                                tmp_str,
                                "Content-length:",
                                file_size
@@ -1151,7 +1167,7 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post
                        break;
                }
 
-               if (pfd[TO_CGI].revents) {
+               if (pfd[TO_CGI].revents & POLLOUT) {
                        /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */
                        /* Have data from peer and can write to CGI */
                        count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt);
@@ -1168,7 +1184,7 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post
                        }
                }
 
-               if (pfd[0].revents) {
+               if (pfd[0].revents & POLLIN) {
                        /* post_len > 0 && hdr_cnt == 0 here */
                        /* We expect data, prev data portion is eaten by CGI
                         * and there *is* data to read from the peer
@@ -1186,7 +1202,7 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post
                        }
                }
 
-               if (pfd[FROM_CGI].revents) {
+               if (pfd[FROM_CGI].revents & POLLIN) {
                        /* There is something to read from CGI */
                        char *rbuf = iobuf;
 
@@ -1309,10 +1325,8 @@ static void send_cgi_and_exit(
        /* Check for [dirs/]script.cgi/PATH_INFO */
        script = (char*)url;
        while ((script = strchr(script + 1, '/')) != NULL) {
-               struct stat sb;
-
                *script = '\0';
-               if (!is_directory(url + 1, 1, &sb)) {
+               if (!is_directory(url + 1, 1, NULL)) {
                        /* not directory, found script.cgi/PATH_INFO */
                        *script = '/';
                        break;
@@ -1488,32 +1502,8 @@ static void send_cgi_and_exit(
  */
 static NOINLINE void send_file_and_exit(const char *url, int what)
 {
-       static const char *const suffixTable[] = {
-       /* Warning: shorter equivalent suffix in one line must be first */
-               ".htm.html", "text/html",
-               ".jpg.jpeg", "image/jpeg",
-               ".gif",      "image/gif",
-               ".png",      "image/png",
-               ".txt.h.c.cc.cpp", "text/plain",
-               ".css",      "text/css",
-               ".wav",      "audio/wav",
-               ".avi",      "video/x-msvideo",
-               ".qt.mov",   "video/quicktime",
-               ".mpe.mpeg", "video/mpeg",
-               ".mid.midi", "audio/midi",
-               ".mp3",      "audio/mpeg",
-#if 0                        /* unpopular */
-               ".au",       "audio/basic",
-               ".pac",      "application/x-ns-proxy-autoconfig",
-               ".vrml.wrl", "model/vrml",
-#endif
-               NULL
-       };
-
        char *suffix;
        int fd;
-       const char *const *table;
-       const char *try_suffix;
        ssize_t count;
 
        fd = open(url, O_RDONLY);
@@ -1527,31 +1517,65 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
                        send_headers_and_exit(HTTP_NOT_FOUND);
                log_and_exit();
        }
-
-       if (DEBUG)
-               bb_error_msg("sending file '%s' content-type: %s",
-                       url, found_mime_type);
-
        /* If you want to know about EPIPE below
         * (happens if you abort downloads from local httpd): */
        signal(SIGPIPE, SIG_IGN);
 
-       suffix = strrchr(url, '.');
-
-       /* If not found, set default as "application/octet-stream";  */
+       /* If not found, default is "application/octet-stream" */
        found_mime_type = "application/octet-stream";
+       suffix = strrchr(url, '.');
        if (suffix) {
+               static const char suffixTable[] ALIGN1 =
+                       /* Shorter suffix must be first:
+                        * ".html.htm" will fail for ".htm"
+                        */
+                       ".txt.h.c.cc.cpp\0" "text/plain\0"
+                       /* .htm line must be after .h line */
+                       ".htm.html\0" "text/html\0"
+                       ".jpg.jpeg\0" "image/jpeg\0"
+                       ".gif\0"      "image/gif\0"
+                       ".png\0"      "image/png\0"
+                       /* .css line must be after .c line */
+                       ".css\0"      "text/css\0"
+                       ".wav\0"      "audio/wav\0"
+                       ".avi\0"      "video/x-msvideo\0"
+                       ".qt.mov\0"   "video/quicktime\0"
+                       ".mpe.mpeg\0" "video/mpeg\0"
+                       ".mid.midi\0" "audio/midi\0"
+                       ".mp3\0"      "audio/mpeg\0"
+#if 0  /* unpopular */
+                       ".au\0"       "audio/basic\0"
+                       ".pac\0"      "application/x-ns-proxy-autoconfig\0"
+                       ".vrml.wrl\0" "model/vrml\0"
+#endif
+                       /* compiler adds another "\0" here */
+               ;
                Htaccess *cur;
-               for (table = suffixTable; *table; table += 2) {
-                       try_suffix = strstr(table[0], suffix);
-                       if (try_suffix) {
-                               try_suffix += strlen(suffix);
-                               if (*try_suffix == '\0' || *try_suffix == '.') {
-                                       found_mime_type = table[1];
-                                       break;
-                               }
+
+               /* Examine built-in table */
+               const char *table = suffixTable;
+               const char *table_next;
+               for (; *table; table = table_next) {
+                       const char *try_suffix;
+                       const char *mime_type;
+                       mime_type  = table + strlen(table) + 1;
+                       table_next = mime_type + strlen(mime_type) + 1;
+                       try_suffix = strstr(table, suffix);
+                       if (!try_suffix)
+                               continue;
+                       try_suffix += strlen(suffix);
+                       if (*try_suffix == '\0' || *try_suffix == '.') {
+                               found_mime_type = mime_type;
+                               break;
                        }
+                       /* Example: strstr(table, ".av") != NULL, but it
+                        * does not match ".avi" after all and we end up here.
+                        * The table is arranged so that in this case we know
+                        * that it can't match anything in the following lines,
+                        * and we stop the search: */
+                       break;
                }
+               /* ...then user's table */
                for (cur = mime_a; cur; cur = cur->next) {
                        if (strcmp(cur->before_colon, suffix) == 0) {
                                found_mime_type = cur->after_colon;
@@ -1559,6 +1583,11 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
                        }
                }
        }
+
+       if (DEBUG)
+               bb_error_msg("sending file '%s' content-type: %s",
+                       url, found_mime_type);
+
 #if ENABLE_FEATURE_HTTPD_RANGES
        if (what == SEND_BODY)
                range_start = 0; /* err pages and ranges don't mix */
@@ -1903,7 +1932,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, &sb)) {
+               if (is_directory(urlcopy + 1, 1, NULL)) {
                        found_moved_temporarily = urlcopy;
                }
        }
@@ -1917,7 +1946,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, &sb)) {
+               if (is_directory(urlcopy + 1, 1, NULL)) {
                        /* may have subdir config */
                        parse_conf(urlcopy + 1, SUBDIR_PARSE);
                        ip_allowed = checkPermIP();
@@ -2074,7 +2103,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
                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 needs to have two disctinct fds */
+               /* cgi_io_loop_and_exit needs to have two distinct fds */
                cgi_io_loop_and_exit(proxy_fd, dup(proxy_fd), length);
        }
 #endif
@@ -2089,8 +2118,12 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
                }
                send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
        }
+#endif
+
+       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;
@@ -2100,16 +2133,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
                                }
                        }
                }
-       }
 #endif
-       if (prequest != request_GET && prequest != request_HEAD) {
-               send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
-       }
-#endif  /* FEATURE_HTTPD_CGI */
-
-       if (urlp[-1] == '/')
-               strcpy(urlp, index_page);
-       if (stat(tptr, &sb) == 0) {
                file_size = sb.st_size;
                last_mod = sb.st_mtime;
        }
@@ -2123,19 +2147,18 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
                        send_cgi_and_exit("/cgi-bin/index.cgi", prequest, length, cookie, content_type);
                }
        }
-#endif
-       /* else {
-        *      fall through to send_file, it errors out if open fails
-        * }
-        */
+       /* else fall through to send_file, it errors out if open fails: */
 
+       if (prequest != request_GET && prequest != request_HEAD) {
+               /* POST for files does not make sense */
+               send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
+       }
        send_file_and_exit(tptr,
-#if ENABLE_FEATURE_HTTPD_CGI
                (prequest != request_HEAD ? SEND_HEADERS_AND_BODY : SEND_HEADERS)
+       );
 #else
-               SEND_HEADERS_AND_BODY
+       send_file_and_exit(tptr, SEND_HEADERS_AND_BODY);
 #endif
-       );
 }
 
 /*
@@ -2324,7 +2347,12 @@ int httpd_main(int argc UNUSED_PARAM, char **argv)
 #endif
 #if ENABLE_FEATURE_HTTPD_AUTH_MD5
        if (opt & OPT_MD5) {
-               puts(pw_encrypt(pass, "$1$", 1));
+               char salt[sizeof("$1$XXXXXXXX")];
+               salt[0] = '$';
+               salt[1] = '1';
+               salt[2] = '$';
+               crypt_make_salt(salt + 3, 4, 0);
+               puts(pw_encrypt(pass, salt, 1));
                return 0;
        }
 #endif