From 6d196c9daa83925cc04f8019c1c0065317f6efdf Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 13 Jul 2014 01:15:01 +0200 Subject: Simple file serving --- Makefile | 5 +- config.c | 2 + http.h | 17 + httpd.c | 209 +++++++++ httpd.h | 68 +++ server.c | 27 +- server_file.c | 123 ++++++ server_http.c | 1307 +++++++-------------------------------------------------- 8 files changed, 593 insertions(+), 1165 deletions(-) create mode 100644 server_file.c diff --git a/Makefile b/Makefile index 0aecfb2..539eb5b 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,13 @@ PROG= httpd SRCS= parse.y -SRCS+= config.c control.c httpd.c log.c proc.c server.c +SRCS+= config.c control.c httpd.c log.c proc.c +SRCS+= server.c server_http.c server_file.c MAN= httpd.8 httpd.conf.5 LDADD= -levent -lssl -lcrypto -lutil DPADD= ${LIBEVENT} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL} -DEBUG= -g -DDEBUG=3 +#DEBUG= -g -DDEBUG=3 CFLAGS+= -Wall -I${.CURDIR} -Werror CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations diff --git a/config.c b/config.c index affe3b2..269d1c4 100644 --- a/config.c +++ b/config.c @@ -199,7 +199,9 @@ config_setserver(struct httpd *env, struct server *srv) int config_getserver(struct httpd *env, struct imsg *imsg) { +#ifdef DEBUG struct privsep *ps = env->sc_ps; +#endif struct server *srv; u_int8_t *p = imsg->data; size_t s; diff --git a/http.h b/http.h index 625359a..4614e50 100644 --- a/http.h +++ b/http.h @@ -119,4 +119,21 @@ struct http_error { { 0, NULL } \ } +/* Used during runtime */ +struct http_descriptor { + struct kv http_pathquery; +#define http_path http_pathquery.kv_key +#define http_query http_pathquery.kv_value +#define http_rescode http_pathquery.kv_key +#define http_resmesg http_pathquery.kv_value + + char *http_version; + enum httpmethod http_method; + int http_chunked; + + /* A tree of headers and attached lists for repeated headers. */ + struct kvtree http_headers; + struct kv *http_lastheader; +}; + #endif /* _HTTPD_HTTP_H */ diff --git a/httpd.c b/httpd.c index 5f9c17d..32ed5ef 100644 --- a/httpd.c +++ b/httpd.c @@ -526,3 +526,212 @@ accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen, } return (ret); } + +struct kv * +kv_add(struct kvtree *keys, char *key, char *value) +{ + struct kv *kv, *oldkv; + + if (key == NULL) + return (NULL); + if ((kv = calloc(1, sizeof(*kv))) == NULL) + return (NULL); + if ((kv->kv_key = strdup(key)) == NULL) { + free(kv); + return (NULL); + } + if (value != NULL && + (kv->kv_value = strdup(value)) == NULL) { + free(kv->kv_key); + free(kv); + return (NULL); + } + TAILQ_INIT(&kv->kv_children); + + if ((oldkv = RB_INSERT(kvtree, keys, kv)) != NULL) { + TAILQ_INSERT_TAIL(&oldkv->kv_children, kv, kv_entry); + kv->kv_parent = oldkv; + } + + return (kv); +} + +int +kv_set(struct kv *kv, char *fmt, ...) +{ + va_list ap; + char *value = NULL; + struct kv *ckv; + + va_start(ap, fmt); + if (vasprintf(&value, fmt, ap) == -1) + return (-1); + va_end(ap); + + /* Remove all children */ + while ((ckv = TAILQ_FIRST(&kv->kv_children)) != NULL) { + TAILQ_REMOVE(&kv->kv_children, ckv, kv_entry); + kv_free(ckv); + free(ckv); + } + + /* Set the new value */ + if (kv->kv_value != NULL) + free(kv->kv_value); + kv->kv_value = value; + + return (0); +} + +int +kv_setkey(struct kv *kv, char *fmt, ...) +{ + va_list ap; + char *key = NULL; + + va_start(ap, fmt); + if (vasprintf(&key, fmt, ap) == -1) + return (-1); + va_end(ap); + + if (kv->kv_key != NULL) + free(kv->kv_key); + kv->kv_key = key; + + return (0); +} + +void +kv_delete(struct kvtree *keys, struct kv *kv) +{ + struct kv *ckv; + + RB_REMOVE(kvtree, keys, kv); + + /* Remove all children */ + while ((ckv = TAILQ_FIRST(&kv->kv_children)) != NULL) { + TAILQ_REMOVE(&kv->kv_children, ckv, kv_entry); + kv_free(ckv); + free(ckv); + } + + kv_free(kv); + free(kv); +} + +struct kv * +kv_extend(struct kvtree *keys, struct kv *kv, char *value) +{ + char *newvalue; + + if (kv == NULL) { + return (NULL); + } else if (kv->kv_value != NULL) { + if (asprintf(&newvalue, "%s%s", kv->kv_value, value) == -1) + return (NULL); + + free(kv->kv_value); + kv->kv_value = newvalue; + } else if ((kv->kv_value = strdup(value)) == NULL) + return (NULL); + + return (kv); +} + +void +kv_purge(struct kvtree *keys) +{ + struct kv *kv; + + while ((kv = RB_MIN(kvtree, keys)) != NULL) + kv_delete(keys, kv); +} + +void +kv_free(struct kv *kv) +{ + if (kv->kv_type == KEY_TYPE_NONE) + return; + if (kv->kv_key != NULL) { + free(kv->kv_key); + } + kv->kv_key = NULL; + if (kv->kv_value != NULL) { + free(kv->kv_value); + } + kv->kv_value = NULL; + memset(kv, 0, sizeof(*kv)); +} + +struct kv * +kv_inherit(struct kv *dst, struct kv *src) +{ + memset(dst, 0, sizeof(*dst)); + memcpy(dst, src, sizeof(*dst)); + TAILQ_INIT(&dst->kv_children); + + if (src->kv_key != NULL) { + if ((dst->kv_key = strdup(src->kv_key)) == NULL) { + kv_free(dst); + return (NULL); + } + } + if (src->kv_value != NULL) { + if ((dst->kv_value = strdup(src->kv_value)) == NULL) { + kv_free(dst); + return (NULL); + } + } + + return (dst); +} + +int +kv_log(struct evbuffer *log, struct kv *kv) +{ + char *msg; + + if (log == NULL) + return (0); + if (asprintf(&msg, " [%s%s%s]", + kv->kv_key == NULL ? "(unknown)" : kv->kv_key, + kv->kv_value == NULL ? "" : ": ", + kv->kv_value == NULL ? "" : kv->kv_value) == -1) + return (-1); + if (evbuffer_add(log, msg, strlen(msg)) == -1) { + free(msg); + return (-1); + } + free(msg); + + return (0); +} + +struct kv * +kv_find(struct kvtree *keys, struct kv *kv) +{ + struct kv *match; + const char *key; + + if (kv->kv_flags & KV_FLAG_GLOBBING) { + /* Test header key using shell globbing rules */ + key = kv->kv_key == NULL ? "" : kv->kv_key; + RB_FOREACH(match, kvtree, keys) { + if (fnmatch(key, match->kv_key, FNM_CASEFOLD) == 0) + break; + } + } else { + /* Fast tree-based lookup only works without globbing */ + match = RB_FIND(kvtree, keys, kv); + } + + return (match); +} + +int +kv_cmp(struct kv *a, struct kv *b) +{ + return (strcasecmp(a->kv_key, b->kv_key)); +} + +RB_GENERATE(kvtree, kv, kv_node, kv_cmp); diff --git a/httpd.h b/httpd.h index 0b9f864..33b9580 100644 --- a/httpd.h +++ b/httpd.h @@ -78,6 +78,36 @@ struct ctl_flags { u_int32_t cf_flags; }; +enum key_type { + KEY_TYPE_NONE = 0, + KEY_TYPE_COOKIE, + KEY_TYPE_HEADER, + KEY_TYPE_PATH, + KEY_TYPE_QUERY, + KEY_TYPE_URL, + KEY_TYPE_MAX +}; + +TAILQ_HEAD(kvlist, kv); +RB_HEAD(kvtree, kv); + +struct kv { + char *kv_key; + char *kv_value; + + enum key_type kv_type; + +#define KV_FLAG_INVALID 0x01 +#define KV_FLAG_GLOBBING 0x02 + u_int8_t kv_flags; + + struct kvlist kv_children; + struct kv *kv_parent; + TAILQ_ENTRY(kv) kv_entry; + + RB_ENTRY(kv) kv_node; +}; + struct portrange { in_port_t val[2]; u_int8_t op; @@ -223,6 +253,10 @@ struct client { struct bufferevent *clt_bev; struct evbuffer *clt_output; struct event clt_ev; + void *clt_desc; + + int clt_fd; + struct bufferevent *clt_file; off_t clt_toread; int clt_line; @@ -329,6 +363,28 @@ int server_bufferevent_write(struct client *, void *, size_t); SPLAY_PROTOTYPE(client_tree, client, clt_nodes, server_client_cmp); +/* server_http.c */ +void server_http_init(struct server *); +void server_http(struct httpd *); +int server_httpdesc_init(struct client *); +void server_read_http(struct bufferevent *, void *); +void server_abort_http(struct client *, u_int, const char *); +u_int server_httpmethod_byname(const char *); +const char + *server_httpmethod_byid(u_int); +const char + *server_httperror_byid(u_int); +void server_read_httpcontent(struct bufferevent *, void *); +void server_read_httpchunks(struct bufferevent *, void *); +int server_writeheader_kv(struct client *, struct kv *); +int server_writeheader_http(struct client *); +int server_writeresponse_http(struct client *); +void server_reset_http(struct client *); +void server_close_http(struct client *); + +/* server_file.c */ +int server_response(struct client *); + /* httpd.c */ void event_again(struct event *, int, short, void (*)(int, short, void *), @@ -342,6 +398,18 @@ char *get_string(u_int8_t *, size_t); void *get_data(u_int8_t *, size_t); int accept_reserve(int, struct sockaddr *, socklen_t *, int, volatile int *); +struct kv *kv_add(struct kvtree *, char *, char *); +int kv_set(struct kv *, char *, ...); +int kv_setkey(struct kv *, char *, ...); +void kv_delete(struct kvtree *, struct kv *); +struct kv *kv_extend(struct kvtree *, struct kv *, char *); +void kv_purge(struct kvtree *); +void kv_free(struct kv *); +struct kv *kv_inherit(struct kv *, struct kv *); +int kv_log(struct evbuffer *, struct kv *); +struct kv *kv_find(struct kvtree *, struct kv *); +int kv_cmp(struct kv *, struct kv *); +RB_PROTOTYPE(kvtree, kv, kv_node, kv_cmp); /* log.c */ void log_init(int); diff --git a/server.c b/server.c index b700b2f..aba3952 100644 --- a/server.c +++ b/server.c @@ -82,7 +82,7 @@ server(struct privsep *ps, struct privsep_proc *p) pid_t pid; env = ps->ps_env; pid = proc_run(ps, p, procs, nitems(procs), server_init, NULL); -// server_http(env); + server_http(env); return (pid); } @@ -108,6 +108,8 @@ server_privinit(struct server *srv) void server_init(struct privsep *ps, struct privsep_proc *p, void *arg) { + server_http(ps->ps_env); + if (config_init(ps->ps_env) == -1) fatal("failed to initialize configuration"); @@ -134,7 +136,7 @@ server_launch(void) struct server *srv; TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { -// server_http_init(srv); + server_http_init(srv); log_debug("%s: running server %s", __func__, srv->srv_conf.name); @@ -296,15 +298,14 @@ server_input(struct client *clt) evbuffercb inrd = server_read; evbuffercb inwr = server_write; -#if 0 - if (server_httpdesc_init(&clt->clt_in) == -1) { + if (server_httpdesc_init(clt) == -1) { server_close(clt, "failed to allocate http descriptor"); return; } -#endif + clt->clt_toread = TOREAD_HTTP_HEADER; -// inrd = server_read_http; + inrd = server_read_http; /* * Client <-> Server @@ -370,8 +371,7 @@ server_read(struct bufferevent *bev, void *arg) goto fail; if (clt->clt_done) goto done; - if (clt->clt_bev) - bufferevent_enable(clt->clt_bev, EV_READ); + bufferevent_enable(bev, EV_READ); return; done: server_close(clt, "done"); @@ -390,7 +390,7 @@ server_error(struct bufferevent *bev, short error, void *arg) return; } if (error & EVBUFFER_ERROR && errno == EFBIG) { - bufferevent_enable(clt->clt_bev, EV_READ); + bufferevent_enable(bev, EV_READ); return; } if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { @@ -444,6 +444,7 @@ server_accept(int fd, short event, void *arg) goto err; clt->clt_s = s; + clt->clt_fd = -1; clt->clt_toread = TOREAD_UNLIMITED; clt->clt_server = srv; clt->clt_id = ++server_cltid; @@ -511,6 +512,8 @@ server_close(struct client *clt, const char *msg) event_del(&clt->clt_ev); if (clt->clt_bev != NULL) bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + if (clt->clt_file != NULL) + bufferevent_disable(clt->clt_file, EV_READ|EV_WRITE); if ((env->sc_opts & HTTPD_OPT_LOGUPDATE) && msg != NULL) { memset(&ibuf, 0, sizeof(ibuf)); @@ -533,6 +536,12 @@ server_close(struct client *clt, const char *msg) bufferevent_free(clt->clt_bev); else if (clt->clt_output != NULL) evbuffer_free(clt->clt_output); + + if (clt->clt_file != NULL) + bufferevent_free(clt->clt_file); + if (clt->clt_fd != -1) + close(clt->clt_fd); + if (clt->clt_s != -1) { close(clt->clt_s); if (/* XXX */ -1) { diff --git a/server_file.c b/server_file.c new file mode 100644 index 0000000..00774c1 --- /dev/null +++ b/server_file.c @@ -0,0 +1,123 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2006 - 2014 Reyk Floeter + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "httpd.h" +#include "http.h" + +int +server_response(struct client *clt) +{ + struct http_descriptor *desc = clt->clt_desc; + struct server *srv = clt->clt_server; + struct kv *kv; + const char *errstr = NULL; + int fd = -1; + char path[MAXPATHLEN]; + struct stat st; + + if (desc->http_path == NULL) + goto fail; + + /* + * XXX hardcode XXX + */ + strlcpy(path, "/htdocs", sizeof(path)); + if (desc->http_path[0] != '/') + strlcat(path, "/", sizeof(path)); + strlcat(path, desc->http_path, sizeof(path)); + if (desc->http_path[strlen(desc->http_path) - 1] == '/') + strlcat(path, "index.html", sizeof(path)); + + if (access(path, R_OK) == -1) { + switch (errno) { + case EACCES: + server_abort_http(clt, 403, path); + break; + case ENOENT: + server_abort_http(clt, 404, path); + break; + default: + server_abort_http(clt, 500, path); + break; + } + return (-1); + } + + if ((fd = open(path, O_RDONLY)) == -1 || fstat(fd, &st) == -1) + goto fail; + + kv_purge(&desc->http_headers); + kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME); + kv_add(&desc->http_headers, "Connection", "close"); + if ((kv = kv_add(&desc->http_headers, "Content-Length", NULL)) != NULL) + kv_set(kv, "%ld", st.st_size); + kv_setkey(&desc->http_pathquery, "200"); + kv_set(&desc->http_pathquery, "%s", server_httperror_byid(200)); + + if (server_writeresponse_http(clt) == -1 || + server_bufferevent_print(clt, "\r\n") == -1 || + server_writeheader_http(clt) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) + goto fail; + + clt->clt_fd = fd; + clt->clt_file = bufferevent_new(clt->clt_fd, server_read, + server_write, server_error, clt); + if (clt->clt_file == NULL) { + errstr = "failed to allocate file buffer event"; + goto fail; + } + + bufferevent_settimeout(clt->clt_file, + srv->srv_conf.timeout.tv_sec, srv->srv_conf.timeout.tv_sec); + bufferevent_enable(clt->clt_file, EV_READ|EV_WRITE); + + return (0); + fail: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, 500, errstr); + return (-1); +} diff --git a/server_http.c b/server_http.c index 75e1521..63d15c7 100644 --- a/server_http.c +++ b/server_http.c @@ -45,52 +45,20 @@ #include -#include "relayd.h" +#include "httpd.h" #include "http.h" -static int _relay_lookup_url(struct ctl_relay_event *, char *, char *, - char *, struct kv *); -int relay_lookup_url(struct ctl_relay_event *, - const char *, struct kv *); -int relay_lookup_query(struct ctl_relay_event *, struct kv *); -int relay_lookup_cookie(struct ctl_relay_event *, const char *, - struct kv *); -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); -int relay_writeheader_kv(struct ctl_relay_event *, struct kv *); -int relay_writeheader_http(struct ctl_relay_event *, - struct ctl_relay_event *); -int relay_writerequest_http(struct ctl_relay_event *, - struct ctl_relay_event *); -int relay_writeresponse_http(struct ctl_relay_event *, - struct ctl_relay_event *); -void relay_reset_http(struct ctl_relay_event *); -static int relay_httpmethod_cmp(const void *, const void *); -static int relay_httperror_cmp(const void *, const void *); -int relay_httpquery_test(struct ctl_relay_event *, - struct relay_rule *, struct kvlist *); -int relay_httpheader_test(struct ctl_relay_event *, - struct relay_rule *, struct kvlist *); -int relay_httppath_test(struct ctl_relay_event *, - struct relay_rule *, struct kvlist *); -int relay_httpurl_test(struct ctl_relay_event *, - struct relay_rule *, struct kvlist *); -int relay_httpcookie_test(struct ctl_relay_event *, - struct relay_rule *, struct kvlist *); -int relay_apply_actions(struct ctl_relay_event *, struct kvlist *); -int relay_match_actions(struct ctl_relay_event *, - struct relay_rule *, struct kvlist *, struct kvlist *); -void relay_httpdesc_free(struct http_descriptor *); - -static struct relayd *env = NULL; +static int server_httpmethod_cmp(const void *, const void *); +static int server_httperror_cmp(const void *, const void *); +void server_httpdesc_free(struct http_descriptor *); + +static struct httpd *env = NULL; static struct http_method http_methods[] = HTTP_METHODS; static struct http_error http_errors[] = HTTP_ERRORS; void -relay_http(struct relayd *x_env) +server_http(struct httpd *x_env) { if (x_env != NULL) env = x_env; @@ -100,25 +68,20 @@ relay_http(struct relayd *x_env) /* Sort the HTTP lookup arrays */ qsort(http_methods, sizeof(http_methods) / sizeof(http_methods[0]) - 1, - sizeof(http_methods[0]), relay_httpmethod_cmp); + sizeof(http_methods[0]), server_httpmethod_cmp); qsort(http_errors, sizeof(http_errors) / sizeof(http_errors[0]) - 1, - sizeof(http_errors[0]), relay_httperror_cmp); + sizeof(http_errors[0]), server_httperror_cmp); } void -relay_http_init(struct relay *rlay) +server_http_init(struct server *srv) { - rlay->rl_proto->close = relay_close_http; - - relay_http(NULL); - - /* Calculate skip step for the filter rules (may take a while) */ - relay_calc_skip_steps(&rlay->rl_proto->rules); + /* nothing */ } int -relay_httpdesc_init(struct ctl_relay_event *cre) +server_httpdesc_init(struct client *clt) { struct http_descriptor *desc; @@ -126,13 +89,13 @@ relay_httpdesc_init(struct ctl_relay_event *cre) return (-1); RB_INIT(&desc->http_headers); - cre->desc = desc; + clt->clt_desc = desc; return (0); } void -relay_httpdesc_free(struct http_descriptor *desc) +server_httpdesc_free(struct http_descriptor *desc) { if (desc->http_path != NULL) { free(desc->http_path); @@ -146,45 +109,31 @@ relay_httpdesc_free(struct http_descriptor *desc) free(desc->http_version); desc->http_version = NULL; } - if (desc->query_key != NULL) { - free(desc->query_key); - desc->query_key = NULL; - } - if (desc->query_val != NULL) { - free(desc->query_val); - desc->query_val = NULL; - } kv_purge(&desc->http_headers); } void -relay_read_http(struct bufferevent *bev, void *arg) +server_read_http(struct bufferevent *bev, void *arg) { - struct ctl_relay_event *cre = arg; - struct http_descriptor *desc = cre->desc; - struct rsession *con = cre->con; - struct relay *rlay = con->se_relay; - struct protocol *proto = rlay->rl_proto; + struct client *clt = arg; + struct http_descriptor *desc = clt->clt_desc; struct evbuffer *src = EVBUFFER_INPUT(bev); char *line = NULL, *key, *value; - int action; const char *errstr; size_t size, linelen; struct kv *hdr = NULL; - getmonotime(&con->se_tv_last); + getmonotime(&clt->clt_tv_last); size = EVBUFFER_LENGTH(src); DPRINTF("%s: session %d: size %lu, to read %lld", - __func__, con->se_id, size, cre->toread); + __func__, clt->clt_id, size, clt->clt_toread); if (!size) { - if (cre->dir == RELAY_DIR_RESPONSE) - return; - cre->toread = TOREAD_HTTP_HEADER; + clt->clt_toread = TOREAD_HTTP_HEADER; goto done; } - while (!cre->done && (line = evbuffer_readline(src)) != NULL) { + while (!clt->clt_done && (line = evbuffer_readline(src)) != NULL) { linelen = strlen(line); /* @@ -192,17 +141,17 @@ relay_read_http(struct bufferevent *bev, void *arg) * libevent already stripped the \r\n for us. */ if (!linelen) { - cre->done = 1; + clt->clt_done = 1; free(line); break; } key = line; /* Limit the total header length minus \r\n */ - cre->headerlen += linelen; - if (cre->headerlen > RELAY_MAXHEADERLENGTH) { + clt->clt_headerlen += linelen; + if (clt->clt_headerlen > SERVER_MAXHEADERLENGTH) { free(line); - relay_abort_http(con, 413, "request too large", 0); + server_abort_http(clt, 413, "request too large"); return; } @@ -210,7 +159,7 @@ relay_read_http(struct bufferevent *bev, void *arg) * The first line is the GET/POST/PUT/... request, * subsequent lines are HTTP headers. */ - if (++cre->line == 1) + if (++clt->clt_line == 1) value = strchr(key, ' '); else if (*key == ' ' || *key == '\t') /* Multiline headers wrap with a space or tab */ @@ -218,9 +167,9 @@ relay_read_http(struct bufferevent *bev, void *arg) else value = strchr(key, ':'); if (value == NULL) { - if (cre->line == 1) { + if (clt->clt_line == 1) { free(line); - relay_abort_http(con, 400, "malformed", 0); + server_abort_http(clt, 400, "malformed"); return; } @@ -242,45 +191,16 @@ relay_read_http(struct bufferevent *bev, void *arg) } DPRINTF("%s: session %d: header '%s: %s'", __func__, - con->se_id, key, value); + clt->clt_id, key, value); /* * Identify and handle specific HTTP request methods */ - if (cre->line == 1 && cre->dir == RELAY_DIR_RESPONSE) { - desc->http_method = HTTP_METHOD_RESPONSE; - /* - * Decode response path and query - */ - desc->http_version = strdup(line); - if (desc->http_version == NULL) { - free(line); - goto fail; - } - desc->http_rescode = strdup(value); - if (desc->http_rescode == NULL) { - free(line); - goto fail; - } - desc->http_resmesg = strchr(desc->http_rescode, ' '); - if (desc->http_resmesg == NULL) { - free(line); - goto fail; - } - *desc->http_resmesg++ = '\0'; - if ((desc->http_resmesg = strdup(desc->http_resmesg)) - == NULL) { - free(line); - goto fail; - } - DPRINTF("http_version %s http_rescode %s " - "http_resmesg %s", desc->http_version, - desc->http_rescode, desc->http_resmesg); - goto lookup; - } else if (cre->line == 1 && cre->dir == RELAY_DIR_REQUEST) { - if ((desc->http_method = relay_httpmethod_byname(key)) + if (clt->clt_line == 1) { + if ((desc->http_method = server_httpmethod_byname(key)) == HTTP_METHOD_NONE) goto fail; + /* * Decode request path and query */ @@ -320,7 +240,7 @@ relay_read_http(struct bufferevent *bev, void *arg) * These method should not have a body * and thus no Content-Length header. */ - relay_abort_http(con, 400, "malformed", 0); + server_abort_http(clt, 400, "malformed"); goto abort; } @@ -331,19 +251,19 @@ relay_read_http(struct bufferevent *bev, void *arg) * the carriage return? And some browsers seem to * include the line length in the content-length. */ - cre->toread = strtonum(value, 0, LLONG_MAX, + clt->clt_toread = strtonum(value, 0, LLONG_MAX, &errstr); if (errstr) { - relay_abort_http(con, 500, errstr, 0); + server_abort_http(clt, 500, errstr); goto abort; } } - lookup: + if (strcasecmp("Transfer-Encoding", key) == 0 && strcasecmp("chunked", value) == 0) desc->http_chunked = 1; - if (cre->line != 1) { + if (clt->clt_line != 1) { if ((hdr = kv_add(&desc->http_headers, key, value)) == NULL) { free(line); @@ -354,188 +274,154 @@ relay_read_http(struct bufferevent *bev, void *arg) free(line); } - if (cre->done) { + if (clt->clt_done) { if (desc->http_method == HTTP_METHOD_NONE) { - relay_abort_http(con, 406, "no method", 0); - return; - } - - action = relay_test(proto, cre); - if (action == RES_FAIL) { - relay_close(con, "filter rule failed"); - return; - } else if (action != RES_PASS) { - relay_abort_http(con, 403, "Forbidden", con->se_label); + server_abort_http(clt, 406, "no method"); return; } switch (desc->http_method) { case HTTP_METHOD_CONNECT: /* Data stream */ - cre->toread = TOREAD_UNLIMITED; - bev->readcb = relay_read; + clt->clt_toread = TOREAD_UNLIMITED; + bev->readcb = server_read; break; case HTTP_METHOD_DELETE: case HTTP_METHOD_GET: case HTTP_METHOD_HEAD: case HTTP_METHOD_OPTIONS: - cre->toread = 0; + clt->clt_toread = 0; break; case HTTP_METHOD_POST: case HTTP_METHOD_PUT: case HTTP_METHOD_RESPONSE: /* HTTP request payload */ - if (cre->toread > 0) - bev->readcb = relay_read_httpcontent; + if (clt->clt_toread > 0) + bev->readcb = server_read_httpcontent; /* Single-pass HTTP body */ - if (cre->toread < 0) { - cre->toread = TOREAD_UNLIMITED; - bev->readcb = relay_read; + if (clt->clt_toread < 0) { + clt->clt_toread = TOREAD_UNLIMITED; + bev->readcb = server_read; } break; default: /* HTTP handler */ - cre->toread = TOREAD_HTTP_HEADER; - bev->readcb = relay_read_http; + clt->clt_toread = TOREAD_HTTP_HEADER; + bev->readcb = server_read_http; break; } if (desc->http_chunked) { /* Chunked transfer encoding */ - cre->toread = TOREAD_HTTP_CHUNK_LENGTH; - bev->readcb = relay_read_httpchunks; - } - - if (cre->dir == RELAY_DIR_REQUEST) { - if (relay_writerequest_http(cre->dst, cre) == -1) - goto fail; - } else { - if (relay_writeresponse_http(cre->dst, cre) == -1) - goto fail; + clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH; + bev->readcb = server_read_httpchunks; } - if (relay_bufferevent_print(cre->dst, "\r\n") == -1 || - relay_writeheader_http(cre->dst, cre) == -1 || - relay_bufferevent_print(cre->dst, "\r\n") == -1) - goto fail; - relay_reset_http(cre); done: - if (cre->dir == RELAY_DIR_REQUEST && cre->toread <= 0 && - cre->dst->bev == NULL) { - if (rlay->rl_conf.fwdmode == FWD_TRANS) { - relay_bindanyreq(con, 0, IPPROTO_TCP); + if (clt->clt_toread <= 0) { + if (server_response(clt) == -1) return; - } - if (relay_connect(con) == -1) - relay_abort_http(con, 502, "session failed", 0); - return; } + + server_reset_http(clt); } - if (con->se_done) { - relay_close(con, "last http read (done)"); + if (clt->clt_done) { + server_close(clt, "done"); return; } - if (EVBUFFER_LENGTH(src) && bev->readcb != relay_read_http) + if (EVBUFFER_LENGTH(src) && bev->readcb != server_read_http) bev->readcb(bev, arg); bufferevent_enable(bev, EV_READ); - if (relay_splice(cre) == -1) - relay_close(con, strerror(errno)); return; fail: - relay_abort_http(con, 500, strerror(errno), 0); + server_abort_http(clt, 500, strerror(errno)); return; abort: free(line); } void -relay_read_httpcontent(struct bufferevent *bev, void *arg) +server_read_httpcontent(struct bufferevent *bev, void *arg) { - struct ctl_relay_event *cre = arg; - struct rsession *con = cre->con; + struct client *clt = arg; struct evbuffer *src = EVBUFFER_INPUT(bev); size_t size; - getmonotime(&con->se_tv_last); + getmonotime(&clt->clt_tv_last); size = EVBUFFER_LENGTH(src); DPRINTF("%s: session %d: size %lu, to read %lld", __func__, - con->se_id, size, cre->toread); + clt->clt_id, size, clt->clt_toread); if (!size) return; - if (relay_spliceadjust(cre) == -1) - goto fail; - if (cre->toread > 0) { + if (clt->clt_toread > 0) { /* Read content data */ - if ((off_t)size > cre->toread) { - size = cre->toread; - if (relay_bufferevent_write_chunk(cre->dst, src, size) - == -1) + if ((off_t)size > clt->clt_toread) { + size = clt->clt_toread; + if (server_bufferevent_write_chunk(clt, + src, size) == -1) goto fail; - cre->toread = 0; + clt->clt_toread = 0; } else { - if (relay_bufferevent_write_buffer(cre->dst, src) == -1) + if (server_bufferevent_write_buffer(clt, src) == -1) goto fail; - cre->toread -= size; + clt->clt_toread -= size; } DPRINTF("%s: done, size %lu, to read %lld", __func__, - size, cre->toread); + size, clt->clt_toread); } - if (cre->toread == 0) { - cre->toread = TOREAD_HTTP_HEADER; - bev->readcb = relay_read_http; + if (clt->clt_toread == 0) { + clt->clt_toread = TOREAD_HTTP_HEADER; + bev->readcb = server_read_http; } - if (con->se_done) + if (clt->clt_done) goto done; - if (bev->readcb != relay_read_httpcontent) + if (bev->readcb != server_read_httpcontent) bev->readcb(bev, arg); bufferevent_enable(bev, EV_READ); return; done: - relay_close(con, "last http content read"); + server_close(clt, "last http content read"); return; fail: - relay_close(con, strerror(errno)); + server_close(clt, strerror(errno)); } void -relay_read_httpchunks(struct bufferevent *bev, void *arg) +server_read_httpchunks(struct bufferevent *bev, void *arg) { - struct ctl_relay_event *cre = arg; - struct rsession *con = cre->con; + struct client *clt = arg; struct evbuffer *src = EVBUFFER_INPUT(bev); char *line; long long llval; size_t size; - getmonotime(&con->se_tv_last); + getmonotime(&clt->clt_tv_last); size = EVBUFFER_LENGTH(src); DPRINTF("%s: session %d: size %lu, to read %lld", __func__, - con->se_id, size, cre->toread); + clt->clt_id, size, clt->clt_toread); if (!size) return; - if (relay_spliceadjust(cre) == -1) - goto fail; - if (cre->toread > 0) { + if (clt->clt_toread > 0) { /* Read chunk data */ - if ((off_t)size > cre->toread) { - size = cre->toread; - if (relay_bufferevent_write_chunk(cre->dst, src, size) + if ((off_t)size > clt->clt_toread) { + size = clt->clt_toread; + if (server_bufferevent_write_chunk(clt, src, size) == -1) goto fail; - cre->toread = 0; + clt->clt_toread = 0; } else { - if (relay_bufferevent_write_buffer(cre->dst, src) == -1) + if (server_bufferevent_write_buffer(clt, src) == -1) goto fail; - cre->toread -= size; + clt->clt_toread -= size; } DPRINTF("%s: done, size %lu, to read %lld", __func__, - size, cre->toread); + size, clt->clt_toread); } - switch (cre->toread) { + switch (clt->clt_toread) { case TOREAD_HTTP_CHUNK_LENGTH: line = evbuffer_readline(src); if (line == NULL) { @@ -554,20 +440,20 @@ relay_read_httpchunks(struct bufferevent *bev, void *arg) */ if (sscanf(line, "%llx", &llval) != 1 || llval < 0) { free(line); - relay_close(con, "invalid chunk size"); + server_close(clt, "invalid chunk size"); return; } - if (relay_bufferevent_print(cre->dst, line) == -1 || - relay_bufferevent_print(cre->dst, "\r\n") == -1) { + if (server_bufferevent_print(clt, line) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) { free(line); goto fail; } free(line); - if ((cre->toread = llval) == 0) { + if ((clt->clt_toread = llval) == 0) { DPRINTF("%s: last chunk", __func__); - cre->toread = TOREAD_HTTP_CHUNK_TRAILER; + clt->clt_toread = TOREAD_HTTP_CHUNK_TRAILER; } break; case TOREAD_HTTP_CHUNK_TRAILER: @@ -578,15 +464,15 @@ relay_read_httpchunks(struct bufferevent *bev, void *arg) bufferevent_enable(bev, EV_READ); return; } - if (relay_bufferevent_print(cre->dst, line) == -1 || - relay_bufferevent_print(cre->dst, "\r\n") == -1) { + if (server_bufferevent_print(clt, line) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) { free(line); goto fail; } if (strlen(line) == 0) { /* Switch to HTTP header mode */ - cre->toread = TOREAD_HTTP_HEADER; - bev->readcb = relay_read_http; + clt->clt_toread = TOREAD_HTTP_HEADER; + bev->readcb = server_read_http; } free(line); break; @@ -595,14 +481,14 @@ relay_read_httpchunks(struct bufferevent *bev, void *arg) line = evbuffer_readline(src); if (line != NULL) free(line); - if (relay_bufferevent_print(cre->dst, "\r\n") == -1) + if (server_bufferevent_print(clt, "\r\n") == -1) goto fail; - cre->toread = TOREAD_HTTP_CHUNK_LENGTH; + clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH; break; } next: - if (con->se_done) + if (clt->clt_done) goto done; if (EVBUFFER_LENGTH(src)) bev->readcb(bev, arg); @@ -610,296 +496,45 @@ relay_read_httpchunks(struct bufferevent *bev, void *arg) return; done: - relay_close(con, "last http chunk read (done)"); + server_close(clt, "last http chunk read (done)"); return; fail: - relay_close(con, strerror(errno)); + server_close(clt, strerror(errno)); } void -relay_reset_http(struct ctl_relay_event *cre) +server_reset_http(struct client *clt) { - struct http_descriptor *desc = cre->desc; + struct http_descriptor *desc = clt->clt_desc; - relay_httpdesc_free(desc); - if (cre->buf != NULL) { - free(cre->buf); - cre->buf = NULL; - cre->buflen = 0; - } + server_httpdesc_free(desc); desc->http_method = 0; desc->http_chunked = 0; - cre->headerlen = 0; - cre->line = 0; - cre->done = 0; -} - -static int -_relay_lookup_url(struct ctl_relay_event *cre, char *host, char *path, - char *query, struct kv *kv) -{ - struct rsession *con = cre->con; - char *val, *md = NULL; - int ret = RES_FAIL; - const char *str = NULL; - - 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 (RES_FAIL); - } - - switch (kv->kv_digest) { - case DIGEST_SHA1: - case DIGEST_MD5: - if ((md = digeststr(kv->kv_digest, - val, strlen(val), NULL)) == NULL) { - relay_abort_http(con, 500, - "failed to allocate digest", 0); - goto fail; - } - str = md; - break; - case DIGEST_NONE: - str = val; - break; - } - - DPRINTF("%s: session %d: %s, %s: %d", __func__, con->se_id, - str, kv->kv_key, strcasecmp(kv->kv_key, str)); - - if (strcasecmp(kv->kv_key, str) == 0) { - ret = RES_DROP; - goto fail; - } - - ret = RES_PASS; - fail: - if (md != NULL) - free(md); - free(val); - return (ret); + clt->clt_headerlen = 0; + clt->clt_line = 0; + clt->clt_done = 0; } -int -relay_lookup_url(struct ctl_relay_event *cre, const char *host, struct kv *kv) -{ - struct rsession *con = cre->con; - struct http_descriptor *desc = (struct http_descriptor *)cre->desc; - int i, j, dots; - char *hi[RELAY_MAXLOOKUPLEVELS], *p, *pp, *c, ch; - char ph[MAXHOSTNAMELEN]; - int ret; - - if (desc->http_path == NULL) - return (RES_PASS); - - /* - * This is an URL lookup algorithm inspired by - * http://code.google.com/apis/safebrowsing/ - * developers_guide.html#PerformingLookups - */ - - DPRINTF("%s: session %d: host '%s', path '%s', query '%s'", - __func__, con->se_id, host, desc->http_path, - desc->http_query == NULL ? "" : desc->http_query); - - if (canonicalize_host(host, ph, sizeof(ph)) == NULL) { - relay_abort_http(con, 400, "invalid host name", 0); - return (RES_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(desc->http_path)) == NULL) { - relay_abort_http(con, 500, "failed to allocate path", 0); - return (RES_FAIL); - } - for (i = (RELAY_MAXLOOKUPLEVELS - 1); i >= 0; i--) { - if (hi[i] == NULL) - continue; - - /* 1. complete path with query */ - if (desc->http_query != NULL) - if ((ret = _relay_lookup_url(cre, hi[i], - pp, desc->http_query, kv)) != RES_PASS) - goto done; - - /* 2. complete path without query */ - if ((ret = _relay_lookup_url(cre, hi[i], - pp, NULL, kv)) != RES_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, kv)) != RES_PASS) - goto done; - *c = ch; - } - } - - ret = RES_PASS; - done: - free(pp); - return (ret); -} - -int -relay_lookup_cookie(struct ctl_relay_event *cre, const char *str, - struct kv *kv) -{ - struct rsession *con = cre->con; - char *val, *ptr, *key, *value; - int ret; - - if ((val = strdup(str)) == NULL) { - relay_abort_http(con, 500, "failed to allocate cookie", 0); - return (RES_FAIL); - } - - for (ptr = val; ptr != NULL && strlen(ptr);) { - if (*ptr == ' ') - *ptr++ = '\0'; - key = ptr; - if ((ptr = strchr(ptr, ';')) != NULL) - *ptr++ = '\0'; - /* - * XXX We do not handle attributes - * ($Path, $Domain, or $Port) - */ - if (*key == '$') - continue; - - if ((value = - strchr(key, '=')) == NULL || - strlen(value) < 1) - continue; - *value++ = '\0'; - if (*value == '"') - *value++ = '\0'; - if (value[strlen(value) - 1] == '"') - value[strlen(value) - 1] = '\0'; - - DPRINTF("%s: session %d: %s = %s, %s = %s : %d", - __func__, con->se_id, - key, value, kv->kv_key, kv->kv_value, - strcasecmp(kv->kv_key, key)); - - if (strcasecmp(kv->kv_key, key) == 0 && - ((kv->kv_value == NULL) || - (fnmatch(kv->kv_value, value, - FNM_CASEFOLD) != FNM_NOMATCH))) { - ret = RES_DROP; - goto done; - } - } - - ret = RES_PASS; - - done: - free(val); - return (ret); -} - -int -relay_lookup_query(struct ctl_relay_event *cre, struct kv *kv) -{ - struct http_descriptor *desc = cre->desc; - struct kv *match = &desc->http_matchquery; - char *val, *ptr, *tmpkey = NULL, *tmpval = NULL; - int ret = -1; - - if (desc->http_query == NULL) - return (-1); - if ((val = strdup(desc->http_query)) == NULL) { - relay_abort_http(cre->con, 500, "failed to allocate query", 0); - return (-1); - } - - ptr = val; - while (ptr != NULL && strlen(ptr)) { - tmpkey = ptr; - if ((ptr = strchr(ptr, '&')) != NULL) - *ptr++ = '\0'; - if ((tmpval = strchr(tmpkey, '=')) == NULL || strlen(tmpval) - < 1) - continue; - *tmpval++ = '\0'; - - if (fnmatch(kv->kv_key, tmpkey, 0) != FNM_NOMATCH && - (kv->kv_value == NULL || fnmatch(kv->kv_value, tmpval, 0) - != FNM_NOMATCH)) - break; - else - tmpkey = NULL; - } - - if (tmpkey == NULL || tmpval == NULL) - goto done; - - match->kv_key = strdup(tmpkey); - if (match->kv_key == NULL) - goto done; - match->kv_value = strdup(tmpval); - if (match->kv_key == NULL) - goto done; - ret = 0; - - done: - free(val); - return (ret); -} - - void -relay_abort_http(struct rsession *con, u_int code, const char *msg, - u_int16_t labelid) +server_abort_http(struct client *clt, u_int code, const char *msg) { - struct relay *rlay = con->se_relay; - struct bufferevent *bev = con->se_in.bev; + struct server *srv = clt->clt_server; + struct bufferevent *bev = clt->clt_bev; const char *httperr = NULL, *text = ""; char *httpmsg; time_t t; struct tm *lt; char tmbuf[32], hbuf[128]; - const char *style, *label = NULL; + const char *style; - if ((httperr = relay_httperror_byid(code)) == NULL) + if ((httperr = server_httperror_byid(code)) == NULL) httperr = "Unknown Error"; - if (labelid != 0) - label = label_id2name(labelid); - - /* 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) + if (print_host(&srv->srv_conf.ss, hbuf, sizeof(hbuf)) == NULL) goto done; /* RFC 2616 "tolerates" asctime() */ @@ -914,8 +549,7 @@ relay_abort_http(struct rsession *con, u_int code, const char *msg, text = msg; /* A CSS stylesheet allows minimal customization by the user */ - if ((style = rlay->rl_proto->style) == NULL) - style = "body { background-color: #a00000; color: white; }"; + style = "body { background-color: white; color: black; }"; /* Generate simple HTTP+HTML error document */ if (asprintf(&httpmsg, @@ -935,147 +569,58 @@ relay_abort_http(struct rsession *con, u_int code, const char *msg, "\n" "

%s

\n" "
%s
\n" - "
%s
\n" "
%s at %s port %d
\n" "\n" "\n", - code, httperr, tmbuf, RELAYD_SERVERNAME, + code, httperr, tmbuf, HTTPD_SERVERNAME, code, httperr, style, httperr, text, - label == NULL ? "" : label, - RELAYD_SERVERNAME, hbuf, ntohs(rlay->rl_conf.port)) == -1) + HTTPD_SERVERNAME, hbuf, ntohs(srv->srv_conf.port)) == -1) goto done; /* Dump the message without checking for success */ - relay_dump(&con->se_in, httpmsg, strlen(httpmsg)); + server_dump(clt, httpmsg, strlen(httpmsg)); free(httpmsg); done: if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) - relay_close(con, msg); + server_close(clt, msg); else { - relay_close(con, httpmsg); + server_close(clt, httpmsg); free(httpmsg); } } void -relay_close_http(struct rsession *con) -{ - struct http_descriptor *desc[2] = { - con->se_in.desc, con->se_out.desc - }; - int i; - - for (i = 0; i < 2; i++) { - if (desc[i] == NULL) - continue; - relay_httpdesc_free(desc[i]); - free(desc[i]); - } -} - -char * -relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf, - size_t len) -{ - struct rsession *con = cre->con; - struct relay *rlay = con->se_relay; - char ibuf[128]; - - if (strlcpy(buf, val, len) >= len) - return (NULL); - - 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), "%lld", - (long long)rlay->rl_conf.timeout.tv_sec); - if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0) - return (NULL); - } - - return (buf); -} - -int -relay_writerequest_http(struct ctl_relay_event *dst, - struct ctl_relay_event *cre) +server_close_http(struct client *clt) { - struct http_descriptor *desc = (struct http_descriptor *)cre->desc; - const char *name = NULL; - - if ((name = relay_httpmethod_byid(desc->http_method)) == NULL) - return (-1); + struct http_descriptor *desc = clt->clt_desc; - if (relay_bufferevent_print(dst, name) == -1 || - relay_bufferevent_print(dst, " ") == -1 || - relay_bufferevent_print(dst, desc->http_path) == -1 || - (desc->http_query != NULL && - (relay_bufferevent_print(dst, "?") == -1 || - relay_bufferevent_print(dst, desc->http_query) == -1)) || - relay_bufferevent_print(dst, " ") == -1 || - relay_bufferevent_print(dst, desc->http_version) == -1) - return (-1); - - return (0); + if (desc == NULL) + return; + server_httpdesc_free(desc); + free(desc); } int -relay_writeresponse_http(struct ctl_relay_event *dst, - struct ctl_relay_event *cre) +server_writeresponse_http(struct client *clt) { - struct http_descriptor *desc = (struct http_descriptor *)cre->desc; + struct http_descriptor *desc = (struct http_descriptor *)clt->clt_desc; DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version, desc->http_rescode, desc->http_resmesg); - if (relay_bufferevent_print(dst, desc->http_version) == -1 || - relay_bufferevent_print(dst, " ") == -1 || - relay_bufferevent_print(dst, desc->http_rescode) == -1 || - relay_bufferevent_print(dst, " ") == -1 || - relay_bufferevent_print(dst, desc->http_resmesg) == -1) + if (server_bufferevent_print(clt, desc->http_version) == -1 || + server_bufferevent_print(clt, " ") == -1 || + server_bufferevent_print(clt, desc->http_rescode) == -1 || + server_bufferevent_print(clt, " ") == -1 || + server_bufferevent_print(clt, desc->http_resmesg) == -1) return (-1); return (0); } int -relay_writeheader_kv(struct ctl_relay_event *dst, struct kv *hdr) +server_writeheader_kv(struct client *clt, struct kv *hdr) { char *ptr; const char *key; @@ -1091,11 +636,11 @@ relay_writeheader_kv(struct ctl_relay_event *dst, struct kv *hdr) ptr = hdr->kv_value; DPRINTF("%s: ptr %s", __func__, ptr); - if (relay_bufferevent_print(dst, key) == -1 || + if (server_bufferevent_print(clt, key) == -1 || (ptr != NULL && - (relay_bufferevent_print(dst, ": ") == -1 || - relay_bufferevent_print(dst, ptr) == -1 || - relay_bufferevent_print(dst, "\r\n") == -1))) + (server_bufferevent_print(clt, ": ") == -1 || + server_bufferevent_print(clt, ptr) == -1 || + server_bufferevent_print(clt, "\r\n") == -1))) return (-1); DPRINTF("%s: %s: %s", __func__, key, hdr->kv_value == NULL ? "" : hdr->kv_value); @@ -1104,17 +649,16 @@ relay_writeheader_kv(struct ctl_relay_event *dst, struct kv *hdr) } int -relay_writeheader_http(struct ctl_relay_event *dst, struct ctl_relay_event - *cre) +server_writeheader_http(struct client *clt) { struct kv *hdr, *kv; - struct http_descriptor *desc = (struct http_descriptor *)cre->desc; + struct http_descriptor *desc = (struct http_descriptor *)clt->clt_desc; RB_FOREACH(hdr, kvtree, &desc->http_headers) { - if (relay_writeheader_kv(dst, hdr) == -1) + if (server_writeheader_kv(clt, hdr) == -1) return (-1); TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) { - if (relay_writeheader_kv(dst, kv) == -1) + if (server_writeheader_kv(clt, kv) == -1) return (-1); } } @@ -1123,7 +667,7 @@ relay_writeheader_http(struct ctl_relay_event *dst, struct ctl_relay_event } enum httpmethod -relay_httpmethod_byname(const char *name) +server_httpmethod_byname(const char *name) { enum httpmethod id = HTTP_METHOD_NONE; struct http_method method, *res = NULL; @@ -1137,14 +681,14 @@ relay_httpmethod_byname(const char *name) */ if ((res = bsearch(&method, http_methods, sizeof(http_methods) / sizeof(http_methods[0]) - 1, - sizeof(http_methods[0]), relay_httpmethod_cmp)) != NULL) + sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL) id = res->method_id; return (id); } const char * -relay_httpmethod_byid(u_int id) +server_httpmethod_byid(u_int id) { const char *name = NULL; int i; @@ -1160,7 +704,7 @@ relay_httpmethod_byid(u_int id) } static int -relay_httpmethod_cmp(const void *a, const void *b) +server_httpmethod_cmp(const void *a, const void *b) { const struct http_method *ma = a; const struct http_method *mb = b; @@ -1168,7 +712,7 @@ relay_httpmethod_cmp(const void *a, const void *b) } const char * -relay_httperror_byid(u_int id) +server_httperror_byid(u_int id) { struct http_error error, *res = NULL; @@ -1177,560 +721,15 @@ relay_httperror_byid(u_int id) res = bsearch(&error, http_errors, sizeof(http_errors) / sizeof(http_errors[0]) - 1, - sizeof(http_errors[0]), relay_httperror_cmp); + sizeof(http_errors[0]), server_httperror_cmp); return (res->error_name); } static int -relay_httperror_cmp(const void *a, const void *b) +server_httperror_cmp(const void *a, const void *b) { const struct http_error *ea = a; const struct http_error *eb = b; return (ea->error_code - eb->error_code); } - -int -relay_httpquery_test(struct ctl_relay_event *cre, struct relay_rule *rule, - struct kvlist *actions) -{ - struct http_descriptor *desc = cre->desc; - struct kv *match = &desc->http_matchquery; - struct kv *kv = &rule->rule_kv[KEY_TYPE_QUERY]; - - if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_QUERY) - return (0); - else if (kv->kv_key == NULL) - return (0); - else if (relay_lookup_query(cre, kv)) - return (-1); - - relay_match(actions, kv, match, NULL); - - return (0); -} - -int -relay_httpheader_test(struct ctl_relay_event *cre, struct relay_rule *rule, - struct kvlist *actions) -{ - struct http_descriptor *desc = cre->desc; - struct kv *kv = &rule->rule_kv[KEY_TYPE_HEADER]; - struct kv *match; - - if (kv->kv_type != KEY_TYPE_HEADER) - return (0); - - match = kv_find(&desc->http_headers, kv); - - if (kv->kv_option == KEY_OPTION_APPEND || - kv->kv_option == KEY_OPTION_SET) { - /* header can be NULL and will be added later */ - } else if (match == NULL) { - /* Fail if header doesn't exist */ - return (-1); - } - - relay_match(actions, kv, match, &desc->http_headers); - - return (0); -} - -int -relay_httppath_test(struct ctl_relay_event *cre, struct relay_rule *rule, - struct kvlist *actions) -{ - struct http_descriptor *desc = cre->desc; - struct kv *kv = &rule->rule_kv[KEY_TYPE_PATH]; - struct kv *match = &desc->http_pathquery; - const char *query; - - if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_PATH) - return (0); - else if (kv->kv_key == NULL) - return (0); - else if (fnmatch(kv->kv_key, desc->http_path, 0) == FNM_NOMATCH) - return (-1); - else if (kv->kv_value != NULL && kv->kv_option == KEY_OPTION_NONE) { - query = desc->http_query == NULL ? "" : desc->http_query; - if (fnmatch(kv->kv_value, query, FNM_CASEFOLD) == FNM_NOMATCH) - return (-1); - } - - relay_match(actions, kv, match, NULL); - - return (0); -} - -int -relay_httpurl_test(struct ctl_relay_event *cre, struct relay_rule *rule, - struct kvlist *actions) -{ - struct http_descriptor *desc = cre->desc; - struct kv *host, key; - struct kv *kv = &rule->rule_kv[KEY_TYPE_URL]; - struct kv *match = &desc->http_pathquery; - - if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_URL || - kv->kv_key == NULL) - return (0); - - key.kv_key = "Host"; - host = kv_find(&desc->http_headers, &key); - - if (host == NULL || host->kv_value == NULL) - return (0); - else if (rule->rule_action != RULE_ACTION_BLOCK && - kv->kv_option == KEY_OPTION_LOG && - fnmatch(kv->kv_key, match->kv_key, FNM_CASEFOLD) != FNM_NOMATCH) { - /* fnmatch url only for logging */ - } else if (relay_lookup_url(cre, host->kv_value, kv) != 0) - return (-1); - - relay_match(actions, kv, match, NULL); - - return (0); -} - -int -relay_httpcookie_test(struct ctl_relay_event *cre, struct relay_rule *rule, - struct kvlist *actions) -{ - struct http_descriptor *desc = cre->desc; - struct kv *kv = &rule->rule_kv[KEY_TYPE_COOKIE], key; - struct kv *match = NULL; - - if (kv->kv_type != KEY_TYPE_COOKIE) - return (0); - - switch (cre->dir) { - case RELAY_DIR_REQUEST: - key.kv_key = "Cookie"; - break; - case RELAY_DIR_RESPONSE: - key.kv_key = "Set-Cookie"; - break; - default: - return (0); - /* NOTREACHED */ - break; - } - - if (kv->kv_option == KEY_OPTION_APPEND || - kv->kv_option == KEY_OPTION_SET) { - /* no cookie, can be NULL and will be added later */ - } else { - match = kv_find(&desc->http_headers, &key); - if (match == NULL) - return (-1); - if (kv->kv_key == NULL || match->kv_value == NULL) - return (0); - else if (relay_lookup_cookie(cre, match->kv_value, kv) != 0) - return (-1); - } - - relay_match(actions, kv, match, &desc->http_headers); - - return (0); -} - -int -relay_match_actions(struct ctl_relay_event *cre, struct relay_rule *rule, - struct kvlist *matches, struct kvlist *actions) -{ - struct rsession *con = cre->con; - struct kv *kv; - - /* - * Apply the following options instantly (action per match). - */ - if (rule->rule_table != NULL) - con->se_table = rule->rule_table; - - if (rule->rule_tag != 0) - con->se_tag = rule->rule_tag == -1 ? 0 : rule->rule_tag; - - if (rule->rule_label != 0) - con->se_label = rule->rule_label == -1 ? 0 : rule->rule_label; - - /* - * Apply the remaining options once after evaluation. - */ - if (matches == NULL) { - /* 'pass' or 'block' rule */ - TAILQ_FOREACH(kv, &rule->rule_kvlist, kv_rule_entry) { - TAILQ_INSERT_TAIL(actions, kv, kv_action_entry); - TAILQ_REMOVE(&rule->rule_kvlist, kv, kv_rule_entry); - } - } else { - /* 'match' rule */ - TAILQ_FOREACH(kv, matches, kv_match_entry) { - TAILQ_INSERT_TAIL(actions, kv, kv_action_entry); - } - } - - return (0); -} - -int -relay_apply_actions(struct ctl_relay_event *cre, struct kvlist *actions) -{ - struct rsession *con = cre->con; - struct http_descriptor *desc = cre->desc; - struct kv *host = NULL; - const char *value; - struct kv *kv, *match, *kp, *mp, kvcopy, matchcopy, key; - int addkv, ret; - char buf[IBUF_READ_SIZE], *ptr; - - memset(&kvcopy, 0, sizeof(kvcopy)); - memset(&matchcopy, 0, sizeof(matchcopy)); - - ret = -1; - kp = mp = NULL; - TAILQ_FOREACH(kv, actions, kv_action_entry) { - kp = NULL; - match = kv->kv_match; - addkv = 0; - - /* - * Although marked as deleted, give a chance to non-critical - * actions, ie. log, to be performed - */ - if (match != NULL && (match->kv_flags & KV_FLAG_INVALID)) - goto matchdel; - - switch (kv->kv_option) { - case KEY_OPTION_APPEND: - case KEY_OPTION_SET: - switch (kv->kv_type) { - case KEY_TYPE_PATH: - if (kv->kv_option == KEY_OPTION_APPEND) { - if (kv_setkey(match, "%s%s", - match->kv_key, kv->kv_key) == -1) - goto fail; - } else { - if (kv_setkey(match, "%s", - kv->kv_value) == -1) - goto fail; - } - break; - case KEY_TYPE_COOKIE: - kp = &kvcopy; - if (kv_inherit(kp, kv) == NULL) - goto fail; - if (kv_set(kp, "%s=%s;", kp->kv_key, - kp->kv_value) == -1) - goto fail; - if (kv_setkey(kp, "%s", cre->dir == - RELAY_DIR_REQUEST ? - "Cookie" : "Set-Cookie") == -1) - goto fail; - /* FALLTHROUGH cookie is a header */ - case KEY_TYPE_HEADER: - if (match == NULL) { - addkv = 1; - break; - } - if (match->kv_value == NULL || - kv->kv_option == KEY_OPTION_SET) { - if (kv_set(match, "%s", - kv->kv_value) == -1) - goto fail; - } else { - if (kv_setkey(match, "%s,%s", - match->kv_key, kv->kv_key) == -1) - goto fail; - } - break; - default: - /* query, url not supported */ - break; - } - break; - case KEY_OPTION_REMOVE: - switch (kv->kv_type) { - case KEY_TYPE_PATH: - if (kv_setkey(match, "/") == -1) - goto fail; - break; - case KEY_TYPE_COOKIE: - case KEY_TYPE_HEADER: - if (kv->kv_matchtree != NULL) - match->kv_flags |= KV_FLAG_INVALID; - else - kv_free(match); - match = kv->kv_match = NULL; - break; - default: - /* query and url not supported */ - break; - } - break; - case KEY_OPTION_HASH: - switch (kv->kv_type) { - case KEY_TYPE_PATH: - value = match->kv_key; - break; - default: - value = match->kv_value; - break; - } - if (!con->se_hashkeyset) - con->se_hashkey = HASHINIT; - con->se_hashkey = hash32_str(value, con->se_hashkey); - con->se_hashkeyset = 1; - log_debug("%s: hashkey 0x%04x", __func__, - con->se_hashkey); - break; - case KEY_OPTION_LOG: - /* perform this later */ - break; - default: - fatalx("relay_action: invalid action"); - /* NOTREACHED */ - } - - /* from now on, reads from kp writes to kv */ - if (kp == NULL) - kp = kv; - if (addkv && kv->kv_matchtree != NULL) { - /* Add new entry to the list (eg. new HTTP header) */ - if ((match = kv_add(kv->kv_matchtree, kp->kv_key, - kp->kv_value)) == NULL) - goto fail; - match->kv_option = kp->kv_option; - match->kv_type = kp->kv_type; - kv->kv_match = match; - } - if (match != NULL && kp->kv_flags & KV_FLAG_MACRO) { - bzero(buf, sizeof(buf)); - if ((ptr = relay_expand_http(cre, kp->kv_value, buf, - sizeof(buf))) == NULL) - goto fail; - if (kv_set(match, ptr) == -1) - goto fail; - } - - matchdel: - switch(kv->kv_option) { - case KEY_OPTION_LOG: - if (match == NULL) - break; - mp = &matchcopy; - if (kv_inherit(mp, match) == NULL) - goto fail; - if (mp->kv_flags & KV_FLAG_INVALID) { - if (kv_set(mp, "%s (removed)", - mp->kv_value) == -1) - goto fail; - } - switch(kv->kv_type) { - case KEY_TYPE_URL: - key.kv_key = "Host"; - host = kv_find(&desc->http_headers, &key); - switch (kv->kv_digest) { - case DIGEST_NONE: - if (host == NULL || - host->kv_value == NULL) - break; - if (kv_setkey(mp, "%s%s", - host->kv_value, mp->kv_key) == - -1) - goto fail; - break; - default: - if (kv_setkey(mp, "%s", kv->kv_key) - == -1) - goto fail; - break; - } - break; - default: - break; - } - if (kv_log(con->se_log, mp, con->se_label) == -1) - goto fail; - break; - default: - break; - } - - /* actions applied, cleanup kv */ - kv->kv_match = NULL; - kv->kv_matchtree = NULL; - TAILQ_REMOVE(actions, kv, kv_match_entry); - - kv_free(&kvcopy); - kv_free(&matchcopy); - } - - ret = 0; - fail: - kv_free(&kvcopy); - kv_free(&matchcopy); - - return (ret); -} - -#define RELAY_GET_SKIP_STEP(i) \ - do { \ - r = r->rule_skip[i]; \ - DPRINTF("%s:%d: skip %d rules", __func__, __LINE__, i); \ - } while (0) - -#define RELAY_GET_NEXT_STEP \ - do { \ - DPRINTF("%s:%d: next rule", __func__, __LINE__); \ - goto nextrule; \ - } while (0) - -int -relay_test(struct protocol *proto, struct ctl_relay_event *cre) -{ - struct rsession *con; - struct http_descriptor *desc = cre->desc; - struct relay_rule *r = NULL, *rule = NULL; - u_int cnt = 0; - u_int action = RES_PASS; - struct kvlist actions, matches; - struct kv *kv; - - con = cre->con; - TAILQ_INIT(&actions); - - r = TAILQ_FIRST(&proto->rules); - while (r != NULL) { - cnt++; - TAILQ_INIT(&matches); - TAILQ_INIT(&r->rule_kvlist); - if (r->rule_dir && r->rule_dir != cre->dir) - RELAY_GET_SKIP_STEP(RULE_SKIP_DIR); - else if (proto->type != r->rule_proto) - RELAY_GET_SKIP_STEP(RULE_SKIP_PROTO); - else if (r->rule_af != AF_UNSPEC && - (cre->ss.ss_family != r->rule_af || - cre->dst->ss.ss_family != r->rule_af)) - RELAY_GET_SKIP_STEP(RULE_SKIP_AF); - else if (RELAY_ADDR_CMP(&r->rule_src, &cre->ss) != 0) - RELAY_GET_SKIP_STEP(RULE_SKIP_SRC); - else if (RELAY_ADDR_CMP(&r->rule_dst, &cre->dst->ss) != 0) - RELAY_GET_SKIP_STEP(RULE_SKIP_DST); - else if (r->rule_method != HTTP_METHOD_NONE && - (desc->http_method == HTTP_METHOD_RESPONSE || - desc->http_method != r->rule_method)) - RELAY_GET_SKIP_STEP(RULE_SKIP_METHOD); - else if (r->rule_tagged && con->se_tag != r->rule_tagged) - RELAY_GET_NEXT_STEP; - else if (relay_httpheader_test(cre, r, &matches) != 0) - RELAY_GET_NEXT_STEP; - else if (relay_httpquery_test(cre, r, &matches) != 0) - RELAY_GET_NEXT_STEP; - else if (relay_httppath_test(cre, r, &matches) != 0) - RELAY_GET_NEXT_STEP; - else if (relay_httpurl_test(cre, r, &matches) != 0) - RELAY_GET_NEXT_STEP; - else if (relay_httpcookie_test(cre, r, &matches) != 0) - RELAY_GET_NEXT_STEP; - else { - DPRINTF("%s: session %d: matched rule %d", - __func__, con->se_id, r->rule_id); - - if (r->rule_action == RULE_ACTION_MATCH) { - if (relay_match_actions(cre, r, &matches, - &actions) != 0) { - /* Something bad happened, drop */ - action = RES_DROP; - break; - } - RELAY_GET_NEXT_STEP; - } else if (r->rule_action == RULE_ACTION_BLOCK) - action = RES_DROP; - else if (r->rule_action == RULE_ACTION_PASS) - action = RES_PASS; - - /* Rule matched */ - rule = r; - - /* Temporarily save actions */ - TAILQ_FOREACH(kv, &matches, kv_match_entry) { - TAILQ_INSERT_TAIL(&rule->rule_kvlist, - kv, kv_rule_entry); - } - - if (rule->rule_flags & RULE_FLAG_QUICK) - break; - - nextrule: - /* Continue to find last matching policy */ - r = TAILQ_NEXT(r, rule_entry); - } - } - - if (rule != NULL && - relay_match_actions(cre, rule, NULL, &actions) != 0) { - /* Something bad happened, drop */ - action = RES_DROP; - } - - if (relay_apply_actions(cre, &actions) != 0) { - /* Something bad happened, drop */ - action = RES_DROP; - } - - DPRINTF("%s: session %d: action %d", __func__, - con->se_id, action); - - return (action); -} - -#define RELAY_SET_SKIP_STEPS(i) \ - do { \ - while (head[i] != cur) { \ - head[i]->rule_skip[i] = cur; \ - head[i] = TAILQ_NEXT(head[i], rule_entry); \ - } \ - } while (0) - -/* This code is derived from pf_calc_skip_steps() from pf.c */ -void -relay_calc_skip_steps(struct relay_rules *rules) -{ - struct relay_rule *head[RULE_SKIP_COUNT], *cur, *prev; - int i; - - cur = TAILQ_FIRST(rules); - prev = cur; - for (i = 0; i < RULE_SKIP_COUNT; ++i) - head[i] = cur; - while (cur != NULL) { - if (cur->rule_dir != prev->rule_dir) - RELAY_SET_SKIP_STEPS(RULE_SKIP_DIR); - else if (cur->rule_proto != prev->rule_proto) - RELAY_SET_SKIP_STEPS(RULE_SKIP_PROTO); - else if (cur->rule_af != prev->rule_af) - RELAY_SET_SKIP_STEPS(RULE_SKIP_AF); - else if (RELAY_ADDR_NEQ(&cur->rule_src, &prev->rule_src)) - RELAY_SET_SKIP_STEPS(RULE_SKIP_SRC); - else if (RELAY_ADDR_NEQ(&cur->rule_dst, &prev->rule_dst)) - RELAY_SET_SKIP_STEPS(RULE_SKIP_DST); - else if (cur->rule_method != prev->rule_method) - RELAY_SET_SKIP_STEPS(RULE_SKIP_METHOD); - - prev = cur; - cur = TAILQ_NEXT(cur, rule_entry); - } - for (i = 0; i < RULE_SKIP_COUNT; ++i) - RELAY_SET_SKIP_STEPS(i); -} - -void -relay_match(struct kvlist *actions, struct kv *kv, struct kv *match, - struct kvtree *matchtree) -{ - if (kv->kv_option != KEY_OPTION_NONE) { - kv->kv_match = match; - kv->kv_matchtree = matchtree; - TAILQ_INSERT_TAIL(actions, kv, kv_match_entry); - } -} -- cgit v1.2.3-54-g00ecf