diff options
author | Reyk Floeter <reyk@cvs.openbsd.org> | 2012-09-20 12:30:21 +0000 |
---|---|---|
committer | Reyk Floeter <reyk@cvs.openbsd.org> | 2012-09-20 12:30:21 +0000 |
commit | bd8ba8cae6c333f007e5c8ca7b31dec50d6a8fef (patch) | |
tree | 471e26a19d994faa054aa4801166cade757ce198 | |
parent | e51223719f927f8ba32f65f562bbeebfae5c7aa7 (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/Makefile | 6 | ||||
-rw-r--r-- | usr.sbin/relayd/relay.c | 1070 | ||||
-rw-r--r-- | usr.sbin/relayd/relay_http.c | 1079 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.h | 34 |
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 */ |