summaryrefslogtreecommitdiff
path: root/usr.sbin/relayd/relay_http.c
diff options
context:
space:
mode:
authorReyk Floeter <reyk@cvs.openbsd.org>2012-09-20 12:30:21 +0000
committerReyk Floeter <reyk@cvs.openbsd.org>2012-09-20 12:30:21 +0000
commitbd8ba8cae6c333f007e5c8ca7b31dec50d6a8fef (patch)
tree471e26a19d994faa054aa4801166cade757ce198 /usr.sbin/relayd/relay_http.c
parente51223719f927f8ba32f65f562bbeebfae5c7aa7 (diff)
Move the HTTP code into an extra file to make future changes easier to
follow. No functional changes, only one function got renamed. ok benno@
Diffstat (limited to 'usr.sbin/relayd/relay_http.c')
-rw-r--r--usr.sbin/relayd/relay_http.c1079
1 files changed, 1079 insertions, 0 deletions
diff --git a/usr.sbin/relayd/relay_http.c b/usr.sbin/relayd/relay_http.c
new file mode 100644
index 00000000000..d02356ca16f
--- /dev/null
+++ b/usr.sbin/relayd/relay_http.c
@@ -0,0 +1,1079 @@
+/* $OpenBSD: relay_http.c,v 1.1 2012/09/20 12:30:20 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 - 2012 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <err.h>
+#include <pwd.h>
+#include <event.h>
+#include <fnmatch.h>
+
+#include <openssl/ssl.h>
+
+#include "relayd.h"
+
+int relay_resolve(struct ctl_relay_event *,
+ struct protonode *, struct protonode *);
+int relay_handle_http(struct ctl_relay_event *,
+ struct protonode *, struct protonode *,
+ struct protonode *, int);
+static int _relay_lookup_url(struct ctl_relay_event *, char *, char *,
+ char *, enum digest_type);
+int relay_lookup_url(struct ctl_relay_event *,
+ const char *, enum digest_type);
+int relay_lookup_query(struct ctl_relay_event *);
+int relay_lookup_cookie(struct ctl_relay_event *, const char *);
+void relay_read_httpcontent(struct bufferevent *, void *);
+void relay_read_httpchunks(struct bufferevent *, void *);
+char *relay_expand_http(struct ctl_relay_event *, char *,
+ char *, size_t);
+void relay_http_request_close(struct ctl_relay_event *);
+
+void
+relay_read_http(struct bufferevent *bev, void *arg)
+{
+ struct ctl_relay_event *cre = (struct ctl_relay_event *)arg;
+ struct rsession *con = cre->con;
+ struct relay *rlay = (struct relay *)con->se_relay;
+ struct protocol *proto = rlay->rl_proto;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ struct protonode *pn, pk, *proot, *pnv = NULL, pkv;
+ char *line;
+ int header = 0, ret, pass = 0;
+ const char *errstr;
+ size_t size;
+
+ if (gettimeofday(&con->se_tv_last, NULL) == -1)
+ goto fail;
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: size %lu, to read %llu", __func__, size, cre->toread);
+ if (!size) {
+ if (cre->dir == RELAY_DIR_RESPONSE)
+ return;
+ cre->toread = 0;
+ goto done;
+ }
+
+ pk.type = NODE_TYPE_HEADER;
+
+ while (!cre->done && (line = evbuffer_readline(src)) != NULL) {
+ /*
+ * An empty line indicates the end of the request.
+ * libevent already stripped the \r\n for us.
+ */
+ if (!strlen(line)) {
+ cre->done = 1;
+ free(line);
+ break;
+ }
+ pk.key = line;
+
+ /*
+ * The first line is the GET/POST/PUT/... request,
+ * subsequent lines are HTTP headers.
+ */
+ if (++cre->line == 1) {
+ pk.value = strchr(pk.key, ' ');
+ } else
+ pk.value = strchr(pk.key, ':');
+ if (pk.value == NULL || strlen(pk.value) < 3) {
+ if (cre->line == 1) {
+ free(line);
+ relay_abort_http(con, 400, "malformed", 0);
+ return;
+ }
+
+ DPRINTF("%s: request '%s'", __func__, line);
+ /* Append line to the output buffer */
+ if (relay_bufferevent_print(cre->dst, line) == -1 ||
+ relay_bufferevent_print(cre->dst, "\r\n") == -1) {
+ free(line);
+ goto fail;
+ }
+ free(line);
+ continue;
+ }
+ if (*pk.value == ':') {
+ *pk.value++ = '\0';
+ pk.value += strspn(pk.value, " \t\r\n");
+ header = 1;
+ } else {
+ *pk.value++ = '\0';
+ header = 0;
+ }
+
+ DPRINTF("%s: header '%s: %s'", __func__, pk.key, pk.value);
+
+ /*
+ * Identify and handle specific HTTP request methods
+ */
+ if (cre->line == 1) {
+ if (cre->dir == RELAY_DIR_RESPONSE) {
+ cre->method = HTTP_METHOD_RESPONSE;
+ goto lookup;
+ } else if (strcmp("HEAD", pk.key) == 0)
+ cre->method = HTTP_METHOD_HEAD;
+ else if (strcmp("POST", pk.key) == 0)
+ cre->method = HTTP_METHOD_POST;
+ else if (strcmp("PUT", pk.key) == 0)
+ cre->method = HTTP_METHOD_PUT;
+ else if (strcmp("DELETE", pk.key) == 0)
+ cre->method = HTTP_METHOD_DELETE;
+ else if (strcmp("OPTIONS", pk.key) == 0)
+ cre->method = HTTP_METHOD_OPTIONS;
+ else if (strcmp("TRACE", pk.key) == 0)
+ cre->method = HTTP_METHOD_TRACE;
+ else if (strcmp("CONNECT", pk.key) == 0)
+ cre->method = HTTP_METHOD_CONNECT;
+ else {
+ /* Use GET method as the default */
+ cre->method = HTTP_METHOD_GET;
+ }
+
+ /*
+ * Decode the path and query
+ */
+ cre->path = strdup(pk.value);
+ if (cre->path == NULL) {
+ free(line);
+ goto fail;
+ }
+ cre->version = strchr(cre->path, ' ');
+ if (cre->version != NULL)
+ *cre->version++ = '\0';
+ cre->args = strchr(cre->path, '?');
+ if (cre->args != NULL)
+ *cre->args++ = '\0';
+#ifdef DEBUG
+ char buf[BUFSIZ];
+ if (snprintf(buf, sizeof(buf), " \"%s\"",
+ cre->path) == -1 ||
+ evbuffer_add(con->se_log, buf, strlen(buf)) == -1) {
+ free(line);
+ goto fail;
+ }
+#endif
+
+ /*
+ * Lookup protocol handlers in the URL path
+ */
+ if ((proto->flags & F_LOOKUP_PATH) == 0)
+ goto lookup;
+
+ pkv.key = cre->path;
+ pkv.type = NODE_TYPE_PATH;
+ pkv.value = cre->args == NULL ? "" : cre->args;
+
+ DPRINTF("%s: lookup path '%s: %s'",
+ __func__, pkv.key, pkv.value);
+
+ if ((proot = RB_FIND(proto_tree,
+ cre->tree, &pkv)) == NULL)
+ goto lookup;
+
+ PROTONODE_FOREACH(pnv, proot, entry) {
+ ret = relay_handle_http(cre, proot,
+ pnv, &pkv, 0);
+ if (ret == PN_FAIL)
+ goto abort;
+ }
+ } else if ((cre->method == HTTP_METHOD_DELETE ||
+ cre->method == HTTP_METHOD_GET ||
+ cre->method == HTTP_METHOD_HEAD ||
+ cre->method == HTTP_METHOD_OPTIONS ||
+ cre->method == HTTP_METHOD_POST ||
+ cre->method == HTTP_METHOD_PUT ||
+ cre->method == HTTP_METHOD_RESPONSE) &&
+ strcasecmp("Content-Length", pk.key) == 0) {
+ /*
+ * Need to read data from the client after the
+ * HTTP header.
+ * XXX What about non-standard clients not using
+ * the carriage return? And some browsers seem to
+ * include the line length in the content-length.
+ */
+ cre->toread = strtonum(pk.value, 0, ULLONG_MAX, &errstr);
+ if (errstr) {
+ relay_abort_http(con, 500, errstr, 0);
+ goto abort;
+ }
+ } else if ((cre->method == HTTP_METHOD_TRACE) &&
+ strcasecmp("Content-Length", pk.key) == 0) {
+ /*
+ * This method should not have a body and thus no
+ * Content-Length header.
+ */
+ relay_abort_http(con, 400, "malformed", 0);
+ goto abort;
+ }
+ lookup:
+ if (strcasecmp("Transfer-Encoding", pk.key) == 0 &&
+ strcasecmp("chunked", pk.value) == 0)
+ cre->chunked = 1;
+
+ /* Match the HTTP header */
+ if ((pn = RB_FIND(proto_tree, cre->tree, &pk)) == NULL)
+ goto next;
+
+ if (cre->dir == RELAY_DIR_RESPONSE)
+ goto handle;
+
+ if (pn->flags & PNFLAG_LOOKUP_URL) {
+ /*
+ * Lookup the URL of type example.com/path?args.
+ * Either as a plain string or SHA1/MD5 digest.
+ */
+ if ((pn->flags & PNFLAG_LOOKUP_DIGEST(0)) &&
+ relay_lookup_url(cre, pk.value,
+ DIGEST_NONE) == PN_FAIL)
+ goto abort;
+ if ((pn->flags & PNFLAG_LOOKUP_DIGEST(DIGEST_SHA1)) &&
+ relay_lookup_url(cre, pk.value,
+ DIGEST_SHA1) == PN_FAIL)
+ goto abort;
+ if ((pn->flags & PNFLAG_LOOKUP_DIGEST(DIGEST_MD5)) &&
+ relay_lookup_url(cre, pk.value,
+ DIGEST_MD5) == PN_FAIL)
+ goto abort;
+ } else if (pn->flags & PNFLAG_LOOKUP_QUERY) {
+ /* Lookup the HTTP query arguments */
+ if (relay_lookup_query(cre) == PN_FAIL)
+ goto abort;
+ } else if (pn->flags & PNFLAG_LOOKUP_COOKIE) {
+ /* Lookup the HTTP cookie */
+ if (relay_lookup_cookie(cre, pk.value) == PN_FAIL)
+ goto abort;
+ }
+
+ handle:
+ pass = 0;
+ PROTONODE_FOREACH(pnv, pn, entry) {
+ ret = relay_handle_http(cre, pn, pnv, &pk, header);
+ if (ret == PN_PASS)
+ pass = 1;
+ else if (ret == PN_FAIL)
+ goto abort;
+ }
+
+ if (pass) {
+ next:
+ if (relay_bufferevent_print(cre->dst, pk.key) == -1 ||
+ relay_bufferevent_print(cre->dst,
+ header ? ": " : " ") == -1 ||
+ relay_bufferevent_print(cre->dst, pk.value) == -1 ||
+ relay_bufferevent_print(cre->dst, "\r\n") == -1) {
+ free(line);
+ goto fail;
+ }
+ }
+ free(line);
+ }
+ if (cre->done) {
+ RB_FOREACH(proot, proto_tree, cre->tree) {
+ PROTONODE_FOREACH(pn, proot, entry)
+ if (relay_resolve(cre, proot, pn) != 0)
+ return;
+ }
+
+ switch (cre->method) {
+ case HTTP_METHOD_NONE:
+ relay_abort_http(con, 406, "no method", 0);
+ return;
+ case HTTP_METHOD_CONNECT:
+ /* Data stream */
+ bev->readcb = relay_read;
+ break;
+ case HTTP_METHOD_DELETE:
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_HEAD:
+ case HTTP_METHOD_OPTIONS:
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_PUT:
+ case HTTP_METHOD_RESPONSE:
+ /* HTTP request payload */
+ if (cre->toread) {
+ bev->readcb = relay_read_httpcontent;
+ break;
+ }
+
+ /* Single-pass HTTP response */
+ bev->readcb = relay_read;
+ break;
+ default:
+ /* HTTP handler */
+ bev->readcb = relay_read_http;
+ break;
+ }
+ if (cre->chunked) {
+ /* Chunked transfer encoding */
+ cre->toread = 0;
+ bev->readcb = relay_read_httpchunks;
+ }
+
+ /* Write empty newline and switch to relay mode */
+ if (relay_bufferevent_print(cre->dst, "\r\n") == -1)
+ goto fail;
+
+ relay_http_request_close(cre);
+
+ done:
+ if (cre->dir == RELAY_DIR_REQUEST && !cre->toread &&
+ proto->lateconnect && cre->dst->bev == NULL) {
+ if (rlay->rl_conf.fwdmode == FWD_TRANS) {
+ relay_bindanyreq(con, 0, IPPROTO_TCP);
+ return;
+ }
+ if (relay_connect(con) == -1)
+ relay_abort_http(con, 502, "session failed", 0);
+ return;
+ }
+ }
+ if (con->se_done) {
+ relay_close(con, "last http read (done)");
+ return;
+ }
+ if (EVBUFFER_LENGTH(src) && bev->readcb != relay_read_http)
+ bev->readcb(bev, arg);
+ bufferevent_enable(bev, EV_READ);
+ return;
+ fail:
+ relay_abort_http(con, 500, strerror(errno), 0);
+ return;
+ abort:
+ free(line);
+}
+
+void
+relay_read_httpcontent(struct bufferevent *bev, void *arg)
+{
+ struct ctl_relay_event *cre = (struct ctl_relay_event *)arg;
+ struct rsession *con = cre->con;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ size_t size;
+
+ if (gettimeofday(&con->se_tv_last, NULL) == -1)
+ goto fail;
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: size %lu, to read %llu", __func__,
+ size, cre->toread);
+ if (!size)
+ return;
+ if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
+ goto fail;
+ if ((off_t)size >= cre->toread)
+ bev->readcb = relay_read_http;
+ cre->toread -= size;
+ DPRINTF("%s: done, size %lu, to read %llu", __func__,
+ size, cre->toread);
+ if (con->se_done)
+ goto done;
+ if (bev->readcb != relay_read_httpcontent)
+ bev->readcb(bev, arg);
+ bufferevent_enable(bev, EV_READ);
+ return;
+ done:
+ relay_close(con, "last http content read");
+ return;
+ fail:
+ relay_close(con, strerror(errno));
+}
+
+void
+relay_read_httpchunks(struct bufferevent *bev, void *arg)
+{
+ struct ctl_relay_event *cre = (struct ctl_relay_event *)arg;
+ struct rsession *con = cre->con;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ char *line;
+ long lval;
+ size_t size;
+
+ if (gettimeofday(&con->se_tv_last, NULL) == -1)
+ goto fail;
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: size %lu, to read %llu", __func__,
+ size, cre->toread);
+ if (!size)
+ return;
+
+ if (!cre->toread) {
+ line = evbuffer_readline(src);
+ if (line == NULL) {
+ /* Ignore empty line, continue */
+ bufferevent_enable(bev, EV_READ);
+ return;
+ }
+ if (!strlen(line)) {
+ free(line);
+ goto next;
+ }
+
+ /* Read prepended chunk size in hex, ingore the trailer */
+ if (sscanf(line, "%lx", &lval) != 1) {
+ free(line);
+ relay_close(con, "invalid chunk size");
+ return;
+ }
+
+ if (relay_bufferevent_print(cre->dst, line) == -1 ||
+ relay_bufferevent_print(cre->dst, "\r\n") == -1) {
+ free(line);
+ goto fail;
+ }
+ free(line);
+
+ /* Last chunk is 0 bytes followed by an empty newline */
+ if ((cre->toread = lval) == 0) {
+ DPRINTF("%s: last chunk", __func__);
+
+ line = evbuffer_readline(src);
+ if (line == NULL) {
+ relay_close(con, "invalid last chunk");
+ return;
+ }
+ free(line);
+ if (relay_bufferevent_print(cre->dst, "\r\n") == -1)
+ goto fail;
+
+ /* Switch to HTTP header mode */
+ bev->readcb = relay_read_http;
+ }
+ } else {
+ /* Read chunk data */
+ if ((off_t)size > cre->toread)
+ size = cre->toread;
+ if (relay_bufferevent_write_chunk(cre->dst, src, size) == -1)
+ goto fail;
+ cre->toread -= size;
+ DPRINTF("%s: done, size %lu, to read %llu", __func__,
+ size, cre->toread);
+
+ if (cre->toread == 0) {
+ /* Chunk is terminated by an empty (empty) newline */
+ line = evbuffer_readline(src);
+ if (line != NULL)
+ free(line);
+ if (relay_bufferevent_print(cre->dst, "\r\n\r\n") == -1)
+ goto fail;
+ }
+ }
+
+ next:
+ if (con->se_done)
+ goto done;
+ if (EVBUFFER_LENGTH(src))
+ bev->readcb(bev, arg);
+ bufferevent_enable(bev, EV_READ);
+ return;
+
+ done:
+ relay_close(con, "last http chunk read (done)");
+ return;
+ fail:
+ relay_close(con, strerror(errno));
+}
+
+void
+relay_http_request_close(struct ctl_relay_event *cre)
+{
+ if (cre->path != NULL) {
+ free(cre->path);
+ cre->path = NULL;
+ }
+
+ cre->args = NULL;
+ cre->version = NULL;
+
+ if (cre->buf != NULL) {
+ free(cre->buf);
+ cre->buf = NULL;
+ cre->buflen = 0;
+ }
+
+ cre->line = 0;
+ cre->method = 0;
+ cre->done = 0;
+ cre->chunked = 0;
+}
+
+static int
+_relay_lookup_url(struct ctl_relay_event *cre, char *host, char *path,
+ char *query, enum digest_type type)
+{
+ struct rsession *con = cre->con;
+ struct protonode *proot, *pnv, pkv;
+ char *val, *md = NULL;
+ int ret = PN_FAIL;
+
+ if (asprintf(&val, "%s%s%s%s",
+ host, path,
+ query == NULL ? "" : "?",
+ query == NULL ? "" : query) == -1) {
+ relay_abort_http(con, 500, "failed to allocate URL", 0);
+ return (PN_FAIL);
+ }
+
+ DPRINTF("%s: %s", __func__, val);
+
+ switch (type) {
+ case DIGEST_SHA1:
+ case DIGEST_MD5:
+ if ((md = digeststr(type, val, strlen(val), NULL)) == NULL) {
+ relay_abort_http(con, 500,
+ "failed to allocate digest", 0);
+ goto fail;
+ }
+ pkv.key = md;
+ break;
+ case DIGEST_NONE:
+ pkv.key = val;
+ break;
+ }
+ pkv.type = NODE_TYPE_URL;
+ pkv.value = "";
+
+ if ((proot = RB_FIND(proto_tree, cre->tree, &pkv)) == NULL)
+ goto done;
+
+ PROTONODE_FOREACH(pnv, proot, entry) {
+ ret = relay_handle_http(cre, proot, pnv, &pkv, 0);
+ if (ret == PN_FAIL)
+ goto fail;
+ }
+
+ done:
+ ret = PN_PASS;
+ fail:
+ if (md != NULL)
+ free(md);
+ free(val);
+ return (ret);
+}
+
+int
+relay_lookup_url(struct ctl_relay_event *cre, const char *str,
+ enum digest_type type)
+{
+ struct rsession *con = cre->con;
+ int i, j, dots;
+ char *hi[RELAY_MAXLOOKUPLEVELS], *p, *pp, *c, ch;
+ char ph[MAXHOSTNAMELEN];
+ int ret;
+
+ if (cre->path == NULL)
+ return (PN_PASS);
+
+ /*
+ * This is an URL lookup algorithm inspired by
+ * http://code.google.com/apis/safebrowsing/
+ * developers_guide.html#PerformingLookups
+ */
+
+ DPRINTF("%s: host: '%s', path: '%s', query: '%s'", __func__,
+ str, cre->path, cre->args == NULL ? "" : cre->args);
+
+ if (canonicalize_host(str, ph, sizeof(ph)) == NULL) {
+ relay_abort_http(con, 400, "invalid host name", 0);
+ return (PN_FAIL);
+ }
+
+ bzero(hi, sizeof(hi));
+ for (dots = -1, i = strlen(ph) - 1; i > 0; i--) {
+ if (ph[i] == '.' && ++dots)
+ hi[dots - 1] = &ph[i + 1];
+ if (dots > (RELAY_MAXLOOKUPLEVELS - 2))
+ break;
+ }
+ if (dots == -1)
+ dots = 0;
+ hi[dots] = ph;
+
+ if ((pp = strdup(cre->path)) == NULL) {
+ relay_abort_http(con, 500, "failed to allocate path", 0);
+ return (PN_FAIL);
+ }
+ for (i = (RELAY_MAXLOOKUPLEVELS - 1); i >= 0; i--) {
+ if (hi[i] == NULL)
+ continue;
+
+ /* 1. complete path with query */
+ if (cre->args != NULL)
+ if ((ret = _relay_lookup_url(cre, hi[i],
+ pp, cre->args, type)) != PN_PASS)
+ goto done;
+
+ /* 2. complete path without query */
+ if ((ret = _relay_lookup_url(cre, hi[i],
+ pp, NULL, type)) != PN_PASS)
+ goto done;
+
+ /* 3. traverse path */
+ for (j = 0, p = strchr(pp, '/');
+ p != NULL; p = strchr(p, '/'), j++) {
+ if (j > (RELAY_MAXLOOKUPLEVELS - 2) || ++p == '\0')
+ break;
+ c = &pp[p - pp];
+ ch = *c;
+ *c = '\0';
+ if ((ret = _relay_lookup_url(cre, hi[i],
+ pp, NULL, type)) != PN_PASS)
+ goto done;
+ *c = ch;
+ }
+ }
+
+ ret = PN_PASS;
+ done:
+ free(pp);
+ return (ret);
+}
+
+int
+relay_lookup_query(struct ctl_relay_event *cre)
+{
+ struct rsession *con = cre->con;
+ struct protonode *proot, *pnv, pkv;
+ char *val, *ptr;
+ int ret;
+
+ if (cre->path == NULL || cre->args == NULL || strlen(cre->args) < 2)
+ return (PN_PASS);
+ if ((val = strdup(cre->args)) == NULL) {
+ relay_abort_http(con, 500, "failed to allocate query", 0);
+ return (PN_FAIL);
+ }
+
+ ptr = val;
+ while (ptr != NULL && strlen(ptr)) {
+ pkv.key = ptr;
+ pkv.type = NODE_TYPE_QUERY;
+ if ((ptr = strchr(ptr, '&')) != NULL)
+ *ptr++ = '\0';
+ if ((pkv.value =
+ strchr(pkv.key, '=')) == NULL ||
+ strlen(pkv.value) < 1)
+ continue;
+ *pkv.value++ = '\0';
+
+ if ((proot = RB_FIND(proto_tree, cre->tree, &pkv)) == NULL)
+ continue;
+ PROTONODE_FOREACH(pnv, proot, entry) {
+ ret = relay_handle_http(cre, proot,
+ pnv, &pkv, 0);
+ if (ret == PN_FAIL)
+ goto done;
+ }
+ }
+
+ ret = PN_PASS;
+ done:
+ free(val);
+ return (ret);
+}
+
+int
+relay_lookup_cookie(struct ctl_relay_event *cre, const char *str)
+{
+ struct rsession *con = cre->con;
+ struct protonode *proot, *pnv, pkv;
+ char *val, *ptr;
+ int ret;
+
+ if ((val = strdup(str)) == NULL) {
+ relay_abort_http(con, 500, "failed to allocate cookie", 0);
+ return (PN_FAIL);
+ }
+
+ for (ptr = val; ptr != NULL && strlen(ptr);) {
+ if (*ptr == ' ')
+ *ptr++ = '\0';
+ pkv.key = ptr;
+ pkv.type = NODE_TYPE_COOKIE;
+ if ((ptr = strchr(ptr, ';')) != NULL)
+ *ptr++ = '\0';
+ /*
+ * XXX We do not handle attributes
+ * ($Path, $Domain, or $Port)
+ */
+ if (*pkv.key == '$')
+ continue;
+
+ if ((pkv.value =
+ strchr(pkv.key, '=')) == NULL ||
+ strlen(pkv.value) < 1)
+ continue;
+ *pkv.value++ = '\0';
+ if (*pkv.value == '"')
+ *pkv.value++ = '\0';
+ if (pkv.value[strlen(pkv.value) - 1] == '"')
+ pkv.value[strlen(pkv.value) - 1] = '\0';
+ if ((proot = RB_FIND(proto_tree, cre->tree, &pkv)) == NULL)
+ continue;
+ PROTONODE_FOREACH(pnv, proot, entry) {
+ ret = relay_handle_http(cre, proot, pnv, &pkv, 0);
+ if (ret == PN_FAIL)
+ goto done;
+ }
+ }
+
+ ret = PN_PASS;
+ done:
+ free(val);
+ return (ret);
+}
+
+void
+relay_abort_http(struct rsession *con, u_int code, const char *msg,
+ u_int16_t labelid)
+{
+ struct relay *rlay = (struct relay *)con->se_relay;
+ struct bufferevent *bev = con->se_in.bev;
+ const char *httperr = print_httperror(code), *text = "";
+ char *httpmsg;
+ time_t t;
+ struct tm *lt;
+ char tmbuf[32], hbuf[128];
+ const char *style, *label = NULL;
+
+ /* In some cases this function may be called from generic places */
+ if (rlay->rl_proto->type != RELAY_PROTO_HTTP ||
+ (rlay->rl_proto->flags & F_RETURN) == 0) {
+ relay_close(con, msg);
+ return;
+ }
+
+ if (bev == NULL)
+ goto done;
+
+ /* Some system information */
+ if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
+ goto done;
+
+ /* RFC 2616 "tolerates" asctime() */
+ time(&t);
+ lt = localtime(&t);
+ tmbuf[0] = '\0';
+ if (asctime_r(lt, tmbuf) != NULL)
+ tmbuf[strlen(tmbuf) - 1] = '\0'; /* skip final '\n' */
+
+ /* Do not send details of the Internal Server Error */
+ if (code != 500)
+ text = msg;
+ if (labelid != 0)
+ label = pn_id2name(labelid);
+
+ /* A CSS stylesheet allows minimal customization by the user */
+ if ((style = rlay->rl_proto->style) == NULL)
+ style = "body { background-color: #a00000; color: white; }";
+
+ /* Generate simple HTTP+HTML error document */
+ if (asprintf(&httpmsg,
+ "HTTP/1.x %03d %s\r\n"
+ "Date: %s\r\n"
+ "Server: %s\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n"
+ "<!DOCTYPE HTML PUBLIC "
+ "\"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>%03d %s</title>\n"
+ "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>%s</h1>\n"
+ "<div id='m'>%s</div>\n"
+ "<div id='l'>%s</div>\n"
+ "<hr><address>%s at %s port %d</address>\n"
+ "</body>\n"
+ "</html>\n",
+ code, httperr, tmbuf, RELAYD_SERVERNAME,
+ code, httperr, style, httperr, text,
+ label == NULL ? "" : label,
+ RELAYD_SERVERNAME, hbuf, ntohs(rlay->rl_conf.port)) == -1)
+ goto done;
+
+ /* Dump the message without checking for success */
+ relay_dump(&con->se_in, httpmsg, strlen(httpmsg));
+ free(httpmsg);
+
+ done:
+ if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1)
+ relay_close(con, msg);
+ else {
+ relay_close(con, httpmsg);
+ free(httpmsg);
+ }
+}
+
+char *
+relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf, size_t len)
+{
+ struct rsession *con = cre->con;
+ struct relay *rlay = (struct relay *)con->se_relay;
+ char ibuf[128];
+
+ (void)strlcpy(buf, val, len);
+
+ if (strstr(val, "$REMOTE_") != NULL) {
+ if (strstr(val, "$REMOTE_ADDR") != NULL) {
+ if (print_host(&cre->ss, ibuf, sizeof(ibuf)) == NULL)
+ return (NULL);
+ if (expand_string(buf, len,
+ "$REMOTE_ADDR", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$REMOTE_PORT") != NULL) {
+ snprintf(ibuf, sizeof(ibuf), "%u", ntohs(cre->port));
+ if (expand_string(buf, len,
+ "$REMOTE_PORT", ibuf) != 0)
+ return (NULL);
+ }
+ }
+ if (strstr(val, "$SERVER_") != NULL) {
+ if (strstr(val, "$SERVER_ADDR") != NULL) {
+ if (print_host(&rlay->rl_conf.ss,
+ ibuf, sizeof(ibuf)) == NULL)
+ return (NULL);
+ if (expand_string(buf, len,
+ "$SERVER_ADDR", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$SERVER_PORT") != NULL) {
+ snprintf(ibuf, sizeof(ibuf), "%u",
+ ntohs(rlay->rl_conf.port));
+ if (expand_string(buf, len,
+ "$SERVER_PORT", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$SERVER_NAME") != NULL) {
+ if (expand_string(buf, len,
+ "$SERVER_NAME", RELAYD_SERVERNAME) != 0)
+ return (NULL);
+ }
+ }
+ if (strstr(val, "$TIMEOUT") != NULL) {
+ snprintf(ibuf, sizeof(ibuf), "%lu",
+ rlay->rl_conf.timeout.tv_sec);
+ if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0)
+ return (NULL);
+ }
+
+ return (buf);
+}
+
+int
+relay_resolve(struct ctl_relay_event *cre,
+ struct protonode *proot, struct protonode *pn)
+{
+ struct rsession *con = cre->con;
+ char buf[IBUF_READ_SIZE], *ptr;
+ int id;
+
+ if (pn->mark && (pn->mark != con->se_mark))
+ return (0);
+
+ switch (pn->action) {
+ case NODE_ACTION_FILTER:
+ id = cre->nodes[proot->id];
+ if (SIMPLEQ_NEXT(pn, entry) == NULL)
+ cre->nodes[proot->id] = 0;
+ if (id <= 1)
+ return (0);
+ break;
+ case NODE_ACTION_EXPECT:
+ id = cre->nodes[proot->id];
+ if (SIMPLEQ_NEXT(pn, entry) == NULL)
+ cre->nodes[proot->id] = 0;
+ if (id > 1)
+ return (0);
+ break;
+ default:
+ if (cre->nodes[pn->id]) {
+ cre->nodes[pn->id] = 0;
+ return (0);
+ }
+ break;
+ }
+ switch (pn->action) {
+ case NODE_ACTION_APPEND:
+ case NODE_ACTION_CHANGE:
+ ptr = pn->value;
+ if ((pn->flags & PNFLAG_MACRO) &&
+ (ptr = relay_expand_http(cre, pn->value,
+ buf, sizeof(buf))) == NULL)
+ break;
+ if (relay_bufferevent_print(cre->dst, pn->key) == -1 ||
+ relay_bufferevent_print(cre->dst, ": ") == -1 ||
+ relay_bufferevent_print(cre->dst, ptr) == -1 ||
+ relay_bufferevent_print(cre->dst, "\r\n") == -1) {
+ relay_abort_http(con, 500,
+ "failed to modify header", 0);
+ return (-1);
+ }
+ DPRINTF("%s: add '%s: %s'", __func__, pn->key, ptr);
+ break;
+ case NODE_ACTION_EXPECT:
+ DPRINTF("%s: missing '%s: %s'", __func__, pn->key, pn->value);
+ relay_abort_http(con, 403, "incomplete request", pn->label);
+ return (-1);
+ case NODE_ACTION_FILTER:
+ DPRINTF("%s: filtered '%s: %s'", __func__, pn->key, pn->value);
+ relay_abort_http(con, 403, "rejecting request", pn->label);
+ return (-1);
+ default:
+ break;
+ }
+ return (0);
+}
+
+int
+relay_handle_http(struct ctl_relay_event *cre, struct protonode *proot,
+ struct protonode *pn, struct protonode *pk, int header)
+{
+ struct rsession *con = cre->con;
+ char buf[IBUF_READ_SIZE], *ptr;
+ int ret = PN_DROP, mark = 0;
+ struct protonode *next;
+
+ /* Check if this action depends on a marked session */
+ if (pn->mark != 0)
+ mark = pn->mark == con->se_mark ? 1 : -1;
+
+ switch (pn->action) {
+ case NODE_ACTION_EXPECT:
+ case NODE_ACTION_FILTER:
+ case NODE_ACTION_MARK:
+ break;
+ default:
+ if (mark == -1)
+ return (PN_PASS);
+ break;
+ }
+
+ switch (pn->action) {
+ case NODE_ACTION_APPEND:
+ if (!header)
+ return (PN_PASS);
+ ptr = pn->value;
+ if ((pn->flags & PNFLAG_MACRO) &&
+ (ptr = relay_expand_http(cre, pn->value,
+ buf, sizeof(buf))) == NULL)
+ break;
+ if (relay_bufferevent_print(cre->dst, pn->key) == -1 ||
+ relay_bufferevent_print(cre->dst, ": ") == -1 ||
+ relay_bufferevent_print(cre->dst, pk->value) == -1 ||
+ relay_bufferevent_print(cre->dst, ", ") == -1 ||
+ relay_bufferevent_print(cre->dst, ptr) == -1 ||
+ relay_bufferevent_print(cre->dst, "\r\n") == -1)
+ goto fail;
+ cre->nodes[pn->id] = 1;
+ DPRINTF("%s: append '%s: %s, %s'", __func__,
+ pk->key, pk->value, ptr);
+ break;
+ case NODE_ACTION_CHANGE:
+ case NODE_ACTION_REMOVE:
+ if (!header)
+ return (PN_PASS);
+ DPRINTF("%s: change/remove '%s: %s'", __func__,
+ pk->key, pk->value);
+ break;
+ case NODE_ACTION_EXPECT:
+ /*
+ * A client may specify the header line for multiple times
+ * trying to circumvent the filter.
+ */
+ if (cre->nodes[proot->id] > 1) {
+ relay_abort_http(con, 400, "repeated header line", 0);
+ return (PN_FAIL);
+ }
+ /* FALLTHROUGH */
+ case NODE_ACTION_FILTER:
+ DPRINTF("%s: %s '%s: %s'", __func__,
+ (pn->action == NODE_ACTION_EXPECT) ? "expect" : "filter",
+ pn->key, pn->value);
+
+ /* Do not drop the entity */
+ ret = PN_PASS;
+
+ if (mark != -1 &&
+ fnmatch(pn->value, pk->value, FNM_CASEFOLD) == 0) {
+ cre->nodes[proot->id] = 1;
+
+ /* Fail instantly */
+ if (pn->action == NODE_ACTION_FILTER) {
+ (void)relay_lognode(con, pn, pk,
+ buf, sizeof(buf));
+ relay_abort_http(con, 403,
+ "rejecting request", pn->label);
+ return (PN_FAIL);
+ }
+ }
+ next = SIMPLEQ_NEXT(pn, entry);
+ if (next == NULL || next->action != pn->action)
+ cre->nodes[proot->id]++;
+ break;
+ case NODE_ACTION_HASH:
+ DPRINTF("%s: hash '%s: %s'", __func__,
+ pn->key, pk->value);
+ con->se_hashkey = hash32_str(pk->value, con->se_hashkey);
+ ret = PN_PASS;
+ break;
+ case NODE_ACTION_LOG:
+ log_info("%s: log '%s: %s'", __func__, pn->key, pk->value);
+ ret = PN_PASS;
+ break;
+ case NODE_ACTION_MARK:
+ DPRINTF("%s: mark '%s: %s'", __func__,
+ pn->key, pk->value);
+ if (fnmatch(pn->value, pk->value, FNM_CASEFOLD) == 0)
+ con->se_mark = pn->mark;
+ ret = PN_PASS;
+ break;
+ case NODE_ACTION_NONE:
+ return (PN_PASS);
+ }
+ if (mark != -1 && relay_lognode(con, pn, pk, buf, sizeof(buf)) == -1)
+ goto fail;
+
+ return (ret);
+ fail:
+ relay_abort_http(con, 500, strerror(errno), 0);
+ return (PN_FAIL);
+}