pscan: new applet (portscanner). ~1350 bytes. By Tito <farmatito@tiscali.it>
[oweals/busybox.git] / networking / httpd.c
index 08b40e014fdebd83cbfce2329b19204e1aac1783..529f66bc4b8a3555526ba22148c05d8bc44cfb2f 100644 (file)
@@ -24,6 +24,9 @@
  * 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 server can also be invoked as a url arg decoder and html text encoder
  * as follows:
  *  foo=`httpd -d $foo`           # decode "Hello%20World" as "Hello World"
  *
 */
 
+#include "libbb.h"
 
-#include "busybox.h"
-
+/* amount of buffering in a pipe */
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096
+#endif
 
-static const char httpdVersion[] = "busybox httpd/1.35 6-Oct-2004";
 static const char default_path_httpd_conf[] = "/etc";
 static const char httpd_conf[] = "httpd.conf";
-static const char home[] = "./";
 
 #define TIMEOUT 60
 
@@ -105,10 +109,7 @@ static const char home[] = "./";
 //       is checked rigorously
 
 //#define DEBUG 1
-
-#ifndef DEBUG
-# define DEBUG 0
-#endif
+#define DEBUG 0
 
 #define MAX_MEMORY_BUFF 8192    /* IO buffer */
 
@@ -119,55 +120,85 @@ typedef struct HT_ACCESS {
 } Htaccess;
 
 typedef struct HT_ACCESS_IP {
-       unsigned int ip;
-       unsigned int mask;
+       unsigned ip;
+       unsigned mask;
        int allow_deny;
        struct HT_ACCESS_IP *next;
 } Htaccess_IP;
 
-typedef struct {
-       char buf[MAX_MEMORY_BUFF];
-
-       USE_FEATURE_HTTPD_BASIC_AUTH(const char *realm;)
-       USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
-
-       const char *query;
-
-       USE_FEATURE_HTTPD_CGI(char *referer;)
-
+struct globals {
+       int server_socket;
+       int accepted_socket;
+       volatile smallint alarm_signaled;
+       smallint flg_deny_all;
+       const char *g_query;
        const char *configFile;
+       const char *home_httpd;
+       unsigned rmt_ip;
 
-       unsigned int rmt_ip;
-#if ENABLE_FEATURE_HTTPD_CGI || DEBUG
-       char rmt_ip_str[16];     /* for set env REMOTE_ADDR */
-#endif
-       unsigned port;           /* server initial port and for
-                                                     set env REMOTE_PORT */
        const char *found_mime_type;
        const char *found_moved_temporarily;
-
        off_t ContentLength;          /* -1 - unknown */
        time_t last_mod;
-
        Htaccess_IP *ip_a_d;          /* config allow/deny lines */
-       int flg_deny_all;
+
+       USE_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
+       USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
+       USE_FEATURE_HTTPD_CGI(char *referer;)
+
+#if ENABLE_FEATURE_HTTPD_CGI || DEBUG
+       char *rmt_ip_str;        /* for set env REMOTE_ADDR */
+#endif
+       unsigned tcp_port;       /* server initial port and for
+                                   set env REMOTE_PORT */
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
-       Htaccess *auth;               /* config user:password lines */
+       Htaccess *g_auth;        /* config user:password lines */
 #endif
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
        Htaccess *mime_a;             /* config mime types */
 #endif
-
-       int server_socket;
-       int accepted_socket;
-       volatile int alarm_signaled;
-
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
        Htaccess *script_i;           /* config script interpreters */
 #endif
-} HttpdConfig;
-
-static HttpdConfig *config;
+       char iobuf[MAX_MEMORY_BUFF];
+};
+#define G (*ptr_to_globals)
+#define server_socket   (G.server_socket  )
+#define accepted_socket (G.accepted_socket)
+#define alarm_signaled  (G.alarm_signaled )
+#define flg_deny_all    (G.flg_deny_all   )
+#define g_query         (G.g_query        )
+#define configFile      (G.configFile     )
+#define home_httpd      (G.home_httpd     )
+#define rmt_ip          (G.rmt_ip         )
+#define found_mime_type (G.found_mime_type)
+#define found_moved_temporarily (G.found_moved_temporarily)
+#define ContentLength   (G.ContentLength  )
+#define last_mod        (G.last_mod       )
+#define ip_a_d          (G.ip_a_d         )
+#define g_realm         (G.g_realm        )
+#define remoteuser      (G.remoteuser     )
+#define referer         (G.referer        )
+#if ENABLE_FEATURE_HTTPD_CGI || DEBUG
+#define rmt_ip_str      (G.rmt_ip_str     )
+#endif
+#define tcp_port        (G.tcp_port       )
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+#define g_auth          (G.g_auth         )
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+#define mime_a          (G.mime_a         )
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+#define script_i        (G.script_i       )
+#endif
+#define iobuf           (G.iobuf          )
+#define INIT_G() do { \
+       PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
+       USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
+       tcp_port = 80; \
+       ContentLength = -1; \
+} while (0)
 
 static const char request_GET[] = "GET";    /* size algorithmic optimize */
 
@@ -261,7 +292,7 @@ static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT";
 #define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
 
 
-static int scan_ip(const char **ep, unsigned int *ip, unsigned char endc)
+static int scan_ip(const char **ep, unsigned *ip, unsigned char endc)
 {
        const char *p = *ep;
        int auto_mask = 8;
@@ -269,7 +300,7 @@ static int scan_ip(const char **ep, unsigned int *ip, unsigned char endc)
 
        *ip = 0;
        for (j = 0; j < 4; j++) {
-               unsigned int octet;
+               unsigned octet;
 
                if ((*p < '0' || *p > '9') && (*p != '/' || j == 0) && *p != 0)
                        return -auto_mask;
@@ -298,10 +329,10 @@ static int scan_ip(const char **ep, unsigned int *ip, unsigned char endc)
        return auto_mask;
 }
 
-static int scan_ip_mask(const char *ipm, unsigned int *ip, unsigned int *mask)
+static int scan_ip_mask(const char *ipm, unsigned *ip, unsigned *mask)
 {
        int i;
-       unsigned int msk;
+       unsigned msk;
 
        i = scan_ip(&ipm, ip, '/');
        if (i < 0)
@@ -335,7 +366,9 @@ static int scan_ip_mask(const char *ipm, unsigned int *ip, unsigned int *mask)
        return 0;
 }
 
-#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
 static void free_config_lines(Htaccess **pprev)
 {
        Htaccess *prev = *pprev;
@@ -384,18 +417,21 @@ static void parse_conf(const char *path, int flag)
 {
        FILE *f;
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
-       Htaccess *prev, *cur;
-#elif ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+       Htaccess *prev;
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
        Htaccess *cur;
 #endif
 
-       const char *cf = config->configFile;
+       const char *cf = configFile;
        char buf[160];
        char *p0 = NULL;
        char *c, *p;
 
        /* free previous ip setup if present */
-       Htaccess_IP *pip = config->ip_a_d;
+       Htaccess_IP *pip = ip_a_d;
 
        while (pip) {
                Htaccess_IP *cur_ipl = pip;
@@ -403,21 +439,23 @@ static void parse_conf(const char *path, int flag)
                pip = cur_ipl->next;
                free(cur_ipl);
        }
-       config->ip_a_d = NULL;
+       ip_a_d = NULL;
 
-       config->flg_deny_all = 0;
+       flg_deny_all = 0;
 
-#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
        /* retain previous auth and mime config only for subdir parse */
        if (flag != SUBDIR_PARSE) {
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
-               free_config_lines(&config->auth);
+               free_config_lines(&g_auth);
 #endif
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
-               free_config_lines(&config->mime_a);
+               free_config_lines(&mime_a);
 #endif
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
-               free_config_lines(&config->script_i);
+               free_config_lines(&script_i);
 #endif
        }
 #endif
@@ -437,16 +475,16 @@ static void parse_conf(const char *path, int flag)
                        /* config file not found, no changes to config */
                        return;
                }
-               if (config->configFile && flag == FIRST_PARSE) /* if -c option given */
+               if (configFile && flag == FIRST_PARSE) /* if -c option given */
                        bb_perror_msg_and_die("%s", cf);
                flag = FIND_FROM_HTTPD_ROOT;
                cf = httpd_conf;
        }
 
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
-               prev = config->auth;
+       prev = g_auth;
 #endif
-               /* This could stand some work */
+       /* This could stand some work */
        while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) {
                c = NULL;
                for (p = p0; *p0 != 0 && *p0 != '#'; p0++) {
@@ -467,7 +505,7 @@ static void parse_conf(const char *path, int flag)
                if (*c == '*') {
                        if (*p0 == 'D') {
                                /* memorize deny all */
-                               config->flg_deny_all++;
+                               flg_deny_all = 1;
                        }
                        /* skip default other "word:*" config lines */
                        continue;
@@ -489,7 +527,7 @@ static void parse_conf(const char *path, int flag)
                         continue;
                if (*p0 == 'A' || *p0 == 'D') {
                        /* storing current config IP line */
-                       pip = calloc(1, sizeof(Htaccess_IP));
+                       pip = xzalloc(sizeof(Htaccess_IP));
                        if (pip) {
                                if (scan_ip_mask(c, &(pip->ip), &(pip->mask))) {
                                        /* syntax IP{/mask} error detected, protect all */
@@ -499,14 +537,14 @@ static void parse_conf(const char *path, int flag)
                                pip->allow_deny = *p0;
                                if (*p0 == 'D') {
                                        /* Deny:form_IP move top */
-                                       pip->next = config->ip_a_d;
-                                       config->ip_a_d = pip;
+                                       pip->next = ip_a_d;
+                                       ip_a_d = pip;
                                } else {
                                        /* add to bottom A:form_IP config line */
-                                       Htaccess_IP *prev_IP = config->ip_a_d;
+                                       Htaccess_IP *prev_IP = ip_a_d;
 
                                        if (prev_IP == NULL) {
-                                               config->ip_a_d = pip;
+                                               ip_a_d = pip;
                                        } else {
                                                while (prev_IP->next)
                                                        prev_IP = prev_IP->next;
@@ -556,9 +594,11 @@ static void parse_conf(const char *path, int flag)
                }
 #endif
 
-#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
                /* storing current config line */
-               cur = calloc(1, sizeof(Htaccess) + strlen(p0));
+               cur = xzalloc(sizeof(Htaccess) + strlen(p0));
                if (cur) {
                        cf = strcpy(cur->before_colon, p0);
                        c = strchr(cf, ':');
@@ -567,16 +607,16 @@ static void parse_conf(const char *path, int flag)
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
                        if (*cf == '.') {
                                /* config .mime line move top for overwrite previous */
-                               cur->next = config->mime_a;
-                               config->mime_a = cur;
+                               cur->next = mime_a;
+                               mime_a = cur;
                                continue;
                        }
 #endif
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
                        if (*cf == '*' && cf[1] == '.') {
                                /* config script interpreter line move top for overwrite previous */
-                               cur->next = config->script_i;
-                               config->script_i = cur;
+                               cur->next = script_i;
+                               script_i = cur;
                                continue;
                        }
 #endif
@@ -584,10 +624,10 @@ static void parse_conf(const char *path, int flag)
                        free(p0);
                        if (prev == NULL) {
                                /* first line */
-                               config->auth = prev = cur;
+                               g_auth = prev = cur;
                        } else {
                                /* sort path, if current lenght eq or bigger then move up */
-                               Htaccess *prev_hti = config->auth;
+                               Htaccess *prev_hti = g_auth;
                                size_t l = strlen(cf);
                                Htaccess *hti;
 
@@ -599,7 +639,7 @@ static void parse_conf(const char *path, int flag)
                                                        prev_hti->next = cur;
                                                } else {
                                                        /* insert as top */
-                                                       config->auth = cur;
+                                                       g_auth = cur;
                                                }
                                                break;
                                        }
@@ -641,17 +681,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 */
@@ -668,39 +707,47 @@ static char *encodeString(const char *string)
  *
  * $Parameters:
  *      (char *) string . . . The first string to decode.
- *      (int)    flag   . . . 1 if require decode '+' as ' ' for CGI
+ *      (int)    option_d . . 1 if called for httpd -d
  *
  * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
  *
  * $Errors: None
  *
  ****************************************************************************/
-static char *decodeString(char *orig, int flag_plus_to_space)
+static char *decodeString(char *orig, int option_d)
 {
        /* note that decoded string is always shorter than original */
        char *string = orig;
        char *ptr = string;
+       char c;
 
-       while (*ptr) {
-               if (*ptr == '+' && flag_plus_to_space) { *string++ = ' '; ptr++; }
-               else if (*ptr != '%') *string++ = *ptr++;
-               else {
-                       unsigned int value1, value2;
-
-                       ptr++;
-                       if (sscanf(ptr, "%1X", &value1) != 1 ||
-                                                   sscanf(ptr+1, "%1X", &value2) != 1) {
-                               if (!flag_plus_to_space)
-                                       return NULL;
-                               *string++ = '%';
-                       } else {
-                               value1 = value1 * 16 + value2;
-                               if (value1 == '/' || value1 == 0)
-                                       return orig+1;
-                               *string++ = value1;
-                               ptr += 2;
-                       }
+       while ((c = *ptr++) != '\0') {
+               unsigned value1, value2;
+
+               if (option_d && c == '+') {
+                       *string++ = ' ';
+                       continue;
+               }
+               if (c != '%') {
+                       *string++ = c;
+                       continue;
+               }
+               if (sscanf(ptr, "%1X", &value1) != 1
+                || sscanf(ptr+1, "%1X", &value2) != 1
+               ) {
+                       if (!option_d)
+                               return NULL;
+                       *string++ = '%';
+                       continue;
+               }
+               value1 = value1 * 16 + value2;
+               if (!option_d && (value1 == '/' || value1 == '\0')) {
+                       /* caller takes it as indication of invalid
+                        * (dangerous wrt exploits) chars */
+                       return orig + 1;
                }
+               *string++ = value1;
+               ptr += 2;
        }
        *string = '\0';
        return orig;
@@ -746,10 +793,9 @@ static void setenv_long(const char *name, long value)
  ****************************************************************************/
 static void decodeBase64(char *Data)
 {
-
        const unsigned char *in = (const unsigned char *)Data;
        // The decoded size will be at most 3/4 the size of the encoded
-       unsigned long ch = 0;
+       unsigned ch = 0;
        int i = 0;
 
        while (*in) {
@@ -779,7 +825,7 @@ static void decodeBase64(char *Data)
                        i = 0;
                }
        }
-       *Data = 0;
+       *Data = '\0';
 }
 #endif
 
@@ -797,27 +843,11 @@ static void decodeBase64(char *Data)
  ****************************************************************************/
 static int openServer(void)
 {
-       struct sockaddr_in lsocket;
        int fd;
-       int on = 1;
 
        /* 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
-       setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on));
-#else
-       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
-#endif
-       xbind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket));
+       fd = create_and_bind_stream_or_die(NULL, tcp_port);
        xlisten(fd, 9);
-       signal(SIGCHLD, SIG_IGN);   /* prevent zombie (defunct) processes */
        return fd;
 }
 
@@ -838,11 +868,11 @@ static int openServer(void)
  ****************************************************************************/
 static int sendHeaders(HttpResponseNum responseNum)
 {
-       char *buf = config->buf;
+       char *buf = iobuf;
        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;
@@ -859,32 +889,33 @@ static int sendHeaders(HttpResponseNum responseNum)
        }
        /* error message is HTML */
        mime_type = responseNum == HTTP_OK ?
-                               config->found_mime_type : "text/html";
+                               found_mime_type : "text/html";
 
        /* emit the current date */
        strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer));
        len = sprintf(buf,
-               "HTTP/1.0 %d %s\r\nContent-type: %s\r\n"
-               "Date: %s\r\nConnection: close\r\n",
+                       "HTTP/1.0 %d %s\r\nContent-type: %s\r\n"
+                       "Date: %s\r\nConnection: close\r\n",
                        responseNum, responseString, mime_type, timeStr);
 
 #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",
+                               g_realm);
        }
 #endif
        if (responseNum == HTTP_MOVED_TEMPORARILY) {
                len += sprintf(buf+len, "Location: %s/%s%s\r\n",
-                               config->found_moved_temporarily,
-                               (config->query ? "?" : ""),
-                               (config->query ? config->query : ""));
+                               found_moved_temporarily,
+                               (g_query ? "?" : ""),
+                               (g_query ? g_query : ""));
        }
 
-       if (config->ContentLength != -1) {    /* file */
-               strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod));
-               len += sprintf(buf+len, "Last-Modified: %s\r\n%s %"OFF_FMT"\r\n",
-                               timeStr, "Content-length:", (off_t) config->ContentLength);
+       if (ContentLength != -1) {    /* file */
+               strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&last_mod));
+               len += sprintf(buf+len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n",
+                       timeStr, "Content-length:", ContentLength);
        }
        strcat(buf, "\r\n");
        len += 2;
@@ -897,7 +928,9 @@ static int sendHeaders(HttpResponseNum responseNum)
        }
        if (DEBUG)
                fprintf(stderr, "headers: '%s'\n", buf);
-       return full_write(config->accepted_socket, buf, len);
+       i = accepted_socket;
+       if (i == 0) i++; /* write to fd# 1 in inetd mode */
+       return full_write(i, buf, len);
 }
 
 /****************************************************************************
@@ -914,9 +947,9 @@ static int sendHeaders(HttpResponseNum responseNum)
 static int getLine(void)
 {
        int count = 0;
-       char *buf = config->buf;
+       char *buf = iobuf;
 
-       while (read(config->accepted_socket, buf + count, 1) == 1) {
+       while (read(accepted_socket, buf + count, 1) == 1) {
                if (buf[count] == '\r') continue;
                if (buf[count] == '\n') {
                        buf[count] = 0;
@@ -945,7 +978,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).
  *
@@ -958,52 +990,67 @@ static int sendCgi(const char *url,
 {
        int fromCgi[2];  /* pipe for reading data from CGI */
        int toCgi[2];    /* pipe for sending data to CGI */
-
-       static char * argp[] = { 0, 0 };
+       char *fullpath;
+       char *argp[] = { NULL, NULL };
        int pid = 0;
        int inFd;
        int outFd;
-       int firstLine = 1;
+       int buf_count;
        int status;
-       size_t post_readed_size, post_readed_idx;
+       size_t post_read_size, post_read_idx;
 
        if (pipe(fromCgi) != 0)
                return 0;
        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).
+ */
+
+// FIXME: setenv leaks memory! (old values of env vars are leaked)
+// Thus we have a bug on NOMMU.
+// Need to use this instead:
+// [malloc +] putenv + (in child: exec) + (in parent: unsetenv [+ free])
+
+#if !BB_MMU
+       fullpath = NULL;
+       pid = vfork();
+#else
        pid = fork();
+#endif
        if (pid < 0)
                return 0;
-       
+
        if (!pid) {
                /* child process */
                char *script;
-               char *purl = strdup(url);
-               char realpath_buff[MAXPATHLEN];
-
-               if (purl == NULL)
-                       _exit(242);
+               char *purl;
 
-               inFd = toCgi[0];
-               outFd = fromCgi[1];
+               if (accepted_socket > 1)
+                       close(accepted_socket);
+               if (server_socket > 1)
+                       close(server_socket);
 
-               dup2(inFd, 0);  // replace stdin with the pipe
-               dup2(outFd, 1);  // replace stdout with the pipe
-               if (!DEBUG)
-                       dup2(outFd, 2);  // replace stderr with the pipe
-
-               close(toCgi[0]);
-               close(toCgi[1]);
+               xmove_fd(toCgi[0], 0);  /* replace stdin with the pipe */
+               xmove_fd(fromCgi[1], 1);  /* replace stdout with the pipe */
                close(fromCgi[0]);
                close(fromCgi[1]);
-
-               close(config->accepted_socket);
-               close(config->server_socket);
+               /* Huh? User seeing stderr can be a security problem...
+                * and if CGI really wants that, it can always dup2(1,2)...
+                * dup2(fromCgi[1], 2); */
 
                /*
                 * 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] */
@@ -1018,31 +1065,50 @@ static int sendCgi(const char *url,
                        *script = '/';          /* is directory, find next '/' */
                }
                setenv1("PATH_INFO", script);   /* set /PATH_INFO or "" */
-               /* setenv1("PATH", getenv("PATH")); redundant */
                setenv1("REQUEST_METHOD", request);
-               if (config->query) {
-                       char *uri = alloca(strlen(purl) + 2 + strlen(config->query));
+               if (g_query) {
+                       char *uri = alloca(strlen(purl) + 2 + strlen(g_query));
                        if (uri)
-                               sprintf(uri, "%s?%s", purl, config->query);
+                               sprintf(uri, "%s?%s", purl, g_query);
                        setenv1("REQUEST_URI", uri);
                } else {
                        setenv1("REQUEST_URI", purl);
                }
                if (script != NULL)
                        *script = '\0';         /* cut off /PATH_INFO */
-                /* SCRIPT_FILENAME required by PHP in CGI mode */
-               if (!realpath(purl + 1, realpath_buff))
-                       goto error_execing_cgi;
-               setenv1("SCRIPT_FILENAME", realpath_buff);
+
+               /* SCRIPT_FILENAME required by PHP in CGI mode */
+               fullpath = concat_path_file(home_httpd, purl);
+               setenv1("SCRIPT_FILENAME", fullpath);
                /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
                setenv1("SCRIPT_NAME", purl);
-               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);
+               /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
+                * QUERY_STRING: The information which follows the ? in the URL
+                * which referenced this script. This is the query information.
+                * It should not be decoded in any fashion. This variable
+                * should always be set when there is query information,
+                * regardless of command line decoding. */
+               /* (Older versions of bbox seem to do some decoding) */
+               setenv1("QUERY_STRING", g_query);
+               putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER);
+               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 = 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 (cp) *cp = ':';
+               }
 #if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
-               setenv_long("REMOTE_PORT", config->port);
+               setenv_long("REMOTE_PORT", tcp_port);
 #endif
                if (bodyLen)
                        setenv_long("CONTENT_LENGTH", bodyLen);
@@ -1051,23 +1117,23 @@ static int sendCgi(const char *url,
                if (content_type)
                        setenv1("CONTENT_TYPE", content_type);
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
-               if (config->remoteuser) {
-                       setenv1("REMOTE_USER", config->remoteuser);
-                       putenv("AUTH_TYPE=Basic");
+               if (remoteuser) {
+                       setenv1("REMOTE_USER", remoteuser);
+                       putenv((char*)"AUTH_TYPE=Basic");
                }
 #endif
-               if (config->referer)
-                       setenv1("HTTP_REFERER", config->referer);
+               if (referer)
+                       setenv1("HTTP_REFERER", referer);
 
                /* set execve argp[0] without path */
                argp[0] = strrchr(purl, '/') + 1;
                /* but script argp[0] must have absolute path and chdiring to this */
-               script = strrchr(realpath_buff, '/');
+               script = strrchr(fullpath, '/');
                if (!script)
                        goto error_execing_cgi;
                *script = '\0';
-               if (chdir(realpath_buff) == 0) {
-                       // now run the program.  If it fails,
+               if (chdir(fullpath) == 0) {
+                       // Now run the program.  If it fails,
                        // use _exit() so no destructors
                        // get called and make a mess.
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
@@ -1076,7 +1142,7 @@ static int sendCgi(const char *url,
 
                        if (suffix) {
                                Htaccess *cur;
-                               for (cur = config->script_i; cur; cur = cur->next) {
+                               for (cur = script_i; cur; cur = cur->next) {
                                        if (strcmp(cur->before_colon + 1, suffix) == 0) {
                                                interpr = cur->after_colon;
                                                break;
@@ -1090,19 +1156,23 @@ static int sendCgi(const char *url,
                                execv(interpr, argp);
                        else
 #endif
-                               execv(realpath_buff, argp);
+                               execv(fullpath, argp);
                }
  error_execing_cgi:
                /* send to stdout (even if we are not from inetd) */
-               config->accepted_socket = 1;
+               accepted_socket = 1;
                sendHeaders(HTTP_NOT_FOUND);
                _exit(242);
        } /* end child */
 
        /* parent process */
+#if !BB_MMU
+       free(fullpath);
+#endif
 
-       post_readed_size = 0;
-       post_readed_idx = 0;
+       buf_count = 0;
+       post_read_size = 0;
+       post_read_idx = 0; /* for gcc */
        inFd = fromCgi[0];
        outFd = toCgi[1];
        close(fromCgi[1]);
@@ -1119,94 +1189,123 @@ static int sendCgi(const char *url,
                FD_ZERO(&readSet);
                FD_ZERO(&writeSet);
                FD_SET(inFd, &readSet);
-               if (bodyLen > 0 || post_readed_size > 0) {
+               if (bodyLen > 0 || post_read_size > 0) {
                        FD_SET(outFd, &writeSet);
                        nfound = outFd > inFd ? outFd : inFd;
-                       if (post_readed_size == 0) {
-                               FD_SET(config->accepted_socket, &readSet);
-                               if (nfound < config->accepted_socket)
-                                       nfound = config->accepted_socket;
+                       if (post_read_size == 0) {
+                               FD_SET(accepted_socket, &readSet);
+                               if (nfound < accepted_socket)
+                                       nfound = accepted_socket;
                        }
                        /* Now wait on the set of sockets! */
-                       nfound = select(nfound + 1, &readSet, &writeSet, 0, NULL);
+                       nfound = select(nfound + 1, &readSet, &writeSet, NULL, NULL);
                } else {
                        if (!bodyLen) {
-                               close(outFd);
+                               close(outFd); /* no more POST data to CGI */
                                bodyLen = -1;
                        }
-                       nfound = select(inFd + 1, &readSet, 0, 0, NULL);
+                       nfound = select(inFd + 1, &readSet, NULL, NULL, NULL);
                }
 
                if (nfound <= 0) {
-                       if (waitpid(pid, &status, WNOHANG) > 0) {
-                               close(inFd);
-                               if (DEBUG && WIFEXITED(status))
-                                       bb_error_msg("piped has exited with status=%d", WEXITSTATUS(status));
-                               if (DEBUG && WIFSIGNALED(status))
-                                       bb_error_msg("piped has exited with signal=%d", WTERMSIG(status));
-                               break;
+                       if (waitpid(pid, &status, WNOHANG) <= 0) {
+                               /* Weird. CGI didn't exit and no fd's
+                                * are ready, yet select returned?! */
+                               continue;
                        }
-               } else if (post_readed_size > 0 && FD_ISSET(outFd, &writeSet)) {
-                       count = full_write(outFd, wbuf + post_readed_idx, post_readed_size);
+                       close(inFd);
+                       if (DEBUG && WIFEXITED(status))
+                               bb_error_msg("piped has exited with status=%d", WEXITSTATUS(status));
+                       if (DEBUG && WIFSIGNALED(status))
+                               bb_error_msg("piped has exited with signal=%d", WTERMSIG(status));
+                       break;
+               }
+
+               if (post_read_size > 0 && FD_ISSET(outFd, &writeSet)) {
+                       /* Have data from peer and can write to CGI */
+               // huh? why full_write? what if we will block?
+               // (imagine that CGI does not read its stdin...)
+                       count = full_write(outFd, wbuf + post_read_idx, post_read_size);
                        if (count > 0) {
-                               post_readed_size -= count;
-                               post_readed_idx += count;
-                               if (post_readed_size == 0)
-                                       post_readed_idx = 0;
+                               post_read_idx += count;
+                               post_read_size -= count;
                        } else {
-                               post_readed_size = post_readed_idx = bodyLen = 0; /* broken pipe to CGI */
+                               post_read_size = bodyLen = 0; /* broken pipe to CGI */
                        }
-               } else if (bodyLen > 0 && post_readed_size == 0 && FD_ISSET(config->accepted_socket, &readSet)) {
+               } else if (bodyLen > 0 && post_read_size == 0
+                && FD_ISSET(accepted_socket, &readSet)
+               ) {
+                       /* We expect data, prev data portion is eaten by CGI
+                        * and there *is* data to read from the peer
+                        * (POSTDATA?) */
                        count = bodyLen > (int)sizeof(wbuf) ? (int)sizeof(wbuf) : bodyLen;
-                       count = safe_read(config->accepted_socket, wbuf, count);
+                       count = safe_read(accepted_socket, wbuf, count);
                        if (count > 0) {
-                               post_readed_size += count;
+                               post_read_size = count;
+                               post_read_idx = 0;
                                bodyLen -= count;
                        } else {
                                bodyLen = 0;    /* closed */
                        }
                }
-               if (FD_ISSET(inFd, &readSet)) {
-                       int s = config->accepted_socket;
-                       char *rbuf = config->buf;
 
-#ifndef PIPE_BUF
-# define PIPESIZE 4096          /* amount of buffering in a pipe */
-#else
-# define PIPESIZE PIPE_BUF
-#endif
+#define PIPESIZE PIPE_BUF
 #if PIPESIZE >= MAX_MEMORY_BUFF
 # error "PIPESIZE >= MAX_MEMORY_BUFF"
 #endif
-
-                       /* There is something to read */
-                       count = safe_read(inFd, rbuf, PIPESIZE);
-                       if (count == 0)
-                               break;  /* closed */
-                       if (count > 0) {
-                               if (firstLine) {
-                                       rbuf[count] = 0;
-                                       /* check to see if the user script added headers */
-                                       if (strncmp(rbuf, "HTTP/1.0 200 OK\r\n", 4) != 0) {
-                                               full_write(s, "HTTP/1.0 200 OK\r\n", 17);
+               if (FD_ISSET(inFd, &readSet)) {
+                       /* There is something to read from CGI */
+                       int s = accepted_socket;
+                       char *rbuf = iobuf;
+
+                       /* 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;
                                        }
-                                       /* Sometimes CGI is writing to pipe in small chunks
-                                        * and we don't see Content-type (because the read
-                                        * is too short) and we emit bogus "text/plain"!
-                                        * Is it a bug or CGI *has to* write it in one piece? */
-                                       if (strstr(rbuf, "ontent-") == 0) {
+                                       /* 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);
                                        }
-                                       firstLine = 0;
+                                        */
+                                       count = buf_count;
+                                       buf_count = -1; /* buffering off */
                                }
-                               if (full_write(s, rbuf, count) != count)
-                                       break;
-
-                               if (DEBUG)
-                                       fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
+                       } else {
+                               count = safe_read(inFd, rbuf, PIPESIZE);
+                               if (count <= 0)
+                                       break;  /* eof (or error) */
                        }
-               }
-       }
+                       if (full_write(s, rbuf, count) != count)
+                               break;
+                       if (DEBUG)
+                               fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
+               } /* if (FD_ISSET(inFd)) */
+       } /* while (1) */
        return 0;
 }
 #endif          /* FEATURE_HTTPD_CGI */
@@ -1239,14 +1338,14 @@ static int sendFile(const char *url)
                                break;
                }
        /* also, if not found, set default as "application/octet-stream";  */
-       config->found_mime_type = table[1];
+       found_mime_type = table[1];
 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
        if (suffix) {
                Htaccess * cur;
 
-               for (cur = config->mime_a; cur; cur = cur->next) {
+               for (cur = mime_a; cur; cur = cur->next) {
                        if (strcmp(cur->before_colon, suffix) == 0) {
-                               config->found_mime_type = cur->after_colon;
+                               found_mime_type = cur->after_colon;
                                break;
                        }
                }
@@ -1255,17 +1354,19 @@ static int sendFile(const char *url)
 
        if (DEBUG)
                fprintf(stderr, "sending file '%s' content-type: %s\n",
-                       url, config->found_mime_type);
+                       url, found_mime_type);
 
        f = open(url, O_RDONLY);
        if (f >= 0) {
                int count;
-               char *buf = config->buf;
+               char *buf = iobuf;
 
                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 = accepted_socket;
+                       if (fd == 0) fd++; /* write to fd# 1 in inetd mode */
+                       if (full_write(fd, buf, count) != count)
                                break;
                }
                close(f);
@@ -1283,25 +1384,28 @@ static int checkPermIP(void)
        Htaccess_IP * cur;
 
        /* This could stand some work */
-       for (cur = config->ip_a_d; cur; cur = cur->next) {
-               if (DEBUG)
-                       fprintf(stderr, "checkPermIP: '%s' ? ", config->rmt_ip_str);
-               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 ((config->rmt_ip & cur->mask) == cur->ip)
+       for (cur = ip_a_d; cur; cur = cur->next) {
+#if ENABLE_FEATURE_HTTPD_CGI && DEBUG
+               fprintf(stderr, "checkPermIP: '%s' ? ", 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),
+                       (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 ((rmt_ip & cur->mask) == cur->ip)
                        return cur->allow_deny == 'A';   /* Allow/Deny */
        }
 
        /* if unconfigured, return 1 - access from all */
-       return !config->flg_deny_all;
+       return !flg_deny_all;
 }
 
 /****************************************************************************
@@ -1331,7 +1435,7 @@ static int checkPerm(const char *path, const char *request)
        const char *prev = NULL;
 
        /* This could stand some work */
-       for (cur = config->auth; cur; cur = cur->next) {
+       for (cur = g_auth; cur; cur = cur->next) {
                size_t l;
 
                p0 = cur->before_colon;
@@ -1377,9 +1481,9 @@ static int checkPerm(const char *path, const char *request)
 
                        if (strcmp(p, request) == 0) {
 set_remoteuser_var:
-                               config->remoteuser = strdup(request);
-                               if (config->remoteuser)
-                                       config->remoteuser[(u - request)] = 0;
+                               remoteuser = strdup(request);
+                               if (remoteuser)
+                                       remoteuser[(u - request)] = 0;
                                return 1;   /* Ok */
                        }
                        /* unauthorized */
@@ -1402,7 +1506,7 @@ set_remoteuser_var:
 static void handle_sigalrm(int sig)
 {
        sendHeaders(HTTP_REQUEST_TIMEOUT);
-       config->alarm_signaled = sig;
+       alarm_signaled = 1;
 }
 
 /****************************************************************************
@@ -1414,7 +1518,7 @@ static void handle_sigalrm(int sig)
  ****************************************************************************/
 static void handleIncoming(void)
 {
-       char *buf = config->buf;
+       char *buf = iobuf;
        char *url;
        char *purl;
        int  blank = -1;
@@ -1484,23 +1588,23 @@ static void handleIncoming(void)
                strcpy(url, buf);
                /* extract url args if present */
                test = strchr(url, '?');
-               config->query = NULL;
+               g_query = NULL;
                if (test) {
                        *test++ = '\0';
-                       config->query = test;
+                       g_query = test;
                }
 
                test = decodeString(url, 0);
                if (test == NULL)
                        goto BAD_REQUEST;
-               /* FIXME: bug? should be "url+1"? */
-               if (test == (buf+1)) {
+               if (test == url+1) {
+                       /* '/' or NUL is encoded */
                        sendHeaders(HTTP_NOT_FOUND);
                        break;
                }
 
                /* algorithm stolen from libbb bb_simplify_path(),
-                        but don't strdup and reducing trailing slash and protect out root */
+                * but don't strdup and reducing trailing slash and protect out root */
                purl = test = url;
                do {
                        if (*purl == '/') {
@@ -1510,18 +1614,18 @@ static void handleIncoming(void)
                                }
                                if (*test == '.') {
                                        /* skip extra '.' */
-                                       if (test[1] == '/' || test[1] == 0) {
+                                       if (test[1] == '/' || !test[1]) {
                                                continue;
-                                       } else
+                                       }
                                        /* '..': be careful */
-                                       if (test[1] == '.' && (test[2] == '/' || test[2] == 0)) {
+                                       if (test[1] == '.' && (test[2] == '/' || !test[2])) {
                                                ++test;
                                                if (purl == url) {
                                                        /* protect out root */
                                                        goto BAD_REQUEST;
                                                }
                                                while (*--purl != '/') /* omit previous dir */;
-                                               continue;
+                                                       continue;
                                        }
                                }
                        }
@@ -1533,11 +1637,11 @@ static void handleIncoming(void)
                /* If URL is directory, adding '/' */
                if (test[-1] != '/') {
                        if (is_directory(url + 1, 1, &sb)) {
-                               config->found_moved_temporarily = url;
+                               found_moved_temporarily = url;
                        }
                }
                if (DEBUG)
-                       fprintf(stderr, "url='%s', args=%s\n", url, config->query);
+                       fprintf(stderr, "url='%s', args=%s\n", url, g_query);
 
                test = url;
                ip_allowed = checkPermIP();
@@ -1568,7 +1672,8 @@ static void handleIncoming(void)
                                        /* extra read only for POST */
                                        if (prequest != request_GET) {
                                                test = buf + sizeof("Content-length:")-1;
-                                               if (!test[0]) goto bail_out;
+                                               if (!test[0])
+                                                       goto bail_out;
                                                errno = 0;
                                                /* not using strtoul: it ignores leading munis! */
                                                length = strtol(test, &test, 10);
@@ -1583,7 +1688,7 @@ static void handleIncoming(void)
                                } else if ((STRNCASECMP(buf, "Content-Type:") == 0)) {
                                        content_type = strdup(skip_whitespace(buf + sizeof("Content-Type:")-1));
                                } else if ((STRNCASECMP(buf, "Referer:") == 0)) {
-                                       config->referer = strdup(skip_whitespace(buf + sizeof("Referer:")-1));
+                                       referer = strdup(skip_whitespace(buf + sizeof("Referer:")-1));
                                }
 #endif
 
@@ -1606,7 +1711,7 @@ static void handleIncoming(void)
                        } /* while extra header reading */
                }
                alarm(0);
-               if (config->alarm_signaled)
+               if (alarm_signaled)
                        break;
 
                if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 || ip_allowed == 0) {
@@ -1625,10 +1730,10 @@ static void handleIncoming(void)
                }
 #endif
 
-               if (config->found_moved_temporarily) {
+               if (found_moved_temporarily) {
                        sendHeaders(HTTP_MOVED_TEMPORARILY);
                        /* clear unforked memory flag */
-                       config->found_moved_temporarily = NULL;
+                       found_moved_temporarily = NULL;
                        break;
                }
 
@@ -1641,6 +1746,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 = 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;
@@ -1650,8 +1769,8 @@ static void handleIncoming(void)
                        strcpy(purl, "index.html");
                if (stat(test, &sb) == 0) {
                        /* It's a dir URL and there is index.html */
-                       config->ContentLength = sb.st_size;
-                       config->last_mod = sb.st_mtime;
+                       ContentLength = sb.st_size;
+                       last_mod = sb.st_mtime;
                }
 #if ENABLE_FEATURE_HTTPD_CGI
                else if (purl[-1] == '/') {
@@ -1659,45 +1778,47 @@ static void handleIncoming(void)
                         * Try cgi-bin/index.cgi */
                        if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) {
                                purl[0] = '\0';
-                               config->query = url;
+                               g_query = url;
                                sendCgi("/cgi-bin/index.cgi", prequest, length, cookie, content_type);
                                break;
                        }
                }
 #endif  /* FEATURE_HTTPD_CGI */
                sendFile(test);
-               config->ContentLength = -1;
+               ContentLength = -1;
        } while (0);
 
+#if ENABLE_FEATURE_HTTPD_CGI
  bail_out:
+#endif
 
        if (DEBUG)
                fprintf(stderr, "closing socket\n\n");
 #if ENABLE_FEATURE_HTTPD_CGI
        free(cookie);
        free(content_type);
-       free(config->referer);
-       config->referer = NULL;
+       free(referer);
+       referer = NULL;
 # if ENABLE_FEATURE_HTTPD_BASIC_AUTH
-       free(config->remoteuser);
-       config->remoteuser = NULL;
+       free(remoteuser);
+       remoteuser = NULL;
 # endif
 #endif
-       shutdown(config->accepted_socket, SHUT_WR);
+       shutdown(accepted_socket, SHUT_WR);
 
        /* Properly wait for remote to closed */
        FD_ZERO(&s_fd);
-       FD_SET(config->accepted_socket, &s_fd);
+       FD_SET(accepted_socket, &s_fd);
 
        do {
                tv.tv_sec = 2;
                tv.tv_usec = 0;
-               retval = select(config->accepted_socket + 1, &s_fd, NULL, NULL, &tv);
-       } while (retval > 0 && read(config->accepted_socket, buf, sizeof(config->buf) > 0));
+               retval = select(accepted_socket + 1, &s_fd, NULL, NULL, &tv);
+       } while (retval > 0 && read(accepted_socket, buf, sizeof(iobuf) > 0));
 
-       shutdown(config->accepted_socket, SHUT_RD);
+       shutdown(accepted_socket, SHUT_RD);
        /* In inetd case, we close fd 1 (stdout) here. We will exit soon anyway */
-       close(config->accepted_socket);
+       close(accepted_socket);
 }
 
 /****************************************************************************
@@ -1724,9 +1845,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;
@@ -1734,63 +1859,75 @@ 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);
+               accepted_socket = s;
+               rmt_ip = 0;
+               tcp_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(rmt_ip_str);
+               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'", rmt_ip_str);
 #endif
 #endif /* FEATURE_HTTPD_CGI */
+               if (fromAddr.sa.sa_family == AF_INET) {
+                       rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr);
+                       tcp_port = ntohs(fromAddr.sin.sin_port);
+               }
+#if ENABLE_FEATURE_IPV6
+               if (fromAddr.sa.sa_family == AF_INET6) {
+                       //rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr);
+                       tcp_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));
-#if !DEBUG
-               if (fork() == 0)
-#endif
-               {
-                       /* This is the spawned thread */
+               setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+               if (DEBUG || fork() == 0) {
+                       /* child */
 #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
                        /* protect reload config, may be confuse checking */
                        signal(SIGHUP, SIG_IGN);
 #endif
                        handleIncoming();
-#if !DEBUG
-                       exit(0);
-#endif
+                       if (!DEBUG)
+                               exit(0);
                }
                close(s);
-       } // while (1)
+       } /* while (1) */
        return 0;
 }
 
 /* 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);
+       rmt_ip = 0;
+       tcp_port = 0;
+#if ENABLE_FEATURE_HTTPD_CGI || DEBUG
+       free(rmt_ip_str);
+       rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr.sa, fromAddrLen);
+#endif
+       if (fromAddr.sa.sa_family == AF_INET) {
+               rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr);
+               tcp_port = ntohs(fromAddr.sin.sin_port);
+       }
+#if ENABLE_FEATURE_IPV6
+       if (fromAddr.sa.sa_family == AF_INET6) {
+               //rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr);
+               tcp_port = ntohs(fromAddr.sin6.sin6_port);
+       }
 #endif
-       config->port = ntohs(fromAddrLen.sin_port);
        handleIncoming();
        return 0;
 }
@@ -1832,18 +1969,11 @@ enum {
        OPT_FOREGROUND  = 1 << p_opt_foreground,
 };
 
-static const char httpd_opts[] = "c:d:h:"
-       USE_FEATURE_HTTPD_ENCODE_URL_STR("e:")
-       USE_FEATURE_HTTPD_BASIC_AUTH("r:")
-       USE_FEATURE_HTTPD_AUTH_MD5("m:")
-       USE_FEATURE_HTTPD_SETUID("u:")
-       "p:if";
-
 
-int httpd_main(int argc, char *argv[])
+int httpd_main(int argc, char **argv);
+int httpd_main(int argc, char **argv)
 {
        unsigned opt;
-       const char *home_httpd = home;
        char *url_for_decode;
        USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
        const char *s_port;
@@ -1856,17 +1986,20 @@ int httpd_main(int argc, char *argv[])
        setlocale(LC_TIME, "C");
 #endif
 
-       config = xzalloc(sizeof(*config));
-#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
-       config->realm = "Web Server Authentication";
-#endif
-       config->port = 80;
-       config->ContentLength = -1;
-
-       opt = getopt32(argc, argv, httpd_opts,
-                       &(config->configFile), &url_for_decode, &home_httpd
+       INIT_G();
+       home_httpd = xrealloc_getcwd_or_warn(NULL);
+       /* We do not "absolutize" path given by -h (home) opt.
+        * If user gives relative path in -h, $SCRIPT_FILENAME can end up
+        * relative too. */
+       opt = getopt32(argc, argv, "c:d:h:"
+                       USE_FEATURE_HTTPD_ENCODE_URL_STR("e:")
+                       USE_FEATURE_HTTPD_BASIC_AUTH("r:")
+                       USE_FEATURE_HTTPD_AUTH_MD5("m:")
+                       USE_FEATURE_HTTPD_SETUID("u:")
+                       "p:if",
+                       &(configFile), &url_for_decode, &home_httpd
                        USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
-                       USE_FEATURE_HTTPD_BASIC_AUTH(, &(config->realm))
+                       USE_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
                        USE_FEATURE_HTTPD_AUTH_MD5(, &pass)
                        USE_FEATURE_HTTPD_SETUID(, &s_ugid)
                        , &s_port
@@ -1888,36 +2021,26 @@ int httpd_main(int argc, char *argv[])
        }
 #endif
        if (opt & OPT_PORT)
-               config->port = xatou16(s_port);
+               tcp_port = xatou16(s_port);
 
 #if ENABLE_FEATURE_HTTPD_SETUID
        if (opt & OPT_SETUID) {
-               char *e;
-               // FIXME: what the default group should be?
-               ugid.gid = -1;
-               ugid.uid = strtoul(s_ugid, &e, 0);
-               if (*e == ':') {
-                       e++;
-                       ugid.gid = strtoul(e, &e, 0);
-               }
-               if (*e != '\0') {
-                       /* not integer */
-                       if (!uidgid_get(&ugid, s_ugid))
-                               bb_error_msg_and_die("unrecognized user[:group] "
+               if (!get_uidgid(&ugid, s_ugid, 1))
+                       bb_error_msg_and_die("unrecognized user[:group] "
                                                "name '%s'", s_ugid);
-               }
        }
 #endif
 
        xchdir(home_httpd);
        if (!(opt & OPT_INETD)) {
-               config->server_socket = openServer();
+               signal(SIGCHLD, SIG_IGN);
+               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);
@@ -1928,14 +2051,12 @@ int httpd_main(int argc, char *argv[])
 #if ENABLE_FEATURE_HTTPD_CGI
        {
                char *p = getenv("PATH");
-               if (p) {
-                       p = xstrdup(p);
-               }
+               /* env strings themself are not freed, no need to strdup(p): */
                clearenv();
                if (p)
-                       setenv1("PATH", p);
+                       putenv(p - 5);
                if (!(opt & OPT_INETD))
-                       setenv_long("SERVER_PORT", config->port);
+                       setenv_long("SERVER_PORT", tcp_port);
        }
 #endif
 
@@ -1949,6 +2070,6 @@ int httpd_main(int argc, char *argv[])
                return miniHttpd_inetd();
 
        if (!(opt & OPT_FOREGROUND))
-               xdaemon(1, 0);     /* don't change current directory */
-       return miniHttpd(config->server_socket);
+               bb_daemonize(0);     /* don't change current directory */
+       return miniHttpd(server_socket);
 }