commit 8d1b399bf516b09e00a953ce65ee1d67e4854251 from: Omar Polo date: Thu Jul 22 11:55:29 2021 UTC initial commit commit - /dev/null commit + 8d1b399bf516b09e00a953ce65ee1d67e4854251 blob - /dev/null blob + 3e6292ab62fb7a49a707cf7e5bcf9b3773e9ed81 (mode 644) --- /dev/null +++ .gitignore @@ -0,0 +1,24 @@ +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 @@ -0,0 +1,15 @@ +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 @@ -0,0 +1,44 @@ +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 @@ -0,0 +1,26 @@ +# 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 @@ -0,0 +1,3 @@ +#!/bin/sh + +exec autoreconf -vfi blob - /dev/null blob + a667f8059a426d01cbf9b232436418e8945da1f3 (mode 644) --- /dev/null +++ client.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 + +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 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016 Nicholas Marriott + * + * 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 + +#include +#include +#include + +#if HAVE_PR_SET_NAME + +#include + +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 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 +#include + +#include +#include +#include + +#ifdef HAVE_QUEUE_H +# include +#else +# include "compat/queue.h" +#endif + +#ifdef HAVE_LIBUTIL +# include +# include +# include +#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 @@ -0,0 +1,70 @@ +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 +#include +], [ + 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 ]]) + +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 @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#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 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include + +#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, " \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 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 @@ -0,0 +1,61 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" 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 @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 +#include + +#include +#include +#include +#include +#include +#include + +#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 @@ -0,0 +1,73 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" 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 @@ -0,0 +1,557 @@ +/* + * Copyright (c) 2021 Omar Polo + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 @@ -0,0 +1,27 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" 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 @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 +#include +#include +#include + +/* 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 @@ -0,0 +1,843 @@ +/* + * Copyright (c) 2021 Omar Polo + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Claudio Jeker + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include + +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 @@ -0,0 +1,884 @@ +/* + * Copyright (c) 2021 Omar Polo + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 STRING +%token NUMBER +%type yesno +%type string +%type 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 @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 + +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 @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 +#include + +#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 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 +#include + +#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 @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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 +#include + +#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 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * 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