tls: reorder tls_handshake_data fields for smaller size, tweak comments
[oweals/busybox.git] / networking / httpd.c
index 7a9065fcc0536227e05961afaa1dce54b049b559..39aad90a8eca29cfe8de72300696fdaa303baadc 100644 (file)
  *
  */
  /* 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 <user> 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 "<Hello World>" produces
+//config:        "&#60Hello&#32World&#62".
+//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 <file>.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]]"
 //usage:     "\n       -d STRING       URL decode STRING"
 
 #include "libbb.h"
+#include "common_bufsiz.h"
 #if ENABLE_PAM
 /* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
 # undef setlocale
@@ -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
@@ -368,6 +492,7 @@ 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;) \
@@ -923,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++) {
@@ -945,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);
@@ -988,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
@@ -1002,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
                );
@@ -1017,12 +1165,18 @@ static void send_headers(int responseNum)
        if (infoString) {
                len += sprintf(iobuf + len,
                                "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n"
-                               "<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n",
+                               "<BODY><H1>%d %s</H1>\n"
+                               "%s\n"
+                               "</BODY></HTML>\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");
@@ -1053,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;
@@ -1178,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;
@@ -1222,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"? */
@@ -2242,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",
@@ -2352,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 */
@@ -2395,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 */