diff options
author | David Gwynne <dlg@cvs.openbsd.org> | 2012-07-11 11:43:28 +0000 |
---|---|---|
committer | David Gwynne <dlg@cvs.openbsd.org> | 2012-07-11 11:43:28 +0000 |
commit | b9076e09efcb9ae4f32155a702061a0121176636 (patch) | |
tree | 4b6cec6323e07883585e69f018ff6147d90d2fb9 /usr.sbin | |
parent | 24ab80e82da7dc045a4052e24c5f383520aa8626 (diff) |
bring in a persistent event driven tftp-proxy to replace the libexec
one run out of inetd.
the libexec tftp-proxy had the same problems as the libexec tftpd, in that
it didnt scale as well as we needed. it also had a bunch of interesting
races with states and addresses on sockets.
manpage is coming.
ok deraadt@ sthen@ henning@ matthew@
Diffstat (limited to 'usr.sbin')
-rw-r--r-- | usr.sbin/tftp-proxy/Makefile | 11 | ||||
-rw-r--r-- | usr.sbin/tftp-proxy/filter.c | 228 | ||||
-rw-r--r-- | usr.sbin/tftp-proxy/filter.h | 28 | ||||
-rw-r--r-- | usr.sbin/tftp-proxy/tftp-proxy.c | 956 |
4 files changed, 1223 insertions, 0 deletions
diff --git a/usr.sbin/tftp-proxy/Makefile b/usr.sbin/tftp-proxy/Makefile new file mode 100644 index 00000000000..dfb2da028c5 --- /dev/null +++ b/usr.sbin/tftp-proxy/Makefile @@ -0,0 +1,11 @@ +# $OpenBSD: Makefile,v 1.1 2012/07/11 11:43:27 dlg Exp $ + +PROG= tftp-proxy +SRCS= tftp-proxy.c filter.c +MAN= tftp-proxy.8 +LDADD= -levent +DPADD= ${LIBEVENT} + +CFLAGS+=-Wall -Werror + +.include <bsd.prog.mk> diff --git a/usr.sbin/tftp-proxy/filter.c b/usr.sbin/tftp-proxy/filter.c new file mode 100644 index 00000000000..f4dff05c900 --- /dev/null +++ b/usr.sbin/tftp-proxy/filter.c @@ -0,0 +1,228 @@ +/* $OpenBSD: filter.c,v 1.1.1.1 2012/07/11 11:43:27 dlg Exp $ */ + +/* + * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl> + * + * 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 <syslog.h> + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/pfvar.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "filter.h" + +/* From netinet/in.h, but only _KERNEL_ gets them. */ +#define satosin(sa) ((struct sockaddr_in *)(sa)) +#define satosin6(sa) ((struct sockaddr_in6 *)(sa)) + +enum { TRANS_FILTER = 0, TRANS_NAT, TRANS_RDR, TRANS_SIZE }; + +int prepare_rule(u_int32_t, struct sockaddr *, struct sockaddr *, + u_int16_t, u_int8_t); + +static struct pfioc_rule pfr; +static struct pfioc_trans pft; +static struct pfioc_trans_e pfte; +static int dev, rule_log; +static char *qname; + +int +add_filter(u_int32_t id, u_int8_t dir, struct sockaddr *src, + struct sockaddr *dst, u_int16_t d_port, u_int8_t proto) +{ + if (!src || !dst || !d_port || !proto) { + errno = EINVAL; + return (-1); + } + + if (prepare_rule(id, src, dst, d_port, proto) == -1) + return (-1); + + pfr.rule.direction = dir; + if (ioctl(dev, DIOCADDRULE, &pfr) == -1) + return (-1); + + return (0); +} + +int +add_rdr(u_int32_t id, struct sockaddr *src, struct sockaddr *dst, + u_int16_t d_port, struct sockaddr *rdr, u_int16_t rdr_port, u_int8_t proto) +{ + if (!src || !dst || !d_port || !rdr || !rdr_port || !proto || + (src->sa_family != rdr->sa_family)) { + errno = EINVAL; + return (-1); + } + + if (prepare_rule(id, src, dst, d_port, proto) == -1) + return (-1); + + pfr.rule.rdr.addr.type = PF_ADDR_ADDRMASK; + if (rdr->sa_family == AF_INET) { + memcpy(&pfr.rule.rdr.addr.v.a.addr.v4, + &satosin(rdr)->sin_addr.s_addr, 4); + memset(&pfr.rule.rdr.addr.v.a.mask.addr8, 255, 4); + } else { + memcpy(&pfr.rule.rdr.addr.v.a.addr.v6, + &satosin6(rdr)->sin6_addr.s6_addr, 16); + memset(&pfr.rule.rdr.addr.v.a.mask.addr8, 255, 16); + } + + pfr.rule.rdr.proxy_port[0] = rdr_port; + if (ioctl(dev, DIOCADDRULE, &pfr) == -1) + return (-1); + + return (0); +} + +int +do_commit(void) +{ + if (ioctl(dev, DIOCXCOMMIT, &pft) == -1) + return (-1); + + return (0); +} + +int +do_rollback(void) +{ + if (ioctl(dev, DIOCXROLLBACK, &pft) == -1) + return (-1); + + return (0); +} + +void +init_filter(char *opt_qname, int opt_verbose) +{ + struct pf_status status; + + qname = opt_qname; + + if (opt_verbose == 1) + rule_log = PF_LOG; + else if (opt_verbose == 2) + rule_log = PF_LOG_ALL; + + dev = open("/dev/pf", O_RDWR); + if (dev == -1) { + syslog(LOG_ERR, "can't open /dev/pf"); + exit(1); + } + if (ioctl(dev, DIOCGETSTATUS, &status) == -1) { + syslog(LOG_ERR, "DIOCGETSTATUS"); + exit(1); + } + if (!status.running) { + syslog(LOG_ERR, "pf is disabled"); + exit(1); + } +} + +int +prepare_commit(u_int32_t id) +{ + memset(&pft, 0, sizeof pft); + memset(&pfte, 0, sizeof pfte); + pft.size = 1; + pft.esize = sizeof pfte; + pft.array = &pfte; + + snprintf(pfte.anchor, PF_ANCHOR_NAME_SIZE, + "%s/%d.%08x", FTP_PROXY_ANCHOR, getpid(), id); + pfte.type = PF_TRANS_RULESET; + + if (ioctl(dev, DIOCXBEGIN, &pft) == -1) + return (-1); + + return (0); +} + +int +prepare_rule(u_int32_t id, struct sockaddr *src, + struct sockaddr *dst, u_int16_t d_port, u_int8_t proto) +{ + if ((src->sa_family != AF_INET && src->sa_family != AF_INET6) || + (src->sa_family != dst->sa_family)) { + errno = EPROTONOSUPPORT; + return (-1); + } + + memset(&pfr, 0, sizeof pfr); + snprintf(pfr.anchor, PF_ANCHOR_NAME_SIZE, + "%s/%d.%08x", FTP_PROXY_ANCHOR, getpid(), id); + + pfr.ticket = pfte.ticket; + + /* Generic for all rule types. */ + pfr.rule.af = src->sa_family; + pfr.rule.proto = proto; + pfr.rule.src.addr.type = PF_ADDR_ADDRMASK; + pfr.rule.dst.addr.type = PF_ADDR_ADDRMASK; + pfr.rule.rdr.addr.type = PF_ADDR_NONE; + pfr.rule.nat.addr.type = PF_ADDR_NONE; + + if (src->sa_family == AF_INET) { + memcpy(&pfr.rule.src.addr.v.a.addr.v4, + &satosin(src)->sin_addr.s_addr, 4); + memset(&pfr.rule.src.addr.v.a.mask.addr8, 255, 4); + memcpy(&pfr.rule.dst.addr.v.a.addr.v4, + &satosin(dst)->sin_addr.s_addr, 4); + memset(&pfr.rule.dst.addr.v.a.mask.addr8, 255, 4); + } else { + memcpy(&pfr.rule.src.addr.v.a.addr.v6, + &satosin6(src)->sin6_addr.s6_addr, 16); + memset(&pfr.rule.src.addr.v.a.mask.addr8, 255, 16); + memcpy(&pfr.rule.dst.addr.v.a.addr.v6, + &satosin6(dst)->sin6_addr.s6_addr, 16); + memset(&pfr.rule.dst.addr.v.a.mask.addr8, 255, 16); + } + pfr.rule.dst.port_op = PF_OP_EQ; + pfr.rule.dst.port[0] = htons(d_port); +#ifdef notyet + pfr.rule.rule_flag = PFRULE_ONCE; +#endif + pfr.rule.action = PF_PASS; + pfr.rule.quick = 1; + pfr.rule.log = rule_log; + pfr.rule.keep_state = 1; + pfr.rule.flags = (proto == IPPROTO_TCP ? TH_SYN : 0); + pfr.rule.flagset = (proto == IPPROTO_TCP ? + (TH_SYN|TH_ACK|TH_FIN|TH_RST) : 0); +#ifdef notyet + pfr.rule.max_states = 1; +#endif + if (qname != NULL) + strlcpy(pfr.rule.qname, qname, sizeof pfr.rule.qname); + + return (0); +} diff --git a/usr.sbin/tftp-proxy/filter.h b/usr.sbin/tftp-proxy/filter.h new file mode 100644 index 00000000000..3c4dda2b0d4 --- /dev/null +++ b/usr.sbin/tftp-proxy/filter.h @@ -0,0 +1,28 @@ +/* $OpenBSD: filter.h,v 1.1.1.1 2012/07/11 11:43:27 dlg Exp $ */ + +/* + * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl> + * + * 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. + */ + +#define FTP_PROXY_ANCHOR "tftp-proxy" + +int add_filter(u_int32_t, u_int8_t, struct sockaddr *, struct sockaddr *, + u_int16_t, u_int8_t); +int add_rdr(u_int32_t, struct sockaddr *, struct sockaddr *, u_int16_t, + struct sockaddr *, u_int16_t, u_int8_t); +int do_commit(void); +int do_rollback(void); +void init_filter(char *, int); +int prepare_commit(u_int32_t); diff --git a/usr.sbin/tftp-proxy/tftp-proxy.c b/usr.sbin/tftp-proxy/tftp-proxy.c new file mode 100644 index 00000000000..a022683cac8 --- /dev/null +++ b/usr.sbin/tftp-proxy/tftp-proxy.c @@ -0,0 +1,956 @@ +/* $OpenBSD: tftp-proxy.c,v 1.1 2012/07/11 11:43:27 dlg Exp $ + * + * Copyright (c) 2005 DLS Internet Services + * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/tftp.h> +#include <net/if.h> +#include <net/pfvar.h> +#include <netdb.h> + +#include <unistd.h> +#include <errno.h> +#include <err.h> +#include <pwd.h> +#include <stdio.h> +#include <syslog.h> +#include <string.h> +#include <stdlib.h> +#include <event.h> + +#include "filter.h" + +#define CHROOT_DIR "/var/empty" +#define NOPRIV_USER "proxy" + +#define DEFTRANSWAIT 2 +#define NTOP_BUFS 4 +#define PKTSIZE SEGSIZE+4 + +const char *opcode(int); +const char *sock_ntop(struct sockaddr *); +static void usage(void); + +struct proxy_listener { + struct event ev; + TAILQ_ENTRY(proxy_listener) entry; + int (*cmsg2dst)(struct cmsghdr *, struct sockaddr_storage *); + int s; +}; + +void proxy_listen(const char *, const char *, int); +void proxy_listener_events(void); +int proxy_dst4(struct cmsghdr *, struct sockaddr_storage *); +int proxy_dst6(struct cmsghdr *, struct sockaddr_storage *); +void proxy_recv(int, short, void *); + +struct fd_reply { + TAILQ_ENTRY(fd_reply) entry; + int fd; +}; + +struct privproc { + struct event pop_ev; + struct event push_ev; + TAILQ_HEAD(, fd_reply) replies; + struct evbuffer *buf; +}; + +void proxy_privproc(int, struct passwd *); +void privproc_push(int, short, void *); +void privproc_pop(int, short, void *); + +void unprivproc_push(int, short, void *); +void unprivproc_pop(int, short, void *); +void unprivproc_timeout(int, short, void *); + +char ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN]; + +struct loggers { + void (*err)(int, const char *, ...); + void (*errx)(int, const char *, ...); + void (*warn)(const char *, ...); + void (*warnx)(const char *, ...); + void (*info)(const char *, ...); +}; + +const struct loggers conslogger = { + err, + errx, + warn, + warnx, + warnx +}; + +void syslog_err(int, const char *, ...); +void syslog_errx(int, const char *, ...); +void syslog_warn(const char *, ...); +void syslog_warnx(const char *, ...); +void syslog_info(const char *, ...); +void syslog_vstrerror(int, int, const char *, va_list); + +const struct loggers syslogger = { + syslog_err, + syslog_errx, + syslog_warn, + syslog_warnx, + syslog_info, +}; + +const struct loggers *logger = &conslogger; + +#define lerr(_e, _f...) logger->err((_e), _f) +#define lerrx(_e, _f...) logger->errx((_e), _f) +#define lwarn(_f...) logger->warn(_f) +#define lwarnx(_f...) logger->warnx(_f) +#define linfo(_f...) logger->info(_f) + +__dead void +usage(void) +{ + extern char *__progname; + fprintf(stderr, "usage: %s [-46v] [-l addr] [-p port] [-t tag] " + "[-w wait]", __progname); + exit(1); +} + +int debug = 0; +int verbose = 0; +struct timeval transwait = { DEFTRANSWAIT, 0 }; + +int on = 1; + +struct addr_pair { + struct sockaddr_storage src; + struct sockaddr_storage dst; +}; + +struct proxy_request { + char buf[SEGSIZE_MAX + 4]; + size_t buflen; + + struct addr_pair addrs; + + struct event ev; + TAILQ_ENTRY(proxy_request) entry; + u_int32_t id; +}; + +struct proxy_child { + TAILQ_HEAD(, proxy_request) fdrequests; + TAILQ_HEAD(, proxy_request) tmrequests; + struct event push_ev; + struct event pop_ev; + struct evbuffer *buf; +}; + +struct proxy_child *child = NULL; +TAILQ_HEAD(, proxy_listener) proxy_listeners; + +int +main(int argc, char *argv[]) +{ + extern char *__progname; + + int c; + const char *errstr; + + struct passwd *pw; + + char *addr = "localhost"; + char *port = "6969"; + int family = AF_UNSPEC; + + char *tag = NULL; + + int pair[2]; + + while ((c = getopt(argc, argv, "46dvl:p:t:w:")) != -1) { + switch (c) { + case '4': + family = AF_INET; + break; + case '6': + family = AF_INET6; + break; + case 'd': + verbose = debug = 1; + break; + case 'l': + addr = optarg; + break; + case 'p': + port = optarg; + break; + case 't': + tag = optarg; + break; + case 'v': + verbose = 1; + break; + case 'w': + transwait.tv_sec = strtonum(optarg, 1, 30, &errstr); + if (errstr) + errx(1, "wait is %s", errstr); + break; + default: + usage(); + /* NOTREACHED */ + } + } + + if (geteuid() != 0) + errx(1, "need root privileges"); + + if (!debug && daemon(1, 0) == -1) + err(1, "daemon"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) == -1) + lerr(1, "socketpair"); + + pw = getpwnam(NOPRIV_USER); + if (pw == NULL) + lerrx(1, "no %s user", NOPRIV_USER); + + switch (fork()) { + case -1: + lerr(1, "fork"); + + case 0: + setproctitle("privproc"); + close(pair[1]); + proxy_privproc(pair[0], pw); + /* this never returns */ + + default: + setproctitle("unprivproc"); + close(pair[0]); + break; + } + + child = calloc(1, sizeof(*child)); + if (child == NULL) + lerr(1, "alloc(child)"); + + child->buf = evbuffer_new(); + if (child->buf == NULL) + lerr(1, "child evbuffer"); + + TAILQ_INIT(&child->fdrequests); + TAILQ_INIT(&child->tmrequests); + + if (!debug) { + openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON); + tzset(); + logger = &syslogger; + } + + proxy_listen(addr, port, family); + + /* open /dev/pf */ + init_filter(NULL, verbose); + + /* revoke privs */ + pw = getpwnam(NOPRIV_USER); + if (!pw) + lerrx(1, "no such user %s", NOPRIV_USER); + + if (chroot(CHROOT_DIR) == -1) + lerr(1, "chroot %s", CHROOT_DIR); + + if (chdir("/") == -1) + lerr(1, "chdir %s", CHROOT_DIR); + + 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)) + err(1, "unable to revoke privs"); + + event_init(); + + proxy_listener_events(); + + if (ioctl(pair[1], FIONBIO, &on) == -1) + lerr(1, "ioctl(FIONBIO)"); + + event_set(&child->pop_ev, pair[1], EV_READ | EV_PERSIST, + unprivproc_pop, NULL); + event_set(&child->push_ev, pair[1], EV_WRITE, + unprivproc_push, NULL); + + event_add(&child->pop_ev, NULL); + + event_dispatch(); + + return(0); +} + + +void +proxy_privproc(int s, struct passwd *pw) +{ + extern char *__progname; + struct privproc p; + + if (!debug) { + openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON); + tzset(); + logger = &syslogger; + } + + if (ioctl(s, FIONBIO, &on) == -1) + lerr(1, "ioctl(FIONBIO)"); + + if (chroot(CHROOT_DIR) == -1) + lerr(1, "chroot to %s", CHROOT_DIR); + + if (chdir("/") == -1) + lerr(1, "chdir to %s", CHROOT_DIR); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid)) + lerr(1, "unable to set group ids"); + + TAILQ_INIT(&p.replies); + + p.buf = evbuffer_new(); + if (p.buf == NULL) + err(1, "pop evbuffer_new"); + + event_init(); + + event_set(&p.pop_ev, s, EV_READ | EV_PERSIST, privproc_pop, &p); + event_set(&p.push_ev, s, EV_WRITE, privproc_push, &p); + + event_add(&p.pop_ev, NULL); + + event_dispatch(); +} + +void +privproc_pop(int fd, short events, void *arg) +{ + struct addr_pair req; + struct privproc *p = arg; + struct fd_reply *rep; + int add = 0; + + switch (evbuffer_read(p->buf, fd, sizeof(req))) { + case 0: + lerrx(1, "unprivproc has gone"); + case -1: + switch (errno) { + case EAGAIN: + case EINTR: + return; + default: + lerr(1, "privproc_pop read"); + } + default: + break; + } + + while (EVBUFFER_LENGTH(p->buf) >= sizeof(req)) { + evbuffer_remove(p->buf, &req, sizeof(req)); + + /* do i really need to check this? */ + if (req.src.ss_family != req.dst.ss_family) + lerrx(1, "family mismatch"); + + rep = calloc(1, sizeof(*rep)); + if (rep == NULL) + lerr(1, "reply calloc"); + + rep->fd = socket(req.src.ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (rep->fd == -1) + lerr(1, "privproc socket"); + + if (ioctl(rep->fd, FIONBIO, &on) == -1) + err(1, "privproc ioctl(FIONBIO)"); + + if (setsockopt(rep->fd, SOL_SOCKET, SO_BINDANY, + &on, sizeof(on)) == -1) + lerr(1, "privproc setsockopt(BINDANY)"); + + if (setsockopt(rep->fd, SOL_SOCKET, SO_REUSEADDR, + &on, sizeof(on)) == -1) + lerr(1, "privproc setsockopt(REUSEADDR)"); + + if (setsockopt(rep->fd, SOL_SOCKET, SO_REUSEPORT, + &on, sizeof(on)) == -1) + lerr(1, "privproc setsockopt(REUSEPORT)"); + + if (bind(rep->fd, (struct sockaddr *)&req.src, + req.src.ss_len) == -1) + lerr(1, "privproc bind"); + + if (TAILQ_EMPTY(&p->replies)) + add = 1; + + TAILQ_INSERT_TAIL(&p->replies, rep, entry); + } + + if (add) + event_add(&p->push_ev, NULL); +} + +void +privproc_push(int fd, short events, void *arg) +{ + struct privproc *p = arg; + struct fd_reply *rep; + + struct msghdr msg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + struct cmsghdr *cmsg; + struct iovec iov; + int result = 0; + + while ((rep = TAILQ_FIRST(&p->replies)) != NULL) { + memset(&msg, 0, sizeof(msg)); + + msg.msg_control = (caddr_t)&cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = rep->fd; + + iov.iov_base = &result; + iov.iov_len = sizeof(int); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + switch (sendmsg(fd, &msg, 0)) { + case sizeof(int): + break; + + case -1: + if (errno == EAGAIN) + goto again; + + lerr(1, "privproc sendmsg"); + /* NOTREACHED */ + + default: + lerrx(1, "privproc sendmsg weird len"); + } + + TAILQ_REMOVE(&p->replies, rep, entry); + close(rep->fd); + free(rep); + } + + if (TAILQ_EMPTY(&p->replies)) + return; + +again: + event_add(&p->push_ev, NULL); +} + +void +proxy_listen(const char *addr, const char *port, int family) +{ + struct proxy_listener *l; + + struct addrinfo hints, *res, *res0; + int error; + int s; + + int serrno; + const char *cause = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + int on = 1; + + TAILQ_INIT(&proxy_listeners); + + error = getaddrinfo(addr, port, &hints, &res0); + if (error) + errx(1, "%s:%s: %s", addr, port, gai_strerror(error)); + + for (res = res0; res != NULL; res = res->ai_next) { + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s == -1) { + cause = "socket"; + continue; + } + + if (bind(s, res->ai_addr, res->ai_addrlen) == -1) { + cause = "bind"; + serrno = errno; + close(s); + errno = serrno; + continue; + } + + l = calloc(1, sizeof(*l)); + if (l == NULL) + err(1, "listener alloc"); + + if (ioctl(s, FIONBIO, &on) == -1) + err(1, "ioctl(FIONBIO)"); + + switch (res->ai_family) { + case AF_INET: + l->cmsg2dst = proxy_dst4; + + if (setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, + &on, sizeof(on)) == -1) + errx(1, "setsockopt(IP_RECVDSTADDR)"); + if (setsockopt(s, IPPROTO_IP, IP_RECVDSTPORT, + &on, sizeof(on)) == -1) + errx(1, "setsockopt(IP_RECVDSTPORT)"); + break; + case AF_INET6: + l->cmsg2dst = proxy_dst6; + + if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &on, sizeof(on)) == -1) + errx(1, "setsockopt(IPV6_RECVPKTINFO)"); + break; + } + l->s = s; + + TAILQ_INSERT_TAIL(&proxy_listeners, l, entry); + } + + if (TAILQ_EMPTY(&proxy_listeners)) + err(1, "%s", cause); +} + +void +proxy_listener_events(void) +{ + struct proxy_listener *l; + + TAILQ_FOREACH(l, &proxy_listeners, entry) { + event_set(&l->ev, l->s, EV_READ | EV_PERSIST, proxy_recv, l); + event_add(&l->ev, NULL); + } +} + +char safety[SEGSIZE_MAX + 4]; + +int +proxy_dst4(struct cmsghdr *cmsg, struct sockaddr_storage *ss) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)ss; + + if (cmsg->cmsg_level != IPPROTO_IP) + return (0); + + switch (cmsg->cmsg_type) { + case IP_RECVDSTADDR: + memcpy(&sin->sin_addr, CMSG_DATA(cmsg), sizeof(sin->sin_addr)); + if (sin->sin_addr.s_addr == INADDR_BROADCAST) + return (-1); + break; + + case IP_RECVDSTPORT: + memcpy(&sin->sin_port, CMSG_DATA(cmsg), sizeof(sin->sin_port)); + break; + } + + return (0); +} + +int +proxy_dst6(struct cmsghdr *cmsg, struct sockaddr_storage *ss) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; + struct in6_pktinfo *ipi = (struct in6_pktinfo *)CMSG_DATA(cmsg); + + if (cmsg->cmsg_level != IPPROTO_IPV6) + return (0); + + switch (cmsg->cmsg_type) { + case IPV6_PKTINFO: + memcpy(&sin6->sin6_addr, &ipi->ipi6_addr, + sizeof(sin6->sin6_addr)); +#ifdef __KAME__ + if (IN6_IS_ADDR_LINKLOCAL(&ipi->ipi6_addr)) + sin6->sin6_scope_id = ipi->ipi6_ifindex; +#endif + break; + + /* XXX PORT */ + } + + return (0); +} + +void +proxy_recv(int fd, short events, void *arg) +{ + struct proxy_listener *l = arg; + + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(struct sockaddr_storage)) + + CMSG_SPACE(sizeof(in_port_t))]; + } cmsgbuf; + struct cmsghdr *cmsg; + struct msghdr msg; + struct iovec iov; + ssize_t n; + + struct proxy_request *r; + struct tftphdr *tp; + + r = calloc(1, sizeof(*r)); + if (r == NULL) { + recv(fd, safety, sizeof(safety), 0); + return; + } + r->id = arc4random(); /* XXX unique? */ + + bzero(&msg, sizeof(msg)); + iov.iov_base = r->buf; + iov.iov_len = sizeof(r->buf); + msg.msg_name = &r->addrs.src; + msg.msg_namelen = sizeof(r->addrs.src); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + n = recvmsg(fd, &msg, 0); + if (n == -1) { + switch (errno) { + case EAGAIN: + case EINTR: + goto err; + default: + lerr(1, "recvmsg"); + /* NOTREACHED */ + } + } + r->buflen = n; + + /* check the packet */ + if (n < 5) { + /* not enough to be a real packet */ + goto err; + } + tp = (struct tftphdr *)r->buf; + switch (ntohs(tp->th_opcode)) { + case RRQ: + case WRQ: + break; + default: + goto err; + } + + r->addrs.dst.ss_family = r->addrs.src.ss_family; + r->addrs.dst.ss_len = r->addrs.src.ss_len; + + /* get local address if possible */ + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (l->cmsg2dst(cmsg, &r->addrs.dst) == -1) + goto err; + } + + if (verbose) { + linfo("%s:%d -> %s:%d \"%s %s\"", + sock_ntop((struct sockaddr *)&r->addrs.src), + ntohs(((struct sockaddr_in *)&r->addrs.src)->sin_port), + sock_ntop((struct sockaddr *)&r->addrs.dst), + ntohs(((struct sockaddr_in *)&r->addrs.dst)->sin_port), + opcode(ntohs(tp->th_opcode)), tp->th_stuff); + /* XXX tp->th_stuff could be garbage */ + } + + TAILQ_INSERT_TAIL(&child->fdrequests, r, entry); + evbuffer_add(child->buf, &r->addrs, sizeof(r->addrs)); + event_add(&child->push_ev, NULL); + + return; + +err: + free(r); +} + +void +unprivproc_push(int fd, short events, void *arg) +{ + if (evbuffer_write(child->buf, fd) == -1) + lerr(1, "child evbuffer_write"); + + if (EVBUFFER_LENGTH(child->buf)) + event_add(&child->push_ev, NULL); +} + +void +unprivproc_pop(int fd, short events, void *arg) +{ + struct proxy_request *r; + + struct msghdr msg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + struct cmsghdr *cmsg; + struct iovec iov; + int result; + int s; + + do { + memset(&msg, 0, sizeof(msg)); + iov.iov_base = &result; + iov.iov_len = sizeof(int); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + switch (recvmsg(fd, &msg, 0)) { + case sizeof(int): + break; + + case -1: + switch (errno) { + case EAGAIN: + case EINTR: + return; + default: + lerr(1, "child recvmsg"); + } + /* NOTREACHED */ + + case 0: + lerrx(1, "privproc closed connection"); + + default: + lerrx(1, "child recvmsg was weird"); + /* NOTREACHED */ + } + + if (result != 0) { + errno = result; + lerr(1, "child fdpass fail"); + } + + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == NULL) + lerrx(1, "%s: no message header", __func__); + + if (cmsg->cmsg_type != SCM_RIGHTS) { + lerrx(1, "%s: expected type %d got %d", __func__, + SCM_RIGHTS, cmsg->cmsg_type); + } + + s = (*(int *)CMSG_DATA(cmsg)); + + r = TAILQ_FIRST(&child->fdrequests); + if (r == NULL) + lerrx(1, "got fd without a pending request"); + + TAILQ_REMOVE(&child->fdrequests, r, entry); + + /* get ready to add rules */ + if (prepare_commit(r->id) == -1) + lerr(1, "%s: prepare_commit", __func__); + + if (add_filter(r->id, PF_IN, (struct sockaddr *)&r->addrs.dst, + (struct sockaddr *)&r->addrs.src, + ntohs(((struct sockaddr_in *)&r->addrs.src)->sin_port), + IPPROTO_UDP) == -1) + lerr(1, "%s: couldn't add pass in", __func__); + + if (add_filter(r->id, PF_OUT, (struct sockaddr *)&r->addrs.dst, + (struct sockaddr *)&r->addrs.src, + ntohs(((struct sockaddr_in *)&r->addrs.src)->sin_port), + IPPROTO_UDP) == -1) + lerr(1, "%s: couldn't add pass out", __func__); + + if (do_commit() == -1) + lerr(1, "%s: couldn't commit rules", __func__); + + /* forward the initial tftp request and start the insanity */ + if (sendto(s, r->buf, r->buflen, 0, + (struct sockaddr *)&r->addrs.dst, + r->addrs.dst.ss_len) == -1) + lerr(1, "%s: unable to send", __func__); + + close(s); + + evtimer_set(&r->ev, unprivproc_timeout, r); + evtimer_add(&r->ev, &transwait); + + TAILQ_INSERT_TAIL(&child->tmrequests, r, entry); + } while (!TAILQ_EMPTY(&child->fdrequests)); +} + +void +unprivproc_timeout(int fd, short events, void *arg) +{ + struct proxy_request *r = arg; + + TAILQ_REMOVE(&child->tmrequests, r, entry); + + /* delete our rdr rule and clean up */ + prepare_commit(r->id); + do_commit(); + + free(r); +} + + +const char * +opcode(int code) +{ + static char str[6]; + + switch (code) { + case 1: + (void)snprintf(str, sizeof(str), "RRQ"); + break; + case 2: + (void)snprintf(str, sizeof(str), "WRQ"); + break; + default: + (void)snprintf(str, sizeof(str), "(%d)", code); + break; + } + + return (str); +} + +const char * +sock_ntop(struct sockaddr *sa) +{ + static int n = 0; + + /* Cycle to next buffer. */ + n = (n + 1) % NTOP_BUFS; + ntop_buf[n][0] = '\0'; + + if (sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + + return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n], + sizeof ntop_buf[0])); + } + + if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + + return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n], + sizeof ntop_buf[0])); + } + + return (NULL); +} + +void +syslog_vstrerror(int e, int priority, const char *fmt, va_list ap) +{ + char *s; + + if (vasprintf(&s, fmt, ap) == -1) { + syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror"); + exit(1); + } + + syslog(priority, "%s: %s", s, strerror(e)); + + free(s); +} + +void +syslog_err(int ecode, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + syslog_vstrerror(errno, LOG_EMERG, fmt, ap); + va_end(ap); + + exit(ecode); +} + +void +syslog_errx(int ecode, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_WARNING, fmt, ap); + va_end(ap); + + exit(ecode); +} + +void +syslog_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + syslog_vstrerror(errno, LOG_WARNING, fmt, ap); + va_end(ap); +} + +void +syslog_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_WARNING, fmt, ap); + va_end(ap); +} + +void +syslog_info(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_INFO, fmt, ap); + va_end(ap); +} + |