diff options
Diffstat (limited to 'usr.sbin/relayd/relay.c')
-rw-r--r-- | usr.sbin/relayd/relay.c | 1927 |
1 files changed, 1927 insertions, 0 deletions
diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c new file mode 100644 index 00000000000..5d9fbac12e2 --- /dev/null +++ b/usr.sbin/relayd/relay.c @@ -0,0 +1,1927 @@ +/* $OpenBSD: relay.c,v 1.1 2007/02/22 03:32:40 reyk Exp $ */ + +/* + * Copyright (c) 2006 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/queue.h> +#include <sys/param.h> +#include <sys/types.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 <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <net/if.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 "hoststated.h" + +void relay_sig_handler(int sig, short, void *); +void relay_statistics(int, short, void *); +void relay_dispatch_pfe(int, short, void *); +void relay_dispatch_parent(int, short, void *); +void relay_shutdown(void); + +void relay_privinit(void); +void relay_protodebug(struct relay *); +void relay_init(void); +void relay_launch(void); +int relay_socket(struct sockaddr_storage *, in_port_t, + struct protocol *); +int relay_socket_listen(struct sockaddr_storage *, in_port_t, + struct protocol *); +int relay_socket_connect(struct sockaddr_storage *, in_port_t, + struct protocol *); + +void relay_accept(int, short, void *); +void relay_input(struct session *); +void relay_close(struct session *, const char *); +void relay_session(struct session *); +void relay_natlook(int, short, void *); + +int relay_connect(struct session *); +void relay_connected(int, short, void *); + +const char *relay_host(struct sockaddr_storage *, char *, size_t); +u_int32_t relay_hash_addr(struct sockaddr_storage *, u_int32_t); +int relay_from_table(struct session *); + +void relay_write(struct bufferevent *, void *); +void relay_read(struct bufferevent *, void *); +void relay_error(struct bufferevent *, short, void *); + +int relay_handle_http(struct ctl_relay_event *, + struct protonode *, struct protonode *, int); +void relay_read_http(struct bufferevent *, void *); +void relay_read_httpcontent(struct bufferevent *, void *); +char *relay_expand_http(struct ctl_relay_event *, char *, + char *, size_t); + +SSL_CTX *relay_ssl_ctx_create(struct relay *); +void relay_ssl_transaction(struct session *); +void relay_ssl_accept(int, short, void *); +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(struct ctl_relay_event *, + void *, size_t); +static __inline int + relay_proto_cmp(struct protonode *, struct protonode *); +extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t, + size_t, void *); + +volatile sig_atomic_t relay_sessions; +objid_t relay_conid; + +static struct hoststated *env = NULL; +struct imsgbuf *ibuf_pfe; +struct imsgbuf *ibuf_main; +int proc_id; + +#if DEBUG > 1 +#define DPRINTF log_debug +#else +#define DPRINTF(x...) do { } while(0) +#endif + +void +relay_sig_handler(int sig, short event, void *arg) +{ + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 0; + + switch (sig) { + case SIGALRM: + case SIGTERM: + case SIGQUIT: + case SIGINT: + event_loopexit(&tv); + } +} + +pid_t +relay(struct hoststated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2], + int pipe_parent2relay[2], int pipe_pfe2hce[2], + int pipe_pfe2relay[RELAY_MAXPROC][2]) +{ + pid_t pid; + struct passwd *pw; + struct event ev_sigint; + struct event ev_sigterm; + int i; + + switch (pid = fork()) { + case -1: + fatal("relay: cannot fork"); + case 0: + break; + default: + return (pid); + } + + env = x_env; + + /* Need root privileges for relay initialization */ + relay_privinit(); + + if ((pw = getpwnam(HOSTSTATED_USER)) == NULL) + fatal("relay: getpwnam"); + +#ifndef DEBUG + if (chroot(pw->pw_dir) == -1) + fatal("relay: chroot"); + if (chdir("/") == -1) + fatal("relay: chdir(\"/\")"); +#endif + + setproctitle("socket relay engine"); + hoststated_process = PROC_RELAY; + +#ifndef DEBUG + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("relay: can't drop privileges"); +#endif + + /* Fork child handlers */ + for (i = 1; i < env->prefork_relay; i++) { + if (fork() == 0) { + proc_id = i; + break; + } + } + + event_init(); + + /* Per-child initialization */ + relay_init(); + + signal_set(&ev_sigint, SIGINT, relay_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, relay_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + /* setup pipes */ + close(pipe_pfe2hce[0]); + close(pipe_pfe2hce[1]); + close(pipe_parent2hce[0]); + close(pipe_parent2hce[1]); + close(pipe_parent2pfe[0]); + close(pipe_parent2pfe[1]); + close(pipe_parent2relay[0]); + for (i = 0; i < env->prefork_relay; i++) { + if (i == proc_id) + continue; + close(pipe_pfe2relay[i][1]); + close(pipe_pfe2relay[i][1]); + } + close(pipe_pfe2relay[proc_id][1]); + + if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL || + (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL) + fatal("relay"); + imsg_init(ibuf_pfe, pipe_pfe2relay[proc_id][0], relay_dispatch_pfe); + imsg_init(ibuf_main, pipe_parent2relay[1], relay_dispatch_parent); + + ibuf_pfe->events = EV_READ; + event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events, + ibuf_pfe->handler, ibuf_pfe); + event_add(&ibuf_pfe->ev, NULL); + + ibuf_main->events = EV_READ; + event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events, + ibuf_main->handler, ibuf_main); + event_add(&ibuf_main->ev, NULL); + + relay_launch(); + + event_dispatch(); + relay_shutdown(); + + return (0); +} + +void +relay_shutdown(void) +{ + struct session *con; + + struct relay *rlay; + TAILQ_FOREACH(rlay, &env->relays, entry) { + if (rlay->flags & F_DISABLE) + continue; + close(rlay->s); + while ((con = TAILQ_FIRST(&rlay->sessions)) != NULL) + relay_close(con, "shutdown"); + } + usleep(200); /* XXX relay needs to shutdown last */ + log_info("socket relay engine exiting"); + _exit(0); +} + +void +relay_protodebug(struct relay *rlay) +{ + struct protocol *proto = rlay->proto; + struct protonode *pn; + + fprintf(stderr, "protocol %d: name %s\n", proto->id, proto->name); + fprintf(stderr, "\tflags: 0x%04x\n", proto->flags); + if (proto->cache != -1) + fprintf(stderr, "\tssl session cache: %d\n", proto->cache); + fprintf(stderr, "\ttype: "); + switch (proto->type) { + case RELAY_PROTO_TCP: + fprintf(stderr, "tcp\n"); + break; + case RELAY_PROTO_HTTP: + fprintf(stderr, "http\n"); + break; + } + RB_FOREACH(pn, proto_tree, &proto->tree) { + fprintf(stderr, "\t\t"); + switch (pn->action) { + case NODE_ACTION_APPEND: + fprintf(stderr, "append \"%s\" to \"%s\"", + pn->value, pn->key); + break; + case NODE_ACTION_CHANGE: + fprintf(stderr, "change \"%s\" to \"%s\"", + pn->key, pn->value); + break; + case NODE_ACTION_REMOVE: + fprintf(stderr, "remove \"%s\"", + pn->key); + break; + case NODE_ACTION_EXPECT: + fprintf(stderr, "%sexpect \"%s\" from \"%s\"", + pn->header ? "" : "url ", + pn->value, pn->key); + break; + case NODE_ACTION_FILTER: + fprintf(stderr, "%sfilter \"%s\" from \"%s\"", + pn->header ? "" : "url ", + pn->value, pn->key); + break; + case NODE_ACTION_HASH: + fprintf(stderr, "%shash \"%s\"", + pn->header ? "" : "url ", + pn->key); + break; + case NODE_ACTION_NONE: + fprintf(stderr, "%snone \"%s\"", + pn->header ? "" : "url ", + pn->key); + break; + } + fprintf(stderr, "\n"); + } +} + +void +relay_privinit(void) +{ + struct relay *rlay; + extern int debug; + + if (env->flags & F_SSL) + ssl_init(env); + + TAILQ_FOREACH(rlay, &env->relays, entry) { + log_debug("relay_init: adding relay %s", rlay->name); + + if (debug) + relay_protodebug(rlay); + + if ((rlay->flags & F_SSL) && + (rlay->ctx = relay_ssl_ctx_create(rlay)) == NULL) + fatal("relay_launch: failed to create SSL context"); + + if ((rlay->s = relay_socket_listen(&rlay->ss, rlay->port, + rlay->proto)) == -1) + fatal("relay_launch: failed to listen"); + } +} + +void +relay_init(void) +{ + struct relay *rlay; + struct host *host; + struct timeval tv; + + TAILQ_FOREACH(rlay, &env->relays, entry) { + if (rlay->dsttable != NULL) { + switch (rlay->dstmode) { + case RELAY_DSTMODE_ROUNDROBIN: + rlay->dstkey = 0; + break; + case RELAY_DSTMODE_LOADBALANCE: + case RELAY_DSTMODE_HASH: + rlay->dstkey = + hash32_str(rlay->name, HASHINIT); + rlay->dstkey = + hash32_str(rlay->dsttable->name, + rlay->dstkey); + break; + } + rlay->dstnhosts = 0; + TAILQ_FOREACH(host, &rlay->dsttable->hosts, entry) { + if (rlay->dstnhosts >= RELAY_MAXHOSTS) + fatal("relay_init: " + "too many hosts in table"); + rlay->dsthost[rlay->dstnhosts++] = host; + } + log_info("adding %d hosts from table %s%s", + rlay->dstnhosts, rlay->dsttable->name, + rlay->dstcheck ? "" : " (no check)"); + } + } + + /* Schedule statistics timer */ + evtimer_set(&env->statev, relay_statistics, NULL); + bcopy(&env->statinterval, &tv, sizeof(tv)); + evtimer_add(&env->statev, &tv); +} + +void +relay_statistics(int fd, short events, void *arg) +{ + struct relay *rlay; + struct ctl_stats crs, *cur; + struct timeval tv, tv_now; + int resethour = 0, resetday = 0; + struct session *con, *next_con; + + /* + * This is a hack to calculate some average statistics. + * It doesn't try to be very accurate, but could be improved... + */ + + timerclear(&tv); + if (gettimeofday(&tv_now, NULL)) + fatal("relay_init: gettimeofday"); + + TAILQ_FOREACH(rlay, &env->relays, entry) { + bzero(&crs, sizeof(crs)); + resethour = resetday = 0; + + cur = &rlay->stats[proc_id]; + cur->cnt += cur->last; + cur->tick++; + cur->avg = (cur->last + cur->avg) / 2; + cur->last_hour += cur->last; + if ((cur->tick % (3600 / env->statinterval.tv_sec)) == 0) { + cur->avg_hour = (cur->last_hour + cur->avg_hour) / 2; + resethour++; + } + cur->last_day += cur->last; + if ((cur->tick % (86400 / env->statinterval.tv_sec)) == 0) { + cur->avg_day = (cur->last_day + cur->avg_day) / 2; + resethour++; + } + bcopy(cur, &crs, sizeof(crs)); + + cur->last = 0; + if (resethour) + cur->last_hour = 0; + if (resetday) + cur->last_day = 0; + + crs.id = rlay->id; + crs.proc = proc_id; + imsg_compose(ibuf_pfe, IMSG_STATISTICS, 0, 0, + &crs, sizeof(crs)); + + for (con = TAILQ_FIRST(&rlay->sessions); + con != NULL; con = next_con) { + next_con = TAILQ_NEXT(con, entry); + timersub(&tv_now, &con->tv_last, &tv); + if (timercmp(&tv, &rlay->timeout, >=)) + relay_close(con, "hard timeout"); + } + } + + /* Schedule statistics timer */ + evtimer_set(&env->statev, relay_statistics, NULL); + bcopy(&env->statinterval, &tv, sizeof(tv)); + evtimer_add(&env->statev, &tv); +} + +void +relay_launch(void) +{ + struct relay *rlay; + + TAILQ_FOREACH(rlay, &env->relays, entry) { + log_debug("relay_launch: running relay %s", rlay->name); + + rlay->up = HOST_UP; + + event_set(&rlay->ev, rlay->s, EV_READ|EV_PERSIST, + relay_accept, rlay); + event_add(&rlay->ev, NULL); + } +} + +int +relay_socket(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s = -1, val; + struct linger lng; + + switch (ss->ss_family) { + case AF_INET: + ((struct sockaddr_in *)ss)->sin_port = port; + ((struct sockaddr_in *)ss)->sin_len = + sizeof(struct sockaddr_in); + break; + case AF_INET6: + ((struct sockaddr_in6 *)ss)->sin6_port = port; + ((struct sockaddr_in6 *)ss)->sin6_len = + sizeof(struct sockaddr_in6); + break; + } + + if ((s = socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) + goto bad; + bzero(&lng, sizeof(lng)); + if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) + goto bad; + val = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(int)) == -1) + goto bad; + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto bad; + + if (proto->tcpflags & (TCPFLAG_NODELAY|TCPFLAG_NNODELAY)) { + if (proto->tcpflags & TCPFLAG_NNODELAY) + val = 0; + else + val = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, + &val, sizeof(val)) == -1) + goto bad; + } + if (proto->tcpflags & (TCPFLAG_SACK|TCPFLAG_NSACK)) { + if (proto->tcpflags & TCPFLAG_NSACK) + val = 0; + else + val = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_SACK_ENABLE, + &val, sizeof(val)) == -1) + goto bad; + } + if (proto->tcpflags & TCPFLAG_BUFSIZ) { + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, + &val, sizeof(val)) == -1) + goto bad; + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, + &val, sizeof(val)) == -1) + goto bad; + } + + return (s); + + bad: + if (s != -1) + close(s); + return (-1); +} + +int +relay_socket_connect(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s; + + if ((s = relay_socket(ss, port, proto)) == -1) + return (-1); + + if (connect(s, (struct sockaddr *)ss, ss->ss_len) == -1) { + if (errno != EINPROGRESS) + goto bad; + } + + return (s); + + bad: + close(s); + return (-1); +} + +int +relay_socket_listen(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s; + + if ((s = relay_socket(ss, port, proto)) == -1) + return (-1); + + if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1) + goto bad; + if (listen(s, 5) == -1) + goto bad; + + return (s); + + bad: + close(s); + return (-1); +} + +void +relay_connected(int fd, short sig, void *arg) +{ + struct session *con = (struct session *)arg; + struct relay *rlay = (struct relay *)con->relay; + evbuffercb outrd = relay_read; + evbuffercb outwr = relay_write; + struct bufferevent *bev; + char ibuf[128], obuf[128]; + + if (sig == EV_TIMEOUT) { + relay_close(con, "connect timeout"); + return; + } + + DPRINTF("relay_connected: session %d: %ssuccessful", + con->id, rlay->proto->lateconnect ? "late connect " : ""); + + if (env->opts & HOSTSTATED_OPT_LOGUPDATE) { + relay_host(&con->in.ss, ibuf, sizeof(ibuf)); + relay_host(&con->out.ss, obuf, sizeof(obuf)); + log_info("relay %s, session %d (%d active), %s -> %s:%d", + rlay->name, con->id, relay_sessions, + ibuf, obuf, ntohs(con->out.port)); + } + + /* + * Relay <-> Server + */ + bev = bufferevent_new(fd, outrd, outwr, relay_error, &con->out); + if (bev == NULL) { + relay_close(con, "failed to allocate output buffer event"); + return; + } + evbuffer_free(bev->output); + bev->output = con->out.output; + if (bev->output == NULL) + fatal("relay_connected: invalid output buffer"); + + con->out.bev = bev; + bufferevent_settimeout(bev, + rlay->timeout.tv_sec, rlay->timeout.tv_sec); + bufferevent_enable(bev, EV_READ|EV_WRITE); +} + +void +relay_input(struct session *con) +{ + struct relay *rlay = (struct relay *)con->relay; + struct protocol *proto = rlay->proto; + evbuffercb inrd = relay_read; + evbuffercb inwr = relay_write; + + switch (rlay->proto->type) { + case RELAY_PROTO_HTTP: + /* Check the client's HTTP request */ + inrd = relay_read_http; + if ((con->in.nodes = calloc(proto->nodecount, + sizeof(u_int8_t))) == NULL) { + relay_close(con, "failed to allocate node buffer"); + return; + } + break; + case RELAY_PROTO_TCP: + /* Use defaults */ + break; + default: + fatalx("relay_input: unknown protocol"); + } + + /* + * Client <-> Relay + */ + con->in.bev = bufferevent_new(con->in.s, inrd, inwr, + relay_error, &con->in); + if (con->in.bev == NULL) { + relay_close(con, "failed to allocate input buffer event"); + return; + } + + /* Initialize the SSL wrapper */ + if ((rlay->flags & F_SSL) && con->in.ssl != NULL) + relay_ssl_connected(&con->in); + + bufferevent_settimeout(con->in.bev, + rlay->timeout.tv_sec, rlay->timeout.tv_sec); + bufferevent_enable(con->in.bev, EV_READ|EV_WRITE); +} + +void +relay_write(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct session *con = (struct session *)cre->con; + if (gettimeofday(&con->tv_last, NULL)) + con->done = 1; + if (con->done) + relay_close(con, "last write, done"); +} + +void +relay_read(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct session *con = (struct session *)cre->con; + struct evbuffer *src = EVBUFFER_INPUT(bev); + + if (gettimeofday(&con->tv_last, NULL)) + goto done; + if (!EVBUFFER_LENGTH(src)) + return; + relay_bufferevent_write_buffer(cre->dst, src); + if (con->done) + goto done; + bufferevent_enable(con->in.bev, EV_READ); + return; + done: + relay_close(con, "last read, done"); +} + +char * +relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf, size_t len) +{ + struct session *con = (struct session *)cre->con; + struct relay *rlay = (struct relay *)con->relay; + char ibuf[128]; + + strlcpy(buf, val, len); + + if (strstr(val, "$REMOTE_") != NULL) { + if (strstr(val, "$REMOTE_ADDR") != NULL) { + relay_host(&cre->ss, ibuf, sizeof(ibuf)); + 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) { + relay_host(&rlay->ss, ibuf, sizeof(ibuf)); + if (expand_string(buf, len, + "$SERVER_ADDR", ibuf) != 0) + return (NULL); + } + if (strstr(val, "$SERVER_PORT") != NULL) { + snprintf(ibuf, sizeof(ibuf), "%u", ntohs(rlay->port)); + if (expand_string(buf, len, + "$SERVER_PORT", ibuf) != 0) + return (NULL); + } + } + if (strstr(val, "$TIMEOUT") != NULL) { + snprintf(ibuf, sizeof(ibuf), "%lu", rlay->timeout.tv_sec); + if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0) + return (NULL); + } + + return (buf); +} + + +int +relay_handle_http(struct ctl_relay_event *cre, struct protonode *pn, + struct protonode *pk, int header) +{ + struct session *con = (struct session *)cre->con; + char buf[READ_BUF_SIZE], *ptr; + + if (pn->header != header) + return (0); + + switch (pn->action) { + case NODE_ACTION_APPEND: + if (!header || (pn->mark && cre->marked == 0)) + return (-1); + ptr = pn->value; + if (pn->macro && (ptr = relay_expand_http(cre, + pn->value, buf, sizeof(buf))) == NULL) + break; + relay_bufferevent_print(cre->dst, pn->key); + relay_bufferevent_print(cre->dst, ": "); + relay_bufferevent_print(cre->dst, pk->value); + relay_bufferevent_print(cre->dst, ", "); + relay_bufferevent_print(cre->dst, ptr); + relay_bufferevent_print(cre->dst, "\r\n"); + cre->nodes[pn->id] = 1; + DPRINTF("relay_handle_http: append '%s: %s, %s'", + pk->key, pk->value, ptr); + break; + case NODE_ACTION_CHANGE: + case NODE_ACTION_REMOVE: + if (!header || (pn->mark && cre->marked == 0)) + return (-1); + DPRINTF("relay_handle_http: change/remove '%s: %s'", + pk->key, pk->value); + break; + case NODE_ACTION_EXPECT: + DPRINTF("relay_handle_http: expect '%s: %s'", + pn->key, pn->value); + if (fnmatch(pn->value, pk->value, FNM_CASEFOLD) == 0) { + if (pn->mark) + cre->marked++; + cre->nodes[pn->id] = 1; + } + break; + case NODE_ACTION_FILTER: + DPRINTF("relay_handle_http: filter '%s: %s'", + pn->key, pn->value); + if (fnmatch(pn->value, pk->value, FNM_CASEFOLD) == + FNM_NOMATCH) { + if (pn->mark) + cre->marked++; + cre->nodes[pn->id] = 1; + } + break; + case NODE_ACTION_HASH: + if (pn->mark && !cre->marked) + return (-1); + DPRINTF("relay_handle_http: hash '%s: %s'", + pn->key, pk->value); + con->outkey = hash32_str(pk->value, con->outkey); + break; + case NODE_ACTION_NONE: + return (-1); + } + + return (0); +} + +void +relay_read_httpcontent(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct session *con = (struct session *)cre->con; + struct evbuffer *src = EVBUFFER_INPUT(bev); + size_t size; + + if (gettimeofday(&con->tv_last, NULL)) + goto done; + size = EVBUFFER_LENGTH(src); + DPRINTF("relay_read_httpcontent: size %d, to read %d", + size, cre->toread); + if (!size) + return; + relay_bufferevent_write_buffer(cre->dst, src); + if (size >= cre->toread) + bev->readcb = relay_read_http; + cre->toread -= size; + DPRINTF("relay_read_httpcontent: done, size %d, to read %d", + size, cre->toread); + if (con->done) + goto done; + bufferevent_enable(bev, EV_READ); + return; + done: + relay_close(con, "last http content read, done"); +} + +void +relay_read_http(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct session *con = (struct session *)cre->con; + struct relay *rlay = (struct relay *)con->relay; + struct protocol *proto = rlay->proto; + struct evbuffer *src = EVBUFFER_INPUT(bev); + struct protonode *pn, pk, *pnv, pkv; + char *line, buf[READ_BUF_SIZE], *ptr, *url, *method; + int done = 0, header = 0; + const char *errstr; + size_t size; + + if (gettimeofday(&con->tv_last, NULL)) + goto done; + size = EVBUFFER_LENGTH(src); + DPRINTF("relay_read_http: size %d, to read %d", size, cre->toread); + if (!size) + return; + + while (!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)) { + 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) { + DPRINTF("relay_read_http: request '%s'", line); + /* Append line to the output buffer */ + relay_bufferevent_print(cre->dst, line); + relay_bufferevent_print(cre->dst, "\r\n"); + free(line); + continue; + } + if (*pk.value == ':') { + *pk.value++ = '\0'; + *pk.value++; + header = 1; + } else { + *pk.value++ = '\0'; + header = 0; + } + + DPRINTF("relay_read_http: header '%s: %s'", pk.key, pk.value); + + /* + * Identify and handle specific HTTP request methods + */ + if (cre->line == 1) { + if (strcmp("GET", pk.key) == 0) + cre->method = HTTP_METHOD_GET; + 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 if ((cre->method == HTTP_METHOD_POST || + cre->method == HTTP_METHOD_PUT) && + strcasecmp("Content-Length", pk.key) == 0) { + /* + * Need to read data from the client after the + * HTTP header. + */ + cre->toread = strtonum(pk.value, 1, INT_MAX, &errstr); + + /* + * \r\n between header and body. + * 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 += 2; + + if (errstr) { + relay_close(con, errstr); + return; + } + } + + /* Match the HTTP header */ + if ((pn = RB_FIND(proto_tree, &proto->tree, &pk)) == NULL) + goto next; + + /* Decode the URL */ + if (pn->getvars) { + url = strdup(pk.value); + if (url == NULL) + goto next; + if ((ptr = strchr(url, '?')) == NULL || + strlen(ptr) < 2) { + free(url); + goto next; + } + *ptr++ = '\0'; + method = strchr(ptr, ' '); + if (method != NULL) + *method++ = '\0'; + while (ptr != NULL && strlen(ptr)) { + pkv.key = ptr; + if ((ptr = strchr(ptr, '&')) != NULL) + *ptr++ = '\0'; + if ((pkv.value = + strchr(pkv.key, '=')) == NULL || + strlen(pkv.value) < 1) { + continue; + } + *pkv.value++ = '\0'; + if ((pnv = RB_FIND(proto_tree, + &proto->tree, &pkv)) == NULL) + continue; + if (relay_handle_http(cre, pnv, &pkv, 0) == -1) + continue; + } + free(url); + } + + if (relay_handle_http(cre, pn, &pk, header) == -1) + goto next; + + free(line); + continue; + +next: + relay_bufferevent_print(cre->dst, pk.key); + if (header) + relay_bufferevent_print(cre->dst, ": "); + else + relay_bufferevent_print(cre->dst, " "); + relay_bufferevent_print(cre->dst, pk.value); + relay_bufferevent_print(cre->dst, "\r\n"); + free(line); + continue; + } + if (done) { + RB_FOREACH(pn, proto_tree, &proto->tree) { + if (cre->nodes[pn->id]) { + cre->nodes[pn->id] = 0; + continue; + } + switch (pn->action) { + case NODE_ACTION_APPEND: + case NODE_ACTION_CHANGE: + ptr = pn->value; + if (pn->mark && cre->marked == 0) + break; + if (pn->macro && (ptr = relay_expand_http(cre, + pn->value, buf, sizeof(buf))) == NULL) + break; + relay_bufferevent_print(cre->dst, pn->key); + relay_bufferevent_print(cre->dst, ": "); + relay_bufferevent_print(cre->dst, ptr); + relay_bufferevent_print(cre->dst, "\r\n"); + DPRINTF("relay_read_http: add '%s: %s'", + pn->key, ptr); + break; + case NODE_ACTION_EXPECT: + if (pn->mark) + break; + DPRINTF("relay_read_http: missing '%s: %s'", + pn->key, pn->value); + relay_close(con, "incomplete header, done"); + return; + case NODE_ACTION_FILTER: + if (pn->mark) + break; + DPRINTF("relay_read_http: filtered '%s: %s'", + pn->key, pn->value); + relay_close(con, "rejecting header, done"); + return; + default: + break; + } + } + + switch (cre->method) { + case HTTP_METHOD_CONNECT: + /* Data stream */ + bev->readcb = relay_read; + break; + case HTTP_METHOD_POST: + case HTTP_METHOD_PUT: + /* HTTP request payload */ + if (cre->toread) { + bev->readcb = relay_read_httpcontent; + break; + } + /* FALLTHROUGH */ + default: + /* HTTP handler */ + bev->readcb = relay_read_http; + break; + } + + /* Write empty newline and switch to relay mode */ + relay_bufferevent_print(cre->dst, "\r\n"); + cre->line = 0; + cre->method = 0; + cre->marked = 0; + + if (proto->lateconnect && cre->bev == NULL && + relay_connect(con) == -1) { + relay_close(con, "session failed"); + return; + } + } + if (con->done) + goto done; + if (EVBUFFER_LENGTH(src)) + relay_bufferevent_write_buffer(cre->dst, src); + bufferevent_enable(bev, EV_READ); + return; + done: + relay_close(con, "last http read, done"); +} + +void +relay_error(struct bufferevent *bev, short error, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct session *con = (struct session *)cre->con; + struct evbuffer *src = EVBUFFER_OUTPUT(bev); + struct evbuffer *dst; + + if (error & EVBUFFER_TIMEOUT) { + relay_close(con, "buffer event timeout"); + return; + } +#if 0 + if (error & EVBUFFER_EOF) { + bufferevent_disable(bev, EV_READ|EV_WRITE); + relay_close(con, "done"); + return; + } +#endif + if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { + bufferevent_disable(bev, EV_READ|EV_WRITE); + + con->done = 1; + if (cre->dst->bev != NULL) { + dst = EVBUFFER_OUTPUT(cre->dst->bev); + if (EVBUFFER_LENGTH(dst)) { + bufferevent_write_buffer(cre->dst->bev, src); + return; + } + } + + relay_close(con, "done"); + return; + } + relay_close(con, "buffer event error"); +} + +const char * +relay_host(struct sockaddr_storage *ss, char *buf, size_t len) +{ + int af = ss->ss_family; + void *ptr; + + bzero(buf, len); + if (af == AF_INET) + ptr = &((struct sockaddr_in *)ss)->sin_addr; + else + ptr = &((struct sockaddr_in6 *)ss)->sin6_addr; + return (inet_ntop(af, ptr, buf, len)); +} + +void +relay_accept(int fd, short sig, void *arg) +{ + struct relay *rlay = (struct relay *)arg; + struct session *con = NULL; + struct ctl_natlook *cnl = NULL; + socklen_t slen; + struct timeval tv; + struct sockaddr_storage ss; + int s = -1; + + slen = sizeof(ss); + if ((s = accept(fd, (struct sockaddr *)&ss, (socklen_t *)&slen)) == -1) + return; + + if (relay_sessions >= RELAY_MAX_SESSIONS || rlay->flags & F_DISABLE) + goto err; + + if ((con = (struct session *) + calloc(1, sizeof(struct session))) == NULL) + goto err; + + con->in.s = s; + con->in.ssl = NULL; + con->out.s = -1; + con->out.ssl = NULL; + con->in.dst = &con->out; + con->out.dst = &con->in; + con->in.con = con; + con->out.con = con; + con->relay = rlay; + con->id = ++relay_conid; + con->outkey = rlay->dstkey; + if (gettimeofday(&con->tv_start, NULL)) + goto err; + bcopy(&con->tv_start, &con->tv_last, sizeof(con->tv_last)); + bcopy(&ss, &con->in.ss, sizeof(con->in.ss)); + + /* Pre-allocate output buffer */ + con->out.output = evbuffer_new(); + if (con->out.output == NULL) { + relay_close(con, "failed to allocate output buffer"); + return; + } + + if (rlay->flags & F_NATLOOK) { + if ((cnl = (struct ctl_natlook *) + calloc(1, sizeof(struct ctl_natlook))) == NULL) + goto err; + } + + relay_sessions++; + TAILQ_INSERT_HEAD(&rlay->sessions, con, entry); + + /* Increment the per-relay session counter */ + rlay->stats[proc_id].last++; + + if (rlay->flags & F_NATLOOK && cnl != NULL) { + con->cnl = cnl;; + bzero(cnl, sizeof(*cnl)); + cnl->in = -1; + cnl->id = con->id; + bcopy(&con->in.ss, &cnl->src, sizeof(cnl->src)); + bcopy(&rlay->ss, &cnl->dst, sizeof(cnl->dst)); + imsg_compose(ibuf_pfe, IMSG_NATLOOK, 0, 0, cnl, sizeof(*cnl)); + + /* Schedule timeout */ + evtimer_set(&con->ev, relay_natlook, con); + bcopy(&rlay->timeout, &tv, sizeof(tv)); + evtimer_add(&con->ev, &tv); + return; + } + + relay_session(con); + return; + err: + if (s != -1) { + close(s); + if (con != NULL) + free(con); + } +} + +u_int32_t +relay_hash_addr(struct sockaddr_storage *ss, u_int32_t p) +{ + struct sockaddr_in *sin4; + struct sockaddr_in6 *sin6; + + if (ss->ss_family == AF_INET) { + sin4 = (struct sockaddr_in *)ss; + p = hash32_buf(&sin4->sin_addr, + sizeof(struct in_addr), p); + } else { + sin6 = (struct sockaddr_in6 *)ss; + p = hash32_buf(&sin6->sin6_addr, + sizeof(struct in6_addr), p); + } + + return (p); +} + +int +relay_from_table(struct session *con) +{ + struct relay *rlay = (struct relay *)con->relay; + struct host *host; + struct table *table = rlay->dsttable; + u_int32_t p = con->outkey; + int idx = 0; + + if (rlay->dstcheck && !table->up) { + log_debug("relay_from_table: no active hosts"); + return (-1); + } + + switch (rlay->dstmode) { + case RELAY_DSTMODE_ROUNDROBIN: + if ((int)rlay->dstkey >= rlay->dstnhosts) + rlay->dstkey = 0; + idx = (int)rlay->dstkey++; + break; + case RELAY_DSTMODE_LOADBALANCE: + p = relay_hash_addr(&con->in.ss, p); + /* FALLTHROUGH */ + case RELAY_DSTMODE_HASH: + p = relay_hash_addr(&rlay->ss, p); + p = hash32_buf(&rlay->port, sizeof(rlay->port), p); + if ((idx = p % rlay->dstnhosts) >= RELAY_MAXHOSTS) + return (-1); + } + host = rlay->dsthost[idx]; + DPRINTF("relay_from_table: host %s, p 0x%08x, idx %d", + host->name, p, idx); + while (host != NULL) { + DPRINTF("relay_from_table: host %s", host->name); + if (!rlay->dstcheck || host->up == HOST_UP) + goto found; + host = TAILQ_NEXT(host, entry); + } + TAILQ_FOREACH(host, &rlay->dsttable->hosts, entry) { + DPRINTF("relay_from_table: next host %s", host->name); + if (!rlay->dstcheck || host->up == HOST_UP) + goto found; + } + + /* Should not happen */ + fatalx("relay_from_table: no active hosts, desynchronized"); + + found: + con->out.port = table->port; + bcopy(&host->ss, &con->out.ss, sizeof(con->out.ss)); + + return (0); +} + +void +relay_natlook(int fd, short event, void *arg) +{ + struct session *con = (struct session *)arg; + struct ctl_natlook *cnl = con->cnl; + + if (cnl == NULL) + fatalx("invalid NAT lookup"); + + if (con->out.ss.ss_family == AF_UNSPEC && cnl->in == -1) { + relay_close(con, "session NAT lookup failed"); + return; + } + if (cnl->in != -1) { + bcopy(&cnl->rdst, &con->out.ss, sizeof(con->out.ss)); + con->out.port = cnl->rdport; + } + free(con->cnl); + con->cnl = NULL; + + relay_session(con); +} + +void +relay_session(struct session *con) +{ + struct relay *rlay = (struct relay *)con->relay; + + if (bcmp(&rlay->ss, &con->out.ss, sizeof(con->out.ss)) == 0 && + con->out.port == rlay->port) { + log_debug("relay_session: session %d: looping", + con->id); + relay_close(con, "session aborted"); + return; + } + + if ((rlay->flags & F_SSL) && (con->in.ssl == NULL)) { + relay_ssl_transaction(con); + return; + } + + if (!rlay->proto->lateconnect && relay_connect(con) == -1) { + relay_close(con, "session failed"); + return; + } + + relay_input(con); +} + +int +relay_connect(struct session *con) +{ + struct relay *rlay = (struct relay *)con->relay; + + if (gettimeofday(&con->tv_start, NULL)) + return (-1); + + if (rlay->dsttable != NULL) { + if (relay_from_table(con) != 0) + return (-1); + } else { + bcopy(&rlay->dstss, &con->out.ss, sizeof(con->out.ss)); + con->out.port = rlay->dstport; + } + + if ((con->out.s = relay_socket_connect(&con->out.ss, con->out.port, + rlay->proto)) == -1) { + log_debug("relay_connect: session %d: forward failed: %s", + con->id, strerror(errno)); + return (-1); + } + if (errno == EINPROGRESS) + event_again(&con->ev, con->out.s, EV_WRITE|EV_TIMEOUT, + relay_connected, &con->tv_start, &env->timeout, con); + else + relay_connected(con->out.s, EV_WRITE, con); + + return (0); +} + +void +relay_close(struct session *con, const char *msg) +{ + struct relay *rlay = (struct relay *)con->relay; + + TAILQ_REMOVE(&rlay->sessions, con, entry); + + event_del(&con->ev); + if (con->in.bev != NULL) + bufferevent_disable(con->in.bev, EV_READ|EV_WRITE); + if (con->out.bev != NULL) + bufferevent_disable(con->out.bev, EV_READ|EV_WRITE); + + if (con->in.bev != NULL) + bufferevent_free(con->in.bev); + else if (con->in.output != NULL) + evbuffer_free(con->in.output); + if (con->in.ssl != NULL) { + /* XXX handle non-blocking shutdown */ + if (SSL_shutdown(con->in.ssl) == 0) + SSL_shutdown(con->in.ssl); + SSL_free(con->in.ssl); + } + if (con->in.s != -1) + close(con->in.s); + if (con->in.buf != NULL) + free(con->in.buf); + if (con->in.nodes != NULL) + free(con->in.nodes); + + if (con->out.bev != NULL) + bufferevent_free(con->out.bev); + else if (con->out.output != NULL) + evbuffer_free(con->out.output); + if (con->out.s != -1) + close(con->out.s); + if (con->out.buf != NULL) + free(con->out.buf); + if (con->out.nodes != NULL) + free(con->out.nodes); + + if (con->cnl != NULL) { +#if 0 + imsg_compose(ibuf_pfe, IMSG_KILLSTATES, 0, 0, + cnl, sizeof(*cnl)); +#endif + free(con->cnl); + } + +#ifdef DEBUG + log_info("relay %s, session %d closed: %s", rlay->name, con->id, msg); +#else + log_debug("relay %s, session %d closed: %s", rlay->name, con->id, msg); +#endif + + free(con); + relay_sessions--; +} + +void +relay_dispatch_pfe(int fd, short event, void *ptr) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + struct session *con; + struct ctl_natlook cnl; + struct timeval tv; + struct host *host; + struct table *table; + struct ctl_status st; + + ibuf = ptr; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("relay_dispatch_pfe: imsg_read_error"); + if (n == 0) + fatalx("relay_dispatch_pfe: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("relay_dispatch_pfe: msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("relay_dispatch_pfe: unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("relay_dispatch_pfe: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_HOST_STATUS: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(st)) + fatalx("relay_dispatch_pfe: invalid request"); + memcpy(&st, imsg.data, sizeof(st)); + if ((host = host_find(env, st.id)) == NULL) + fatalx("relay_dispatch_pfe: invalid host id"); + + if (host->up == st.up) { + log_debug("relay_dispatch_pfe: host %d => %d", + host->id, host->up); + fatalx("relay_dispatch_pfe: desynchronized"); + } + + if ((table = table_find(env, host->tableid)) == NULL) + fatalx("relay_dispatch_pfe: invalid table id"); + + DPRINTF("relay_dispatch_pfe: [%d] state %d for " + "host %u %s", proc_id, st.up, host->id, host->name); + + if ((st.up == HOST_UNKNOWN && host->up == HOST_DOWN) || + (st.up == HOST_DOWN && host->up == HOST_UNKNOWN)) { + host->up = st.up; + break; + } + if (st.up == HOST_UP) + table->up++; + else + table->up--; + host->up = st.up; + break; + case IMSG_NATLOOK: + bcopy(imsg.data, &cnl, sizeof(cnl)); + if ((con = session_find(env, cnl.id)) == NULL || + con->cnl == NULL) { + log_debug("relay_dispatch_pfe: " + "session expired"); + break; + } + bcopy(&cnl, con->cnl, sizeof(*con->cnl)); + evtimer_del(&con->ev); + evtimer_set(&con->ev, relay_natlook, con); + bzero(&tv, sizeof(tv)); + evtimer_add(&con->ev, &tv); + break; + default: + log_debug("relay_dispatch_msg: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +void +relay_dispatch_parent(int fd, short event, void * ptr) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = ptr; + switch (event) { + case EV_READ: + if ((n = imsg_read(ibuf)) == -1) + fatal("relay_dispatch_parent: imsg_read error"); + if (n == 0) + fatalx("relay_dispatch_parent: pipe closed"); + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("relay_dispatch_parent: msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("relay_dispatch_parent: unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("relay_dispatch_parent: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + log_debug("relay_dispatch_parent: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } +} + +SSL_CTX * +relay_ssl_ctx_create(struct relay *rlay) +{ + SSL_CTX *ctx; + char certfile[PATH_MAX], hbuf[128]; + + ctx = SSL_CTX_new(SSLv23_method()); + if (ctx == NULL) + goto err; + + /* Modify session timeout and cache size*/ + SSL_CTX_set_timeout(ctx, rlay->timeout.tv_sec); + if (rlay->proto->cache < -1) { + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + } else if (rlay->proto->cache >= -1) { + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER); + if (rlay->proto->cache >= 0) + SSL_CTX_sess_set_cache_size(ctx, rlay->proto->cache); + } + + /* Enable all workarounds */ + SSL_CTX_set_options(ctx, SSL_OP_ALL); + + if (relay_host(&rlay->ss, hbuf, sizeof(hbuf)) == NULL) + goto err; + + /* Load the certificate */ + if (snprintf(certfile, sizeof(certfile), + "/etc/ssl/%s.crt", hbuf) == -1) + goto err; + log_debug("relay_ssl_ctx_create: using certificate %s", certfile); + if (!SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM)) + goto err; + + /* Load the private key */ + if (snprintf(certfile, sizeof(certfile), + "/etc/ssl/private/%s.key", hbuf) == -1) { + goto err; + } + log_debug("relay_ssl_ctx_create: using private key %s", certfile); + if (!SSL_CTX_use_PrivateKey_file(ctx, certfile, SSL_FILETYPE_PEM)) + goto err; + if (!SSL_CTX_check_private_key(ctx)) + goto err; + + /* Set session context to the local relay name */ + if (!SSL_CTX_set_session_id_context(ctx, rlay->name, + strlen(rlay->name))) + goto err; + + return (ctx); + + err: + if (ctx != NULL) + SSL_CTX_free(ctx); + ssl_error(rlay->name, "relay_ssl_ctx_create"); + return (NULL); +} + +void +relay_ssl_transaction(struct session *con) +{ + struct relay *rlay = (struct relay *)con->relay; + SSL *ssl; + + ssl = SSL_new(rlay->ctx); + if (ssl == NULL) + goto err; + + if (!SSL_set_ssl_method(ssl, SSLv23_server_method())) + goto err; + if (!SSL_set_fd(ssl, con->in.s)) + goto err; + SSL_set_accept_state(ssl); + + con->in.ssl = ssl; + + event_again(&con->ev, con->in.s, EV_TIMEOUT|EV_READ, + relay_ssl_accept, &con->tv_start, &env->timeout, con); + return; + + err: + if (ssl != NULL) + SSL_free(ssl); + ssl_error(rlay->name, "relay_ssl_transaction"); +} + +void +relay_ssl_accept(int fd, short event, void *arg) +{ + struct session *con = (struct session *)arg; + struct relay *rlay = (struct relay *)con->relay; + int ret; + int ssl_err; + int retry_flag; + + if (event == EV_TIMEOUT) { + relay_close(con, "SSL accept timeout"); + return; + } + + retry_flag = ssl_err = 0; + + ret = SSL_accept(con->in.ssl); + if (ret <= 0) { + ssl_err = SSL_get_error(con->in.ssl, ret); + + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + retry_flag = EV_READ; + goto retry; + case SSL_ERROR_WANT_WRITE: + retry_flag = EV_WRITE; + goto retry; + default: + ssl_error(rlay->name, "relay_ssl_accept"); + return; + } + } + + DPRINTF("relay_ssl_accept: session %d: connection established", + con->id); + relay_session(con); + return; + +retry: + DPRINTF("relay_ssl_accept: session %d: scheduling on %s", con->id, + (retry_flag == EV_READ) ? "EV_READ" : "EV_WRITE"); + event_again(&con->ev, fd, EV_TIMEOUT|retry_flag, relay_ssl_accept, + &con->tv_start, &env->timeout, con); +} + +void +relay_ssl_connected(struct ctl_relay_event *cre) +{ + /* + * Hack libevent - we overwrite the internal bufferevent I/O + * functions to handle the SSL abstraction. + */ + event_set(&cre->bev->ev_read, cre->s, EV_READ, + relay_ssl_readcb, cre->bev); + event_set(&cre->bev->ev_write, cre->s, EV_WRITE, + relay_ssl_writecb, cre->bev); +} + +void +relay_ssl_readcb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + struct ctl_relay_event *cre = (struct ctl_relay_event *)bufev->cbarg; + struct session *con = (struct session *)cre->con; + struct relay *rlay = (struct relay *)con->relay; + int ret = 0, ssl_err = 0; + short what = EVBUFFER_READ; + size_t len; + char rbuf[READ_BUF_SIZE]; + int howmuch = READ_BUF_SIZE; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (bufev->wm_read.high != 0) + howmuch = MIN(sizeof(rbuf), bufev->wm_read.high); + + ret = SSL_read(cre->ssl, rbuf, howmuch); + if (ret <= 0) { + ssl_err = SSL_get_error(cre->ssl, ret); + + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + DPRINTF("relay_ssl_readcb: session %d: " + "want read", con->id); + goto retry; + case SSL_ERROR_WANT_WRITE: + DPRINTF("relay_ssl_readcb: session %d: " + "want write", con->id); + goto retry; + default: + if (ret == 0) + what |= EVBUFFER_EOF; + else { + ssl_error(rlay->name, "relay_ssl_readcb"); + what |= EVBUFFER_ERROR; + } + goto err; + } + } + + if (evbuffer_add(bufev->input, rbuf, ret) == -1) { + what |= EVBUFFER_ERROR; + goto err; + } + + relay_bufferevent_add(&bufev->ev_read, bufev->timeout_read); + + len = EVBUFFER_LENGTH(bufev->input); + if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) + return; + if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) { + struct evbuffer *buf = bufev->input; + event_del(&bufev->ev_read); + evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev); + return; + } + + if (bufev->readcb != NULL) + (*bufev->readcb)(bufev, bufev->cbarg); + return; + + retry: + relay_bufferevent_add(&bufev->ev_read, bufev->timeout_read); + return; + + err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +void +relay_ssl_writecb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + struct ctl_relay_event *cre = (struct ctl_relay_event *)bufev->cbarg; + struct session *con = (struct session *)cre->con; + struct relay *rlay = (struct relay *)con->relay; + int ret = 0, ssl_err; + short what = EVBUFFER_WRITE; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (EVBUFFER_LENGTH(bufev->output)) { + if (cre->buf == NULL) { + cre->buflen = EVBUFFER_LENGTH(bufev->output); + if ((cre->buf = malloc(cre->buflen)) == NULL) { + what |= EVBUFFER_ERROR; + goto err; + } + bcopy(EVBUFFER_DATA(bufev->output), + cre->buf, cre->buflen); + } + + ret = SSL_write(cre->ssl, cre->buf, cre->buflen); + if (ret <= 0) { + ssl_err = SSL_get_error(cre->ssl, ret); + + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + DPRINTF("relay_ssl_writecb: session %d: " + "want read", con->id); + goto retry; + case SSL_ERROR_WANT_WRITE: + DPRINTF("relay_ssl_writecb: session %d: " + "want write", con->id); + goto retry; + default: + if (ret == 0) + what |= EVBUFFER_EOF; + else { + ssl_error(rlay->name, + "relay_ssl_writecb"); + what |= EVBUFFER_ERROR; + } + goto err; + } + } + evbuffer_drain(bufev->output, ret); + } + if (cre->buf != NULL) { + free(cre->buf); + cre->buf = NULL; + cre->buflen = 0; + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) + relay_bufferevent_add(&bufev->ev_write, bufev->timeout_write); + + if (bufev->writecb != NULL && + EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) + (*bufev->writecb)(bufev, bufev->cbarg); + return; + + retry: + if (cre->buflen != 0) + relay_bufferevent_add(&bufev->ev_write, bufev->timeout_write); + return; + + err: + if (cre->buf != NULL) { + free(cre->buf); + cre->buf = NULL; + cre->buflen = 0; + } + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +int +relay_bufferevent_add(struct event *ev, int timeout) +{ + struct timeval tv, *ptv = NULL; + + if (timeout) { + timerclear(&tv); + tv.tv_sec = timeout; + ptv = &tv; + } + + return (event_add(ev, ptv)); +} + +#ifdef notyet +int +relay_bufferevent_printf(struct ctl_relay_event *cre, const char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = evbuffer_add_vprintf(cre->output, fmt, ap); + va_end(ap); + + if (cre->bev != NULL && + ret != -1 && EVBUFFER_LENGTH(cre->output) > 0 && + (cre->bev->enabled & EV_WRITE)) + bufferevent_enable(cre->bev, EV_WRITE); + + return (ret); +} +#endif + +int +relay_bufferevent_print(struct ctl_relay_event *cre, char *str) +{ + if (cre->bev == NULL) + return (evbuffer_add(cre->output, str, strlen(str))); + return (bufferevent_write(cre->bev, str, strlen(str))); +} + +int +relay_bufferevent_write_buffer(struct ctl_relay_event *cre, struct + evbuffer *buf) +{ + if (cre->bev == NULL) + return (evbuffer_add_buffer(cre->output, buf)); + return (bufferevent_write_buffer(cre->bev, buf)); +} + +int +relay_bufferevent_write(struct ctl_relay_event *cre, void *data, size_t size) +{ + if (cre->bev == NULL) + return (evbuffer_add(cre->output, data, size)); + return (bufferevent_write(cre->bev, data, size)); +} + +static __inline int +relay_proto_cmp(struct protonode *a, struct protonode *b) +{ + return (strcasecmp(a->key, b->key)); +} + +RB_GENERATE(proto_tree, protonode, nodes, relay_proto_cmp); |