commit - /dev/null
commit + 8d1b399bf516b09e00a953ce65ee1d67e4854251
blob - /dev/null
blob + 3e6292ab62fb7a49a707cf7e5bcf9b3773e9ed81 (mode 644)
--- /dev/null
+++ .gitignore
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.h.in
+configure
+depcomp
+install-sh
+missing
+ylwrap
+stamp-h1
+config.status
+config.log
+config.h
+.deps
+
+compile_flags.txt
+
+kamid
+kamictl
+*.o
+kamid.conf
+parse.c
blob - /dev/null
blob + a1f37807e9f87083a9511dc3c4339e97fd60d74f (mode 644)
--- /dev/null
+++ LICENSE
+Copyright 2021 Omar Polo
+
+Permission to use, copy, modify, and/or 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.
blob - /dev/null
blob + 283776fb74ccd4126192e81c47f378ef88d4160d (mode 644)
--- /dev/null
+++ Makefile.am
+bin_PROGRAMS = kamictl kamid
+
+kamictl_SOURCES = compat.h \
+ compat/*.[ch] \
+ ctl_parser.c \
+ ctl_parser.h \
+ kamictl.c \
+ log.c \
+ log.h \
+ sandbox.c \
+ sandbox.h
+
+kamid_SOURCES = client.c \
+ client.h \
+ control.c \
+ control.h \
+ compat.h \
+ compat/*.[ch] \
+ kamid.c \
+ kamid.h \
+ listener.c \
+ listener.h \
+ log.c \
+ log.h \
+ parse.y \
+ sandbox.c \
+ sandbox.h \
+ table.c \
+ table.h \
+ table_static.c \
+ utils.c \
+ utils.h
+
+LDADD = $(LIBOBJ)
+
+BUILT_SOURCES = compile_flags.txt
+CLEANFILES = compile_flags.txt
+
+dist_doc_DATA = README.md LICENSE
+dist_man5_MANS = kamid.conf.5
+dist_man8_MANS = kamictl.8 kamid.8
+
+compile_flags.txt:
+ printf "%s\n" ${CFLAGS} > compile_flags.txt
blob - /dev/null
blob + a581c7e45eee806ee4efc42cfa56e70a18032bb0 (mode 644)
--- /dev/null
+++ README.md
+# kamid -- 9p file server daemon
+
+kamid is a FREE implementation of a 9p file server daemon for
+UNIX-like systems.
+
+
+## Building
+
+kamid depends on libtls, libevent and yacc/GNU bison. To build from a
+release tarball:
+
+ ./configure
+ make
+ sudo make install # eventually
+
+to build from a git checkout:
+
+ ./bootstrap
+ ./configure
+ make
+
+
+## License
+
+kamid is released under a BSD-like license. The bulk of the code is
+under the ISC license, but some file are BSD2 or BSD3.
blob - /dev/null
blob + 70027cbf3cf809c67ca8b5cff38df304aa29bcf6 (mode 755)
--- /dev/null
+++ bootstrap
+#!/bin/sh
+
+exec autoreconf -vfi
blob - /dev/null
blob + a667f8059a426d01cbf9b232436418e8945da1f3 (mode 644)
--- /dev/null
+++ client.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#include "compat.h"
+
+#include <errno.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "kamid.h"
+#include "log.h"
+#include "sandbox.h"
+
+static struct imsgev *iev_listener;
+
+static __dead void client_shutdown(void);
+static void client_sig_handler(int, short, void *);
+static void client_dispatch_listener(int, short, void *);
+static void client_privdrop(const char *, const char *);
+
+static int client_imsg_compose_listener(int, uint32_t,
+ const void *, uint16_t);
+
+__dead void
+client(int debug, int verbose)
+{
+ struct event ev_sigint, ev_sigterm;
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ setproctitle("client");
+ log_procinit("client");
+
+ log_debug("warming up");
+
+ event_init();
+
+ /* Setup signal handlers */
+ signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, client_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 listener process */
+ if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL)
+ fatal(NULL);
+
+ imsg_init(&iev_listener->ibuf, 3);
+ iev_listener->handler = client_dispatch_listener;
+
+ /* Setup event handlers. */
+ iev_listener->events = EV_READ;
+ event_set(&iev_listener->ev, iev_listener->ibuf.fd,
+ iev_listener->events, iev_listener->handler, iev_listener);
+ event_add(&iev_listener->ev, NULL);
+
+ log_debug("before dispatch");
+ event_dispatch();
+ client_shutdown();
+}
+
+static __dead void
+client_shutdown(void)
+{
+ msgbuf_clear(&iev_listener->ibuf.w);
+ close(iev_listener->ibuf.fd);
+
+ free(iev_listener);
+
+ log_info("client exiting");
+ exit(0);
+}
+
+static void
+client_sig_handler(int sig, short event, void *d)
+{
+ /*
+ * Normal signal handler rules don't apply because libevent
+ * decouples for us.
+ */
+
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ client_shutdown();
+ default:
+ fatalx("unexpected signal %d", sig);
+ }
+}
+
+#define AUTH_NONE 0
+#define AUTH_USER 1
+#define AUTH_DONE 2
+
+static void
+client_dispatch_listener(int fd, short event, void *d)
+{
+ static int auth = AUTH_NONE;
+ static char username[64] = {0};
+ static char dir[PATH_MAX] = {0};
+ struct imsg imsg;
+ struct imsgev *iev = d;
+ struct imsgbuf *ibuf;
+ 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;
+
+ log_debug("client: got message type %d", imsg.hdr.type);
+ switch (imsg.hdr.type) {
+ case IMSG_AUTH:
+ if (auth)
+ fatalx("%s: IMSG_AUTH already done", __func__);
+ auth = AUTH_USER;
+ ((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0';
+ strlcpy(username, imsg.data, sizeof(username));
+ break;
+ case IMSG_AUTH_DIR:
+ if (auth != AUTH_USER)
+ fatalx("%s: IMSG_AUTH_DIR not after IMSG_AUTH",
+ __func__);
+ auth = AUTH_DONE;
+ ((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0';
+ strlcpy(dir, imsg.data, sizeof(dir));
+ client_privdrop(username, dir);
+ memset(username, 0, sizeof(username));
+ memset(dir, 0, sizeof(username));
+ break;
+ case IMSG_BUF:
+ /* echo! */
+ client_imsg_compose_listener(IMSG_BUF, imsg.hdr.peerid,
+ imsg.data, IMSG_DATA_SIZE(imsg));
+ break;
+ case IMSG_CONN_GONE:
+ log_debug("closing");
+ shut = 1;
+ 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);
+ log_warnx("pipe closed, shutting down...");
+ event_loopexit(NULL);
+ }
+}
+
+static void
+client_privdrop(const char *username, const char *dir)
+{
+ struct passwd *pw;
+
+ setproctitle("client %s", username);
+
+ if ((pw = getpwnam(username)) == NULL)
+ fatalx("getpwnam(%s) failed", username);
+
+ if (chroot(dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+
+ 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");
+
+ sandbox_client();
+ log_debug("client ready");
+}
+
+static int
+client_imsg_compose_listener(int type, uint32_t peerid,
+ const void *data, uint16_t len)
+{
+ int ret;
+
+ if ((ret = imsg_compose(&iev_listener->ibuf, type, peerid, 0, -1,
+ data, len)) != -1)
+ imsg_event_add(iev_listener);
+
+ return ret;
+}
blob - /dev/null
blob + a007ec7f0ca02d97660866c9201bfe28e6af56a4 (mode 644)
--- /dev/null
+++ client.h
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "compat.h"
+
+__dead void client(int, int);
+
+#endif
blob - /dev/null
blob + 1b50f0c49503e60bd7ce9207d9cc07e7686673c2 (mode 644)
--- /dev/null
+++ compat/getprogname.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#include "compat.h"
+
+#ifdef HAVE_PROGRAM_INVOCATION_SHORT_NAME
+
+#include <errno.h>
+
+extern char *program_invocation_short_name;
+
+const char *
+getprogname(void)
+{
+ return program_invocation_short_name;
+}
+
+#else
+
+const char *
+getprogname(void)
+{
+ return "kamid";
+}
+
+#endif
blob - /dev/null
blob + 0ee7185b60aaf71dee305cd764f52643acccd3a1 (mode 644)
--- /dev/null
+++ compat/setprocname.c
+/*
+ * Copyright (c) 2016 Nicholas Marriott <nicholas.marriott@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 MIND, 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 "../compat.h"
+
+#include <sys/types.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#if HAVE_PR_SET_NAME
+
+#include <sys/prctl.h>
+
+void
+setproctitle(const char *fmt, ...)
+{
+ char title[16], name[16], *cp;
+ va_list ap;
+ int used;
+
+ va_start(ap, fmt);
+ vsnprintf(title, sizeof title, fmt, ap);
+ va_end(ap);
+
+ used = snprintf(name, sizeof name, "%s: %s", getprogname(), title);
+ if (used >= (int)sizeof name) {
+ cp = strrchr(name, ' ');
+ if (cp != NULL)
+ *cp = '\0';
+ }
+ prctl(PR_SET_NAME, name);
+}
+#else
+void
+setproctitle(const char *fmt, ...)
+{
+ (void)fmt;
+}
+#endif
blob - /dev/null
blob + b114fceb83c0e6f8c970e6773504d2f659d179bd (mode 644)
--- /dev/null
+++ compat.h
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#ifndef COMPAT_H
+#define COMPAT_H
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef HAVE_QUEUE_H
+# include <sys/queue.h>
+#else
+# include "compat/queue.h"
+#endif
+
+#ifdef HAVE_LIBUTIL
+# include <imsg.h>
+# include <ohash.h>
+# include <util.h>
+#else
+# include "compat/imsg.h"
+# include "compat/ohash.h"
+# define FMT_SCALED_STRSIZE 7 /* minus sign, 4 digits, suffix, null byte */
+int fmt_scaled(long long, char *);
+#endif
+
+#ifndef HAVE_GETPROGNAME
+const char *getprogname(void);
+#endif
+
+#ifndef HAVE_SETPROGNAME
+void setproctitle(const char *, ...);
+#endif
+
+#endif
blob - /dev/null
blob + dafd12868478445d7aa9b8d670211df2247e16db (mode 644)
--- /dev/null
+++ configure.ac
+AC_INIT([kamid], [0.1], [kamid@omarpolo.com], [kamid], [gemini://kamid.omarpolo.com])
+AC_CONFIG_LIBOBJ_DIR(compat)
+AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
+AC_PROG_CC
+AC_USE_SYSTEM_EXTENSIONS
+AC_PROG_YACC
+
+AC_REPLACE_FUNCS([
+ getprogname \
+ setprogname \
+])
+
+AC_MSG_CHECKING([for sys/queue.h with TAILQ_FOREACH_SAFE])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#include <sys/queue.h>
+#include <stddef.h>
+], [
+ TAILQ_HEAD(tailhead, entry) head;
+ struct entry {
+ TAILQ_ENTRY(entry) entries;
+ } *np, *nt;
+ TAILQ_INIT(&head);
+ TAILQ_FOREACH_SAFE(np, &head, entries, nt) {
+ /* nop */ ;
+ }
+ return 0;
+])], [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE([HAVE_QUEUE_H], 1, [QUEUE_H])
+], AC_MSG_RESULT(no))
+
+AC_CHECK_DECL(PR_SET_NAME, AC_DEFINE([HAVE_PR_SET_NAME], 1, [pr_set_name]), [],
+ [[#include <sys/prctl.h>]])
+
+AC_CHECK_LIB(tls, tls_init, [], [
+ AC_MSG_ERROR([requires libtls])
+])
+
+AC_CHECK_LIB(event, event_init, [], [
+ AC_MSG_ERROR([requires libevent])
+])
+
+AC_CHECK_LIB(util, imsg_init, [], [
+ AC_LIBOBJ(imsg)
+ AC_LIBOBJ(imsg-buffer)
+])
+
+# check compiler flags
+AC_DEFUN([CC_ADD_CHECK_FLAGS], [
+ AC_MSG_CHECKING([if $CC supports $1 flag])
+ old_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $1"
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],
+ AC_MSG_RESULT(yes),
+ AC_MSG_RESULT(no)
+ CFLAGS="$old_CFLAGS")
+])
+CC_ADD_CHECK_FLAGS([-Wall])
+CC_ADD_CHECK_FLAGS([-Wextra])
+CC_ADD_CHECK_FLAGS([-Wmissing-prototypes])
+CC_ADD_CHECK_FLAGS([-Wstrict-prototypes])
+CC_ADD_CHECK_FLAGS([-Wwrite-strings])
+CC_ADD_CHECK_FLAGS([-Wno-unused-parameter])
+
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_FILES([
+ Makefile
+])
+
+AC_OUTPUT
blob - /dev/null
blob + 380aa0cd20327bab3abc65db7f51690a8f5fa4d5 (mode 644)
--- /dev/null
+++ control.c
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "kamid.h"
+#include "control.h"
+#include "listener.h"
+
+#define CONTROL_BACKLOG 5
+
+struct {
+ struct event ev;
+ struct event evt;
+ int fd;
+} control_state = {.fd = -1};
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ struct imsgev iev;
+};
+
+TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns);
+
+struct ctl_conn *control_connbyfd(int);
+struct ctl_conn *control_connbypid(pid_t);
+void control_close(int);
+
+int
+control_init(const 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);
+ }
+
+ return (fd);
+}
+
+int
+control_listen(int fd)
+{
+ if (control_state.fd != -1)
+ fatalx("%s: received unexpected controlsock", __func__);
+
+ control_state.fd = fd;
+ 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_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:
+ listener_imsg_compose_main(imsg.hdr.type, 0, NULL, 0);
+ break;
+ case IMSG_CTL_LOG_VERBOSE:
+ if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
+ break;
+
+ /* Forward to all other processes. */
+ listener_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid,
+ imsg.data, IMSG_DATA_SIZE(imsg));
+
+ /* XXX: send to every client? */
+
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_setverbose(verbose);
+ 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_DATA_SIZE(*imsg)));
+}
blob - /dev/null
blob + 81ce3ca4ad9e77106d6d9b9d2f3d4af999039bdc (mode 644)
--- /dev/null
+++ control.h
+/*
+ * 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 CONTROL_H
+#define CONTROL_H
+
+#include "compat.h"
+
+int control_init(const char *);
+int control_listen(int fd);
+void control_accept(int, short, void *);
+void control_dispatch_imsg(int, short, void *);
+int control_imsg_relay(struct imsg *);
+
+#endif
blob - /dev/null
blob + 491222d3473842f9bcedd0560be0a98b6150472b (mode 644)
--- /dev/null
+++ ctl_parser.c
+/*
+ * 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 <stdio.h>
+#include <string.h>
+
+#include "ctl_parser.h"
+#include "kamid.h"
+
+enum token_type {
+ NOTOKEN,
+ ENDTOKEN,
+ KEYWORD,
+};
+
+
+struct token {
+ enum token_type type;
+ const char *keyword;
+ int value;
+ const struct token *next;
+};
+
+static const struct token t_main[];
+static const struct token t_log[];
+
+static const struct token t_main[] = {
+ {KEYWORD, "reload", RELOAD, NULL},
+ {KEYWORD, "log", NONE, t_log},
+ {ENDTOKEN, "", NONE, NULL},
+};
+
+static const struct token t_log[] = {
+ {KEYWORD, "verbose", LOG_VERBOSE, NULL},
+ {KEYWORD, "brief", LOG_BRIEF, NULL},
+ {ENDTOKEN, "", NONE, NULL},
+};
+
+static const struct token *match_token(const char *, const struct token *,
+ struct parse_result *);
+static void show_valid_args(const struct token *);
+
+struct parse_result *
+parse(int argc, char **argv)
+{
+ static struct parse_result res;
+ const struct token *table = t_main;
+ const struct token *match;
+
+ memset(&res, 0, sizeof(res));
+
+ while (argc >= 0) {
+ if ((match = match_token(argv[0], table, &res)) == NULL) {
+ fprintf(stderr, "valid commands/args:\n");
+ show_valid_args(table);
+ return NULL;
+ }
+
+ argc--;
+ argv++;
+
+ if (match->type == NOTOKEN || match->next == NULL)
+ break;
+
+ table = match->next;
+ }
+
+ if (argc > 0) {
+ fprintf(stderr, "superfluous argument: %s\n", argv[0]);
+ return NULL;
+ }
+
+ return &res;
+}
+
+static const struct token *
+match_token(const char *word, const struct token *table,
+ struct parse_result *res)
+{
+ size_t i, match;
+ const struct token *t = NULL;
+
+ match = 0;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ if (word == NULL || strlen(word) == 0) {
+ match++;
+ t = &table[i];
+ }
+ break;
+ case KEYWORD:
+ if (word != NULL && strncmp(word, table[i].keyword,
+ strlen(word)) == 0) {
+ match++;
+ t = &table[i];
+ if (t->value)
+ res->action = t->value;
+ }
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+
+ if (match != 1) {
+ if (word == NULL)
+ fprintf(stderr, "missing argument:\n");
+ else if (match > 1)
+ fprintf(stderr, "ambiuous argument: %s\n", word);
+ else if (match < 1)
+ fprintf(stderr, "unknown argument: %s\n", word);
+ return NULL;
+ }
+
+ return t;
+}
+
+static void
+show_valid_args(const struct token *table)
+{
+ int i;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ fprintf(stderr, " <cr>\n");
+ break;
+ case KEYWORD:
+ fprintf(stderr, " %s\n", table[i].keyword);
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+}
blob - /dev/null
blob + 1c41496f316a505b67099be11f5f5d3deec51a7b (mode 644)
--- /dev/null
+++ ctl_parser.h
+/*
+ * 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.
+ */
+
+#ifndef CTL_PARSER_H
+#define CTL_PARSER_H
+
+enum actions {
+ NONE,
+ LOG_VERBOSE,
+ LOG_BRIEF,
+ RELOAD,
+};
+
+struct parse_result {
+ enum actions action;
+};
+
+struct parse_result *parse(int, char **);
+
+#endif
blob - /dev/null
blob + a15881b0f5c97d15001742bbfee8b7783630fc90 (mode 644)
--- /dev/null
+++ kamictl.8
+.\" Copyright (c) 2021 Omar Polo <op@omarpolo.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 07 2021 $
+.Dt KAMICTL 8
+.Os
+.Sh NAME
+.Nm kamictl
+.Nd control the kamid daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl s Ar socket
+.Ar command
+.Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+program contrlos the
+.Xr kamid 8
+daemon.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl s Ar socket
+Use
+.Ar socket
+instead of the default
+.Pa /var/run/kamid.sock
+to communicate with
+.Xr kamid 8 .
+.El
+.Pp
+The following commands are available:
+.Bl -tag -width Ds
+.It Cm log brief
+Disable verbose debug logging.
+.It Cm log verbose
+Enable verbose debug logging.
+.It Cm reload
+Reload the configuration file.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/kamid.sockXX" -compact
+.It Pa /var/run/kamid.sock
+UNIX-domain socket used for communication with
+.Xr kamid 8 .
+.El
+.Sh SEE ALSO
+.Xr kamid.conf 5 ,
+.Xr kamid 8
blob - /dev/null
blob + e6fc64817d33ec6dfb448540ad0c05c9151903bd (mode 644)
--- /dev/null
+++ kamictl.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "ctl_parser.h"
+#include "kamid.h"
+#include "log.h"
+
+__dead void usage(void);
+
+struct imsgbuf *ibuf;
+
+__dead void
+usage(void)
+{
+ /*
+ * XXX: this will print `kamid' if compat/getprogname.c is
+ * used.
+ */
+ fprintf(stderr, "usage: %s [-s socket] command [argument ...]\n",
+ getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct sockaddr_un sun;
+ struct parse_result *res;
+ struct imsg imsg;
+ int ctl_sock;
+ int done = 0;
+ int n, verbose = 0;
+ int ch;
+ const char *sockname;
+
+ log_init(1, LOG_DAEMON); /* Log to stderr. */
+
+ sockname = KD_SOCKET;
+ while ((ch = getopt(argc, argv, "s:")) != -1) {
+ switch (ch) {
+ case 's':
+ sockname = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* parse command line */
+ if ((res = parse(argc, argv)) == NULL)
+ exit(1);
+
+ /* connect to control socket */
+ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path));
+
+ if (connect(ctl_sock, (struct sockaddr*)&sun, sizeof(sun)) == -1)
+ err(1, "connect: %s", sockname);
+
+#ifdef __OpenBSD__
+ if (pledge("stdio", NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ if ((ibuf = calloc(1, sizeof(*ibuf))) == NULL)
+ err(1, NULL);
+ imsg_init(ibuf, ctl_sock);
+ done = 0;
+
+ /* process user request */
+ switch (res->action) {
+ case LOG_VERBOSE:
+ verbose = 1;
+ /* fallthrough */
+ case LOG_BRIEF:
+ imsg_compose(ibuf, IMSG_CTL_LOG_VERBOSE, 0, 0, -1,
+ &verbose, sizeof(verbose));
+ puts("logging request sent.");
+ done = 1;
+ break;
+ case RELOAD:
+ imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, NULL, 0);
+ puts("reload request sent.");
+ done = 1;
+ break;
+ default:
+ usage();
+ }
+
+ imsg_flush(ibuf);
+
+ /*
+ * Later we may add commands which requires a response.
+ */
+ while (!done) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ errx(1, "imsg_get error");
+ if (n == 0)
+ break;
+
+ switch (res->action) {
+ default:
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ close(ctl_sock);
+ free(ibuf);
+
+ return 0;
+}
blob - /dev/null
blob + a9379514f81edfcf3e8b1d67858e6df371adf8f4 (mode 644)
--- /dev/null
+++ kamid.8
+.\" Copyright (c) 2021 Omar Polo <op@omarpolo.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 07 2021 $
+.Dt KAMID 8
+.Os
+.Sh NAME
+.Nm kamid
+.Nd 9p file server daemon
+.Sh SYNOPSYS
+.Nm
+.Op Fl dnv
+.Op Fl D Ar macro Ns = Ns Ar value
+.Op Fl f Pa file
+.Op Fl s Pa socket
+.Sh DESCRIPTION
+.Nm
+is a 9p file server daemon.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D Ar macro Ns = Ns Ar value
+Set a
+.Ar macro
+to a
+.Ar value .
+Macros can be referenced in the configuration files.
+.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/kamid.sockXX" -compact
+.It Pa /etc/kamid.conf
+Default
+.Nm
+configuration file.
+.It Pa /var/run/kamid.sock
+UNIX-domain socket used for communication with
+.Xr kamictl 8 .
+.El
+.Sh SEE ALSO
+.Xr kami.conf 5 ,
+.Xr kamictl 8
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Omar Polo Aq Mt op@omarpolo.com .
blob - /dev/null
blob + fe394808955b6c3901b3fdf11aaa08dc44c72ff3 (mode 644)
--- /dev/null
+++ kamid.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * 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/socket.h>
+#include <sys/wait.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "control.h"
+#include "kamid.h"
+#include "listener.h"
+#include "log.h"
+#include "sandbox.h"
+#include "utils.h"
+
+enum kd_process {
+ PROC_MAIN,
+ PROC_LISTENER,
+ PROC_CLIENTCONN,
+};
+
+const char *saved_argv0;
+static int debug, nflag;
+int verbose;
+
+__dead void usage(void);
+
+void main_sig_handler(int, short, void *);
+void main_dispatch_listener(int, short, void *);
+int main_reload(void);
+int main_imsg_send_config(struct kd_conf *);
+void main_dispatch_listener(int, short, void *);
+__dead void main_shutdown(void);
+
+static pid_t start_child(enum kd_process, int, int, int);
+
+struct kd_conf *main_conf;
+static struct imsgev *iev_listener;
+const char *conffile;
+pid_t listener_pid;
+uint32_t cmd_opts;
+
+__dead void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n",
+ getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct event ev_sigint, ev_sigterm, ev_sighup;
+ int ch;
+ int listener_flag = 0, client_flag = 0;
+ int pipe_main2listener[2];
+ int control_fd;
+ const char *csock;
+
+ conffile = KD_CONF_FILE;
+ csock = KD_SOCKET;
+
+ log_init(1, LOG_DAEMON); /* Log to stderr until deamonized. */
+ log_setverbose(1);
+
+ saved_argv0 = argv[0];
+ if (saved_argv0 == NULL)
+ saved_argv0 = "kamid";
+
+ while ((ch = getopt(argc, argv, "D:df:nsT:v")) != -1) {
+ switch (ch) {
+ case 'D':
+ if (cmdline_symset(optarg) == -1)
+ log_warnx("could not parse macro definition %s",
+ optarg);
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 's':
+ csock = optarg;
+ break;
+ case 'T':
+ switch (*optarg) {
+ case 'c':
+ client_flag = 1;
+ break;
+ case 'l':
+ listener_flag = 1;
+ break;
+ default:
+ fatalx("invalid process spec %c", *optarg);
+ }
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc > 0 || (listener_flag && client_flag))
+ usage();
+
+ if (client_flag)
+ client(debug, verbose);
+ else if (listener_flag)
+ listener(debug, verbose);
+
+ if ((main_conf = parse_config(conffile)) == NULL)
+ exit(1);
+
+ if (nflag) {
+ fprintf(stderr, "configuratino OK\n");
+ exit(0);
+ }
+
+ /* Check for root privileges. */
+ if (geteuid())
+ fatalx("need root privileges");
+
+ /* Check for assigned daemon user. */
+ if (getpwnam(KD_USER) == NULL)
+ fatalx("unknown user %s", KD_USER);
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ if (!debug)
+ daemon(1, 0);
+
+ log_info("startup");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ PF_UNSPEC, pipe_main2listener) == -1)
+ fatal("main2listener socketpair");
+
+ /* Start children. */
+ listener_pid = start_child(PROC_LISTENER, pipe_main2listener[1],
+ debug, verbose);
+
+ log_procinit("main");
+
+ 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(SIGCHLD, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+
+ if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL)
+ fatal(NULL);
+ imsg_init(&iev_listener->ibuf, pipe_main2listener[0]);
+ iev_listener->handler = main_dispatch_listener;
+
+ /* Setup event handlers for pipes to listener. */
+ iev_listener->events = EV_READ;
+ event_set(&iev_listener->ev, iev_listener->ibuf.fd,
+ iev_listener->events, iev_listener->handler, iev_listener);
+ event_add(&iev_listener->ev, NULL);
+
+ if ((control_fd = control_init(csock)) == -1)
+ fatalx("control socket setup failed");
+
+ main_imsg_compose_listener(IMSG_CONTROLFD, control_fd, 0,
+ NULL, 0);
+ main_imsg_send_config(main_conf);
+
+ sandbox_main();
+
+ event_dispatch();
+
+ main_shutdown();
+ return 0;
+}
+
+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();
+ break;
+ case SIGHUP:
+ if (main_reload() == -1)
+ log_warnx("configuration reload failed");
+ else
+ log_debug("configuration reloaded");
+ break;
+ default:
+ fatalx("unexpected signal %d", sig);
+ }
+}
+
+static inline void
+do_auth_tls(struct imsg *imsg)
+{
+ const char *hash, *username = "op";
+ struct passwd *pw;
+ int p[2];
+
+ hash = imsg->data;
+ if (hash[IMSG_DATA_SIZE(*imsg)-1] != '\0')
+ goto err;
+
+ log_debug("tls hash=%s", hash);
+ log_debug("assuming it refers to user `%s'",
+ username);
+
+ if ((pw = getpwnam(username)) == NULL) {
+ log_warn("getpwnam(%s)", username);
+ goto err;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK,
+ PF_UNSPEC, p) == -1)
+ fatal("socketpair");
+
+ start_child(PROC_CLIENTCONN, p[1], debug, verbose);
+
+ main_imsg_compose_listener(IMSG_AUTH, p[0], imsg->hdr.peerid,
+ username, strlen(username)+1);
+ main_imsg_compose_listener(IMSG_AUTH_DIR, -1, imsg->hdr.peerid,
+ pw->pw_dir, strlen(pw->pw_dir)+1);
+
+ return;
+
+err:
+ main_imsg_compose_listener(IMSG_AUTH, -1, imsg->hdr.peerid,
+ NULL, 0);
+}
+
+void
+main_dispatch_listener(int fd, short event, void *d)
+{
+ struct imsgev *iev = d;
+ 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) {
+ case IMSG_AUTH_TLS:
+ do_auth_tls(&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);
+ }
+}
+
+int
+main_reload(void)
+{
+ struct kd_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;
+}
+
+static inline int
+make_socket_for(struct kd_listen_conf *l)
+{
+ struct sockaddr_in addr4;
+ size_t len;
+ int fd, v;
+
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = AF_INET;
+ addr4.sin_port = htons(l->port);
+ addr4.sin_addr.s_addr = INADDR_ANY;
+
+ if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+ fatal("socket");
+
+ v = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1)
+ fatal("setsockopt(SO_REUSEADDR)");
+
+ v = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1)
+ fatal("setsockopt(SO_REUSEPORT)");
+
+ len = sizeof(addr4);
+ if (bind(fd, (struct sockaddr *)&addr4, len) == -1)
+ fatal("bind(%s, %d)", l->iface, l->port);
+
+ if (listen(fd, 16) == -1)
+ fatal("l(%s, %d)", l->iface, l->port);
+
+ return fd;
+}
+
+int
+main_imsg_send_config(struct kd_conf *xconf)
+{
+ struct kd_pki_conf *pki;
+ struct kd_listen_conf *listen;
+
+#define SEND(type, fd, data, len) do { \
+ if (main_imsg_compose_listener(type, fd, 0, data, len) \
+ == -1) \
+ return -1; \
+ } while (0)
+
+ /* Send fixed part of config to children. */
+ SEND(IMSG_RECONF_CONF, -1, xconf, sizeof(*xconf));
+
+ SIMPLEQ_FOREACH(pki, &xconf->pki_head, entry) {
+ log_debug("sending pki %s", pki->name);
+ SEND(IMSG_RECONF_PKI, -1, pki->name, sizeof(pki->name));
+ SEND(IMSG_RECONF_PKI_CERT, -1, pki->cert, pki->certlen);
+ SEND(IMSG_RECONF_PKI_KEY, -1, pki->key, pki->keylen);
+ }
+
+ SIMPLEQ_FOREACH(listen, &xconf->listen_head, entry) {
+ log_debug("sending listen on port %d", listen->port);
+ SEND(IMSG_RECONF_LISTEN, make_socket_for(listen), listen,
+ sizeof(*listen));
+ }
+
+ SEND(IMSG_RECONF_END, -1, NULL, 0);
+ return 0;
+
+#undef SEND
+}
+
+void
+merge_config(struct kd_conf *conf, struct kd_conf *xconf)
+{
+ /* do stuff... */
+
+ free(xconf);
+}
+
+struct kd_conf *
+config_new_empty(void)
+{
+ struct kd_conf *xconf;
+
+ if ((xconf = calloc(1, sizeof(*xconf))) == NULL)
+ fatal(NULL);
+
+ /* set default values */
+
+ return xconf;
+}
+
+void
+config_clear(struct kd_conf *conf)
+{
+ struct kd_conf *xconf;
+
+ /* Merge current config with an empty one. */
+ xconf = config_new_empty();
+ merge_config(conf, xconf);
+
+ free(conf);
+}
+
+__dead void
+main_shutdown(void)
+{
+ pid_t pid;
+ int status;
+
+ /* close pipes. */
+ 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 == listener_pid) ? "logger" : "clientconn",
+ WTERMSIG(status));
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ free(iev_listener);
+
+ log_info("terminating");
+ exit(0);
+}
+
+static pid_t
+start_child(enum kd_process p, int fd, int debug, int verbose)
+{
+ const char *argv[5];
+ int argc = 0;
+ pid_t pid;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("cannot fork");
+ case 0:
+ break;
+ default:
+ close(fd);
+ return pid;
+ }
+
+ if (fd != 3) {
+ if (dup2(fd, 3) == -1)
+ fatal("cannot setup imsg fd");
+ } else if (fcntl(F_SETFD, 0) == -1)
+ fatal("cannot setup imsg fd");
+
+ argv[argc++] = saved_argv0;
+ switch (p) {
+ case PROC_MAIN:
+ fatalx("Can not start main process");
+ case PROC_LISTENER:
+ argv[argc++] = "-Tl";
+ break;
+ case PROC_CLIENTCONN:
+ argv[argc++] = "-Tc";
+ break;
+ }
+ if (debug)
+ argv[argc++] = "-d";
+ if (verbose)
+ argv[argc++] = "-v";
+ argv[argc++] = NULL;
+
+ /* really? */
+ execvp(saved_argv0, (char *const *)argv);
+ fatal("execvp");
+}
+
+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, const 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;
+}
+
+int
+main_imsg_compose_listener(int type, int fd, uint32_t peerid,
+ const void *data, uint16_t datalen)
+{
+ if (iev_listener)
+ return imsg_compose_event(iev_listener, type, peerid, 0,
+ fd, data, datalen);
+ else
+ return -1;
+}
blob - /dev/null
blob + 4de54e055192b0c2114fc76dcbbb7ca730e7226e (mode 644)
--- /dev/null
+++ kamid.conf.5
+.\" Copyright (c) 2021 Omar Polo <op@omarpolo.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 07 2021 $
+.Dt KAMID.CONF 5
+.Os
+.Sh NAME
+.Nm kamid.conf
+.Nd 9p file server daemon configuration file
+.Sh DESCRIPTION
+The
+.Xr kamid 8
+daemon is a 9p file server daemon.
+.Sh SEE ALSO
+.Xr kamictl 8 ,
+.Xr kamid 8
blob - /dev/null
blob + ab1b10aa6f2f2b04606cca2b0d1339d419b94e0a (mode 644)
--- /dev/null
+++ kamid.h
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#ifndef KAMID_H
+#define KAMID_H
+
+#include "compat.h"
+
+#include <event.h>
+#include <limits.h>
+#include <stdint.h>
+#include <tls.h>
+
+/* TODO: make these customizable */
+#define KD_CONF_FILE "/etc/kamid.conf"
+#define KD_USER "_kamid"
+#define KD_SOCKET "/var/run/kamid.sock"
+
+#define IMSG_DATA_SIZE(imsg) ((imsg).hdr.len - IMSG_HEADER_SIZE)
+
+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_CONTROLFD,
+ IMSG_STARTUP,
+ IMSG_RECONF_CONF,
+ IMSG_RECONF_PKI,
+ IMSG_RECONF_PKI_CERT,
+ IMSG_RECONF_PKI_KEY,
+ IMSG_RECONF_LISTEN,
+ IMSG_RECONF_END,
+ IMSG_AUTH,
+ IMSG_AUTH_DIR,
+ IMSG_AUTH_TLS,
+ IMSG_CONN_GONE,
+ IMSG_BUF,
+};
+
+struct kd_options_conf {
+ /* ... */
+};
+
+enum table_type {
+ T_NONE = 0,
+ T_HASH = 0x01,
+};
+
+struct table {
+ char t_name[LINE_MAX];
+ enum table_type t_type;
+ char t_path[PATH_MAX];
+ void *t_handle;
+ struct table_backend *t_backend;
+};
+
+struct table_backend {
+ const char *name;
+ int (*open)(struct table *);
+ int (*add)(struct table *, const char *, const char *);
+ int (*lookup)(struct table *, const char *, char **);
+ void (*close)(struct table *);
+};
+
+/* table_static.c */
+extern struct table_backend table_static;
+
+struct kd_listen_conf {
+ SIMPLEQ_ENTRY(kd_listen_conf) entry;
+ uint32_t id;
+ int fd;
+ char iface[LINE_MAX];
+ uint16_t port;
+ struct table *auth_table;
+ char pki[LINE_MAX];
+ struct event ev;
+ struct tls *ctx;
+};
+
+struct kd_pki_conf {
+ SIMPLEQ_ENTRY(kd_pki_conf) entry;
+ char name[LINE_MAX];
+ uint8_t *cert;
+ size_t certlen;
+ uint8_t *key;
+ size_t keylen;
+ struct tls_config *tlsconf;
+};
+
+struct kd_tables_conf {
+ SIMPLEQ_ENTRY(kd_tables_conf) entry;
+ struct table *table;
+};
+
+struct kd_conf {
+ struct kd_options_conf kd_options;
+ SIMPLEQ_HEAD(kd_pki_conf_head, kd_pki_conf) pki_head;
+ SIMPLEQ_HEAD(kd_tables_conf_head, kd_tables_conf) table_head;
+ SIMPLEQ_HEAD(kd_listen_conf_head, kd_listen_conf) listen_head;
+};
+
+/* kamid.c */
+extern int verbose;
+int main_imsg_compose_listener(int, int, uint32_t, const void *, uint16_t);
+void merge_config(struct kd_conf *, struct kd_conf *);
+void imsg_event_add(struct imsgev *);
+int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t,
+ int, const void *, uint16_t);
+
+struct kd_conf *config_new_empty(void);
+void config_clear(struct kd_conf *);
+
+/* parse.y */
+struct kd_conf *parse_config(const char *);
+int cmdline_symset(char *);
+
+#endif
blob - /dev/null
blob + 500d70bd7cd5fa5512a5c46bc2f4fe39b6cc7709 (mode 644)
--- /dev/null
+++ listener.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * 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 "compat.h"
+
+#include <sys/socket.h>
+#include <sys/tree.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "control.h"
+#include "kamid.h"
+#include "listener.h"
+#include "log.h"
+#include "sandbox.h"
+#include "utils.h"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+static struct kd_conf *listener_conf;
+static struct imsgev *iev_main;
+
+static void listener_sig_handler(int, short, void *);
+__dead void listener_shutdown(void);
+
+SPLAY_HEAD(clients_tree_id, client) clients;
+
+struct client {
+ uint32_t id;
+ int fd;
+ int done;
+ struct tls *ctx;
+ struct event event;
+ struct imsgev iev;
+ struct bufferevent *bev;
+ SPLAY_ENTRY(client) sp_entry;
+};
+
+static void listener_imsg_event_add(struct imsgev *, void *);
+static void listener_dispatch_client(int, short, void *);
+static int listener_imsg_compose_client(struct client *, int,
+ uint32_t, const void *, uint16_t);
+
+static void apply_config(struct kd_conf *);
+static void handle_accept(int, short, void *);
+
+static void handle_handshake(int, short, void *);
+static void client_read(struct bufferevent *, void *);
+static void client_write(struct bufferevent *, void *);
+static void client_error(struct bufferevent *, short, void *);
+static void client_tls_readcb(int, short, void *);
+static void client_tls_writecb(int, short, void *);
+static void close_conn(struct client *);
+static void handle_close(int, short, void *);
+
+static inline int
+clients_tree_cmp(struct client *a, struct client *b)
+{
+ if (a->id == b->id)
+ return 0;
+ else if (a->id < b->id)
+ return -1;
+ else
+ return +1;
+}
+
+SPLAY_PROTOTYPE(clients_tree_id, client, sp_entry, clients_tree_cmp);
+SPLAY_GENERATE(clients_tree_id, client, sp_entry, clients_tree_cmp)
+
+static void
+listener_sig_handler(int sig, short event, void *d)
+{
+ /*
+ * Normal signal handler rules don't apply because libevent
+ * decouples for us.
+ */
+
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ listener_shutdown();
+ default:
+ fatalx("unexpected signal %d", sig);
+ }
+}
+
+void
+listener(int debug, int verbose)
+{
+ struct event ev_sigint, ev_sigterm;
+ struct passwd *pw;
+
+ /* listener_conf = config_new_empty(); */
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ if ((pw = getpwnam(KD_USER)) == NULL)
+ fatal("getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+
+ setproctitle("listener");
+ log_procinit("listener");
+
+ 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");
+
+ event_init();
+
+ /* Setup signal handlers(s). */
+ signal_set(&ev_sigint, SIGINT, listener_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, listener_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(*iev_main))) == NULL)
+ fatal(NULL);
+
+ imsg_init(&iev_main->ibuf, 3);
+ iev_main->handler = listener_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);
+
+ sandbox_listener();
+ event_dispatch();
+ listener_shutdown();
+}
+
+__dead void
+listener_shutdown(void)
+{
+ msgbuf_clear(&iev_main->ibuf.w);
+ close(iev_main->ibuf.fd);
+
+ config_clear(listener_conf);
+
+ free(iev_main);
+
+ log_info("listener exiting");
+ exit(0);
+}
+
+static void
+listener_receive_config(struct imsg *imsg, struct kd_conf **nconf,
+ struct kd_pki_conf **pki)
+{
+ struct kd_listen_conf *listen;
+ char *t;
+
+ switch (imsg->hdr.type) {
+ case IMSG_RECONF_CONF:
+ if (*nconf != NULL)
+ fatalx("%s: IMSG_RECONF_CONF already in "
+ "progress", __func__);
+
+ if (listener_conf != NULL)
+ fatalx("%s: don't know how reload the "
+ "configuration yet", __func__);
+
+ if (IMSG_DATA_SIZE(*imsg) != sizeof(struct kd_conf))
+ fatalx("%s: IMSG_RECONF_CONF wrong length: %lu",
+ __func__, IMSG_DATA_SIZE(*imsg));
+ if ((*nconf = malloc(sizeof(**nconf))) == NULL)
+ fatal(NULL);
+ memcpy(*nconf, imsg->data, sizeof(**nconf));
+ memset(&(*nconf)->pki_head, 0, sizeof((*nconf)->pki_head));
+ memset(&(*nconf)->table_head, 0, sizeof((*nconf)->table_head));
+ memset(&(*nconf)->listen_head, 0, sizeof((*nconf)->listen_head));
+ break;
+ case IMSG_RECONF_PKI:
+ if (*nconf == NULL)
+ fatalx("%s: IMSG_RECONF_PKI without "
+ "IMSG_RECONF_CONF", __func__);
+ *pki = xcalloc(1, sizeof(**pki));
+ t = imsg->data;
+ t[IMSG_DATA_SIZE(*imsg)-1] = '\0';
+ strlcpy((*pki)->name, t, sizeof((*pki)->name));
+ break;
+ case IMSG_RECONF_PKI_CERT:
+ if (*pki == NULL)
+ fatalx("%s: IMSG_RECONF_PKI_CERT without "
+ "IMSG_RECONF_PKI", __func__);
+ (*pki)->certlen = IMSG_DATA_SIZE(*imsg);
+ (*pki)->cert = xmemdup(imsg->data, (*pki)->certlen);
+ break;
+ case IMSG_RECONF_PKI_KEY:
+ if (*pki == NULL)
+ fatalx("%s: IMSG_RECONF_PKI_KEY without "
+ "IMSG_RECONF_PKI", __func__);
+ (*pki)->keylen = IMSG_DATA_SIZE(*imsg);
+ (*pki)->key = xmemdup(imsg->data, (*pki)->keylen);
+ SIMPLEQ_INSERT_HEAD(&(*nconf)->pki_head, *pki, entry);
+ pki = NULL;
+ break;
+ case IMSG_RECONF_LISTEN:
+ if (*nconf == NULL)
+ fatalx("%s: IMSG_RECONF_LISTEN without "
+ "IMSG_RECONF_CONF", __func__);
+ if (IMSG_DATA_SIZE(*imsg) != sizeof(*listen))
+ fatalx("%s: IMSG_RECONF_LISTEN wrong length: %lu",
+ __func__, IMSG_DATA_SIZE(*imsg));
+ listen = xcalloc(1, sizeof(*listen));
+ memcpy(listen, imsg->data, sizeof(*listen));
+ memset(&listen->entry, 0, sizeof(listen->entry));
+ if ((listen->fd = imsg->fd) == -1)
+ fatalx("%s: IMSG_RECONF_LISTEN no fd",
+ __func__);
+ listen->auth_table = NULL;
+ memset(&listen->ev, 0, sizeof(listen->ev));
+ SIMPLEQ_INSERT_HEAD(&(*nconf)->listen_head, listen, entry);
+ break;
+ case IMSG_RECONF_END:
+ if (*nconf == NULL)
+ fatalx("%s: IMSG_RECONF_END without "
+ "IMSG_RECONF_CONF", __func__);
+ /* merge_config(listener_conf, nconf); */
+ apply_config(*nconf);
+ *nconf = NULL;
+ break;
+ }
+}
+
+void
+listener_dispatch_main(int fd, short event, void *d)
+{
+ static struct kd_conf *nconf;
+ static struct kd_pki_conf *pki;
+ struct client *client, find;
+ struct imsg imsg;
+ struct imsgev *iev = d;
+ struct imsgbuf *ibuf;
+ 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_CONTROLFD:
+ if ((fd = imsg.fd) == -1)
+ fatalx("%s: expected to receive imsg "
+ "control fd but didn't receive any",
+ __func__);
+ /* Listen on control socket. */
+ control_listen(fd);
+ break;
+ case IMSG_RECONF_CONF:
+ case IMSG_RECONF_PKI:
+ case IMSG_RECONF_PKI_CERT:
+ case IMSG_RECONF_PKI_KEY:
+ case IMSG_RECONF_LISTEN:
+ case IMSG_RECONF_END:
+ listener_receive_config(&imsg, &nconf, &pki);
+ break;
+ case IMSG_AUTH:
+ find.id = imsg.hdr.peerid;
+ client = SPLAY_FIND(clients_tree_id, &clients, &find);
+ if (client == NULL) {
+ if (imsg.fd == -1)
+ close(imsg.fd);
+ break;
+ }
+ if (imsg.fd == -1) {
+ close_conn(client);
+ break;
+ }
+ imsg_init(&client->iev.ibuf, imsg.fd);
+ client->iev.events = EV_READ;
+ client->iev.handler = listener_dispatch_client;
+ event_set(&client->iev.ev, client->iev.ibuf.fd,
+ client->iev.events, client->iev.handler, client);
+ listener_imsg_compose_client(client, IMSG_AUTH,
+ 0, imsg.data, IMSG_DATA_SIZE(imsg));
+ break;
+ case IMSG_AUTH_DIR:
+ find.id = imsg.hdr.peerid;
+ client = SPLAY_FIND(clients_tree_id, &clients, &find);
+ if (client == NULL) {
+ log_info("got AUTH_DIR but client gone");
+ break;
+ }
+ listener_imsg_compose_client(client, IMSG_AUTH_DIR,
+ 0, imsg.data, IMSG_DATA_SIZE(imsg));
+ client->bev = bufferevent_new(client->fd,
+ client_read, client_write, client_error,
+ client);
+ if (client->bev == NULL) {
+ log_info("failed to allocate client buffer");
+ close_conn(client);
+ return;
+ }
+ event_set(&client->bev->ev_read, client->fd, EV_READ,
+ client_tls_readcb, client->bev);
+ event_set(&client->bev->ev_write, client->fd, EV_WRITE,
+ client_tls_writecb, client->bev);
+
+ /* TODO: adjust watermarks */
+ bufferevent_setwatermark(client->bev, EV_WRITE, 1, 0);
+ bufferevent_setwatermark(client->bev, EV_READ, 1, 0);
+
+ bufferevent_enable(client->bev, EV_READ|EV_WRITE);
+ break;
+ default:
+ log_debug("%s: unexpected imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ if (!shut)
+ listener_imsg_event_add(iev, d);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ log_warnx("pipe closed, shutting down...");
+ event_loopexit(NULL);
+ }
+}
+
+int
+listener_imsg_compose_main(int type, uint32_t peerid, const void *data,
+ uint16_t datalen)
+{
+ return imsg_compose_event(iev_main, type, peerid, 0, -1, data,
+ datalen);
+}
+
+static void
+listener_imsg_event_add(struct imsgev *iev, void *d)
+{
+ 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, d);
+ event_add(&iev->ev, NULL);
+}
+
+static void
+listener_dispatch_client(int fd, short event, void *d)
+{
+ struct client find, *client = d;
+ struct imsg imsg;
+ struct imsgev *iev;
+ struct imsgbuf *ibuf;
+ ssize_t n;
+ int r, shut = 0;
+
+ iev = &client->iev;
+ 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_BUF:
+ find.id = imsg.hdr.peerid;
+ client = SPLAY_FIND(clients_tree_id, &clients, &find);
+ if (client == NULL) {
+ log_info("got IMSG_BUF but client (%d) gone",
+ imsg.hdr.peerid);
+ break;
+ }
+ r = bufferevent_write(client->bev, imsg.data,
+ IMSG_DATA_SIZE(imsg));
+ if (r == -1) {
+ log_warn("%s: bufferevent_write failed",
+ __func__);
+ close_conn(client);
+ break;
+ }
+ break;
+ default:
+ log_debug("%s: unexpected imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ if (!shut)
+ listener_imsg_event_add(iev, d);
+ else {
+ /* This pipe is dead. Remove its handler */
+ log_debug("client proc vanished");
+ close_conn(client);
+ }
+}
+
+static int
+listener_imsg_compose_client(struct client *client, int type,
+ uint32_t peerid, const void *data, uint16_t len)
+{
+ int ret;
+
+ if ((ret = imsg_compose(&client->iev.ibuf, type, peerid, 0, -1,
+ data, len)) != -1)
+ listener_imsg_event_add(&client->iev, client);
+
+ return ret;
+}
+
+static inline struct kd_pki_conf *
+pki_by_name(const char *name)
+{
+ struct kd_pki_conf *pki;
+
+ SIMPLEQ_FOREACH(pki, &listener_conf->pki_head, entry) {
+ if (!strcmp(name, pki->name))
+ return pki;
+ }
+
+ return NULL;
+}
+
+static void
+apply_config(struct kd_conf *conf)
+{
+ struct kd_pki_conf *pki;
+ struct kd_listen_conf *listen;
+
+ listener_conf = conf;
+
+ /* prepare the various tls_config */
+ SIMPLEQ_FOREACH(pki, &listener_conf->pki_head, entry) {
+ if ((pki->tlsconf = tls_config_new()) == NULL)
+ fatal("tls_config_new");
+ tls_config_verify_client_optional(pki->tlsconf);
+ tls_config_insecure_noverifycert(pki->tlsconf);
+ if (tls_config_set_keypair_mem(pki->tlsconf,
+ pki->cert, pki->certlen,
+ pki->key, pki->keylen) == -1)
+ fatalx("tls_config_set_keypair_mem: %s",
+ tls_config_error(pki->tlsconf));
+ }
+
+ /* prepare and kickoff the listeners */
+ SIMPLEQ_FOREACH(listen, &listener_conf->listen_head, entry) {
+ if ((listen->ctx = tls_server()) == NULL)
+ fatal("tls_server");
+
+ pki = pki_by_name(listen->pki);
+ if (tls_configure(listen->ctx, pki->tlsconf) == -1)
+ fatalx("tls_configure: %s",
+ tls_config_error(pki->tlsconf));
+
+ event_set(&listen->ev, listen->fd, EV_READ|EV_PERSIST,
+ handle_accept, listen);
+ event_add(&listen->ev, NULL);
+ }
+}
+
+static inline void
+yield_r(struct client *c, void (*fn)(int, short, void *))
+{
+ if (event_pending(&c->event, EV_WRITE|EV_READ, NULL))
+ event_del(&c->event);
+ event_set(&c->event, c->fd, EV_READ, fn, c);
+ event_add(&c->event, NULL);
+}
+
+static inline void
+yield_w(struct client *c, void (*fn)(int, short, void *))
+{
+ if (event_pending(&c->event, EV_WRITE|EV_READ, NULL))
+ event_del(&c->event);
+ event_set(&c->event, c->fd, EV_WRITE, fn, c);
+ event_add(&c->event, NULL);
+}
+
+static inline uint32_t
+random_id(void)
+{
+ struct client find, *res;
+
+ for (;;) {
+ find.id = arc4random();
+ res = SPLAY_FIND(clients_tree_id, &clients, &find);
+ if (res == NULL)
+ return find.id;
+ }
+}
+
+static void
+handle_accept(int fd, short ev, void *data)
+{
+ struct kd_listen_conf *listen = data;
+ struct client *c;
+ int s;
+
+ if ((s = accept(fd, NULL, NULL)) == -1) {
+ log_warn("accept");
+ return;
+ }
+
+ c = xcalloc(1, sizeof(*c));
+ c->iev.ibuf.fd = -1;
+
+ if (tls_accept_socket(listen->ctx, &c->ctx, s) == -1) {
+ log_warnx("tls_accept_socket: %s",
+ tls_error(listen->ctx));
+ free(c);
+ close(s);
+ return;
+ }
+
+ c->fd = s;
+ c->id = random_id();
+
+ SPLAY_INSERT(clients_tree_id, &clients, c);
+
+ /* initialize the event */
+ event_set(&c->event, c->fd, EV_READ, NULL, NULL);
+
+ yield_r(c, handle_handshake);
+}
+
+static void
+handle_handshake(int fd, short ev, void *data)
+{
+ struct client *c = data;
+ ssize_t r;
+ const char *hash;
+
+ switch (r = tls_handshake(c->ctx)) {
+ case TLS_WANT_POLLIN:
+ yield_r(c, handle_handshake);
+ return;
+ case TLS_WANT_POLLOUT:
+ yield_w(c, handle_handshake);
+ return;
+ case -1:
+ log_debug("handhsake failed: %s", tls_error(c->ctx));
+ close_conn(c);
+ return;
+ }
+
+ if ((hash = tls_peer_cert_hash(c->ctx)) == NULL) {
+ log_warnx("client didn't provide certificate");
+ close_conn(c);
+ return;
+ }
+
+ listener_imsg_compose_main(IMSG_AUTH_TLS, c->id,
+ hash, strlen(hash)+1);
+}
+
+static void
+client_read(struct bufferevent *bev, void *d)
+{
+ struct client *client = d;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ char buf[BUFSIZ];
+ size_t len;
+
+ if (!EVBUFFER_LENGTH(src))
+ return;
+
+ len = bufferevent_read(bev, buf, sizeof(buf));
+ if (len == 0) {
+ (*bev->errorcb)(bev, EVBUFFER_READ, bev->cbarg);
+ return;
+ }
+
+ listener_imsg_compose_client(client, IMSG_BUF, client->id, buf, len);
+}
+
+static void
+client_write(struct bufferevent *bev, void *d)
+{
+ /*
+ * here we can do some fancy logic like deciding when to call
+ *
+ * (*bev->errorcb)(bev, EVBUFFER_WRITE, bev->cbarg)
+ *
+ * to signal the end of the transaction.
+ */
+
+ return;
+}
+
+static void
+client_error(struct bufferevent *bev, short err, void *d)
+{
+ struct client *client = d;
+ struct evbuffer *buf;
+
+ if (err & EVBUFFER_ERROR) {
+ if (errno == EFBIG) {
+ bufferevent_enable(bev, EV_READ);
+ return;
+ }
+ log_debug("buffer event error");
+ close_conn(client);
+ return;
+ }
+
+ if (err & EVBUFFER_EOF) {
+ close_conn(client);
+ return;
+ }
+
+ if (err & (EVBUFFER_READ|EVBUFFER_WRITE)) {
+ bufferevent_disable(bev, EV_READ|EV_WRITE);
+ client->done = 1;
+
+ buf = EVBUFFER_OUTPUT(client->bev);
+ if (EVBUFFER_LENGTH(buf) != 0) {
+ /* finish writing all the data first */
+ bufferevent_enable(client->bev, EV_WRITE);
+ return;
+ }
+
+ close_conn(client);
+ return;
+ }
+
+ log_warnx("unknown event error, closing client connection");
+ close_conn(client);
+}
+
+static void
+client_tls_readcb(int fd, short event, void *d)
+{
+ struct bufferevent *bufev = d;
+ struct client *client = bufev->cbarg;
+ char buf[BUFSIZ];
+ int what = EVBUFFER_READ;
+ int howmuch = IBUF_READ_SIZE;
+ ssize_t ret;
+ size_t len;
+
+ if (event == EV_TIMEOUT) {
+ what |= EVBUFFER_TIMEOUT;
+ goto err;
+ }
+
+ if (bufev->wm_read.high != 0)
+ howmuch = MIN(sizeof(buf), bufev->wm_read.high);
+
+ switch (ret = tls_read(client->ctx, buf, howmuch)) {
+ case TLS_WANT_POLLIN:
+ case TLS_WANT_POLLOUT:
+ goto retry;
+ case -1:
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+ len = ret;
+
+ if (len == 0) {
+ what |= EVBUFFER_EOF;
+ goto err;
+ }
+
+ if (evbuffer_add(bufev->input, buf, len) == -1) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+
+ event_add(&bufev->ev_read, NULL);
+
+ len = EVBUFFER_LENGTH(bufev->input);
+ if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+ return;
+ if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
+ /*
+ * here we could implement some read pressure
+ * mechanism.
+ */
+ }
+
+ if (bufev->readcb != NULL)
+ (*bufev->readcb)(bufev, bufev->cbarg);
+
+ return;
+
+retry:
+ event_add(&bufev->ev_read, NULL);
+ return;
+
+err:
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+static void
+client_tls_writecb(int fd, short event, void *d)
+{
+ struct bufferevent *bufev = d;
+ struct client *client = bufev->cbarg;
+ ssize_t ret;
+ size_t len;
+ short what = EVBUFFER_WRITE;
+
+ if (event == EV_TIMEOUT) {
+ what |= EVBUFFER_TIMEOUT;
+ goto err;
+ }
+
+ if (EVBUFFER_LENGTH(bufev->output) != 0) {
+ ret = tls_write(client->ctx,
+ EVBUFFER_DATA(bufev->output),
+ EVBUFFER_LENGTH(bufev->output));
+ switch (ret) {
+ case TLS_WANT_POLLIN:
+ case TLS_WANT_POLLOUT:
+ goto retry;
+ case -1:
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+ len = ret;
+ evbuffer_drain(bufev->output, len);
+ }
+
+ if (EVBUFFER_LENGTH(bufev->output) != 0)
+ event_add(&bufev->ev_write, NULL);
+
+ if (bufev->writecb != NULL &&
+ EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+ (*bufev->writecb)(bufev, bufev->cbarg);
+ return;
+
+retry:
+ event_add(&bufev->ev_write, NULL);
+ return;
+
+err:
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+static void
+close_conn(struct client *c)
+{
+ log_debug("closing connection");
+
+ if (c->iev.ibuf.fd != -1) {
+ listener_imsg_compose_client(c, IMSG_CONN_GONE, 0, NULL, 0);
+ imsg_flush(&c->iev.ibuf);
+ msgbuf_clear(&c->iev.ibuf.w);
+ event_del(&c->iev.ev);
+ close(c->iev.ibuf.fd);
+ }
+
+ handle_close(c->fd, 0, c);
+}
+
+static void
+handle_close(int fd, short ev, void *d)
+{
+ struct client *c = d;
+
+ switch (tls_close(c->ctx)) {
+ case TLS_WANT_POLLIN:
+ yield_r(c, handle_close);
+ return;
+ case TLS_WANT_POLLOUT:
+ yield_w(c, handle_close);
+ return;
+ }
+
+ event_del(&c->event);
+ tls_free(c->ctx);
+ close(c->fd);
+ free(c);
+}
blob - /dev/null
blob + 4869c415ac1237dc481e958ab418713eb08d313b (mode 644)
--- /dev/null
+++ listener.h
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#ifndef LISTENER_H
+#define LISTENER_H
+
+#include "compat.h"
+
+void listener(int, int);
+void listener_dispatch_main(int, short, void *);
+int listener_imsg_compose_main(int, uint32_t, const void *, uint16_t);
+
+#endif
blob - /dev/null
blob + ea61ad443260427cac94b2098146f1138c477520 (mode 644)
--- /dev/null
+++ log.c
+/*
+ * 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 "compat.h"
+
+#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)
+{
+ debug = n_debug;
+ verbose = n_debug;
+ log_procinit(getprogname());
+
+ if (!debug)
+ openlog(getprogname(), 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);
+}
blob - /dev/null
blob + 408dec92e050085f6a48c675e9cb0dde00869cd4 (mode 644)
--- /dev/null
+++ log.h
+/*
+ * 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 */
blob - /dev/null
blob + 974fdb6e052693f211b33ce37bcf8730bf019e03 (mode 644)
--- /dev/null
+++ parse.y
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * 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 "compat.h"
+
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "kamid.h"
+#include "table.h"
+#include "utils.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 kd_conf *xconf);
+
+static void add_table(const char *, const char *, const char *);
+static struct table *findtable(const char *name);
+static void add_cert(const char *, const char *);
+static void add_key(const char *, const char *);
+static void add_listen(const char *, uint32_t, const char *, struct table *);
+
+static uint32_t counter;
+static struct table *table;
+static struct kd_conf *conf;
+static int errors;
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ struct table *table;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token AUTH
+%token CERT
+%token ERROR
+%token INCLUDE
+%token KEY
+%token LISTEN
+%token NO
+%token ON
+%token PKI PORT
+%token TABLE TLS
+%token YES
+
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.number> yesno
+%type <v.string> string
+%type <v.table> tableref
+
+%%
+
+grammar : /* empty */
+ | grammar include '\n'
+ | grammar '\n'
+ | grammar table '\n'
+ | grammar pki '\n'
+ | grammar listen '\n'
+ | grammar varset '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 0)) == 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; }
+ ;
+
+optnl : '\n' optnl /* zero or more newlines */
+ | /*empty*/
+ ;
+
+nl : '\n' optnl /* one or more newlines */
+ ;
+
+arrow : '=' '>' ;
+
+comma : ',' optnl
+ | nl
+ ;
+
+varset : STRING '=' string {
+ char *s = $1;
+ if (verbose)
+ printf("%s = \"%s\"\n", $1, $3);
+ while (*s++) {
+ if (isspace((unsigned char)*s)) {
+ yyerror("macro name cannot contain "
+ "whitespace");
+ free($1);
+ free($3);
+ YYERROR;
+ }
+ }
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+pki : PKI STRING CERT STRING { add_cert($2, $4); }
+ | PKI STRING KEY STRING { add_key($2, $4); }
+ ;
+
+table_kp : string arrow string {
+ if (table_add(table, $1, $3) == -1)
+ yyerror("can't add to table %s",
+ table->t_name);
+ free($1);
+ free($3);
+ }
+ ;
+
+table_kps : table_kp
+ | table_kp comma table_kps
+ ;
+
+stringel : STRING {
+ if (table_add(table, $1, NULL) == -1)
+ yyerror("can't add to table %s",
+ table->t_name);
+ free($1);
+ }
+ ;
+
+string_list : stringel
+ | stringel comma string_list
+ ;
+
+table_vals : table_kps
+ | string_list
+ ;
+
+table : TABLE STRING STRING {
+ char *p;
+
+ if ((p = strchr($3, ':')) == NULL) {
+ yyerror("invalid table %s", $2);
+ YYERROR;
+ }
+
+ *p = '\0';
+ add_table($2, $3, p+1);
+ free($2);
+ free($3);
+ }
+ | TABLE STRING {
+ add_table($2, "static", NULL);
+ } '{' table_vals '}' {
+ table = NULL;
+ }
+ ;
+
+tableref : '<' STRING '>' {
+ $$ = findtable($2);
+ free($2);
+ if ($$ == NULL)
+ YYERROR;
+ }
+ ;
+
+listen : LISTEN ON STRING PORT NUMBER TLS PKI STRING /* AUTH tableref */ {
+ char *iface = $3;
+ uint32_t port = $5;
+ char *pki = $8;
+ struct table *auth = NULL; /* $10 */
+
+ add_listen(iface, port, pki, auth);
+ free(iface);
+ free(pki);
+ }
+ ;
+
+%%
+
+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[] = {
+ {"auth", AUTH},
+ {"cert", CERT},
+ {"include", INCLUDE},
+ {"key", KEY},
+ {"listen", LISTEN},
+ {"no", NO},
+ {"on", ON},
+ {"pki", PKI},
+ {"port", PORT},
+ {"table", TABLE},
+ {"tls", TLS},
+ {"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 || next == ' ' ||
+ next == '\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 ((size_t)(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 ((size_t)(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 kd_conf *
+parse_config(const char *filename)
+{
+ struct sym *sym, *next;
+
+ counter = 0;
+ conf = config_new_empty();
+
+ file = pushfile(filename, 0);
+ 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 (verbose && !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;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return -1;
+ sym = strndup(s, val - s);
+ if (sym == NULL)
+ errx(1, "%s: strndup", __func__);
+ 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;
+}
+
+void
+clear_config(struct kd_conf *xconf)
+{
+ /* free stuff? */
+
+ free(xconf);
+}
+
+static void
+add_table(const char *name, const char *type, const char *path)
+{
+ if (table_open(conf, name, type, path) == -1)
+ yyerror("can't initialize table %s", name);
+ table = SIMPLEQ_FIRST(&conf->table_head)->table;
+}
+
+static struct table *
+findtable(const char *name)
+{
+ struct kd_tables_conf *i;
+
+ SIMPLEQ_FOREACH(i, &conf->table_head, entry) {
+ if (!strcmp(i->table->t_name, name))
+ return i->table;
+ }
+
+ yyerror("unknown table %s", name);
+ return NULL;
+}
+
+static void
+add_cert(const char *name, const char *path)
+{
+ struct kd_pki_conf *pki;
+
+ SIMPLEQ_FOREACH(pki, &conf->pki_head, entry) {
+ if (strcmp(name, pki->name) != 0)
+ continue;
+
+ if (pki->cert != NULL) {
+ yyerror("duplicate `pki %s cert'", name);
+ return;
+ }
+
+ goto set;
+ }
+
+ pki = xcalloc(1, sizeof(*pki));
+ strlcpy(pki->name, name, sizeof(pki->name));
+ SIMPLEQ_INSERT_HEAD(&conf->pki_head, pki, entry);
+
+set:
+ if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL)
+ fatal(NULL);
+}
+
+static void
+add_key(const char *name, const char *path)
+{
+ struct kd_pki_conf *pki;
+
+ SIMPLEQ_FOREACH(pki, &conf->pki_head, entry) {
+ if (strcmp(name, pki->name) != 0)
+ continue;
+
+ if (pki->key != NULL) {
+ yyerror("duplicate `pki %s key'", name);
+ return;
+ }
+
+ goto set;
+ }
+
+ pki = xcalloc(1, sizeof(*pki));
+ strlcpy(pki->name, name, sizeof(pki->name));
+ SIMPLEQ_INSERT_HEAD(&conf->pki_head, pki, entry);
+
+set:
+ if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL)
+ fatal(NULL);
+}
+
+static void
+add_listen(const char *iface, uint32_t portno, const char *pki, struct table *auth)
+{
+ struct kd_listen_conf *l;
+
+ if (portno >= UINT16_MAX)
+ fatalx("invalid port number: %"PRIu32, portno);
+
+ l = xcalloc(1, sizeof(*l));
+
+ l->id = counter++;
+ l->fd = -1;
+ strlcpy(l->iface, iface, sizeof(l->iface));
+ l->port = portno;
+ l->auth_table = auth;
+ strlcpy(l->pki, pki, sizeof(l->pki));
+
+ SIMPLEQ_INSERT_HEAD(&conf->listen_head, l, entry);
+}
blob - /dev/null
blob + cf1ac0e9438622950da2943dd9476e842bc8531c (mode 644)
--- /dev/null
+++ sandbox.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#include "log.h"
+#include "sandbox.h"
+
+#ifdef __OpenBSD__
+
+#include <unistd.h>
+
+void
+sandbox_main(void)
+{
+ return;
+}
+
+void
+sandbox_listener(void)
+{
+ return;
+}
+
+void
+sandbox_client(void)
+{
+ return;
+}
+
+#else
+#warning "No sandbox available for this OS"
+
+void
+sandbox_main(void)
+{
+ log_warnx("No sandbox available for this os");
+ return;
+}
+
+void
+sandbox_listener(void)
+{
+ return;
+}
+
+void
+sandbox_client(void)
+{
+ return;
+}
+
+#endif
blob - /dev/null
blob + c964b983ea10e3af850adc627f269bd1051a2619 (mode 644)
--- /dev/null
+++ sandbox.h
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#ifndef SANDBOX_H
+#define SANDBOX_H
+
+void sandbox_main(void);
+void sandbox_listener(void);
+void sandbox_client(void);
+
+#endif
blob - /dev/null
blob + 99669fa4b4f029b50bd79d2407004fdd377a6041 (mode 644)
--- /dev/null
+++ table.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#include "compat.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "table.h"
+#include "utils.h"
+
+int
+table_open(struct kd_conf *conf, const char *name, const char *type,
+ const char *path)
+{
+ struct table *t;
+ struct kd_tables_conf *entry;
+ struct table_backend backends[] = {
+ table_static,
+ }, *b;
+ size_t i;
+
+ for (i = 0; i < sizeof(backends)/sizeof(backends[0]); ++i) {
+ b = &backends[i];
+ if (!strcmp(type, b->name))
+ goto found;
+ }
+ log_warn("unknown table type %s", type);
+ return -1;
+
+found:
+ if (b->open == NULL) {
+ log_warn("can't open table %s (type %s)",
+ name, b->name);
+ return -1;
+ }
+
+ t = xcalloc(1, sizeof(*t));
+ strlcpy(t->t_name, name, sizeof(t->t_name));
+ if (path != NULL)
+ strlcpy(t->t_path, path, sizeof(t->t_path));
+ t->t_backend = b;
+
+ if (t->t_backend->open(t) == -1)
+ fatal("can't open table %s (type %s)",
+ name, path);
+
+ entry = xcalloc(1, sizeof(*entry));
+ entry->table = t;
+ SIMPLEQ_INSERT_HEAD(&conf->table_head, entry, entry);
+ return 0;
+}
+
+int
+table_add(struct table *t, const char *key, const char *val)
+{
+ if (t->t_backend->add == NULL) {
+ log_warn("can't add to table %s (type %s)",
+ t->t_name, t->t_backend->name);
+ return -1;
+ }
+
+ return t->t_backend->add(t, key, val);
+}
+
+int
+table_lookup(struct table *t, const char *key, char **ret_val)
+{
+ if (t->t_backend->lookup == NULL) {
+ log_warn("can't lookup table %s (type %s)",
+ t->t_name, t->t_backend->name);
+ return -1;
+ }
+
+ return t->t_backend->lookup(t, key, ret_val);
+}
+
+void
+table_close(struct table *t)
+{
+ if (t->t_backend->close != NULL)
+ t->t_backend->close(t);
+}
blob - /dev/null
blob + 5380d45279b042b87c616571f4f34c053c0cf217 (mode 644)
--- /dev/null
+++ table.h
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#ifndef TABLE_H
+#define TABLE_H
+
+#include "kamid.h"
+
+int table_open(struct kd_conf *, const char *, const char *, const char *);
+int table_add(struct table *, const char *, const char *);
+int table_lookup(struct table *, const char *, char **);
+void table_close(struct table *);
+
+#endif
blob - /dev/null
blob + f110f58e673bafb2faaa1f32105ec7382909fd87 (mode 644)
--- /dev/null
+++ table_static.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#include "compat.h"
+
+#include <ohash.h>
+#include <stdlib.h>
+
+#include "kamid.h"
+#include "utils.h"
+
+static void *hash_alloc(size_t, void *);
+static void *hash_calloc(size_t, size_t, void *);
+static void hash_free(void *, void *);
+
+static int table_static_open(struct table *);
+static int table_static_add(struct table *, const char *, const char *);
+static int table_static_lookup(struct table *, const char *, char **);
+static void table_static_close(struct table *);
+
+struct table_backend table_static = {
+ "static",
+ table_static_open,
+ table_static_add,
+ table_static_lookup,
+ table_static_close,
+};
+
+struct kp {
+ char *key;
+ char *val;
+};
+
+static void *
+hash_alloc(size_t len, void *d)
+{
+ return xmalloc(len);
+}
+
+static void *
+hash_calloc(size_t nmemb, size_t size, void *d)
+{
+ return xcalloc(nmemb, size);
+}
+
+static void
+hash_free(void *ptr, void *d)
+{
+ free(ptr);
+}
+
+static int
+table_static_open(struct table *t)
+{
+ struct ohash_info info = {
+ .key_offset = offsetof(struct kp, key),
+ .calloc = hash_calloc,
+ .free = hash_free,
+ .alloc = hash_alloc,
+ };
+
+ t->t_handle = xmalloc(sizeof(struct ohash));
+ ohash_init(t->t_handle, 5, &info);
+ return 0;
+}
+
+int
+table_static_add(struct table *t, const char *key, const char *val)
+{
+ struct kp *kp;
+ unsigned int slot;
+
+ kp = xcalloc(1, sizeof(*kp));
+ kp->key = xstrdup(key);
+ if (val != NULL)
+ kp->val = xstrdup(val);
+
+ slot = ohash_qlookup(t->t_handle, kp->key);
+ ohash_insert(t->t_handle, slot, kp);
+
+ return 0;
+}
+
+int
+table_static_lookup(struct table *t, const char *key, char **ret_val)
+{
+ struct kp *kp;
+ unsigned int slot;
+
+ slot = ohash_qlookup(t->t_handle, key);
+ if ((kp = ohash_find(t->t_handle, slot)) == NULL)
+ return -1;
+
+ *ret_val = xstrdup(kp->val);
+ return 0;
+}
+
+static void
+table_static_close(struct table *t)
+{
+ struct kp *kp;
+ unsigned int i;
+
+ for (kp = ohash_first(t->t_handle, &i);
+ kp != NULL;
+ kp = ohash_next(t->t_handle, &i)) {
+ ohash_remove(t->t_handle, i);
+ free(kp->key);
+ free(kp->val);
+ free(kp);
+ }
+
+ free(t->t_handle);
+}
blob - /dev/null
blob + 17258a36da036b89832db92803ec3545d00647eb (mode 644)
--- /dev/null
+++ utils.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#include "utils.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "utils.h"
+
+void *
+xmalloc(size_t size)
+{
+ void *r;
+
+ if ((r = malloc(size)) == NULL)
+ fatal("malloc");
+ return r;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+ void *r;
+
+ if ((r = calloc(nmemb, size)) == NULL)
+ fatal("calloc");
+ return r;
+}
+
+char *
+xstrdup(const char *s)
+{
+ char *r;
+
+ if ((r = strdup(s)) == NULL)
+ fatal("strdup");
+ return r;
+}
+
+void *
+xmemdup(const void *d, size_t len)
+{
+ void *r;
+
+ if ((r = malloc(len)) == NULL)
+ fatal("malloc");
+ memcpy(r, d, len);
+ return r;
+}
blob - /dev/null
blob + f1d33819c799d82d87f77f31b0e3ff59e6f9f254 (mode 644)
--- /dev/null
+++ utils.h
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.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.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include "compat.h"
+
+void *xmalloc(size_t);
+void *xcalloc(size_t, size_t);
+char *xstrdup(const char *);
+void *xmemdup(const void *, size_t);
+
+#endif