summaryrefslogtreecommitdiff
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
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@
-rw-r--r--usr.sbin/relayd/Makefile6
-rw-r--r--usr.sbin/relayd/relay.c1070
-rw-r--r--usr.sbin/relayd/relay_http.c1079
-rw-r--r--usr.sbin/relayd/relayd.h34
4 files changed, 1119 insertions, 1070 deletions
diff --git a/usr.sbin/relayd/Makefile b/usr.sbin/relayd/Makefile
index 0ae2acc345b..5e5b8df07e3 100644
--- a/usr.sbin/relayd/Makefile
+++ b/usr.sbin/relayd/Makefile
@@ -1,10 +1,10 @@
-# $OpenBSD: Makefile,v 1.22 2011/05/19 08:56:49 reyk Exp $
+# $OpenBSD: Makefile,v 1.23 2012/09/20 12:30:20 reyk Exp $
PROG= relayd
SRCS= parse.y log.c control.c ssl.c ssl_privsep.c \
relayd.c pfe.c pfe_filter.c pfe_route.c hce.c relay.c \
- relay_udp.c carp.c check_icmp.c check_tcp.c check_script.c \
- name2id.c snmp.c shuffle.c proc.c config.c
+ relay_http.c relay_udp.c carp.c check_icmp.c check_tcp.c \
+ check_script.c name2id.c snmp.c shuffle.c proc.c config.c
MAN= relayd.8 relayd.conf.5
LDADD= -levent -lssl -lcrypto -lutil
diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c
index e4120b8430f..c3ba78464c2 100644
--- a/usr.sbin/relayd/relay.c
+++ b/usr.sbin/relayd/relay.c
@@ -1,7 +1,7 @@
-/* $OpenBSD: relay.c,v 1.151 2012/09/17 19:27:38 benno Exp $ */
+/* $OpenBSD: relay.c,v 1.152 2012/09/20 12:30:20 reyk Exp $ */
/*
- * Copyright (c) 2006, 2007, 2008 Reyk Floeter <reyk@openbsd.org>
+ * 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
@@ -68,43 +68,11 @@ int relay_socket_connect(struct sockaddr_storage *, in_port_t,
void relay_accept(int, short, void *);
void relay_input(struct rsession *);
-int relay_connect(struct rsession *);
-void relay_connected(int, short, void *);
-void relay_bindanyreq(struct rsession *, in_port_t, int);
-void relay_bindany(int, short, void *);
-
u_int32_t relay_hash_addr(struct sockaddr_storage *, u_int32_t);
-void relay_write(struct bufferevent *, void *);
-void relay_read(struct bufferevent *, void *);
-void relay_error(struct bufferevent *, short, void *);
-void relay_dump(struct ctl_relay_event *, const void *, size_t);
-
int relay_splice(struct ctl_relay_event *);
int relay_splicelen(struct ctl_relay_event *);
-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);
-int relay_lognode(struct rsession *,
- struct protonode *, struct protonode *, char *, size_t);
-void relay_read_http(struct bufferevent *, void *);
-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_close_http(struct rsession *, u_int, const char *,
- u_int16_t);
-void relay_http_request_close(struct ctl_relay_event *);
-
SSL_CTX *relay_ssl_ctx_create(struct relay *);
void relay_ssl_transaction(struct rsession *,
struct ctl_relay_event *);
@@ -114,18 +82,6 @@ void relay_ssl_connected(struct ctl_relay_event *);
void relay_ssl_readcb(int, short, void *);
void relay_ssl_writecb(int, short, void *);
-int relay_bufferevent_add(struct event *, int);
-#ifdef notyet
-int relay_bufferevent_printf(struct ctl_relay_event *,
- const char *, ...);
-#endif
-int relay_bufferevent_print(struct ctl_relay_event *, char *);
-int relay_bufferevent_write_buffer(struct ctl_relay_event *,
- struct evbuffer *);
-int relay_bufferevent_write_chunk(struct ctl_relay_event *,
- struct evbuffer *, size_t);
-int relay_bufferevent_write(struct ctl_relay_event *,
- void *, size_t);
char *relay_load_file(const char *, off_t *);
static __inline int
relay_proto_cmp(struct protonode *, struct protonode *);
@@ -657,7 +613,7 @@ relay_connected(int fd, short sig, void *arg)
int error;
if (sig == EV_TIMEOUT) {
- relay_close_http(con, 504, "connect timeout", 0);
+ relay_abort_http(con, 504, "connect timeout", 0);
return;
}
@@ -666,7 +622,7 @@ relay_connected(int fd, short sig, void *arg)
&len) == -1 || error) {
if (error)
errno = error;
- relay_close_http(con, 500, "socket error", 0);
+ relay_abort_http(con, 500, "socket error", 0);
return;
}
@@ -685,7 +641,7 @@ relay_connected(int fd, short sig, void *arg)
outrd = relay_read_http;
if ((con->se_out.nodes = calloc(proto->response_nodes,
sizeof(u_int8_t))) == NULL) {
- relay_close_http(con, 500,
+ relay_abort_http(con, 500,
"failed to allocate nodes", 0);
return;
}
@@ -703,7 +659,7 @@ relay_connected(int fd, short sig, void *arg)
*/
bev = bufferevent_new(fd, outrd, outwr, relay_error, &con->se_out);
if (bev == NULL) {
- relay_close_http(con, 500,
+ relay_abort_http(con, 500,
"failed to allocate output buffer event", 0);
return;
}
@@ -830,127 +786,6 @@ relay_read(struct bufferevent *bev, void *arg)
}
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_close_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_close_http(con, 403, "incomplete request", pn->label);
- return (-1);
- case NODE_ACTION_FILTER:
- DPRINTF("%s: filtered '%s: %s'", __func__, pn->key, pn->value);
- relay_close_http(con, 403, "rejecting request", pn->label);
- return (-1);
- default:
- break;
- }
- return (0);
-}
-
-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_lognode(struct rsession *con, struct protonode *pn, struct protonode *pk,
char *buf, size_t len)
{
@@ -971,899 +806,6 @@ relay_lognode(struct rsession *con, struct protonode *pn, struct protonode *pk,
}
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_close_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_close_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_close_http(con, 500, strerror(errno), 0);
- return (PN_FAIL);
-}
-
-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;
-}
-
-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_close_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_close_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_close_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_close_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_close_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_close_http(con, 500, strerror(errno), 0);
- return;
- abort:
- free(line);
-}
-
-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_close_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_close_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_close_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_close_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_close_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_close_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_close_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);
- }
-}
-
-int
relay_splice(struct ctl_relay_event *cre)
{
struct rsession *con = cre->con;
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);
+}
diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h
index f68b58457b9..0667258ae35 100644
--- a/usr.sbin/relayd/relayd.h
+++ b/usr.sbin/relayd/relayd.h
@@ -1,8 +1,8 @@
-/* $OpenBSD: relayd.h,v 1.156 2012/07/09 09:52:05 deraadt Exp $ */
+/* $OpenBSD: relayd.h,v 1.157 2012/09/20 12:30:20 reyk Exp $ */
/*
+ * Copyright (c) 2006 - 2012 Reyk Floeter <reyk@openbsd.org>
* Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
- * Copyright (c) 2006, 2007, 2008 Reyk Floeter <reyk@openbsd.org>
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
@@ -18,6 +18,9 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#ifndef _RELAYD_H
+#define _RELAYD_H
+
#include <sys/tree.h>
#include <sys/param.h> /* MAXHOSTNAMELEN */
@@ -959,11 +962,34 @@ int relay_socket_af(struct sockaddr_storage *, in_port_t);
in_port_t
relay_socket_getport(struct sockaddr_storage *);
int relay_cmp_af(struct sockaddr_storage *,
- struct sockaddr_storage *);
+ struct sockaddr_storage *);
+void relay_write(struct bufferevent *, void *);
+void relay_read(struct bufferevent *, void *);
+void relay_error(struct bufferevent *, short, void *);
+int relay_lognode(struct rsession *,
+ struct protonode *, struct protonode *, char *, size_t);
+int relay_connect(struct rsession *);
+void relay_connected(int, short, void *);
+void relay_bindanyreq(struct rsession *, in_port_t, int);
+void relay_bindany(int, short, void *);
+void relay_dump(struct ctl_relay_event *, const void *, size_t);
+int relay_bufferevent_add(struct event *, int);
+int relay_bufferevent_print(struct ctl_relay_event *, char *);
+int relay_bufferevent_write_buffer(struct ctl_relay_event *,
+ struct evbuffer *);
+int relay_bufferevent_write_chunk(struct ctl_relay_event *,
+ struct evbuffer *, size_t);
+int relay_bufferevent_write(struct ctl_relay_event *,
+ void *, size_t);
RB_PROTOTYPE(proto_tree, protonode, se_nodes, relay_proto_cmp);
SPLAY_PROTOTYPE(session_tree, rsession, se_nodes, relay_session_cmp);
+/* relay_http.c */
+void relay_abort_http(struct rsession *, u_int, const char *,
+ u_int16_t);
+void relay_read_http(struct bufferevent *, void *);
+
/* relay_udp.c */
void relay_udp_privinit(struct relayd *, struct relay *);
void relay_udp_init(struct relay *);
@@ -1115,3 +1141,5 @@ int config_setprotonode(struct relayd *, enum privsep_procid,
int config_getprotonode(struct relayd *, struct imsg *);
int config_setrelay(struct relayd *env, struct relay *);
int config_getrelay(struct relayd *, struct imsg *);
+
+#endif /* _RELAYD_H */