From patchwork Wed May 18 10:50:00 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Dickinson X-Patchwork-Id: 623506 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2001:1868:205::9]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3r8rdD2f5Gz9t49 for ; Wed, 18 May 2016 20:52:48 +1000 (AEST) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1b2z3p-0004MV-Bg; Wed, 18 May 2016 10:50:41 +0000 Received: from s2.neomailbox.net ([5.148.176.60]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1b2z3l-0004FD-5w for lede-dev@lists.infradead.org; Wed, 18 May 2016 10:50:39 +0000 From: lede@daniel.thecshore.com To: lede-dev@lists.infradead.org Date: Wed, 18 May 2016 06:50:00 -0400 Message-Id: <1463568601-24541-1-git-send-email-lede@daniel.thecshore.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20160518_035037_589790_6EFCE91C X-CRM114-Status: GOOD ( 18.30 ) X-Spam-Score: -1.2 (-) X-Spam-Report: SpamAssassin version 3.4.0 on bombadil.infradead.org summary: Content analysis details: (-1.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- 0.7 SPF_SOFTFAIL SPF: sender does not match SPF record (softfail) -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Subject: [LEDE-DEV] [RFC] uhttpd: [PATCH 1/2] modules: Add proxy module X-BeenThere: lede-dev@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Daniel Dickinson , Daniel Dickinson MIME-Version: 1.0 Sender: "Lede-dev" Errors-To: lede-dev-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org From: Daniel Dickinson 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 --- 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 + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2015 Daniel Dickinson + * + * 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 +#include +#include "uhttpd.h" +#include "plugin.h" +#include +#include +#include +#include +#include + +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