Fast CGI for busybox
Stefani Seibold
stefani at seibold.net
Mon Jul 7 13:49:01 UTC 2014
Hi,
this patch add Fast CGI Support to the busybox httpd web server. I hope you like it ;-)
To connect a extension to a fast CGI socket add a
*.<extension>:@<path to fcgi socket>
to the httpd.conf
The patch is against busybox 1.22.1
Greetings,
Stefani
diff -u -N -r -p busybox-1.22.1.orig/networking/httpd.c busybox-1.22.1/networking/httpd.c
--- busybox-1.22.1/networking/httpd.c 2014-01-09 19:15:44.000000000 +0100
+++ busybox-1.22.1/networking/httpd.c 2014-07-04 09:37:56.128167910 +0200
@@ -136,6 +136,10 @@
#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
# include <sys/sendfile.h>
#endif
+#if ENABLE_FEATURE_HTTPD_FAST_CGI
+# include <sys/un.h>
+#endif
+
/* amount of buffering in a pipe */
#ifndef PIPE_BUF
# define PIPE_BUF 4096
@@ -955,10 +959,16 @@ static void send_headers(int responseNum
/* emit the current date */
strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&timer));
len = sprintf(iobuf,
- "HTTP/1.0 %d %s\r\nContent-type: %s\r\n"
- "Date: %s\r\nConnection: close\r\n",
- responseNum, responseString, mime_type, tmp_str);
+ "HTTP/1.0 %d %s\r\n"
+ "Date: %s\r\n"
+ "Connection: close\r\n"
+ "Content-Type: %s",
+ responseNum, responseString, tmp_str, mime_type);
+ if (infoString) {
+ len += sprintf(iobuf + len, "; charset=iso-8859-1");
+ }
+ len += sprintf(iobuf + len, "\r\n");
#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
if (responseNum == HTTP_UNAUTHORIZED) {
len += sprintf(iobuf + len,
@@ -1009,7 +1019,7 @@ static void send_headers(int responseNum
);
}
- if (content_gzip)
+ if (content_gzip && !infoString)
len += sprintf(iobuf + len, "Content-Encoding: gzip\r\n");
iobuf[len++] = '\r';
@@ -1263,6 +1273,520 @@ static NOINLINE void cgi_io_loop_and_exi
}
#endif
+#if ENABLE_FEATURE_HTTPD_FAST_CGI
+
+/*
+ * Error codes. Assigned to avoid conflict with EOF and errno(2).
+ */
+#define FCGX_UNSUPPORTED_VERSION -2
+#define FCGX_PROTOCOL_ERROR -3
+
+typedef struct {
+ unsigned char version;
+ unsigned char type;
+ unsigned char requestIdB1;
+ unsigned char requestIdB0;
+ unsigned char contentLengthB1;
+ unsigned char contentLengthB0;
+ unsigned char paddingLength;
+ unsigned char reserved;
+} FCGI_Header;
+
+/*
+ * Number of bytes in a FCGI_Header. Future versions of the protocol
+ * will not reduce this number.
+ */
+#define FCGI_HEADER_LEN 8
+
+/*
+ * Value for version component of FCGI_Header
+ */
+#define FCGI_VERSION_1 1
+
+/*
+ * Values for type component of FCGI_Header
+ */
+#define FCGI_BEGIN_REQUEST 1
+#define FCGI_ABORT_REQUEST 2
+#define FCGI_END_REQUEST 3
+#define FCGI_PARAMS 4
+#define FCGI_STDIN 5
+#define FCGI_STDOUT 6
+#define FCGI_STDERR 7
+#define FCGI_DATA 8
+#define FCGI_GET_VALUES 9
+#define FCGI_GET_VALUES_RESULT 10
+#define FCGI_UNKNOWN_TYPE 11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+/*
+ * Value for requestId component of FCGI_Header
+ */
+#define FCGI_NULL_REQUEST_ID 0
+
+typedef struct {
+ unsigned char roleB1;
+ unsigned char roleB0;
+ unsigned char flags;
+ unsigned char reserved[5];
+} FCGI_BeginRequestBody;
+
+/*
+ * Mask for flags component of FCGI_BeginRequestBody
+ */
+#define FCGI_KEEP_CONN 1
+
+/*
+ * Values for role component of FCGI_BeginRequestBody
+ */
+#define FCGI_RESPONDER 1
+#define FCGI_AUTHORIZER 2
+#define FCGI_FILTER 3
+
+typedef struct {
+ unsigned char appStatusB3;
+ unsigned char appStatusB2;
+ unsigned char appStatusB1;
+ unsigned char appStatusB0;
+ unsigned char protocolStatus;
+ unsigned char reserved[3];
+} FCGI_EndRequestBody;
+
+/*
+ * Values for protocolStatus component of FCGI_EndRequestBody
+ */
+#define FCGI_REQUEST_COMPLETE 0
+#define FCGI_CANT_MPX_CONN 1
+#define FCGI_OVERLOADED 2
+#define FCGI_UNKNOWN_ROLE 3
+
+/*
+ * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
+ */
+#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
+#define FCGI_MAX_REQS "FCGI_MAX_REQS"
+#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
+
+static const unsigned short requestId = 1;
+
+static void * fcgi_flush_param(int sockfd, unsigned char *buf)
+{
+ FCGI_Header *header = (FCGI_Header *)iobuf;
+ unsigned short clen = buf - (unsigned char *)iobuf - sizeof(*header);
+ unsigned int len = (clen + 7) & ~7;
+
+ header->contentLengthB1 = clen >> 8;
+ header->contentLengthB0 = clen;
+ header->paddingLength = len - clen;
+
+ memset(buf, 0, len - clen);
+
+ len += sizeof(*header);
+
+ if (full_write(sockfd, iobuf, len) != len)
+ log_and_exit();
+
+ return iobuf + sizeof(*header);
+}
+
+static void * fcgi_store(int sockfd, unsigned char *buf, unsigned char v)
+{
+ if (buf - (unsigned char *)iobuf >= IOBUF_SIZE)
+ buf = fcgi_flush_param(sockfd, buf);
+
+ *buf++ = v;
+
+ return buf;
+}
+
+static void * fcgi_store_len(int sockfd, unsigned char *buf, unsigned int len)
+{
+ if (len < 0x80) {
+ buf = fcgi_store(sockfd, buf, len);
+ } else {
+ buf = fcgi_store(sockfd, buf, (len >> 24) | 0x80);
+ buf = fcgi_store(sockfd, buf, len >> 16);
+ buf = fcgi_store(sockfd, buf, len >> 8);
+ buf = fcgi_store(sockfd, buf, len);
+ }
+ return buf;
+}
+
+static void * fcgi_set_param(int sockfd, unsigned char *buf, char *env, unsigned int nameLen, unsigned int valueLen)
+{
+ buf = fcgi_store_len(sockfd, buf, nameLen);
+ buf = fcgi_store_len(sockfd, buf, valueLen);
+
+ while(nameLen--)
+ buf = fcgi_store(sockfd, buf, *env++);
+ env++;
+ while(valueLen--)
+ buf = fcgi_store(sockfd, buf, *env++);
+
+ return buf;
+}
+
+static void * fcgi_begin_request(void *buf, unsigned short role, int keepConnection)
+{
+ FCGI_BeginRequestBody *body = buf;
+
+ body->roleB1 = role >> 8;
+ body->roleB0 = role;
+ body->flags = keepConnection ? FCGI_KEEP_CONN : 0;
+ memset(&body->reserved, 0, sizeof(body->reserved));
+
+ return buf + sizeof(*body);
+}
+
+static void _fcgi_make_header(FCGI_Header *header, unsigned char type, unsigned short contentLength, unsigned char paddingLength)
+{
+ header->version = FCGI_VERSION_1;
+ header->type = type;
+ header->requestIdB1 = requestId >> 8;
+ header->requestIdB0 = requestId;
+ header->contentLengthB1 = contentLength >> 8;
+ header->contentLengthB0 = contentLength;
+ header->paddingLength = paddingLength;
+ header->reserved = 0;
+}
+
+static void * fcgi_make_header(unsigned char type, unsigned short contentLength, unsigned char paddingLength)
+{
+ FCGI_Header *header = (FCGI_Header *)iobuf;
+
+ _fcgi_make_header(header, type, contentLength, paddingLength);
+
+ return iobuf + sizeof(*header);
+}
+
+/* gcc 4.2.1 fares better with NOINLINE */
+static NOINLINE void fcgi_io_loop_and_exit(int sockfd, int post_len) NORETURN;
+static NOINLINE void fcgi_io_loop_and_exit(int sockfd, int post_len)
+{
+ struct pollfd pfd[2];
+ int out_cnt; /* we buffer a bit of initial CGI output */
+ const int init_len = 8;
+ unsigned int rbuf_cnt;
+ int count;
+ FCGI_Header toFCGIhdr, fromFCGIhdr;
+ unsigned int toFCGIhdr_cnt, fromFCGIhdr_cnt;
+ unsigned int clen, plen;
+ char *rbuf = NULL;
+ int read_flag;
+
+ _fcgi_make_header(&toFCGIhdr, FCGI_STDIN, hdr_cnt, 0);
+ toFCGIhdr_cnt = sizeof(toFCGIhdr);
+ fromFCGIhdr_cnt = 0;
+ clen = 0;
+ plen = 0;
+
+ /* iobuf is used for CGI -> network data,
+ * hdr_buf is for network -> CGI data (POSTDATA) */
+
+ /* If CGI dies, we still want to correctly finish reading its output
+ * and send it to the peer. So please no SIGPIPEs! */
+ signal(SIGPIPE, SIG_IGN);
+
+ // We inconsistently handle a case when more POSTDATA from network
+ // is coming than we expected. We may give *some part* of that
+ // extra data to CGI.
+
+ /* NB: breaking out of this loop jumps to log_and_exit() */
+ out_cnt = 0;
+ rbuf_cnt = 0;
+
+ while (1) {
+ memset(pfd, 0, sizeof(pfd));
+
+ pfd[1].fd = sockfd;
+ pfd[1].events = POLLIN;
+
+ if (toFCGIhdr_cnt || hdr_cnt) {
+ pfd[1].events |= POLLOUT;
+ } else {
+ if (post_len > 0)
+ pfd[0].events = POLLIN;
+ }
+
+ /* Now wait on the set of sockets */
+ count = safe_poll(pfd, 2, -1);
+ if (count <= 0)
+ break;
+
+ if (pfd[0].revents) {
+ /* post_len > 0 && hdr_cnt == 0 here */
+ /* We expect data, prev data portion is eaten by CGI
+ * and there *is* data to read from the peer
+ * (POSTDATA) */
+ count = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
+ if (count > 0) {
+ hdr_cnt = count;
+ hdr_ptr = hdr_buf;
+ _fcgi_make_header(&toFCGIhdr, FCGI_STDIN, hdr_cnt, 0);
+ toFCGIhdr_cnt = sizeof(toFCGIhdr);
+ } else {
+ /* no more POST data can be read */
+ post_len = 0;
+ }
+ }
+
+ if (pfd[1].revents & POLLOUT) {
+ /* Have data from peer and can write to CGI */
+ if (toFCGIhdr_cnt > 0) {
+ count = safe_write(sockfd, (unsigned char *)&toFCGIhdr + sizeof(toFCGIhdr) - toFCGIhdr_cnt, toFCGIhdr_cnt);
+ if (count > 0) {
+ toFCGIhdr_cnt -= count;
+
+ if (!toFCGIhdr_cnt && !post_len) {
+ shutdown(sockfd, SHUT_WR);
+ }
+ } else {
+ toFCGIhdr_cnt = hdr_cnt = post_len = 0;
+ }
+ }
+ if (!toFCGIhdr_cnt && hdr_cnt > 0) {
+ count = safe_write(sockfd, hdr_ptr, hdr_cnt);
+ if (count > 0) {
+ hdr_ptr += count;
+ hdr_cnt -= count;
+ post_len -= count;
+
+ /* terminate with an empty FCGI_STDIN message */
+ if (!hdr_cnt && !post_len) {
+ _fcgi_make_header(&toFCGIhdr, FCGI_STDIN, hdr_cnt, 0);
+ toFCGIhdr_cnt = sizeof(toFCGIhdr);
+ }
+ } else {
+ hdr_cnt = post_len = 0;
+ }
+ }
+ }
+
+ if (pfd[1].revents & POLLIN) {
+ /* There is something to read from CGI */
+
+ read_flag = 0;
+
+ if (fromFCGIhdr_cnt < sizeof(fromFCGIhdr)) {
+ count = safe_read(sockfd, (unsigned char *)&fromFCGIhdr + fromFCGIhdr_cnt, sizeof(fromFCGIhdr) - fromFCGIhdr_cnt);
+ if (count <= 0)
+ goto exit_loop;
+
+ fromFCGIhdr_cnt += count;
+
+ /*
+ * Header is complete (possibly from previous call). What now?
+ */
+ if (fromFCGIhdr_cnt < sizeof(fromFCGIhdr))
+ continue;
+
+ if (fromFCGIhdr.version != FCGI_VERSION_1)
+ exit(FCGX_UNSUPPORTED_VERSION);
+
+ if ((fromFCGIhdr.requestIdB1 << 8) + fromFCGIhdr.requestIdB0 != requestId)
+ exit(FCGX_PROTOCOL_ERROR);
+
+ clen = (fromFCGIhdr.contentLengthB1 << 8) + fromFCGIhdr.contentLengthB0;
+ plen = fromFCGIhdr.paddingLength;
+
+ switch(fromFCGIhdr.type) {
+ case FCGI_STDOUT:
+ if (out_cnt >= 0)
+ rbuf_cnt = out_cnt;
+ else
+ rbuf_cnt = 0;
+ rbuf = iobuf;
+ break;
+ case FCGI_END_REQUEST:
+ if(clen != sizeof(FCGI_EndRequestBody))
+ exit(FCGX_PROTOCOL_ERROR);
+ case FCGI_STDERR:
+ case FCGI_GET_VALUES_RESULT:
+ case FCGI_UNKNOWN_TYPE:
+ rbuf = iobuf + init_len;
+ rbuf_cnt = init_len;
+ break;
+ default:
+ exit(FCGX_PROTOCOL_ERROR);
+ }
+
+ read_flag = 1;
+ }
+
+ if (clen) {
+ count = safe_read(sockfd, rbuf + rbuf_cnt, clen + rbuf_cnt < PIPE_BUF ? clen : PIPE_BUF - rbuf_cnt);
+ if (count <= 0) {
+ if (read_flag)
+ continue;
+ goto exit_loop;
+ }
+
+ clen -= count;
+
+ switch(fromFCGIhdr.type) {
+ case FCGI_STDOUT:
+ /* Are we still buffering CGI output? */
+ if (out_cnt >= 0) {
+ out_cnt += count;
+ if (out_cnt < init_len) {
+ rbuf_cnt += count;
+ break;
+ }
+
+ count = out_cnt;
+ out_cnt = -1; /* buffering off */
+
+ /* "Status" header format is: "Status: 302 Redirected\r\n" */
+ if (!memcmp(rbuf, "Status: ", 8)) {
+ /* send "HTTP/1.0 " */
+ if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9)
+ goto write_err;
+
+ /* skip "Status: " */
+ count -= 8;
+ if (full_write(STDOUT_FILENO, rbuf + 8, count) != count)
+ goto write_err;
+ break;
+ } else if (memcmp(rbuf, HTTP_200, 4)) {
+ /* there is no "HTTP", do it ourself */
+ if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
+ goto write_err;
+ }
+ }
+ if (full_write(STDOUT_FILENO, rbuf, count) != count)
+ goto write_err;
+
+ break;
+ case FCGI_STDERR:
+ full_write(STDERR_FILENO, rbuf, count);
+ break;
+ case FCGI_END_REQUEST:
+ break;
+ default:
+ break;
+ }
+
+ if (clen)
+ continue;
+
+ read_flag = 1;
+ }
+
+ if (plen) {
+ count = safe_read(sockfd, rbuf, plen < PIPE_BUF ? plen : PIPE_BUF);
+ if (count <= 0) {
+ if (read_flag)
+ continue;
+ goto exit_loop;
+ }
+
+ plen -= count;
+
+ if (plen)
+ continue;
+ }
+
+ fromFCGIhdr_cnt = 0;
+
+ if (fromFCGIhdr.type == FCGI_END_REQUEST) {
+#if 0
+ FCGI_EndRequestBody *erBody;
+
+ erBody = rbuf;
+ exitStatus = (erBody.appStatusB3 << 24)
+ + (erBody.appStatusB2 << 16)
+ + (erBody.appStatusB1 << 8)
+ + (erBody.appStatusB0);
+#endif
+ goto exit_loop;
+ }
+ }
+ }
+exit_loop:
+ /* eof (or error) and there was no "HTTP",
+ * so write it, then write received data */
+ if (out_cnt > 0) {
+ if (out_cnt >=4 && memcmp(iobuf, HTTP_200, 4))
+ full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1);
+ full_write(STDOUT_FILENO, iobuf, out_cnt);
+ }
+write_err:
+ log_and_exit();
+}
+
+static int fcgi_connect(const char *addr)
+{
+ int sockfd;
+ len_and_sockaddr *r;
+
+ if (*addr == '/') {
+ struct sockaddr_un *sun;
+
+ r = xzalloc(LSA_LEN_SIZE + sizeof(struct sockaddr_un));
+ r->len = sizeof(struct sockaddr_un);
+ r->u.sa.sa_family = AF_UNIX;
+ sun = (struct sockaddr_un *)&r->u.sa;
+ safe_strncpy(sun->sun_path, addr, sizeof(sun->sun_path));
+ }
+ else {
+ r = xhost2sockaddr(addr, 9000);
+ }
+
+ sockfd = socket(r->u.sa.sa_family, SOCK_STREAM, 0);
+ if (sockfd != -1) {
+ if (connect(sockfd, &r->u.sa, r->len)) {
+ close(sockfd);
+ sockfd = -1;
+ }
+ }
+ return sockfd;
+}
+
+static void fcgi_exec(const char *server, int post_len) NORETURN;
+static void fcgi_exec(const char *server, int post_len)
+{
+ unsigned char *buf;
+ int nameLen, valueLen;
+ char **envp;
+ int sockfd;
+
+ sockfd = fcgi_connect(server);
+ if (sockfd < 0)
+ send_headers_and_exit(HTTP_NOT_FOUND);
+
+ buf = fcgi_make_header(FCGI_BEGIN_REQUEST, sizeof(FCGI_BeginRequestBody), 0);
+ buf = fcgi_begin_request(buf, FCGI_RESPONDER, FALSE);
+
+ if (full_write(sockfd, iobuf, buf - (unsigned char *)iobuf) != buf - (unsigned char *)iobuf)
+ log_and_exit();
+
+ /*
+ * Send environment to the FCGI application server
+ */
+ buf = fcgi_make_header(FCGI_PARAMS, 0, 0);
+
+ for(envp = environ ; *envp; envp++) {
+ char *p = strchr(*envp, '=');
+
+ if (!p) {
+ nameLen = strlen(*envp);
+ valueLen = 0;
+ }
+ else {
+ nameLen = p - *envp;
+ valueLen = strlen(p + 1);
+ }
+ buf = fcgi_set_param(sockfd, buf, *envp, nameLen, valueLen);
+ }
+
+ if (buf != (unsigned char *)iobuf + sizeof(FCGI_Header))
+ buf = fcgi_flush_param(sockfd, buf);
+
+ fcgi_flush_param(sockfd, buf);
+ fcgi_io_loop_and_exit(sockfd, post_len);
+}
+#endif
+
#if ENABLE_FEATURE_HTTPD_CGI
static void setenv1(const char *name, const char *value)
@@ -1303,6 +1827,9 @@ static void send_cgi_and_exit(
struct fd_pair toCgi; /* httpd -> CGI pipe */
char *script, *last_slash;
int pid;
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ Htaccess *interpr = NULL;
+#endif
/* Make a copy. NB: caller guarantees:
* url[0] == '/', url[1] != '/' */
@@ -1395,9 +1922,34 @@ static void send_cgi_and_exit(
if (referer)
setenv1("HTTP_REFERER", referer);
setenv1("HTTP_HOST", host); /* set to "" if NULL */
- /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
- * just run "env SERVER_NAME=xyz httpd ..." instead */
+
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ {
+ char *suffix = strrchr(last_slash + 1, '.');
+
+ if (suffix) {
+ for (interpr = script_i; interpr; interpr = interpr->next) {
+ if (strcmp(interpr->before_colon + 1, suffix) == 0) {
+ /* found interpreter name */
+ break;
+ }
+ }
+ }
+ }
+
+#if ENABLE_FEATURE_HTTPD_FAST_CGI
+ if (interpr && *interpr->after_colon == '@') {
+ if (!getenv("SERVER_NAME"))
+ setenv1("SERVER_NAME", safe_gethostname());
+
+ if (!getenv("SERVER_ADDR"))
+ setenv1("SERVER_ADDR", "127.0.0.1");
+
+ fcgi_exec(interpr->after_colon + 1, post_len);
+ }
+#endif
+#endif
xpiped_pair(fromCgi);
xpiped_pair(toCgi);
@@ -1434,26 +1986,17 @@ static void send_cgi_and_exit(
}
script++;
- /* set argv[0] to name without path */
- argv[0] = script;
- argv[1] = NULL;
-
#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ if (interpr) {
+ argv[0] = interpr->after_colon;
+ argv[1] = script;
+ argv[2] = NULL;
+ } else
+#else
{
- char *suffix = strrchr(script, '.');
-
- if (suffix) {
- Htaccess *cur;
- for (cur = script_i; cur; cur = cur->next) {
- if (strcmp(cur->before_colon + 1, suffix) == 0) {
- /* found interpreter name */
- argv[0] = cur->after_colon;
- argv[1] = script;
- argv[2] = NULL;
- break;
- }
- }
- }
+ /* set argv[0] to name without path */
+ argv[0] = script;
+ argv[1] = NULL;
}
#endif
/* restore default signal dispositions for CGI process */
diff -u -N -r -p busybox-1.22.1.orig/networking/Config.src busybox-1.22.1/networking/Config.src
--- busybox-1.22.1.orig/networking/Config.src 2014-01-09 19:15:44.000000000 +0100
+++ busybox-1.22.1/networking/Config.src 2014-07-04 09:36:00.849170052 +0200
@@ -224,6 +224,14 @@ config FEATURE_HTTPD_CGI
This option allows scripts and executables to be invoked
when specific URLs are requested.
+config FEATURE_HTTPD_FAST_CGI
+ bool "Enabel fast CGI Support"
+ default y
+ depends on FEATURE_HTTPD_CGI
+ select FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ help
+ This option enables support for fast cgi
+
config FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
bool "Support for running scripts through an interpreter"
default y
More information about the busybox
mailing list