1
0
mirror of https://github.com/openbsd/src.git synced 2025-01-09 22:38:01 -08:00
openbsd-src/usr.sbin/relayd/relay.c
reyk dbb03cc582 Disable client-initiated TLS renegotiation by default.
It is rarely needed and imposes a light DoS risk.  LibreSSL's libssl
allows to turn it off with a simple SSL_OP_NO_CLIENT_RENEGOTIATION
option instead of the complicated implementation that was used before.
It now turns it off completely instead of allowing one initial
client-initiated renegotiation.

It can still be enabled with "tls client-renegotiation".

ok benno@ beck@ jsing@
2017-02-02 08:24:16 +00:00

2797 lines
67 KiB
C

/* $OpenBSD: relay.c,v 1.219 2017/02/02 08:24:16 reyk Exp $ */
/*
* Copyright (c) 2006 - 2014 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/tree.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <event.h>
#include <siphash.h>
#include <imsg.h>
#include <openssl/dh.h>
#include <openssl/ssl.h>
#include "relayd.h"
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
void relay_statistics(int, short, void *);
int relay_dispatch_parent(int, struct privsep_proc *,
struct imsg *);
int relay_dispatch_pfe(int, struct privsep_proc *,
struct imsg *);
int relay_dispatch_ca(int, struct privsep_proc *,
struct imsg *);
int relay_dispatch_hce(int, struct privsep_proc *,
struct imsg *);
void relay_shutdown(void);
void relay_protodebug(struct relay *);
void relay_ruledebug(struct relay_rule *);
void relay_init(struct privsep *, struct privsep_proc *p, void *);
void relay_launch(void);
int relay_socket(struct sockaddr_storage *, in_port_t,
struct protocol *, int, int);
int relay_socket_listen(struct sockaddr_storage *, in_port_t,
struct protocol *);
int relay_socket_connect(struct sockaddr_storage *, in_port_t,
struct protocol *, int);
void relay_accept(int, short, void *);
void relay_input(struct rsession *);
void relay_hash_addr(SIPHASH_CTX *, struct sockaddr_storage *, int);
DH * relay_tls_get_dhparams(int);
DH *relay_tls_callback_dh(SSL *, int, int);
SSL_CTX *relay_tls_ctx_create(struct relay *);
void relay_tls_transaction(struct rsession *,
struct ctl_relay_event *);
void relay_tls_accept(int, short, void *);
void relay_connect_retry(int, short, void *);
void relay_tls_connect(int, short, void *);
void relay_tls_connected(struct ctl_relay_event *);
void relay_tls_readcb(int, short, void *);
void relay_tls_writecb(int, short, void *);
struct tls_ticket *relay_get_ticket_key(unsigned char *);
int relay_tls_session_ticket(SSL *, unsigned char *,
unsigned char *, EVP_CIPHER_CTX *, HMAC_CTX *, int);
char *relay_load_file(const char *, off_t *);
extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t,
size_t, void *);
volatile int relay_sessions;
volatile int relay_inflight = 0;
objid_t relay_conid;
static struct relayd *env = NULL;
static struct privsep_proc procs[] = {
{ "parent", PROC_PARENT, relay_dispatch_parent },
{ "pfe", PROC_PFE, relay_dispatch_pfe },
{ "ca", PROC_CA, relay_dispatch_ca },
{ "hce", PROC_HCE, relay_dispatch_hce },
};
void
relay(struct privsep *ps, struct privsep_proc *p)
{
env = ps->ps_env;
proc_run(ps, p, procs, nitems(procs), relay_init, NULL);
relay_http(env);
}
void
relay_shutdown(void)
{
config_purge(env, CONFIG_ALL);
usleep(200); /* XXX relay needs to shutdown last */
}
void
relay_ruledebug(struct relay_rule *rule)
{
struct kv *kv = NULL;
u_int i;
fprintf(stderr, "\t\t");
switch (rule->rule_action) {
case RULE_ACTION_MATCH:
fprintf(stderr, "match ");
break;
case RULE_ACTION_BLOCK:
fprintf(stderr, "block ");
break;
case RULE_ACTION_PASS:
fprintf(stderr, "pass ");
break;
}
switch (rule->rule_dir) {
case RELAY_DIR_ANY:
break;
case RELAY_DIR_REQUEST:
fprintf(stderr, "request ");
break;
case RELAY_DIR_RESPONSE:
fprintf(stderr, "response ");
break;
default:
return;
/* NOTREACHED */
break;
}
if (rule->rule_flags & RULE_FLAG_QUICK)
fprintf(stderr, "quick ");
for (i = 1; i < KEY_TYPE_MAX; i++) {
kv = &rule->rule_kv[i];
if (kv->kv_type != i)
continue;
switch (kv->kv_type) {
case KEY_TYPE_COOKIE:
fprintf(stderr, "cookie ");
break;
case KEY_TYPE_HEADER:
fprintf(stderr, "header ");
break;
case KEY_TYPE_PATH:
fprintf(stderr, "path ");
break;
case KEY_TYPE_QUERY:
fprintf(stderr, "query ");
break;
case KEY_TYPE_URL:
fprintf(stderr, "url ");
break;
default:
continue;
}
switch (kv->kv_option) {
case KEY_OPTION_APPEND:
fprintf(stderr, "append ");
break;
case KEY_OPTION_SET:
fprintf(stderr, "set ");
break;
case KEY_OPTION_REMOVE:
fprintf(stderr, "remove ");
break;
case KEY_OPTION_HASH:
fprintf(stderr, "hash ");
break;
case KEY_OPTION_LOG:
fprintf(stderr, "log ");
break;
case KEY_OPTION_NONE:
break;
}
switch (kv->kv_digest) {
case DIGEST_SHA1:
case DIGEST_MD5:
fprintf(stderr, "digest ");
break;
default:
break;
}
fprintf(stderr, "%s%s%s%s%s%s ",
kv->kv_key == NULL ? "" : "\"",
kv->kv_key == NULL ? "" : kv->kv_key,
kv->kv_key == NULL ? "" : "\"",
kv->kv_value == NULL ? "" : " value \"",
kv->kv_value == NULL ? "" : kv->kv_value,
kv->kv_value == NULL ? "" : "\"");
}
if (rule->rule_tablename[0])
fprintf(stderr, "forward to <%s> ", rule->rule_tablename);
if (rule->rule_tag == -1)
fprintf(stderr, "no tag ");
else if (rule->rule_tag && rule->rule_tagname[0])
fprintf(stderr, "tag \"%s\" ",
rule->rule_tagname);
if (rule->rule_tagged && rule->rule_taggedname[0])
fprintf(stderr, "tagged \"%s\" ",
rule->rule_taggedname);
if (rule->rule_label == -1)
fprintf(stderr, "no label ");
else if (rule->rule_label && rule->rule_labelname[0])
fprintf(stderr, "label \"%s\" ",
rule->rule_labelname);
fprintf(stderr, "\n");
}
void
relay_protodebug(struct relay *rlay)
{
struct protocol *proto = rlay->rl_proto;
struct relay_rule *rule = NULL;
fprintf(stderr, "protocol %d: name %s\n",
proto->id, proto->name);
fprintf(stderr, "\tflags: %s, relay flags: %s\n",
printb_flags(proto->flags, F_BITS),
printb_flags(rlay->rl_conf.flags, F_BITS));
if (proto->tcpflags)
fprintf(stderr, "\ttcp flags: %s\n",
printb_flags(proto->tcpflags, TCPFLAG_BITS));
if ((rlay->rl_conf.flags & (F_TLS|F_TLSCLIENT)) && proto->tlsflags)
fprintf(stderr, "\ttls flags: %s\n",
printb_flags(proto->tlsflags, TLSFLAG_BITS));
fprintf(stderr, "\ttls session tickets: %s\n",
(proto->tickets > -1) ? "enabled" : "disabled");
fprintf(stderr, "\ttype: ");
switch (proto->type) {
case RELAY_PROTO_TCP:
fprintf(stderr, "tcp\n");
break;
case RELAY_PROTO_HTTP:
fprintf(stderr, "http\n");
break;
case RELAY_PROTO_DNS:
fprintf(stderr, "dns\n");
break;
}
rule = TAILQ_FIRST(&proto->rules);
while (rule != NULL) {
relay_ruledebug(rule);
rule = TAILQ_NEXT(rule, rule_entry);
}
}
int
relay_privinit(struct relay *rlay)
{
log_debug("%s: adding relay %s", __func__, rlay->rl_conf.name);
if (log_getverbose() > 1)
relay_protodebug(rlay);
switch (rlay->rl_proto->type) {
case RELAY_PROTO_DNS:
relay_udp_privinit(env, rlay);
break;
case RELAY_PROTO_TCP:
break;
case RELAY_PROTO_HTTP:
break;
}
if (rlay->rl_conf.flags & F_UDP)
rlay->rl_s = relay_udp_bind(&rlay->rl_conf.ss,
rlay->rl_conf.port, rlay->rl_proto);
else
rlay->rl_s = relay_socket_listen(&rlay->rl_conf.ss,
rlay->rl_conf.port, rlay->rl_proto);
if (rlay->rl_s == -1)
return (-1);
return (0);
}
void
relay_init(struct privsep *ps, struct privsep_proc *p, void *arg)
{
struct timeval tv;
if (config_init(ps->ps_env) == -1)
fatal("failed to initialize configuration");
/* We use a custom shutdown callback */
p->p_shutdown = relay_shutdown;
/* Unlimited file descriptors (use system limits) */
socket_rlimit(-1);
if (pledge("stdio recvfd inet", NULL) == -1)
fatal("pledge");
/* Schedule statistics timer */
evtimer_set(&env->sc_statev, relay_statistics, ps);
bcopy(&env->sc_conf.statinterval, &tv, sizeof(tv));
evtimer_add(&env->sc_statev, &tv);
}
void
relay_session_publish(struct rsession *s)
{
proc_compose(env->sc_ps, PROC_PFE, IMSG_SESS_PUBLISH, s, sizeof(*s));
}
void
relay_session_unpublish(struct rsession *s)
{
proc_compose(env->sc_ps, PROC_PFE, IMSG_SESS_UNPUBLISH,
&s->se_id, sizeof(s->se_id));
}
void
relay_statistics(int fd, short events, void *arg)
{
struct privsep *ps = arg;
struct relay *rlay;
struct ctl_stats crs, *cur;
struct timeval tv, tv_now;
int resethour = 0, resetday = 0;
struct rsession *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);
getmonotime(&tv_now);
TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
bzero(&crs, sizeof(crs));
resethour = resetday = 0;
cur = &rlay->rl_stats[ps->ps_instance];
cur->cnt += cur->last;
cur->tick++;
cur->avg = (cur->last + cur->avg) / 2;
cur->last_hour += cur->last;
if ((cur->tick %
(3600 / env->sc_conf.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->sc_conf.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->rl_conf.id;
crs.proc = ps->ps_instance;
proc_compose(env->sc_ps, PROC_PFE, IMSG_STATISTICS,
&crs, sizeof(crs));
for (con = SPLAY_ROOT(&rlay->rl_sessions);
con != NULL; con = next_con) {
next_con = SPLAY_NEXT(session_tree,
&rlay->rl_sessions, con);
timersub(&tv_now, &con->se_tv_last, &tv);
if (timercmp(&tv, &rlay->rl_conf.timeout, >=))
relay_close(con, "hard timeout");
}
}
/* Schedule statistics timer */
evtimer_set(&env->sc_statev, relay_statistics, ps);
bcopy(&env->sc_conf.statinterval, &tv, sizeof(tv));
evtimer_add(&env->sc_statev, &tv);
}
void
relay_launch(void)
{
void (*callback)(int, short, void *);
struct relay *rlay;
struct host *host;
struct relay_table *rlt;
TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
if ((rlay->rl_conf.flags & (F_TLS|F_TLSCLIENT)) &&
(rlay->rl_ssl_ctx = relay_tls_ctx_create(rlay)) == NULL)
fatal("relay_init: failed to create TLS context");
TAILQ_FOREACH(rlt, &rlay->rl_tables, rlt_entry) {
/*
* set rule->rule_table in advance and save time
* looking up for this later on rule/connection
* evalution
*/
rule_settable(&rlay->rl_proto->rules, rlt);
rlt->rlt_index = 0;
rlt->rlt_nhosts = 0;
TAILQ_FOREACH(host, &rlt->rlt_table->hosts, entry) {
if (rlt->rlt_nhosts >= RELAY_MAXHOSTS)
fatal("relay_init: "
"too many hosts in table");
host->idx = rlt->rlt_nhosts;
rlt->rlt_host[rlt->rlt_nhosts++] = host;
}
log_info("adding %d hosts from table %s%s",
rlt->rlt_nhosts, rlt->rlt_table->conf.name,
rlt->rlt_table->conf.check ? "" : " (no check)");
}
switch (rlay->rl_proto->type) {
case RELAY_PROTO_DNS:
relay_udp_init(rlay);
break;
case RELAY_PROTO_TCP:
case RELAY_PROTO_HTTP:
relay_http_init(rlay);
/* Use defaults */
break;
}
log_debug("%s: running relay %s", __func__,
rlay->rl_conf.name);
rlay->rl_up = HOST_UP;
if (rlay->rl_conf.flags & F_UDP)
callback = relay_udp_server;
else
callback = relay_accept;
event_set(&rlay->rl_ev, rlay->rl_s, EV_READ,
callback, rlay);
event_add(&rlay->rl_ev, NULL);
evtimer_set(&rlay->rl_evt, callback, rlay);
}
}
int
relay_socket_af(struct sockaddr_storage *ss, in_port_t port)
{
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;
default:
return (-1);
}
return (0);
}
in_port_t
relay_socket_getport(struct sockaddr_storage *ss)
{
switch (ss->ss_family) {
case AF_INET:
return (((struct sockaddr_in *)ss)->sin_port);
case AF_INET6:
return (((struct sockaddr_in6 *)ss)->sin6_port);
default:
return (0);
}
/* NOTREACHED */
return (0);
}
int
relay_socket(struct sockaddr_storage *ss, in_port_t port,
struct protocol *proto, int fd, int reuseport)
{
struct linger lng;
int s = -1, val;
if (relay_socket_af(ss, port) == -1)
goto bad;
s = fd == -1 ? socket(ss->ss_family,
SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP) : fd;
if (s == -1)
goto bad;
/*
* Socket options
*/
bzero(&lng, sizeof(lng));
if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
goto bad;
if (reuseport) {
val = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val,
sizeof(int)) == -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;
}
/*
* IP options
*/
if (proto->tcpflags & TCPFLAG_IPTTL) {
val = (int)proto->tcpipttl;
switch (ss->ss_family) {
case AF_INET:
if (setsockopt(s, IPPROTO_IP, IP_TTL,
&val, sizeof(val)) == -1)
goto bad;
break;
case AF_INET6:
if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
&val, sizeof(val)) == -1)
goto bad;
break;
}
}
if (proto->tcpflags & TCPFLAG_IPMINTTL) {
val = (int)proto->tcpipminttl;
switch (ss->ss_family) {
case AF_INET:
if (setsockopt(s, IPPROTO_IP, IP_MINTTL,
&val, sizeof(val)) == -1)
goto bad;
break;
case AF_INET6:
if (setsockopt(s, IPPROTO_IPV6, IPV6_MINHOPCOUNT,
&val, sizeof(val)) == -1)
goto bad;
break;
}
}
/*
* TCP options
*/
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;
}
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 fd)
{
int s;
if ((s = relay_socket(ss, port, proto, fd, 0)) == -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, 1)) == -1)
return (-1);
if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1)
goto bad;
if (listen(s, proto->tcpbacklog) == -1)
goto bad;
return (s);
bad:
close(s);
return (-1);
}
void
relay_connected(int fd, short sig, void *arg)
{
struct rsession *con = arg;
struct relay *rlay = con->se_relay;
struct protocol *proto = rlay->rl_proto;
evbuffercb outrd = relay_read;
evbuffercb outwr = relay_write;
struct bufferevent *bev;
struct ctl_relay_event *out = &con->se_out;
socklen_t len;
int error;
if (sig == EV_TIMEOUT) {
relay_abort_http(con, 504, "connect timeout", 0);
return;
}
len = sizeof(error);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error,
&len) == -1 || error) {
if (error)
errno = error;
relay_abort_http(con, 500, "socket error", 0);
return;
}
if ((rlay->rl_conf.flags & F_TLSCLIENT) && (out->ssl == NULL)) {
relay_tls_transaction(con, out);
return;
}
DPRINTF("%s: session %d: successful", __func__, con->se_id);
switch (rlay->rl_proto->type) {
case RELAY_PROTO_HTTP:
if (relay_httpdesc_init(out) == -1) {
relay_close(con,
"failed to allocate http descriptor");
return;
}
con->se_out.toread = TOREAD_HTTP_HEADER;
outrd = relay_read_http;
break;
case RELAY_PROTO_TCP:
/* Use defaults */
break;
default:
fatalx("relay_connected: unknown protocol");
}
/*
* Relay <-> Server
*/
bev = bufferevent_new(fd, outrd, outwr, relay_error, &con->se_out);
if (bev == NULL) {
relay_abort_http(con, 500,
"failed to allocate output buffer event", 0);
return;
}
evbuffer_free(bev->output);
bev->output = con->se_out.output;
if (bev->output == NULL)
fatal("relay_connected: invalid output buffer");
con->se_out.bev = bev;
/* Initialize the TLS wrapper */
if ((rlay->rl_conf.flags & F_TLSCLIENT) && (out->ssl != NULL))
relay_tls_connected(out);
bufferevent_settimeout(bev,
rlay->rl_conf.timeout.tv_sec, rlay->rl_conf.timeout.tv_sec);
bufferevent_setwatermark(bev, EV_WRITE,
RELAY_MIN_PREFETCHED * proto->tcpbufsiz, 0);
bufferevent_enable(bev, EV_READ|EV_WRITE);
if (con->se_in.bev)
bufferevent_enable(con->se_in.bev, EV_READ);
if (relay_splice(&con->se_out) == -1)
relay_close(con, strerror(errno));
}
void
relay_input(struct rsession *con)
{
struct relay *rlay = con->se_relay;
struct protocol *proto = rlay->rl_proto;
evbuffercb inrd = relay_read;
evbuffercb inwr = relay_write;
switch (rlay->rl_proto->type) {
case RELAY_PROTO_HTTP:
if (relay_httpdesc_init(&con->se_in) == -1) {
relay_close(con,
"failed to allocate http descriptor");
return;
}
con->se_in.toread = TOREAD_HTTP_HEADER;
inrd = relay_read_http;
break;
case RELAY_PROTO_TCP:
/* Use defaults */
break;
default:
fatalx("relay_input: unknown protocol");
}
/*
* Client <-> Relay
*/
con->se_in.bev = bufferevent_new(con->se_in.s, inrd, inwr,
relay_error, &con->se_in);
if (con->se_in.bev == NULL) {
relay_close(con, "failed to allocate input buffer event");
return;
}
/* Initialize the TLS wrapper */
if ((rlay->rl_conf.flags & F_TLS) && con->se_in.ssl != NULL)
relay_tls_connected(&con->se_in);
bufferevent_settimeout(con->se_in.bev,
rlay->rl_conf.timeout.tv_sec, rlay->rl_conf.timeout.tv_sec);
bufferevent_setwatermark(con->se_in.bev, EV_WRITE,
RELAY_MIN_PREFETCHED * proto->tcpbufsiz, 0);
bufferevent_enable(con->se_in.bev, EV_READ|EV_WRITE);
if (relay_splice(&con->se_in) == -1)
relay_close(con, strerror(errno));
}
void
relay_write(struct bufferevent *bev, void *arg)
{
struct ctl_relay_event *cre = arg;
struct rsession *con = cre->con;
getmonotime(&con->se_tv_last);
if (con->se_done)
goto done;
if (relay_splice(cre->dst) == -1)
goto fail;
if (cre->dst->bev)
bufferevent_enable(cre->dst->bev, EV_READ);
return;
done:
relay_close(con, "last write (done)");
return;
fail:
relay_close(con, strerror(errno));
}
void
relay_dump(struct ctl_relay_event *cre, const void *buf, size_t len)
{
if (!len)
return;
/*
* This function will dump the specified message directly
* to the underlying session, without waiting for success
* of non-blocking events etc. This is useful to print an
* error message before gracefully closing the session.
*/
if (cre->ssl != NULL)
(void)SSL_write(cre->ssl, buf, len);
else
(void)write(cre->s, buf, len);
}
void
relay_read(struct bufferevent *bev, void *arg)
{
struct ctl_relay_event *cre = arg;
struct rsession *con = cre->con;
struct protocol *proto = con->se_relay->rl_proto;
struct evbuffer *src = EVBUFFER_INPUT(bev);
getmonotime(&con->se_tv_last);
cre->timedout = 0;
if (!EVBUFFER_LENGTH(src))
return;
if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
goto fail;
if (con->se_done)
goto done;
if (cre->dst->bev)
bufferevent_enable(cre->dst->bev, EV_READ);
if (cre->dst->bev && EVBUFFER_LENGTH(EVBUFFER_OUTPUT(cre->dst->bev)) >
(size_t)RELAY_MAX_PREFETCH * proto->tcpbufsiz)
bufferevent_disable(bev, EV_READ);
return;
done:
relay_close(con, "last read (done)");
return;
fail:
relay_close(con, strerror(errno));
}
/*
* Splice sockets from cre to cre->dst if applicable. Returns:
* -1 socket splicing has failed
* 0 socket splicing is currently not possible
* 1 socket splicing was successful
*/
int
relay_splice(struct ctl_relay_event *cre)
{
struct rsession *con = cre->con;
struct relay *rlay = con->se_relay;
struct protocol *proto = rlay->rl_proto;
struct splice sp;
if ((rlay->rl_conf.flags & (F_TLS|F_TLSCLIENT)) ||
(proto->tcpflags & TCPFLAG_NSPLICE))
return (0);
if (cre->splicelen >= 0)
return (0);
/* still not connected */
if (cre->bev == NULL || cre->dst->bev == NULL)
return (0);
if (!(cre->toread == TOREAD_UNLIMITED || cre->toread > 0)) {
DPRINTF("%s: session %d: splice dir %d, nothing to read %lld",
__func__, con->se_id, cre->dir, cre->toread);
return (0);
}
/* do not splice before buffers have not been completely flushed */
if (EVBUFFER_LENGTH(cre->bev->input) ||
EVBUFFER_LENGTH(cre->dst->bev->output)) {
DPRINTF("%s: session %d: splice dir %d, dirty buffer",
__func__, con->se_id, cre->dir);
bufferevent_disable(cre->bev, EV_READ);
return (0);
}
bzero(&sp, sizeof(sp));
sp.sp_fd = cre->dst->s;
sp.sp_max = cre->toread > 0 ? cre->toread : 0;
bcopy(&rlay->rl_conf.timeout, &sp.sp_idle, sizeof(sp.sp_idle));
if (setsockopt(cre->s, SOL_SOCKET, SO_SPLICE, &sp, sizeof(sp)) == -1) {
log_debug("%s: session %d: splice dir %d failed: %s",
__func__, con->se_id, cre->dir, strerror(errno));
return (-1);
}
cre->splicelen = 0;
bufferevent_enable(cre->bev, EV_READ);
DPRINTF("%s: session %d: splice dir %d, maximum %lld, successful",
__func__, con->se_id, cre->dir, cre->toread);
return (1);
}
int
relay_splicelen(struct ctl_relay_event *cre)
{
struct rsession *con = cre->con;
off_t len;
socklen_t optlen;
if (cre->splicelen < 0)
return (0);
optlen = sizeof(len);
if (getsockopt(cre->s, SOL_SOCKET, SO_SPLICE, &len, &optlen) == -1) {
log_debug("%s: session %d: splice dir %d get length failed: %s",
__func__, con->se_id, cre->dir, strerror(errno));
return (-1);
}
DPRINTF("%s: session %d: splice dir %d, length %lld",
__func__, con->se_id, cre->dir, len);
if (len > cre->splicelen) {
getmonotime(&con->se_tv_last);
cre->splicelen = len;
return (1);
}
return (0);
}
int
relay_spliceadjust(struct ctl_relay_event *cre)
{
if (cre->splicelen < 0)
return (0);
if (relay_splicelen(cre) == -1)
return (-1);
if (cre->splicelen > 0 && cre->toread > 0)
cre->toread -= cre->splicelen;
cre->splicelen = -1;
return (0);
}
void
relay_error(struct bufferevent *bev, short error, void *arg)
{
struct ctl_relay_event *cre = arg;
struct rsession *con = cre->con;
struct evbuffer *dst;
if (error & EVBUFFER_TIMEOUT) {
if (cre->splicelen >= 0) {
bufferevent_enable(bev, EV_READ);
} else if (cre->dst->splicelen >= 0) {
switch (relay_splicelen(cre->dst)) {
case -1:
goto fail;
case 0:
relay_close(con, "buffer event timeout");
break;
case 1:
cre->timedout = 1;
bufferevent_enable(bev, EV_READ);
break;
}
} else {
relay_close(con, "buffer event timeout");
}
return;
}
if (error & EVBUFFER_ERROR && errno == ETIMEDOUT) {
if (cre->dst->splicelen >= 0) {
switch (relay_splicelen(cre->dst)) {
case -1:
goto fail;
case 0:
relay_close(con, "splice timeout");
return;
case 1:
bufferevent_enable(bev, EV_READ);
break;
}
} else if (cre->dst->timedout) {
relay_close(con, "splice timeout");
return;
}
if (relay_spliceadjust(cre) == -1)
goto fail;
if (relay_splice(cre) == -1)
goto fail;
return;
}
if (error & EVBUFFER_ERROR && errno == EFBIG) {
if (relay_spliceadjust(cre) == -1)
goto fail;
bufferevent_enable(cre->bev, EV_READ);
return;
}
if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
bufferevent_disable(bev, EV_READ|EV_WRITE);
con->se_done = 1;
if (cre->dst->bev != NULL) {
dst = EVBUFFER_OUTPUT(cre->dst->bev);
if (EVBUFFER_LENGTH(dst))
return;
} else if (cre->toread == TOREAD_UNLIMITED || cre->toread == 0)
return;
relay_close(con, "done");
return;
}
relay_close(con, "buffer event error");
return;
fail:
relay_close(con, strerror(errno));
}
void
relay_accept(int fd, short event, void *arg)
{
struct privsep *ps = env->sc_ps;
struct relay *rlay = arg;
struct rsession *con = NULL;
struct ctl_natlook *cnl = NULL;
socklen_t slen;
struct timeval tv;
struct sockaddr_storage ss;
int s = -1;
event_add(&rlay->rl_ev, NULL);
if ((event & EV_TIMEOUT))
return;
slen = sizeof(ss);
if ((s = accept_reserve(fd, (struct sockaddr *)&ss,
&slen, FD_RESERVE, &relay_inflight)) == -1) {
/*
* Pause accept if we are out of file descriptors, or
* libevent will haunt us here too.
*/
if (errno == ENFILE || errno == EMFILE) {
struct timeval evtpause = { 1, 0 };
event_del(&rlay->rl_ev);
evtimer_add(&rlay->rl_evt, &evtpause);
log_debug("%s: deferring connections", __func__);
}
return;
}
if (relay_sessions >= RELAY_MAX_SESSIONS ||
rlay->rl_conf.flags & F_DISABLE)
goto err;
if ((con = calloc(1, sizeof(*con))) == NULL)
goto err;
/* Pre-allocate log buffer */
con->se_haslog = 0;
con->se_log = evbuffer_new();
if (con->se_log == NULL)
goto err;
con->se_in.s = s;
con->se_in.ssl = NULL;
con->se_out.s = -1;
con->se_out.ssl = NULL;
con->se_in.dst = &con->se_out;
con->se_out.dst = &con->se_in;
con->se_in.con = con;
con->se_out.con = con;
con->se_in.splicelen = -1;
con->se_out.splicelen = -1;
con->se_in.toread = TOREAD_UNLIMITED;
con->se_out.toread = TOREAD_UNLIMITED;
con->se_relay = rlay;
con->se_id = ++relay_conid;
con->se_relayid = rlay->rl_conf.id;
con->se_pid = getpid();
con->se_in.dir = RELAY_DIR_REQUEST;
con->se_out.dir = RELAY_DIR_RESPONSE;
con->se_retry = rlay->rl_conf.dstretry;
con->se_bnds = -1;
con->se_out.port = rlay->rl_conf.dstport;
switch (ss.ss_family) {
case AF_INET:
con->se_in.port = ((struct sockaddr_in *)&ss)->sin_port;
break;
case AF_INET6:
con->se_in.port = ((struct sockaddr_in6 *)&ss)->sin6_port;
break;
}
bcopy(&ss, &con->se_in.ss, sizeof(con->se_in.ss));
getmonotime(&con->se_tv_start);
bcopy(&con->se_tv_start, &con->se_tv_last, sizeof(con->se_tv_last));
if (rlay->rl_conf.flags & F_HASHKEY) {
SipHash24_Init(&con->se_siphashctx,
&rlay->rl_conf.hashkey.siphashkey);
}
relay_sessions++;
SPLAY_INSERT(session_tree, &rlay->rl_sessions, con);
relay_session_publish(con);
/* Increment the per-relay session counter */
rlay->rl_stats[ps->ps_instance].last++;
/* Pre-allocate output buffer */
con->se_out.output = evbuffer_new();
if (con->se_out.output == NULL) {
relay_close(con, "failed to allocate output buffer");
return;
}
if (rlay->rl_conf.flags & F_DIVERT) {
slen = sizeof(con->se_out.ss);
if (getsockname(s, (struct sockaddr *)&con->se_out.ss,
&slen) == -1) {
relay_close(con, "peer lookup failed");
return;
}
con->se_out.port = relay_socket_getport(&con->se_out.ss);
/* Detect loop and fall back to the alternate forward target */
if (bcmp(&rlay->rl_conf.ss, &con->se_out.ss,
sizeof(con->se_out.ss)) == 0 &&
con->se_out.port == rlay->rl_conf.port)
con->se_out.ss.ss_family = AF_UNSPEC;
} else if (rlay->rl_conf.flags & F_NATLOOK) {
if ((cnl = calloc(1, sizeof(*cnl))) == NULL) {
relay_close(con, "failed to allocate nat lookup");
return;
}
con->se_cnl = cnl;
bzero(cnl, sizeof(*cnl));
cnl->in = -1;
cnl->id = con->se_id;
cnl->proc = ps->ps_instance;
cnl->proto = IPPROTO_TCP;
bcopy(&con->se_in.ss, &cnl->src, sizeof(cnl->src));
slen = sizeof(cnl->dst);
if (getsockname(s,
(struct sockaddr *)&cnl->dst, &slen) == -1) {
relay_close(con, "failed to get local address");
return;
}
proc_compose(env->sc_ps, PROC_PFE, IMSG_NATLOOK,
cnl, sizeof(*cnl));
/* Schedule timeout */
evtimer_set(&con->se_ev, relay_natlook, con);
bcopy(&rlay->rl_conf.timeout, &tv, sizeof(tv));
evtimer_add(&con->se_ev, &tv);
return;
}
if (rlay->rl_conf.flags & F_TLSINSPECT) {
relay_preconnect(con);
return;
}
relay_session(con);
return;
err:
if (s != -1) {
close(s);
free(con);
/*
* the session struct was not completely set up, but still
* counted as an inflight session. account for this.
*/
relay_inflight--;
log_debug("%s: inflight decremented, now %d",
__func__, relay_inflight);
}
}
void
relay_hash_addr(SIPHASH_CTX *ctx, struct sockaddr_storage *ss, int portset)
{
struct sockaddr_in *sin4;
struct sockaddr_in6 *sin6;
in_port_t port;
if (ss->ss_family == AF_INET) {
sin4 = (struct sockaddr_in *)ss;
SipHash24_Update(ctx, &sin4->sin_addr,
sizeof(struct in_addr));
} else {
sin6 = (struct sockaddr_in6 *)ss;
SipHash24_Update(ctx, &sin6->sin6_addr,
sizeof(struct in6_addr));
}
if (portset != -1) {
port = (in_port_t)portset;
SipHash24_Update(ctx, &port, sizeof(port));
}
}
int
relay_from_table(struct rsession *con)
{
struct relay *rlay = con->se_relay;
struct host *host = NULL;
struct relay_table *rlt = NULL;
struct table *table = NULL;
int idx = -1;
int cnt = 0;
int maxtries;
u_int64_t p = 0;
/* the table is already selected */
if (con->se_table != NULL) {
rlt = con->se_table;
table = rlt->rlt_table;
if (table->conf.check && !table->up)
table = NULL;
goto gottable;
}
/* otherwise grep the first active table */
TAILQ_FOREACH(rlt, &rlay->rl_tables, rlt_entry) {
table = rlt->rlt_table;
if ((rlt->rlt_flags & F_USED) == 0 ||
(table->conf.check && !table->up))
table = NULL;
else
break;
}
gottable:
if (table == NULL) {
log_debug("%s: session %d: no active hosts",
__func__, con->se_id);
return (-1);
}
switch (rlt->rlt_mode) {
case RELAY_DSTMODE_ROUNDROBIN:
if ((int)rlt->rlt_index >= rlt->rlt_nhosts)
rlt->rlt_index = 0;
idx = (int)rlt->rlt_index;
break;
case RELAY_DSTMODE_RANDOM:
idx = (int)arc4random_uniform(rlt->rlt_nhosts);
break;
case RELAY_DSTMODE_SRCHASH:
/* Source IP address without port */
relay_hash_addr(&con->se_siphashctx, &con->se_in.ss, -1);
break;
case RELAY_DSTMODE_LOADBALANCE:
/* Source IP address without port */
relay_hash_addr(&con->se_siphashctx, &con->se_in.ss, -1);
/* FALLTHROUGH */
case RELAY_DSTMODE_HASH:
/* Local "destination" IP address and port */
relay_hash_addr(&con->se_siphashctx, &rlay->rl_conf.ss,
rlay->rl_conf.port);
break;
default:
fatalx("relay_from_table: unsupported mode");
/* NOTREACHED */
}
if (idx == -1) {
/* handle all hashing algorithms */
p = SipHash24_End(&con->se_siphashctx);
/* Reset hash context */
SipHash24_Init(&con->se_siphashctx,
&rlay->rl_conf.hashkey.siphashkey);
maxtries = (rlt->rlt_nhosts < RELAY_MAX_HASH_RETRIES ?
rlt->rlt_nhosts : RELAY_MAX_HASH_RETRIES);
for (cnt = 0; cnt < maxtries; cnt++) {
if ((idx = p % rlt->rlt_nhosts) >= RELAY_MAXHOSTS)
return (-1);
host = rlt->rlt_host[idx];
DPRINTF("%s: session %d: table %s host %s, "
"p 0x%016llx, idx %d, cnt %d, max %d",
__func__, con->se_id, table->conf.name,
host->conf.name, p, idx, cnt, maxtries);
if (!table->conf.check || host->up == HOST_UP)
goto found;
p = p >> 1;
}
} else {
/* handle all non-hashing algorithms */
host = rlt->rlt_host[idx];
DPRINTF("%s: session %d: table %s host %s, p 0x%016llx, idx %d",
__func__, con->se_id, table->conf.name, host->conf.name,
p, idx);
}
while (host != NULL) {
DPRINTF("%s: session %d: host %s", __func__,
con->se_id, host->conf.name);
if (!table->conf.check || host->up == HOST_UP)
goto found;
host = TAILQ_NEXT(host, entry);
}
TAILQ_FOREACH(host, &table->hosts, entry) {
DPRINTF("%s: session %d: next host %s",
__func__, con->se_id, host->conf.name);
if (!table->conf.check || host->up == HOST_UP)
goto found;
}
/* Should not happen */
fatalx("relay_from_table: no active hosts, desynchronized");
found:
if (rlt->rlt_mode == RELAY_DSTMODE_ROUNDROBIN)
rlt->rlt_index = host->idx + 1;
con->se_retry = host->conf.retry;
con->se_out.port = table->conf.port;
bcopy(&host->conf.ss, &con->se_out.ss, sizeof(con->se_out.ss));
return (0);
}
void
relay_natlook(int fd, short event, void *arg)
{
struct rsession *con = arg;
struct relay *rlay = con->se_relay;
struct ctl_natlook *cnl = con->se_cnl;
if (cnl == NULL)
fatalx("invalid NAT lookup");
if (con->se_out.ss.ss_family == AF_UNSPEC && cnl->in == -1 &&
rlay->rl_conf.dstss.ss_family == AF_UNSPEC &&
TAILQ_EMPTY(&rlay->rl_tables)) {
relay_close(con, "session NAT lookup failed");
return;
}
if (cnl->in != -1) {
bcopy(&cnl->rdst, &con->se_out.ss, sizeof(con->se_out.ss));
con->se_out.port = cnl->rdport;
}
free(con->se_cnl);
con->se_cnl = NULL;
relay_session(con);
}
void
relay_session(struct rsession *con)
{
struct relay *rlay = con->se_relay;
struct ctl_relay_event *in = &con->se_in, *out = &con->se_out;
if (bcmp(&rlay->rl_conf.ss, &out->ss, sizeof(out->ss)) == 0 &&
out->port == rlay->rl_conf.port) {
log_debug("%s: session %d: looping", __func__, con->se_id);
relay_close(con, "session aborted");
return;
}
if (rlay->rl_conf.flags & F_UDP) {
/*
* Call the UDP protocol-specific handler
*/
if (rlay->rl_proto->request == NULL)
fatalx("invalide UDP session");
if ((*rlay->rl_proto->request)(con) == -1)
relay_close(con, "session failed");
return;
}
if ((rlay->rl_conf.flags & F_TLS) && (in->ssl == NULL)) {
relay_tls_transaction(con, in);
return;
}
if (rlay->rl_proto->type != RELAY_PROTO_HTTP) {
if (rlay->rl_conf.fwdmode == FWD_TRANS)
relay_bindanyreq(con, 0, IPPROTO_TCP);
else if (relay_connect(con) == -1) {
relay_close(con, "session failed");
return;
}
}
relay_input(con);
}
void
relay_bindanyreq(struct rsession *con, in_port_t port, int proto)
{
struct privsep *ps = env->sc_ps;
struct relay *rlay = con->se_relay;
struct ctl_bindany bnd;
struct timeval tv;
bzero(&bnd, sizeof(bnd));
bnd.bnd_id = con->se_id;
bnd.bnd_proc = ps->ps_instance;
bnd.bnd_port = port;
bnd.bnd_proto = proto;
bcopy(&con->se_in.ss, &bnd.bnd_ss, sizeof(bnd.bnd_ss));
proc_compose(env->sc_ps, PROC_PARENT, IMSG_BINDANY,
&bnd, sizeof(bnd));
/* Schedule timeout */
evtimer_set(&con->se_ev, relay_bindany, con);
bcopy(&rlay->rl_conf.timeout, &tv, sizeof(tv));
evtimer_add(&con->se_ev, &tv);
}
void
relay_bindany(int fd, short event, void *arg)
{
struct rsession *con = arg;
if (con->se_bnds == -1) {
relay_close(con, "bindany failed, invalid socket");
return;
}
if (relay_connect(con) == -1)
relay_close(con, "session failed");
}
void
relay_connect_retry(int fd, short sig, void *arg)
{
struct timeval evtpause = { 1, 0 };
struct rsession *con = arg;
struct relay *rlay = con->se_relay;
int bnds = -1;
if (relay_inflight < 1) {
log_warnx("relay_connect_retry: no connection in flight");
relay_inflight = 1;
}
DPRINTF("%s: retry %d of %d, inflight: %d",__func__,
con->se_retrycount, con->se_retry, relay_inflight);
if (sig != EV_TIMEOUT)
fatalx("relay_connect_retry: called without timeout");
evtimer_del(&con->se_inflightevt);
/*
* XXX we might want to check if the inbound socket is still
* available: client could have closed it while we were waiting?
*/
DPRINTF("%s: got EV_TIMEOUT", __func__);
if (getdtablecount() + FD_RESERVE +
relay_inflight > getdtablesize()) {
if (con->se_retrycount < RELAY_OUTOF_FD_RETRIES) {
evtimer_add(&con->se_inflightevt, &evtpause);
return;
}
/* we waited for RELAY_OUTOF_FD_RETRIES seconds, give up */
event_add(&rlay->rl_ev, NULL);
relay_abort_http(con, 504, "connection timed out", 0);
return;
}
if (rlay->rl_conf.fwdmode == FWD_TRANS) {
/* con->se_bnds cannot be unset */
bnds = con->se_bnds;
}
retry:
if ((con->se_out.s = relay_socket_connect(&con->se_out.ss,
con->se_out.port, rlay->rl_proto, bnds)) == -1) {
log_debug("%s: session %d: "
"forward failed: %s, %s", __func__,
con->se_id, strerror(errno),
con->se_retry ? "next retry" : "last retry");
con->se_retrycount++;
if ((errno == ENFILE || errno == EMFILE) &&
(con->se_retrycount < con->se_retry)) {
event_del(&rlay->rl_ev);
evtimer_add(&con->se_inflightevt, &evtpause);
evtimer_add(&rlay->rl_evt, &evtpause);
return;
} else if (con->se_retrycount < con->se_retry)
goto retry;
event_add(&rlay->rl_ev, NULL);
relay_abort_http(con, 504, "connect failed", 0);
return;
}
if (rlay->rl_conf.flags & F_TLSINSPECT)
con->se_out.state = STATE_PRECONNECT;
else
con->se_out.state = STATE_CONNECTED;
relay_inflight--;
DPRINTF("%s: inflight decremented, now %d",__func__, relay_inflight);
event_add(&rlay->rl_ev, NULL);
if (errno == EINPROGRESS)
event_again(&con->se_ev, con->se_out.s, EV_WRITE|EV_TIMEOUT,
relay_connected, &con->se_tv_start, &rlay->rl_conf.timeout,
con);
else
relay_connected(con->se_out.s, EV_WRITE, con);
return;
}
int
relay_preconnect(struct rsession *con)
{
int rv;
log_debug("%s: session %d: process %d", __func__,
con->se_id, privsep_process);
rv = relay_connect(con);
if (con->se_out.state == STATE_CONNECTED)
con->se_out.state = STATE_PRECONNECT;
return (rv);
}
int
relay_connect(struct rsession *con)
{
struct relay *rlay = con->se_relay;
struct timeval evtpause = { 1, 0 };
int bnds = -1, ret;
/* relay_connect should only be called once per relay */
if (con->se_out.state == STATE_CONNECTED) {
log_debug("%s: connect already called once", __func__);
return (0);
}
/* Connection is already established but session not active */
if ((rlay->rl_conf.flags & F_TLSINSPECT) &&
con->se_out.state == STATE_PRECONNECT) {
if (con->se_out.ssl == NULL) {
log_debug("%s: tls connect failed", __func__);
return (-1);
}
relay_connected(con->se_out.s, EV_WRITE, con);
con->se_out.state = STATE_CONNECTED;
return (0);
}
if (relay_inflight < 1) {
log_warnx("relay_connect: no connection in flight");
relay_inflight = 1;
}
getmonotime(&con->se_tv_start);
if (!TAILQ_EMPTY(&rlay->rl_tables)) {
if (relay_from_table(con) != 0)
return (-1);
} else if (con->se_out.ss.ss_family == AF_UNSPEC) {
bcopy(&rlay->rl_conf.dstss, &con->se_out.ss,
sizeof(con->se_out.ss));
con->se_out.port = rlay->rl_conf.dstport;
}
if (rlay->rl_conf.fwdmode == FWD_TRANS) {
if (con->se_bnds == -1) {
log_debug("%s: could not bind any sock", __func__);
return (-1);
}
bnds = con->se_bnds;
}
/* Do the IPv4-to-IPv6 or IPv6-to-IPv4 translation if requested */
if (rlay->rl_conf.dstaf.ss_family != AF_UNSPEC) {
if (con->se_out.ss.ss_family == AF_INET &&
rlay->rl_conf.dstaf.ss_family == AF_INET6)
ret = map4to6(&con->se_out.ss, &rlay->rl_conf.dstaf);
else if (con->se_out.ss.ss_family == AF_INET6 &&
rlay->rl_conf.dstaf.ss_family == AF_INET)
ret = map6to4(&con->se_out.ss);
else
ret = 0;
if (ret != 0) {
log_debug("%s: mapped to invalid address", __func__);
return (-1);
}
}
retry:
if ((con->se_out.s = relay_socket_connect(&con->se_out.ss,
con->se_out.port, rlay->rl_proto, bnds)) == -1) {
if (errno == ENFILE || errno == EMFILE) {
log_debug("%s: session %d: forward failed: %s",
__func__, con->se_id, strerror(errno));
evtimer_set(&con->se_inflightevt, relay_connect_retry,
con);
event_del(&rlay->rl_ev);
evtimer_add(&con->se_inflightevt, &evtpause);
evtimer_add(&rlay->rl_evt, &evtpause);
/* this connect is pending */
con->se_out.state = STATE_PENDING;
return (0);
} else {
if (con->se_retry) {
con->se_retry--;
log_debug("%s: session %d: "
"forward failed: %s, %s", __func__,
con->se_id, strerror(errno),
con->se_retry ?
"next retry" : "last retry");
goto retry;
}
log_debug("%s: session %d: forward failed: %s",
__func__, con->se_id, strerror(errno));
return (-1);
}
}
con->se_out.state = STATE_CONNECTED;
relay_inflight--;
DPRINTF("%s: inflight decremented, now %d",__func__,
relay_inflight);
if (errno == EINPROGRESS)
event_again(&con->se_ev, con->se_out.s, EV_WRITE|EV_TIMEOUT,
relay_connected, &con->se_tv_start, &rlay->rl_conf.timeout,
con);
else
relay_connected(con->se_out.s, EV_WRITE, con);
return (0);
}
void
relay_close(struct rsession *con, const char *msg)
{
char ibuf[128], obuf[128], *ptr = NULL;
struct relay *rlay = con->se_relay;
struct protocol *proto = rlay->rl_proto;
SPLAY_REMOVE(session_tree, &rlay->rl_sessions, con);
relay_session_unpublish(con);
event_del(&con->se_ev);
if (con->se_in.bev != NULL)
bufferevent_disable(con->se_in.bev, EV_READ|EV_WRITE);
if (con->se_out.bev != NULL)
bufferevent_disable(con->se_out.bev, EV_READ|EV_WRITE);
if ((env->sc_conf.opts & RELAYD_OPT_LOGUPDATE) && msg != NULL) {
bzero(&ibuf, sizeof(ibuf));
bzero(&obuf, sizeof(obuf));
(void)print_host(&con->se_in.ss, ibuf, sizeof(ibuf));
(void)print_host(&con->se_out.ss, obuf, sizeof(obuf));
if (EVBUFFER_LENGTH(con->se_log) &&
evbuffer_add_printf(con->se_log, "\r\n") != -1)
ptr = evbuffer_readline(con->se_log);
log_info("relay %s, "
"session %d (%d active), %s, %s -> %s:%d, "
"%s%s%s", rlay->rl_conf.name, con->se_id, relay_sessions,
con->se_tag != 0 ? tag_id2name(con->se_tag) : "0", ibuf,
obuf, ntohs(con->se_out.port), msg, ptr == NULL ? "" : ",",
ptr == NULL ? "" : ptr);
free(ptr);
}
if (proto->close != NULL)
(*proto->close)(con);
free(con->se_priv);
if (con->se_in.bev != NULL)
bufferevent_free(con->se_in.bev);
else if (con->se_in.output != NULL)
evbuffer_free(con->se_in.output);
if (con->se_in.ssl != NULL) {
/* XXX handle non-blocking shutdown */
if (SSL_shutdown(con->se_in.ssl) == 0)
SSL_shutdown(con->se_in.ssl);
SSL_free(con->se_in.ssl);
}
if (con->se_in.tlscert != NULL)
X509_free(con->se_in.tlscert);
if (con->se_in.s != -1) {
close(con->se_in.s);
if (con->se_out.s == -1) {
/*
* the output was never connected,
* thus this was an inflight session.
*/
relay_inflight--;
log_debug("%s: sessions inflight decremented, now %d",
__func__, relay_inflight);
}
}
free(con->se_in.buf);
if (con->se_out.bev != NULL)
bufferevent_free(con->se_out.bev);
else if (con->se_out.output != NULL)
evbuffer_free(con->se_out.output);
if (con->se_out.ssl != NULL) {
/* XXX handle non-blocking shutdown */
if (SSL_shutdown(con->se_out.ssl) == 0)
SSL_shutdown(con->se_out.ssl);
SSL_free(con->se_out.ssl);
}
if (con->se_out.tlscert != NULL)
X509_free(con->se_out.tlscert);
if (con->se_out.s != -1) {
close(con->se_out.s);
/* Some file descriptors are available again. */
if (evtimer_pending(&rlay->rl_evt, NULL)) {
evtimer_del(&rlay->rl_evt);
event_add(&rlay->rl_ev, NULL);
}
}
con->se_out.state = STATE_INIT;
free(con->se_out.buf);
if (con->se_log != NULL)
evbuffer_free(con->se_log);
if (con->se_cnl != NULL) {
#if 0
proc_compose_imsg(env->sc_ps, PROC_PFE, -1, IMSG_KILLSTATES, -1,
cnl, sizeof(*cnl));
#endif
free(con->se_cnl);
}
free(con);
relay_sessions--;
}
int
relay_dispatch_pfe(int fd, struct privsep_proc *p, struct imsg *imsg)
{
struct relay *rlay;
struct rsession *con, se;
struct ctl_natlook cnl;
struct timeval tv;
struct host *host;
struct table *table;
struct ctl_status st;
objid_t id;
int cid;
switch (imsg->hdr.type) {
case IMSG_HOST_DISABLE:
memcpy(&id, imsg->data, sizeof(id));
if ((host = host_find(env, id)) == NULL)
fatalx("relay_dispatch_pfe: desynchronized");
if ((table = table_find(env, host->conf.tableid)) ==
NULL)
fatalx("relay_dispatch_pfe: invalid table id");
if (host->up == HOST_UP)
table->up--;
host->flags |= F_DISABLE;
host->up = HOST_UNKNOWN;
break;
case IMSG_HOST_ENABLE:
memcpy(&id, imsg->data, sizeof(id));
if ((host = host_find(env, id)) == NULL)
fatalx("relay_dispatch_pfe: desynchronized");
host->flags &= ~(F_DISABLE);
host->up = HOST_UNKNOWN;
break;
case IMSG_TABLE_DISABLE:
memcpy(&id, imsg->data, sizeof(id));
if ((table = table_find(env, id)) == NULL)
fatalx("relay_dispatch_pfe: desynchronized");
table->conf.flags |= F_DISABLE;
table->up = 0;
TAILQ_FOREACH(host, &table->hosts, entry)
host->up = HOST_UNKNOWN;
break;
case IMSG_TABLE_ENABLE:
memcpy(&id, imsg->data, sizeof(id));
if ((table = table_find(env, id)) == NULL)
fatalx("relay_dispatch_pfe: desynchronized");
table->conf.flags &= ~(F_DISABLE);
table->up = 0;
TAILQ_FOREACH(host, &table->hosts, entry)
host->up = HOST_UNKNOWN;
break;
case IMSG_HOST_STATUS:
IMSG_SIZE_CHECK(imsg, &st);
memcpy(&st, imsg->data, sizeof(st));
if ((host = host_find(env, st.id)) == NULL)
fatalx("relay_dispatch_pfe: invalid host id");
if (host->flags & F_DISABLE)
break;
if (host->up == st.up) {
log_debug("%s: host %d => %d", __func__,
host->conf.id, host->up);
fatalx("relay_dispatch_pfe: desynchronized");
}
if ((table = table_find(env, host->conf.tableid))
== NULL)
fatalx("relay_dispatch_pfe: invalid table id");
DPRINTF("%s: [%d] state %d for "
"host %u %s", __func__, p->p_ps->ps_instance, st.up,
host->conf.id, host->conf.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->se_cnl == NULL) {
log_debug("%s: session %d: expired",
__func__, cnl.id);
break;
}
bcopy(&cnl, con->se_cnl, sizeof(*con->se_cnl));
evtimer_del(&con->se_ev);
evtimer_set(&con->se_ev, relay_natlook, con);
bzero(&tv, sizeof(tv));
evtimer_add(&con->se_ev, &tv);
break;
case IMSG_CTL_SESSION:
IMSG_SIZE_CHECK(imsg, &cid);
memcpy(&cid, imsg->data, sizeof(cid));
TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
SPLAY_FOREACH(con, session_tree,
&rlay->rl_sessions) {
memcpy(&se, con, sizeof(se));
se.se_cid = cid;
proc_compose(env->sc_ps, p->p_id,
IMSG_CTL_SESSION, &se, sizeof(se));
}
}
proc_compose(env->sc_ps, p->p_id, IMSG_CTL_END,
&cid, sizeof(cid));
break;
default:
return (-1);
}
return (0);
}
int
relay_dispatch_ca(int fd, struct privsep_proc *p, struct imsg *imsg)
{
return (-1);
}
int
relay_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
{
struct rsession *con;
struct timeval tv;
objid_t id;
switch (imsg->hdr.type) {
case IMSG_BINDANY:
bcopy(imsg->data, &id, sizeof(id));
if ((con = session_find(env, id)) == NULL) {
log_debug("%s: session %d: expired",
__func__, id);
break;
}
/* Will validate the result later */
con->se_bnds = imsg->fd;
evtimer_del(&con->se_ev);
evtimer_set(&con->se_ev, relay_bindany, con);
bzero(&tv, sizeof(tv));
evtimer_add(&con->se_ev, &tv);
break;
case IMSG_CFG_TABLE:
config_gettable(env, imsg);
break;
case IMSG_CFG_HOST:
config_gethost(env, imsg);
break;
case IMSG_CFG_PROTO:
config_getproto(env, imsg);
break;
case IMSG_CFG_RULE:
config_getrule(env, imsg);
break;
case IMSG_CFG_RELAY:
config_getrelay(env, imsg);
break;
case IMSG_CFG_RELAY_TABLE:
config_getrelaytable(env, imsg);
break;
case IMSG_CFG_DONE:
config_getcfg(env, imsg);
break;
case IMSG_CTL_START:
relay_launch();
break;
case IMSG_CTL_RESET:
config_getreset(env, imsg);
break;
case IMSG_TLSTICKET_REKEY:
IMSG_SIZE_CHECK(imsg, (&env->sc_tls_ticket));
/* rotate keys */
memcpy(&env->sc_tls_ticket_bak, &env->sc_tls_ticket,
sizeof(env->sc_tls_ticket));
env->sc_tls_ticket_bak.tt_backup = 1;
memcpy(&env->sc_tls_ticket, imsg->data,
sizeof(env->sc_tls_ticket));
break;
default:
return (-1);
}
return (0);
}
DH *
relay_tls_get_dhparams(int keylen)
{
DH *dh;
BIGNUM *(*prime)(BIGNUM *);
const char *gen;
gen = "2";
if (keylen >= 8192)
prime = get_rfc3526_prime_8192;
else if (keylen >= 4096)
prime = get_rfc3526_prime_4096;
else if (keylen >= 3072)
prime = get_rfc3526_prime_3072;
else if (keylen >= 2048)
prime = get_rfc3526_prime_2048;
else if (keylen >= 1536)
prime = get_rfc3526_prime_1536;
else
prime = get_rfc2409_prime_1024;
if ((dh = DH_new()) == NULL)
return (NULL);
dh->p = (*prime)(NULL);
BN_dec2bn(&dh->g, gen);
if (dh->p == NULL || dh->g == NULL) {
DH_free(dh);
return (NULL);
}
return (dh);
}
DH *
relay_tls_callback_dh(SSL *ssl, int export, int keylen)
{
struct ctl_relay_event *cre;
EVP_PKEY *pkey;
int keytype, maxlen;
DH *dh = NULL;
/* Get maximum key length from config */
if ((cre = (struct ctl_relay_event *)SSL_get_app_data(ssl)) == NULL)
return (NULL);
maxlen = cre->con->se_relay->rl_proto->tlsdhparams;
/* Get the private key length from the cert */
if ((pkey = SSL_get_privatekey(ssl))) {
keytype = EVP_PKEY_type(pkey->type);
if (keytype == EVP_PKEY_RSA || keytype == EVP_PKEY_DSA)
keylen = EVP_PKEY_bits(pkey);
else
return (NULL);
}
/* get built-in params based on the shorter key length */
dh = relay_tls_get_dhparams(MINIMUM(keylen, maxlen));
return (dh);
}
int
relay_dispatch_hce(int fd, struct privsep_proc *p, struct imsg *imsg)
{
switch (imsg->hdr.type) {
default:
break;
}
return (-1);
}
SSL_CTX *
relay_tls_ctx_create(struct relay *rlay)
{
struct protocol *proto = rlay->rl_proto;
SSL_CTX *ctx;
EC_KEY *ecdhkey;
ctx = SSL_CTX_new(SSLv23_method());
if (ctx == NULL)
goto err;
/*
* Disable the session cache by default.
* Everything modern uses tickets
*/
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
/* Set callback for TLS session tickets if enabled */
if (proto->tickets == -1)
SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
else {
if (!SSL_CTX_set_tlsext_ticket_key_cb(ctx,
relay_tls_session_ticket))
log_warnx("could not set the TLS ticket callback");
/* set timeout to the ticket rekey time */
SSL_CTX_set_timeout(ctx, TLS_TICKET_REKEY_TIME);
}
/* Enable all workarounds and set SSL options */
SSL_CTX_set_options(ctx, SSL_OP_ALL);
SSL_CTX_set_options(ctx,
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
if (proto->tlsflags & TLSFLAG_CIPHER_SERVER_PREF)
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
if ((proto->tlsflags & TLSFLAG_CLIENT_RENEG) == 0)
SSL_CTX_set_options(ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
/* Set the allowed SSL protocols */
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
SSL_CTX_clear_options(ctx, SSL_OP_NO_SSLv3);
if ((proto->tlsflags & TLSFLAG_SSLV3) == 0)
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
SSL_CTX_clear_options(ctx, SSL_OP_NO_TLSv1);
if ((proto->tlsflags & TLSFLAG_TLSV1_0) == 0)
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
SSL_CTX_clear_options(ctx, SSL_OP_NO_TLSv1_1);
if ((proto->tlsflags & TLSFLAG_TLSV1_1) == 0)
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
SSL_CTX_clear_options(ctx, SSL_OP_NO_TLSv1_2);
if ((proto->tlsflags & TLSFLAG_TLSV1_2) == 0)
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
if (proto->tlsecdhcurve > 0) {
/* Enable ECDHE support for TLS perfect forward secrecy */
if ((ecdhkey =
EC_KEY_new_by_curve_name(proto->tlsecdhcurve)) == NULL)
goto err;
SSL_CTX_set_tmp_ecdh(ctx, ecdhkey);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
EC_KEY_free(ecdhkey);
}
if (proto->tlsdhparams > 0) {
/* Enable EDH params (forward secrecy for older clients) */
SSL_CTX_set_tmp_dh_callback(ctx, relay_tls_callback_dh);
}
if (!SSL_CTX_set_cipher_list(ctx, proto->tlsciphers))
goto err;
/* Verify the server certificate if we have a CA chain */
if ((rlay->rl_conf.flags & F_TLSCLIENT) &&
(rlay->rl_tls_ca != NULL)) {
if (!SSL_CTX_load_verify_mem(ctx,
rlay->rl_tls_ca, rlay->rl_conf.tls_ca_len))
goto err;
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
}
if ((rlay->rl_conf.flags & F_TLS) == 0)
return (ctx);
log_debug("%s: loading certificate", __func__);
if (!SSL_CTX_use_certificate_chain_mem(ctx,
rlay->rl_tls_cert, rlay->rl_conf.tls_cert_len))
goto err;
log_debug("%s: loading private key", __func__);
if (!ssl_ctx_fake_private_key(ctx,
&rlay->rl_conf.tls_keyid, sizeof(rlay->rl_conf.tls_keyid),
rlay->rl_tls_cert, rlay->rl_conf.tls_cert_len,
&rlay->rl_tls_x509, &rlay->rl_tls_pkey))
goto err;
if (!SSL_CTX_check_private_key(ctx))
goto err;
if (rlay->rl_conf.tls_cacert_len) {
log_debug("%s: loading CA private key", __func__);
if (!ssl_load_pkey(&rlay->rl_conf.tls_cakeyid,
sizeof(rlay->rl_conf.tls_cakeyid),
rlay->rl_tls_cacert, rlay->rl_conf.tls_cacert_len,
&rlay->rl_tls_cacertx509, &rlay->rl_tls_capkey))
goto err;
}
/*
* Set session ID context to a random value. It needs to be the
* same accross all relay processes or session caching will fail.
*/
if (!SSL_CTX_set_session_id_context(ctx, env->sc_conf.tls_sid,
sizeof(env->sc_conf.tls_sid)))
goto err;
/* The text versions of the keys/certs are not needed anymore */
purge_key(&rlay->rl_tls_cert, rlay->rl_conf.tls_cert_len);
purge_key(&rlay->rl_tls_cacert, rlay->rl_conf.tls_cacert_len);
return (ctx);
err:
SSL_CTX_free(ctx);
ssl_error(rlay->rl_conf.name, "relay_tls_ctx_create");
return (NULL);
}
void
relay_tls_transaction(struct rsession *con, struct ctl_relay_event *cre)
{
struct relay *rlay = con->se_relay;
SSL *ssl;
const SSL_METHOD *method;
void (*cb)(int, short, void *);
u_int flag;
ssl = SSL_new(rlay->rl_ssl_ctx);
if (ssl == NULL)
goto err;
if (cre->dir == RELAY_DIR_REQUEST) {
cb = relay_tls_accept;
method = SSLv23_server_method();
flag = EV_READ;
/* Use session-specific certificate for TLS inspection. */
if (cre->tlscert != NULL)
SSL_use_certificate(ssl, cre->tlscert);
} else {
cb = relay_tls_connect;
method = SSLv23_client_method();
flag = EV_WRITE;
}
if (!SSL_set_ssl_method(ssl, method))
goto err;
if (!SSL_set_fd(ssl, cre->s))
goto err;
if (cre->dir == RELAY_DIR_REQUEST)
SSL_set_accept_state(ssl);
else
SSL_set_connect_state(ssl);
SSL_set_app_data(ssl, cre);
cre->ssl = ssl;
DPRINTF("%s: session %d: scheduling on %s", __func__, con->se_id,
(flag == EV_READ) ? "EV_READ" : "EV_WRITE");
event_again(&con->se_ev, cre->s, EV_TIMEOUT|flag, cb,
&con->se_tv_start, &rlay->rl_conf.timeout, con);
return;
err:
SSL_free(ssl);
ssl_error(rlay->rl_conf.name, "relay_tls_transaction");
relay_close(con, "session tls failed");
}
void
relay_tls_accept(int fd, short event, void *arg)
{
struct rsession *con = arg;
struct relay *rlay = con->se_relay;
int retry_flag = 0;
int tls_err = 0;
int ret;
if (event == EV_TIMEOUT) {
relay_close(con, "TLS accept timeout");
return;
}
ret = SSL_accept(con->se_in.ssl);
if (ret <= 0) {
tls_err = SSL_get_error(con->se_in.ssl, ret);
switch (tls_err) {
case SSL_ERROR_WANT_READ:
retry_flag = EV_READ;
goto retry;
case SSL_ERROR_WANT_WRITE:
retry_flag = EV_WRITE;
goto retry;
case SSL_ERROR_ZERO_RETURN:
case SSL_ERROR_SYSCALL:
if (ret == 0) {
relay_close(con, "closed");
return;
}
/* FALLTHROUGH */
default:
ssl_error(rlay->rl_conf.name, "relay_tls_accept");
relay_close(con, "TLS accept error");
return;
}
}
#ifdef DEBUG
log_info(
#else
log_debug(
#endif
"relay %s, session %d established (%d active)",
rlay->rl_conf.name, con->se_id, relay_sessions);
relay_session(con);
return;
retry:
DPRINTF("%s: session %d: scheduling on %s", __func__, con->se_id,
(retry_flag == EV_READ) ? "EV_READ" : "EV_WRITE");
event_again(&con->se_ev, fd, EV_TIMEOUT|retry_flag, relay_tls_accept,
&con->se_tv_start, &rlay->rl_conf.timeout, con);
}
void
relay_tls_connect(int fd, short event, void *arg)
{
struct rsession *con = arg;
struct relay *rlay = con->se_relay;
int retry_flag = 0;
int tls_err = 0;
int ret;
X509 *servercert = NULL;
if (event == EV_TIMEOUT) {
relay_close(con, "TLS connect timeout");
return;
}
ret = SSL_connect(con->se_out.ssl);
if (ret <= 0) {
tls_err = SSL_get_error(con->se_out.ssl, ret);
switch (tls_err) {
case SSL_ERROR_WANT_READ:
retry_flag = EV_READ;
goto retry;
case SSL_ERROR_WANT_WRITE:
retry_flag = EV_WRITE;
goto retry;
case SSL_ERROR_ZERO_RETURN:
case SSL_ERROR_SYSCALL:
if (ret == 0) {
relay_close(con, "closed");
return;
}
/* FALLTHROUGH */
default:
ssl_error(rlay->rl_conf.name, "relay_tls_connect");
relay_close(con, "TLS connect error");
return;
}
}
#ifdef DEBUG
log_info(
#else
log_debug(
#endif
"relay %s, tls session %d connected (%d active)",
rlay->rl_conf.name, con->se_id, relay_sessions);
if (rlay->rl_conf.flags & F_TLSINSPECT) {
if ((servercert =
SSL_get_peer_certificate(con->se_out.ssl)) != NULL) {
con->se_in.tlscert =
ssl_update_certificate(servercert,
rlay->rl_tls_pkey, rlay->rl_tls_capkey,
rlay->rl_tls_cacertx509);
} else
con->se_in.tlscert = NULL;
if (servercert != NULL)
X509_free(servercert);
if (con->se_in.tlscert == NULL)
relay_close(con, "could not create certificate");
else
relay_session(con);
return;
}
relay_connected(fd, EV_WRITE, con);
return;
retry:
DPRINTF("%s: session %d: scheduling on %s", __func__, con->se_id,
(retry_flag == EV_READ) ? "EV_READ" : "EV_WRITE");
event_again(&con->se_ev, fd, EV_TIMEOUT|retry_flag, relay_tls_connect,
&con->se_tv_start, &rlay->rl_conf.timeout, con);
}
void
relay_tls_connected(struct ctl_relay_event *cre)
{
/*
* Hack libevent - we overwrite the internal bufferevent I/O
* functions to handle the TLS abstraction.
*/
event_set(&cre->bev->ev_read, cre->s, EV_READ,
relay_tls_readcb, cre->bev);
event_set(&cre->bev->ev_write, cre->s, EV_WRITE,
relay_tls_writecb, cre->bev);
}
void
relay_tls_readcb(int fd, short event, void *arg)
{
char rbuf[IBUF_READ_SIZE];
struct bufferevent *bufev = arg;
struct ctl_relay_event *cre = bufev->cbarg;
struct rsession *con = cre->con;
struct relay *rlay = con->se_relay;
int ret = 0, tls_err = 0;
short what = EVBUFFER_READ;
int howmuch = IBUF_READ_SIZE;
size_t len;
if (event == EV_TIMEOUT) {
what |= EVBUFFER_TIMEOUT;
goto err;
}
if (bufev->wm_read.high != 0)
howmuch = MINIMUM(sizeof(rbuf), bufev->wm_read.high);
ret = SSL_read(cre->ssl, rbuf, howmuch);
if (ret <= 0) {
tls_err = SSL_get_error(cre->ssl, ret);
switch (tls_err) {
case SSL_ERROR_WANT_READ:
DPRINTF("%s: session %d: want read",
__func__, con->se_id);
goto retry;
case SSL_ERROR_WANT_WRITE:
DPRINTF("%s: session %d: want write",
__func__, con->se_id);
goto retry;
default:
if (ret == 0)
what |= EVBUFFER_EOF;
else {
ssl_error(rlay->rl_conf.name,
"relay_tls_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_tls_writecb(int fd, short event, void *arg)
{
struct bufferevent *bufev = arg;
struct ctl_relay_event *cre = bufev->cbarg;
struct rsession *con = cre->con;
struct relay *rlay = con->se_relay;
int ret = 0, tls_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) {
tls_err = SSL_get_error(cre->ssl, ret);
switch (tls_err) {
case SSL_ERROR_WANT_READ:
DPRINTF("%s: session %d: want read",
__func__, con->se_id);
goto retry;
case SSL_ERROR_WANT_WRITE:
DPRINTF("%s: session %d: want write",
__func__, con->se_id);
goto retry;
default:
if (ret == 0)
what |= EVBUFFER_EOF;
else {
ssl_error(rlay->rl_conf.name,
"relay_tls_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);
}
struct tls_ticket *
relay_get_ticket_key(unsigned char *keyname)
{
if (keyname) {
if (timingsafe_memcmp(keyname,
env->sc_tls_ticket_bak.tt_key_name,
sizeof(env->sc_tls_ticket_bak.tt_key_name)) == 0)
return &env->sc_tls_ticket_bak;
if (timingsafe_memcmp(keyname,
env->sc_tls_ticket.tt_key_name,
sizeof(env->sc_tls_ticket.tt_key_name)) == 0)
return &env->sc_tls_ticket;
return NULL;
}
return &env->sc_tls_ticket;
}
int
relay_tls_session_ticket(SSL *ssl, unsigned char *keyname, unsigned char *iv,
EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int mode)
{
struct tls_ticket *key;
if (mode == 1) {
/* create new session */
key = relay_get_ticket_key(NULL);
memcpy(keyname, key->tt_key_name, sizeof(key->tt_key_name));
arc4random_buf(iv, EVP_MAX_IV_LENGTH);
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL,
key->tt_aes_key, iv);
HMAC_Init_ex(hctx, key->tt_hmac_key, sizeof(key->tt_hmac_key),
EVP_sha256(), NULL);
return 0;
} else {
/* get key by name */
key = relay_get_ticket_key(keyname);
if (!key)
return 0;
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL,
key->tt_aes_key, iv);
HMAC_Init_ex(hctx, key->tt_hmac_key, sizeof(key->tt_hmac_key),
EVP_sha256(), NULL);
/* time to renew the ticket? */
if (key->tt_backup) {
return 2;
}
return 1;
}
}
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, const 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_chunk(struct ctl_relay_event *cre,
struct evbuffer *buf, size_t size)
{
int ret;
ret = relay_bufferevent_write(cre, buf->buffer, size);
if (ret != -1)
evbuffer_drain(buf, size);
return (ret);
}
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));
}
int
relay_cmp_af(struct sockaddr_storage *a, struct sockaddr_storage *b)
{
int ret = -1;
struct sockaddr_in ia, ib;
struct sockaddr_in6 ia6, ib6;
switch (a->ss_family) {
case AF_INET:
bcopy(a, &ia, sizeof(struct sockaddr_in));
bcopy(b, &ib, sizeof(struct sockaddr_in));
ret = memcmp(&ia.sin_addr, &ib.sin_addr,
sizeof(ia.sin_addr));
if (ret == 0)
ret = memcmp(&ia.sin_port, &ib.sin_port,
sizeof(ia.sin_port));
break;
case AF_INET6:
bcopy(a, &ia6, sizeof(struct sockaddr_in6));
bcopy(b, &ib6, sizeof(struct sockaddr_in6));
ret = memcmp(&ia6.sin6_addr, &ib6.sin6_addr,
sizeof(ia6.sin6_addr));
if (ret == 0)
ret = memcmp(&ia6.sin6_port, &ib6.sin6_port,
sizeof(ia6.sin6_port));
break;
default:
break;
}
return (ret);
}
char *
relay_load_file(const char *name, off_t *len)
{
struct stat st;
off_t size;
u_int8_t *buf = NULL;
int fd;
if ((fd = open(name, O_RDONLY)) == -1)
return (NULL);
if (fstat(fd, &st) != 0)
goto fail;
size = st.st_size;
if ((buf = calloc(1, size + 1)) == NULL)
goto fail;
if (read(fd, buf, size) != size)
goto fail;
close(fd);
*len = size;
return (buf);
fail:
free(buf);
close(fd);
return (NULL);
}
int
relay_load_certfiles(struct relay *rlay)
{
char certfile[PATH_MAX];
char hbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
struct protocol *proto = rlay->rl_proto;
int useport = htons(rlay->rl_conf.port);
if (rlay->rl_conf.flags & F_TLSCLIENT) {
if (strlen(proto->tlsca)) {
if ((rlay->rl_tls_ca =
relay_load_file(proto->tlsca,
&rlay->rl_conf.tls_ca_len)) == NULL)
return (-1);
log_debug("%s: using ca %s", __func__, proto->tlsca);
}
if (strlen(proto->tlscacert)) {
if ((rlay->rl_tls_cacert =
relay_load_file(proto->tlscacert,
&rlay->rl_conf.tls_cacert_len)) == NULL)
return (-1);
log_debug("%s: using ca certificate %s", __func__,
proto->tlscacert);
}
if (strlen(proto->tlscakey) && proto->tlscapass != NULL) {
if ((rlay->rl_tls_cakey =
ssl_load_key(env, proto->tlscakey,
&rlay->rl_conf.tls_cakey_len,
proto->tlscapass)) == NULL)
return (-1);
log_debug("%s: using ca key %s", __func__,
proto->tlscakey);
}
}
if ((rlay->rl_conf.flags & F_TLS) == 0)
return (0);
if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
return (-1);
if (snprintf(certfile, sizeof(certfile),
"/etc/ssl/%s:%u.crt", hbuf, useport) == -1)
return (-1);
if ((rlay->rl_tls_cert = relay_load_file(certfile,
&rlay->rl_conf.tls_cert_len)) == NULL) {
if (snprintf(certfile, sizeof(certfile),
"/etc/ssl/%s.crt", hbuf) == -1)
return (-1);
if ((rlay->rl_tls_cert = relay_load_file(certfile,
&rlay->rl_conf.tls_cert_len)) == NULL)
return (-1);
useport = 0;
}
log_debug("%s: using certificate %s", __func__, certfile);
if (useport) {
if (snprintf(certfile, sizeof(certfile),
"/etc/ssl/private/%s:%u.key", hbuf, useport) == -1)
return -1;
} else {
if (snprintf(certfile, sizeof(certfile),
"/etc/ssl/private/%s.key", hbuf) == -1)
return -1;
}
if ((rlay->rl_tls_key = ssl_load_key(env, certfile,
&rlay->rl_conf.tls_key_len, NULL)) == NULL)
return (-1);
log_debug("%s: using private key %s", __func__, certfile);
return (0);
}
int
relay_session_cmp(struct rsession *a, struct rsession *b)
{
struct relay *rlay = b->se_relay;
struct protocol *proto = rlay->rl_proto;
if (proto != NULL && proto->cmp != NULL)
return ((*proto->cmp)(a, b));
return ((int)a->se_id - b->se_id);
}
void
relay_log(struct rsession *con, char *msg)
{
if (con->se_haslog && con->se_log != NULL) {
evbuffer_add(con->se_log, msg, strlen(msg));
}
}
SPLAY_GENERATE(session_tree, rsession, se_nodes, relay_session_cmp);