diff options
| author | blogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-05 10:12:53 +0000 | 
|---|---|---|
| committer | blogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-05 10:12:53 +0000 | 
| commit | 5c105d9f3fd086aff195d3849dcf847d6b0bd927 (patch) | |
| tree | 1229a11f725bfa58aa7c57a76898553bb5f6654a /package/uhttpd/src/uhttpd.c | |
| download | openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.tar.gz openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.zip | |
branch Attitude Adjustment
git-svn-id: svn://svn.openwrt.org/openwrt/branches/attitude_adjustment@33625 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'package/uhttpd/src/uhttpd.c')
| -rw-r--r-- | package/uhttpd/src/uhttpd.c | 1288 | 
1 files changed, 1288 insertions, 0 deletions
| diff --git a/package/uhttpd/src/uhttpd.c b/package/uhttpd/src/uhttpd.c new file mode 100644 index 000000000..1efcbf0f5 --- /dev/null +++ b/package/uhttpd/src/uhttpd.c @@ -0,0 +1,1288 @@ +/* + * uhttpd - Tiny single-threaded httpd - Main component + * + *   Copyright (C) 2010 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. + */ + +#define _XOPEN_SOURCE 500	/* crypt() */ + +#include "uhttpd.h" +#include "uhttpd-utils.h" +#include "uhttpd-file.h" + +#ifdef HAVE_CGI +#include "uhttpd-cgi.h" +#endif + +#ifdef HAVE_LUA +#include "uhttpd-lua.h" +#endif + +#ifdef HAVE_TLS +#include "uhttpd-tls.h" +#endif + + +const char * http_methods[] = { "GET", "POST", "HEAD", }; +const char * http_versions[] = { "HTTP/0.9", "HTTP/1.0", "HTTP/1.1", }; + +static int run = 1; + +static void uh_sigterm(int sig) +{ +	run = 0; +} + +static void uh_config_parse(struct config *conf) +{ +	FILE *c; +	char line[512]; +	char *col1 = NULL; +	char *col2 = NULL; +	char *eol  = NULL; + +	const char *path = conf->file ? conf->file : "/etc/httpd.conf"; + + +	if ((c = fopen(path, "r")) != NULL) +	{ +		memset(line, 0, sizeof(line)); + +		while (fgets(line, sizeof(line) - 1, c)) +		{ +			if ((line[0] == '/') && (strchr(line, ':') != NULL)) +			{ +				if (!(col1 = strchr(line, ':')) || (*col1++ = 0) || +				    !(col2 = strchr(col1, ':')) || (*col2++ = 0) || +					!(eol = strchr(col2, '\n')) || (*eol++  = 0)) +				{ +					continue; +				} + +				if (!uh_auth_add(line, col1, col2)) +				{ +					fprintf(stderr, +							"Notice: No password set for user %s, ignoring " +							"authentication on %s\n", col1, line +					); +				} +			} +			else if (!strncmp(line, "I:", 2)) +			{ +				if (!(col1 = strchr(line, ':')) || (*col1++ = 0) || +				    !(eol = strchr(col1, '\n')) || (*eol++  = 0)) +				{ +				   	continue; +				} + +				conf->index_file = strdup(col1); +			} +			else if (!strncmp(line, "E404:", 5)) +			{ +				if (!(col1 = strchr(line, ':')) || (*col1++ = 0) || +				    !(eol = strchr(col1, '\n')) || (*eol++  = 0)) +				{ +					continue; +				} + +				conf->error_handler = strdup(col1); +			} +#ifdef HAVE_CGI +			else if ((line[0] == '*') && (strchr(line, ':') != NULL)) +			{ +				if (!(col1 = strchr(line, '*')) || (*col1++ = 0) || +				    !(col2 = strchr(col1, ':')) || (*col2++ = 0) || +				    !(eol = strchr(col2, '\n')) || (*eol++  = 0)) +				{ +					continue; +				} + +				if (!uh_interpreter_add(col1, col2)) +				{ +					fprintf(stderr, +							"Unable to add interpreter %s for extension %s: " +							"Out of memory\n", col2, col1 +					); +				} +			} +#endif +		} + +		fclose(c); +	} +} + +static void uh_listener_cb(struct uloop_fd *u, unsigned int events); + +static int uh_socket_bind(const char *host, const char *port, +                          struct addrinfo *hints, int do_tls, +                          struct config *conf) +{ +	int sock = -1; +	int yes = 1; +	int status; +	int bound = 0; + +	int tcp_ka_idl, tcp_ka_int, tcp_ka_cnt; + +	struct listener *l = NULL; +	struct addrinfo *addrs = NULL, *p = NULL; + +	if ((status = getaddrinfo(host, port, hints, &addrs)) != 0) +	{ +		fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status)); +	} + +	/* try to bind a new socket to each found address */ +	for (p = addrs; p; p = p->ai_next) +	{ +		/* get the socket */ +		if ((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) +		{ +			perror("socket()"); +			goto error; +		} + +		/* "address already in use" */ +		if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) +		{ +			perror("setsockopt()"); +			goto error; +		} + +		/* TCP keep-alive */ +		if (conf->tcp_keepalive > 0) +		{ +			tcp_ka_idl = 1; +			tcp_ka_cnt = 3; +			tcp_ka_int = conf->tcp_keepalive; + +			if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) || +			    setsockopt(sock, SOL_TCP, TCP_KEEPIDLE,  &tcp_ka_idl, sizeof(tcp_ka_idl)) || +			    setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &tcp_ka_int, sizeof(tcp_ka_int)) || +			    setsockopt(sock, SOL_TCP, TCP_KEEPCNT,   &tcp_ka_cnt, sizeof(tcp_ka_cnt))) +			{ +			    fprintf(stderr, "Notice: Unable to enable TCP keep-alive: %s\n", +			    	strerror(errno)); +			} +		} + +		/* required to get parallel v4 + v6 working */ +		if (p->ai_family == AF_INET6) +		{ +			if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) == -1) +			{ +				perror("setsockopt()"); +				goto error; +			} +		} + +		/* bind */ +		if (bind(sock, p->ai_addr, p->ai_addrlen) == -1) +		{ +			perror("bind()"); +			goto error; +		} + +		/* listen */ +		if (listen(sock, UH_LIMIT_CLIENTS) == -1) +		{ +			perror("listen()"); +			goto error; +		} + +		/* add listener to global list */ +		if (!(l = uh_listener_add(sock, conf))) +		{ +			fprintf(stderr, "uh_listener_add(): Failed to allocate memory\n"); +			goto error; +		} + +#ifdef HAVE_TLS +		/* init TLS */ +		l->tls = do_tls ? conf->tls : NULL; +#endif + +		/* add socket to uloop */ +		fd_cloexec(sock); +		uh_ufd_add(&l->fd, uh_listener_cb, ULOOP_READ); + +		bound++; +		continue; + +		error: +		if (sock > 0) +			close(sock); +	} + +	freeaddrinfo(addrs); + +	return bound; +} + +static struct http_request * uh_http_header_parse(struct client *cl, +												  char *buffer, int buflen) +{ +	char *method  = buffer; +	char *path    = NULL; +	char *version = NULL; + +	char *headers = NULL; +	char *hdrname = NULL; +	char *hdrdata = NULL; + +	int i; +	int hdrcount = 0; + +	struct http_request *req = &cl->request; + + +	/* terminate initial header line */ +	if ((headers = strfind(buffer, buflen, "\r\n", 2)) != NULL) +	{ +		buffer[buflen-1] = 0; + +		*headers++ = 0; +		*headers++ = 0; + +		/* find request path */ +		if ((path = strchr(buffer, ' ')) != NULL) +			*path++ = 0; + +		/* find http version */ +		if ((path != NULL) && ((version = strchr(path, ' ')) != NULL)) +			*version++ = 0; + + +		/* check method */ +		if (method && !strcmp(method, "GET")) +			req->method = UH_HTTP_MSG_GET; +		else if (method && !strcmp(method, "POST")) +			req->method = UH_HTTP_MSG_POST; +		else if (method && !strcmp(method, "HEAD")) +			req->method = UH_HTTP_MSG_HEAD; +		else +		{ +			/* invalid method */ +			uh_http_response(cl, 405, "Method Not Allowed"); +			return NULL; +		} + +		/* check path */ +		if (!path || !strlen(path)) +		{ +			/* malformed request */ +			uh_http_response(cl, 400, "Bad Request"); +			return NULL; +		} +		else +		{ +			req->url = path; +		} + +		/* check version */ +		if (version && !strcmp(version, "HTTP/0.9")) +			req->version = UH_HTTP_VER_0_9; +		else if (version && !strcmp(version, "HTTP/1.0")) +			req->version = UH_HTTP_VER_1_0; +		else if (version && !strcmp(version, "HTTP/1.1")) +			req->version = UH_HTTP_VER_1_1; +		else +		{ +			/* unsupported version */ +			uh_http_response(cl, 400, "Bad Request"); +			return NULL; +		} + +		D("SRV: %s %s %s\n", +		  http_methods[req->method], req->url, http_versions[req->version]); + +		/* process header fields */ +		for (i = (int)(headers - buffer); i < buflen; i++) +		{ +			/* found eol and have name + value, push out header tuple */ +			if (hdrname && hdrdata && (buffer[i] == '\r' || buffer[i] == '\n')) +			{ +				buffer[i] = 0; + +				/* store */ +				if ((hdrcount + 1) < array_size(req->headers)) +				{ +					D("SRV: HTTP: %s: %s\n", hdrname, hdrdata); + +					req->headers[hdrcount++] = hdrname; +					req->headers[hdrcount++] = hdrdata; + +					hdrname = hdrdata = NULL; +				} + +				/* too large */ +				else +				{ +					D("SRV: HTTP: header too big (too many headers)\n"); +					uh_http_response(cl, 413, "Request Entity Too Large"); +					return NULL; +				} +			} + +			/* have name but no value and found a colon, start of value */ +			else if (hdrname && !hdrdata && +					 ((i+1) < buflen) && (buffer[i] == ':')) +			{ +				buffer[i] = 0; +				hdrdata = &buffer[i+1]; + +				while ((hdrdata + 1) < (buffer + buflen) && *hdrdata == ' ') +					hdrdata++; +			} + +			/* have no name and found [A-Za-z], start of name */ +			else if (!hdrname && isalpha(buffer[i])) +			{ +				hdrname = &buffer[i]; +			} +		} + +		/* valid enough */ +		req->redirect_status = 200; +		return req; +	} + +	/* Malformed request */ +	uh_http_response(cl, 400, "Bad Request"); +	return NULL; +} + + +static struct http_request * uh_http_header_recv(struct client *cl) +{ +	char *bufptr = cl->httpbuf.buf; +	char *idxptr = NULL; + +	ssize_t blen = sizeof(cl->httpbuf.buf)-1; +	ssize_t rlen = 0; + +	memset(bufptr, 0, sizeof(cl->httpbuf.buf)); + +	while (blen > 0) +	{ +		/* receive data */ +		ensure_out(rlen = uh_tcp_recv(cl, bufptr, blen)); +		D("SRV: Client(%d) peek(%d) = %d\n", cl->fd.fd, blen, rlen); + +		if (rlen <= 0) +		{ +			D("SRV: Client(%d) dead [%s]\n", cl->fd.fd, strerror(errno)); +			return NULL; +		} + +		blen -= rlen; +		bufptr += rlen; + +		if ((idxptr = strfind(cl->httpbuf.buf, sizeof(cl->httpbuf.buf), +							  "\r\n\r\n", 4))) +		{ +			/* 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: +	return NULL; +} + +#if defined(HAVE_LUA) || defined(HAVE_CGI) +static int uh_path_match(const char *prefix, const char *url) +{ +	if ((strstr(url, prefix) == url) && +		((prefix[strlen(prefix)-1] == '/') || +		 (strlen(url) == strlen(prefix))   || +		 (url[strlen(prefix)] == '/'))) +	{ +		return 1; +	} + +	return 0; +} +#endif + +static bool uh_dispatch_request(struct client *cl, struct http_request *req) +{ +	struct path_info *pin; +	struct interpreter *ipr = NULL; +	struct config *conf = cl->server->conf; + +#ifdef HAVE_LUA +	/* Lua request? */ +	if (conf->lua_state && +		uh_path_match(conf->lua_prefix, req->url)) +	{ +		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)) +	{ +		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_socket_cb(struct uloop_fd *u, unsigned int events); + +static void uh_listener_cb(struct uloop_fd *u, unsigned int events) +{ +	int new_fd; +	struct listener *serv; +	struct client *cl; +	struct config *conf; + +	struct sockaddr_in6 sa; +	socklen_t sl = sizeof(sa); + +	serv = container_of(u, struct listener, fd); +	conf = serv->conf; + +	/* defer client if maximum number of requests is exceeded */ +	if (serv->n_clients >= conf->max_requests) +		return; + +	/* handle new connections */ +	if ((new_fd = accept(u->fd, (struct sockaddr *)&sa, &sl)) != -1) +	{ +		D("SRV: Server(%d) accept => Client(%d)\n", u->fd, new_fd); + +		/* add to global client list */ +		if ((cl = uh_client_add(new_fd, serv, &sa)) != NULL) +		{ +			/* add client socket to global fdset */ +			uh_ufd_add(&cl->fd, uh_socket_cb, ULOOP_READ); +			fd_cloexec(cl->fd.fd); + +#ifdef HAVE_TLS +			/* setup client tls context */ +			if (conf->tls) +			{ +				if (conf->tls_accept(cl) < 1) +				{ +					D("SRV: Client(%d) SSL handshake failed, drop\n", new_fd); + +					/* remove from global client list */ +					uh_client_remove(cl); +					return; +				} +			} +#endif +		} + +		/* insufficient resources */ +		else +		{ +			fprintf(stderr, "uh_client_add(): Cannot allocate memory\n"); +			close(new_fd); +		} +	} +} + +static void uh_client_cb(struct client *cl, unsigned int events); + +static void uh_rpipe_cb(struct uloop_fd *u, unsigned int events) +{ +	struct client *cl = container_of(u, struct client, rpipe); + +	D("SRV: Client(%d) rpipe readable\n", cl->fd.fd); + +	uh_client_cb(cl, ULOOP_WRITE); +} + +static void uh_socket_cb(struct uloop_fd *u, unsigned int events) +{ +	struct client *cl = container_of(u, struct client, fd); + +	D("SRV: Client(%d) socket readable\n", cl->fd.fd); + +	uh_client_cb(cl, ULOOP_READ); +} + +static void uh_child_cb(struct uloop_process *p, int rv) +{ +	struct client *cl = container_of(p, struct client, proc); + +	D("SRV: Client(%d) child(%d) dead\n", cl->fd.fd, cl->proc.pid); + +	uh_client_cb(cl, ULOOP_READ | ULOOP_WRITE); +} + +static void uh_kill9_cb(struct uloop_timeout *t) +{ +	struct client *cl = container_of(t, struct client, timeout); + +	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 client *cl, unsigned int events) +{ +	int i; +	struct config *conf; +	struct http_request *req; + +	conf = cl->server->conf; + +	D("SRV: Client(%d) enter callback\n", cl->fd.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", cl->fd.fd); +			return; +		} + +		/* attempt to receive and parse headers */ +		if (!(req = uh_http_header_recv(cl))) +		{ +			D("SRV: Client(%d) failed to receive header\n", cl->fd.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", +				  cl->fd.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", cl->fd.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", cl->fd.fd); +			uh_client_shutdown(cl); +			return; +		} + +		/* request handler spawned a pipe, register handler */ +		if (cl->rpipe.fd > -1) +		{ +			D("SRV: Client(%d) pipe(%d) spawned\n", cl->fd.fd, cl->rpipe.fd); + +			uh_ufd_add(&cl->rpipe, uh_rpipe_cb, ULOOP_READ); +		} + +		/* request handler spawned a child, register handler */ +		if (cl->proc.pid) +		{ +			D("SRV: Client(%d) child(%d) spawned\n", cl->fd.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", cl->fd.fd); +		cl->dispatched = true; +	} + +	if (!cl->cb(cl)) +	{ +		D("SRV: Client(%d) response callback signalized EOF\n", cl->fd.fd); +		uh_client_shutdown(cl); +		return; +	} +} + +#ifdef HAVE_TLS +static inline int uh_inittls(struct config *conf) +{ +	/* library handle */ +	void *lib; + +	/* already loaded */ +	if (conf->tls != NULL) +		return 0; + +	/* load TLS plugin */ +	if (!(lib = dlopen("uhttpd_tls.so", RTLD_LAZY | RTLD_GLOBAL))) +	{ +		fprintf(stderr, +				"Notice: Unable to load TLS plugin - disabling SSL support! " +				"(Reason: %s)\n", dlerror() +		); + +		return 1; +	} +	else +	{ +		/* resolve functions */ +		if (!(conf->tls_init   = dlsym(lib, "uh_tls_ctx_init"))      || +		    !(conf->tls_cert   = dlsym(lib, "uh_tls_ctx_cert"))      || +		    !(conf->tls_key    = dlsym(lib, "uh_tls_ctx_key"))       || +		    !(conf->tls_free   = dlsym(lib, "uh_tls_ctx_free"))      || +		    !(conf->tls_accept = dlsym(lib, "uh_tls_client_accept")) || +		    !(conf->tls_close  = dlsym(lib, "uh_tls_client_close"))  || +		    !(conf->tls_recv   = dlsym(lib, "uh_tls_client_recv"))   || +		    !(conf->tls_send   = dlsym(lib, "uh_tls_client_send"))) +		{ +			fprintf(stderr, +					"Error: Failed to lookup required symbols " +					"in TLS plugin: %s\n", dlerror() +			); +			exit(1); +		} + +		/* init SSL context */ +		if (!(conf->tls = conf->tls_init())) +		{ +			fprintf(stderr, "Error: Failed to initalize SSL context\n"); +			exit(1); +		} +	} + +	return 0; +} +#endif + +int main (int argc, char **argv) +{ +	/* working structs */ +	struct addrinfo hints; +	struct sigaction sa; +	struct config conf; + +	/* maximum file descriptor number */ +	int cur_fd = 0; + +#ifdef HAVE_TLS +	int tls = 0; +	int keys = 0; +#endif + +	int bound = 0; +	int nofork = 0; + +	/* args */ +	int opt; +	char addr[128]; +	char *port = NULL; + +#if defined(HAVE_LUA) || defined(HAVE_TLS) || defined(HAVE_UBUS) +	/* library handle */ +	void *lib; +#endif + +	/* 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_sigterm; +	sigaction(SIGINT,  &sa, NULL); +	sigaction(SIGTERM, &sa, NULL); + +	/* prepare addrinfo hints */ +	memset(&hints, 0, sizeof(hints)); +	hints.ai_family   = AF_UNSPEC; +	hints.ai_socktype = SOCK_STREAM; +	hints.ai_flags    = AI_PASSIVE; + +	/* parse args */ +	memset(&conf, 0, sizeof(conf)); + +	uloop_init(); + +	while ((opt = getopt(argc, argv, +						 "fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:n:x:i:t:T:A:u:U:")) > 0) +	{ +		switch(opt) +		{ +			/* [addr:]port */ +			case 'p': +			case 's': +				memset(addr, 0, sizeof(addr)); + +				if ((port = strrchr(optarg, ':')) != NULL) +				{ +					if ((optarg[0] == '[') && (port > optarg) && (port[-1] == ']')) +						memcpy(addr, optarg + 1, +							min(sizeof(addr), (int)(port - optarg) - 2)); +					else +						memcpy(addr, optarg, +							min(sizeof(addr), (int)(port - optarg))); + +					port++; +				} +				else +				{ +					port = optarg; +				} + +#ifdef HAVE_TLS +				if (opt == 's') +				{ +					if (uh_inittls(&conf)) +					{ +						fprintf(stderr, +							"Notice: TLS support is disabled, " +							"ignoring '-s %s'\n", optarg +						); +						continue; +					} + +					tls = 1; +				} +#endif + +				/* bind sockets */ +				bound += uh_socket_bind(addr[0] ? addr : NULL, port, &hints, +				                        (opt == 's'), &conf); +				break; + +#ifdef HAVE_TLS +			/* certificate */ +			case 'C': +				if (!uh_inittls(&conf)) +				{ +					if (conf.tls_cert(conf.tls, optarg) < 1) +					{ +						fprintf(stderr, +								"Error: Invalid certificate file given\n"); +						exit(1); +					} + +					keys++; +				} + +				break; + +			/* key */ +			case 'K': +				if (!uh_inittls(&conf)) +				{ +					if (conf.tls_key(conf.tls, optarg) < 1) +					{ +						fprintf(stderr, +								"Error: Invalid private key file given\n"); +						exit(1); +					} + +					keys++; +				} + +				break; +#else +			case 'C': +			case 'K': +				fprintf(stderr, +				        "Notice: TLS support not compiled, ignoring -%c\n", +				        opt); +				break; +#endif + +			/* docroot */ +			case 'h': +				if (! realpath(optarg, conf.docroot)) +				{ +					fprintf(stderr, "Error: Invalid directory %s: %s\n", +							optarg, strerror(errno)); +					exit(1); +				} +				break; + +			/* error handler */ +			case 'E': +				if ((strlen(optarg) == 0) || (optarg[0] != '/')) +				{ +					fprintf(stderr, "Error: Invalid error handler: %s\n", +							optarg); +					exit(1); +				} +				conf.error_handler = optarg; +				break; + +			/* index file */ +			case 'I': +				if ((strlen(optarg) == 0) || (optarg[0] == '/')) +				{ +					fprintf(stderr, "Error: Invalid index page: %s\n", +							optarg); +					exit(1); +				} +				conf.index_file = optarg; +				break; + +			/* don't follow symlinks */ +			case 'S': +				conf.no_symlinks = 1; +				break; + +			/* don't list directories */ +			case 'D': +				conf.no_dirlists = 1; +				break; + +			case 'R': +				conf.rfc1918_filter = 1; +				break; + +			case 'n': +				conf.max_requests = atoi(optarg); +				break; + +#ifdef HAVE_CGI +			/* cgi prefix */ +			case 'x': +				conf.cgi_prefix = optarg; +				break; + +			/* interpreter */ +			case 'i': +				if ((optarg[0] == '.') && (port = strchr(optarg, '='))) +				{ +					*port++ = 0; +					uh_interpreter_add(optarg, port); +				} +				else +				{ +					fprintf(stderr, "Error: Invalid interpreter: %s\n", +							optarg); +					exit(1); +				} +				break; +#else +			case 'x': +			case 'i': +				fprintf(stderr, +				        "Notice: CGI support not compiled, ignoring -%c\n", +				        opt); +				break; +#endif + +#ifdef HAVE_LUA +			/* lua prefix */ +			case 'l': +				conf.lua_prefix = optarg; +				break; + +			/* lua handler */ +			case 'L': +				conf.lua_handler = optarg; +				break; +#else +			case 'l': +			case 'L': +				fprintf(stderr, +				        "Notice: Lua support not compiled, ignoring -%c\n", +				        opt); +				break; +#endif + +#ifdef HAVE_UBUS +			/* ubus prefix */ +			case 'u': +				conf.ubus_prefix = optarg; +				break; + +			/* ubus socket */ +			case 'U': +				conf.ubus_socket = optarg; +				break; +#else +			case 'u': +			case 'U': +				fprintf(stderr, +				        "Notice: UBUS support not compiled, ignoring -%c\n", +				        opt); +				break; +#endif + +#if defined(HAVE_CGI) || defined(HAVE_LUA) +			/* script timeout */ +			case 't': +				conf.script_timeout = atoi(optarg); +				break; +#endif + +			/* network timeout */ +			case 'T': +				conf.network_timeout = atoi(optarg); +				break; + +			/* tcp keep-alive */ +			case 'A': +				conf.tcp_keepalive = atoi(optarg); +				break; + +			/* no fork */ +			case 'f': +				nofork = 1; +				break; + +			/* urldecode */ +			case 'd': +				if ((port = malloc(strlen(optarg)+1)) != NULL) +				{ +					/* "decode" plus to space to retain compat */ +					for (opt = 0; optarg[opt]; opt++) +						if (optarg[opt] == '+') +							optarg[opt] = ' '; +					/* opt now contains strlen(optarg) -- no need to re-scan */ +					memset(port, 0, opt+1); +					if (uh_urldecode(port, opt, optarg, opt) < 0) +					    fprintf(stderr, "uhttpd: invalid encoding\n"); + +					printf("%s", port); +					free(port); +					exit(0); +				} +				break; + +			/* basic auth realm */ +			case 'r': +				conf.realm = optarg; +				break; + +			/* md5 crypt */ +			case 'm': +				printf("%s\n", crypt(optarg, "$1$")); +				exit(0); +				break; + +			/* config file */ +			case 'c': +				conf.file = optarg; +				break; + +			default: +				fprintf(stderr, +					"Usage: %s -p [addr:]port [-h docroot]\n" +					"	-f              Do not fork to background\n" +					"	-c file         Configuration file, default is '/etc/httpd.conf'\n" +					"	-p [addr:]port  Bind to specified address and port, multiple allowed\n" +#ifdef HAVE_TLS +					"	-s [addr:]port  Like -p but provide HTTPS on this port\n" +					"	-C file         ASN.1 server certificate file\n" +					"	-K file         ASN.1 server private key file\n" +#endif +					"	-h directory    Specify the document root, default is '.'\n" +					"	-E string       Use given virtual URL as 404 error handler\n" +					"	-I string       Use given filename as index page for directories\n" +					"	-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\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) || 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" +					"	-r string       Specify basic auth realm\n" +					"	-m string       MD5 crypt given string\n" +					"\n", argv[0] +				); + +				exit(1); +		} +	} + +#ifdef HAVE_TLS +	if ((tls == 1) && (keys < 2)) +	{ +		fprintf(stderr, "Error: Missing private key or certificate file\n"); +		exit(1); +	} +#endif + +	if (bound < 1) +	{ +		fprintf(stderr, "Error: No sockets bound, unable to continue\n"); +		exit(1); +	} + +	/* default docroot */ +	if (!conf.docroot[0] && !realpath(".", conf.docroot)) +	{ +		fprintf(stderr, "Error: Can not determine default document root: %s\n", +			strerror(errno)); +		exit(1); +	} + +	/* default realm */ +	if (!conf.realm) +		conf.realm = "Protected Area"; + +	/* 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) || defined(HAVE_UBUS) +	/* default script timeout */ +	if (conf.script_timeout <= 0) +		conf.script_timeout = 60; +#endif + +#ifdef HAVE_CGI +	/* default cgi prefix */ +	if (!conf.cgi_prefix) +		conf.cgi_prefix = "/cgi-bin"; +#endif + +#ifdef HAVE_LUA +	/* load Lua plugin */ +	if (!(lib = dlopen("uhttpd_lua.so", RTLD_LAZY | RTLD_GLOBAL))) +	{ +		fprintf(stderr, +				"Notice: Unable to load Lua plugin - disabling Lua support! " +				"(Reason: %s)\n", dlerror()); +	} +	else +	{ +		/* resolve functions */ +		if (!(conf.lua_init    = dlsym(lib, "uh_lua_init"))    || +		    !(conf.lua_close   = dlsym(lib, "uh_lua_close"))   || +		    !(conf.lua_request = dlsym(lib, "uh_lua_request"))) +		{ +			fprintf(stderr, +					"Error: Failed to lookup required symbols " +					"in Lua plugin: %s\n", dlerror() +			); +			exit(1); +		} + +		/* init Lua runtime if handler is specified */ +		if (conf.lua_handler) +		{ +			/* default lua prefix */ +			if (!conf.lua_prefix) +				conf.lua_prefix = "/lua"; + +			conf.lua_state = conf.lua_init(&conf); +		} +	} +#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 if (conf.ubus_prefix) +	{ +		/* 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); +		} + +		/* initialize ubus */ +		conf.ubus_state = conf.ubus_init(&conf); +	} +#endif + +	/* fork (if not disabled) */ +	if (!nofork) +	{ +		switch (fork()) +		{ +			case -1: +				perror("fork()"); +				exit(1); + +			case 0: +				/* daemon setup */ +				if (chdir("/")) +					perror("chdir()"); + +				if ((cur_fd = open("/dev/null", O_WRONLY)) > -1) +					dup2(cur_fd, 0); + +				if ((cur_fd = open("/dev/null", O_RDONLY)) > -1) +					dup2(cur_fd, 1); + +				if ((cur_fd = open("/dev/null", O_RDONLY)) > -1) +					dup2(cur_fd, 2); + +				break; + +			default: +				exit(0); +		} +	} + +	/* server main loop */ +	uloop_run(); + +#ifdef HAVE_LUA +	/* destroy the Lua state */ +	if (conf.lua_state != NULL) +		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; +} | 
