From 36e143f8304389d2de1f08cb738a83c5e23265e5 Mon Sep 17 00:00:00 2001
From: Jo-Philipp Wich <jow@openwrt.org>
Date: Sat, 14 Aug 2010 00:54:24 +0000
Subject: [package] uhttpd: 	- more robust handling of network failures on
 static file serving 	- support unlimited amount of authentication realms,
 listener and client sockets 	- support for interpreters (.php =>
 /usr/bin/php-cgi)

git-svn-id: svn://svn.openwrt.org/openwrt/trunk@22630 3c298f89-4303-0410-b956-a3cf2f4a3e73
---
 package/uhttpd/Makefile            |   2 +-
 package/uhttpd/files/uhttpd.config |   7 +
 package/uhttpd/files/uhttpd.init   |   7 +-
 package/uhttpd/src/uhttpd-cgi.c    |  19 +-
 package/uhttpd/src/uhttpd-cgi.h    |   3 +-
 package/uhttpd/src/uhttpd-file.c   |  24 +--
 package/uhttpd/src/uhttpd-utils.c  | 123 ++++++-----
 package/uhttpd/src/uhttpd-utils.h  |   5 +
 package/uhttpd/src/uhttpd.c        | 414 ++++++++++++++++++++-----------------
 package/uhttpd/src/uhttpd.h        |  15 +-
 10 files changed, 364 insertions(+), 255 deletions(-)

diff --git a/package/uhttpd/Makefile b/package/uhttpd/Makefile
index cb85ad88c0..de9eee9e9f 100644
--- a/package/uhttpd/Makefile
+++ b/package/uhttpd/Makefile
@@ -8,7 +8,7 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=uhttpd
-PKG_RELEASE:=14
+PKG_RELEASE:=15
 
 PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
 PKG_BUILD_DEPENDS := libcyassl liblua
diff --git a/package/uhttpd/files/uhttpd.config b/package/uhttpd/files/uhttpd.config
index 534e8f8b29..a29910a65f 100644
--- a/package/uhttpd/files/uhttpd.config
+++ b/package/uhttpd/files/uhttpd.config
@@ -27,6 +27,13 @@ config uhttpd main
 	# Default is /cgi-bin
 	option cgi_prefix	/cgi-bin
 
+	# List of extension->interpreter mappings.
+	# Files with an associated interpreter can
+	# be called outside of the CGI prefix and do
+	# not need to be executable.
+#	list interpreter	".php=/usr/bin/php-cgi"
+#	list interpreter	".cgi=/usr/bin/perl"
+
 	# Lua url prefix and handler script.
 	# Lua support is disabled if no prefix given.
 #	option lua_prefix	/luci
diff --git a/package/uhttpd/files/uhttpd.init b/package/uhttpd/files/uhttpd.init
index 8221d859bf..f8f1754e90 100755
--- a/package/uhttpd/files/uhttpd.init
+++ b/package/uhttpd/files/uhttpd.init
@@ -56,7 +56,7 @@ start_instance()
 
 	local cfg="$1"
 	local realm="$(uci_get system.@system[0].hostname)"
-	local listen http https
+	local listen http https interpreter path
 
 	append_arg "$cfg" home "-h"
 	append_arg "$cfg" realm "-r" "${realm:-OpenWrt}"
@@ -78,6 +78,11 @@ start_instance()
 		append UHTTPD_ARGS "-p $listen"
 	done
 
+	config_get interpreter "$cfg" interpreter
+	for path in $interpreter; do
+		append UHTTPD_ARGS "-i $path"
+	done
+
 	config_get https "$cfg" listen_https
 	config_get UHTTPD_KEY  "$cfg" key  /etc/uhttpd.key
 	config_get UHTTPD_CERT "$cfg" cert /etc/uhttpd.crt
diff --git a/package/uhttpd/src/uhttpd-cgi.c b/package/uhttpd/src/uhttpd-cgi.c
index 0861249162..f9dd9810da 100644
--- a/package/uhttpd/src/uhttpd-cgi.c
+++ b/package/uhttpd/src/uhttpd-cgi.c
@@ -135,8 +135,10 @@ static int uh_cgi_error_500(struct client *cl, struct http_request *req, const c
 }
 
 
-void uh_cgi_request(struct client *cl, struct http_request *req, struct path_info *pi)
-{
+void uh_cgi_request(
+	struct client *cl, struct http_request *req,
+	struct path_info *pi, struct interpreter *ip
+) {
 	int i, hdroff, bufoff;
 	int hdrlen = 0;
 	int buflen = 0;
@@ -199,9 +201,9 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf
 			dup2(rfd[1], 1);
 			dup2(wfd[0], 0);
 
-			/* check for regular, world-executable file */
-			if( (pi->stat.st_mode & S_IFREG) &&
-			    (pi->stat.st_mode & S_IXOTH)
+			/* check for regular, world-executable file _or_ interpreter */
+			if( ((pi->stat.st_mode & S_IFREG) &&
+			     (pi->stat.st_mode & S_IXOTH)) || (ip != NULL)
 			) {
 				/* build environment */
 				clearenv();
@@ -320,14 +322,17 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf
 				if( chdir(pi->root) )
 					perror("chdir()");
 
-				execl(pi->phys, pi->phys, NULL);
+				if( ip != NULL )
+					execl(ip->path, ip->path, pi->phys, NULL);
+				else
+					execl(pi->phys, pi->phys, NULL);
 
 				/* in case it fails ... */
 				printf(
 					"Status: 500 Internal Server Error\r\n\r\n"
 					"Unable to launch the requested CGI program:\n"
 					"  %s: %s\n",
-						pi->phys, strerror(errno)
+						ip ? ip->path : pi->phys, strerror(errno)
 				);
 			}
 
diff --git a/package/uhttpd/src/uhttpd-cgi.h b/package/uhttpd/src/uhttpd-cgi.h
index c90557d8fd..cb84dae0c6 100644
--- a/package/uhttpd/src/uhttpd-cgi.h
+++ b/package/uhttpd/src/uhttpd-cgi.h
@@ -25,7 +25,8 @@
 #include <linux/limits.h>
 
 void uh_cgi_request(
-	struct client *cl, struct http_request *req, struct path_info *pi
+	struct client *cl, struct http_request *req,
+	struct path_info *pi, struct interpreter *ip
 );
 
 #endif
diff --git a/package/uhttpd/src/uhttpd-file.c b/package/uhttpd/src/uhttpd-file.c
index ef9a77b6cc..25a5f6ece3 100644
--- a/package/uhttpd/src/uhttpd-file.c
+++ b/package/uhttpd/src/uhttpd-file.c
@@ -98,9 +98,9 @@ static char * uh_file_header_lookup(struct http_request *req, const char *name)
 }
 
 #define ensure_ret(x) \
-	do { if( x < 0 ) return; } while(0)
+	do { if( x < 0 ) return -1; } while(0)
 
-static void uh_file_response_ok_hdrs(struct client *cl, struct http_request *req, struct stat *s)
+static int uh_file_response_ok_hdrs(struct client *cl, struct http_request *req, struct stat *s)
 {
 	ensure_ret(uh_http_sendf(cl, NULL, "Connection: close\r\n"));
 
@@ -110,26 +110,26 @@ static void uh_file_response_ok_hdrs(struct client *cl, struct http_request *req
 		ensure_ret(uh_http_sendf(cl, NULL, "Last-Modified: %s\r\n", uh_file_unix2date(s->st_mtime)));
 	}
 
-	ensure_ret(uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL))));
+	return uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL)));
 }
 
-static void uh_file_response_200(struct client *cl, struct http_request *req, struct stat *s)
+static int uh_file_response_200(struct client *cl, struct http_request *req, struct stat *s)
 {
 	ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 200 OK\r\n", req->version));
-	uh_file_response_ok_hdrs(cl, req, s);
+	return uh_file_response_ok_hdrs(cl, req, s);
 }
 
-static void uh_file_response_304(struct client *cl, struct http_request *req, struct stat *s)
+static int uh_file_response_304(struct client *cl, struct http_request *req, struct stat *s)
 {
 	ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 304 Not Modified\r\n", req->version));
-	uh_file_response_ok_hdrs(cl, req, s);
+	return uh_file_response_ok_hdrs(cl, req, s);
 }
 
-static void uh_file_response_412(struct client *cl, struct http_request *req)
+static int uh_file_response_412(struct client *cl, struct http_request *req)
 {
-	ensure_ret(uh_http_sendf(cl, NULL,
+	return uh_http_sendf(cl, NULL,
 		"HTTP/%.1f 412 Precondition Failed\r\n"
-		"Connection: close\r\n", req->version));
+		"Connection: close\r\n", req->version);
 }
 
 static int uh_file_if_match(struct client *cl, struct http_request *req, struct stat *s)
@@ -350,7 +350,7 @@ void uh_file_request(struct client *cl, struct http_request *req, struct path_in
 			uh_file_if_none_match(cl, req, &pi->stat)
 		) {
 			/* write status */
-			uh_file_response_200(cl, req, &pi->stat);
+			ensure_out(uh_file_response_200(cl, req, &pi->stat));
 
 			ensure_out(uh_http_sendf(cl, NULL, "Content-Type: %s\r\n", uh_file_mime_lookup(pi->name)));
 			ensure_out(uh_http_sendf(cl, NULL, "Content-Length: %i\r\n", pi->stat.st_size));
@@ -385,7 +385,7 @@ void uh_file_request(struct client *cl, struct http_request *req, struct path_in
 	else if( (pi->stat.st_mode & S_IFDIR) && !cl->server->conf->no_dirlists )
 	{
 		/* write status */
-		uh_file_response_200(cl, req, NULL);
+		ensure_out(uh_file_response_200(cl, req, NULL));
 
 		if( req->version > 1.0 )
 			ensure_out(uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1));
diff --git a/package/uhttpd/src/uhttpd-utils.c b/package/uhttpd/src/uhttpd-utils.c
index 4a1423c715..e68926e935 100644
--- a/package/uhttpd/src/uhttpd-utils.c
+++ b/package/uhttpd/src/uhttpd-utils.c
@@ -605,8 +605,7 @@ struct path_info * uh_path_lookup(struct client *cl, const char *url)
 }
 
 
-static char uh_realms[UH_LIMIT_AUTHREALMS * sizeof(struct auth_realm)] = { 0 };
-static int uh_realm_count = 0;
+static struct auth_realm *uh_realms = NULL;
 
 struct auth_realm * uh_auth_add(char *path, char *user, char *pass)
 {
@@ -614,11 +613,8 @@ struct auth_realm * uh_auth_add(char *path, char *user, char *pass)
 	struct passwd *pwd;
 	struct spwd *spwd;
 
-	if( uh_realm_count < UH_LIMIT_AUTHREALMS )
+	if((new = (struct auth_realm *)malloc(sizeof(struct auth_realm))) != NULL)
 	{
-		new = (struct auth_realm *)
-			&uh_realms[uh_realm_count * sizeof(struct auth_realm)];
-
 		memset(new, 0, sizeof(struct auth_realm));
 
 		memcpy(new->path, path,
@@ -655,9 +651,13 @@ struct auth_realm * uh_auth_add(char *path, char *user, char *pass)
 
 		if( new->pass[0] )
 		{
-			uh_realm_count++;
+			new->next = uh_realms;
+			uh_realms = new;
+
 			return new;
 		}
+
+		free(new);
 	}
 
 	return NULL;
@@ -677,11 +677,8 @@ int uh_auth_check(
 	protected = 0;
 
 	/* check whether at least one realm covers the requested url */
-	for( i = 0; i < uh_realm_count; i++ )
+	for( realm = uh_realms; realm; realm = realm->next )
 	{
-		realm = (struct auth_realm *)
-			&uh_realms[i * sizeof(struct auth_realm)];
-
 		rlen = strlen(realm->path);
 
 		if( (plen >= rlen) && !strncasecmp(pi->name, realm->path, rlen) )
@@ -721,11 +718,8 @@ int uh_auth_check(
 		if( user && pass )
 		{
 			/* find matching realm */
-			for( i = 0, realm = NULL; i < uh_realm_count; i++ )
+			for( realm = uh_realms; realm; realm = realm->next )
 			{
-				realm = (struct auth_realm *)
-					&uh_realms[i * sizeof(struct auth_realm)];
-
 				rlen = strlen(realm->path);
 
 				if( (plen >= rlen) &&
@@ -769,22 +763,17 @@ int uh_auth_check(
 }
 
 
-static char uh_listeners[UH_LIMIT_LISTENERS * sizeof(struct listener)] = { 0 };
-static char uh_clients[UH_LIMIT_CLIENTS * sizeof(struct client)] = { 0 };
-
-static int uh_listener_count = 0;
-static int uh_client_count = 0;
-
+static struct listener *uh_listeners = NULL;
+static struct client *uh_clients = NULL;
 
 struct listener * uh_listener_add(int sock, struct config *conf)
 {
 	struct listener *new = NULL;
 	socklen_t sl;
 
-	if( uh_listener_count < UH_LIMIT_LISTENERS )
+	if( (new = (struct listener *)malloc(sizeof(struct listener))) != NULL )
 	{
-		new = (struct listener *)
-			&uh_listeners[uh_listener_count * sizeof(struct listener)];
+		memset(new, 0, sizeof(struct listener));
 
 		new->socket = sock;
 		new->conf   = conf;
@@ -794,24 +783,22 @@ struct listener * uh_listener_add(int sock, struct config *conf)
 		memset(&(new->addr), 0, sl);
 		getsockname(sock, (struct sockaddr *) &(new->addr), &sl);
 
-		uh_listener_count++;
+		new->next = uh_listeners;
+		uh_listeners = new;
+
+		return new;
 	}
 
-	return new;
+	return NULL;
 }
 
 struct listener * uh_listener_lookup(int sock)
 {
 	struct listener *cur = NULL;
-	int i;
-
-	for( i = 0; i < uh_listener_count; i++ )
-	{
-		cur = (struct listener *) &uh_listeners[i * sizeof(struct listener)];
 
+	for( cur = uh_listeners; cur; cur = cur->next )
 		if( cur->socket == sock )
 			return cur;
-	}
 
 	return NULL;
 }
@@ -822,10 +809,9 @@ struct client * uh_client_add(int sock, struct listener *serv)
 	struct client *new = NULL;
 	socklen_t sl;
 
-	if( uh_client_count < UH_LIMIT_CLIENTS )
+	if( (new = (struct client *)malloc(sizeof(struct client))) != NULL )
 	{
-		new = (struct client *)
-			&uh_clients[uh_client_count * sizeof(struct client)];
+		memset(new, 0, sizeof(struct client));
 
 		new->socket = sock;
 		new->server = serv;
@@ -840,7 +826,8 @@ struct client * uh_client_add(int sock, struct listener *serv)
 		memset(&(new->servaddr), 0, sl);
 		getsockname(sock, (struct sockaddr *) &(new->servaddr), &sl);
 
-		uh_client_count++;
+		new->next = uh_clients;
+		uh_clients = new;
 	}
 
 	return new;
@@ -849,30 +836,72 @@ struct client * uh_client_add(int sock, struct listener *serv)
 struct client * uh_client_lookup(int sock)
 {
 	struct client *cur = NULL;
-	int i;
-
-	for( i = 0; i < uh_client_count; i++ )
-	{
-		cur = (struct client *) &uh_clients[i * sizeof(struct client)];
 
+	for( cur = uh_clients; cur; cur = cur->next )
 		if( cur->socket == sock )
 			return cur;
-	}
 
 	return NULL;
 }
 
 void uh_client_remove(int sock)
 {
-	struct client *del = uh_client_lookup(sock);
+	struct client *cur = NULL;
+	struct client *prv = NULL;
+
+	for( cur = uh_clients; cur; prv = cur, cur = cur->next )
+	{
+		if( cur->socket == sock )
+		{
+			if( prv )
+				prv->next = cur->next;
+			else
+				uh_clients = cur->next;
+
+			free(cur);
+			break;
+		}
+	}
+}
+
+
+#ifdef HAVE_CGI
+static struct interpreter *uh_interpreters = NULL;
+
+struct interpreter * uh_interpreter_add(const char *extn, const char *path)
+{
+	struct interpreter *new = NULL;
 
-	if( del )
+	if( (new = (struct interpreter *)
+			malloc(sizeof(struct interpreter))) != NULL )
 	{
-		memmove(del, del + 1,
-			sizeof(uh_clients) - (int)((char *)del - uh_clients) - sizeof(struct client));
+		memset(new, 0, sizeof(struct interpreter));
+
+		memcpy(new->extn, extn, min(strlen(extn), sizeof(new->extn)-1));
+		memcpy(new->path, path, min(strlen(path), sizeof(new->path)-1));
+
+		new->next = uh_interpreters;
+		uh_interpreters = new;
 
-		uh_client_count--;
+		return new;
 	}
+
+	return NULL;
 }
 
+struct interpreter * uh_interpreter_lookup(const char *path)
+{
+	struct interpreter *cur = NULL;
+	const char *e;
+
+	for( cur = uh_interpreters; cur; cur = cur->next )
+	{
+		e = &path[max(strlen(path) - strlen(cur->extn), 0)];
 
+		if( !strcmp(e, cur->extn) )
+			return cur;
+	}
+
+	return NULL;
+}
+#endif
diff --git a/package/uhttpd/src/uhttpd-utils.h b/package/uhttpd/src/uhttpd-utils.h
index 1b18265417..95535d6fe7 100644
--- a/package/uhttpd/src/uhttpd-utils.h
+++ b/package/uhttpd/src/uhttpd-utils.h
@@ -101,4 +101,9 @@ struct client * uh_client_add(int sock, struct listener *serv);
 struct client * uh_client_lookup(int sock);
 void uh_client_remove(int sock);
 
+#ifdef HAVE_CGI
+struct interpreter * uh_interpreter_add(const char *extn, const char *path);
+struct interpreter * uh_interpreter_lookup(const char *path);
+#endif
+
 #endif
diff --git a/package/uhttpd/src/uhttpd.c b/package/uhttpd/src/uhttpd.c
index 764ff7d570..6406e459ad 100644
--- a/package/uhttpd/src/uhttpd.c
+++ b/package/uhttpd/src/uhttpd.c
@@ -51,8 +51,8 @@ static void uh_config_parse(struct config *conf)
 {
 	FILE *c;
 	char line[512];
-	char *user = NULL;
-	char *pass = NULL;
+	char *col1 = NULL;
+	char *col2 = NULL;
 	char *eol  = NULL;
 
 	const char *path = conf->file ? conf->file : "/etc/httpd.conf";
@@ -66,35 +66,51 @@ static void uh_config_parse(struct config *conf)
 		{
 			if( (line[0] == '/') && (strchr(line, ':') != NULL) )
 			{
-				if( !(user = strchr(line, ':')) || (*user++ = 0) ||
-				    !(pass = strchr(user, ':')) || (*pass++ = 0) ||
-					!(eol = strchr(pass, '\n')) || (*eol++  = 0) )
+				if( !(col1 = strchr(line, ':')) || (*col1++ = 0) ||
+				    !(col2 = strchr(col1, ':')) || (*col2++ = 0) ||
+					!(eol = strchr(col2, '\n')) || (*eol++  = 0) )
 						continue;
 
-				if( !uh_auth_add(line, user, pass) )
+				if( !uh_auth_add(line, col1, col2) )
 				{
 					fprintf(stderr,
 						"Notice: No password set for user %s, ignoring "
-						"authentication on %s\n", user, line
+						"authentication on %s\n", col1, line
 					);
 				}
 			}
 			else if( !strncmp(line, "I:", 2) )
 			{
-				if( !(user = strchr(line, ':')) || (*user++ = 0) ||
-				    !(eol = strchr(user, '\n')) || (*eol++  = 0) )
+				if( !(col1 = strchr(line, ':')) || (*col1++ = 0) ||
+				    !(eol = strchr(col1, '\n')) || (*eol++  = 0) )
 				    	continue;
 
-				conf->index_file = strdup(user);
+				conf->index_file = strdup(col1);
 			}
 			else if( !strncmp(line, "E404:", 5) )
 			{
-				if( !(user = strchr(line, ':')) || (*user++ = 0) ||
-				    !(eol = strchr(user, '\n')) || (*eol++  = 0) )
+				if( !(col1 = strchr(line, ':')) || (*col1++ = 0) ||
+				    !(eol = strchr(col1, '\n')) || (*eol++  = 0) )
 						continue;
 
-				conf->error_handler = strdup(user);
+				conf->error_handler = strdup(col1);
 			}
+#ifdef HAVE_CGI
+			else if( (line[0] == '.') && (strchr(line, ':') != NULL) )
+			{
+				if( !(col1 = strchr(line, ':')) || (*col1++ = 0) ||
+				    !(eol = strchr(col1, '\n')) || (*eol++  = 0) )
+						continue;
+
+				if( !uh_interpreter_add(line, col1) )
+				{
+					fprintf(stderr,
+						"Unable to add interpreter %s for extension %s: "
+						"Out of memory\n", col1, line
+					);
+				}
+			}
+#endif
 		}
 
 		fclose(c);
@@ -162,11 +178,7 @@ static int uh_socket_bind(
 		/* add listener to global list */
 		if( ! (l = uh_listener_add(sock, conf)) )
 		{
-			fprintf(stderr,
-				"uh_listener_add(): Can not create more than "
-				"%i listen sockets\n", UH_LIMIT_LISTENERS
-			);
-
+			fprintf(stderr, "uh_listener_add(): Failed to allocate memory\n");
 			goto error;
 		}
 
@@ -397,6 +409,7 @@ static struct http_request * uh_http_header_recv(struct client *cl)
 	return NULL;
 }
 
+#if defined(HAVE_LUA) || defined(HAVE_CGI)
 static int uh_path_match(const char *prefix, const char *url)
 {
 	if( (strstr(url, prefix) == url) &&
@@ -409,23 +422,193 @@ static int uh_path_match(const char *prefix, const char *url)
 
 	return 0;
 }
+#endif
 
+static void uh_dispatch_request(
+	struct client *cl, struct http_request *req, struct path_info *pin
+) {
+#ifdef HAVE_CGI
+	struct interpreter *ipr = NULL;
 
-int main (int argc, char **argv)
+	if( uh_path_match(cl->server->conf->cgi_prefix, pin->name) ||
+		(ipr = uh_interpreter_lookup(pin->phys)) )
+	{
+		uh_cgi_request(cl, req, pin, ipr);
+	}
+	else
+#endif
+	{
+		uh_file_request(cl, req, pin);
+	}
+}
+
+static void uh_mainloop(struct config *conf, fd_set serv_fds, int max_fd)
 {
+	/* master file descriptor list */
+	fd_set used_fds, read_fds;
+
+	/* working structs */
+	struct http_request *req;
+	struct path_info *pin;
+	struct client *cl;
+
+	/* maximum file descriptor number */
+	int new_fd, cur_fd = 0;
+
+	/* clear the master and temp sets */
+	FD_ZERO(&used_fds);
+	FD_ZERO(&read_fds);
+
+	/* backup server descriptor set */
+	used_fds = serv_fds;
+
+	/* loop */
+	while(run)
+	{
+		/* create a working copy of the used fd set */
+		read_fds = used_fds;
+
+		/* sleep until socket activity */
+		if( select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1 )
+		{
+			perror("select()");
+			exit(1);
+		}
+
+		/* run through the existing connections looking for data to be read */
+		for( cur_fd = 0; cur_fd <= max_fd; cur_fd++ )
+		{
+			/* is a socket managed by us */
+			if( FD_ISSET(cur_fd, &read_fds) )
+			{
+				/* is one of our listen sockets */
+				if( FD_ISSET(cur_fd, &serv_fds) )
+				{
+					/* handle new connections */
+					if( (new_fd = accept(cur_fd, NULL, 0)) != -1 )
+					{
+						/* add to global client list */
+						if( (cl = uh_client_add(new_fd, uh_listener_lookup(cur_fd))) != NULL )
+						{
+#ifdef HAVE_TLS
+							/* setup client tls context */
+							if( conf->tls )
+								conf->tls_accept(cl);
+#endif
+
+							/* add client socket to global fdset */
+							FD_SET(new_fd, &used_fds);
+							fd_cloexec(new_fd);
+							max_fd = max(max_fd, new_fd);
+						}
+
+						/* insufficient resources */
+						else
+						{
+							fprintf(stderr,
+								"uh_client_add(): Cannot allocate memory\n");
+
+							close(new_fd);
+						}
+					}
+				}
+
+				/* is a client socket */
+				else
+				{
+					if( ! (cl = uh_client_lookup(cur_fd)) )
+					{
+						/* this should not happen! */
+						fprintf(stderr,
+							"uh_client_lookup(): No entry for fd %i!\n",
+							cur_fd);
+
+						goto cleanup;
+					}
+
+					/* parse message header */
+					if( (req = uh_http_header_recv(cl)) != NULL )
+					{
+						/* RFC1918 filtering required? */
+						if( conf->rfc1918_filter &&
+						    sa_rfc1918(&cl->peeraddr) &&
+						    !sa_rfc1918(&cl->servaddr) )
+						{
+							uh_http_sendhf(cl, 403, "Forbidden",
+								"Rejected request from RFC1918 IP "
+								"to public server address");
+						}
+						else
 #ifdef HAVE_LUA
-	/* Lua runtime */
-	lua_State *L = NULL;
+						/* Lua request? */
+						if( conf->lua_state &&
+						    uh_path_match(conf->lua_prefix, req->url) )
+						{
+							conf->lua_request(cl, req, conf->lua_state);
+						}
+						else
 #endif
+						/* dispatch request */
+						if( (pin = uh_path_lookup(cl, req->url)) != NULL )
+						{
+							/* auth ok? */
+							if( uh_auth_check(cl, req, pin) )
+								uh_dispatch_request(cl, req, pin);
+						}
 
+						/* 404 */
+						else
+						{
+							/* Try to invoke an error handler */
+							pin = uh_path_lookup(cl, conf->error_handler);
+
+							if( pin && uh_auth_check(cl, req, pin) )
+							{
+								req->redirect_status = 404;
+								uh_dispatch_request(cl, req, pin);
+							}
+							else
+							{
+								uh_http_sendhf(cl, 404, "Not Found",
+									"No such file or directory");
+							}
+						}
+					}
+
+#ifdef HAVE_TLS
+					/* free client tls context */
+					if( conf->tls )
+						conf->tls_close(cl);
+#endif
+
+					cleanup:
+
+					/* close client socket */
+					close(cur_fd);
+					FD_CLR(cur_fd, &used_fds);
+
+					/* remove from global client list */
+					uh_client_remove(cur_fd);
+				}
+			}
+		}
+	}
+
+#ifdef HAVE_LUA
+	/* destroy the Lua state */
+	if( conf->lua_state != NULL )
+		conf->lua_close(conf->lua_state);
+#endif
+}
+
+
+int main (int argc, char **argv)
+{
 	/* master file descriptor list */
 	fd_set used_fds, serv_fds, read_fds;
 
 	/* working structs */
 	struct addrinfo hints;
-	struct http_request *req;
-	struct path_info *pin;
-	struct client *cl;
 	struct sigaction sa;
 	struct config conf;
 
@@ -433,7 +616,7 @@ int main (int argc, char **argv)
 	sigset_t ss;
 
 	/* maximum file descriptor number */
-	int new_fd, cur_fd, max_fd = 0;
+	int cur_fd, max_fd = 0;
 
 #ifdef HAVE_TLS
 	int tls = 0;
@@ -525,7 +708,7 @@ int main (int argc, char **argv)
 #endif
 
 	while( (opt = getopt(argc, argv,
-		"fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:x:t:T:")) > 0
+		"fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:x:i:t:T:")) > 0
 	) {
 		switch(opt)
 		{
@@ -658,6 +841,21 @@ int main (int argc, char **argv)
 			case 'x':
 				conf.cgi_prefix = optarg;
 				break;
+
+			/* interpreter */
+			case 'i':
+				if( (optarg[0] == '.') && (port = strchr(optarg, '=')) )
+				{
+					*port++ = 0;
+					uh_interpreter_add(optarg, port);
+				}
+				else
+				{
+					fprintf(stderr, "Error: Invalid interpreter: %s\n",
+						optarg);
+					exit(1);
+				}
+				break;
 #endif
 
 #ifdef HAVE_LUA
@@ -740,6 +938,7 @@ int main (int argc, char **argv)
 #endif
 #ifdef HAVE_CGI
 					"	-x string       URL prefix for CGI handler, default is '/cgi-bin'\n"
+					"	-i .ext=path    Use interpreter at path for files with the given extension\n"
 #endif
 #if defined(HAVE_CGI) || defined(HAVE_LUA)
 					"	-t seconds      CGI and Lua script timeout in seconds, default is 60\n"
@@ -830,7 +1029,7 @@ int main (int argc, char **argv)
 			if( ! conf.lua_prefix )
 				conf.lua_prefix = "/lua";
 
-			L = conf.lua_init(conf.lua_handler);
+			conf.lua_state = conf.lua_init(conf.lua_handler);
 		}
 	}
 #endif
@@ -865,166 +1064,13 @@ int main (int argc, char **argv)
 		}
 	}
 
-	/* backup server descriptor set */
-	used_fds = serv_fds;
-
-	/* loop */
-	while(run)
-	{
-		/* create a working copy of the used fd set */
-		read_fds = used_fds;
-
-		/* sleep until socket activity */
-		if( select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1 )
-		{
-			perror("select()");
-			exit(1);
-		}
-
-		/* run through the existing connections looking for data to be read */
-		for( cur_fd = 0; cur_fd <= max_fd; cur_fd++ )
-		{
-			/* is a socket managed by us */
-			if( FD_ISSET(cur_fd, &read_fds) )
-			{
-				/* is one of our listen sockets */
-				if( FD_ISSET(cur_fd, &serv_fds) )
-				{
-					/* handle new connections */
-					if( (new_fd = accept(cur_fd, NULL, 0)) != -1 )
-					{
-						/* add to global client list */
-						if( (cl = uh_client_add(new_fd, uh_listener_lookup(cur_fd))) != NULL )
-						{
-#ifdef HAVE_TLS
-							/* setup client tls context */
-							if( conf.tls )
-								conf.tls_accept(cl);
-#endif
-
-							/* add client socket to global fdset */
-							FD_SET(new_fd, &used_fds);
-							fd_cloexec(new_fd);
-							max_fd = max(max_fd, new_fd);
-						}
-
-						/* insufficient resources */
-						else
-						{
-							fprintf(stderr,
-								"uh_client_add(): Can not manage more than "
-								"%i client sockets, connection dropped\n",
-								UH_LIMIT_CLIENTS
-							);
-
-							close(new_fd);
-						}
-					}
-				}
-
-				/* is a client socket */
-				else
-				{
-					if( ! (cl = uh_client_lookup(cur_fd)) )
-					{
-						/* this should not happen! */
-						fprintf(stderr,
-							"uh_client_lookup(): No entry for fd %i!\n",
-							cur_fd);
-
-						goto cleanup;
-					}
-
-					/* parse message header */
-					if( (req = uh_http_header_recv(cl)) != NULL )
-					{
-						/* RFC1918 filtering required? */
-						if( conf.rfc1918_filter && sa_rfc1918(&cl->peeraddr) &&
-						    !sa_rfc1918(&cl->servaddr) )
-						{
-							uh_http_sendhf(cl, 403, "Forbidden",
-								"Rejected request from RFC1918 IP to public server address");
-						}
-						else
-#ifdef HAVE_LUA
-						/* Lua request? */
-						if( L && uh_path_match(conf.lua_prefix, req->url) )
-						{
-							conf.lua_request(cl, req, L);
-						}
-						else
-#endif
-						/* dispatch request */
-						if( (pin = uh_path_lookup(cl, req->url)) != NULL )
-						{
-							/* auth ok? */
-							if( uh_auth_check(cl, req, pin) )
-							{
-#ifdef HAVE_CGI
-								if( uh_path_match(conf.cgi_prefix, pin->name) )
-								{
-									uh_cgi_request(cl, req, pin);
-								}
-								else
-#endif
-								{
-									uh_file_request(cl, req, pin);
-								}
-							}
-						}
-
-						/* 404 */
-						else
-						{
-							/* Try to invoke an error handler */
-							pin = uh_path_lookup(cl, conf.error_handler);
-
-							if( pin && uh_auth_check(cl, req, pin) )
-							{
-								req->redirect_status = 404;
-
-#ifdef HAVE_CGI
-								if( uh_path_match(conf.cgi_prefix, pin->name) )
-								{
-									uh_cgi_request(cl, req, pin);
-								}
-								else
-#endif
-								{
-									uh_file_request(cl, req, pin);
-								}
-							}
-							else
-							{
-								uh_http_sendhf(cl, 404, "Not Found",
-									"No such file or directory");
-							}
-						}
-					}
-
-#ifdef HAVE_TLS
-					/* free client tls context */
-					if( conf.tls )
-						conf.tls_close(cl);
-#endif
-
-					cleanup:
-
-					/* close client socket */
-					close(cur_fd);
-					FD_CLR(cur_fd, &used_fds);
-
-					/* remove from global client list */
-					uh_client_remove(cur_fd);
-				}
-			}
-		}
-	}
+	/* server main loop */
+	uh_mainloop(&conf, serv_fds, max_fd);
 
 #ifdef HAVE_LUA
 	/* destroy the Lua state */
-	if( L != NULL )
-		conf.lua_close(L);
+	if( conf.lua_state != NULL )
+		conf.lua_close(conf.lua_state);
 #endif
 
 	return 0;
diff --git a/package/uhttpd/src/uhttpd.h b/package/uhttpd/src/uhttpd.h
index fd2176ebdd..78cca7b3b2 100644
--- a/package/uhttpd/src/uhttpd.h
+++ b/package/uhttpd/src/uhttpd.h
@@ -48,9 +48,7 @@
 #define UH_LIMIT_MSGHEAD	4096
 #define UH_LIMIT_HEADERS	64
 
-#define UH_LIMIT_LISTENERS	16
 #define UH_LIMIT_CLIENTS	64
-#define UH_LIMIT_AUTHREALMS	8
 
 #define UH_HTTP_MSG_GET		0
 #define UH_HTTP_MSG_HEAD	1
@@ -58,6 +56,7 @@
 
 struct listener;
 struct client;
+struct interpreter;
 struct http_request;
 
 struct config {
@@ -76,6 +75,7 @@ struct config {
 #ifdef HAVE_LUA
 	char *lua_prefix;
 	char *lua_handler;
+	lua_State *lua_state;
 	lua_State * (*lua_init) (const char *handler);
 	void (*lua_close) (lua_State *L);
 	void (*lua_request) (struct client *cl, struct http_request *req, lua_State *L);
@@ -105,6 +105,7 @@ struct listener {
 #ifdef HAVE_TLS
 	SSL_CTX *tls;
 #endif
+	struct listener *next;
 };
 
 struct client {
@@ -117,12 +118,14 @@ struct client {
 #ifdef HAVE_TLS
 	SSL *tls;
 #endif
+	struct client *next;
 };
 
 struct auth_realm {
 	char path[PATH_MAX];
 	char user[32];
 	char pass[128];
+	struct auth_realm *next;
 };
 
 struct http_request {
@@ -140,5 +143,13 @@ struct http_response {
 	char *headers[UH_LIMIT_HEADERS];
 };
 
+#ifdef HAVE_CGI
+struct interpreter {
+	char path[PATH_MAX];
+	char extn[32];
+	struct interpreter *next;
+};
+#endif
+
 #endif
 
-- 
cgit v1.2.3