summaryrefslogtreecommitdiff
path: root/usr.sbin
diff options
context:
space:
mode:
authorDavid Gwynne <dlg@cvs.openbsd.org>2012-07-11 11:43:28 +0000
committerDavid Gwynne <dlg@cvs.openbsd.org>2012-07-11 11:43:28 +0000
commitb9076e09efcb9ae4f32155a702061a0121176636 (patch)
tree4b6cec6323e07883585e69f018ff6147d90d2fb9 /usr.sbin
parent24ab80e82da7dc045a4052e24c5f383520aa8626 (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/Makefile11
-rw-r--r--usr.sbin/tftp-proxy/filter.c228
-rw-r--r--usr.sbin/tftp-proxy/filter.h28
-rw-r--r--usr.sbin/tftp-proxy/tftp-proxy.c956
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);
+}
+