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