diff options
| author | jow <jow@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-05-28 00:52:24 +0000 | 
|---|---|---|
| committer | jow <jow@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-05-28 00:52:24 +0000 | 
| commit | 8b4e17bfbc92205fbeaeffee3783e5eceb65d0bf (patch) | |
| tree | 4d207828bf9f50659136b8e931995b9689fc8a4b | |
| parent | dfea9499495c913e17f5789dff1a1581c85fb090 (diff) | |
[package] uhttpd:
	- rewrite large parts of the server, use uloop event driven structure
	- support concurrent requests and make the upper limit configurable
	- implement initial version of HTTP-to-ubus JSON proxy and session.* namespace
	- add compile time support for debug information
	- code style changes
	- bump package revision
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@31931 3c298f89-4303-0410-b956-a3cf2f4a3e73
| -rw-r--r-- | package/uhttpd/Makefile | 55 | ||||
| -rw-r--r-- | package/uhttpd/files/uhttpd.config | 7 | ||||
| -rwxr-xr-x | package/uhttpd/files/uhttpd.init | 1 | ||||
| -rw-r--r-- | package/uhttpd/src/Makefile | 15 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-cgi.c | 792 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-cgi.h | 20 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-file.c | 143 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-file.h | 4 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-lua.c | 608 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-lua.h | 17 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-tls.c | 244 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-tls.h | 8 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-ubus.c | 957 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-ubus.h | 70 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-utils.c | 250 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd-utils.h | 28 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd.c | 582 | ||||
| -rw-r--r-- | package/uhttpd/src/uhttpd.h | 89 | 
18 files changed, 2567 insertions, 1323 deletions
diff --git a/package/uhttpd/Makefile b/package/uhttpd/Makefile index 0331470bf..f30d6cabd 100644 --- a/package/uhttpd/Makefile +++ b/package/uhttpd/Makefile @@ -8,14 +8,16 @@  include $(TOPDIR)/rules.mk  PKG_NAME:=uhttpd -PKG_RELEASE:=32 +PKG_RELEASE:=33  PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)  PKG_CONFIG_DEPENDS := \ +	CONFIG_PACKAGE_uhttpd_debug \  	CONFIG_PACKAGE_uhttpd-mod-lua \  	CONFIG_PACKAGE_uhttpd-mod-tls \  	CONFIG_PACKAGE_uhttpd-mod-tls_cyassl \ -	CONFIG_PACKAGE_uhttpd-mod-tls_openssl +	CONFIG_PACKAGE_uhttpd-mod-tls_openssl \ +	CONFIG_PACKAGE_uhttpd-mod-ubus  include $(INCLUDE_DIR)/package.mk @@ -29,7 +31,7 @@ endef  define Package/uhttpd    $(Package/uhttpd/default) -  MENU:=1 +  DEPENDS:=+libubox  endef  define Package/uhttpd/description @@ -38,6 +40,12 @@ define Package/uhttpd/description   HTTP daemon.  endef +define Package/uhttpd/config +  config PACKAGE_uhttpd_debug +    bool "Build with debug messages" +    default n +endef +  define Package/uhttpd-mod-tls    $(Package/uhttpd/default) @@ -50,17 +58,17 @@ define Package/uhttpd-mod-tls/description  endef  define Package/uhttpd-mod-tls/config -        choice -                depends on PACKAGE_uhttpd-mod-tls -                prompt "TLS Provider" -                default PACKAGE_uhttpd-mod-tls_cyassl +  choice +    depends on PACKAGE_uhttpd-mod-tls +    prompt "TLS Provider" +    default PACKAGE_uhttpd-mod-tls_cyassl -                config PACKAGE_uhttpd-mod-tls_cyassl -                        bool "CyaSSL" +    config PACKAGE_uhttpd-mod-tls_cyassl +      bool "CyaSSL" -                config PACKAGE_uhttpd-mod-tls_openssl -                        bool "OpenSSL" -        endchoice +    config PACKAGE_uhttpd-mod-tls_openssl +      bool "OpenSSL" +  endchoice  endef  UHTTPD_TLS:= @@ -91,12 +99,25 @@ define Package/uhttpd-mod-lua/description  endef -TARGET_CFLAGS += $(TLS_CFLAGS) -TARGET_LDFLAGS += -Wl,-rpath-link=$(STAGING_DIR)/usr/lib +define Package/uhttpd-mod-ubus +  $(Package/uhttpd/default) +  TITLE+= (ubus plugin) +  DEPENDS:=uhttpd +libubus +libblobmsg-json +endef + +define Package/uhttpd-mod-ubus/description + The ubus plugin adds a HTTP/JSON RPC proxy for ubus and publishes the + session.* namespace and procedures. +endef + + +TARGET_CFLAGS += $(TLS_CFLAGS) $(if $(CONFIG_PACKAGE_uhttpd_debug),-DDEBUG) -ggdb3 +TARGET_LDFLAGS += -lubox -Wl,-rpath-link=$(STAGING_DIR)/usr/lib  MAKE_VARS += \  	FPIC="$(FPIC)" \  	LUA_SUPPORT="$(if $(CONFIG_PACKAGE_uhttpd-mod-lua),1)" \  	TLS_SUPPORT="$(if $(CONFIG_PACKAGE_uhttpd-mod-tls),1)" \ +	UBUS_SUPPORT="$(if $(CONFIG_PACKAGE_uhttpd-mod-ubus),1)" \  	UHTTPD_TLS="$(UHTTPD_TLS)" \  	TLS_CFLAGS="$(TLS_CFLAGS)" \  	TLS_LDFLAGS="$(TLS_LDFLAGS)" @@ -131,7 +152,13 @@ define Package/uhttpd-mod-lua/install  	$(INSTALL_BIN) $(PKG_BUILD_DIR)/uhttpd_lua.so $(1)/usr/lib/  endef +define Package/uhttpd-mod-ubus/install +	$(INSTALL_DIR) $(1)/usr/lib +	$(INSTALL_BIN) $(PKG_BUILD_DIR)/uhttpd_ubus.so $(1)/usr/lib/ +endef +  $(eval $(call BuildPackage,uhttpd))  $(eval $(call BuildPackage,uhttpd-mod-tls))  $(eval $(call BuildPackage,uhttpd-mod-lua)) +$(eval $(call BuildPackage,uhttpd-mod-ubus)) diff --git a/package/uhttpd/files/uhttpd.config b/package/uhttpd/files/uhttpd.config index 08ca5e5e0..b33411e97 100644 --- a/package/uhttpd/files/uhttpd.config +++ b/package/uhttpd/files/uhttpd.config @@ -17,6 +17,12 @@ config uhttpd main  	# This is a DNS rebinding countermeasure.  	option rfc1918_filter 1 +	# Maximum number of concurrent requests. +	# If this number is exceeded, further requests are +	# queued until the number of running requests drops +	# below the limit again. +	option max_requests 3 +  	# Certificate and private key for HTTPS.  	# If no listen_https addresses are given,  	# the key options are ignored. @@ -81,4 +87,3 @@ config cert px5g  	# Common name  	option commonname	OpenWrt - diff --git a/package/uhttpd/files/uhttpd.init b/package/uhttpd/files/uhttpd.init index d4037a1b9..379a9f5b5 100755 --- a/package/uhttpd/files/uhttpd.init +++ b/package/uhttpd/files/uhttpd.init @@ -72,6 +72,7 @@ start_instance()  	append_arg "$cfg" tcp_keepalive "-A"  	append_arg "$cfg" error_page "-E"  	append_arg "$cfg" index_page "-I" +	append_arg "$cfg" max_requests "-n" 3  	append_bool "$cfg" no_symlinks "-S" 0  	append_bool "$cfg" no_dirlists "-D" 0 diff --git a/package/uhttpd/src/Makefile b/package/uhttpd/src/Makefile index 2b08ec668..98226ed20 100644 --- a/package/uhttpd/src/Makefile +++ b/package/uhttpd/src/Makefile @@ -41,6 +41,10 @@ ifeq ($(LUA_SUPPORT),1)    CFLAGS += -DHAVE_LUA  endif +ifeq ($(UBUS_SUPPORT),1) +  CFLAGS += -DHAVE_UBUS +endif +  world: compile @@ -66,10 +70,19 @@ ifeq ($(TLS_SUPPORT),1)  			-o $(TLSLIB) uhttpd-tls.c  endif +ifeq ($(UBUS_SUPPORT),1) +  UBUSLIB := uhttpd_ubus.so + +  $(UBUSLIB): uhttpd-ubus.c +		$(CC) $(CFLAGS) $(LDFLAGS) $(FPIC) \ +			-shared -lubus -ljson -lblobmsg_json \ +			-o $(UBUSLIB) uhttpd-ubus.c +endif +  %.o: %.c  	$(CC) $(CFLAGS) -c -o $@ $< -compile: $(OBJ) $(TLSLIB) $(LUALIB) +compile: $(OBJ) $(TLSLIB) $(LUALIB) $(UBUSLIB)  	$(CC) -o uhttpd $(LDFLAGS) $(OBJ) $(LIB)  clean: diff --git a/package/uhttpd/src/uhttpd-cgi.c b/package/uhttpd/src/uhttpd-cgi.c index f85212569..2f7ea7afa 100644 --- a/package/uhttpd/src/uhttpd-cgi.c +++ b/package/uhttpd/src/uhttpd-cgi.c @@ -1,7 +1,7 @@  /*   * uhttpd - Tiny single-threaded httpd - CGI handler   * - *   Copyright (C) 2010-2011 Jo-Philipp Wich <xm@subsignal.org> + *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>   *   *  Licensed under the Apache License, Version 2.0 (the "License");   *  you may not use this file except in compliance with the License. @@ -20,25 +20,24 @@  #include "uhttpd-utils.h"  #include "uhttpd-cgi.h" -static struct http_response * uh_cgi_header_parse(char *buf, int len, int *off) + +static bool +uh_cgi_header_parse(struct http_response *res, char *buf, int len, int *off)  {  	char *bufptr = NULL;  	char *hdrname = NULL;  	int hdrcount = 0;  	int pos = 0; -	static struct http_response res; - -  	if (((bufptr = strfind(buf, len, "\r\n\r\n", 4)) != NULL) ||  	    ((bufptr = strfind(buf, len, "\n\n", 2)) != NULL))  	{  		*off = (int)(bufptr - buf) + ((bufptr[0] == '\r') ? 4 : 2); -		memset(&res, 0, sizeof(res)); +		memset(res, 0, sizeof(*res)); -		res.statuscode = 200; -		res.statusmsg  = "OK"; +		res->statuscode = 200; +		res->statusmsg  = "OK";  		bufptr = &buf[0]; @@ -70,25 +69,30 @@ static struct http_response * uh_cgi_header_parse(char *buf, int len, int *off)  				if (pos <= len)  				{ -					if ((hdrcount + 1) < array_size(res.headers)) +					if ((hdrcount+1) < array_size(res->headers))  					{  						if (!strcasecmp(hdrname, "Status"))  						{ -							res.statuscode = atoi(bufptr); +							res->statuscode = atoi(bufptr); -							if (res.statuscode < 100) -								res.statuscode = 200; +							if (res->statuscode < 100) +								res->statuscode = 200;  							if (((bufptr = strchr(bufptr, ' ')) != NULL) &&  								(&bufptr[1] != 0))  							{ -								res.statusmsg = &bufptr[1]; +								res->statusmsg = &bufptr[1];  							} + +							D("CGI: HTTP/1.x %03d %s\n", +							  res->statuscode, res->statusmsg);  						}  						else  						{ -							res.headers[hdrcount++] = hdrname; -							res.headers[hdrcount++] = bufptr; +							D("CGI: HTTP: %s: %s\n", hdrname, bufptr); + +							res->headers[hdrcount++] = hdrname; +							res->headers[hdrcount++] = bufptr;  						}  						bufptr = &buf[pos]; @@ -96,16 +100,16 @@ static struct http_response * uh_cgi_header_parse(char *buf, int len, int *off)  					}  					else  					{ -						return NULL; +						return false;  					}  				}  			}  		} -		return &res; +		return true;  	} -	return NULL; +	return false;  }  static char * uh_cgi_header_lookup(struct http_response *res, @@ -122,485 +126,443 @@ static char * uh_cgi_header_lookup(struct http_response *res,  	return NULL;  } -static int uh_cgi_error_500(struct client *cl, struct http_request *req, -							const char *message) +static void uh_cgi_shutdown(struct uh_cgi_state *state)  { -	if (uh_http_sendf(cl, NULL, -					  "HTTP/%.1f 500 Internal Server Error\r\n" -					  "Content-Type: text/plain\r\n%s\r\n", -					  req->version, -					  (req->version > 1.0) -					      ? "Transfer-Encoding: chunked\r\n" : "") >= 0) -	{ -		return uh_http_send(cl, req, message, -1); -	} - -	return -1; +	close(state->rfd); +	close(state->wfd); +	free(state);  } - -void uh_cgi_request(struct client *cl, struct http_request *req, -					struct path_info *pi, struct interpreter *ip) +static bool uh_cgi_socket_cb(struct client *cl)  { -	int i, hdroff, bufoff, rv; -	int hdrlen = 0; -	int buflen = 0; -	int fd_max = 0; -	int content_length = 0; -	int header_sent = 0; +	int i, len, hdroff; +	char buf[UH_LIMIT_MSGHEAD]; -	int rfd[2] = { 0, 0 }; -	int wfd[2] = { 0, 0 }; +	struct uh_cgi_state *state = (struct uh_cgi_state *)cl->priv; +	struct http_response *res = &state->cl->response; +	struct http_request *req = &state->cl->request; -	char buf[UH_LIMIT_MSGHEAD]; -	char hdr[UH_LIMIT_MSGHEAD]; +	/* there is unread post data waiting */ +	while (state->content_length > 0) +	{ +		/* remaining data in http head buffer ... */ +		if (state->cl->httpbuf.len > 0) +		{ +			len = min(state->content_length, state->cl->httpbuf.len); -	pid_t child; +			D("CGI: Child(%d) feed %d HTTP buffer bytes\n", +			  state->cl->proc.pid, len); -	fd_set reader; -	fd_set writer; +			memcpy(buf, state->cl->httpbuf.ptr, len); -	sigset_t ss; +			state->cl->httpbuf.len -= len; +			state->cl->httpbuf.ptr +=len; +		} -	struct sigaction sa; -	struct timeval timeout; -	struct http_response *res; +		/* read it from socket ... */ +		else +		{ +			len = uh_tcp_recv(state->cl, buf, +							  min(state->content_length, sizeof(buf))); +			if ((len < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) +				break; -	/* spawn pipes for me->child, child->me */ -	if ((pipe(rfd) < 0) || (pipe(wfd) < 0)) -	{ -		uh_http_sendhf(cl, 500, "Internal Server Error", -					   "Failed to create pipe: %s", strerror(errno)); +			D("CGI: Child(%d) feed %d/%d TCP socket bytes\n", +			  state->cl->proc.pid, len, +			  min(state->content_length, sizeof(buf))); +		} -		if (rfd[0] > 0) close(rfd[0]); -		if (rfd[1] > 0) close(rfd[1]); -		if (wfd[0] > 0) close(wfd[0]); -		if (wfd[1] > 0) close(wfd[1]); +		if (len) +			state->content_length -= len; +		else +			state->content_length = 0; -		return; +		/* ... write to CGI process */ +		len = uh_raw_send(state->wfd, buf, len, +						  cl->server->conf->script_timeout);  	} -	/* fork off child process */ -	switch ((child = fork())) +	/* try to read data from child */ +	while ((len = uh_raw_recv(state->rfd, buf, sizeof(buf), -1)) > 0)  	{ -		/* oops */ -		case -1: -			uh_http_sendhf(cl, 500, "Internal Server Error", -						   "Failed to fork child: %s", strerror(errno)); -			return; - -		/* exec child */ -		case 0: -			/* unblock signals */ -			sigemptyset(&ss); -			sigprocmask(SIG_SETMASK, &ss, NULL); - -			/* restore SIGTERM */ -			sa.sa_flags = 0; -			sa.sa_handler = SIG_DFL; -			sigemptyset(&sa.sa_mask); -			sigaction(SIGTERM, &sa, NULL); - -			/* close loose pipe ends */ -			close(rfd[0]); -			close(wfd[1]); - -			/* patch stdout and stdin to pipes */ -			dup2(rfd[1], 1); -			dup2(wfd[0], 0); - -			/* avoid leaking our pipe into child-child processes */ -			fd_cloexec(rfd[1]); -			fd_cloexec(wfd[0]); - -			/* check for regular, world-executable file _or_ interpreter */ -			if (((pi->stat.st_mode & S_IFREG) && -			     (pi->stat.st_mode & S_IXOTH)) || (ip != NULL)) +		/* we have not pushed out headers yet, parse input */ +		if (!state->header_sent) +		{ +			/* try to parse header ... */ +			memcpy(state->httpbuf, buf, len); + +			if (uh_cgi_header_parse(res, state->httpbuf, len, &hdroff))  			{ -				/* build environment */ -				clearenv(); +				/* write status */ +				ensure_out(uh_http_sendf(state->cl, NULL, +					"HTTP/%.1f %03d %s\r\n" +					"Connection: close\r\n", +					req->version, res->statuscode, res->statusmsg)); + +				/* add Content-Type if no Location or Content-Type */ +				if (!uh_cgi_header_lookup(res, "Location") && +					!uh_cgi_header_lookup(res, "Content-Type")) +				{ +					ensure_out(uh_http_send(state->cl, NULL, +						"Content-Type: text/plain\r\n", -1)); +				} -				/* common information */ -				setenv("GATEWAY_INTERFACE", "CGI/1.1", 1); -				setenv("SERVER_SOFTWARE", "uHTTPd", 1); -				setenv("PATH", "/sbin:/usr/sbin:/bin:/usr/bin", 1); +				/* if request was HTTP 1.1 we'll respond chunked */ +				if ((req->version > 1.0) && +					!uh_cgi_header_lookup(res, "Transfer-Encoding")) +				{ +					ensure_out(uh_http_send(state->cl, NULL, +						"Transfer-Encoding: chunked\r\n", -1)); +				} -#ifdef HAVE_TLS -				/* https? */ -				if (cl->tls) -					setenv("HTTPS", "on", 1); -#endif +				/* write headers from CGI program */ +				foreach_header(i, res->headers) +				{ +					ensure_out(uh_http_sendf(state->cl, NULL, "%s: %s\r\n", +						res->headers[i], res->headers[i+1])); +				} + +				/* terminate header */ +				ensure_out(uh_http_send(state->cl, NULL, "\r\n", -1)); -				/* addresses */ -				setenv("SERVER_NAME", sa_straddr(&cl->servaddr), 1); -				setenv("SERVER_ADDR", sa_straddr(&cl->servaddr), 1); -				setenv("SERVER_PORT", sa_strport(&cl->servaddr), 1); -				setenv("REMOTE_HOST", sa_straddr(&cl->peeraddr), 1); -				setenv("REMOTE_ADDR", sa_straddr(&cl->peeraddr), 1); -				setenv("REMOTE_PORT", sa_strport(&cl->peeraddr), 1); - -				/* path information */ -				setenv("SCRIPT_NAME", pi->name, 1); -				setenv("SCRIPT_FILENAME", pi->phys, 1); -				setenv("DOCUMENT_ROOT", pi->root, 1); -				setenv("QUERY_STRING", pi->query ? pi->query : "", 1); - -				if (pi->info) -					setenv("PATH_INFO", pi->info, 1); - -				/* REDIRECT_STATUS, php-cgi wants it */ -				switch (req->redirect_status) +				state->header_sent = true; + +				/* push out remaining head buffer */ +				if (hdroff < len)  				{ -					case 404: -						setenv("REDIRECT_STATUS", "404", 1); -						break; +					D("CGI: Child(%d) relaying %d rest bytes\n", +					  state->cl->proc.pid, len - hdroff); -					default: -						setenv("REDIRECT_STATUS", "200", 1); -						break; +					ensure_out(uh_http_send(state->cl, req, +											&buf[hdroff], len - hdroff));  				} +			} -				/* http version */ -				if (req->version > 1.0) -					setenv("SERVER_PROTOCOL", "HTTP/1.1", 1); -				else -					setenv("SERVER_PROTOCOL", "HTTP/1.0", 1); +			/* ... failed and head buffer exceeded */ +			else +			{ +				/* I would do this ... +				 * +				 *    uh_cgi_error_500(cl, req, +				 *        "The CGI program generated an " +				 *        "invalid response:\n\n"); +				 * +				 * ... but in order to stay as compatible as possible, +				 * treat whatever we got as text/plain response and +				 * build the required headers here. +				 */ + +				ensure_out(uh_http_sendf(state->cl, NULL, +										 "HTTP/%.1f 200 OK\r\n" +										 "Content-Type: text/plain\r\n" +										 "%s\r\n", +										 req->version, (req->version > 1.0) +										 ? "Transfer-Encoding: chunked\r\n" : "" +				)); + +				state->header_sent = true; + +				D("CGI: Child(%d) relaying %d invalid bytes\n", +				  state->cl->proc.pid, len); + +				ensure_out(uh_http_send(state->cl, req, buf, len)); +			} +		} +		else +		{ +			/* headers complete, pass through buffer to socket */ +			D("CGI: Child(%d) relaying %d normal bytes\n", +			  state->cl->proc.pid, len); -				/* request method */ -				switch (req->method) -				{ -					case UH_HTTP_MSG_GET: -						setenv("REQUEST_METHOD", "GET", 1); -						break; +			ensure_out(uh_http_send(state->cl, req, buf, len)); +		} +	} -					case UH_HTTP_MSG_HEAD: -						setenv("REQUEST_METHOD", "HEAD", 1); -						break; +	/* child has been marked dead by timeout or child handler, bail out */ +	if (false && cl->dead) +	{ +		D("CGI: Child(%d) is marked dead, returning\n", state->cl->proc.pid); +		goto out; +	} -					case UH_HTTP_MSG_POST: -						setenv("REQUEST_METHOD", "POST", 1); -						break; -				} +	if ((len == 0) || +		((errno != EAGAIN) && (errno != EWOULDBLOCK) && (len == -1))) +	{ +		D("CGI: Child(%d) presumed dead [%s]\n", +		  state->cl->proc.pid, strerror(errno)); -				/* request url */ -				setenv("REQUEST_URI", req->url, 1); +		goto out; +	} -				/* remote user */ -				if (req->realm) -					setenv("REMOTE_USER", req->realm->user, 1); +	return true; -				/* request message headers */ -				foreach_header(i, req->headers) -				{ -					if (!strcasecmp(req->headers[i], "Accept")) -						setenv("HTTP_ACCEPT", req->headers[i+1], 1); +out: +	if (!state->header_sent) +	{ +		if (state->cl->timeout.pending) +			uh_http_sendhf(state->cl, 502, "Bad Gateway", +						   "The CGI process did not produce any response\n"); +		else +			uh_http_sendhf(state->cl, 504, "Gateway Timeout", +						   "The CGI process took too long to produce a " +						   "response\n"); +	} +	else +	{ +		uh_http_send(state->cl, req, "", 0); +	} -					else if (!strcasecmp(req->headers[i], "Accept-Charset")) -						setenv("HTTP_ACCEPT_CHARSET", req->headers[i+1], 1); +	uh_cgi_shutdown(state); +	return false; +} -					else if (!strcasecmp(req->headers[i], "Accept-Encoding")) -						setenv("HTTP_ACCEPT_ENCODING", req->headers[i+1], 1); +bool uh_cgi_request(struct client *cl, struct path_info *pi, +					struct interpreter *ip) +{ +	int i; -					else if (!strcasecmp(req->headers[i], "Accept-Language")) -						setenv("HTTP_ACCEPT_LANGUAGE", req->headers[i+1], 1); +	int rfd[2] = { 0, 0 }; +	int wfd[2] = { 0, 0 }; -					else if (!strcasecmp(req->headers[i], "Authorization")) -						setenv("HTTP_AUTHORIZATION", req->headers[i+1], 1); +	pid_t child; -					else if (!strcasecmp(req->headers[i], "Connection")) -						setenv("HTTP_CONNECTION", req->headers[i+1], 1); +	struct uh_cgi_state *state; +	struct http_request *req = &cl->request; -					else if (!strcasecmp(req->headers[i], "Cookie")) -						setenv("HTTP_COOKIE", req->headers[i+1], 1); +	/* allocate state */ +	if (!(state = malloc(sizeof(*state)))) +	{ +		uh_http_sendhf(cl, 500, "Internal Server Error", "Out of memory"); +		return false; +	} -					else if (!strcasecmp(req->headers[i], "Host")) -						setenv("HTTP_HOST", req->headers[i+1], 1); +	/* spawn pipes for me->child, child->me */ +	if ((pipe(rfd) < 0) || (pipe(wfd) < 0)) +	{ +		if (rfd[0] > 0) close(rfd[0]); +		if (rfd[1] > 0) close(rfd[1]); +		if (wfd[0] > 0) close(wfd[0]); +		if (wfd[1] > 0) close(wfd[1]); -					else if (!strcasecmp(req->headers[i], "Referer")) -						setenv("HTTP_REFERER", req->headers[i+1], 1); +		uh_http_sendhf(cl, 500, "Internal Server Error", +						"Failed to create pipe: %s\n", strerror(errno)); + +		return false; +	} -					else if (!strcasecmp(req->headers[i], "User-Agent")) -						setenv("HTTP_USER_AGENT", req->headers[i+1], 1); +	/* fork off child process */ +	switch ((child = fork())) +	{ +	/* oops */ +	case -1: +		uh_http_sendhf(cl, 500, "Internal Server Error", +						"Failed to fork child: %s\n", strerror(errno)); -					else if (!strcasecmp(req->headers[i], "Content-Type")) -						setenv("CONTENT_TYPE", req->headers[i+1], 1); +		return false; -					else if (!strcasecmp(req->headers[i], "Content-Length")) -						setenv("CONTENT_LENGTH", req->headers[i+1], 1); -				} +	/* exec child */ +	case 0: +#ifdef DEBUG +		sleep(atoi(getenv("UHTTPD_SLEEP_ON_FORK") ?: "0")); +#endif +		/* close loose pipe ends */ +		close(rfd[0]); +		close(wfd[1]); -				/* execute child code ... */ -				if (chdir(pi->root)) -					perror("chdir()"); +		/* patch stdout and stdin to pipes */ +		dup2(rfd[1], 1); +		dup2(wfd[0], 0); -				if (ip != NULL) -					execl(ip->path, ip->path, pi->phys, NULL); -				else -					execl(pi->phys, pi->phys, NULL); +		/* avoid leaking our pipe into child-child processes */ +		fd_cloexec(rfd[1]); +		fd_cloexec(wfd[0]); -				/* 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", -					   ip ? ip->path : pi->phys, strerror(errno)); +		/* 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(); + +			/* common information */ +			setenv("GATEWAY_INTERFACE", "CGI/1.1", 1); +			setenv("SERVER_SOFTWARE", "uHTTPd", 1); +			setenv("PATH", "/sbin:/usr/sbin:/bin:/usr/bin", 1); + +#ifdef HAVE_TLS +			/* https? */ +			if (cl->tls) +				setenv("HTTPS", "on", 1); +#endif + +			/* addresses */ +			setenv("SERVER_NAME", sa_straddr(&cl->servaddr), 1); +			setenv("SERVER_ADDR", sa_straddr(&cl->servaddr), 1); +			setenv("SERVER_PORT", sa_strport(&cl->servaddr), 1); +			setenv("REMOTE_HOST", sa_straddr(&cl->peeraddr), 1); +			setenv("REMOTE_ADDR", sa_straddr(&cl->peeraddr), 1); +			setenv("REMOTE_PORT", sa_strport(&cl->peeraddr), 1); + +			/* path information */ +			setenv("SCRIPT_NAME", pi->name, 1); +			setenv("SCRIPT_FILENAME", pi->phys, 1); +			setenv("DOCUMENT_ROOT", pi->root, 1); +			setenv("QUERY_STRING", pi->query ? pi->query : "", 1); + +			if (pi->info) +				setenv("PATH_INFO", pi->info, 1); + +			/* REDIRECT_STATUS, php-cgi wants it */ +			switch (req->redirect_status) +			{ +				case 404: +					setenv("REDIRECT_STATUS", "404", 1); +					break; + +				default: +					setenv("REDIRECT_STATUS", "200", 1); +					break;  			} -			/* 403 */ +			/* http version */ +			if (req->version > 1.0) +				setenv("SERVER_PROTOCOL", "HTTP/1.1", 1);  			else +				setenv("SERVER_PROTOCOL", "HTTP/1.0", 1); + +			/* request method */ +			switch (req->method)  			{ -				printf("Status: 403 Forbidden\r\n\r\n" -					   "Access to this resource is forbidden\n"); -			} +				case UH_HTTP_MSG_GET: +					setenv("REQUEST_METHOD", "GET", 1); +					break; -			close(wfd[0]); -			close(rfd[1]); -			exit(0); +				case UH_HTTP_MSG_HEAD: +					setenv("REQUEST_METHOD", "HEAD", 1); +					break; -			break; +				case UH_HTTP_MSG_POST: +					setenv("REQUEST_METHOD", "POST", 1); +					break; +			} -		/* parent; handle I/O relaying */ -		default: -			/* close unneeded pipe ends */ -			close(rfd[1]); -			close(wfd[0]); +			/* request url */ +			setenv("REQUEST_URI", req->url, 1); -			/* max watch fd */ -			fd_max = max(rfd[0], wfd[1]) + 1; +			/* remote user */ +			if (req->realm) +				setenv("REMOTE_USER", req->realm->user, 1); -			/* find content length */ -			if (req->method == UH_HTTP_MSG_POST) +			/* request message headers */ +			foreach_header(i, req->headers)  			{ -				foreach_header(i, req->headers) -				{ -					if (!strcasecmp(req->headers[i], "Content-Length")) -					{ -						content_length = atoi(req->headers[i+1]); -						break; -					} -				} -			} +				if (!strcasecmp(req->headers[i], "Accept")) +					setenv("HTTP_ACCEPT", req->headers[i+1], 1); +				else if (!strcasecmp(req->headers[i], "Accept-Charset")) +					setenv("HTTP_ACCEPT_CHARSET", req->headers[i+1], 1); -			memset(hdr, 0, sizeof(hdr)); +				else if (!strcasecmp(req->headers[i], "Accept-Encoding")) +					setenv("HTTP_ACCEPT_ENCODING", req->headers[i+1], 1); -			/* I/O loop, watch our pipe ends and dispatch child reads/writes from/to socket */ -			while (1) -			{ -				FD_ZERO(&reader); -				FD_ZERO(&writer); +				else if (!strcasecmp(req->headers[i], "Accept-Language")) +					setenv("HTTP_ACCEPT_LANGUAGE", req->headers[i+1], 1); -				FD_SET(rfd[0], &reader); -				FD_SET(wfd[1], &writer); +				else if (!strcasecmp(req->headers[i], "Authorization")) +					setenv("HTTP_AUTHORIZATION", req->headers[i+1], 1); -				timeout.tv_sec = (header_sent < 1) ? cl->server->conf->script_timeout : 3; -				timeout.tv_usec = 0; +				else if (!strcasecmp(req->headers[i], "Connection")) +					setenv("HTTP_CONNECTION", req->headers[i+1], 1); -				ensure_out(rv = select_intr(fd_max, &reader, -											(content_length > -1) -												? &writer : NULL, -											NULL, &timeout)); +				else if (!strcasecmp(req->headers[i], "Cookie")) +					setenv("HTTP_COOKIE", req->headers[i+1], 1); -				/* timeout */ -				if (rv == 0) -				{ -					ensure_out(kill(child, 0)); -				} +				else if (!strcasecmp(req->headers[i], "Host")) +					setenv("HTTP_HOST", req->headers[i+1], 1); -				/* wait until we can read or write or both */ -				else if (rv > 0) -				{ -					/* ready to write to cgi program */ -					if (FD_ISSET(wfd[1], &writer)) -					{ -						/* there is unread post data waiting */ -						if (content_length > 0) -						{ -							/* read it from socket ... */ -							ensure_out(buflen = uh_tcp_recv(cl, buf, -								min(content_length, sizeof(buf)))); +				else if (!strcasecmp(req->headers[i], "Referer")) +					setenv("HTTP_REFERER", req->headers[i+1], 1); -							if (buflen > 0) -							{ -								/* ... and write it to child's stdin */ -								if (write(wfd[1], buf, buflen) < 0) -									perror("write()"); +				else if (!strcasecmp(req->headers[i], "User-Agent")) +					setenv("HTTP_USER_AGENT", req->headers[i+1], 1); -								content_length -= buflen; -							} +				else if (!strcasecmp(req->headers[i], "Content-Type")) +					setenv("CONTENT_TYPE", req->headers[i+1], 1); -							/* unexpected eof! */ -							else -							{ -								if (write(wfd[1], "", 0) < 0) -									perror("write()"); +				else if (!strcasecmp(req->headers[i], "Content-Length")) +					setenv("CONTENT_LENGTH", req->headers[i+1], 1); +			} -								content_length = 0; -							} -						} -						/* there is no more post data, close pipe to child's stdin */ -						else if (content_length > -1) -						{ -							close(wfd[1]); -							content_length = -1; -						} -					} +			/* execute child code ... */ +			if (chdir(pi->root)) +				perror("chdir()"); -					/* ready to read from cgi program */ -					if (FD_ISSET(rfd[0], &reader)) -					{ -						/* read data from child ... */ -						if ((buflen = read(rfd[0], buf, sizeof(buf))) > 0) -						{ -							/* we have not pushed out headers yet, parse input */ -							if (!header_sent) -							{ -								/* head buffer not full and no end yet */ -								if (hdrlen < sizeof(hdr)) -								{ -									bufoff = min(buflen, sizeof(hdr) - hdrlen); -									memcpy(&hdr[hdrlen], buf, bufoff); -									hdrlen += bufoff; -								} -								else -								{ -									bufoff = 0; -								} - - -								/* try to parse header ... */ -								if ((res = uh_cgi_header_parse(hdr, hdrlen, &hdroff)) != NULL) -								{ -									/* write status */ -									ensure_out(uh_http_sendf(cl, NULL, -										"HTTP/%.1f %03d %s\r\n" -										"Connection: close\r\n", -										req->version, res->statuscode, -										res->statusmsg)); - -									/* add Content-Type if no Location or Content-Type */ -									if( !uh_cgi_header_lookup(res, "Location") && -									    !uh_cgi_header_lookup(res, "Content-Type") -									) { -										ensure_out(uh_http_send(cl, NULL, -											"Content-Type: text/plain\r\n", -1)); -									} - -									/* if request was HTTP 1.1 we'll respond chunked */ -									if( (req->version > 1.0) && -									    !uh_cgi_header_lookup(res, "Transfer-Encoding") -									) { -										ensure_out(uh_http_send(cl, NULL, -											"Transfer-Encoding: chunked\r\n", -1)); -									} - -									/* write headers from CGI program */ -									foreach_header(i, res->headers) -									{ -										ensure_out(uh_http_sendf(cl, NULL, "%s: %s\r\n", -											res->headers[i], res->headers[i+1])); -									} - -									/* terminate header */ -									ensure_out(uh_http_send(cl, NULL, "\r\n", -1)); - -									/* push out remaining head buffer */ -									if (hdroff < hdrlen) -										ensure_out(uh_http_send(cl, req, &hdr[hdroff], hdrlen - hdroff)); -								} - -								/* ... failed and head buffer exceeded */ -								else if (hdrlen >= sizeof(hdr)) -								{ -									ensure_out(uh_cgi_error_500(cl, req, -										"The CGI program generated an invalid response:\n\n")); - -									ensure_out(uh_http_send(cl, req, hdr, hdrlen)); -								} - -								/* ... failed but free buffer space, try again */ -								else -								{ -									continue; -								} - -								/* push out remaining read buffer */ -								if (bufoff < buflen) -									ensure_out(uh_http_send(cl, req, &buf[bufoff], buflen - bufoff)); - -								header_sent = 1; -								continue; -							} +			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", ip ? ip->path : pi->phys, strerror(errno)); +		} -							/* headers complete, pass through buffer to socket */ -							ensure_out(uh_http_send(cl, req, buf, buflen)); -						} +		/* 403 */ +		else +		{ +			printf("Status: 403 Forbidden\r\n\r\n" +				   "Access to this resource is forbidden\n"); +		} -						/* looks like eof from child */ -						else -						{ -							/* cgi script did not output useful stuff at all */ -							if (!header_sent) -							{ -								/* I would do this ... -								 * -								 *    uh_cgi_error_500(cl, req, -								 *        "The CGI program generated an " -								 *        "invalid response:\n\n"); -								 * -								 * ... but in order to stay as compatible as possible, -								 * treat whatever we got as text/plain response and -								 * build the required headers here. -								 */ - -								ensure_out(uh_http_sendf(cl, NULL, -									"HTTP/%.1f 200 OK\r\n" -									"Content-Type: text/plain\r\n" -									"%s\r\n", -										req->version, (req->version > 1.0) -											? "Transfer-Encoding: chunked\r\n" : "" -								)); - -								ensure_out(uh_http_send(cl, req, hdr, hdrlen)); -							} +		close(wfd[0]); +		close(rfd[1]); +		exit(0); -							/* send final chunk if we're in chunked transfer mode */ -							ensure_out(uh_http_send(cl, req, "", 0)); -							break; -						} -					} -				} +		break; -				/* timeout exceeded or interrupted by SIGCHLD */ -				else -				{ -					if ((errno != EINTR) && ! header_sent) -					{ -						ensure_out(uh_http_sendhf(cl, 504, "Gateway Timeout", -							"The CGI script took too long to produce " -							"a response")); -					} +	/* parent; handle I/O relaying */ +	default: +		memset(state, 0, sizeof(*state)); + +		state->cl = cl; +		state->cl->proc.pid = child; + +		/* close unneeded pipe ends */ +		close(rfd[1]); +		close(wfd[0]); -					/* send final chunk if we're in chunked transfer mode */ -					ensure_out(uh_http_send(cl, req, "", 0)); +		D("CGI: Child(%d) created: rfd(%d) wfd(%d)\n", child, rfd[0], wfd[1]); +		state->content_length = cl->httpbuf.len; + +		/* find content length */ +		if (req->method == UH_HTTP_MSG_POST) +		{ +			foreach_header(i, req->headers) +			{ +				if (!strcasecmp(req->headers[i], "Content-Length")) +				{ +					state->content_length = atoi(req->headers[i+1]);  					break;  				}  			} +		} -		out: -			close(rfd[0]); -			close(wfd[1]); +		state->rfd = rfd[0]; +		fd_nonblock(state->rfd); -			if (!kill(child, 0)) -			{ -				kill(child, SIGTERM); -				waitpid(child, NULL, 0); -			} +		state->wfd = wfd[1]; +		fd_nonblock(state->wfd); -			break; +		cl->cb = uh_cgi_socket_cb; +		cl->priv = state; + +		break;  	} + +	return true;  } diff --git a/package/uhttpd/src/uhttpd-cgi.h b/package/uhttpd/src/uhttpd-cgi.h index cb84dae0c..18816bae1 100644 --- a/package/uhttpd/src/uhttpd-cgi.h +++ b/package/uhttpd/src/uhttpd-cgi.h @@ -1,7 +1,7 @@  /*   * uhttpd - Tiny single-threaded httpd - CGI header   * - *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org> + *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>   *   *  Licensed under the Apache License, Version 2.0 (the "License");   *  you may not use this file except in compliance with the License. @@ -24,9 +24,19 @@  #include <sys/types.h>  #include <linux/limits.h> -void uh_cgi_request( -	struct client *cl, struct http_request *req, -	struct path_info *pi, struct interpreter *ip -); +#include <time.h> + + +struct uh_cgi_state { +	int rfd; +	int wfd; +	struct client *cl; +	char httpbuf[UH_LIMIT_MSGHEAD]; +	int content_length; +	bool header_sent; +}; + +bool uh_cgi_request(struct client *cl, 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 0d9a207b1..2e5914a3c 100644 --- a/package/uhttpd/src/uhttpd-file.c +++ b/package/uhttpd/src/uhttpd-file.c @@ -1,7 +1,7 @@  /*   * uhttpd - Tiny single-threaded httpd - Static file handler   * - *   Copyright (C) 2010-2011 Jo-Philipp Wich <xm@subsignal.org> + *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>   *   *  Licensed under the Apache License, Version 2.0 (the "License");   *  you may not use this file except in compliance with the License. @@ -83,22 +83,21 @@ static char * uh_file_unix2date(time_t ts)  	return str;  } -static char * uh_file_header_lookup(struct http_request *req, const char *name) +static char * uh_file_header_lookup(struct client *cl, const char *name)  {  	int i; -	foreach_header(i, req->headers) +	foreach_header(i, cl->request.headers)  	{ -		if (!strcasecmp(req->headers[i], name)) -			return req->headers[i+1]; +		if (!strcasecmp(cl->request.headers[i], name)) +			return cl->request.headers[i+1];  	}  	return NULL;  } -static int 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 stat *s)  {  	ensure_ret(uh_http_sendf(cl, NULL, "Connection: close\r\n")); @@ -112,32 +111,33 @@ static int uh_file_response_ok_hdrs(struct client *cl, struct http_request *req,  	return uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL)));  } -static int uh_file_response_200(struct client *cl, struct http_request *req, -								struct stat *s) +static int uh_file_response_200(struct client *cl, struct stat *s)  { -	ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 200 OK\r\n", req->version)); -	return uh_file_response_ok_hdrs(cl, req, s); +	ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 200 OK\r\n", +							 cl->request.version)); + +	return uh_file_response_ok_hdrs(cl, s);  } -static int uh_file_response_304(struct client *cl, struct http_request *req, -								struct stat *s) +static int uh_file_response_304(struct client *cl, struct stat *s)  { -	ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 304 Not Modified\r\n", req->version)); -	return uh_file_response_ok_hdrs(cl, req, s); +	ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 304 Not Modified\r\n", +							 cl->request.version)); + +	return uh_file_response_ok_hdrs(cl, s);  } -static int uh_file_response_412(struct client *cl, struct http_request *req) +static int uh_file_response_412(struct client *cl)  {  	return uh_http_sendf(cl, NULL, -		"HTTP/%.1f 412 Precondition Failed\r\n" -		"Connection: close\r\n", req->version); +						 "HTTP/%.1f 412 Precondition Failed\r\n" +						 "Connection: close\r\n", cl->request.version);  } -static int uh_file_if_match(struct client *cl, struct http_request *req, -							struct stat *s, int *ok) +static int uh_file_if_match(struct client *cl, struct stat *s, int *ok)  {  	const char *tag = uh_file_mktag(s); -	char *hdr = uh_file_header_lookup(req, "If-Match"); +	char *hdr = uh_file_header_lookup(cl, "If-Match");  	char *p;  	int i; @@ -160,7 +160,7 @@ static int uh_file_if_match(struct client *cl, struct http_request *req,  		}  		*ok = 0; -		ensure_ret(uh_file_response_412(cl, req)); +		ensure_ret(uh_file_response_412(cl));  		return *ok;  	} @@ -168,11 +168,9 @@ static int uh_file_if_match(struct client *cl, struct http_request *req,  	return *ok;  } -static int uh_file_if_modified_since(struct client *cl, -									 struct http_request *req, struct stat *s, -									 int *ok) +static int uh_file_if_modified_since(struct client *cl, struct stat *s, int *ok)  { -	char *hdr = uh_file_header_lookup(req, "If-Modified-Since"); +	char *hdr = uh_file_header_lookup(cl, "If-Modified-Since");  	*ok = 1;  	if (hdr) @@ -180,18 +178,17 @@ static int uh_file_if_modified_since(struct client *cl,  		if (uh_file_date2unix(hdr) >= s->st_mtime)  		{  			*ok = 0; -			ensure_ret(uh_file_response_304(cl, req, s)); +			ensure_ret(uh_file_response_304(cl, s));  		}  	}  	return *ok;  } -static int uh_file_if_none_match(struct client *cl, struct http_request *req, -								 struct stat *s, int *ok) +static int uh_file_if_none_match(struct client *cl, struct stat *s, int *ok)  {  	const char *tag = uh_file_mktag(s); -	char *hdr = uh_file_header_lookup(req, "If-None-Match"); +	char *hdr = uh_file_header_lookup(cl, "If-None-Match");  	char *p;  	int i;  	*ok = 1; @@ -211,14 +208,14 @@ static int uh_file_if_none_match(struct client *cl, struct http_request *req,  			{  				*ok = 0; -				if ((req->method == UH_HTTP_MSG_GET) || -				    (req->method == UH_HTTP_MSG_HEAD)) +				if ((cl->request.method == UH_HTTP_MSG_GET) || +				    (cl->request.method == UH_HTTP_MSG_HEAD))  				{ -					ensure_ret(uh_file_response_304(cl, req, s)); +					ensure_ret(uh_file_response_304(cl, s));  				}  				else  				{ -					ensure_ret(uh_file_response_412(cl, req)); +					ensure_ret(uh_file_response_412(cl));  				}  				break; @@ -229,26 +226,24 @@ static int uh_file_if_none_match(struct client *cl, struct http_request *req,  	return *ok;  } -static int uh_file_if_range(struct client *cl, struct http_request *req, -							struct stat *s, int *ok) +static int uh_file_if_range(struct client *cl, struct stat *s, int *ok)  { -	char *hdr = uh_file_header_lookup(req, "If-Range"); +	char *hdr = uh_file_header_lookup(cl, "If-Range");  	*ok = 1;  	if (hdr)  	{  		*ok = 0; -		ensure_ret(uh_file_response_412(cl, req)); +		ensure_ret(uh_file_response_412(cl));  	}  	return *ok;  } -static int uh_file_if_unmodified_since(struct client *cl, -									   struct http_request *req, struct stat *s, +static int uh_file_if_unmodified_since(struct client *cl, struct stat *s,  									   int *ok)  { -	char *hdr = uh_file_header_lookup(req, "If-Unmodified-Since"); +	char *hdr = uh_file_header_lookup(cl, "If-Unmodified-Since");  	*ok = 1;  	if (hdr) @@ -256,7 +251,7 @@ static int uh_file_if_unmodified_since(struct client *cl,  		if (uh_file_date2unix(hdr) <= s->st_mtime)  		{  			*ok = 0; -			ensure_ret(uh_file_response_412(cl, req)); +			ensure_ret(uh_file_response_412(cl));  		}  	} @@ -269,8 +264,7 @@ static int uh_file_scandir_filter_dir(const struct dirent *e)  	return strcmp(e->d_name, ".") ? 1 : 0;  } -static void uh_file_dirlist(struct client *cl, struct http_request *req, -							struct path_info *pi) +static void uh_file_dirlist(struct client *cl, struct path_info *pi)  {  	int i;  	int count = 0; @@ -279,7 +273,7 @@ static void uh_file_dirlist(struct client *cl, struct http_request *req,  	struct dirent **files = NULL;  	struct stat s; -	ensure_out(uh_http_sendf(cl, req, +	ensure_out(uh_http_sendf(cl, &cl->request,  							 "<html><head><title>Index of %s</title></head>"  							 "<body><h1>Index of %s</h1><hr /><ol>",  							 pi->name, pi->name)); @@ -300,7 +294,7 @@ static void uh_file_dirlist(struct client *cl, struct http_request *req,  			if (!stat(filename, &s) &&  				(s.st_mode & S_IFDIR) && (s.st_mode & S_IXOTH))  			{ -				ensure_out(uh_http_sendf(cl, req, +				ensure_out(uh_http_sendf(cl, &cl->request,  										 "<li><strong><a href='%s%s'>%s</a>/"  										 "</strong><br /><small>modified: %s"  										 "<br />directory - %.02f kbyte<br />" @@ -323,7 +317,7 @@ static void uh_file_dirlist(struct client *cl, struct http_request *req,  			if (!stat(filename, &s) &&  				!(s.st_mode & S_IFDIR) && (s.st_mode & S_IROTH))  			{ -				ensure_out(uh_http_sendf(cl, req, +				ensure_out(uh_http_sendf(cl, &cl->request,  										 "<li><strong><a href='%s%s'>%s</a>"  										 "</strong><br /><small>modified: %s"  										 "<br />%s - %.02f kbyte<br />" @@ -339,8 +333,8 @@ static void uh_file_dirlist(struct client *cl, struct http_request *req,  		}  	} -	ensure_out(uh_http_sendf(cl, req, "</ol><hr /></body></html>")); -	ensure_out(uh_http_sendf(cl, req, "")); +	ensure_out(uh_http_sendf(cl, &cl->request, "</ol><hr /></body></html>")); +	ensure_out(uh_http_sendf(cl, &cl->request, ""));  out:  	if (files) @@ -353,7 +347,7 @@ out:  } -void uh_file_request(struct client *cl, struct http_request *req, struct path_info *pi) +bool uh_file_request(struct client *cl, struct path_info *pi)  {  	int rlen;  	int ok = 1; @@ -364,36 +358,43 @@ void uh_file_request(struct client *cl, struct http_request *req, struct path_in  	if ((pi->stat.st_mode & S_IFREG) && ((fd = open(pi->phys, O_RDONLY)) > 0))  	{  		/* test preconditions */ -		if (ok) ensure_out(uh_file_if_modified_since(cl, req, &pi->stat, &ok)); -		if (ok) ensure_out(uh_file_if_match(cl, req, &pi->stat, &ok)); -		if (ok) ensure_out(uh_file_if_range(cl, req, &pi->stat, &ok)); -		if (ok) ensure_out(uh_file_if_unmodified_since(cl, req, &pi->stat, &ok)); -		if (ok) ensure_out(uh_file_if_none_match(cl, req, &pi->stat, &ok)); +		if (ok) ensure_out(uh_file_if_modified_since(cl, &pi->stat, &ok)); +		if (ok) ensure_out(uh_file_if_match(cl, &pi->stat, &ok)); +		if (ok) ensure_out(uh_file_if_range(cl, &pi->stat, &ok)); +		if (ok) ensure_out(uh_file_if_unmodified_since(cl, &pi->stat, &ok)); +		if (ok) ensure_out(uh_file_if_none_match(cl, &pi->stat, &ok));  		if (ok > 0)  		{  			/* write status */ -			ensure_out(uh_file_response_200(cl, req, &pi->stat)); +			ensure_out(uh_file_response_200(cl, &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-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)); +			ensure_out(uh_http_sendf(cl, NULL, "Content-Length: %i\r\n", +									 pi->stat.st_size));  			/* if request was HTTP 1.1 we'll respond chunked */ -			if ((req->version > 1.0) && (req->method != UH_HTTP_MSG_HEAD)) -				ensure_out(uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1)); +			if ((cl->request.version > 1.0) && +				(cl->request.method != UH_HTTP_MSG_HEAD)) +			{ +				ensure_out(uh_http_send(cl, NULL, +										"Transfer-Encoding: chunked\r\n", -1)); +			}  			/* close header */  			ensure_out(uh_http_send(cl, NULL, "\r\n", -1));  			/* send body */ -			if (req->method != UH_HTTP_MSG_HEAD) +			if (cl->request.method != UH_HTTP_MSG_HEAD)  			{  				/* pump file data */  				while ((rlen = read(fd, buf, sizeof(buf))) > 0) -					ensure_out(uh_http_send(cl, req, buf, rlen)); +					ensure_out(uh_http_send(cl, &cl->request, buf, rlen));  				/* send trailer in chunked mode */ -				ensure_out(uh_http_send(cl, req, "", 0)); +				ensure_out(uh_http_send(cl, &cl->request, "", 0));  			}  		} @@ -408,25 +409,29 @@ 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 */ -		ensure_out(uh_file_response_200(cl, req, NULL)); +		ensure_out(uh_file_response_200(cl, NULL)); -		if (req->version > 1.0) -			ensure_out(uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1)); +		if (cl->request.version > 1.0) +			ensure_out(uh_http_send(cl, NULL, +									"Transfer-Encoding: chunked\r\n", -1)); -		ensure_out(uh_http_send(cl, NULL, "Content-Type: text/html\r\n\r\n", -1)); +		ensure_out(uh_http_send(cl, NULL, +								"Content-Type: text/html\r\n\r\n", -1));  		/* content */ -		uh_file_dirlist(cl, req, pi); +		uh_file_dirlist(cl, pi);  	}  	/* 403 */  	else  	{  		ensure_out(uh_http_sendhf(cl, 403, "Forbidden", -			"Access to this resource is forbidden")); +								  "Access to this resource is forbidden"));  	}  out:  	if (fd > -1)  		close(fd); + +	return false;  } diff --git a/package/uhttpd/src/uhttpd-file.h b/package/uhttpd/src/uhttpd-file.h index 3d4681516..08dbe2cf9 100644 --- a/package/uhttpd/src/uhttpd-file.h +++ b/package/uhttpd/src/uhttpd-file.h @@ -31,8 +31,6 @@ struct mimetype {  	const char *mime;  }; -void uh_file_request( -	struct client *cl, struct http_request *req, struct path_info *pi -); +bool uh_file_request(struct client *cl, struct path_info *pi);  #endif diff --git a/package/uhttpd/src/uhttpd-lua.c b/package/uhttpd/src/uhttpd-lua.c index ea6f26cc9..7d602f7c5 100644 --- a/package/uhttpd/src/uhttpd-lua.c +++ b/package/uhttpd/src/uhttpd-lua.c @@ -1,7 +1,7 @@  /*   * uhttpd - Tiny single-threaded httpd - Lua handler   * - *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org> + *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>   *   *  Licensed under the Apache License, Version 2.0 (the "License");   *  you may not use this file except in compliance with the License. @@ -24,54 +24,59 @@  static int uh_lua_recv(lua_State *L)  {  	size_t length; +  	char buffer[UH_LIMIT_MSGHEAD]; -	ssize_t rlen = 0; -	fd_set reader; -	struct timeval timeout; + +	int to = 1; +	int fd = fileno(stdin); +	int rlen = 0;  	length = luaL_checknumber(L, 1);  	if ((length > 0) && (length <= sizeof(buffer)))  	{ -		FD_ZERO(&reader); -		FD_SET(fileno(stdin), &reader); - -		/* fail after 0.1s */ -		timeout.tv_sec  = 0; -		timeout.tv_usec = 100000; +		/* receive data */ +		rlen = uh_raw_recv(fd, buffer, length, to); -		/* check whether fd is readable */ -		if (select(fileno(stdin) + 1, &reader, NULL, NULL, &timeout) > 0) +		/* data read */ +		if (rlen > 0)  		{ -			/* receive data */ -			rlen = read(fileno(stdin), buffer, length);  			lua_pushnumber(L, rlen); +			lua_pushlstring(L, buffer, rlen); +			return 2; +		} -			if (rlen > 0) -			{ -				lua_pushlstring(L, buffer, rlen); -				return 2; -			} - +		/* eof */ +		else if (rlen == 0) +		{ +			lua_pushnumber(L, 0);  			return 1;  		}  		/* no, timeout and actually no data */ -		lua_pushnumber(L, -2); -		return 1; +		else +		{ +			lua_pushnumber(L, -1); +			return 1; +		}  	}  	/* parameter error */ -	lua_pushnumber(L, -3); +	lua_pushnumber(L, -2);  	return 1;  }  static int uh_lua_send_common(lua_State *L, int chunked)  {  	size_t length; -	const char *buffer; +  	char chunk[16]; -	ssize_t slen = 0; +	const char *buffer; + +	int rv; +	int to = 1; +	int fd = fileno(stdout); +	int slen = 0;  	buffer = luaL_checklstring(L, 1, &length); @@ -80,20 +85,27 @@ static int uh_lua_send_common(lua_State *L, int chunked)  		if (length > 0)  		{  			snprintf(chunk, sizeof(chunk), "%X\r\n", length); -			slen =  write(fileno(stdout), chunk, strlen(chunk)); -			slen += write(fileno(stdout), buffer, length); -			slen += write(fileno(stdout), "\r\n", 2); + +			ensure_out(rv = uh_raw_send(fd, chunk, strlen(chunk), to)); +			slen += rv; + +			ensure_out(rv = uh_raw_send(fd, buffer, length, to)); +			slen += rv; + +			ensure_out(rv = uh_raw_send(fd, "\r\n", 2, to)); +			slen += rv;  		}  		else  		{ -			slen = write(fileno(stdout), "0\r\n\r\n", 5); +			slen = uh_raw_send(fd, "0\r\n\r\n", 5, to);  		}  	}  	else  	{ -		slen = write(fileno(stdout), buffer, length); +		slen = uh_raw_send(fd, buffer, length, to);  	} +out:  	lua_pushnumber(L, slen);  	return 1;  } @@ -118,8 +130,8 @@ static int uh_lua_str2str(lua_State *L, int (*xlate_func) (char *, int, const ch  	inbuf = luaL_checklstring(L, 1, &inlen);  	outlen = (* xlate_func)(outbuf, sizeof(outbuf), inbuf, inlen);  	if (outlen < 0) -		luaL_error( L, "%s on URL-encode codec", -			(outlen==-1) ? "buffer overflow" : "malformed string" ); +		luaL_error(L, "%s on URL-encode codec", +				   (outlen==-1) ? "buffer overflow" : "malformed string");  	lua_pushlstring(L, outbuf, outlen);  	return 1; @@ -181,17 +193,17 @@ lua_State * uh_lua_init(const struct config *conf)  	{  		case LUA_ERRSYNTAX:  			fprintf(stderr, -				"Lua handler contains syntax errors, unable to continue\n"); +					"Lua handler contains syntax errors, unable to continue\n");  			exit(1);  		case LUA_ERRMEM:  			fprintf(stderr, -				"Lua handler ran out of memory, unable to continue\n"); +					"Lua handler ran out of memory, unable to continue\n");  			exit(1);  		case LUA_ERRFILE:  			fprintf(stderr, -				"Lua cannot open the handler script, unable to continue\n"); +					"Lua cannot open the handler script, unable to continue\n");  			exit(1);  		default: @@ -201,17 +213,17 @@ lua_State * uh_lua_init(const struct config *conf)  				case LUA_ERRRUN:  					err_str = luaL_checkstring(L, -1);  					fprintf(stderr, -						"Lua handler had runtime error, unable to continue\n" -						"Error: %s\n", err_str -					); +							"Lua handler had runtime error, " +							"unable to continue\n" +							"Error: %s\n", err_str);  					exit(1);  				case LUA_ERRMEM:  					err_str = luaL_checkstring(L, -1);  					fprintf(stderr, -						"Lua handler ran out of memory, unable to continue\n" -						"Error: %s\n", err_str -					); +							"Lua handler ran out of memory, " +							"unable to continue\n" +							"Error: %s\n", err_str);  					exit(1);  				default: @@ -221,7 +233,8 @@ lua_State * uh_lua_init(const struct config *conf)  					if (! lua_isfunction(L, -1))  					{  						fprintf(stderr, -							"Lua handler provides no " UH_LUA_CALLBACK "(), unable to continue\n"); +								"Lua handler provides no "UH_LUA_CALLBACK"(), " +								"unable to continue\n");  						exit(1);  					} @@ -235,12 +248,107 @@ lua_State * uh_lua_init(const struct config *conf)  	return L;  } -void uh_lua_request(struct client *cl, struct http_request *req, lua_State *L) +static void uh_lua_shutdown(struct uh_lua_state *state)  { -	int i, data_sent; -	int content_length = 0; -	int buflen = 0; -	int fd_max = 0; +	close(state->rfd); +	close(state->wfd); +	free(state); +} + +static bool uh_lua_socket_cb(struct client *cl) +{ +	int len; +	char buf[UH_LIMIT_MSGHEAD]; + +	struct uh_lua_state *state = (struct uh_lua_state *)cl->priv; + +	/* there is unread post data waiting */ +	while (state->content_length > 0) +	{ +		/* remaining data in http head buffer ... */ +		if (state->cl->httpbuf.len > 0) +		{ +			len = min(state->content_length, state->cl->httpbuf.len); + +			D("Lua: Child(%d) feed %d HTTP buffer bytes\n", +			  state->cl->proc.pid, len); + +			memcpy(buf, state->cl->httpbuf.ptr, len); + +			state->cl->httpbuf.len -= len; +			state->cl->httpbuf.ptr += len; +		} + +		/* read it from socket ... */ +		else +		{ +			len = uh_tcp_recv(state->cl, buf, +							  min(state->content_length, sizeof(buf))); + +			if ((len < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) +				break; + +			D("Lua: Child(%d) feed %d/%d TCP socket bytes\n", +			  state->cl->proc.pid, len, +			  min(state->content_length, sizeof(buf))); +		} + +		if (len) +			state->content_length -= len; +		else +			state->content_length = 0; + +		/* ... write to CGI process */ +		len = uh_raw_send(state->wfd, buf, len, +						  cl->server->conf->script_timeout); +	} + +	/* try to read data from child */ +	while ((len = uh_raw_recv(state->rfd, buf, sizeof(buf), -1)) > 0) +	{ +		/* pass through buffer to socket */ +		D("Lua: Child(%d) relaying %d normal bytes\n", state->cl->proc.pid, len); +		ensure_out(uh_tcp_send(state->cl, buf, len)); +		state->data_sent = true; +	} + +	/* child has been marked dead by timeout or child handler, bail out */ +	if (false && cl->dead) +	{ +		D("Lua: Child(%d) is marked dead, returning\n", state->cl->proc.pid); +		goto out; +	} + +	if ((len == 0) || +		((errno != EAGAIN) && (errno != EWOULDBLOCK) && (len == -1))) +	{ +		D("Lua: Child(%d) presumed dead [%s]\n", +		  state->cl->proc.pid, strerror(errno)); + +		goto out; +	} + +	return true; + +out: +	if (!state->data_sent) +	{ +		if (state->cl->timeout.pending) +			uh_http_sendhf(state->cl, 502, "Bad Gateway", +						   "The Lua process did not produce any response\n"); +		else +			uh_http_sendhf(state->cl, 504, "Gateway Timeout", +						   "The Lua process took too long to produce a " +						   "response\n"); +	} + +	uh_lua_shutdown(state); +	return false; +} + +bool uh_lua_request(struct client *cl, lua_State *L) +{ +	int i;  	char *query_string;  	const char *prefix = cl->server->conf->lua_prefix;  	const char *err_str = NULL; @@ -248,325 +356,243 @@ void uh_lua_request(struct client *cl, struct http_request *req, lua_State *L)  	int rfd[2] = { 0, 0 };  	int wfd[2] = { 0, 0 }; -	char buf[UH_LIMIT_MSGHEAD]; -  	pid_t child; -	fd_set reader; -	fd_set writer; +	struct uh_lua_state *state; +	struct http_request *req = &cl->request; -	struct sigaction sa; -	struct timeval timeout; +	int content_length = cl->httpbuf.len; +	/* allocate state */ +	if (!(state = malloc(sizeof(*state)))) +	{ +		uh_client_error(cl, 500, "Internal Server Error", "Out of memory"); +		return false; +	} +  	/* spawn pipes for me->child, child->me */  	if ((pipe(rfd) < 0) || (pipe(wfd) < 0))  	{ -		uh_http_sendhf(cl, 500, "Internal Server Error", -			"Failed to create pipe: %s", strerror(errno)); -  		if (rfd[0] > 0) close(rfd[0]);  		if (rfd[1] > 0) close(rfd[1]);  		if (wfd[0] > 0) close(wfd[0]);  		if (wfd[1] > 0) close(wfd[1]); -		return; +		uh_client_error(cl, 500, "Internal Server Error", +						"Failed to create pipe: %s", strerror(errno)); + +		return false;  	}  	switch ((child = fork()))  	{ -		case -1: -			uh_http_sendhf(cl, 500, "Internal Server Error", -				"Failed to fork child: %s", strerror(errno)); -			break; +	case -1: +		uh_client_error(cl, 500, "Internal Server Error", +						"Failed to fork child: %s", strerror(errno)); -		case 0: -			/* restore SIGTERM */ -			sa.sa_flags = 0; -			sa.sa_handler = SIG_DFL; -			sigemptyset(&sa.sa_mask); -			sigaction(SIGTERM, &sa, NULL); +		return false; -			/* close loose pipe ends */ -			close(rfd[0]); -			close(wfd[1]); +	case 0: +#ifdef DEBUG +		sleep(atoi(getenv("UHTTPD_SLEEP_ON_FORK") ?: "0")); +#endif -			/* patch stdout and stdin to pipes */ -			dup2(rfd[1], 1); -			dup2(wfd[0], 0); +		/* close loose pipe ends */ +		close(rfd[0]); +		close(wfd[1]); -			/* put handler callback on stack */ -			lua_getglobal(L, UH_LUA_CALLBACK); +		/* patch stdout and stdin to pipes */ +		dup2(rfd[1], 1); +		dup2(wfd[0], 0); -			/* build env table */ -			lua_newtable(L); +		/* avoid leaking our pipe into child-child processes */ +		fd_cloexec(rfd[1]); +		fd_cloexec(wfd[0]); -			/* request method */ -			switch(req->method) -			{ -				case UH_HTTP_MSG_GET: -					lua_pushstring(L, "GET"); -					break; +		/* put handler callback on stack */ +		lua_getglobal(L, UH_LUA_CALLBACK); -				case UH_HTTP_MSG_HEAD: -					lua_pushstring(L, "HEAD"); -					break; +		/* build env table */ +		lua_newtable(L); -				case UH_HTTP_MSG_POST: -					lua_pushstring(L, "POST"); -					break; -			} +		/* request method */ +		switch(req->method) +		{ +			case UH_HTTP_MSG_GET: +				lua_pushstring(L, "GET"); +				break; -			lua_setfield(L, -2, "REQUEST_METHOD"); +			case UH_HTTP_MSG_HEAD: +				lua_pushstring(L, "HEAD"); +				break; -			/* request url */ -			lua_pushstring(L, req->url); -			lua_setfield(L, -2, "REQUEST_URI"); +			case UH_HTTP_MSG_POST: +				lua_pushstring(L, "POST"); +				break; +		} -			/* script name */ -			lua_pushstring(L, cl->server->conf->lua_prefix); -			lua_setfield(L, -2, "SCRIPT_NAME"); +		lua_setfield(L, -2, "REQUEST_METHOD"); -			/* query string, path info */ -			if ((query_string = strchr(req->url, '?')) != NULL) -			{ -				lua_pushstring(L, query_string + 1); -				lua_setfield(L, -2, "QUERY_STRING"); +		/* request url */ +		lua_pushstring(L, req->url); +		lua_setfield(L, -2, "REQUEST_URI"); -				if ((int)(query_string - req->url) > strlen(prefix)) -				{ -					lua_pushlstring(L, -						&req->url[strlen(prefix)], -						(int)(query_string - req->url) - strlen(prefix) -					); +		/* script name */ +		lua_pushstring(L, cl->server->conf->lua_prefix); +		lua_setfield(L, -2, "SCRIPT_NAME"); -					lua_setfield(L, -2, "PATH_INFO"); -				} -			} -			else if (strlen(req->url) > strlen(prefix)) +		/* query string, path info */ +		if ((query_string = strchr(req->url, '?')) != NULL) +		{ +			lua_pushstring(L, query_string + 1); +			lua_setfield(L, -2, "QUERY_STRING"); + +			if ((int)(query_string - req->url) > strlen(prefix))  			{ -				lua_pushstring(L, &req->url[strlen(prefix)]); +				lua_pushlstring(L, +					&req->url[strlen(prefix)], +					(int)(query_string - req->url) - strlen(prefix) +				); +  				lua_setfield(L, -2, "PATH_INFO");  			} +		} +		else if (strlen(req->url) > strlen(prefix)) +		{ +			lua_pushstring(L, &req->url[strlen(prefix)]); +			lua_setfield(L, -2, "PATH_INFO"); +		} -			/* http protcol version */ -			lua_pushnumber(L, floor(req->version * 10) / 10); -			lua_setfield(L, -2, "HTTP_VERSION"); +		/* http protcol version */ +		lua_pushnumber(L, floor(req->version * 10) / 10); +		lua_setfield(L, -2, "HTTP_VERSION"); -			if (req->version > 1.0) -				lua_pushstring(L, "HTTP/1.1"); -			else -				lua_pushstring(L, "HTTP/1.0"); +		if (req->version > 1.0) +			lua_pushstring(L, "HTTP/1.1"); +		else +			lua_pushstring(L, "HTTP/1.0"); -			lua_setfield(L, -2, "SERVER_PROTOCOL"); +		lua_setfield(L, -2, "SERVER_PROTOCOL"); -			/* address information */ -			lua_pushstring(L, sa_straddr(&cl->peeraddr)); -			lua_setfield(L, -2, "REMOTE_ADDR"); +		/* address information */ +		lua_pushstring(L, sa_straddr(&cl->peeraddr)); +		lua_setfield(L, -2, "REMOTE_ADDR"); -			lua_pushinteger(L, sa_port(&cl->peeraddr)); -			lua_setfield(L, -2, "REMOTE_PORT"); +		lua_pushinteger(L, sa_port(&cl->peeraddr)); +		lua_setfield(L, -2, "REMOTE_PORT"); -			lua_pushstring(L, sa_straddr(&cl->servaddr)); -			lua_setfield(L, -2, "SERVER_ADDR"); +		lua_pushstring(L, sa_straddr(&cl->servaddr)); +		lua_setfield(L, -2, "SERVER_ADDR"); -			lua_pushinteger(L, sa_port(&cl->servaddr)); -			lua_setfield(L, -2, "SERVER_PORT"); +		lua_pushinteger(L, sa_port(&cl->servaddr)); +		lua_setfield(L, -2, "SERVER_PORT"); -			/* essential env vars */ -			foreach_header(i, req->headers) +		/* essential env vars */ +		foreach_header(i, req->headers) +		{ +			if (!strcasecmp(req->headers[i], "Content-Length"))  			{ -				if (!strcasecmp(req->headers[i], "Content-Length")) -				{ -					lua_pushnumber(L, atoi(req->headers[i+1])); -					lua_setfield(L, -2, "CONTENT_LENGTH"); -				} -				else if (!strcasecmp(req->headers[i], "Content-Type")) -				{ -					lua_pushstring(L, req->headers[i+1]); -					lua_setfield(L, -2, "CONTENT_TYPE"); -				} +				content_length = atoi(req->headers[i+1]);  			} - -			/* misc. headers */ -			lua_newtable(L); - -			foreach_header(i, req->headers) +			else if (!strcasecmp(req->headers[i], "Content-Type"))  			{ -				if( strcasecmp(req->headers[i], "Content-Length") && -					strcasecmp(req->headers[i], "Content-Type") -				) { -					lua_pushstring(L, req->headers[i+1]); -					lua_setfield(L, -2, req->headers[i]); -				} +				lua_pushstring(L, req->headers[i+1]); +				lua_setfield(L, -2, "CONTENT_TYPE");  			} +		} -			lua_setfield(L, -2, "headers"); +		lua_pushnumber(L, content_length); +		lua_setfield(L, -2, "CONTENT_LENGTH"); +		/* misc. headers */ +		lua_newtable(L); -			/* call */ -			switch (lua_pcall(L, 1, 0, 0)) +		foreach_header(i, req->headers) +		{ +			if( strcasecmp(req->headers[i], "Content-Length") && +				strcasecmp(req->headers[i], "Content-Type"))  			{ -				case LUA_ERRMEM: -				case LUA_ERRRUN: -					err_str = luaL_checkstring(L, -1); - -					if (! err_str) -						err_str = "Unknown error"; - -					printf( -						"HTTP/%.1f 500 Internal Server Error\r\n" -						"Connection: close\r\n" -						"Content-Type: text/plain\r\n" -						"Content-Length: %i\r\n\r\n" -						"Lua raised a runtime error:\n  %s\n", -							req->version, 31 + strlen(err_str), err_str -					); - -					break; - -				default: -					break; +				lua_pushstring(L, req->headers[i+1]); +				lua_setfield(L, -2, req->headers[i]);  			} +		} -			close(wfd[0]); -			close(rfd[1]); -			exit(0); +		lua_setfield(L, -2, "headers"); -			break; -		/* parent; handle I/O relaying */ -		default: -			/* close unneeded pipe ends */ -			close(rfd[1]); -			close(wfd[0]); +		/* call */ +		switch (lua_pcall(L, 1, 0, 0)) +		{ +			case LUA_ERRMEM: +			case LUA_ERRRUN: +				err_str = luaL_checkstring(L, -1); -			/* max watch fd */ -			fd_max = max(rfd[0], wfd[1]) + 1; +				if (! err_str) +					err_str = "Unknown error"; -			/* find content length */ -			if (req->method == UH_HTTP_MSG_POST) -			{ -				foreach_header(i, req->headers) -				{ -					if (! strcasecmp(req->headers[i], "Content-Length")) -					{ -						content_length = atoi(req->headers[i+1]); -						break; -					} -				} -			} +				printf("HTTP/%.1f 500 Internal Server Error\r\n" +					   "Connection: close\r\n" +					   "Content-Type: text/plain\r\n" +					   "Content-Length: %i\r\n\r\n" +					   "Lua raised a runtime error:\n  %s\n", +					   req->version, 31 + strlen(err_str), err_str); +				break; -#define ensure(x) \ -	do { if (x < 0) goto out; } while(0) +			default: +				break; +		} -			data_sent = 0; +		close(wfd[0]); +		close(rfd[1]); +		exit(0); -			timeout.tv_sec = cl->server->conf->script_timeout; -			timeout.tv_usec = 0; +		break; -			/* I/O loop, watch our pipe ends and dispatch child reads/writes from/to socket */ -			while (1) -			{ -				FD_ZERO(&reader); -				FD_ZERO(&writer); +	/* parent; handle I/O relaying */ +	default: +		memset(state, 0, sizeof(*state)); -				FD_SET(rfd[0], &reader); -				FD_SET(wfd[1], &writer); +		state->cl = cl; +		state->cl->proc.pid = child; -				/* wait until we can read or write or both */ -				if (select_intr(fd_max, &reader, -								(content_length > -1) ? &writer : NULL, -								NULL, -								(data_sent < 1) ? &timeout : NULL) > 0) -				{ -					/* ready to write to Lua child */ -					if (FD_ISSET(wfd[1], &writer)) -					{ -						/* there is unread post data waiting */ -						if (content_length > 0) -						{ -							/* read it from socket ... */ -							if ((buflen = uh_tcp_recv(cl, buf, min(content_length, sizeof(buf)))) > 0) -							{ -								/* ... and write it to child's stdin */ -								if (write(wfd[1], buf, buflen) < 0) -									perror("write()"); - -								content_length -= buflen; -							} - -							/* unexpected eof! */ -							else -							{ -								if (write(wfd[1], "", 0) < 0) -									perror("write()"); - -								content_length = 0; -							} -						} - -						/* there is no more post data, close pipe to child's stdin */ -						else if (content_length > -1) -						{ -							close(wfd[1]); -							content_length = -1; -						} -					} +		/* close unneeded pipe ends */ +		close(rfd[1]); +		close(wfd[0]); -					/* ready to read from Lua child */ -					if (FD_ISSET(rfd[0], &reader)) -					{ -						/* read data from child ... */ -						if ((buflen = read(rfd[0], buf, sizeof(buf))) > 0) -						{ -							/* pass through buffer to socket */ -							ensure(uh_tcp_send(cl, buf, buflen)); -							data_sent = 1; -						} - -						/* looks like eof from child */ -						else -						{ -							/* error? */ -							if (!data_sent) -								uh_http_sendhf(cl, 500, "Internal Server Error", -									"The Lua child did not produce any response"); - -							break; -						} -					} -				} +		D("Lua: Child(%d) created: rfd(%d) wfd(%d)\n", child, rfd[0], wfd[1]); -				/* timeout exceeded or interrupted by SIGCHLD */ -				else -				{ -					if ((errno != EINTR) && ! data_sent) -					{ -						ensure(uh_http_sendhf(cl, 504, "Gateway Timeout", -							"The Lua script took too long to produce " -							"a response")); -					} +		state->content_length = cl->httpbuf.len; +		/* find content length */ +		if (req->method == UH_HTTP_MSG_POST) +		{ +			foreach_header(i, req->headers) +			{ +				if (!strcasecmp(req->headers[i], "Content-Length")) +				{ +					state->content_length = atoi(req->headers[i+1]);  					break;  				}  			} +		} -		out: -			close(rfd[0]); -			close(wfd[1]); +		state->rfd = rfd[0]; +		fd_nonblock(state->rfd); -			if (!kill(child, 0)) -			{ -				kill(child, SIGTERM); -				waitpid(child, NULL, 0); -			} +		state->wfd = wfd[1]; +		fd_nonblock(state->wfd); -			break; +		cl->cb = uh_lua_socket_cb; +		cl->priv = state; + +		break;  	} + +	return true;  }  void uh_lua_close(lua_State *L) diff --git a/package/uhttpd/src/uhttpd-lua.h b/package/uhttpd/src/uhttpd-lua.h index 2d2f73c1c..9a10933fc 100644 --- a/package/uhttpd/src/uhttpd-lua.h +++ b/package/uhttpd/src/uhttpd-lua.h @@ -1,7 +1,7 @@  /*   * uhttpd - Tiny single-threaded httpd - Lua header   * - *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org> + *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>   *   *  Licensed under the Apache License, Version 2.0 (the "License");   *  you may not use this file except in compliance with the License. @@ -32,12 +32,17 @@  #define UH_LUA_ERR_PARAM   -3 -lua_State * uh_lua_init(const struct config *conf); - -void uh_lua_request( -	struct client *cl, struct http_request *req, lua_State *L -); +struct uh_lua_state { +	int rfd; +	int wfd; +	struct client *cl; +	char httpbuf[UH_LIMIT_MSGHEAD]; +	int content_length; +	bool data_sent; +}; +lua_State * uh_lua_init(const struct config *conf); +bool uh_lua_request(struct client *cl, lua_State *L);  void uh_lua_close(lua_State *L);  #endif diff --git a/package/uhttpd/src/uhttpd-tls.c b/package/uhttpd/src/uhttpd-tls.c index 4a9e90792..9c6eb81db 100644 --- a/package/uhttpd/src/uhttpd-tls.c +++ b/package/uhttpd/src/uhttpd-tls.c @@ -23,150 +23,19 @@  #include <syslog.h>  #define dbg(...) syslog(LOG_INFO, __VA_ARGS__) -#ifdef TLS_IS_CYASSL -static int uh_cyassl_recv_cb(char *buf, int sz, void *ctx) -{ -	int rv; -	int socket = *(int *)ctx; -	struct client *cl; - -	if (!(cl = uh_client_lookup(socket))) -		return -1; /* unexpected error */ - -	rv = uh_tcp_recv_lowlevel(cl, buf, sz); - -	if (rv < 0) -		return -4; /* interrupted */ - -	if (rv == 0) -		return -5; /* connection closed */ - -	return rv; -} - -static int uh_cyassl_send_cb(char *buf, int sz, void *ctx) -{ -	int rv; -	int socket = *(int *)ctx; -	struct client *cl; - -	if (!(cl = uh_client_lookup(socket))) -		return -1; /* unexpected error */ - -	rv = uh_tcp_send_lowlevel(cl, buf, sz); - -	if (rv <= 0) -		return -5; /* connection dead */ - -	return rv; -} - -void SetCallbackIORecv_Ctx(SSL_CTX*, int (*)(char *, int, void *)); -void SetCallbackIOSend_Ctx(SSL_CTX*, int (*)(char *, int, void *)); - -static void uh_tls_ctx_setup(SSL_CTX *ctx) -{ -	SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); -	SetCallbackIORecv_Ctx(ctx, uh_cyassl_recv_cb); -	SetCallbackIOSend_Ctx(ctx, uh_cyassl_send_cb); -	return; -} - -static int uh_tls_client_ctx_setup(SSL *ssl, int socket) -{ -	return SSL_set_fd(ssl, socket); -} -#endif /* TLS_IS_CYASSL */ - -#ifdef TLS_IS_OPENSSL -static long uh_openssl_bio_ctrl_cb(BIO *b, int cmd, long num, void *ptr) -{ -	long rv = 1; - -	switch (cmd) -	{ -		case BIO_C_SET_FD: -			b->num      = *((int *)ptr); -			b->shutdown = (int)num; -			b->init     = 1; -			break; - -		case BIO_C_GET_FD: -			if (!b->init) -				return -1; - -			if (ptr) -				*((int *)ptr) = b->num; - -			rv = b->num; -			break; -	} - -	return rv; -} - -static int uh_openssl_bio_read_cb(BIO *b, char *out, int outl) -{ -	int rv = 0; -	struct client *cl; - -	if (!(cl = uh_client_lookup(b->num))) -		return -1; - -	if (out != NULL) -		rv = uh_tcp_recv_lowlevel(cl, out, outl); - -	return rv; -} - -static int uh_openssl_bio_write_cb(BIO *b, const char *in, int inl) -{ -	struct client *cl; - -	if (!(cl = uh_client_lookup(b->num))) -		return -1; - -	return uh_tcp_send_lowlevel(cl, in, inl); -} - -static BIO_METHOD uh_openssl_bio_methods = { -	.type   = BIO_TYPE_SOCKET, -	.name   = "uhsocket", -	.ctrl   = uh_openssl_bio_ctrl_cb, -	.bwrite = uh_openssl_bio_write_cb, -	.bread  = uh_openssl_bio_read_cb -}; - -static void uh_tls_ctx_setup(SSL_CTX *ctx) -{ -	SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); -	return; -} - -static int uh_tls_client_ctx_setup(SSL *ssl, int socket) -{ -	BIO *b; - -	if (!(b = BIO_new(&uh_openssl_bio_methods))) -		return 0; - -	BIO_set_fd(b, socket, BIO_NOCLOSE); -	SSL_set_bio(ssl, b, b); - -	return 1; -} -#endif /* TLS_IS_OPENSSL */ - - -SSL_CTX * uh_tls_ctx_init() +SSL_CTX * uh_tls_ctx_init(void)  {  	SSL_CTX *c;  	SSL_load_error_strings();  	SSL_library_init(); +#if TLS_IS_OPENSSL +	if ((c = SSL_CTX_new(SSLv23_server_method())) != NULL) +#else  	if ((c = SSL_CTX_new(TLSv1_server_method())) != NULL) -		uh_tls_ctx_setup(c); +#endif +		SSL_CTX_set_verify(c, SSL_VERIFY_NONE, NULL);  	return c;  } @@ -199,53 +68,100 @@ void uh_tls_ctx_free(struct listener *l)  int uh_tls_client_accept(struct client *c)  { -	int rv; +	int rv, err; +	int fd = c->fd.fd; -	if( c->server && c->server->tls ) +	if (!c->server || !c->server->tls)  	{ -		c->tls = SSL_new(c->server->tls); -		if( c->tls ) -		{ -			if( (rv = uh_tls_client_ctx_setup(c->tls, c->socket)) < 1 ) -				goto cleanup; +		c->tls = NULL; +		return 1; +	} -			if( (rv = SSL_accept(c->tls)) < 1 ) -				goto cleanup; +	if ((c->tls = SSL_new(c->server->tls))) +	{ +		if ((rv = SSL_set_fd(c->tls, fd)) < 1) +		{ +			SSL_free(c->tls); +			c->tls = NULL;  		}  		else -			rv = 0; -	} -	else -	{ -		c->tls = NULL; -		rv = 1; -	} +		{ +			while (true) +			{ +				rv = SSL_accept(c->tls); +				err = SSL_get_error(c->tls, rv); + +				if ((rv != 1) && +					(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)) +				{ +					if (uh_socket_wait(fd, c->server->conf->network_timeout, +									   (err == SSL_ERROR_WANT_WRITE))) +					{ +						D("TLS: accept(%d) = retry\n", fd); +						continue; +					} + +					D("TLS: accept(%d) = timeout\n", fd); +				} +				else if (rv == 1) +				{ +					D("TLS: accept(%d) = %p\n", fd, c->tls); +					return 1; +				} -done: -	return rv; +#ifdef TLS_IS_OPENSSL +				D("TLS: accept(%d) = failed: %s\n", +				  fd, ERR_error_string(ERR_get_error(), NULL)); +#endif + +				SSL_free(c->tls); +				c->tls = NULL; +				break; +			} +		} +	} -cleanup: -	SSL_free(c->tls); -	c->tls = NULL; -	goto done; +	return 0;  } -int uh_tls_client_recv(struct client *c, void *buf, int len) +int uh_tls_client_recv(struct client *c, char *buf, int len)  {  	int rv = SSL_read(c->tls, buf, len); -	return (rv > 0) ? rv : -1; +	int err = SSL_get_error(c->tls, 0); + +	if ((rv == -1) && (err == SSL_ERROR_WANT_READ)) +	{ +		D("TLS: recv(%d, %d) = retry\n", c->fd.fd, len); +		errno = EAGAIN; +		return -1; +	} + +	D("TLS: recv(%d, %d) = %d\n", c->fd.fd, len, rv); +	return rv;  } -int uh_tls_client_send(struct client *c, void *buf, int len) +int uh_tls_client_send(struct client *c, const char *buf, int len)  {  	int rv = SSL_write(c->tls, buf, len); -	return (rv > 0) ? rv : -1; +	int err = SSL_get_error(c->tls, 0); + +	if ((rv == -1) && (err == SSL_ERROR_WANT_WRITE)) +	{ +		D("TLS: send(%d, %d) = retry\n", c->fd.fd, len); +		errno = EAGAIN; +		return -1; +	} + +	D("TLS: send(%d, %d) = %d\n", c->fd.fd, len, rv); +	return rv;  }  void uh_tls_client_close(struct client *c)  { -	if( c->tls ) +	if (c->tls)  	{ +		D("TLS: close(%d)\n", c->fd.fd); +  		SSL_shutdown(c->tls);  		SSL_free(c->tls); diff --git a/package/uhttpd/src/uhttpd-tls.h b/package/uhttpd/src/uhttpd-tls.h index 24dfb4407..8644c2ac5 100644 --- a/package/uhttpd/src/uhttpd-tls.h +++ b/package/uhttpd/src/uhttpd-tls.h @@ -19,7 +19,9 @@  #ifndef _UHTTPD_TLS_  #include <openssl/ssl.h> - +#ifdef TLS_IS_OPENSSL +#include <openssl/err.h> +#endif  SSL_CTX * uh_tls_ctx_init();  int uh_tls_ctx_cert(SSL_CTX *c, const char *file); @@ -27,8 +29,8 @@ int uh_tls_ctx_key(SSL_CTX *c, const char *file);  void uh_tls_ctx_free(struct listener *l);  int uh_tls_client_accept(struct client *c); -int uh_tls_client_recv(struct client *c, void *buf, int len); -int uh_tls_client_send(struct client *c, void *buf, int len); +int uh_tls_client_recv(struct client *c, char *buf, int len); +int uh_tls_client_send(struct client *c, const char *buf, int len);  void uh_tls_client_close(struct client *c);  #endif diff --git a/package/uhttpd/src/uhttpd-ubus.c b/package/uhttpd/src/uhttpd-ubus.c new file mode 100644 index 000000000..20781629b --- /dev/null +++ b/package/uhttpd/src/uhttpd-ubus.c @@ -0,0 +1,957 @@ +/* + * uhttpd - Tiny single-threaded httpd - ubus handler + * + *   Copyright (C) 2012 Jo-Philipp Wich <xm@subsignal.org> + * + *  Licensed under the Apache License, Version 2.0 (the "License"); + *  you may not use this file except in compliance with the License. + *  You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + *  Unless required by applicable law or agreed to in writing, software + *  distributed under the License is distributed on an "AS IS" BASIS, + *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *  See the License for the specific language governing permissions and + *  limitations under the License. + */ + +#include "uhttpd.h" +#include "uhttpd-utils.h" +#include "uhttpd-ubus.h" + + +enum { +	UH_UBUS_SN_TIMEOUT, +	__UH_UBUS_SN_MAX, +}; + +static const struct blobmsg_policy new_policy[__UH_UBUS_SN_MAX] = { +	[UH_UBUS_SN_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 }, +}; + + +enum { +	UH_UBUS_SI_SID, +	__UH_UBUS_SI_MAX, +}; + +static const struct blobmsg_policy sid_policy[__UH_UBUS_SI_MAX] = { +	[UH_UBUS_SI_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING }, +}; + + +enum { +	UH_UBUS_SS_SID, +	UH_UBUS_SS_VALUES, +	__UH_UBUS_SS_MAX, +}; + +static const struct blobmsg_policy set_policy[__UH_UBUS_SS_MAX] = { +	[UH_UBUS_SS_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING }, +	[UH_UBUS_SS_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE }, +}; + + +enum { +	UH_UBUS_SG_SID, +	UH_UBUS_SG_KEYS, +	__UH_UBUS_SG_MAX, +}; + +static const struct blobmsg_policy get_policy[__UH_UBUS_SG_MAX] = { +	[UH_UBUS_SG_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING }, +	[UH_UBUS_SG_KEYS] = { .name = "keys", .type = BLOBMSG_TYPE_ARRAY }, +}; + + +enum { +	UH_UBUS_SA_SID, +	UH_UBUS_SA_OBJECTS, +	__UH_UBUS_SA_MAX, +}; + +static const struct blobmsg_policy acl_policy[__UH_UBUS_SA_MAX] = { +	[UH_UBUS_SA_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING }, +	[UH_UBUS_SA_OBJECTS] = { .name = "objects", .type = BLOBMSG_TYPE_ARRAY }, +}; + + +static bool +uh_ubus_strmatch(const char *str, const char *pat) +{ +	while (*pat) +	{ +		if (*pat == '?') +		{ +			if (!*str) +				return false; + +			str++; +			pat++; +		} +		else if (*pat == '*') +		{ +			if (uh_ubus_strmatch(str, pat+1)) +				return true; + +			if (*str && uh_ubus_strmatch(str+1, pat)) +				return true; + +			return false; +		} +		else if (*str++ != *pat++) +		{ +			return false; +		} +	} + +	return (!*str && !*pat); +} + +static int +uh_ubus_avlcmp(const void *k1, const void *k2, void *ptr) +{ +	return strcmp((char *)k1, (char *)k2); +} + +static void +uh_ubus_random(char *dest) +{ +	int i; +	unsigned char buf[16] = { 0 }; +	FILE *f; + +	if ((f = fopen("/dev/urandom", "r")) != NULL) +	{ +		fread(buf, 1, sizeof(buf), f); +		fclose(f); +	} + +	for (i = 0; i < sizeof(buf); i++) +		sprintf(dest + (i<<1), "%02x", buf[i]); +} + +static void +uh_ubus_session_dump_data(struct uh_ubus_session *ses, struct blob_buf *b) +{ +	struct uh_ubus_session_data *d; + +	avl_for_each_element(&ses->data, d, avl) +	{ +		blobmsg_add_field(b, blobmsg_type(d->attr), blobmsg_name(d->attr), +						  blobmsg_data(d->attr), blobmsg_data_len(d->attr)); +	} +} + +static void +uh_ubus_session_dump_acls(struct uh_ubus_session *ses, struct blob_buf *b) +{ +	struct uh_ubus_session_acl *acl; +	const char *lastobj = NULL; +	void *c = NULL; + +	avl_for_each_element(&ses->acls, acl, avl) +	{ +		if (!lastobj || strcmp(acl->object, lastobj)) +		{ +			if (c) blobmsg_close_array(b, c); +			c = blobmsg_open_array(b, acl->object); +		} + +		blobmsg_add_string(b, NULL, acl->function); +		lastobj = acl->object; +	} + +	if (c) blobmsg_close_array(b, c); +} + +static void +uh_ubus_session_dump(struct uh_ubus_session *ses, +					 struct ubus_context *ctx, +					 struct ubus_request_data *req) +{ +	void *c; +	struct blob_buf b; + +	memset(&b, 0, sizeof(b)); +	blob_buf_init(&b, 0); + +	blobmsg_add_string(&b, "sid", ses->id); +	blobmsg_add_u32(&b, "timeout", ses->timeout); +	blobmsg_add_u32(&b, "touched", ses->touched.tv_sec); + +	c = blobmsg_open_table(&b, "acls"); +	uh_ubus_session_dump_acls(ses, &b); +	blobmsg_close_table(&b, c); + +	c = blobmsg_open_table(&b, "data"); +	uh_ubus_session_dump_data(ses, &b); +	blobmsg_close_table(&b, c); + +	ubus_send_reply(ctx, req, b.head); +	blob_buf_free(&b); +} + +static struct uh_ubus_session * +uh_ubus_session_create(struct uh_ubus_state *state, int timeout) +{ +	struct uh_ubus_session *ses; + +	ses = malloc(sizeof(*ses)); + +	/* failed to allocate memory... */ +	if (!ses) +		return NULL; + +	memset(ses, 0, sizeof(*ses)); + +	uh_ubus_random(ses->id); + +	ses->timeout  = timeout; +	ses->avl.key  = ses->id; + +	avl_insert(&state->sessions, &ses->avl); +	avl_init(&ses->acls, uh_ubus_avlcmp, true, NULL); +	avl_init(&ses->data, uh_ubus_avlcmp, false, NULL); +	clock_gettime(CLOCK_MONOTONIC, &ses->touched); + +	return ses; +} + + +static struct uh_ubus_session * +uh_ubus_session_get(struct uh_ubus_state *state, const char *id) +{ +	struct uh_ubus_session *ses; + +	ses = avl_find_element(&state->sessions, id, ses, avl); + +	if (ses) +		clock_gettime(CLOCK_MONOTONIC, &ses->touched); + +	return ses; +} + +static void +uh_ubus_session_destroy(struct uh_ubus_state *state, +						struct uh_ubus_session *ses) +{ +	struct uh_ubus_session_acl *acl, *nacl; +	struct uh_ubus_session_data *data, *ndata; + +	avl_remove_all_elements(&ses->acls, acl, avl, nacl) +		free(acl); + +	avl_remove_all_elements(&ses->data, data, avl, ndata) +		free(data); + +	avl_delete(&state->sessions, &ses->avl); +	free(ses); +} + +static void +uh_ubus_session_cleanup(struct uh_ubus_state *state) +{ +	struct timespec now; +	struct uh_ubus_session *ses, *nses; + +	clock_gettime(CLOCK_MONOTONIC, &now); + +	avl_for_each_element_safe(&state->sessions, ses, avl, nses) +	{ +		if ((now.tv_sec - ses->touched.tv_sec) >= ses->timeout) +			uh_ubus_session_destroy(state, ses); +	} +} + + +static int +uh_ubus_handle_create(struct ubus_context *ctx, struct ubus_object *obj, +					  struct ubus_request_data *req, const char *method, +					  struct blob_attr *msg) +{ +	struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus); +	struct uh_ubus_session *ses; +	struct blob_attr *tb[__UH_UBUS_SN_MAX]; + +	int timeout = state->timeout; + +	blobmsg_parse(new_policy, __UH_UBUS_SN_MAX, tb, blob_data(msg), blob_len(msg)); + +	/* TODO: make this a uloop timeout */ +	uh_ubus_session_cleanup(state); + +	if (tb[UH_UBUS_SN_TIMEOUT]) +		timeout = *(uint32_t *)blobmsg_data(tb[UH_UBUS_SN_TIMEOUT]); + +	ses = uh_ubus_session_create(state, timeout); + +	if (ses) +		uh_ubus_session_dump(ses, ctx, req); + +	return 0; +} + +static int +uh_ubus_handle_list(struct ubus_context *ctx, struct ubus_object *obj, +					struct ubus_request_data *req, const char *method, +					struct blob_attr *msg) +{ +	struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus); +	struct uh_ubus_session *ses; +	struct blob_attr *tb[__UH_UBUS_SI_MAX]; + +	blobmsg_parse(sid_policy, __UH_UBUS_SI_MAX, tb, blob_data(msg), blob_len(msg)); + +	/* TODO: make this a uloop timeout */ +	uh_ubus_session_cleanup(state); + +	if (!tb[UH_UBUS_SI_SID]) +	{ +		avl_for_each_element(&state->sessions, ses, avl) +			uh_ubus_session_dump(ses, ctx, req); +	} +	else +	{ +		ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SI_SID])); + +		if (!ses) +			return UBUS_STATUS_NOT_FOUND; + +		uh_ubus_session_dump(ses, ctx, req); +	} + +	return 0; +} + + +static int +uh_ubus_session_grant(struct uh_ubus_session *ses, struct ubus_context *ctx, +					  const char *object, const char *function) +{ +	struct uh_ubus_session_acl *acl, *nacl; + +	acl = avl_find_element(&ses->acls, object, acl, avl); + +	if (acl) +	{ +		avl_for_element_to_last(&ses->acls, acl, acl, avl) +		{ +			if (!strcmp(acl->function, function)) +				return 1; +		} +	} + +	nacl = malloc(sizeof(*nacl) + strlen(object) + strlen(function) + 2); + +	if (nacl) +	{ +		memset(nacl, 0, sizeof(*nacl)); +		nacl->function = nacl->object + 1; +		nacl->function += sprintf(nacl->object, "%s", object); +		sprintf(nacl->function, "%s", function); + +		nacl->avl.key = nacl->object; +		avl_insert(&ses->acls, &nacl->avl); +	} + +	return 0; +} + +static int +uh_ubus_session_revoke(struct uh_ubus_session *ses, struct ubus_context *ctx, +					   const char *object, const char *function) +{ +	struct uh_ubus_session_acl *acl, *nacl; + +	if (!object && !function) +	{ +		avl_remove_all_elements(&ses->acls, acl, avl, nacl) +			free(acl); +	} +	else +	{ +		avl_for_each_element_safe(&ses->acls, acl, avl, nacl) +		{ +			if (uh_ubus_strmatch(acl->object, object) && +				uh_ubus_strmatch(acl->function, function)) +			{ +				avl_delete(&ses->acls, &acl->avl); +				free(acl); +			} +		} +	} + +	return 0; +} + + +static int +uh_ubus_handle_grant(struct ubus_context *ctx, struct ubus_object *obj, +					 struct ubus_request_data *req, const char *method, +					 struct blob_attr *msg) +{ +	struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus); +	struct uh_ubus_session *ses; +	struct blob_attr *tb[__UH_UBUS_SA_MAX]; +	struct blob_attr *attr, *sattr; +	const char *object, *function; +	int rem1, rem2; + +	blobmsg_parse(acl_policy, __UH_UBUS_SA_MAX, tb, blob_data(msg), blob_len(msg)); + +	if (!tb[UH_UBUS_SA_SID] || !tb[UH_UBUS_SA_OBJECTS]) +		return UBUS_STATUS_INVALID_ARGUMENT; + +	ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SA_SID])); + +	if (!ses) +		return UBUS_STATUS_NOT_FOUND; + +	blobmsg_for_each_attr(attr, tb[UH_UBUS_SA_OBJECTS], rem1) +	{ +		if (blob_id(attr) != BLOBMSG_TYPE_ARRAY) +			continue; + +		object = NULL; +		function = NULL; + +		blobmsg_for_each_attr(sattr, attr, rem2) +		{ +			if (blob_id(sattr) != BLOBMSG_TYPE_STRING) +				continue; + +			if (!object) +				object = blobmsg_data(sattr); +			else if (!function) +				function = blobmsg_data(sattr); +			else +				break; +		} + +		if (object && function) +			uh_ubus_session_grant(ses, ctx, object, function); +	} + +	return 0; +} + +static int +uh_ubus_handle_revoke(struct ubus_context *ctx, struct ubus_object *obj, +					  struct ubus_request_data *req, const char *method, +					  struct blob_attr *msg) +{ +	struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus); +	struct uh_ubus_session *ses; +	struct blob_attr *tb[__UH_UBUS_SA_MAX]; +	struct blob_attr *attr, *sattr; +	const char *object, *function; +	int rem1, rem2; + +	blobmsg_parse(acl_policy, __UH_UBUS_SA_MAX, tb, blob_data(msg), blob_len(msg)); + +	if (!tb[UH_UBUS_SA_SID]) +		return UBUS_STATUS_INVALID_ARGUMENT; + +	ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SA_SID])); + +	if (!ses) +		return UBUS_STATUS_NOT_FOUND; + +	if (!tb[UH_UBUS_SA_OBJECTS]) +	{ +		uh_ubus_session_revoke(ses, ctx, NULL, NULL); +	} +	else +	{ +		blobmsg_for_each_attr(attr, tb[UH_UBUS_SA_OBJECTS], rem1) +		{ +			if (blob_id(attr) != BLOBMSG_TYPE_ARRAY) +				continue; + +			object = NULL; +			function = NULL; + +			blobmsg_for_each_attr(sattr, attr, rem2) +			{ +				if (blob_id(sattr) != BLOBMSG_TYPE_STRING) +					continue; + +				if (!object) +					object = blobmsg_data(sattr); +				else if (!function) +					function = blobmsg_data(sattr); +				else +					break; +			} + +			if (object && function) +				uh_ubus_session_revoke(ses, ctx, object, function); +		} +	} + +	return 0; +} + +static int +uh_ubus_handle_set(struct ubus_context *ctx, struct ubus_object *obj, +				   struct ubus_request_data *req, const char *method, +				   struct blob_attr *msg) +{ +	struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus); +	struct uh_ubus_session *ses; +	struct uh_ubus_session_data *data; +	struct blob_attr *tb[__UH_UBUS_SA_MAX]; +	struct blob_attr *attr; +	int rem; + +	blobmsg_parse(set_policy, __UH_UBUS_SS_MAX, tb, blob_data(msg), blob_len(msg)); + +	if (!tb[UH_UBUS_SS_SID] || !tb[UH_UBUS_SS_VALUES]) +		return UBUS_STATUS_INVALID_ARGUMENT; + +	ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SS_SID])); + +	if (!ses) +		return UBUS_STATUS_NOT_FOUND; + +	blobmsg_for_each_attr(attr, tb[UH_UBUS_SS_VALUES], rem) +	{ +		if (!blobmsg_name(attr)[0]) +			continue; + +		data = avl_find_element(&ses->data, blobmsg_name(attr), data, avl); + +		if (data) +		{ +			avl_delete(&ses->data, &data->avl); +			free(data); +		} + +		data = malloc(sizeof(*data) + blob_pad_len(attr)); + +		if (!data) +			break; + +		memset(data, 0, sizeof(*data) + blob_pad_len(attr)); +		memcpy(data->attr, attr, blob_pad_len(attr)); + +		data->avl.key = blobmsg_name(data->attr); +		avl_insert(&ses->data, &data->avl); +	} + +	return 0; +} + +static int +uh_ubus_handle_get(struct ubus_context *ctx, struct ubus_object *obj, +				   struct ubus_request_data *req, const char *method, +				   struct blob_attr *msg) +{ +	struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus); +	struct uh_ubus_session *ses; +	struct uh_ubus_session_data *data; +	struct blob_attr *tb[__UH_UBUS_SA_MAX]; +	struct blob_attr *attr; +	struct blob_buf b; +	void *c; +	int rem; + +	blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg)); + +	if (!tb[UH_UBUS_SG_SID]) +		return UBUS_STATUS_INVALID_ARGUMENT; + +	ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SG_SID])); + +	if (!ses) +		return UBUS_STATUS_NOT_FOUND; + +	memset(&b, 0, sizeof(b)); +	blob_buf_init(&b, 0); +	c = blobmsg_open_table(&b, "values"); + +	if (!tb[UH_UBUS_SG_KEYS]) +	{ +		uh_ubus_session_dump_data(ses, &b); +	} +	else +	{ +		blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem) +		{ +			if (blob_id(attr) != BLOBMSG_TYPE_STRING) +				continue; + +			data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl); + +			if (!data) +				continue; + +			blobmsg_add_field(&b, blobmsg_type(data->attr), +							  blobmsg_name(data->attr), +							  blobmsg_data(data->attr), +							  blobmsg_data_len(data->attr)); +		} +	} + +	blobmsg_close_table(&b, c); +	ubus_send_reply(ctx, req, b.head); +	blob_buf_free(&b); + +	return 0; +} + +static int +uh_ubus_handle_unset(struct ubus_context *ctx, struct ubus_object *obj, +				     struct ubus_request_data *req, const char *method, +				     struct blob_attr *msg) +{ +	struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus); +	struct uh_ubus_session *ses; +	struct uh_ubus_session_data *data, *ndata; +	struct blob_attr *tb[__UH_UBUS_SA_MAX]; +	struct blob_attr *attr; +	int rem; + +	blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg)); + +	if (!tb[UH_UBUS_SG_SID]) +		return UBUS_STATUS_INVALID_ARGUMENT; + +	ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SG_SID])); + +	if (!ses) +		return UBUS_STATUS_NOT_FOUND; + +	if (!tb[UH_UBUS_SG_KEYS]) +	{ +		avl_remove_all_elements(&ses->data, data, avl, ndata) +			free(data); +	} +	else +	{ +		blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem) +		{ +			if (blob_id(attr) != BLOBMSG_TYPE_STRING) +				continue; + +			data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl); + +			if (!data) +				continue; + +			avl_delete(&ses->data, &data->avl); +			free(data); +		} +	} + +	return 0; +} + +static int +uh_ubus_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj, +					   struct ubus_request_data *req, const char *method, +					   struct blob_attr *msg) +{ +	struct uh_ubus_state *state = container_of(obj, struct uh_ubus_state, ubus); +	struct uh_ubus_session *ses; +	struct blob_attr *tb[__UH_UBUS_SA_MAX]; + +	blobmsg_parse(sid_policy, __UH_UBUS_SI_MAX, tb, blob_data(msg), blob_len(msg)); + +	if (!tb[UH_UBUS_SI_SID]) +		return UBUS_STATUS_INVALID_ARGUMENT; + +	ses = uh_ubus_session_get(state, blobmsg_data(tb[UH_UBUS_SI_SID])); + +	if (!ses) +		return UBUS_STATUS_NOT_FOUND; + +	uh_ubus_session_destroy(state, ses); + +	return 0; +} + + +struct uh_ubus_state * +uh_ubus_init(const struct config *conf) +{ +	int rv; +	struct uh_ubus_state *state; +	struct ubus_object *session_object; + +	static struct ubus_method session_methods[] = { +		UBUS_METHOD("create",  uh_ubus_handle_create,  new_policy), +		UBUS_METHOD("list",    uh_ubus_handle_list,    sid_policy), +		UBUS_METHOD("grant",   uh_ubus_handle_grant,   acl_policy), +		UBUS_METHOD("revoke",  uh_ubus_handle_revoke,  acl_policy), +		UBUS_METHOD("set",     uh_ubus_handle_set,     set_policy), +		UBUS_METHOD("get",     uh_ubus_handle_get,     get_policy), +		UBUS_METHOD("unset",   uh_ubus_handle_unset,   get_policy), +		UBUS_METHOD("destroy", uh_ubus_handle_destroy, sid_policy), +	}; + +	static struct ubus_object_type session_type = +		UBUS_OBJECT_TYPE("uhttpd", session_methods); + +	state = malloc(sizeof(*state)); + +	if (!state) +	{ +		fprintf(stderr, "Unable to allocate memory for ubus state\n"); +		exit(1); +	} + +	memset(state, 0, sizeof(*state)); +	state->ctx = ubus_connect(conf->ubus_socket); +	state->timeout = conf->script_timeout; + +	if (!state->ctx) +	{ +		fprintf(stderr, "Unable to connect to ubus socket\n"); +		exit(1); +	} + +	ubus_add_uloop(state->ctx); + +	session_object = &state->ubus; +	session_object->name = "session"; +	session_object->type = &session_type; +	session_object->methods = session_methods; +	session_object->n_methods = ARRAY_SIZE(session_methods); + +	rv = ubus_add_object(state->ctx, &state->ubus); + +	if (rv) +	{ +		fprintf(stderr, "Unable to publish ubus object: %s\n", +				ubus_strerror(rv)); +		exit(1); +	} + +	blob_buf_init(&state->buf, 0); +	avl_init(&state->sessions, uh_ubus_avlcmp, false, NULL); + +	return state; +} + + +static bool +uh_ubus_request_parse_url(struct client *cl, char **sid, char **obj, char **fun) +{ +	char *url = cl->request.url + strlen(cl->server->conf->ubus_prefix); + +	for (; url && *url == '/'; *url++ = 0); +	*sid = url; + +	for (url = url ? strchr(url, '/') : NULL; url && *url == '/'; *url++ = 0); +	*obj = url; + +	for (url = url ? strchr(url, '/') : NULL; url && *url == '/'; *url++ = 0); +	*fun = url; + +	for (url = url ? strchr(url, '/') : NULL; url && *url == '/'; *url++ = 0); +	return (*sid && *obj && *fun); +} + +static bool +uh_ubus_request_parse_post(struct client *cl, int len, struct blob_buf *b) +{ +	int rlen; +	bool rv = false; +	char buf[UH_LIMIT_MSGHEAD]; + +	struct json_object *obj = NULL; +	struct json_tokener *tok = NULL; + +	if (!len) +		return NULL; + +	memset(b, 0, sizeof(*b)); +	blob_buf_init(b, 0); + +	tok = json_tokener_new(); + +	while (len > 0) +	{ +		/* remaining data in http head buffer ... */ +		if (cl->httpbuf.len > 0) +		{ +			rlen = min(len, cl->httpbuf.len); + +			D("ubus: feed %d HTTP buffer bytes\n", rlen); + +			memcpy(buf, cl->httpbuf.ptr, rlen); + +			cl->httpbuf.len -= rlen; +			cl->httpbuf.ptr += rlen; +		} + +		/* read it from socket ... */ +		else +		{ +			ensure_out(rlen = uh_tcp_recv(cl, buf, min(len, sizeof(buf)))); + +			if ((rlen < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) +				break; + +			D("ubus: feed %d/%d TCP socket bytes\n", +			  rlen, min(len, sizeof(buf))); +		} + +		obj = json_tokener_parse_ex(tok, buf, rlen); +		len -= rlen; + +		if (tok->err != json_tokener_continue && !is_error(obj)) +			break; +	} + +out: +	if (!is_error(obj)) +	{ +		if (json_object_get_type(obj) == json_type_object) +		{ +			rv = true; +			json_object_object_foreach(obj, key, val) +			{ +				if (!blobmsg_add_json_element(b, key, val)) +				{ +					rv = false; +					break; +				} +			} +		} + +		json_object_put(obj); +	} + +	json_tokener_free(tok); + +	if (!rv) +		blob_buf_free(b); + +	return rv; +} + +static void +uh_ubus_request_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ +	int len; +	char *str; +	struct client *cl = (struct client *)req->priv; + +	if (!msg) +	{ +		uh_http_sendhf(cl, 204, "No content", "Function did not return data\n"); +		return; +	} + +	str = blobmsg_format_json_indent(msg, true, 0); +	len = strlen(str); + +	ensure_out(uh_http_sendf(cl, NULL, "HTTP/1.0 200 OK\r\n")); +	ensure_out(uh_http_sendf(cl, NULL, "Content-Type: application/json\r\n")); +	ensure_out(uh_http_sendf(cl, NULL, "Content-Length: %i\r\n\r\n", len)); +	ensure_out(uh_http_send(cl, NULL, str, len)); + +out: +	free(str); +} + +bool +uh_ubus_request(struct client *cl, struct uh_ubus_state *state) +{ +	int i, len = 0; +	bool access = false; +	char *sid, *obj, *fun; + +	struct blob_buf buf; +	struct uh_ubus_session *ses; +	struct uh_ubus_session_acl *acl; + +	uint32_t obj_id; + + +	memset(&buf, 0, sizeof(buf)); +	blob_buf_init(&buf, 0); + +	if (!uh_ubus_request_parse_url(cl, &sid, &obj, &fun)) +	{ +		uh_http_sendhf(cl, 400, "Bad Request", "Invalid Request\n"); +		goto out; +	} + +	if (!(ses = uh_ubus_session_get(state, sid))) +	{ +		uh_http_sendhf(cl, 404, "Not Found", "No such session\n"); +		goto out; +	} + +	avl_for_each_element(&ses->acls, acl, avl) +	{ +		if (uh_ubus_strmatch(obj, acl->object) && +			uh_ubus_strmatch(fun, acl->function)) +		{ +			access = true; +			break; +		} +	} + +	if (!access) +	{ +		uh_http_sendhf(cl, 403, "Denied", "Access to object denied\n"); +		goto out; +	} + +	/* find content length */ +	if (cl->request.method == UH_HTTP_MSG_POST) +	{ +		foreach_header(i, cl->request.headers) +		{ +			if (!strcasecmp(cl->request.headers[i], "Content-Length")) +			{ +				len = atoi(cl->request.headers[i+1]); +				break; +			} +		} +	} + +	if (len > UH_UBUS_MAX_POST_SIZE) +	{ +		uh_http_sendhf(cl, 413, "Too Large", "Message too big\n"); +		goto out; +	} + +	if (len && !uh_ubus_request_parse_post(cl, len, &buf)) +	{ +		uh_http_sendhf(cl, 400, "Bad Request", "Invalid JSON data\n"); +		goto out; +	} + +	if (ubus_lookup_id(state->ctx, obj, &obj_id)) +	{ +		uh_http_sendhf(cl, 500, "Internal Error", "Unable to lookup object\n"); +		goto out; +	} + +	if (ubus_invoke(state->ctx, obj_id, fun, buf.head, +					uh_ubus_request_cb, cl, state->timeout * 1000)) +	{ +		uh_http_sendhf(cl, 500, "Internal Error", "Unable to invoke function\n"); +		goto out; +	} + +out: +	blob_buf_free(&buf); +	return false; +} + +void +uh_ubus_close(struct uh_ubus_state *state) +{ +	if (state->ctx) +		ubus_free(state->ctx); + +	free(state); +} diff --git a/package/uhttpd/src/uhttpd-ubus.h b/package/uhttpd/src/uhttpd-ubus.h new file mode 100644 index 000000000..777ce27fd --- /dev/null +++ b/package/uhttpd/src/uhttpd-ubus.h @@ -0,0 +1,70 @@ +/* + * uhttpd - Tiny single-threaded httpd - ubus header + * + *   Copyright (C) 2012 Jo-Philipp Wich <xm@subsignal.org> + * + *  Licensed under the Apache License, Version 2.0 (the "License"); + *  you may not use this file except in compliance with the License. + *  You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + *  Unless required by applicable law or agreed to in writing, software + *  distributed under the License is distributed on an "AS IS" BASIS, + *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *  See the License for the specific language governing permissions and + *  limitations under the License. + */ + +#ifndef _UHTTPD_UBUS_ + +#include <time.h> + +#include <libubus.h> +#include <libubox/avl.h> +#include <libubox/blobmsg_json.h> +#include <json/json.h> + + +#define UH_UBUS_MAX_POST_SIZE	4096 + + +struct uh_ubus_state { +	struct ubus_context *ctx; +	struct ubus_object ubus; +	struct blob_buf buf; +	struct avl_tree sessions; +	int timeout; +}; + +struct uh_ubus_request_data { +	const char *sid; +	const char *object; +	const char *function; +}; + +struct uh_ubus_session { +	char id[33]; +	int timeout; +	struct avl_node avl; +	struct avl_tree data; +	struct avl_tree acls; +	struct timespec touched; +}; + +struct uh_ubus_session_data { +	struct avl_node avl; +	struct blob_attr attr[]; +}; + +struct uh_ubus_session_acl { +	struct avl_node avl; +	char *function; +	char object[]; +}; + +struct uh_ubus_state * uh_ubus_init(const struct config *conf); +bool uh_ubus_request(struct client *cl, struct uh_ubus_state *state); +void uh_ubus_close(struct uh_ubus_state *state); + +#endif diff --git a/package/uhttpd/src/uhttpd-utils.c b/package/uhttpd/src/uhttpd-utils.c index 18969e74d..dec952357 100644 --- a/package/uhttpd/src/uhttpd-utils.c +++ b/package/uhttpd/src/uhttpd-utils.c @@ -1,7 +1,7 @@  /*   * uhttpd - Tiny single-threaded httpd - Utility functions   * - *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org> + *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>   *   *  Licensed under the Apache License, Version 2.0 (the "License");   *  you may not use this file except in compliance with the License. @@ -103,120 +103,171 @@ char *strfind(char *haystack, int hslen, const char *needle, int ndlen)  	return NULL;  } -/* interruptable select() */ -int select_intr(int n, fd_set *r, fd_set *w, fd_set *e, struct timeval *t) +bool uh_socket_wait(int fd, int sec, bool write)  {  	int rv; -	sigset_t ssn, sso; +	struct timeval timeout; -	/* unblock SIGCHLD */ -	sigemptyset(&ssn); -	sigaddset(&ssn, SIGCHLD); -	sigaddset(&ssn, SIGPIPE); -	sigprocmask(SIG_UNBLOCK, &ssn, &sso); +	fd_set fds; -	rv = select(n, r, w, e, t); +	FD_ZERO(&fds); +	FD_SET(fd, &fds); -	/* restore signal mask */ -	sigprocmask(SIG_SETMASK, &sso, NULL); +	timeout.tv_sec = sec; +	timeout.tv_usec = 0; -	return rv; -} +	while (((rv = select(fd+1, write ? NULL : &fds, write ? &fds : NULL, +						 NULL, &timeout)) < 0) && (errno == EINTR)) +	{ +		D("IO: Socket(%d) select interrupted: %s\n", +				fd, strerror(errno)); +		continue; +	} -int uh_tcp_send_lowlevel(struct client *cl, const char *buf, int len) -{ -	fd_set writer; -	struct timeval timeout; +	if (rv <= 0) +	{ +		D("IO: Socket(%d) appears dead (rv=%d)\n", fd, rv); +		return false; +	} + +	return true; +} -	FD_ZERO(&writer); -	FD_SET(cl->socket, &writer); +static int __uh_raw_send(struct client *cl, const char *buf, int len, int sec, +						 int (*wfn) (struct client *, const char *, int)) +{ +	ssize_t rv; +	int fd = cl->fd.fd; -	timeout.tv_sec = cl->server->conf->network_timeout; -	timeout.tv_usec = 0; +	while (true) +	{ +		if ((rv = wfn(cl, buf, len)) < 0) +		{ +			if (errno == EINTR) +			{ +				D("IO: Socket(%d) interrupted\n", cl->fd.fd); +				continue; +			} +			else if ((sec > 0) && (errno == EAGAIN || errno == EWOULDBLOCK)) +			{ +				if (!uh_socket_wait(fd, sec, true)) +					return -1; +			} +			else +			{ +				D("IO: Socket(%d) write error: %s\n", fd, strerror(errno)); +				return -1; +			} +		} +		/* +		 * It is not entirely clear whether rv = 0 on nonblocking sockets +		 * is an error. In real world fuzzing tests, not handling it as close +		 * led to tight infinite loops in this send procedure, so treat it as +		 * closed and break out. +		 */ +		else if (rv == 0) +		{ +			D("IO: Socket(%d) closed\n", fd); +			return 0; +		} +		else if (rv < len) +		{ +			D("IO: Socket(%d) short write %d/%d bytes\n", fd, rv, len); +			len -= rv; +			buf += rv; +			continue; +		} +		else +		{ +			D("IO: Socket(%d) sent %d/%d bytes\n", fd, rv, len); +			return rv; +		} +	} +} -	if (select(cl->socket + 1, NULL, &writer, NULL, &timeout) > 0) -		return send(cl->socket, buf, len, 0); +int uh_tcp_send_lowlevel(struct client *cl, const char *buf, int len) +{ +	return write(cl->fd.fd, buf, len); +} -	return -1; +int uh_raw_send(int fd, const char *buf, int len, int sec) +{ +	struct client_light cl = { .fd = { .fd = fd } }; +	return __uh_raw_send((struct client *)&cl, buf, len, sec, +						 uh_tcp_send_lowlevel);  }  int uh_tcp_send(struct client *cl, const char *buf, int len)  { +	int seconds = cl->server->conf->network_timeout;  #ifdef HAVE_TLS  	if (cl->tls) -		return cl->server->conf->tls_send(cl, (void *)buf, len); -	else +		return __uh_raw_send(cl, buf, len, seconds, +							 cl->server->conf->tls_send);  #endif -		return uh_tcp_send_lowlevel(cl, buf, len); +	return __uh_raw_send(cl, buf, len, seconds, uh_tcp_send_lowlevel);  } -int uh_tcp_peek(struct client *cl, char *buf, int len) +static int __uh_raw_recv(struct client *cl, char *buf, int len, int sec, +						 int (*rfn) (struct client *, char *, int))  { -	/* sanity check, prevent overflowing peek buffer */ -	if (len > sizeof(cl->peekbuf)) -		return -1; - -	int sz = uh_tcp_recv(cl, buf, len); +	ssize_t rv; +	int fd = cl->fd.fd; -	/* store received data in peek buffer */ -	if (sz > 0) +	while (true)  	{ -		cl->peeklen = sz; -		memcpy(cl->peekbuf, buf, sz); +		if ((rv = rfn(cl, buf, len)) < 0) +		{ +			if (errno == EINTR) +			{ +				continue; +			} +			else if ((sec > 0) && (errno == EAGAIN || errno == EWOULDBLOCK)) +			{ +				if (!uh_socket_wait(fd, sec, false)) +					return -1; +			} +			else +			{ +				D("IO: Socket(%d) read error: %s\n", fd, strerror(errno)); +				return -1; +			} +		} +		else if (rv == 0) +		{ +			D("IO: Socket(%d) closed\n", fd); +			return 0; +		} +		else +		{ +			D("IO: Socket(%d) read %d bytes\n", fd, rv); +			return rv; +		}  	} - -	return sz;  }  int uh_tcp_recv_lowlevel(struct client *cl, char *buf, int len)  { -	fd_set reader; -	struct timeval timeout; - -	FD_ZERO(&reader); -	FD_SET(cl->socket, &reader); - -	timeout.tv_sec  = cl->server->conf->network_timeout; -	timeout.tv_usec = 0; - -	if (select(cl->socket + 1, &reader, NULL, NULL, &timeout) > 0) -		return recv(cl->socket, buf, len, 0); +	return read(cl->fd.fd, buf, len); +} -	return -1; +int uh_raw_recv(int fd, char *buf, int len, int sec) +{ +	struct client_light cl = { .fd = { .fd = fd } }; +	return __uh_raw_recv((struct client *)&cl, buf, len, sec, +						 uh_tcp_recv_lowlevel);  }  int uh_tcp_recv(struct client *cl, char *buf, int len)  { -	int sz = 0; -	int rsz = 0; - -	/* first serve data from peek buffer */ -	if (cl->peeklen > 0) -	{ -		sz = min(cl->peeklen, len); -		len -= sz; cl->peeklen -= sz; -		memcpy(buf, cl->peekbuf, sz); -		memmove(cl->peekbuf, &cl->peekbuf[sz], cl->peeklen); -	} - -	/* caller wants more */ -	if (len > 0) -	{ +	int seconds = cl->server->conf->network_timeout;  #ifdef HAVE_TLS -		if (cl->tls) -			rsz = cl->server->conf->tls_recv(cl, (void *)&buf[sz], len); -		else +	if (cl->tls) +		return __uh_raw_recv(cl, buf, len, seconds, +							 cl->server->conf->tls_recv);  #endif -			rsz = uh_tcp_recv_lowlevel(cl, (void *)&buf[sz], len); - -		if (rsz < 0) -			return rsz; - -		sz += rsz; -	} - -	return sz; +	return __uh_raw_recv(cl, buf, len, seconds, uh_tcp_recv_lowlevel);  } @@ -841,8 +892,9 @@ struct listener * uh_listener_add(int sock, struct config *conf)  	{  		memset(new, 0, sizeof(struct listener)); -		new->socket = sock; -		new->conf   = conf; +		new->fd.fd = sock; +		new->conf  = conf; +  		/* get local endpoint addr */  		sl = sizeof(struct sockaddr_in6); @@ -863,7 +915,7 @@ struct listener * uh_listener_lookup(int sock)  	struct listener *cur = NULL;  	for (cur = uh_listeners; cur; cur = cur->next) -		if (cur->socket == sock) +		if (cur->fd.fd == sock)  			return cur;  	return NULL; @@ -879,7 +931,7 @@ struct client * uh_client_add(int sock, struct listener *serv)  	{  		memset(new, 0, sizeof(struct client)); -		new->socket = sock; +		new->fd.fd  = sock;  		new->server = serv;  		/* get remote endpoint addr */ @@ -894,6 +946,8 @@ struct client * uh_client_add(int sock, struct listener *serv)  		new->next = uh_clients;  		uh_clients = new; + +		serv->n_clients++;  	}  	return new; @@ -904,26 +958,50 @@ struct client * uh_client_lookup(int sock)  	struct client *cur = NULL;  	for (cur = uh_clients; cur; cur = cur->next) -		if (cur->socket == sock) +		if (cur->fd.fd == sock)  			return cur;  	return NULL;  } -void uh_client_remove(int sock) +void uh_client_shutdown(struct client *cl) +{ +#ifdef HAVE_TLS +	/* free client tls context */ +	if (cl->server && cl->server->conf->tls) +		cl->server->conf->tls_close(cl); +#endif + +	/* remove from global client list */ +	uh_client_remove(cl); +} + +void uh_client_remove(struct client *cl)  {  	struct client *cur = NULL;  	struct client *prv = NULL;  	for (cur = uh_clients; cur; prv = cur, cur = cur->next)  	{ -		if (cur->socket == sock) +		if ((cur == cl) || (!cl && cur->dead))  		{  			if (prv)  				prv->next = cur->next;  			else  				uh_clients = cur->next; +			if (cur->timeout.pending) +				uloop_timeout_cancel(&cur->timeout); + +			if (cur->proc.pid) +				uloop_process_delete(&cur->proc); + +			uloop_fd_delete(&cur->fd); +			close(cur->fd.fd); + +			D("IO: Socket(%d) closing\n", cur->fd.fd); +			cur->server->n_clients--; +  			free(cur);  			break;  		} diff --git a/package/uhttpd/src/uhttpd-utils.h b/package/uhttpd/src/uhttpd-utils.h index a2cac35ac..797b07def 100644 --- a/package/uhttpd/src/uhttpd-utils.h +++ b/package/uhttpd/src/uhttpd-utils.h @@ -1,7 +1,7 @@  /*   * uhttpd - Tiny single-threaded httpd - Utility header   * - *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org> + *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>   *   *  Licensed under the Apache License, Version 2.0 (the "License");   *  you may not use this file except in compliance with the License. @@ -39,6 +39,9 @@  #define fd_cloexec(fd) \  	fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC) +#define fd_nonblock(fd) \ +	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) +  #define ensure_out(x) \  	do { if((x) < 0) goto out; } while(0) @@ -64,18 +67,17 @@ int sa_rfc1918(void *sa);  char *strfind(char *haystack, int hslen, const char *needle, int ndlen); -int select_intr(int n, fd_set *r, fd_set *w, fd_set *e, struct timeval *t); +bool uh_socket_wait(int fd, int sec, bool write); +int uh_raw_send(int fd, const char *buf, int len, int seconds); +int uh_raw_recv(int fd, char *buf, int len, int seconds);  int uh_tcp_send(struct client *cl, const char *buf, int len);  int uh_tcp_send_lowlevel(struct client *cl, const char *buf, int len); -int uh_tcp_peek(struct client *cl, char *buf, int len);  int uh_tcp_recv(struct client *cl, char *buf, int len);  int uh_tcp_recv_lowlevel(struct client *cl, char *buf, int len); -int uh_http_sendhf( -	struct client *cl, int code, const char *summary, -	const char *fmt, ... -); +int uh_http_sendhf(struct client *cl, int code, const char *summary, +				   const char *fmt, ...);  #define uh_http_response(cl, code, message) \  	uh_http_sendhf(cl, code, message, message) @@ -112,7 +114,17 @@ struct listener * uh_listener_lookup(int sock);  struct client * uh_client_add(int sock, struct listener *serv);  struct client * uh_client_lookup(int sock); -void uh_client_remove(int sock); + +#define uh_client_error(cl, code, status, ...) do { \ +	uh_http_sendhf(cl, code, status, __VA_ARGS__);  \ +	uh_client_shutdown(cl);                         \ +} while(0) + +void uh_client_shutdown(struct client *cl); +void uh_client_remove(struct client *cl); + +#define uh_client_gc() uh_client_remove(NULL) +  #ifdef HAVE_CGI  struct interpreter * uh_interpreter_add(const char *extn, const char *path); diff --git a/package/uhttpd/src/uhttpd.c b/package/uhttpd/src/uhttpd.c index 059281138..e10f5dc9e 100644 --- a/package/uhttpd/src/uhttpd.c +++ b/package/uhttpd/src/uhttpd.c @@ -42,11 +42,6 @@ static void uh_sigterm(int sig)  	run = 0;  } -static void uh_sigchld(int sig) -{ -	while (waitpid(-1, NULL, WNOHANG) > 0) { } -} -  static void uh_config_parse(struct config *conf)  {  	FILE *c; @@ -126,6 +121,8 @@ static void uh_config_parse(struct config *conf)  	}  } +static void uh_listener_cb(struct uloop_fd *u, unsigned int events); +  static int uh_socket_bind(fd_set *serv_fds, int *max_fd,  						  const char *host, const char *port,  						  struct addrinfo *hints, int do_tls, @@ -221,6 +218,9 @@ static int uh_socket_bind(fd_set *serv_fds, int *max_fd,  		fd_cloexec(sock);  		*max_fd = max(*max_fd, sock); +		l->fd.cb = uh_listener_cb; +		uloop_fd_add(&l->fd, ULOOP_READ | ULOOP_WRITE); +  		bound++;  		continue; @@ -237,7 +237,7 @@ static int uh_socket_bind(fd_set *serv_fds, int *max_fd,  static struct http_request * uh_http_header_parse(struct client *cl,  												  char *buffer, int buflen)  { -	char *method  = &buffer[0]; +	char *method  = buffer;  	char *path    = NULL;  	char *version = NULL; @@ -248,9 +248,7 @@ static struct http_request * uh_http_header_parse(struct client *cl,  	int i;  	int hdrcount = 0; -	static struct http_request req; - -	memset(&req, 0, sizeof(req)); +	struct http_request *req = &cl->request;  	/* terminate initial header line */ @@ -282,15 +280,15 @@ static struct http_request * uh_http_header_parse(struct client *cl,  			switch(method[0])  			{  				case 'G': -					req.method = UH_HTTP_MSG_GET; +					req->method = UH_HTTP_MSG_GET;  					break;  				case 'H': -					req.method = UH_HTTP_MSG_HEAD; +					req->method = UH_HTTP_MSG_HEAD;  					break;  				case 'P': -					req.method = UH_HTTP_MSG_POST; +					req->method = UH_HTTP_MSG_POST;  					break;  			}  		} @@ -304,7 +302,7 @@ static struct http_request * uh_http_header_parse(struct client *cl,  		}  		else  		{ -			req.url = path; +			req->url = path;  		}  		/* check version */ @@ -317,9 +315,13 @@ static struct http_request * uh_http_header_parse(struct client *cl,  		}  		else  		{ -			req.version = strtof(&version[5], NULL); +			req->version = strtof(&version[5], NULL);  		} +		D("SRV: %s %s HTTP/%.1f\n", +		  (req->method == UH_HTTP_MSG_POST) ? "POST" : +			(req->method == UH_HTTP_MSG_GET) ? "GET" : "HEAD", +		  req->url, req->version);  		/* process header fields */  		for (i = (int)(headers - buffer); i < buflen; i++) @@ -330,10 +332,12 @@ static struct http_request * uh_http_header_parse(struct client *cl,  				buffer[i] = 0;  				/* store */ -				if ((hdrcount + 1) < array_size(req.headers)) +				if ((hdrcount + 1) < array_size(req->headers))  				{ -					req.headers[hdrcount++] = hdrname; -					req.headers[hdrcount++] = hdrdata; +					D("SRV: HTTP: %s: %s\n", hdrname, hdrdata); + +					req->headers[hdrcount++] = hdrname; +					req->headers[hdrcount++] = hdrdata;  					hdrname = hdrdata = NULL;  				} @@ -341,6 +345,7 @@ static struct http_request * uh_http_header_parse(struct client *cl,  				/* too large */  				else  				{ +					D("SRV: HTTP: header too big (too many headers)\n");  					uh_http_response(cl, 413, "Request Entity Too Large");  					return NULL;  				} @@ -365,8 +370,8 @@ static struct http_request * uh_http_header_parse(struct client *cl,  		}  		/* valid enough */ -		req.redirect_status = 200; -		return &req; +		req->redirect_status = 200; +		return req;  	}  	/* Malformed request */ @@ -377,64 +382,43 @@ static struct http_request * uh_http_header_parse(struct client *cl,  static struct http_request * uh_http_header_recv(struct client *cl)  { -	static char buffer[UH_LIMIT_MSGHEAD]; -	char *bufptr = &buffer[0]; +	char *bufptr = cl->httpbuf.buf;  	char *idxptr = NULL; -	struct timeval timeout; - -	fd_set reader; - -	ssize_t blen = sizeof(buffer)-1; +	ssize_t blen = sizeof(cl->httpbuf)-1;  	ssize_t rlen = 0; -	memset(buffer, 0, sizeof(buffer)); +	memset(bufptr, 0, sizeof(cl->httpbuf));  	while (blen > 0)  	{ -		FD_ZERO(&reader); -		FD_SET(cl->socket, &reader); +		/* receive data */ +		ensure_out(rlen = uh_tcp_recv(cl, bufptr, blen)); +		D("SRV: Client(%d) peek(%d) = %d\n", cl->fd.fd, blen, rlen); -		/* fail after 0.1s */ -		timeout.tv_sec  = 0; -		timeout.tv_usec = 100000; - -		/* check whether fd is readable */ -		if (select(cl->socket + 1, &reader, NULL, NULL, &timeout) > 0) +		if (rlen <= 0)  		{ -			/* receive data */ -			ensure_out(rlen = uh_tcp_peek(cl, bufptr, blen)); - -			if ((idxptr = strfind(buffer, sizeof(buffer), "\r\n\r\n", 4))) -			{ -				ensure_out(rlen = uh_tcp_recv(cl, bufptr, -					(int)(idxptr - bufptr) + 4)); - -				/* header read complete ... */ -				blen -= rlen; -				return uh_http_header_parse(cl, buffer, -					sizeof(buffer) - blen - 1); -			} -			else -			{ -				ensure_out(rlen = uh_tcp_recv(cl, bufptr, rlen)); +			D("SRV: Client(%d) dead [%s]\n", cl->fd.fd, strerror(errno)); +			return NULL; +		} -				/* unexpected eof - #7904 */ -				if (rlen == 0) -					return NULL; +		blen -= rlen; +		bufptr += rlen; -				blen -= rlen; -				bufptr += rlen; -			} -		} -		else +		if ((idxptr = strfind(cl->httpbuf.buf, sizeof(cl->httpbuf.buf), +							  "\r\n\r\n", 4)))  		{ -			/* invalid request (unexpected eof/timeout) */ -			return NULL; +			/* header read complete ... */ +			cl->httpbuf.ptr = idxptr + 4; +			cl->httpbuf.len = bufptr - cl->httpbuf.ptr; + +			return uh_http_header_parse(cl, cl->httpbuf.buf, +										(cl->httpbuf.ptr - cl->httpbuf.buf));  		}  	}  	/* request entity too large */ +	D("SRV: HTTP: header too big (buffer exceeded)\n");  	uh_http_response(cl, 413, "Request Entity Too Large");  out: @@ -456,197 +440,276 @@ static int uh_path_match(const char *prefix, const char *url)  }  #endif -static void uh_dispatch_request(struct client *cl, struct http_request *req, -								struct path_info *pin) +static bool uh_dispatch_request(struct client *cl, struct http_request *req)  { -#ifdef HAVE_CGI +	struct path_info *pin;  	struct interpreter *ipr = NULL; +	struct config *conf = cl->server->conf; -	if (uh_path_match(cl->server->conf->cgi_prefix, pin->name) || -		(ipr = uh_interpreter_lookup(pin->phys))) +#ifdef HAVE_LUA +	/* Lua request? */ +	if (conf->lua_state && +		uh_path_match(conf->lua_prefix, req->url))  	{ -		uh_cgi_request(cl, req, pin, ipr); +		return conf->lua_request(cl, conf->lua_state);  	}  	else  #endif + +#ifdef HAVE_UBUS +	/* ubus request? */ +	if (conf->ubus_state && +		uh_path_match(conf->ubus_prefix, req->url))  	{ -		uh_file_request(cl, req, pin); +		return conf->ubus_request(cl, conf->ubus_state);  	} +	else +#endif + +	/* dispatch request */ +	if ((pin = uh_path_lookup(cl, req->url)) != NULL) +	{ +		/* auth ok? */ +		if (!pin->redirected && uh_auth_check(cl, req, pin)) +		{ +#ifdef HAVE_CGI +			if (uh_path_match(conf->cgi_prefix, pin->name) || +				(ipr = uh_interpreter_lookup(pin->phys)) != NULL) +			{ +				return uh_cgi_request(cl, pin, ipr); +			} +#endif +			return uh_file_request(cl, pin); +		} +	} + +	/* 404 - pass 1 */ +	else +	{ +		/* Try to invoke an error handler */ +		if ((pin = uh_path_lookup(cl, conf->error_handler)) != NULL) +		{ +			/* auth ok? */ +			if (uh_auth_check(cl, req, pin)) +			{ +				req->redirect_status = 404; +#ifdef HAVE_CGI +				if (uh_path_match(conf->cgi_prefix, pin->name) || +					(ipr = uh_interpreter_lookup(pin->phys)) != NULL) +				{ +					return uh_cgi_request(cl, pin, ipr); +				} +#endif +				return uh_file_request(cl, pin); +			} +		} + +		/* 404 - pass 2 */ +		else +		{ +			uh_http_sendhf(cl, 404, "Not Found", "No such file or directory"); +		} +	} + +	return false;  } -static void uh_mainloop(struct config *conf, fd_set serv_fds, int max_fd) -{ -	/* master file descriptor list */ -	fd_set used_fds, read_fds; +static void uh_client_cb(struct uloop_fd *u, unsigned int events); -	/* working structs */ -	struct http_request *req; -	struct path_info *pin; +static void uh_listener_cb(struct uloop_fd *u, unsigned int events) +{ +	int new_fd; +	struct listener *serv;  	struct client *cl; +	struct config *conf; -	/* maximum file descriptor number */ -	int new_fd, cur_fd = 0; - -	/* clear the master and temp sets */ -	FD_ZERO(&used_fds); -	FD_ZERO(&read_fds); +	serv = container_of(u, struct listener, fd); +	conf = serv->conf; -	/* backup server descriptor set */ -	used_fds = serv_fds; +	/* defer client if maximum number of requests is exceeded */ +	if (serv->n_clients >= conf->max_requests) +		return; -	/* loop */ -	while (run) +	/* handle new connections */ +	if ((new_fd = accept(u->fd, NULL, 0)) != -1)  	{ -		/* create a working copy of the used fd set */ -		read_fds = used_fds; +		D("SRV: Server(%d) accept => Client(%d)\n", u->fd, new_fd); -		/* sleep until socket activity */ -		if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1) +		/* add to global client list */ +		if ((cl = uh_client_add(new_fd, serv)) != NULL)  		{ -			perror("select()"); -			exit(1); -		} +			/* add client socket to global fdset */ +			uloop_fd_add(&cl->fd, ULOOP_READ | ULOOP_WRITE); -		/* 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)) +#ifdef HAVE_TLS +			/* setup client tls context */ +			if (conf->tls)  			{ -				/* is one of our listen sockets */ -				if (FD_ISSET(cur_fd, &serv_fds)) +				if (conf->tls_accept(cl) < 1)  				{ -					/* 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) -							{ -								if (conf->tls_accept(cl) < 1) -								{ -									fprintf(stderr, -											"tls_accept failed, " -											"connection dropped\n"); - -									/* close client socket */ -									close(new_fd); - -									/* remove from global client list */ -									uh_client_remove(new_fd); - -									continue; -								} -							} -#endif +					D("SRV: Client(%d) SSL handshake failed, drop\n", new_fd); -							/* 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); -						} -					} +					/* remove from global client list */ +					uh_client_remove(cl); +					return;  				} +			} +#endif -				/* 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); +			cl->fd.cb = uh_client_cb; +			fd_cloexec(new_fd); +		} -						goto cleanup; -					} +		/* insufficient resources */ +		else +		{ +			fprintf(stderr, "uh_client_add(): Cannot allocate memory\n"); +			close(new_fd); +		} +	} +} -					/* 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 (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 (!pin->redirected && 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"); -							} -						} -					} +static void uh_child_cb(struct uloop_process *p, int rv) +{ +	struct client *cl = container_of(p, struct client, proc); -#ifdef HAVE_TLS -					/* free client tls context */ -					if (conf->tls) -						conf->tls_close(cl); -#endif +	D("SRV: Client(%d) child(%d) is dead\n", cl->fd.fd, cl->proc.pid); -					cleanup: +	cl->dead = true; +	cl->fd.eof = true; +	uh_client_cb(&cl->fd, ULOOP_READ | ULOOP_WRITE); +} -					/* close client socket */ -					close(cur_fd); -					FD_CLR(cur_fd, &used_fds); +static void uh_kill9_cb(struct uloop_timeout *t) +{ +	struct client *cl = container_of(t, struct client, timeout); -					/* remove from global client list */ -					uh_client_remove(cur_fd); -				} +	if (!kill(cl->proc.pid, 0)) +	{ +		D("SRV: Client(%d) child(%d) kill(SIGKILL)...\n", +		  cl->fd.fd, cl->proc.pid); + +		kill(cl->proc.pid, SIGKILL); +	} +} + +static void uh_timeout_cb(struct uloop_timeout *t) +{ +	struct client *cl = container_of(t, struct client, timeout); + +	D("SRV: Client(%d) child(%d) timed out\n", cl->fd.fd, cl->proc.pid); + +	if (!kill(cl->proc.pid, 0)) +	{ +		D("SRV: Client(%d) child(%d) kill(SIGTERM)...\n", +		  cl->fd.fd, cl->proc.pid); + +		kill(cl->proc.pid, SIGTERM); + +		cl->timeout.cb = uh_kill9_cb; +		uloop_timeout_set(&cl->timeout, 1000); +	} +} + +static void uh_client_cb(struct uloop_fd *u, unsigned int events) +{ +	int i; +	struct client *cl; +	struct config *conf; +	struct http_request *req; + +	cl = container_of(u, struct client, fd); +	conf = cl->server->conf; + +	D("SRV: Client(%d) enter callback\n", u->fd); + +	/* undispatched yet */ +	if (!cl->dispatched) +	{ +		/* we have no headers yet and this was a write event, ignore... */ +		if (!(events & ULOOP_READ)) +		{ +			D("SRV: Client(%d) ignoring write event before headers\n", u->fd); +			return; +		} + +		/* attempt to receive and parse headers */ +		if (!(req = uh_http_header_recv(cl))) +		{ +			D("SRV: Client(%d) failed to receive header\n", u->fd); +			uh_client_shutdown(cl); +			return; +		} + +		/* process expect headers */ +		foreach_header(i, req->headers) +		{ +			if (strcasecmp(req->headers[i], "Expect")) +				continue; + +			if (strcasecmp(req->headers[i+1], "100-continue")) +			{ +				D("SRV: Client(%d) unknown expect header (%s)\n", +				  u->fd, req->headers[i+1]); + +				uh_http_response(cl, 417, "Precondition Failed"); +				uh_client_shutdown(cl); +				return; +			} +			else +			{ +				D("SRV: Client(%d) sending HTTP/1.1 100 Continue\n", u->fd); + +				uh_http_sendf(cl, NULL, "HTTP/1.1 100 Continue\r\n\r\n"); +				cl->httpbuf.len = 0; /* client will re-send the body */ +				break;  			}  		} + +		/* RFC1918 filtering */ +		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"); + +			uh_client_shutdown(cl); +			return; +		} + +		/* dispatch request */ +		if (!uh_dispatch_request(cl, req)) +		{ +			D("SRV: Client(%d) failed to dispach request\n", u->fd); +			uh_client_shutdown(cl); +			return; +		} + +		/* request handler spawned a child, register handler */ +		if (cl->proc.pid) +		{ +			D("SRV: Client(%d) child(%d) spawned\n", u->fd, cl->proc.pid); + +			cl->proc.cb = uh_child_cb; +			uloop_process_add(&cl->proc); + +			cl->timeout.cb = uh_timeout_cb; +			uloop_timeout_set(&cl->timeout, conf->script_timeout * 1000); +		} + +		/* header processing complete */ +		D("SRV: Client(%d) dispatched\n", u->fd); +		cl->dispatched = true; +		return;  	} -#ifdef HAVE_LUA -	/* destroy the Lua state */ -	if (conf->lua_state != NULL) -		conf->lua_close(conf->lua_state); -#endif +	if (!cl->cb(cl)) +	{ +		D("SRV: Client(%d) response callback signalized EOF\n", u->fd); +		uh_client_shutdown(cl); +		return; +	}  }  #ifdef HAVE_TLS @@ -710,9 +773,6 @@ int main (int argc, char **argv)  	struct sigaction sa;  	struct config conf; -	/* signal mask */ -	sigset_t ss; -  	/* maximum file descriptor number */  	int cur_fd, max_fd = 0; @@ -736,25 +796,17 @@ int main (int argc, char **argv)  	FD_ZERO(&serv_fds); -	/* handle SIGPIPE, SIGINT, SIGTERM, SIGCHLD */ +	/* handle SIGPIPE, SIGINT, SIGTERM */  	sa.sa_flags = 0;  	sigemptyset(&sa.sa_mask);  	sa.sa_handler = SIG_IGN;  	sigaction(SIGPIPE, &sa, NULL); -	sa.sa_handler = uh_sigchld; -	sigaction(SIGCHLD, &sa, NULL); -  	sa.sa_handler = uh_sigterm;  	sigaction(SIGINT,  &sa, NULL);  	sigaction(SIGTERM, &sa, NULL); -	/* defer SIGCHLD */ -	sigemptyset(&ss); -	sigaddset(&ss, SIGCHLD); -	sigprocmask(SIG_BLOCK, &ss, NULL); -  	/* prepare addrinfo hints */  	memset(&hints, 0, sizeof(hints));  	hints.ai_family   = AF_UNSPEC; @@ -765,9 +817,10 @@ int main (int argc, char **argv)  	memset(&conf, 0, sizeof(conf));  	memset(bind, 0, sizeof(bind)); +	uloop_init();  	while ((opt = getopt(argc, argv, -						 "fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:x:i:t:T:A:")) > 0) +						 "fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:n:x:i:t:T:A:u:U:")) > 0)  	{  		switch(opt)  		{ @@ -894,6 +947,10 @@ int main (int argc, char **argv)  				conf.rfc1918_filter = 1;  				break; +			case 'n': +				conf.max_requests = atoi(optarg); +				break; +  #ifdef HAVE_CGI  			/* cgi prefix */  			case 'x': @@ -928,6 +985,18 @@ int main (int argc, char **argv)  				break;  #endif +#ifdef HAVE_UBUS +			/* ubus prefix */ +			case 'u': +				conf.ubus_prefix = optarg; +				break; + +			/* ubus socket */ +			case 'U': +				conf.ubus_socket = optarg; +				break; +#endif +  #if defined(HAVE_CGI) || defined(HAVE_LUA)  			/* script timeout */  			case 't': @@ -1002,16 +1071,21 @@ int main (int argc, char **argv)  					"	-S              Do not follow symbolic links outside of the docroot\n"  					"	-D              Do not allow directory listings, send 403 instead\n"  					"	-R              Enable RFC1918 filter\n" +					"	-n count        Maximum allowed number of concurrent requests\n"  #ifdef HAVE_LUA  					"	-l string       URL prefix for Lua handler, default is '/lua'\n"  					"	-L file         Lua handler script, omit to disable Lua\n"  #endif +#ifdef HAVE_UBUS +					"	-u string       URL prefix for HTTP/JSON handler, default is '/ubus'\n" +					"	-U file         Override ubus socket path\n" +#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" +#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS) +					"	-t seconds      CGI, Lua and UBUS script timeout in seconds, default is 60\n"  #endif  					"	-T seconds      Network timeout in seconds, default is 30\n"  					"	-d string       URL decode given string\n" @@ -1053,11 +1127,15 @@ int main (int argc, char **argv)  	/* config file */  	uh_config_parse(&conf); +	/* default max requests */ +	if (conf.max_requests <= 0) +		conf.max_requests = 3; +  	/* default network timeout */  	if (conf.network_timeout <= 0)  		conf.network_timeout = 30; -#if defined(HAVE_CGI) || defined(HAVE_LUA) +#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS)  	/* default script timeout */  	if (conf.script_timeout <= 0)  		conf.script_timeout = 60; @@ -1103,6 +1181,36 @@ int main (int argc, char **argv)  	}  #endif +#ifdef HAVE_UBUS +	/* load ubus plugin */ +	if (!(lib = dlopen("uhttpd_ubus.so", RTLD_LAZY | RTLD_GLOBAL))) +	{ +		fprintf(stderr, +				"Notice: Unable to load ubus plugin - disabling ubus support! " +				"(Reason: %s)\n", dlerror()); +	} +	else +	{ +		/* resolve functions */ +		if (!(conf.ubus_init    = dlsym(lib, "uh_ubus_init"))    || +		    !(conf.ubus_close   = dlsym(lib, "uh_ubus_close"))   || +		    !(conf.ubus_request = dlsym(lib, "uh_ubus_request"))) +		{ +			fprintf(stderr, +					"Error: Failed to lookup required symbols " +					"in ubus plugin: %s\n", dlerror() +			); +			exit(1); +		} + +		/* default ubus prefix */ +		if (!conf.ubus_prefix) +			conf.ubus_prefix = "/ubus"; + +		conf.ubus_state = conf.ubus_init(&conf); +	} +#endif +  	/* fork (if not disabled) */  	if (!nofork)  	{ @@ -1134,7 +1242,7 @@ int main (int argc, char **argv)  	}  	/* server main loop */ -	uh_mainloop(&conf, serv_fds, max_fd); +	uloop_run();  #ifdef HAVE_LUA  	/* destroy the Lua state */ @@ -1142,5 +1250,11 @@ int main (int argc, char **argv)  		conf.lua_close(conf.lua_state);  #endif +#ifdef HAVE_UBUS +	/* destroy the ubus state */ +	if (conf.ubus_state != NULL) +		conf.ubus_close(conf.ubus_state); +#endif +  	return 0;  } diff --git a/package/uhttpd/src/uhttpd.h b/package/uhttpd/src/uhttpd.h index c03d1ae65..8fa3f219b 100644 --- a/package/uhttpd/src/uhttpd.h +++ b/package/uhttpd/src/uhttpd.h @@ -20,6 +20,7 @@  #include <stdio.h>  #include <stdlib.h> +#include <stdbool.h>  #include <string.h>  #include <unistd.h>  #include <signal.h> @@ -36,6 +37,9 @@  #include <errno.h>  #include <dlfcn.h> +#include <libubox/list.h> +#include <libubox/uloop.h> +  #ifdef HAVE_LUA  #include <lua.h> @@ -50,6 +54,12 @@  #define SOL_TCP	6  #endif +#ifdef DEBUG +#define D(...) fprintf(stderr, __VA_ARGS__) +#else +#define D(...) +#endif +  #define UH_LIMIT_MSGHEAD	4096  #define UH_LIMIT_HEADERS	64 @@ -60,10 +70,14 @@  #define UH_HTTP_MSG_HEAD	1  #define UH_HTTP_MSG_POST	2 +#define UH_SOCK_CLIENT		0 +#define UH_SOCK_SERVER		1 +  struct listener;  struct client;  struct interpreter;  struct http_request; +struct uh_ubus_state;  struct config {  	char docroot[PATH_MAX]; @@ -76,6 +90,7 @@ struct config {  	int network_timeout;  	int rfc1918_filter;  	int tcp_keepalive; +	int max_requests;  #ifdef HAVE_CGI  	char *cgi_prefix;  #endif @@ -85,9 +100,17 @@ struct config {  	lua_State *lua_state;  	lua_State * (*lua_init) (const struct config *conf);  	void (*lua_close) (lua_State *L); -	void (*lua_request) (struct client *cl, struct http_request *req, lua_State *L); +	bool (*lua_request) (struct client *cl, lua_State *L); +#endif +#ifdef HAVE_UBUS +	char *ubus_prefix; +	char *ubus_socket; +	void *ubus_state; +	struct uh_ubus_state * (*ubus_init) (const struct config *conf); +	void (*ubus_close) (struct uh_ubus_state *state); +	bool (*ubus_request) (struct client *cl, struct uh_ubus_state *state);  #endif -#if defined(HAVE_CGI) || defined(HAVE_LUA) +#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS)  	int script_timeout;  #endif  #ifdef HAVE_TLS @@ -100,13 +123,30 @@ struct config {  	void (*tls_free) (struct listener *l);  	int (*tls_accept) (struct client *c);  	void (*tls_close) (struct client *c); -	int (*tls_recv) (struct client *c, void *buf, int len); -	int (*tls_send) (struct client *c, void *buf, int len); +	int (*tls_recv) (struct client *c, char *buf, int len); +	int (*tls_send) (struct client *c, const char *buf, int len);  #endif  }; +struct http_request { +	int	method; +	float version; +	int redirect_status; +	char *url; +	char *headers[UH_LIMIT_HEADERS]; +	struct auth_realm *realm; +}; + +struct http_response { +	int statuscode; +	char *statusmsg; +	char *headers[UH_LIMIT_HEADERS]; +}; +  struct listener { +	struct uloop_fd fd;  	int socket; +	int n_clients;  	struct sockaddr_in6 addr;  	struct config *conf;  #ifdef HAVE_TLS @@ -116,16 +156,34 @@ struct listener {  };  struct client { -	int socket; -	int peeklen; -	char peekbuf[UH_LIMIT_MSGHEAD]; +#ifdef HAVE_TLS +	SSL *tls; +#endif +	struct uloop_fd fd; +	struct uloop_process proc; +	struct uloop_timeout timeout; +	bool (*cb)(struct client *); +	void *priv; +	bool dispatched; +	bool dead; +	struct { +		char buf[UH_LIMIT_MSGHEAD]; +		char *ptr; +		int len; +	} httpbuf;  	struct listener *server; +	struct http_request request; +	struct http_response response;  	struct sockaddr_in6 servaddr;  	struct sockaddr_in6 peeraddr; +	struct client *next; +}; + +struct client_light {  #ifdef HAVE_TLS  	SSL *tls;  #endif -	struct client *next; +	struct uloop_fd fd;  };  struct auth_realm { @@ -135,21 +193,6 @@ struct auth_realm {  	struct auth_realm *next;  }; -struct http_request { -	int	method; -	float version; -	int redirect_status; -	char *url; -	char *headers[UH_LIMIT_HEADERS]; -	struct auth_realm *realm; -}; - -struct http_response { -	int statuscode; -	char *statusmsg; -	char *headers[UH_LIMIT_HEADERS]; -}; -  #ifdef HAVE_CGI  struct interpreter {  	char path[PATH_MAX];  | 
