- don't automatically select run-parts but point out that this is a system-utility...
[oweals/busybox.git] / networking / httpd.c
index 818590f78ff92996e54a3f3a9028f8ffc31c65c3..72b03de792b0671fd5619485caa0ae11fad18541 100644 (file)
@@ -142,7 +142,7 @@ typedef struct {
 
        unsigned int rmt_ip;
 #if ENABLE_FEATURE_HTTPD_CGI || DEBUG
-       char rmt_ip_str[16];     /* for set env REMOTE_ADDR */
+       char *rmt_ip_str;        /* for set env REMOTE_ADDR */
 #endif
        unsigned port;           /* server initial port and for
                                                      set env REMOTE_PORT */
@@ -653,17 +653,16 @@ static char *encodeString(const char *string)
        /* take the simple route and encode everything */
        /* could possibly scan once to get length.     */
        int len = strlen(string);
-       char *out = malloc(len * 6 + 1);
+       char *out = xmalloc(len * 6 + 1);
        char *p = out;
        char ch;
 
-       if (!out) return "";
        while ((ch = *string++)) {
                // very simple check for what to encode
                if (isalnum(ch)) *p++ = ch;
                else p += sprintf(p, "&#%d;", (unsigned char) ch);
        }
-       *p = 0;
+       *p = '\0';
        return out;
 }
 #endif          /* FEATURE_HTTPD_ENCODE_URL_STR */
@@ -817,30 +816,11 @@ static void decodeBase64(char *Data)
  ****************************************************************************/
 static int openServer(void)
 {
-       struct sockaddr_in lsocket;
        int fd;
 
        /* create the socket right now */
-       /* inet_addr() returns a value that is already in network order */
-       memset(&lsocket, 0, sizeof(lsocket));
-       lsocket.sin_family = AF_INET;
-       lsocket.sin_addr.s_addr = INADDR_ANY;
-       lsocket.sin_port = htons(config->port);
-       fd = xsocket(AF_INET, SOCK_STREAM, 0);
-       /* tell the OS it's OK to reuse a previous address even though */
-       /* it may still be in a close down state.  Allows bind to succeed. */
-#ifdef SO_REUSEPORT
-       {
-               static const int on = 1;
-               setsockopt(fd, SOL_SOCKET, SO_REUSEPORT,
-                               (void *)&on, sizeof(on));
-       }
-#else
-       setsockopt_reuseaddr(fd);
-#endif
-       xbind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket));
+       fd = create_and_bind_stream_or_die(NULL, config->port);
        xlisten(fd, 9);
-       signal(SIGCHLD, SIG_IGN);   /* prevent zombie (defunct) processes */
        return fd;
 }
 
@@ -865,7 +845,7 @@ static int sendHeaders(HttpResponseNum responseNum)
        const char *responseString = "";
        const char *infoString = 0;
        const char *mime_type;
-       unsigned int i;
+       unsigned i;
        time_t timer = time(0);
        char timeStr[80];
        int len;
@@ -893,8 +873,9 @@ static int sendHeaders(HttpResponseNum responseNum)
 
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
        if (responseNum == HTTP_UNAUTHORIZED) {
-               len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n",
-                                                                   config->realm);
+               len += sprintf(buf+len,
+                               "WWW-Authenticate: Basic realm=\"%s\"\r\n",
+                               config->realm);
        }
 #endif
        if (responseNum == HTTP_MOVED_TEMPORARILY) {
@@ -920,7 +901,9 @@ static int sendHeaders(HttpResponseNum responseNum)
        }
        if (DEBUG)
                fprintf(stderr, "headers: '%s'\n", buf);
-       return full_write(config->accepted_socket, buf, len);
+       i = config->accepted_socket;
+       if (i == 0) i++; /* write to fd# 1 in inetd mode */
+       return full_write(i, buf, len);
 }
 
 /****************************************************************************
@@ -968,7 +951,6 @@ static int getLine(void)
  *      (int bodyLen)  . . . . . . . . Length of the post body.
  *      (const char *cookie) . . . . . For set HTTP_COOKIE.
  *      (const char *content_type) . . For set CONTENT_TYPE.
-
  *
  * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
  *
@@ -986,7 +968,7 @@ static int sendCgi(const char *url,
        int pid = 0;
        int inFd;
        int outFd;
-       int firstLine = 1;
+       int buf_count;
        int status;
        size_t post_read_size, post_read_idx;
 
@@ -995,38 +977,52 @@ static int sendCgi(const char *url,
        if (pipe(toCgi) != 0)
                return 0;
 
+/*
+ * Note: We can use vfork() here in the no-mmu case, although
+ * the child modifies the parent's variables, due to:
+ * 1) The parent does not use the child-modified variables.
+ * 2) The allocated memory (in the child) is freed when the process
+ *    exits. This happens instantly after the child finishes,
+ *    since httpd is run from inetd (and it can't run standalone
+ *    in uClinux).
+ */
+#ifdef BB_NOMMU
+       pid = vfork();
+#else
        pid = fork();
+#endif
        if (pid < 0)
                return 0;
 
        if (!pid) {
                /* child process */
                char *script;
-               char *purl = xstrdup(url);
+               char *purl;
                char realpath_buff[MAXPATHLEN];
 
-               if (purl == NULL)
-                       _exit(242);
+               if (config->accepted_socket > 1)
+                       close(config->accepted_socket);
+               if (config->server_socket > 1)
+                       close(config->server_socket);
 
-               inFd = toCgi[0];
-               outFd = fromCgi[1];
-
-               dup2(inFd, 0);  // replace stdin with the pipe
-               dup2(outFd, 1);  // replace stdout with the pipe
+               dup2(toCgi[0], 0);  // replace stdin with the pipe
+               dup2(fromCgi[1], 1);  // replace stdout with the pipe
+               /* Huh? User seeing stderr can be a security problem...
+                * and if CGI really wants that, it can always dup2(1,2)...
                if (!DEBUG)
-                       dup2(outFd, 2);  // replace stderr with the pipe
-
+                       dup2(fromCgi[1], 2);  // replace stderr with the pipe
+               */
+               /* I think we cannot inadvertently close 0, 1 here... */
                close(toCgi[0]);
                close(toCgi[1]);
                close(fromCgi[0]);
                close(fromCgi[1]);
 
-               close(config->accepted_socket);
-               close(config->server_socket);
-
                /*
                 * Find PATH_INFO.
                 */
+               xfunc_error_retval = 242;
+               purl = xstrdup(url);
                script = purl;
                while ((script = strchr(script + 1, '/')) != NULL) {
                        /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */
@@ -1068,9 +1064,21 @@ static int sendCgi(const char *url,
                /* (Older versions of bbox seem to do some decoding) */
                setenv1("QUERY_STRING", config->query);
                setenv1("SERVER_SOFTWARE", httpdVersion);
-               putenv("SERVER_PROTOCOL=HTTP/1.0");
-               putenv("GATEWAY_INTERFACE=CGI/1.1");
-               setenv1("REMOTE_ADDR", config->rmt_ip_str);
+               putenv((char*)"SERVER_PROTOCOL=HTTP/1.0");
+               putenv((char*)"GATEWAY_INTERFACE=CGI/1.1");
+               /* Having _separate_ variables for IP and port defeats
+                * the purpose of having socket abstraction. Which "port"
+                * are you using on Unix domain socket?
+                * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense.
+                * Oh well... */
+               {
+                       char *p = config->rmt_ip_str ? : (char*)"";
+                       char *cp = strrchr(p, ':');
+                       if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']'))
+                               cp = NULL;
+                       if (cp) *cp = '\0'; /* delete :PORT */
+                       setenv1("REMOTE_ADDR", p);
+               }
 #if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
                setenv_long("REMOTE_PORT", config->port);
 #endif
@@ -1083,7 +1091,7 @@ static int sendCgi(const char *url,
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
                if (config->remoteuser) {
                        setenv1("REMOTE_USER", config->remoteuser);
-                       putenv("AUTH_TYPE=Basic");
+                       putenv((char*)"AUTH_TYPE=Basic");
                }
 #endif
                if (config->referer)
@@ -1131,6 +1139,7 @@ static int sendCgi(const char *url,
 
        /* parent process */
 
+       buf_count = 0;
        post_read_size = 0;
        post_read_idx = 0; /* for gcc */
        inFd = fromCgi[0];
@@ -1168,10 +1177,11 @@ static int sendCgi(const char *url,
                }
 
                if (nfound <= 0) {
-                       if (waitpid(pid, &status, WNOHANG) <= 0)
+                       if (waitpid(pid, &status, WNOHANG) <= 0) {
                                /* Weird. CGI didn't exit and no fd's
-                                *  are ready, yet select returned?! */
+                                * are ready, yet select returned?! */
                                continue;
+                       }
                        close(inFd);
                        if (DEBUG && WIFEXITED(status))
                                bb_error_msg("piped has exited with status=%d", WEXITSTATUS(status));
@@ -1196,7 +1206,7 @@ static int sendCgi(const char *url,
                ) {
                        /* We expect data, prev data portion is eaten by CGI
                         * and there *is* data to read from the peer
-                        * (POST data?) */
+                        * (POSTDATA?) */
                        count = bodyLen > (int)sizeof(wbuf) ? (int)sizeof(wbuf) : bodyLen;
                        count = safe_read(config->accepted_socket, wbuf, count);
                        if (count > 0) {
@@ -1208,42 +1218,56 @@ static int sendCgi(const char *url,
                        }
                }
 
-               if (FD_ISSET(inFd, &readSet)) {
-                       /* There is something to read from CGI */
-                       int s = config->accepted_socket;
-                       char *rbuf = config->buf;
 #define PIPESIZE PIPE_BUF
 #if PIPESIZE >= MAX_MEMORY_BUFF
 # error "PIPESIZE >= MAX_MEMORY_BUFF"
 #endif
-                       /* NB: was safe_read. If it *has to be* safe_read, */
-                       /* please explain why in this comment... */
-                       count = full_read(inFd, rbuf, PIPESIZE);
-                       if (count == 0)
-                               break;  /* closed */
-                       if (count < 0)
-                               continue; /* huh, error, why continue?? */
-
-                       if (firstLine) {
-                               /* full_read (above) avoids
-                                * "chopped up into small chunks" syndrome here */
-                               rbuf[count] = '\0';
-                               /* check to see if the user script added headers */
-#define HTTP_200 "HTTP/1.0 200 OK\r\n\r\n"
-                               if (memcmp(rbuf, HTTP_200, 4) != 0) {
-                                       /* there is no "HTTP", do it ourself */
-                                       full_write(s, HTTP_200, sizeof(HTTP_200)-1);
+               if (FD_ISSET(inFd, &readSet)) {
+                       /* There is something to read from CGI */
+                       int s = config->accepted_socket;
+                       char *rbuf = config->buf;
+
+                       /* Are we still buffering CGI output? */
+                       if (buf_count >= 0) {
+                               static const char HTTP_200[] = "HTTP/1.0 200 OK\r\n";
+                               /* Must use safe_read, not full_read, because
+                                * CGI may output a few first bytes and then wait
+                                * for POSTDATA without closing stdout.
+                                * With full_read we may wait here forever. */
+                               count = safe_read(inFd, rbuf + buf_count, PIPESIZE - 4);
+                               if (count <= 0) {
+                                       /* eof (or error) and there was no "HTTP",
+                                        * so add one and write out the received data */
+                                       if (buf_count) {
+                                               full_write(s, HTTP_200, sizeof(HTTP_200)-1);
+                                               full_write(s, rbuf, buf_count);
+                                       }
+                                       break;  /* closed */
+                               }
+                               buf_count += count;
+                               count = 0;
+                               if (buf_count >= 4) {
+                                       /* check to see if CGI added "HTTP" */
+                                       if (memcmp(rbuf, HTTP_200, 4) != 0) {
+                                               /* there is no "HTTP", do it ourself */
+                                               if (full_write(s, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
+                                                       break;
+                                       }
+                                       /* example of valid CGI without "Content-type:"
+                                        * echo -en "HTTP/1.0 302 Found\r\n"
+                                        * echo -en "Location: http://www.busybox.net\r\n"
+                                        * echo -en "\r\n"
+                                       if (!strstr(rbuf, "ontent-")) {
+                                               full_write(s, "Content-type: text/plain\r\n\r\n", 28);
+                                       }
+                                        */
+                                       count = buf_count;
+                                       buf_count = -1; /* buffering off */
                                }
-#undef HTTP_200
-                               /* Example of valid GCI without "Content-type:"
-                                * echo -en "HTTP/1.0 302 Found\r\n"
-                                * echo -en "Location: http://www.busybox.net\r\n"
-                                * echo -en "\r\n"
-                                */
-                               //if (!strstr(rbuf, "ontent-")) {
-                               //      full_write(s, "Content-type: text/plain\r\n\r\n", 28);
-                               //}
-                               firstLine = 0;
+                       } else {
+                               count = safe_read(inFd, rbuf, PIPESIZE);
+                               if (count <= 0)
+                                       break;  /* eof (or error) */
                        }
                        if (full_write(s, rbuf, count) != count)
                                break;
@@ -1309,7 +1333,9 @@ static int sendFile(const char *url)
                sendHeaders(HTTP_OK);
                /* TODO: sendfile() */
                while ((count = full_read(f, buf, MAX_MEMORY_BUFF)) > 0) {
-                       if (full_write(config->accepted_socket, buf, count) != count)
+                       int fd = config->accepted_socket;
+                       if (fd == 0) fd++; /* write to fd# 1 in inetd mode */
+                       if (full_write(fd, buf, count) != count)
                                break;
                }
                close(f);
@@ -1328,19 +1354,21 @@ static int checkPermIP(void)
 
        /* This could stand some work */
        for (cur = config->ip_a_d; cur; cur = cur->next) {
-#if DEBUG
+#if ENABLE_FEATURE_HTTPD_CGI && DEBUG
                fprintf(stderr, "checkPermIP: '%s' ? ", config->rmt_ip_str);
 #endif
-               if (DEBUG)
-                       fprintf(stderr, "'%u.%u.%u.%u/%u.%u.%u.%u'\n",
-                               (unsigned char)(cur->ip >> 24),
-                               (unsigned char)(cur->ip >> 16),
-                               (unsigned char)(cur->ip >> 8),
-                                               cur->ip & 0xff,
-                               (unsigned char)(cur->mask >> 24),
-                               (unsigned char)(cur->mask >> 16),
-                               (unsigned char)(cur->mask >> 8),
-                                               cur->mask & 0xff);
+#if DEBUG
+               fprintf(stderr, "'%u.%u.%u.%u/%u.%u.%u.%u'\n",
+                       (unsigned char)(cur->ip >> 24),
+                       (unsigned char)(cur->ip >> 16),
+                       (unsigned char)(cur->ip >> 8),
+                       (unsigned char)(cur->ip),
+                       (unsigned char)(cur->mask >> 24),
+                       (unsigned char)(cur->mask >> 16),
+                       (unsigned char)(cur->mask >> 8),
+                       (unsigned char)(cur->mask)
+               );
+#endif
                if ((config->rmt_ip & cur->mask) == cur->ip)
                        return cur->allow_deny == 'A';   /* Allow/Deny */
        }
@@ -1687,6 +1715,20 @@ static void handleIncoming(void)
                        sendCgi(url, prequest, length, cookie, content_type);
                        break;
                }
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+               {
+                       char *suffix = strrchr(test, '.');
+                       if (suffix) {
+                               Htaccess *cur;
+                               for (cur = config->script_i; cur; cur = cur->next) {
+                                       if (strcmp(cur->before_colon + 1, suffix) == 0) {
+                                               sendCgi(url, prequest, length, cookie, content_type);
+                                               goto bail_out;
+                                       }
+                               }
+                       }
+               }
+#endif
                if (prequest != request_GET) {
                        sendHeaders(HTTP_NOT_IMPLEMENTED);
                        break;
@@ -1772,9 +1814,13 @@ static int miniHttpd(int server)
 
        /* copy the ports we are watching to the readfd set */
        while (1) {
-               int on, s;
-               socklen_t fromAddrLen;
-               struct sockaddr_in fromAddr;
+               int s;
+               union {
+                       struct sockaddr sa;
+                       struct sockaddr_in sin;
+                       USE_FEATURE_IPV6(struct sockaddr_in6 sin6;)
+               } fromAddr;
+               socklen_t fromAddrLen = sizeof(fromAddr);
 
                /* Now wait INDEFINITELY on the set of sockets! */
                readfd = portfd;
@@ -1782,28 +1828,32 @@ static int miniHttpd(int server)
                        continue;
                if (!FD_ISSET(server, &readfd))
                        continue;
-               fromAddrLen = sizeof(fromAddr);
-               s = accept(server, (struct sockaddr *)&fromAddr, &fromAddrLen);
+               s = accept(server, &fromAddr.sa, &fromAddrLen);
                if (s < 0)
                        continue;
                config->accepted_socket = s;
-               config->rmt_ip = ntohl(fromAddr.sin_addr.s_addr);
+               config->rmt_ip = 0;
+               config->port = 0;
 #if ENABLE_FEATURE_HTTPD_CGI || DEBUG
-               sprintf(config->rmt_ip_str, "%u.%u.%u.%u",
-                               (unsigned char)(config->rmt_ip >> 24),
-                               (unsigned char)(config->rmt_ip >> 16),
-                               (unsigned char)(config->rmt_ip >> 8),
-                               config->rmt_ip & 0xff);
-               config->port = ntohs(fromAddr.sin_port);
+               free(config->rmt_ip_str);
+               config->rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr.sa, fromAddrLen);
 #if DEBUG
-               bb_error_msg("connection from IP=%s, port %u",
-                               config->rmt_ip_str, config->port);
+               bb_error_msg("connection from '%s'", config->rmt_ip_str);
 #endif
 #endif /* FEATURE_HTTPD_CGI */
+               if (fromAddr.sa.sa_family == AF_INET) {
+                       config->rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr);
+                       config->port = ntohs(fromAddr.sin.sin_port);
+               }
+#if ENABLE_FEATURE_IPV6
+               if (fromAddr.sa.sa_family == AF_INET6) {
+                       //config->rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr);
+                       config->port = ntohs(fromAddr.sin6.sin6_port);
+               }
+#endif
 
                /* set the KEEPALIVE option to cull dead connections */
-               on = 1;
-               setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
+               setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
 
                if (DEBUG || fork() == 0) {
                        /* child */
@@ -1823,19 +1873,30 @@ static int miniHttpd(int server)
 /* from inetd */
 static int miniHttpd_inetd(void)
 {
-       struct sockaddr_in fromAddrLen;
-       socklen_t sinlen = sizeof(struct sockaddr_in);
-
-       getpeername(0, (struct sockaddr *)&fromAddrLen, &sinlen);
-       config->rmt_ip = ntohl(fromAddrLen.sin_addr.s_addr);
-#if ENABLE_FEATURE_HTTPD_CGI
-       sprintf(config->rmt_ip_str, "%u.%u.%u.%u",
-                               (unsigned char)(config->rmt_ip >> 24),
-                               (unsigned char)(config->rmt_ip >> 16),
-                               (unsigned char)(config->rmt_ip >> 8),
-                                               config->rmt_ip & 0xff);
+       union {
+               struct sockaddr sa;
+               struct sockaddr_in sin;
+               USE_FEATURE_IPV6(struct sockaddr_in6 sin6;)
+       } fromAddr;
+       socklen_t fromAddrLen = sizeof(fromAddr);
+
+       getpeername(0, &fromAddr.sa, &fromAddrLen);
+       config->rmt_ip = 0;
+       config->port = 0;
+#if ENABLE_FEATURE_HTTPD_CGI || DEBUG
+       free(config->rmt_ip_str);
+       config->rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr.sa, fromAddrLen);
+#endif
+       if (fromAddr.sa.sa_family == AF_INET) {
+               config->rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr);
+               config->port = ntohs(fromAddr.sin.sin_port);
+       }
+#if ENABLE_FEATURE_IPV6
+       if (fromAddr.sa.sa_family == AF_INET6) {
+               //config->rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr);
+               config->port = ntohs(fromAddr.sin6.sin6_port);
+       }
 #endif
-       config->port = ntohs(fromAddrLen.sin_port);
        handleIncoming();
        return 0;
 }
@@ -1885,6 +1946,7 @@ static const char httpd_opts[] = "c:d:h:"
        "p:if";
 
 
+int httpd_main(int argc, char *argv[]);
 int httpd_main(int argc, char *argv[])
 {
        unsigned opt;
@@ -1945,13 +2007,14 @@ int httpd_main(int argc, char *argv[])
 
        xchdir(home_httpd);
        if (!(opt & OPT_INETD)) {
+               signal(SIGCHLD, SIG_IGN);
                config->server_socket = openServer();
 #if ENABLE_FEATURE_HTTPD_SETUID
                /* drop privileges */
                if (opt & OPT_SETUID) {
                        if (ugid.gid != (gid_t)-1) {
                                if (setgroups(1, &ugid.gid) == -1)
-                                       bb_perror_msg_and_die("setgroups");
+                                       bb_perror_msg_and_die("setgroups");
                                xsetgid(ugid.gid);
                        }
                        xsetuid(ugid.uid);