[LEDE-DEV] [RFC] uhttpd: [PATCH 1/2] modules: Add proxy module

lede at daniel.thecshore.com lede at daniel.thecshore.com
Wed May 18 03:50:00 PDT 2016


From: Daniel Dickinson <openwrt at daniel.thecshore.com>

A first attempt at simple HTTP proxy support (to allow
URL beginning with /prefix to really be access to some
other server; this is potenitally useful with some
web applications, or if you want to have a single
entry point to multiple servers (and aren't serving
huge traffic).

Signed-off-by: Daniel Dickinson <lede at daniel.thecshore.com>
---

 NOTE: This is basically untested and is provided to get comments
 before proceeding with something which may be of no interest.

 CMakeLists.txt |  11 +-
 client.c       |  90 +++++++++++--
 listen.c       |   6 +-
 main.c         |  41 +++++-
 proc.c         |  38 ++++++
 proxy.c        | 406 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 uhttpd.h       |  53 ++++++++
 utils.c        |  52 ++++++--
 8 files changed, 667 insertions(+), 30 deletions(-)
 create mode 100644 proxy.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8514351..3bf39db 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -10,6 +10,7 @@ ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -Os -Wall -Werror -Wmissing-declarations
 OPTION(TLS_SUPPORT "TLS support" ON)
 OPTION(LUA_SUPPORT "Lua support" ON)
 OPTION(UBUS_SUPPORT "ubus support" ON)
+OPTION(PROXY_SUPPORT "proxy support" ON)
 
 IF(APPLE)
   INCLUDE_DIRECTORIES(/opt/local/include)
@@ -32,9 +33,7 @@ IF(HAVE_SHADOW)
     ADD_DEFINITIONS(-DHAVE_SHADOW)
 ENDIF()
 
-ADD_EXECUTABLE(uhttpd ${SOURCES})
 FIND_LIBRARY(libjson NAMES json-c json)
-TARGET_LINK_LIBRARIES(uhttpd ubox dl json_script blobmsg_json ${libjson} ${LIBS})
 
 SET(PLUGINS "")
 IF(LUA_SUPPORT)
@@ -73,6 +72,14 @@ IF(UBUS_SUPPORT)
 	TARGET_LINK_LIBRARIES(uhttpd_ubus ubus ubox blobmsg_json ${libjson})
 ENDIF()
 
+IF(PROXY_SUPPORT)
+	SET(SOURCES ${SOURCES} proxy.c)
+	ADD_DEFINITIONS(-DHAVE_PROXY)
+ENDIF()
+
+ADD_EXECUTABLE(uhttpd ${SOURCES})
+TARGET_LINK_LIBRARIES(uhttpd ubox dl json_script blobmsg_json ${libjson} ${LIBS})
+
 IF(PLUGINS)
 	SET_TARGET_PROPERTIES(${PLUGINS} PROPERTIES
 		PREFIX ""
diff --git a/client.c b/client.c
index 73e0e49..4643a56 100644
--- a/client.c
+++ b/client.c
@@ -24,9 +24,11 @@
 #include "tls.h"
 
 static LIST_HEAD(clients);
-static bool client_done = false;
+bool client_done = false;
 
 int n_clients = 0;
+int n_connections = 0;
+
 struct config conf = {};
 
 const char * const http_versions[] = {
@@ -40,6 +42,9 @@ const char * const http_methods[] = {
 	[UH_HTTP_MSG_POST] = "POST",
 	[UH_HTTP_MSG_HEAD] = "HEAD",
 	[UH_HTTP_MSG_OPTIONS] = "OPTIONS",
+	[UH_HTTP_MSG_DELETE] = "DELETE",
+	[UH_HTTP_MSG_PUT] = "PUT",
+	[UH_HTTP_MSG_CONNECT] = "CONNECT",
 };
 
 void uh_http_header(struct client *cl, int code, const char *summary)
@@ -73,7 +78,7 @@ static void uh_connection_close(struct client *cl)
 	ustream_state_change(cl->us);
 }
 
-static void uh_dispatch_done(struct client *cl)
+void uh_dispatch_done(struct client *cl)
 {
 	if (cl->dispatch.free)
 		cl->dispatch.free(cl);
@@ -104,7 +109,7 @@ static void uh_keepalive_poll_cb(struct uloop_timeout *timeout)
 	cl->us->notify_read(cl->us, 0);
 }
 
-static void uh_poll_connection(struct client *cl)
+void uh_poll_connection(struct client *cl)
 {
 	cl->timeout.cb = uh_keepalive_poll_cb;
 	uloop_timeout_set(&cl->timeout, 1);
@@ -160,6 +165,49 @@ static int find_idx(const char * const *list, int max, const char *str)
 	return -1;
 }
 
+#ifdef HAVE_PROXY
+static int client_parse_response(struct client *cl, char *data)
+{
+	struct client *proxycl = cl->proxycl;
+
+	if (!proxycl)
+		return CLIENT_STATE_DONE;
+
+	struct http_request *req = &cl->request;
+	char *code, *msg, *version;
+	int h_version;
+
+	version = strtok(data, " ");
+	code = strtok(NULL, " ");
+	msg = strtok(NULL, " ");
+	if (!code || !msg || !version)
+		goto error;
+
+	memset(&cl->request, 0, sizeof(cl->request));
+	h_version = find_idx(http_versions, ARRAY_SIZE(http_versions), version);
+	if (h_version < 0) {
+		req->version = UH_HTTP_VER_1_0;
+		return CLIENT_STATE_DONE;
+	}
+
+	req->method = proxycl->request.method;
+	req->version = h_version;
+	if (req->version < UH_HTTP_VER_1_1 || req->method == UH_HTTP_MSG_POST ||
+	    !conf.http_keepalive)
+		req->connection_close = true;
+	req->code = atoi(code);
+	req->msg = strdup(msg);
+	if (req->code <= 0)
+		goto error;
+
+	return CLIENT_STATE_HEADER;
+
+	error:
+		uh_client_error(cl->proxycl, 502, "Bad Gateway", "Invalid response from target\n");
+		return CLIENT_STATE_DONE;
+}
+#endif
+
 static int client_parse_request(struct client *cl, char *data)
 {
 	struct http_request *req = &cl->request;
@@ -206,10 +254,30 @@ static bool client_init_cb(struct client *cl, char *buf, int len)
 
 	*newline = 0;
 	blob_buf_init(&cl->hdr, 0);
+#ifdef HAVE_PROXY
+	if (cl->response) {
+		cl->state = client_parse_response(cl, buf);
+	} else {
+#endif
 	cl->state = client_parse_request(cl, buf);
+#ifdef HAVE_PROXY
+	}
+#endif
 	ustream_consume(cl->us, newline + 2 - buf);
-	if (cl->state == CLIENT_STATE_DONE)
-		uh_header_error(cl, 400, "Bad Request");
+
+	if (cl->state == CLIENT_STATE_DONE) {
+#ifdef HAVE_PROXY
+		if (!cl->response) {
+#endif
+			uh_header_error(cl, 400, "Bad Request");
+#ifdef HAVE_PROXY
+		} else {
+			uh_client_error(cl->proxycl, 502, "Bad Gateway", "Invalid response from target\n");
+#endif
+#ifdef HAVE_PROXY
+		}
+#endif
+	}
 
 	return true;
 }
@@ -303,7 +371,7 @@ static void client_header_complete(struct client *cl)
 	uh_handle_request(cl);
 }
 
-static void client_parse_header(struct client *cl, char *data)
+void uh_client_parse_header(struct client *cl, char *data)
 {
 	struct http_request *r = &cl->request;
 	char *err;
@@ -473,7 +541,7 @@ static bool client_header_cb(struct client *cl, char *buf, int len)
 		return false;
 
 	*newline = 0;
-	client_parse_header(cl, buf);
+	uh_client_parse_header(cl, buf);
 	line_len = newline + 2 - buf;
 	ustream_consume(cl->us, line_len);
 	if (cl->state == CLIENT_STATE_DATA)
@@ -522,6 +590,7 @@ static void client_close(struct client *cl)
 
 	client_done = true;
 	n_clients--;
+	n_connections--;
 	uh_dispatch_done(cl);
 	uloop_timeout_cancel(&cl->timeout);
 	if (cl->tls)
@@ -572,7 +641,7 @@ static void client_notify_state(struct ustream *s)
 	uh_client_notify_state(cl);
 }
 
-static void set_addr(struct uh_addr *addr, void *src)
+void uh_set_addr(struct uh_addr *addr, void *src)
 {
 	struct sockaddr_in *sin = src;
 	struct sockaddr_in6 *sin6 = src;
@@ -606,10 +675,10 @@ bool uh_accept_client(int fd, bool tls)
 	if (sfd < 0)
 		return false;
 
-	set_addr(&cl->peer_addr, &addr);
+	uh_set_addr(&cl->peer_addr, &addr);
 	sl = sizeof(addr);
 	getsockname(sfd, (struct sockaddr *) &addr, &sl);
-	set_addr(&cl->srv_addr, &addr);
+	uh_set_addr(&cl->srv_addr, &addr);
 
 	cl->us = &cl->sfd.stream;
 	if (tls) {
@@ -628,6 +697,7 @@ bool uh_accept_client(int fd, bool tls)
 
 	next_client = NULL;
 	n_clients++;
+	n_connections++;
 	cl->id = client_id++;
 	cl->tls = tls;
 
diff --git a/listen.c b/listen.c
index 92ca680..63d6da5 100644
--- a/listen.c
+++ b/listen.c
@@ -57,7 +57,7 @@ static void uh_poll_listeners(struct uloop_timeout *timeout)
 	struct listener *l;
 
 	if ((!n_blocked && conf.max_connections) ||
-	    n_clients >= conf.max_connections)
+	    n_connections >= conf.max_connections)
 		return;
 
 	list_for_each_entry(l, &listeners, list) {
@@ -65,7 +65,7 @@ static void uh_poll_listeners(struct uloop_timeout *timeout)
 			continue;
 
 		l->fd.cb(&l->fd, ULOOP_READ);
-	    if (n_clients >= conf.max_connections)
+	    if (n_connections >= conf.max_connections)
 			break;
 
 		n_blocked--;
@@ -92,7 +92,7 @@ static void listener_cb(struct uloop_fd *fd, unsigned int events)
 			break;
 	}
 
-	if (conf.max_connections && n_clients >= conf.max_connections)
+	if (conf.max_connections && n_connections >= conf.max_connections)
 		uh_block_listener(l);
 }
 
diff --git a/main.c b/main.c
index fb27665..2c6eb17 100644
--- a/main.c
+++ b/main.c
@@ -154,6 +154,10 @@ static int usage(const char *name)
 		"	-a              Do not authenticate JSON-RPC requests against UBUS session api\n"
 		"	-X		Enable CORS HTTP headers on JSON-RPC api\n"
 #endif
+#ifdef HAVE_PROXY
+		"	-P prefix=uri   Redirect prefix to alternate URI (proxy)\n"
+		"       -M count        Maximum number of proxied requests\n"
+#endif
 		"	-x string       URL prefix for CGI handler, default is '/cgi-bin'\n"
 		"	-y alias[=path]	URL alias handle\n"
 		"	-i .ext=path    Use interpreter at path for files with the given extension\n"
@@ -174,6 +178,10 @@ static void init_defaults_pre(void)
 	conf.network_timeout = 30;
 	conf.http_keepalive = 20;
 	conf.max_script_requests = 3;
+#ifdef HAVE_PROXY
+	conf.max_proxy_requests = 10;
+	INIT_LIST_HEAD(&conf.proxies);
+#endif
 	conf.max_connections = 100;
 	conf.realm = "Protected Area";
 	conf.cgi_prefix = "/cgi-bin";
@@ -229,10 +237,13 @@ int main(int argc, char **argv)
 	BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX);
 
 	uh_dispatch_add(&cgi_dispatch);
+#ifdef HAVE_PROXY
+	uh_dispatch_add(&proxy_dispatch);
+#endif
 	init_defaults_pre();
 	signal(SIGPIPE, SIG_IGN);
 
-	while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
+	while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:P:M:")) != -1) {
 		switch(ch) {
 #ifdef HAVE_TLS
 		case 'C':
@@ -447,6 +458,34 @@ int main(int argc, char **argv)
 			                "ignoring -%c\n", ch);
 			break;
 #endif
+#ifdef HAVE_PROXY
+		case 'P':
+			optarg = strdup(optarg);
+			port = strchr(optarg, '=');
+			if (!port) {
+				fprintf(stderr, "Error: Invalid proxy destination: %s\n",
+					optarg);
+			}
+			*port++ = 0;
+			if ((strcmp(&optarg[0], "http://") == 0) && port) {
+				uh_proxy_add(optarg, &port[7]);
+			} else {
+				fprintf(stderr, "Error: Invalid proxy destination or protocol: %s\n",
+					optarg);
+				exit(1);
+			}
+
+			break;
+
+		case 'M':
+			conf.max_proxy_requests = atoi(optarg);
+			break;
+#else
+		case 'P':
+		case 'M':
+			fprintf(stderr, "uhttpd: Proxy support not compiled, "
+			                "ignoring -%c\n", ch);
+#endif
 		default:
 			return usage(argv[0]);
 		}
diff --git a/proc.c b/proc.c
index 4819e08..66288fa 100644
--- a/proc.c
+++ b/proc.c
@@ -121,6 +121,44 @@ static struct env_var extra_vars[] = {
 	[VAR_REMOTE_PORT] = { "REMOTE_PORT", remote_port },
 };
 
+char *uh_get_local_addr(struct client *cl)
+{
+	static char local_addr[INET6_ADDRSTRLEN];
+	inet_ntop(cl->srv_addr.family, &cl->srv_addr.in, local_addr, sizeof(local_addr));
+	return local_addr;
+}
+
+char *uh_get_local_port(struct client *cl)
+{
+	static char local_port[6];
+	snprintf(local_port, sizeof(local_port), "%d", cl->srv_addr.port);
+	return local_port;
+}
+
+char *uh_get_remote_addr(struct client *cl)
+{
+	static char remote_addr[INET6_ADDRSTRLEN];
+	inet_ntop(cl->peer_addr.family, &cl->peer_addr.in, remote_addr, sizeof(remote_addr));
+	return remote_addr;
+}
+
+char *uh_get_remote_port(struct client *cl)
+{
+	static char remote_port[6];
+	snprintf(remote_port, sizeof(remote_port), "%d", cl->peer_addr.port);
+	return remote_port;
+}
+
+char *uh_get_redirect_status(struct client *cl)
+{
+	static char redirect_status[4];
+	struct http_request *req = &cl->request;
+	snprintf(redirect_status, sizeof(redirect_status),
+		 "%d", req->redirect_status);
+
+	return redirect_status;
+}
+
 struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi)
 {
 	struct http_request *req = &cl->request;
diff --git a/proxy.c b/proxy.c
new file mode 100644
index 0000000..27c856b
--- /dev/null
+++ b/proxy.c
@@ -0,0 +1,406 @@
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2010-2013 Jo-Philipp Wich <xm at subsignal.org>
+ *   Copyright (C) 2013 Felix Fietkau <nbd at openwrt.org>
+ *   Copyright (C) 2015 Daniel Dickinson <openwrt at daniel.thecshore.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include "uhttpd.h"
+#include "plugin.h"
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <limits.h>
+
+bool proxy_done = false;
+static int n_proxies = 0;
+
+void uh_proxy_add(const char *prefix, const char *uri)
+{
+	struct proxy_uri *pu;
+	char * new_prefix;
+	char * new_port;
+	char * new_hostname;
+	char * new_path;
+
+	char *path = strchr(uri, '/');
+	*path++ = 0;
+
+	char *port = strchr(uri, ':');
+	if (!port)
+		port = "80";
+
+	pu = calloc_a(sizeof(*pu),
+		&new_prefix, strlen(prefix) + 1,
+		&new_hostname, strlen(uri) + 1,
+		&new_port, strlen(port) + 1,
+		&new_path, strlen(path) + 1);
+
+	pu->prefix = strcpy(new_prefix, prefix);
+	pu->hostname = strcpy(new_hostname, uri);
+	pu->port = strcpy(new_port, port);
+	pu->path = strcpy(new_path, path);
+
+	list_add_tail(&pu->list, &conf.proxies);
+}
+
+static int proxy_sock_init(struct proxy_uri *target, struct sockaddr_in6 *addr, unsigned int *sl)
+{
+	int sock = -1;
+	int yes = 1;
+	int status;
+
+	struct addrinfo *addrs = NULL, *p = NULL;
+	static struct addrinfo hints = {
+		.ai_family = AF_UNSPEC,
+		.ai_socktype = SOCK_STREAM,
+		.ai_flags = AI_PASSIVE,
+	};
+
+	if ((status = getaddrinfo(target->hostname, target->port, &hints, &addrs)) != 0) {
+		fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status));
+		return 0;
+	}
+
+	for (p = addrs; p; p = p->ai_next) {
+		/* get the socket */
+		sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
+		if (sock < 0) {
+			perror("socket()");
+			goto error;
+		}
+
+		/* required to get parallel v4 + v6 working */
+		if (p->ai_family == AF_INET6 &&
+		    setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0) {
+			perror("setsockopt()");
+			goto error;
+		}
+
+		if (connect(sock, p->ai_addr, p->ai_addrlen) < 0) {
+			continue;
+		} else {
+			memcpy(addr, p->ai_addr, sizeof(*addr));
+			*sl = p->ai_addrlen;
+			fd_cloexec(sock);
+			break;
+		}
+	}
+	freeaddrinfo(addrs);
+
+	if (sock < 0) {
+		goto error;
+	}
+
+	return sock;
+
+error:
+	if (sock > -1)
+		close(sock);
+	return -1;
+}
+
+static void proxy_close(struct client *proxy)
+{
+	proxy_done = true;
+	uh_dispatch_done(proxy);
+	uloop_timeout_cancel(&proxy->timeout);
+	ustream_free(&proxy->sfd.stream);
+	close(proxy->sfd.fd.fd);
+	n_connections--;
+	n_proxies--;
+	blob_buf_free(&proxy->hdr);
+	if (proxy->request.msg)
+		free(proxy->request.msg);
+	if (proxy->request.url)
+		free(proxy->request.url);
+	free(proxy);
+}
+
+static void proxy_client_notify_state(struct client *proxy)
+{
+	struct ustream *s = proxy->us;
+
+	if (!s->write_error && proxy->state != CLIENT_STATE_CLEANUP) {
+		if (proxy->state == CLIENT_STATE_DATA)
+			return;
+
+		if (!s->eof || s->w.data_bytes)
+			return;
+	}
+
+	return proxy_close(proxy);
+}
+
+static void proxy_ustream_notify_state(struct ustream *s)
+{
+	struct client *cl = container_of(s, struct client, sfd.stream);
+
+	proxy_client_notify_state(cl);
+}
+
+static int proxy_send_data_cb(struct client *cl, const char *buf, int len)
+{
+	/* For proxy cl = proxy and cl->proxycl = originator
+         * For the originator the opposite is true
+         * We just shovel data from one side to the other.
+         */
+	if (!cl->proxycl)
+		return INT_MAX;
+
+	uh_ustream_chunk_write(cl, cl->proxycl->us, buf, len);
+
+	ustream_consume(cl->us, len);
+
+	return len;
+}
+
+static void proxy_ustream_read_response_cb(struct ustream *s, int bytes)
+{
+	struct client *proxy = container_of(s, struct client, sfd.stream);
+
+	proxy->response = true;
+	uh_client_read_cb(proxy);
+}
+
+static bool proxy_write_request_init_cb(struct client *cl, char *buf, int len) {
+	struct client *proxy = cl->proxycl;
+
+	if (!proxy)
+		return false;
+
+	switch (proxy->request.method) {
+	case UH_HTTP_MSG_GET:
+		uh_ustream_chunk_printf(cl, proxy->us, "GET ");
+		break;
+	case UH_HTTP_MSG_POST:
+		uh_ustream_chunk_printf(cl, proxy->us, "POST ");
+		break;
+	case UH_HTTP_MSG_HEAD:
+		uh_ustream_chunk_printf(cl, proxy->us, "HEAD ");
+		break;
+	case UH_HTTP_MSG_DELETE:
+		uh_ustream_chunk_printf(cl, proxy->us, "DELETE ");
+		break;
+	case UH_HTTP_MSG_PUT:
+		uh_ustream_chunk_printf(cl, proxy->us, "PUT ");
+		break;
+	case UH_HTTP_MSG_OPTIONS:
+		uh_ustream_chunk_printf(cl, proxy->us, "OPTIONS ");
+		break;
+	case UH_HTTP_MSG_CONNECT:
+		uh_ustream_chunk_printf(cl, proxy->us, "CONNECT ");
+		break;
+	}
+
+	uh_ustream_chunk_printf(cl, proxy->us, "%s", proxy->request.proxy->path);
+
+	char *match = strstr(proxy->request.url, proxy->request.proxy->path) + strlen(proxy->request.proxy->path);
+
+	if (strlen(match) > 0) {
+		uh_ustream_chunk_printf(cl, proxy->us, "/%s ", match);
+	}
+
+	uh_ustream_chunk_printf(cl, proxy->us, "HTTP/");
+
+	switch(cl->request.version) {
+	case UH_HTTP_VER_0_9:
+		uh_ustream_chunk_printf(cl, proxy->us, "0.9");
+		break;
+	case UH_HTTP_VER_1_0:
+		uh_ustream_chunk_printf(cl, proxy->us, "1.0");
+		break;
+	case UH_HTTP_VER_1_1:
+		uh_ustream_chunk_printf(cl, proxy->us, "1.1");
+		break;
+	}
+	uh_ustream_chunk_printf(cl, proxy->us, "\r\n");
+
+	proxy->wreqstate = CLIENT_STATE_HEADER;
+
+	return true;
+}
+
+static bool proxy_write_request_header_cb(struct client *cl, char *buf, int len) {
+	struct client *proxy = cl->proxycl;
+	int rem;
+	struct proxy_uri *pu = proxy->request.proxy;
+
+	struct blob_attr *cur;
+
+	if (!proxy)
+		return false;
+
+	blob_for_each_attr(cur, cl->hdr.head, rem) {
+		if (strcmp(blobmsg_name(cur), "Host") == 0) {
+			uh_ustream_chunk_printf(cl, proxy->us, "Host: %s:%s\r\n", pu->hostname, pu->port);
+			uh_ustream_chunk_printf(cl, proxy->us, "X-Forwarded-Host: %s\r\n'", blobmsg_data(cur));
+		} else {
+			uh_ustream_chunk_printf(cl, proxy->us, "%s: %s\r\n", blobmsg_name(cur), blobmsg_data(cur));
+		}
+	}
+
+	char *remote_addr = uh_get_remote_addr(cl);
+	char *remote_port = uh_get_remote_port(cl);
+
+        uh_ustream_chunk_printf(cl, proxy->us, "X-Forwarded-For: %s:%s\r\n", remote_addr, remote_port);
+	if (cl->tls) {
+		uh_ustream_chunk_printf(cl, proxy->us, "X-Forwarded-Proto: https\r\n");
+	} else {
+		uh_ustream_chunk_printf(cl, proxy->us, "X-Forwarded-Proto: http\r\n");
+	}
+
+	uh_ustream_chunk_printf(cl, proxy->us, "\r\n");
+
+	proxy->wreqstate = CLIENT_STATE_DATA;
+
+	return true;
+}
+
+static bool proxy_write_request_data_cb(struct client *cl, char *buf, int len)
+{
+	client_poll_post_data(cl);
+	return false;
+}
+
+typedef bool (*proxy_write_request_cb_t)(struct client *cl, char *buf, int len);
+static proxy_write_request_cb_t proxy_write_request_cbs[] = {
+	[CLIENT_STATE_INIT] = proxy_write_request_init_cb,
+	[CLIENT_STATE_HEADER] = proxy_write_request_header_cb,
+	[CLIENT_STATE_DATA] = proxy_write_request_data_cb
+};
+
+static void proxy_write_request_cb(struct client *cl) {
+	struct client *proxy = cl->proxycl;
+
+	if (!proxy)
+		return;
+
+	struct ustream *us = cl->us;
+	char *str;
+	int len;
+
+	proxy_done = false;
+	while (!client_done && !proxy_done) {
+		str = ustream_get_read_buf(us, &len);
+		if (!str || !len)
+			break;
+
+		if (proxy->wreqstate >= array_size(proxy_write_request_cbs) || !proxy_write_request_cbs[proxy->wreqstate])
+			break;
+
+		if (!proxy_write_request_cbs[proxy->wreqstate](cl, str, len))
+			if (len == us->r.buffer_len &&
+			    cl->state != CLIENT_STATE_DATA) {
+				uh_client_error(cl, 502, "Bad Gateway",
+						  "Connection terminated prematurely.");
+			}
+	}
+}
+
+static void proxy_ustream_write_request_cb(struct ustream *s, int bytes)
+{
+	struct client *proxy = container_of(s, struct client, sfd.stream);
+
+	proxy_write_request_cb(proxy);
+}
+
+static struct client *proxy_init_client(struct client *cl, struct proxy_uri *target, char *url)
+{
+	static struct client *proxy;
+	unsigned int sl;
+	int sfd;
+	struct sockaddr_in6 addr;
+
+	proxy = calloc(1, sizeof(struct client));
+
+	sl = sizeof(addr);
+	sfd = proxy_sock_init(target, &addr, &sl);
+
+	if (sfd < 0)
+		return NULL;
+
+	uh_set_addr(&proxy->peer_addr, &addr);
+	sl = sizeof(addr);
+	getsockname(sfd, (struct sockaddr *) &addr, &sl);
+	uh_set_addr(&proxy->srv_addr, &addr);
+
+	proxy->sfd.fd.fd = sfd;
+	proxy->us = &proxy->sfd.stream;
+	proxy->us->notify_write = proxy_ustream_write_request_cb;
+	proxy->us->notify_read = proxy_ustream_read_response_cb;
+	proxy->us->notify_state = proxy_ustream_notify_state;
+	proxy->us->string_data = true;
+	proxy->dispatch.data_send = proxy_send_data_cb;
+	cl->dispatch.data_send = proxy_send_data_cb;
+
+	cl->proxycl = proxy;
+	proxy->proxycl = cl;
+	proxy->request.proxy = target;
+	proxy->request.url = strdup(url);
+
+	ustream_fd_init(&proxy->sfd, sfd);
+
+	uh_poll_connection(proxy);
+
+	return proxy;
+}
+
+static void proxy_init(struct client *cl, char *url, struct proxy_uri *pu) {
+	if ((conf.max_connections && (n_connections >= conf.max_connections)) || (conf.max_proxy_requests && (n_proxies >= conf.max_proxy_requests)))
+		return;
+
+	struct client *proxy = proxy_init_client(cl, pu, url);
+
+	if (!proxy) {
+		uh_client_error(cl, 502, "Bad Gateway",
+				  "Proxy %s failed to connect to any address\n", pu->prefix);
+		return;
+	}
+	n_connections++;
+	n_proxies++;
+
+        return;
+}
+
+static void proxy_handle_request(struct client *cl, char *url, struct path_info *pi)
+{
+	struct proxy_uri *p;
+
+        if (!list_empty(&conf.proxies)) list_for_each_entry(p, &conf.proxies, list)
+		if (uh_path_match(p->prefix, url))
+			return proxy_init(cl, url, p);
+
+}
+
+static bool check_proxy_url(const char *url)
+{
+        struct proxy_uri *p;
+
+        if (!list_empty(&conf.proxies)) list_for_each_entry(p, &conf.proxies, list)
+		if (uh_path_match(p->prefix, url))
+			return true;
+	return false;
+}
+
+struct dispatch_handler proxy_dispatch = {
+	.script = false,
+	.check_url = check_proxy_url,
+	.handle_request = proxy_handle_request,
+};
diff --git a/uhttpd.h b/uhttpd.h
index f9ea761..d47b7b4 100644
--- a/uhttpd.h
+++ b/uhttpd.h
@@ -71,6 +71,10 @@ struct config {
 	int tls_redirect;
 	int tcp_keepalive;
 	int max_script_requests;
+#ifdef HAVE_PROXY
+	struct list_head proxies;
+	int max_proxy_requests;
+#endif
 	int max_connections;
 	int http_keepalive;
 	int script_timeout;
@@ -92,6 +96,9 @@ enum http_method {
 	UH_HTTP_MSG_POST,
 	UH_HTTP_MSG_HEAD,
 	UH_HTTP_MSG_OPTIONS,
+	UH_HTTP_MSG_DELETE,
+	UH_HTTP_MSG_PUT,
+	UH_HTTP_MSG_CONNECT,
 };
 
 enum http_version {
@@ -123,6 +130,12 @@ struct http_request {
 	bool disable_chunked;
 	uint8_t transfer_chunked;
 	const struct auth_realm *realm;
+#ifdef HAVE_PROXY
+	int code;
+	char *msg;
+	struct proxy_uri *proxy;
+	char *url;
+#endif
 };
 
 enum client_state {
@@ -140,6 +153,16 @@ struct interpreter {
 	const char *ext;
 };
 
+#ifdef HAVE_PROXY
+struct proxy_uri {
+	struct list_head list;
+	const char *prefix;
+	const char *hostname;
+	const char *port;
+	const char *path;
+};
+#endif
+
 struct path_info {
 	const char *root;
 	const char *phys;
@@ -150,6 +173,9 @@ struct path_info {
 	bool redirected;
 	struct stat stat;
 	const struct interpreter *ip;
+#ifdef HAVE_PROXY
+	struct proxy_uri *proxy;
+#endif
 };
 
 struct env_var {
@@ -248,6 +274,13 @@ struct client {
 	struct ustream_ssl ssl;
 #endif
 	struct uloop_timeout timeout;
+
+#ifdef HAVE_PROXY
+	struct client *proxycl;
+	enum client_state wreqstate;
+	bool response;
+#endif
+
 	int requests;
 
 	enum client_state state;
@@ -263,14 +296,19 @@ struct client {
 
 extern char uh_buf[4096];
 extern int n_clients;
+extern int n_connections;
 extern struct config conf;
 extern const char * const http_versions[];
 extern const char * const http_methods[];
 extern struct dispatch_handler cgi_dispatch;
+extern struct dispatch_handler proxy_dispatch;
+extern bool client_done;
+extern bool proxy_done;
 
 void uh_index_add(const char *filename);
 
 bool uh_accept_client(int fd, bool tls);
+void uh_set_addr(struct uh_addr *addr, void *src);
 
 void uh_unblock_listeners(void);
 void uh_setup_listeners(void);
@@ -279,6 +317,9 @@ int uh_socket_bind(const char *host, const char *port, bool tls);
 int uh_first_tls_port(int family);
 
 bool uh_use_chunked(struct client *cl);
+void uh_ustream_chunk_write(struct client *cl, struct ustream *us, const void *data, int len);
+void uh_ustream_chunk_vprintf(struct client *cl, struct ustream *us, const char *format, va_list arg);
+void uh_ustream_chunk_printf(struct client *cl, struct ustream *us, const char *format, ...);
 void uh_chunk_write(struct client *cl, const void *data, int len);
 void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg);
 
@@ -296,6 +337,9 @@ void uh_handle_request(struct client *cl);
 void client_poll_post_data(struct client *cl);
 void uh_client_read_cb(struct client *cl);
 void uh_client_notify_state(struct client *cl);
+void uh_client_parse_header(struct client *cl, char *data);
+void uh_dispatch_done(struct client *cl);
+void uh_poll_connection(struct client *cl);
 
 void uh_auth_add(const char *path, const char *user, const char *pass);
 bool uh_auth_check(struct client *cl, struct path_info *pi);
@@ -304,6 +348,9 @@ void uh_close_listen_fds(void);
 void uh_close_fds(void);
 
 void uh_interpreter_add(const char *ext, const char *path);
+#ifdef HAVE_PROXY
+void uh_proxy_add(const char *prefix, const char *uri);
+#endif
 void uh_dispatch_add(struct dispatch_handler *d);
 
 void uh_relay_open(struct client *cl, struct relay *r, int fd, int pid);
@@ -312,6 +359,12 @@ void uh_relay_free(struct relay *r);
 void uh_relay_kill(struct client *cl, struct relay *r);
 
 struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi);
+char *uh_get_local_addr(struct client *cl);
+char *uh_get_local_port(struct client *cl);
+char *uh_get_remote_addr(struct client *cl);
+char *uh_get_remote_port(struct client *cl);
+char *uh_get_redirect_status(struct client *cl);
+
 bool uh_create_process(struct client *cl, struct path_info *pi, char *url,
 		       void (*cb)(struct client *cl, struct path_info *pi, char *url));
 
diff --git a/utils.c b/utils.c
index 29e03c0..bd4082f 100644
--- a/utils.c
+++ b/utils.c
@@ -35,7 +35,7 @@ bool uh_use_chunked(struct client *cl)
 	return !cl->request.disable_chunked;
 }
 
-void uh_chunk_write(struct client *cl, const void *data, int len)
+void uh_ustream_chunk_write(struct client *cl, struct ustream *us, const void *data, int len)
 {
 	bool chunked = uh_use_chunked(cl);
 
@@ -44,13 +44,13 @@ void uh_chunk_write(struct client *cl, const void *data, int len)
 
 	uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
 	if (chunked)
-		ustream_printf(cl->us, "%X\r\n", len);
-	ustream_write(cl->us, data, len, true);
+		ustream_printf(us, "%X\r\n", len);
+	ustream_write(us, data, len, true);
 	if (chunked)
-		ustream_printf(cl->us, "\r\n", len);
+		ustream_printf(us, "\r\n", len);
 }
 
-void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
+void uh_ustream_chunk_vprintf(struct client *cl, struct ustream *us, const char *format, va_list arg)
 {
 	char buf[256];
 	va_list arg2;
@@ -61,7 +61,7 @@ void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
 
 	uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
 	if (!uh_use_chunked(cl)) {
-		ustream_vprintf(cl->us, format, arg);
+		ustream_vprintf(us, format, arg);
 		return;
 	}
 
@@ -69,24 +69,24 @@ void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
 	len = vsnprintf(buf, sizeof(buf), format, arg2);
 	va_end(arg2);
 
-	ustream_printf(cl->us, "%X\r\n", len);
+	ustream_printf(us, "%X\r\n", len);
 	if (len < sizeof(buf))
-		ustream_write(cl->us, buf, len, true);
+		ustream_write(us, buf, len, true);
 	else
-		ustream_vprintf(cl->us, format, arg);
-	ustream_printf(cl->us, "\r\n", len);
+		ustream_vprintf(us, format, arg);
+	ustream_printf(us, "\r\n", len);
 }
 
-void uh_chunk_printf(struct client *cl, const char *format, ...)
+void uh_ustream_chunk_printf(struct client *cl, struct ustream *us, const char *format, ...)
 {
 	va_list arg;
 
 	va_start(arg, format);
-	uh_chunk_vprintf(cl, format, arg);
+	uh_ustream_chunk_vprintf(cl, us, format, arg);
 	va_end(arg);
 }
 
-void uh_chunk_eof(struct client *cl)
+static void ustream_chunk_eof(struct client *cl, struct ustream *us)
 {
 	if (!uh_use_chunked(cl))
 		return;
@@ -94,7 +94,31 @@ void uh_chunk_eof(struct client *cl)
 	if (cl->state == CLIENT_STATE_CLEANUP)
 		return;
 
-	ustream_printf(cl->us, "0\r\n\r\n");
+	ustream_printf(us, "0\r\n\r\n");
+}
+
+void uh_chunk_write(struct client *cl, const void *data, int len)
+{
+	uh_ustream_chunk_write(cl, cl->us, data, len);
+}
+
+void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
+{
+	uh_ustream_chunk_vprintf(cl, cl->us, format, arg);
+}
+
+void uh_chunk_printf(struct client *cl, const char *format, ...)
+{
+	va_list arg;
+
+	va_start(arg, format);
+	uh_ustream_chunk_printf(cl, cl->us, format, arg);
+	va_end(arg);
+}
+
+void uh_chunk_eof(struct client *cl)
+{
+	ustream_chunk_eof(cl, cl->us);
 }
 
 /* blen is the size of buf; slen is the length of src.  The input-string need
-- 
1.9.1




More information about the Lede-dev mailing list