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 */
/* 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 */
****************************************************************************/
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;
}
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;
#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) {
}
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);
}
/****************************************************************************
* (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).
*
int pid = 0;
int inFd;
int outFd;
- int firstLine = 1;
+ int buf_count;
int status;
size_t post_read_size, post_read_idx;
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] */
/* (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
#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)
/* parent process */
+ buf_count = 0;
post_read_size = 0;
post_read_idx = 0; /* for gcc */
inFd = fromCgi[0];
}
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));
) {
/* 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) {
}
}
- 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;
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);
/* 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 */
}
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;
/* 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;
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 */
/* 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;
}
"p:if";
+int httpd_main(int argc, char *argv[]);
int httpd_main(int argc, char *argv[])
{
unsigned opt;
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);