diff options
-rw-r--r-- | usr.sbin/rad/Makefile | 18 | ||||
-rw-r--r-- | usr.sbin/rad/control.c | 307 | ||||
-rw-r--r-- | usr.sbin/rad/control.h | 35 | ||||
-rw-r--r-- | usr.sbin/rad/engine.c | 478 | ||||
-rw-r--r-- | usr.sbin/rad/engine.h | 21 | ||||
-rw-r--r-- | usr.sbin/rad/frontend.c | 936 | ||||
-rw-r--r-- | usr.sbin/rad/frontend.h | 27 | ||||
-rw-r--r-- | usr.sbin/rad/log.c | 199 | ||||
-rw-r--r-- | usr.sbin/rad/log.h | 46 | ||||
-rw-r--r-- | usr.sbin/rad/parse.y | 891 | ||||
-rw-r--r-- | usr.sbin/rad/printconf.c | 102 | ||||
-rw-r--r-- | usr.sbin/rad/rad.8 | 147 | ||||
-rw-r--r-- | usr.sbin/rad/rad.c | 783 | ||||
-rw-r--r-- | usr.sbin/rad/rad.conf.5 | 148 | ||||
-rw-r--r-- | usr.sbin/rad/rad.h | 161 |
15 files changed, 4299 insertions, 0 deletions
diff --git a/usr.sbin/rad/Makefile b/usr.sbin/rad/Makefile new file mode 100644 index 00000000000..bb1d1e0a3e2 --- /dev/null +++ b/usr.sbin/rad/Makefile @@ -0,0 +1,18 @@ +# $OpenBSD: Makefile,v 1.1 2018/07/10 16:39:54 florian Exp $ + +PROG= rad +SRCS= control.c engine.c frontend.c log.c rad.c parse.y printconf.c + +MAN= rad.8 rad.conf.5 + +#DEBUG= -g -DDEBUG=3 -O0 +CFLAGS+= -Wall -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +YFLAGS= +LDADD+= -levent -lutil +DPADD+= ${LIBEVENT} ${LIBUTIL} + +.include <bsd.prog.mk> diff --git a/usr.sbin/rad/control.c b/usr.sbin/rad/control.c new file mode 100644 index 00000000000..1771ad3d4b9 --- /dev/null +++ b/usr.sbin/rad/control.c @@ -0,0 +1,307 @@ +/* $OpenBSD: control.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/stat.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/un.h> + +#include <netinet/in.h> +#include <net/if.h> + +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <md5.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "log.h" +#include "rad.h" +#include "control.h" +#include "frontend.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_conn *control_connbyfd(int); +struct ctl_conn *control_connbypid(pid_t); +void control_close(int); + +int +control_init(char *path) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("%s: unlink %s", __func__, path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("%s: bind: %s", __func__, path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("%s: chmod", __func__); + close(fd); + (void)unlink(path); + return (-1); + } + + control_state.fd = fd; + + return (0); +} + +int +control_listen(void) +{ + + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { + log_warn("%s: listen", __func__); + return (-1); + } + + event_set(&control_state.ev, control_state.fd, EV_READ, + control_accept, NULL); + event_add(&control_state.ev, NULL); + evtimer_set(&control_state.evt, control_accept, NULL); + + return (0); +} + +void +control_cleanup(char *path) +{ + if (path == NULL) + return; + event_del(&control_state.ev); + event_del(&control_state.evt); + unlink(path); +} + +void +control_accept(int listenfd, short event, void *bula) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + + event_add(&control_state.ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + len = sizeof(sun); + if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len, + SOCK_CLOEXEC | SOCK_NONBLOCK)) == -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(&control_state.ev); + evtimer_add(&control_state.evt, &evtpause); + } else if (errno != EWOULDBLOCK && errno != EINTR && + errno != ECONNABORTED) + log_warn("%s: accept4", __func__); + return; + } + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn("%s: calloc", __func__); + close(connfd); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, + c->iev.handler, &c->iev); + event_add(&c->iev.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.fd == fd) + break; + } + + return (c); +} + +struct ctl_conn * +control_connbypid(pid_t pid) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.pid == pid) + break; + } + + return (c); +} + +void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + + /* Some file descriptors are available again. */ + if (evtimer_pending(&control_state.evt, NULL)) { + evtimer_del(&control_state.evt); + event_add(&control_state.ev, NULL); + } + + free(c); +} + +void +control_dispatch_imsg(int fd, short event, void *bula) +{ + struct ctl_conn *c; + struct imsg imsg; + ssize_t n; + int verbose; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + if (event & EV_READ) { + if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || + n == 0) { + control_close(fd); + return; + } + } + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { + control_close(fd); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_RELOAD: + frontend_imsg_compose_main(imsg.hdr.type, 0, NULL, 0); + break; + case IMSG_CTL_LOG_VERBOSE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(verbose)) + break; + + /* Forward to all other processes. */ + frontend_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + frontend_imsg_compose_engine(imsg.hdr.type, + imsg.hdr.pid, imsg.data, + imsg.hdr.len - IMSG_HEADER_SIZE); + + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_setverbose(verbose); + break; + case IMSG_CTL_SHOW_MAIN_INFO: + c->iev.ibuf.pid = imsg.hdr.pid; + frontend_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + break; + case IMSG_CTL_SHOW_FRONTEND_INFO: + frontend_showinfo_ctl(c); + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, + NULL, 0); + break; + case IMSG_CTL_SHOW_ENGINE_INFO: + c->iev.ibuf.pid = imsg.hdr.pid; + frontend_imsg_compose_engine(imsg.hdr.type, + imsg.hdr.pid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +int +control_imsg_relay(struct imsg *imsg) +{ + struct ctl_conn *c; + + if ((c = control_connbypid(imsg->hdr.pid)) == NULL) + return (0); + + return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, + -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE)); +} diff --git a/usr.sbin/rad/control.h b/usr.sbin/rad/control.h new file mode 100644 index 00000000000..7ac69527f11 --- /dev/null +++ b/usr.sbin/rad/control.h @@ -0,0 +1,35 @@ +/* $OpenBSD: control.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +struct { + struct event ev; + struct event evt; + int fd; +} control_state; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + struct imsgev iev; +}; + +int control_init(char *); +int control_listen(void); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +int control_imsg_relay(struct imsg *); +void control_cleanup(char *); diff --git a/usr.sbin/rad/engine.c b/usr.sbin/rad/engine.c new file mode 100644 index 00000000000..3fd06d2c9f1 --- /dev/null +++ b/usr.sbin/rad/engine.c @@ -0,0 +1,478 @@ +/* $OpenBSD: engine.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser <florian@openbsd.org> + * Copyright (c) 2004, 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/socket.h> +#include <sys/syslog.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <netinet/icmp6.h> + +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <pwd.h> +#include <unistd.h> + +#include "log.h" +#include "rad.h" +#include "engine.h" + +__dead void engine_shutdown(void); +void engine_sig_handler(int sig, short, void *); +void engine_dispatch_frontend(int, short, void *); +void engine_dispatch_main(int, short, void *); +void engine_showinfo_ctl(struct imsg *); +void parse_ra_rs(struct imsg_ra_rs *); +void parse_ra(struct imsg_ra_rs *); +void parse_rs(struct imsg_ra_rs *); + +struct rad_conf *engine_conf; +struct imsgev *iev_frontend; +struct imsgev *iev_main; +struct sockaddr_in6 all_nodes; + +void +engine_sig_handler(int sig, short event, void *arg) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGINT: + case SIGTERM: + engine_shutdown(); + default: + fatalx("unexpected signal"); + } +} + +void +engine(int debug, int verbose) +{ + struct event ev_sigint, ev_sigterm; + struct passwd *pw; + + engine_conf = config_new_empty(); + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + if ((pw = getpwnam(RAD_USER)) == NULL) + fatal("getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + rad_process = PROC_ENGINE; + setproctitle("%s", log_procnames[rad_process]); + log_procinit(log_procnames[rad_process]); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + if (pledge("stdio recvfd", NULL) == -1) + fatal("pledge"); + + event_init(); + + /* Setup signal handler(s). */ + signal_set(&ev_sigint, SIGINT, engine_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, engine_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* Setup pipe and event handler to the main process. */ + if ((iev_main = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + + imsg_init(&iev_main->ibuf, 3); + iev_main->handler = engine_dispatch_main; + + /* Setup event handlers. */ + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + all_nodes.sin6_len = sizeof(all_nodes); + all_nodes.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, "ff02::1", &all_nodes.sin6_addr) != 1) + fatal("inet_pton"); + + event_dispatch(); + + engine_shutdown(); +} + +__dead void +engine_shutdown(void) +{ + /* Close pipes. */ + msgbuf_clear(&iev_frontend->ibuf.w); + close(iev_frontend->ibuf.fd); + msgbuf_clear(&iev_main->ibuf.w); + close(iev_main->ibuf.fd); + + config_clear(engine_conf); + + free(iev_frontend); + free(iev_main); + + log_info("engine exiting"); + exit(0); +} + +int +engine_imsg_compose_frontend(int type, pid_t pid, void *data, uint16_t datalen) +{ + return (imsg_compose_event(iev_frontend, type, 0, pid, -1, + data, datalen)); +} + +void +engine_dispatch_frontend(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct imsg imsg; + struct imsg_ra_rs ra_rs; + ssize_t n; + int shut = 0, verbose; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_RA_RS: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(ra_rs)) + fatal("%s: IMSG_RA_RS wrong length: %d", + __func__, imsg.hdr.len); + memcpy(&ra_rs, imsg.data, sizeof(ra_rs)); + parse_ra_rs(&ra_rs); + break; + case IMSG_CTL_LOG_VERBOSE: + /* Already checked by frontend. */ + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_setverbose(verbose); + break; + case IMSG_CTL_SHOW_ENGINE_INFO: + engine_showinfo_ctl(&imsg); + break; + default: + log_debug("%s: unexpected imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +engine_dispatch_main(int fd, short event, void *bula) +{ + static struct rad_conf *nconf; + static struct ra_iface_conf *ra_iface_conf; + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct ra_prefix_conf *ra_prefix_conf; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_SOCKET_IPC: + /* + * Setup pipe and event handler to the frontend + * process. + */ + if (iev_frontend) { + log_warnx("%s: received unexpected imsg fd " + "to engine", __func__); + break; + } + if ((fd = imsg.fd) == -1) { + log_warnx("%s: expected to receive imsg fd to " + "engine but didn't receive any", __func__); + break; + } + + iev_frontend = malloc(sizeof(struct imsgev)); + if (iev_frontend == NULL) + fatal(NULL); + + imsg_init(&iev_frontend->ibuf, fd); + iev_frontend->handler = engine_dispatch_frontend; + iev_frontend->events = EV_READ; + + event_set(&iev_frontend->ev, iev_frontend->ibuf.fd, + iev_frontend->events, iev_frontend->handler, + iev_frontend); + event_add(&iev_frontend->ev, NULL); + break; + case IMSG_RECONF_CONF: + if ((nconf = malloc(sizeof(struct rad_conf))) == NULL) + fatal(NULL); + memcpy(nconf, imsg.data, sizeof(struct rad_conf)); + SIMPLEQ_INIT(&nconf->ra_iface_list); + break; + case IMSG_RECONF_RA_IFACE: + if ((ra_iface_conf = malloc(sizeof(struct + ra_iface_conf))) == NULL) + fatal(NULL); + memcpy(ra_iface_conf, imsg.data, + sizeof(struct ra_iface_conf)); + ra_iface_conf->autoprefix = NULL; + SIMPLEQ_INIT(&ra_iface_conf->ra_prefix_list); + SIMPLEQ_INSERT_TAIL(&nconf->ra_iface_list, + ra_iface_conf, entry); + break; + case IMSG_RECONF_RA_AUTOPREFIX: + if ((ra_iface_conf->autoprefix = malloc(sizeof(struct + ra_prefix_conf))) == NULL) + fatal(NULL); + memcpy(ra_iface_conf->autoprefix, imsg.data, + sizeof(struct ra_prefix_conf)); + break; + case IMSG_RECONF_RA_PREFIX: + if ((ra_prefix_conf = malloc(sizeof(struct + ra_prefix_conf))) == NULL) + fatal(NULL); + memcpy(ra_prefix_conf, imsg.data, sizeof(struct + ra_prefix_conf)); + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list, + ra_prefix_conf, entry); + break; + case IMSG_RECONF_END: + merge_config(engine_conf, nconf); + nconf = NULL; + break; + default: + log_debug("%s: unexpected imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +engine_showinfo_ctl(struct imsg *imsg) +{ + char filter[IF_NAMESIZE]; + struct ctl_engine_info cei; + struct ra_iface_conf *ra_iface_conf; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_ENGINE_INFO: + memcpy(filter, imsg->data, sizeof(filter)); + SIMPLEQ_FOREACH(ra_iface_conf, &engine_conf->ra_iface_list, + entry) { + if (filter[0] == '\0' || memcmp(filter, + ra_iface_conf->name, sizeof(filter)) == 0) { + memcpy(cei.name, ra_iface_conf->name, + sizeof(cei.name)); + + engine_imsg_compose_frontend( + IMSG_CTL_SHOW_ENGINE_INFO, imsg->hdr.pid, + &cei, sizeof(cei)); + } + } + engine_imsg_compose_frontend(IMSG_CTL_END, imsg->hdr.pid, NULL, + 0); + break; + default: + log_debug("%s: error handling imsg", __func__); + break; + } +} + +void +parse_ra_rs(struct imsg_ra_rs *ra_rs) +{ + char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ]; + struct icmp6_hdr *hdr; + + hdr = (struct icmp6_hdr *) ra_rs->packet; + + switch (hdr->icmp6_type) { + case ND_ROUTER_ADVERT: + parse_ra(ra_rs); + break; + case ND_ROUTER_SOLICIT: + parse_rs(ra_rs); + break; + default: + log_warnx("unexpected icmp6_type: %d from %s on %s", + hdr->icmp6_type, inet_ntop(AF_INET6, &ra_rs->from.sin6_addr, + ntopbuf, INET6_ADDRSTRLEN), if_indextoname(ra_rs->if_index, + ifnamebuf)); + break; + } +} + +void +parse_ra(struct imsg_ra_rs *ra) +{ + char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ]; + log_debug("got RA from %s on %s", + inet_ntop(AF_INET6, &ra->from.sin6_addr, ntopbuf, + INET6_ADDRSTRLEN), if_indextoname(ra->if_index, + ifnamebuf)); + /* XXX not yet */ +} + +void +parse_rs(struct imsg_ra_rs *rs) +{ + struct nd_router_solicit *nd_rs; + struct imsg_send_ra send_ra; + ssize_t len; + int source_linkaddr = 0; + const char *hbuf; + char ifnamebuf[IFNAMSIZ]; + uint8_t *p; + + hbuf = sin6_to_str(&rs->from); + + send_ra.if_index = rs->if_index; + memcpy(&send_ra.to, &all_nodes, sizeof(send_ra.to)); + + log_debug("got RS from %s on %s", hbuf, if_indextoname(rs->if_index, + ifnamebuf)); + + len = rs->len; + + if (!IN6_IS_ADDR_LINKLOCAL(&rs->from.sin6_addr)) { + log_warnx("RA from non link local address %s", hbuf); + return; + } + + if ((size_t)len < sizeof(struct nd_router_solicit)) { + log_warnx("received too short message (%ld) from %s", len, + hbuf); + return; + } + + p = rs->packet; + nd_rs = (struct nd_router_solicit *)p; + len -= sizeof(struct nd_router_solicit); + p += sizeof(struct nd_router_solicit); + + if (nd_rs->nd_rs_code != 0) { + log_warnx("invalid ICMPv6 code (%d) from %s", nd_rs->nd_rs_code, + hbuf); + return; + } + while ((size_t)len >= sizeof(struct nd_opt_hdr)) { + struct nd_opt_hdr *nd_opt_hdr = (struct nd_opt_hdr *)p; + + len -= sizeof(struct nd_opt_hdr); + p += sizeof(struct nd_opt_hdr); + + if (nd_opt_hdr->nd_opt_len * 8 - 2 > len) { + log_warnx("invalid option len: %u > %ld", + nd_opt_hdr->nd_opt_len, len); + return; + } + switch (nd_opt_hdr->nd_opt_type) { + case ND_OPT_SOURCE_LINKADDR: + log_debug("got RS with source linkaddr option"); + memcpy(&send_ra.to, &rs->from, sizeof(send_ra.to)); + break; + default: + log_debug("\t\tUNKNOWN: %d", nd_opt_hdr->nd_opt_type); + break; + } + len -= nd_opt_hdr->nd_opt_len * 8 - 2; + p += nd_opt_hdr->nd_opt_len * 8 - 2; + } + engine_imsg_compose_frontend(IMSG_SEND_RA, 0, &send_ra, + sizeof(send_ra)); +} diff --git a/usr.sbin/rad/engine.h b/usr.sbin/rad/engine.h new file mode 100644 index 00000000000..68cb2b9587d --- /dev/null +++ b/usr.sbin/rad/engine.h @@ -0,0 +1,21 @@ +/* $OpenBSD: engine.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser <florian@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@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. + */ + +void engine(int, int); +int engine_imsg_compose_frontend(int, pid_t, void *, uint16_t); diff --git a/usr.sbin/rad/frontend.c b/usr.sbin/rad/frontend.c new file mode 100644 index 00000000000..5a33378b194 --- /dev/null +++ b/usr.sbin/rad/frontend.c @@ -0,0 +1,936 @@ +/* $OpenBSD: frontend.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser <florian@openbsd.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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/types.h> +#include <sys/ioctl.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/syslog.h> +#include <sys/uio.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <arpa/inet.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <netinet6/nd6.h> +#include <netinet6/in6_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> + +#include <errno.h> +#include <event.h> +#include <ifaddrs.h> +#include <imsg.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "log.h" +#include "rad.h" +#include "frontend.h" +#include "control.h" + +struct icmp6_ev { + struct event ev; + uint8_t answer[1500]; + struct msghdr rcvmhdr; + struct iovec rcviov[1]; + struct sockaddr_in6 from; +} icmp6ev; + +struct ra_iface { + TAILQ_ENTRY(ra_iface) entry; + struct ra_prefix_conf_head prefixes; + char name[IF_NAMESIZE]; + uint32_t if_index; + int removed; + int prefix_count; + size_t datalen; + uint8_t data[1500]; +}; + +TAILQ_HEAD(, ra_iface) ra_interfaces; + +__dead void frontend_shutdown(void); +void frontend_sig_handler(int, short, void *); +void frontend_startup(void); +void icmp6_receive(int, short, void *); +void join_all_routers_mcast_group(struct ra_iface *); +void leave_all_routers_mcast_group(struct ra_iface *); +void merge_ra_interfaces(void); +struct ra_iface *find_ra_iface_by_id(uint32_t); +struct ra_iface *find_ra_iface_by_name(char *); +struct ra_iface_conf *find_ra_iface_conf(struct ra_iface_conf_head *, + char *); +struct ra_prefix_conf *find_ra_prefix_conf(struct ra_prefix_conf_head*, + struct in6_addr *, int); +void add_new_prefix_to_ra_iface(struct ra_iface *r, + struct in6_addr *, int, struct ra_prefix_conf *); +void free_ra_iface(struct ra_iface *); +int in6_mask2prefixlen(struct in6_addr *); +void get_interface_prefixes(struct ra_iface *, + struct ra_prefix_conf *); +void build_package(struct ra_iface *); +void ra_output(struct ra_iface *, struct sockaddr_in6 *); + +struct rad_conf *frontend_conf; +struct imsgev *iev_main; +struct imsgev *iev_engine; +int icmp6sock = -1, ioctlsock = -1; +struct ipv6_mreq all_routers; +struct sockaddr_in6 all_nodes; +struct msghdr sndmhdr; +struct iovec sndiov[2]; + +void +frontend_sig_handler(int sig, short event, void *bula) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGINT: + case SIGTERM: + frontend_shutdown(); + default: + fatalx("unexpected signal"); + } +} + +void +frontend(int debug, int verbose, char *sockname) +{ + struct event ev_sigint, ev_sigterm; + struct passwd *pw; + size_t rcvcmsglen, sndcmsgbuflen; + uint8_t *rcvcmsgbuf; + uint8_t *sndcmsgbuf = NULL; + + frontend_conf = config_new_empty(); + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + /* XXX pass in from main */ + /* Create rad control socket outside chroot. */ + if (control_init(sockname) == -1) + fatalx("control socket setup failed"); + + if ((pw = getpwnam(RAD_USER)) == NULL) + fatal("getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + rad_process = PROC_FRONTEND; + setproctitle("%s", log_procnames[rad_process]); + log_procinit(log_procnames[rad_process]); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + /* XXX pass in from main */ + if ((ioctlsock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0) + fatal("socket"); + + if (pledge("stdio inet unix recvfd route mcast", NULL) == -1) + fatal("pledge"); + + event_init(); + + /* Setup signal handler. */ + signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, frontend_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* Setup pipe and event handler to the parent process. */ + if ((iev_main = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_main->ibuf, 3); + iev_main->handler = frontend_dispatch_main; + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + rcvcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + + CMSG_SPACE(sizeof(int)); + if((rcvcmsgbuf = malloc(rcvcmsglen)) == NULL) + fatal("malloc"); + + icmp6ev.rcviov[0].iov_base = (caddr_t)icmp6ev.answer; + icmp6ev.rcviov[0].iov_len = sizeof(icmp6ev.answer); + icmp6ev.rcvmhdr.msg_name = (caddr_t)&icmp6ev.from; + icmp6ev.rcvmhdr.msg_namelen = sizeof(icmp6ev.from); + icmp6ev.rcvmhdr.msg_iov = icmp6ev.rcviov; + icmp6ev.rcvmhdr.msg_iovlen = 1; + icmp6ev.rcvmhdr.msg_control = (caddr_t) rcvcmsgbuf; + icmp6ev.rcvmhdr.msg_controllen = rcvcmsglen; + + if (inet_pton(AF_INET6, "ff02::2", + &all_routers.ipv6mr_multiaddr.s6_addr) == -1) + fatal("inet_pton"); + + all_nodes.sin6_len = sizeof(all_nodes); + all_nodes.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, "ff02::1", &all_nodes.sin6_addr) != 1) + fatal("inet_pton"); + + sndcmsgbuflen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + + CMSG_SPACE(sizeof(int)); + if ((sndcmsgbuf = malloc(sndcmsgbuflen)) == NULL) + fatal("%s", __func__); + + sndmhdr.msg_namelen = sizeof(struct sockaddr_in6); + sndmhdr.msg_iov = sndiov; + sndmhdr.msg_iovlen = 1; + sndmhdr.msg_control = sndcmsgbuf; + sndmhdr.msg_controllen = sndcmsgbuflen; + + TAILQ_INIT(&ra_interfaces); + + /* Listen on control socket. */ + TAILQ_INIT(&ctl_conns); + control_listen(); + + event_dispatch(); + + frontend_shutdown(); +} + +__dead void +frontend_shutdown(void) +{ + /* Close pipes. */ + msgbuf_write(&iev_engine->ibuf.w); + msgbuf_clear(&iev_engine->ibuf.w); + close(iev_engine->ibuf.fd); + msgbuf_write(&iev_main->ibuf.w); + msgbuf_clear(&iev_main->ibuf.w); + close(iev_main->ibuf.fd); + + config_clear(frontend_conf); + + free(iev_engine); + free(iev_main); + + log_info("frontend exiting"); + exit(0); +} + +int +frontend_imsg_compose_main(int type, pid_t pid, void *data, uint16_t datalen) +{ + return (imsg_compose_event(iev_main, type, 0, pid, -1, data, + datalen)); +} + +int +frontend_imsg_compose_engine(int type, pid_t pid, void *data, uint16_t datalen) +{ + return (imsg_compose_event(iev_engine, type, 0, pid, -1, data, + datalen)); +} + +void +frontend_dispatch_main(int fd, short event, void *bula) +{ + static struct rad_conf *nconf; + static struct ra_iface_conf *ra_iface_conf; + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct ra_prefix_conf *ra_prefix_conf; + int n, shut = 0; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_SOCKET_IPC: + /* + * Setup pipe and event handler to the engine + * process. + */ + if (iev_engine) { + log_warnx("%s: received unexpected imsg fd " + "to frontend", __func__); + break; + } + if ((fd = imsg.fd) == -1) { + log_warnx("%s: expected to receive imsg fd to " + "frontend but didn't receive any", + __func__); + break; + } + + iev_engine = malloc(sizeof(struct imsgev)); + if (iev_engine == NULL) + fatal(NULL); + + imsg_init(&iev_engine->ibuf, fd); + iev_engine->handler = frontend_dispatch_engine; + iev_engine->events = EV_READ; + + event_set(&iev_engine->ev, iev_engine->ibuf.fd, + iev_engine->events, iev_engine->handler, iev_engine); + event_add(&iev_engine->ev, NULL); + break; + case IMSG_RECONF_CONF: + if ((nconf = malloc(sizeof(struct rad_conf))) == + NULL) + fatal(NULL); + memcpy(nconf, imsg.data, sizeof(struct rad_conf)); + SIMPLEQ_INIT(&nconf->ra_iface_list); + break; + case IMSG_RECONF_RA_IFACE: + if ((ra_iface_conf = malloc(sizeof(struct + ra_iface_conf))) == NULL) + fatal(NULL); + memcpy(ra_iface_conf, imsg.data, sizeof(struct + ra_iface_conf)); + ra_iface_conf->autoprefix = NULL; + SIMPLEQ_INIT(&ra_iface_conf->ra_prefix_list); + SIMPLEQ_INSERT_TAIL(&nconf->ra_iface_list, + ra_iface_conf, entry); + break; + case IMSG_RECONF_RA_AUTOPREFIX: + if ((ra_iface_conf->autoprefix = malloc(sizeof(struct + ra_prefix_conf))) == NULL) + fatal(NULL); + memcpy(ra_iface_conf->autoprefix, imsg.data, + sizeof(struct ra_prefix_conf)); + break; + case IMSG_RECONF_RA_PREFIX: + if ((ra_prefix_conf = malloc(sizeof(struct + ra_prefix_conf))) == NULL) + fatal(NULL); + memcpy(ra_prefix_conf, imsg.data, + sizeof(struct ra_prefix_conf)); + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list, + ra_prefix_conf, entry); + break; + case IMSG_RECONF_END: + merge_config(frontend_conf, nconf); + merge_ra_interfaces(); + nconf = NULL; + break; + case IMSG_ICMP6SOCK: + if ((icmp6sock = imsg.fd) == -1) + fatalx("%s: expected to receive imsg " + "ICMPv6 fd but didn't receive any", + __func__); + event_set(&icmp6ev.ev, icmp6sock, EV_READ | EV_PERSIST, + icmp6_receive, NULL); + case IMSG_STARTUP: + if (pledge("stdio inet unix route mcast", NULL) == -1) + fatal("pledge"); + frontend_startup(); + break; + case IMSG_CTL_END: + case IMSG_CTL_SHOW_MAIN_INFO: + control_imsg_relay(&imsg); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +frontend_dispatch_engine(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + struct imsg_send_ra send_ra; + struct ra_iface *ra_iface; + int n, shut = 0; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_END: + case IMSG_CTL_SHOW_ENGINE_INFO: + control_imsg_relay(&imsg); + break; + case IMSG_SEND_RA: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(send_ra)) + fatal("%s: IMSG_SEND_RA wrong length: %d", + __func__, imsg.hdr.len); + memcpy(&send_ra, imsg.data, sizeof(send_ra)); + ra_iface = find_ra_iface_by_id(send_ra.if_index); + if (ra_iface) + ra_output(ra_iface, &send_ra.to); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +frontend_showinfo_ctl(struct ctl_conn *c) +{ + static struct ctl_frontend_info cfi; + + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_FRONTEND_INFO, 0, 0, -1, + &cfi, sizeof(struct ctl_frontend_info)); +} + +void +frontend_startup(void) +{ +#if 0 + if (!event_initialized(&ev_route)) + fatalx("%s: did not receive a route socket from the main " + "process", __func__); + + event_add(&ev_route, NULL); +#endif + + if (!event_initialized(&icmp6ev.ev)) + fatalx("%s: did not receive a icmp6 socket fd from the main " + "process", __func__); + + event_add(&icmp6ev.ev, NULL); + + frontend_imsg_compose_main(IMSG_STARTUP_DONE, 0, NULL, 0); +} + + +void +icmp6_receive(int fd, short events, void *arg) +{ + struct imsg_ra_rs ra_rs; + struct in6_pktinfo *pi = NULL; + struct cmsghdr *cm; + ssize_t len; + int if_index = 0, *hlimp = NULL; + char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ]; + + if ((len = recvmsg(fd, &icmp6ev.rcvmhdr, 0)) < 0) { + log_warn("recvmsg"); + return; + } + + /* extract optional information via Advanced API */ + for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&icmp6ev.rcvmhdr); cm; + cm = (struct cmsghdr *)CMSG_NXTHDR(&icmp6ev.rcvmhdr, cm)) { + if (cm->cmsg_level == IPPROTO_IPV6 && + cm->cmsg_type == IPV6_PKTINFO && + cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) { + pi = (struct in6_pktinfo *)(CMSG_DATA(cm)); + if_index = pi->ipi6_ifindex; + } + if (cm->cmsg_level == IPPROTO_IPV6 && + cm->cmsg_type == IPV6_HOPLIMIT && + cm->cmsg_len == CMSG_LEN(sizeof(int))) + hlimp = (int *)CMSG_DATA(cm); + } + + if (if_index == 0) { + log_warnx("failed to get receiving interface"); + return; + } + + if (hlimp == NULL) { + log_warnx("failed to get receiving hop limit"); + return; + } + + if (*hlimp != 255) { + log_warnx("invalid RA or RS with hop limit of %d from %s on %s", + *hlimp, inet_ntop(AF_INET6, &icmp6ev.from.sin6_addr, + ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index, + ifnamebuf)); + return; + } + + log_warnx("RA or RS with hop limit of %d from %s on %s", + *hlimp, inet_ntop(AF_INET6, &icmp6ev.from.sin6_addr, + ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index, + ifnamebuf)); + + if ((size_t)len > sizeof(ra_rs.packet)) { + log_warnx("invalid RA or RS with size %ld from %s on %s", + len, inet_ntop(AF_INET6, &icmp6ev.from.sin6_addr, + ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index, + ifnamebuf)); + return; + } + + ra_rs.if_index = if_index; + memcpy(&ra_rs.from, &icmp6ev.from, sizeof(ra_rs.from)); + ra_rs.len = len; + memcpy(ra_rs.packet, icmp6ev.answer, len); + + frontend_imsg_compose_engine(IMSG_RA_RS, 0, &ra_rs, sizeof(ra_rs)); +} + +void +join_all_routers_mcast_group(struct ra_iface *ra_iface) +{ + log_debug("joining multicast group on %s", ra_iface->name); + all_routers.ipv6mr_interface = ra_iface->if_index; + if (setsockopt(icmp6sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &all_routers, sizeof(all_routers)) == -1) + fatal("IPV6_JOIN_GROUP(%s)", ra_iface->name); +} + +void +leave_all_routers_mcast_group(struct ra_iface *ra_iface) +{ + log_debug("leaving multicast group on %s", ra_iface->name); + all_routers.ipv6mr_interface = ra_iface->if_index; + if (setsockopt(icmp6sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, + &all_routers, sizeof(all_routers)) == -1) + fatal("IPV6_LEAVE_GROUP(%s)", ra_iface->name); +} + +struct ra_iface* +find_ra_iface_by_id(uint32_t if_index) { + struct ra_iface *ra_iface; + + TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) { + if (ra_iface->if_index == if_index) + return ra_iface; + } + return (NULL); +} + +struct ra_iface* +find_ra_iface_by_name(char *if_name) +{ + struct ra_iface *ra_iface; + + TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) { + if (strcmp(ra_iface->name, if_name) == 0) + return ra_iface; + } + return (NULL); +} + +struct ra_iface_conf* +find_ra_iface_conf(struct ra_iface_conf_head *head, char *if_name) +{ + struct ra_iface_conf *ra_iface_conf; + + SIMPLEQ_FOREACH(ra_iface_conf, head, entry) { + if (strcmp(ra_iface_conf->name, if_name) == 0) + return ra_iface_conf; + } + return (NULL); +} + +void +merge_ra_interfaces(void) +{ + struct ra_iface_conf *ra_iface_conf; + struct ra_prefix_conf *ra_prefix_conf; + struct ra_iface *ra_iface, *tmp; + uint32_t if_index; + + TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) + ra_iface->removed = 1; + + SIMPLEQ_FOREACH(ra_iface_conf, &frontend_conf->ra_iface_list, entry) { + ra_iface = find_ra_iface_by_name(ra_iface_conf->name); + if (ra_iface == NULL) { + log_debug("new interface %s", ra_iface_conf->name); + if ((if_index = if_nametoindex(ra_iface_conf->name)) + == 0) + continue; + log_debug("adding interface %s", ra_iface_conf->name); + if ((ra_iface = calloc(1, sizeof(*ra_iface))) == NULL) + fatal("%s", __func__); + + (void) strlcpy(ra_iface->name, ra_iface_conf->name, + sizeof(ra_iface->name)); + ra_iface->if_index = if_index; + SIMPLEQ_INIT(&ra_iface->prefixes); + TAILQ_INSERT_TAIL(&ra_interfaces, ra_iface, entry); + join_all_routers_mcast_group(ra_iface); + } else { + log_debug("keeping interface %s", ra_iface_conf->name); + ra_iface->removed = 0; + } + } + + TAILQ_FOREACH_SAFE(ra_iface, &ra_interfaces, entry, tmp) { + if (ra_iface->removed) { + TAILQ_REMOVE(&ra_interfaces, ra_iface, entry); + free_ra_iface(ra_iface); + } + } + + TAILQ_FOREACH(ra_iface, &ra_interfaces, entry) { + while ((ra_prefix_conf = SIMPLEQ_FIRST(&ra_iface->prefixes)) + != NULL) { + SIMPLEQ_REMOVE_HEAD(&ra_iface->prefixes, + entry); + free(ra_prefix_conf); + } + ra_iface->prefix_count = 0; + + ra_iface_conf = find_ra_iface_conf( + &frontend_conf->ra_iface_list, ra_iface->name); + + if (ra_iface_conf->autoprefix) + get_interface_prefixes(ra_iface, + ra_iface_conf->autoprefix); + + log_debug("add static prefixes for %s", ra_iface->name); + + SIMPLEQ_FOREACH(ra_prefix_conf, &ra_iface_conf->ra_prefix_list, + entry) { + add_new_prefix_to_ra_iface(ra_iface, + &ra_prefix_conf->prefix, + ra_prefix_conf->prefixlen, ra_prefix_conf); + } + build_package(ra_iface); + } +} + +void +free_ra_iface(struct ra_iface *ra_iface) +{ + struct ra_prefix_conf *prefix; + + leave_all_routers_mcast_group(ra_iface); + + while ((prefix = SIMPLEQ_FIRST(&ra_iface->prefixes)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&ra_iface->prefixes, entry); + free(prefix); + } + + free(ra_iface); +} + +/* from kame via ifconfig, where it's called prefix() */ +int +in6_mask2prefixlen(struct in6_addr *in6) +{ + u_char *nam = (u_char *)in6; + int byte, bit, plen = 0, size = sizeof(struct in6_addr); + + for (byte = 0; byte < size; byte++, plen += 8) + if (nam[byte] != 0xff) + break; + if (byte == size) + return (plen); + for (bit = 7; bit != 0; bit--, plen++) + if (!(nam[byte] & (1 << bit))) + break; + for (; bit != 0; bit--) + if (nam[byte] & (1 << bit)) + return (0); + byte++; + for (; byte < size; byte++) + if (nam[byte]) + return (0); + return (plen); +} + +void +get_interface_prefixes(struct ra_iface *ra_iface, struct ra_prefix_conf + *autoprefix) +{ + struct in6_ifreq ifr6; + struct ifaddrs *ifap, *ifa; + struct sockaddr_in6 *sin6; + int prefixlen; + + log_debug("%s: %s", __func__, ra_iface->name); + + if (getifaddrs(&ifap) != 0) + fatal("getifaddrs"); + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (strcmp(ra_iface->name, ifa->ifa_name) != 0) + continue; + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + continue; + + memset(&ifr6, 0, sizeof(ifr6)); + (void) strlcpy(ifr6.ifr_name, ra_iface->name, + sizeof(ifr6.ifr_name)); + memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr)); + + if (ioctl(ioctlsock, SIOCGIFNETMASK_IN6, (caddr_t)&ifr6) < 0) + fatal("SIOCGIFNETMASK_IN6"); + + prefixlen = in6_mask2prefixlen(&((struct sockaddr_in6 *) + &ifr6.ifr_addr)->sin6_addr); + + if (prefixlen == 128) + continue; + + mask_prefix(&sin6->sin6_addr, prefixlen); + + add_new_prefix_to_ra_iface(ra_iface, &sin6->sin6_addr, + prefixlen, autoprefix); + + } +} + +struct ra_prefix_conf* +find_ra_prefix_conf(struct ra_prefix_conf_head* head, struct in6_addr *prefix, + int prefixlen) +{ + struct ra_prefix_conf *ra_prefix_conf; + + SIMPLEQ_FOREACH(ra_prefix_conf, head, entry) { + if (ra_prefix_conf->prefixlen == prefixlen && + memcmp(&ra_prefix_conf->prefix, prefix, sizeof(*prefix)) == + 0) + return (ra_prefix_conf); + } + return (NULL); +} + +void +add_new_prefix_to_ra_iface(struct ra_iface *ra_iface, struct in6_addr *addr, + int prefixlen, struct ra_prefix_conf *ra_prefix_conf) +{ + struct ra_prefix_conf *new_ra_prefix_conf; + + if (find_ra_prefix_conf(&ra_iface->prefixes, addr, prefixlen)) { + log_debug("ignoring duplicate %s/%d prefix", + in6_to_str(addr), prefixlen); + return; + } + + log_debug("adding %s/%d prefix", in6_to_str(addr), prefixlen); + + if ((new_ra_prefix_conf = calloc(1, sizeof(*ra_prefix_conf))) == NULL) + fatal("%s", __func__); + new_ra_prefix_conf->prefix = *addr; + new_ra_prefix_conf->prefixlen = prefixlen; + new_ra_prefix_conf->vltime = ra_prefix_conf->vltime; + new_ra_prefix_conf->pltime = ra_prefix_conf->pltime; + new_ra_prefix_conf->aflag = ra_prefix_conf->aflag; + new_ra_prefix_conf->lflag = ra_prefix_conf->lflag; + SIMPLEQ_INSERT_TAIL(&ra_iface->prefixes, new_ra_prefix_conf, entry); + ra_iface->prefix_count++; +} + +void +build_package(struct ra_iface *ra_iface) +{ + struct nd_router_advert *ra; + struct nd_opt_prefix_info *ndopt_pi; + struct ra_iface_conf *ra_iface_conf; + struct ra_options_conf *ra_options_conf; + struct ra_prefix_conf *ra_prefix_conf; + uint8_t *buf; + + ra_iface_conf = find_ra_iface_conf(&frontend_conf->ra_iface_list, + ra_iface->name); + ra_options_conf = &ra_iface_conf->ra_options; + + ra_iface->datalen = sizeof(*ra); + ra_iface->datalen += sizeof(*ndopt_pi) * ra_iface->prefix_count; + + if (ra_iface->datalen > sizeof(ra_iface->data)) + fatal("%s: packet too big", __func__); /* XXX send multiple */ + + buf = ra_iface->data; + + ra = (struct nd_router_advert *)buf; + + memset(ra, 0, sizeof(*ra)); + + ra->nd_ra_type = ND_ROUTER_ADVERT; + ra->nd_ra_curhoplimit = ra_options_conf->cur_hl; + if (ra_options_conf->m_flag) + ra->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED; + if (ra_options_conf->o_flag) + ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER; + if (ra_options_conf->dfr) + ra->nd_ra_router_lifetime = + htons(ra_options_conf->router_lifetime); + ra->nd_ra_reachable = htonl(ra_options_conf->reachable_time); + ra->nd_ra_retransmit = htonl(ra_options_conf->retrans_timer); + buf += sizeof(*ra); + + SIMPLEQ_FOREACH(ra_prefix_conf, &ra_iface->prefixes, entry) { + ndopt_pi = (struct nd_opt_prefix_info *)buf; + memset(ndopt_pi, 0, sizeof(*ndopt_pi)); + ndopt_pi->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; + ndopt_pi->nd_opt_pi_len = 4; + ndopt_pi->nd_opt_pi_prefix_len = ra_prefix_conf->prefixlen; + if (ra_prefix_conf->lflag) + ndopt_pi->nd_opt_pi_flags_reserved |= + ND_OPT_PI_FLAG_ONLINK; + if (ra_prefix_conf->aflag) + ndopt_pi->nd_opt_pi_flags_reserved |= + ND_OPT_PI_FLAG_AUTO; + ndopt_pi->nd_opt_pi_valid_time = htonl(ra_prefix_conf->vltime); + ndopt_pi->nd_opt_pi_preferred_time = + htonl(ra_prefix_conf->pltime); + ndopt_pi->nd_opt_pi_prefix = ra_prefix_conf->prefix; + + buf += sizeof(*ndopt_pi); + } +} + +void +ra_output(struct ra_iface *ra_iface, struct sockaddr_in6 *to) +{ + + struct cmsghdr *cm; + struct in6_pktinfo *pi; + ssize_t len; + int hoplimit = 255; + + sndmhdr.msg_name = to; + sndmhdr.msg_iov[0].iov_base = ra_iface->data; + sndmhdr.msg_iov[0].iov_len = ra_iface->datalen; + + cm = CMSG_FIRSTHDR(&sndmhdr); + /* specify the outgoing interface */ + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + pi = (struct in6_pktinfo *)CMSG_DATA(cm); + memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); + pi->ipi6_ifindex = ra_iface->if_index; + + /* specify the hop limit of the packet */ + cm = CMSG_NXTHDR(&sndmhdr, cm); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_HOPLIMIT; + cm->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int)); + + log_debug("send RA on %s", ra_iface->name); + + len = sendmsg(icmp6sock, &sndmhdr, 0); + if (len < 0) + log_warn("sendmsg on %s", ra_iface->name); + +} diff --git a/usr.sbin/rad/frontend.h b/usr.sbin/rad/frontend.h new file mode 100644 index 00000000000..ac368cb7e33 --- /dev/null +++ b/usr.sbin/rad/frontend.h @@ -0,0 +1,27 @@ +/* $OpenBSD: frontend.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser <florian@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@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. + */ + +TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns; + +void frontend(int, int, char *); +void frontend_dispatch_main(int, short, void *); +void frontend_dispatch_engine(int, short, void *); +int frontend_imsg_compose_main(int, pid_t, void *, uint16_t); +int frontend_imsg_compose_engine(int, pid_t, void *, uint16_t); +void frontend_showinfo_ctl(struct ctl_conn *); diff --git a/usr.sbin/rad/log.c b/usr.sbin/rad/log.c new file mode 100644 index 00000000000..50b0a0e95ac --- /dev/null +++ b/usr.sbin/rad/log.c @@ -0,0 +1,199 @@ +/* $OpenBSD: log.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <syslog.h> +#include <errno.h> +#include <time.h> + +#include "log.h" + +static int debug; +static int verbose; +static const char *log_procname; + +void +log_init(int n_debug, int facility) +{ + extern char *__progname; + + debug = n_debug; + verbose = n_debug; + log_procinit(__progname); + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_setverbose(int v) +{ + verbose = v; +} + +int +log_getverbose(void) +{ + return (verbose); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + int saved_errno = errno; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); + + errno = saved_errno; +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + int saved_errno = errno; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_ERR, "%s", strerror(saved_errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { + /* we tried it... */ + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); + } else { + vlog(LOG_ERR, nfmt, ap); + free(nfmt); + } + va_end(ap); + } + + errno = saved_errno; +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_ERR, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatalc(int code, const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (code) + logit(LOG_CRIT, "fatal in %s: %s%s%s", + log_procname, s, sep, strerror(code)); + else + logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(errno, emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(0, emsg, ap); + va_end(ap); + exit(1); +} diff --git a/usr.sbin/rad/log.h b/usr.sbin/rad/log.h new file mode 100644 index 00000000000..317b3314f7a --- /dev/null +++ b/usr.sbin/rad/log.h @@ -0,0 +1,46 @@ +/* $OpenBSD: log.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +#ifndef LOG_H +#define LOG_H + +#include <stdarg.h> +#include <sys/cdefs.h> + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +#endif /* LOG_H */ diff --git a/usr.sbin/rad/parse.y b/usr.sbin/rad/parse.y new file mode 100644 index 00000000000..ab533c8be09 --- /dev/null +++ b/usr.sbin/rad/parse.y @@ -0,0 +1,891 @@ +/* $OpenBSD: parse.y,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser <florian@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <net/if.h> + +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <ifaddrs.h> +#include <imsg.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "log.h" +#include "rad.h" +#include "frontend.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; + +int symset(const char *, const char *, int); +char *symget(const char *); + +void clear_config(struct rad_conf *xconf); + +static struct rad_conf *conf; +static struct ra_options_conf *ra_options; +static int errors; + +static struct ra_iface_conf *ra_iface_conf; +static struct ra_prefix_conf *ra_prefix_conf; + +struct ra_prefix_conf *conf_get_ra_prefix(struct in6_addr*, int); +struct ra_iface_conf *conf_get_ra_iface(char *); + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; + +%} + +%token RA_IFACE YES NO INCLUDE ERROR +%token DEFAULT ROUTER HOP LIMIT MANAGED ADDRESS +%token CONFIGURATION OTHER LIFETIME REACHABLE TIME RETRANS TIMER +%token AUTO PREFIX VALID PREFERRED LIFETIME ONLINK AUTONOMOUS +%token ADDRESS_CONFIGURATION + +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.number> yesno +%type <v.string> string + +%% + +grammar : /* empty */ + | grammar include '\n' + | grammar '\n' + | grammar { ra_options = &conf->ra_options; } conf_main '\n' + | grammar varset '\n' + | grammar ra_iface '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 1)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) { + free($1); + free($2); + yyerror("string: asprintf"); + YYERROR; + } + free($1); + free($2); + } + | STRING + ; + +yesno : YES { $$ = 1; } + | NO { $$ = 0; } + ; + +varset : STRING '=' string { + char *s = $1; + if (cmd_opts & OPT_VERBOSE) + printf("%s = \"%s\"\n", $1, $3); + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +conf_main : ra_opt_block { + ra_options = &conf->ra_options; + } + ; + +ra_opt_block : DEFAULT ROUTER yesno { + ra_options->dfr = $3; + } + | HOP LIMIT NUMBER { + ra_options->cur_hl = $3; + } + | MANAGED ADDRESS CONFIGURATION yesno { + ra_options->m_flag = $4; + } + | OTHER CONFIGURATION yesno { + ra_options->o_flag = $3; + } + | ROUTER LIFETIME NUMBER { + ra_options->router_lifetime = $3; + } + | REACHABLE TIME NUMBER { + ra_options->reachable_time = $3; + } + | RETRANS TIMER NUMBER { + ra_options->retrans_timer = $3; + } + ; + +optnl : '\n' optnl /* zero or more newlines */ + | /*empty*/ + ; + +nl : '\n' optnl /* one or more newlines */ + ; + +ra_iface : RA_IFACE STRING { + ra_iface_conf = conf_get_ra_iface($2); + /* set auto prefix defaults */ + ra_iface_conf->autoprefix = conf_get_ra_prefix(NULL, 0); + ra_options = &ra_iface_conf->ra_options; + } ra_iface_block { + ra_iface_conf = NULL; + } + ; + +ra_iface_block : '{' optnl ra_ifaceopts_l '}' + | '{' optnl '}' + | /* empty */ + ; + +ra_ifaceopts_l : ra_ifaceopts_l ra_ifaceoptsl nl + | ra_ifaceoptsl optnl + ; + +ra_ifaceoptsl : NO AUTO PREFIX { + free(ra_iface_conf->autoprefix); + ra_iface_conf->autoprefix = NULL; + } + | AUTO PREFIX { + if (ra_iface_conf->autoprefix == NULL) + ra_iface_conf->autoprefix = + ra_prefix_conf = conf_get_ra_prefix(NULL, 0); + } ra_prefix_block { + ra_prefix_conf = NULL; + } + | PREFIX STRING { + struct in6_addr addr; + int prefixlen; + + memset(&addr, 0, sizeof(addr)); + prefixlen = inet_net_pton(AF_INET6, $2, &addr, + sizeof(addr)); + if (prefixlen == -1) { + yyerror("error parsing prefix"); + free($2); + YYERROR; + } + mask_prefix(&addr, prefixlen); + ra_prefix_conf = conf_get_ra_prefix(&addr, prefixlen); + } ra_prefix_block { + ra_prefix_conf = NULL; + } + | ra_opt_block + ; + +ra_prefix_block : '{' optnl ra_prefixopts_l '}' + | '{' optnl '}' + | /* empty */ + ; + +ra_prefixopts_l : ra_prefixopts_l ra_prefixoptsl nl + | ra_prefixoptsl optnl + ; + +ra_prefixoptsl : VALID LIFETIME NUMBER { + ra_prefix_conf->vltime = $3; + } + | PREFERRED LIFETIME NUMBER { + ra_prefix_conf->pltime = $3; + } + | ONLINK yesno { + ra_prefix_conf->lflag = $2; + } + | AUTONOMOUS ADDRESS_CONFIGURATION yesno { + ra_prefix_conf->aflag = $3; + } + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* This has to be sorted always. */ + static const struct keywords keywords[] = { + {"address", ADDRESS}, + {"address-configuration", ADDRESS_CONFIGURATION}, + {"auto", AUTO}, + {"autonomous", AUTONOMOUS}, + {"configuration", CONFIGURATION}, + {"default", DEFAULT}, + {"hop", HOP}, + {"include", INCLUDE}, + {"interface", RA_IFACE}, + {"lifetime", LIFETIME}, + {"limit", LIMIT}, + {"managed", MANAGED}, + {"no", NO}, + {"on-link", ONLINK}, + {"other", OTHER}, + {"preferred", PREFERRED}, + {"prefix", PREFIX}, + {"reachable", REACHABLE}, + {"retrans", RETRANS}, + {"router", ROUTER}, + {"time", TIME}, + {"timer", TIMER}, + {"valid", VALID}, + {"yes", YES}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return (c); +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return ('\n'); + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = igetc(); + } + } + return (c); +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "lungetc"); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* Skip to either EOF or the first real EOL. */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc(*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("%s: group writable or world read/writable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("calloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("strdup"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + log_warn("malloc"); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +struct rad_conf * +parse_config(char *filename) +{ + struct sym *sym, *next; + + conf = config_new_empty(); + ra_options = NULL; + + file = pushfile(filename, !(cmd_opts & OPT_NOACTION)); + if (file == NULL) { + free(conf); + return (NULL); + } + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { + if ((cmd_opts & OPT_VERBOSE2) && !sym->used) + fprintf(stderr, "warning: macro '%s' not used\n", + sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (errors) { + clear_config(conf); + return (NULL); + } + + return (conf); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(1, "cmdline_symset: malloc"); + + strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} + +struct ra_prefix_conf * +conf_get_ra_prefix(struct in6_addr *addr, int prefixlen) +{ + struct ra_prefix_conf *prefix; + + if (addr == NULL) { + if (ra_iface_conf->autoprefix != NULL) + return (ra_iface_conf->autoprefix); + } else { + SIMPLEQ_FOREACH(prefix, &ra_iface_conf->ra_prefix_list, entry) { + if (prefix->prefixlen == prefixlen && memcmp(addr, + &prefix->prefix, sizeof(*addr)) == 0) + return (prefix); + } + } + + prefix = calloc(1, sizeof(*prefix)); + if (prefix == NULL) + errx(1, "%s: calloc", __func__); + prefix->prefixlen = prefixlen; + prefix->vltime = 2592000; /* 30 days */ + prefix->pltime = 604800; /* 7 days */ + prefix->lflag = 1; + prefix->aflag = 1; + + if (addr == NULL) + ra_iface_conf->autoprefix = prefix; + else { + prefix->prefix = *addr; + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list, prefix, + entry); + } + + return (prefix); +} + +struct ra_iface_conf * +conf_get_ra_iface(char *name) +{ + struct ra_iface_conf *iface; + size_t n; + + SIMPLEQ_FOREACH(iface, &conf->ra_iface_list, entry) { + if (strcmp(name, iface->name) == 0) + return (iface); + } + + iface = calloc(1, sizeof(*iface)); + if (iface == NULL) + errx(1, "%s: calloc", __func__); + n = strlcpy(iface->name, name, sizeof(iface->name)); + if (n >= sizeof(iface->name)) + errx(1, "%s: name too long", __func__); + + /* Inherit attributes set in global section. */ + iface->ra_options = conf->ra_options; + + SIMPLEQ_INIT(&iface->ra_prefix_list); + + SIMPLEQ_INSERT_TAIL(&conf->ra_iface_list, iface, entry); + + return (iface); +} + +void +clear_config(struct rad_conf *xconf) +{ + struct ra_iface_conf *iface; + + while((iface = SIMPLEQ_FIRST(&xconf->ra_iface_list)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&xconf->ra_iface_list, entry); + free(iface); + } + + free(xconf); +} diff --git a/usr.sbin/rad/printconf.c b/usr.sbin/rad/printconf.c new file mode 100644 index 00000000000..57ccb2ccaf5 --- /dev/null +++ b/usr.sbin/rad/printconf.c @@ -0,0 +1,102 @@ +/* $OpenBSD: printconf.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser <florian@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@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/socket.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <net/if.h> + +#include <arpa/inet.h> + +#include <event.h> +#include <imsg.h> +#include <stdio.h> + +#include "rad.h" + +const char* yesno(int); +void print_ra_options(const char*, const struct ra_options_conf*); +void print_prefix_options(const char*, const struct ra_prefix_conf*); + +const char* +yesno(int flag) +{ + return flag ? "yes" : "no"; +} + +void +print_ra_options(const char *indent, const struct ra_options_conf *ra_options) +{ + printf("%sdefault router %s\n", indent, yesno(ra_options->dfr)); + printf("%shop limit %d\n", indent, ra_options->cur_hl); + printf("%smanaged address configuration %s\n", indent, + yesno(ra_options->m_flag)); + printf("%sother configuration %s\n", indent, yesno(ra_options->o_flag)); + printf("%srouter lifetime %d\n", indent, ra_options->router_lifetime); + printf("%sreachable time %u\n", indent, ra_options->reachable_time); + printf("%sretrans timer %u\n", indent, ra_options->retrans_timer); +} + +void +print_prefix_options(const char *indent, const struct ra_prefix_conf + *ra_prefix_conf) +{ + printf("%svalid lifetime %u\n", indent, ra_prefix_conf->vltime); + printf("%spreferred lifetime %u\n", indent, ra_prefix_conf->pltime); + printf("%son-link %s\n", indent, yesno(ra_prefix_conf->lflag)); + printf("%sautonomous address-configuration %s\n", indent, + yesno(ra_prefix_conf->aflag)); +} + +void +print_config(struct rad_conf *conf) +{ + struct ra_iface_conf *iface; + struct ra_prefix_conf *prefix; + char buf[INET6_ADDRSTRLEN], *bufp; + + print_ra_options("", &conf->ra_options); + printf("\n"); + + SIMPLEQ_FOREACH(iface, &conf->ra_iface_list, entry) { + printf("interface %s {\n", iface->name); + + print_ra_options("\t", &iface->ra_options); + + if (iface->autoprefix) { + printf("\tauto prefix {\n"); + print_prefix_options("\t\t", iface->autoprefix); + printf("\t}\n"); + } else + printf("\tno auto prefix\n"); + + SIMPLEQ_FOREACH(prefix, &iface->ra_prefix_list, entry) { + bufp = inet_net_ntop(AF_INET6, &prefix->prefix, + prefix->prefixlen, buf, sizeof(buf)); + printf("\tprefix %s {\n", bufp); + print_prefix_options("\t\t", prefix); + printf("\t}\n"); + } + + printf("}\n"); + } +} diff --git a/usr.sbin/rad/rad.8 b/usr.sbin/rad/rad.8 new file mode 100644 index 00000000000..c1017ae062d --- /dev/null +++ b/usr.sbin/rad/rad.8 @@ -0,0 +1,147 @@ +.\" $OpenBSD: rad.8,v 1.1 2018/07/10 16:39:54 florian Exp $ +.\" +.\" Copyright (c) 2018 Florian Obser <florian@openbsd.org> +.\" Copyright (c) 2016 Kenneth R Westerback <kwesterback@gmail.com> +.\" +.\" 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. +.\" +.Dd $Mdocdate: July 10 2018 $ +.Dt RAD 8 +.Os +.Sh NAME +.Nm rad +.Nd router advertisement daemon +.Sh SYNOPSIS +.Nm +.Op Fl dnv +.Op Fl f Ar file +.Op Fl s Ar socket +.Sh DESCRIPTION +.Nm +is an IPv6 router advertisement daemon. +It periodically sends IPv6 router advertisement messages with prefix +and default router informations. +Clients like +.Xr slaacd 8 +use these to configure IPv6 addresses on network interfaces and set default +routes. +Additionally it listens for IPv6 router solicitation messages and responds +with router advertisements. +.Pp +.Nm +is usually started at boot time, and can be enabled by +setting the following in +.Pa /etc/rc.conf.local : +.Pp +.Dl rad_flags=\&"\&" +.Pp +See +.Xr rc 8 +and +.Xr rc.conf 8 +for more information on the boot process +and enabling daemons. +.Pp +A running +.Nm +can be controlled with the +.Xr ractl 8 +utility. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl f Ar file +Specify an alternative configuration file. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl s Ar socket +Use an alternate location for the default control socket. +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/var/run/rad.sockXX" -compact +.It Pa /etc/rad.conf +Default +.Nm +configuration file. +.It Pa /var/run/rad.sock +.Ux Ns -domain +socket used for communication with +.Xr ractl 8 . +.El +.Sh SEE ALSO +.Xr rad.conf 5 , +.Xr ractl 8 +.Sh STANDARDS +.Rs +.%A R. Draves +.%A D. Thaler +.%D November 2005 +.%R RFC 4191 +.%T Default Router Preferences and More-Specific Routes +.Re +.Pp +.Rs +.%A R. Hinden +.%A S. Deering +.%D February 2006 +.%R RFC 4291 +.%T IP Version 6 Addressing Architecture +.Re +.Pp +.Rs +.%A T. Narten +.%A E. Nordmark +.%A W. Simpson +.%A H. Soliman +.%D September 2007 +.%R RFC 4861 +.%T Neighbor Discovery for IP version 6 (IPv6) +.Re +.Pp +.Rs +.%A A. Yourtchenko +.%A L. Colitti +.%D February 2016 +.%R RFC 7772 +.%T Reducing Energy Consumption of Router Advertisements +.Re +.Pp +.Rs +.%A J. Jeong +.%A S. Park +.%A L. Beloeil +.%A S. Madanapalli +.%D March 2017 +.%R RFC 8106 +.%T IPv6 Router Advertisement Options for DNS Configuration +.Re +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 6.4 . +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Florian Obser Aq Mt florian@openbsd.org . diff --git a/usr.sbin/rad/rad.c b/usr.sbin/rad/rad.c new file mode 100644 index 00000000000..ae3a4238817 --- /dev/null +++ b/usr.sbin/rad/rad.c @@ -0,0 +1,783 @@ +/* $OpenBSD: rad.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser <florian@openbsd.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/socket.h> +#include <sys/syslog.h> +#include <sys/uio.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <netinet6/in6_var.h> +#include <netinet/icmp6.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <netdb.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> + +#include "log.h" +#include "rad.h" +#include "frontend.h" +#include "engine.h" +#include "control.h" + +const char* imsg_type_name[] = { + "IMSG_NONE", + "IMSG_CTL_LOG_VERBOSE", + "IMSG_CTL_RELOAD", + "IMSG_CTL_SHOW_ENGINE_INFO", + "IMSG_CTL_SHOW_FRONTEND_INFO", + "IMSG_CTL_SHOW_MAIN_INFO", + "IMSG_CTL_END", + "IMSG_RECONF_CONF", + "IMSG_RECONF_RA_IFACE", + "IMSG_RECONF_RA_AUTOPREFIX", + "IMSG_RECONF_RA_PREFIX", + "IMSG_RECONF_END", + "IMSG_ICMP6SOCK", + "IMSG_STARTUP", + "IMSG_STARTUP_DONE", + "IMSG_SOCKET_IPC", +}; + +__dead void usage(void); +__dead void main_shutdown(void); + +void main_sig_handler(int, short, void *); + +static pid_t start_child(int, char *, int, int, int, char *); + +void main_dispatch_frontend(int, short, void *); +void main_dispatch_engine(int, short, void *); + +static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *); +static int main_imsg_send_config(struct rad_conf *); + +int main_reload(void); +int main_sendboth(enum imsg_type, void *, uint16_t); +void main_showinfo_ctl(struct imsg *); + +void free_ra_iface_conf(struct ra_iface_conf *); +void in6_prefixlen2mask(struct in6_addr *, int len); + +struct rad_conf *main_conf; +struct imsgev *iev_frontend; +struct imsgev *iev_engine; +char *conffile; +char *csock; + +pid_t frontend_pid; +pid_t engine_pid; + +uint32_t cmd_opts; + +void +main_sig_handler(int sig, short event, void *arg) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGTERM: + case SIGINT: + main_shutdown(); + case SIGHUP: + if (main_reload() == -1) + log_warnx("configuration reload failed"); + else + log_debug("configuration reloaded"); + break; + default: + fatalx("unexpected signal"); + } +} + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct event ev_sigint, ev_sigterm, ev_sighup; + struct icmp6_filter filt; + int ch; + int debug = 0, engine_flag = 0, frontend_flag = 0; + char *saved_argv0; + int pipe_main2frontend[2]; + int pipe_main2engine[2]; + int icmp6sock, on = 1; + + conffile = CONF_FILE; + csock = RAD_SOCKET; + + log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */ + log_setverbose(1); + + saved_argv0 = argv[0]; + if (saved_argv0 == NULL) + saved_argv0 = "rad"; + + while ((ch = getopt(argc, argv, "dEFf:ns:v")) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 'E': + engine_flag = 1; + break; + case 'F': + frontend_flag = 1; + break; + case 'f': + conffile = optarg; + break; + case 'n': + cmd_opts |= OPT_NOACTION; + break; + case 's': + csock = optarg; + break; + case 'v': + if (cmd_opts & OPT_VERBOSE) + cmd_opts |= OPT_VERBOSE2; + cmd_opts |= OPT_VERBOSE; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + if (argc > 0 || (engine_flag && frontend_flag)) + usage(); + + if (engine_flag) + engine(debug, cmd_opts & OPT_VERBOSE); + else if (frontend_flag) + frontend(debug, cmd_opts & OPT_VERBOSE, csock); + + /* parse config file */ + if ((main_conf = parse_config(conffile)) == NULL) { + exit(1); + } + + if (cmd_opts & OPT_NOACTION) { + if (cmd_opts & OPT_VERBOSE) + print_config(main_conf); + else + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + /* Check for root privileges. */ + if (geteuid()) + errx(1, "need root privileges"); + + /* Check for assigned daemon user */ + if (getpwnam(RAD_USER) == NULL) + errx(1, "unknown user %s", RAD_USER); + + log_init(debug, LOG_DAEMON); + log_setverbose(cmd_opts & OPT_VERBOSE); + + if (!debug) + daemon(1, 0); + + log_info("startup"); + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_main2frontend) == -1) + fatal("main2frontend socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_main2engine) == -1) + fatal("main2engine socketpair"); + + /* Start children. */ + engine_pid = start_child(PROC_ENGINE, saved_argv0, pipe_main2engine[1], + debug, cmd_opts & OPT_VERBOSE, NULL); + frontend_pid = start_child(PROC_FRONTEND, saved_argv0, + pipe_main2frontend[1], debug, cmd_opts & OPT_VERBOSE, csock); + + rad_process = PROC_MAIN; + log_procinit(log_procnames[rad_process]); + + event_init(); + + /* Setup signal handler. */ + signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, SIG_IGN); + + /* Setup pipes to children. */ + + if ((iev_frontend = malloc(sizeof(struct imsgev))) == NULL || + (iev_engine = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_frontend->ibuf, pipe_main2frontend[0]); + iev_frontend->handler = main_dispatch_frontend; + imsg_init(&iev_engine->ibuf, pipe_main2engine[0]); + iev_engine->handler = main_dispatch_engine; + + /* Setup event handlers for pipes to engine & frontend. */ + iev_frontend->events = EV_READ; + event_set(&iev_frontend->ev, iev_frontend->ibuf.fd, + iev_frontend->events, iev_frontend->handler, iev_frontend); + event_add(&iev_frontend->ev, NULL); + + iev_engine->events = EV_READ; + event_set(&iev_engine->ev, iev_engine->ibuf.fd, iev_engine->events, + iev_engine->handler, iev_engine); + event_add(&iev_engine->ev, NULL); + + if (main_imsg_send_ipc_sockets(&iev_frontend->ibuf, &iev_engine->ibuf)) + fatal("could not establish imsg links"); + + if ((icmp6sock = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, + IPPROTO_ICMPV6)) < 0) + fatal("ICMPv6 socket"); + + if (setsockopt(icmp6sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, + sizeof(on)) < 0) + fatal("IPV6_RECVPKTINFO"); + + if (setsockopt(icmp6sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, + sizeof(on)) < 0) + fatal("IPV6_RECVHOPLIMIT"); + + /* only router advertisements and solicitations */ + ICMP6_FILTER_SETBLOCKALL(&filt); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); + ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filt); + if (setsockopt(icmp6sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, + sizeof(filt)) == -1) + fatal("ICMP6_FILTER"); + + main_imsg_compose_frontend_fd(IMSG_ICMP6SOCK, 0, icmp6sock); + + main_imsg_send_config(main_conf); + + if (pledge("stdio rpath cpath sendfd", NULL) == -1) + fatal("pledge"); + + event_dispatch(); + + main_shutdown(); + return (0); +} + +__dead void +main_shutdown(void) +{ + pid_t pid; + int status; + + /* Close pipes. */ + msgbuf_clear(&iev_frontend->ibuf.w); + close(iev_frontend->ibuf.fd); + msgbuf_clear(&iev_engine->ibuf.w); + close(iev_engine->ibuf.fd); + + config_clear(main_conf); + + log_debug("waiting for children to terminate"); + do { + pid = wait(&status); + if (pid == -1) { + if (errno != EINTR && errno != ECHILD) + fatal("wait"); + } else if (WIFSIGNALED(status)) + log_warnx("%s terminated; signal %d", + (pid == engine_pid) ? "engine" : + "frontend", WTERMSIG(status)); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + free(iev_frontend); + free(iev_engine); + + control_cleanup(csock); + + log_info("terminating"); + exit(0); +} + +static pid_t +start_child(int p, char *argv0, int fd, int debug, int verbose, char *sockname) +{ + char *argv[7]; + int argc = 0; + pid_t pid; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + close(fd); + return (pid); + } + + if (dup2(fd, 3) == -1) + fatal("cannot setup imsg fd"); + + argv[argc++] = argv0; + switch (p) { + case PROC_MAIN: + fatalx("Can not start main process"); + case PROC_ENGINE: + argv[argc++] = "-E"; + break; + case PROC_FRONTEND: + argv[argc++] = "-F"; + break; + } + if (debug) + argv[argc++] = "-d"; + if (verbose) + argv[argc++] = "-v"; + if (sockname) { + argv[argc++] = "-s"; + argv[argc++] = sockname; + } + argv[argc++] = NULL; + + execvp(argv0, argv); + fatal("execvp"); +} + +void +main_dispatch_frontend(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0, verbose; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_STARTUP_DONE: + if (pledge("stdio rpath cpath", NULL) == -1) + fatal("pledge"); + break; + case IMSG_CTL_RELOAD: + if (main_reload() == -1) + log_warnx("configuration reload failed"); + else + log_warnx("configuration reloaded"); + break; + case IMSG_CTL_LOG_VERBOSE: + /* Already checked by frontend. */ + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_setverbose(verbose); + break; + case IMSG_CTL_SHOW_MAIN_INFO: + main_showinfo_ctl(&imsg); + break; + default: + log_debug("%s: error handling imsg %s", __func__, + imsg_type_name[imsg.hdr.type]); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +main_dispatch_engine(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +main_imsg_compose_frontend(int type, pid_t pid, void *data, uint16_t datalen) +{ + if (iev_frontend) + imsg_compose_event(iev_frontend, type, 0, pid, -1, data, + datalen); +} + +void +main_imsg_compose_frontend_fd(int type, pid_t pid, int fd) +{ + if (iev_frontend) + imsg_compose_event(iev_frontend, type, 0, pid, fd, NULL, 0); +} + + +void +main_imsg_compose_engine(int type, pid_t pid, void *data, uint16_t datalen) +{ + if (iev_engine) + imsg_compose_event(iev_engine, type, 0, pid, -1, data, + datalen); +} + +void +imsg_event_add(struct imsgev *iev) +{ + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, + pid_t pid, int fd, void *data, uint16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, + datalen)) != -1) + imsg_event_add(iev); + + return (ret); +} + +static int +main_imsg_send_ipc_sockets(struct imsgbuf *frontend_buf, + struct imsgbuf *engine_buf) +{ + int pipe_frontend2engine[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_frontend2engine) == -1) + return (-1); + + if (imsg_compose(frontend_buf, IMSG_SOCKET_IPC, 0, 0, + pipe_frontend2engine[0], NULL, 0) == -1) + return (-1); + if (imsg_compose(engine_buf, IMSG_SOCKET_IPC, 0, 0, + pipe_frontend2engine[1], NULL, 0) == -1) + return (-1); + + return (0); +} + +int +main_reload(void) +{ + struct rad_conf *xconf; + + if ((xconf = parse_config(conffile)) == NULL) + return (-1); + + if (main_imsg_send_config(xconf) == -1) + return (-1); + + merge_config(main_conf, xconf); + + return (0); +} + +int +main_imsg_send_config(struct rad_conf *xconf) +{ + struct ra_iface_conf *ra_iface_conf; + struct ra_prefix_conf *ra_prefix_conf; + + /* Send fixed part of config to children. */ + if (main_sendboth(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1) + return (-1); + + /* Send the interface list to children. */ + SIMPLEQ_FOREACH(ra_iface_conf, &xconf->ra_iface_list, entry) { + if (main_sendboth(IMSG_RECONF_RA_IFACE, ra_iface_conf, + sizeof(*ra_iface_conf)) == -1) + return (-1); + if (ra_iface_conf->autoprefix) { + if (main_sendboth(IMSG_RECONF_RA_AUTOPREFIX, + ra_iface_conf->autoprefix, + sizeof(*ra_iface_conf->autoprefix)) == -1) + return (-1); + } + SIMPLEQ_FOREACH(ra_prefix_conf, &ra_iface_conf->ra_prefix_list, + entry) { + if (main_sendboth(IMSG_RECONF_RA_PREFIX, + ra_prefix_conf, sizeof(*ra_prefix_conf)) == -1) + return (-1); + } + } + + /* Tell children the revised config is now complete. */ + if (main_sendboth(IMSG_RECONF_END, NULL, 0) == -1) + return (-1); + + return (0); +} + +int +main_sendboth(enum imsg_type type, void *buf, uint16_t len) +{ + if (imsg_compose_event(iev_frontend, type, 0, 0, -1, buf, len) == -1) + return (-1); + if (imsg_compose_event(iev_engine, type, 0, 0, -1, buf, len) == -1) + return (-1); + return (0); +} + +void +main_showinfo_ctl(struct imsg *imsg) +{ + struct ctl_main_info cmi; + size_t n; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_MAIN_INFO: + memset(cmi.text, 0, sizeof(cmi.text)); + n = strlcpy(cmi.text, "I'm a little teapot.", + sizeof(cmi.text)); + if (n >= sizeof(cmi.text)) + log_debug("%s: I was cut off!", __func__); + main_imsg_compose_frontend(IMSG_CTL_SHOW_MAIN_INFO, + imsg->hdr.pid, &cmi, sizeof(cmi)); + memset(cmi.text, 0, sizeof(cmi.text)); + n = strlcpy(cmi.text, "Full of sencha.", + sizeof(cmi.text)); + if (n >= sizeof(cmi.text)) + log_debug("%s: I was cut off!", __func__); + main_imsg_compose_frontend(IMSG_CTL_SHOW_MAIN_INFO, + imsg->hdr.pid, &cmi, sizeof(cmi)); + main_imsg_compose_frontend(IMSG_CTL_END, imsg->hdr.pid, NULL, + 0); + break; + default: + log_debug("%s: error handling imsg", __func__); + break; + } +} + +void +free_ra_iface_conf(struct ra_iface_conf *ra_iface_conf) +{ + struct ra_prefix_conf *prefix; + + if (!ra_iface_conf) + return; + + free(ra_iface_conf->autoprefix); + + while ((prefix = SIMPLEQ_FIRST(&ra_iface_conf->ra_prefix_list)) + != NULL) { + SIMPLEQ_REMOVE_HEAD(&ra_iface_conf->ra_prefix_list, entry); + free(prefix); + } + + free(ra_iface_conf); +} + +void +merge_config(struct rad_conf *conf, struct rad_conf *xconf) +{ + struct ra_iface_conf *ra_iface_conf; + + conf->ra_options = xconf->ra_options; + + /* Remove & discard existing interfaces. */ + while ((ra_iface_conf = SIMPLEQ_FIRST(&conf->ra_iface_list)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&conf->ra_iface_list, entry); + free_ra_iface_conf(ra_iface_conf); + } + + /* Add new interfaces. */ + while ((ra_iface_conf = SIMPLEQ_FIRST(&xconf->ra_iface_list)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&xconf->ra_iface_list, entry); + SIMPLEQ_INSERT_TAIL(&conf->ra_iface_list, ra_iface_conf, entry); + } + + free(xconf); +} + +struct rad_conf * +config_new_empty(void) +{ + struct rad_conf *xconf; + + xconf = calloc(1, sizeof(*xconf)); + if (xconf == NULL) + fatal(NULL); + + SIMPLEQ_INIT(&xconf->ra_iface_list); + + xconf->ra_options.dfr = 1; + xconf->ra_options.cur_hl = 0; + xconf->ra_options.m_flag = 0; + xconf->ra_options.o_flag = 0; + xconf->ra_options.router_lifetime = 1800; + xconf->ra_options.reachable_time = 0; + xconf->ra_options.retrans_timer = 0; + + return (xconf); +} + +void +config_clear(struct rad_conf *conf) +{ + struct rad_conf *xconf; + + /* Merge current config with an empty config. */ + xconf = config_new_empty(); + merge_config(conf, xconf); + + free(conf); +} + +void mask_prefix(struct in6_addr* in6, int len) +{ + uint8_t bitmask[8] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe}; + int i, skip; + + if (len < 0 || len > 128) + fatalx("invalid prefix length: %d", len); + + skip = len / 8; + + in6->s6_addr[skip] &= bitmask[len % 8]; + + for (i = skip + 1; i < 16; i++) + in6->s6_addr[i] = 0; +} + +const char* +sin6_to_str(struct sockaddr_in6 *sin6) +{ + static char hbuf[NI_MAXHOST]; + int error; + + error = getnameinfo((struct sockaddr *)sin6, sin6->sin6_len, hbuf, + sizeof(hbuf), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV); + if (error) { + log_warnx("%s", gai_strerror(error)); + strlcpy(hbuf, "unknown", sizeof(hbuf)); + } + return hbuf; +} + +const char* +in6_to_str(struct in6_addr *in6) +{ + + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_len = sizeof(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = *in6; + + return (sin6_to_str(&sin6)); +} diff --git a/usr.sbin/rad/rad.conf.5 b/usr.sbin/rad/rad.conf.5 new file mode 100644 index 00000000000..4cd90836e94 --- /dev/null +++ b/usr.sbin/rad/rad.conf.5 @@ -0,0 +1,148 @@ +.\" $OpenBSD: rad.conf.5,v 1.1 2018/07/10 16:39:54 florian Exp $ +.\" +.\" Copyright (c) 2018 Florian Obser <florian@openbsd.org> +.\" Copyright (c) 2005 Esben Norby <norby@openbsd.org> +.\" Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org> +.\" Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> +.\" Copyright (c) 2002 Daniel Hartmeier <dhartmei@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. +.\" +.Dd $Mdocdate: July 10 2018 $ +.Dt RAD.CONF 5 +.Os +.Sh NAME +.Nm rad.conf +.Nd router advertisement daemon configuration file +.Sh DESCRIPTION +The +.Xr rad 8 +daemon is an IPv6 router advertisement daemon. +.Pp +The +.Nm +config file is divided into the following main sections: +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Global Configuration +Global settings for +.Xr rad 8 . +These are used as default values for +.Ic interface +definitions and can be overwritten in an +.Ic interface +block. +.It Sy Interfaces +.Xr rad 8 +sends IPv6 router advertisement messages. +This section defines on which interfaces to advertise prefix information +and their associated parameters. +.El +.Pp +Additional configuration files can be included with the +.Ic include +keyword. +.Sh MACROS +Macros can be defined that will later be expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters. +Macro names may not be reserved words (for example, +.Ic interface ) +Macros are not expanded inside quotes. +.Sh GLOBAL CONFIGURATION +The global configuration section sets defaults for router advertisement +messages. +These can be overwritten in interface blocks. +.Pp +.Bl -tag -width Ds -compact +.It Ic default router Pq Ic yes Ns | Ns Ic no +act as a default router or not. +Default is yes. +.It Ic hop limit Ar hops +specifies the diameter of the internet. +Default is 0 meaning unspecified by this router. +.It managed address configuration Pq Ic yes Ns | Ns Ic no +if set to yes indicates that stateless address configuration prefixes are +not available and hosts should consult DHCPv6. +Default is no. +.It other configuration Pq Ic yes Ns | Ns Ic no +if set to yes hosts should consult DHCPv6 for additional configuration +like NTP servers or DNS resolvers. +.It Ic router lifetime Ar seconds +number of seconds this router is a valid default router after receiving +a router advertisement message. +Default 1800 seconds. +.\" .It Ic reachable time Ar number +.\" XXX +.\" .It Ic retrans timer Ar number +.\" XXX +.El +.Sh INTERFACES +A list of interfaces to send advertisments on. +.Bl -tag -width interface +.It Ic interface Ar name Op { prefix list } +.El +.Pp +Options set in the global section can be overwritten inside an interface +block. +In addition an interface block can contain a list of prefixes: +.Bl -tag -width prefix +.It Oo Ic no Oc Ic auto prefix Op { prefix options } +.It Ic prefix Ar prefix Op { prefix options } +.El +.Pp +The default is to discover prefixes to announce by inspecting the IPv6 +addresses configured on an interface. +This can be disabled with +.Ic no auto prefix . +.Pp +.Ic prefix +options are as follows: +.Pp +.Bl -tag -width Ds -compact +.It Ic autonomous address-configuration Pq Ic yes Ns | Ns Ic no +this prefix can be used to generate IPv6 addresses. +The default is yes. +.It Ic on-link Pq Ic yes Ns | Ns Ic no +this prefix shall be considered on-link. +The default is yes. +.It Ic preferred lifetime Ar seconds +preferred lifetime (pltime) in seconds for addresses generated from this +prefix. +The default is 604800. +.It Ic valid lifetime Ar seconds +valid lifetime (vltime) in seconds for addresses generated from this +prefix. +The default is 2592000. +.El +.Sh FILES +.Bl -tag -width "/etc/rad.conf" -compact +.It Pa /etc/rad.conf +.Xr rad 8 +configuration file +.El +.Sh EXAMPLES +.Bd -literal -offset indent +interface ix1 +.Ed +.Sh SEE ALSO +.Xr ractl 8 , +.Xr rad 8 , +.Xr rc.conf.local 8 +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 6.4 . diff --git a/usr.sbin/rad/rad.h b/usr.sbin/rad/rad.h new file mode 100644 index 00000000000..d670d1d18ea --- /dev/null +++ b/usr.sbin/rad/rad.h @@ -0,0 +1,161 @@ +/* $OpenBSD: rad.h,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2018 Florian Obser <florian@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +#define CONF_FILE "/etc/rad.conf" +#define RAD_SOCKET "/var/run/rad.sock" +#define RAD_USER "_rtadvd" /* XXX */ + +#define OPT_VERBOSE 0x00000001 +#define OPT_VERBOSE2 0x00000002 +#define OPT_NOACTION 0x00000004 + +#define RAD_MAXTEXT 256 + +enum { + PROC_MAIN, + PROC_ENGINE, + PROC_FRONTEND +} rad_process; + +static const char * const log_procnames[] = { + "main", + "engine", + "frontend", +}; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + short events; +}; + +enum imsg_type { + IMSG_NONE, + IMSG_CTL_LOG_VERBOSE, + IMSG_CTL_RELOAD, + IMSG_CTL_SHOW_ENGINE_INFO, + IMSG_CTL_SHOW_FRONTEND_INFO, + IMSG_CTL_SHOW_MAIN_INFO, + IMSG_CTL_END, + IMSG_RECONF_CONF, + IMSG_RECONF_RA_IFACE, + IMSG_RECONF_RA_AUTOPREFIX, + IMSG_RECONF_RA_PREFIX, + IMSG_RECONF_END, + IMSG_ICMP6SOCK, + IMSG_STARTUP, + IMSG_STARTUP_DONE, + IMSG_RA_RS, + IMSG_SEND_RA, + IMSG_SOCKET_IPC +}; + +extern const char* imsg_type_name[]; + +/* RFC 4861 Section 4.2 */ +struct ra_options_conf { + int dfr; /* is default router? */ + int cur_hl; /* current hop limit */ + int m_flag; /* managed address conf flag */ + int o_flag; /* other conf flag */ + int router_lifetime; /* default router lifetime */ + uint32_t reachable_time; + uint32_t retrans_timer; +}; + +/* RFC 4861 Section 4.6.2 */ +struct ra_prefix_conf { + SIMPLEQ_ENTRY(ra_prefix_conf) entry; + struct in6_addr prefix; /* prefix */ + int prefixlen; /* prefix length */ + uint32_t vltime; /* valid lifetime */ + uint32_t pltime; /* prefered lifetime */ + int lflag; /* on-link flag*/ + int aflag; /* autonom. addr flag */ +}; + +struct ra_iface_conf { + SIMPLEQ_ENTRY(ra_iface_conf) entry; + struct ra_options_conf ra_options; + struct ra_prefix_conf *autoprefix; + SIMPLEQ_HEAD(ra_prefix_conf_head, + ra_prefix_conf) ra_prefix_list; + char name[IF_NAMESIZE]; +}; + +struct rad_conf { + struct ra_options_conf ra_options; + SIMPLEQ_HEAD(ra_iface_conf_head, ra_iface_conf) ra_iface_list; +}; + +struct ctl_frontend_info { + int yesno; + int integer; + char global_text[RAD_MAXTEXT]; +}; + +struct ctl_engine_info { + char name[IF_NAMESIZE]; + int yesno; + int integer; +}; + +struct ctl_main_info { + char text[RAD_MAXTEXT]; +}; + +struct imsg_ra_rs { + uint32_t if_index; + struct sockaddr_in6 from; + ssize_t len; + uint8_t packet[1500]; +}; + +struct imsg_send_ra { + uint32_t if_index; + struct sockaddr_in6 to; +}; + +extern uint32_t cmd_opts; + +/* rad.c */ +void main_imsg_compose_frontend(int, pid_t, void *, uint16_t); +void main_imsg_compose_frontend_fd(int, pid_t, int); + +void main_imsg_compose_engine(int, pid_t, void *, uint16_t); +void merge_config(struct rad_conf *, struct rad_conf *); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t, + int, void *, uint16_t); + +struct rad_conf *config_new_empty(void); +void config_clear(struct rad_conf *); + +void mask_prefix(struct in6_addr*, int len); +const char *sin6_to_str(struct sockaddr_in6 *); +const char *in6_to_str(struct in6_addr *); + +/* printconf.c */ +void print_config(struct rad_conf *); + +/* parse.y */ +struct rad_conf *parse_config(char *); +int cmdline_symset(char *); |