diff --git a/MANIFEST b/MANIFEST index 4398ece23..23cdb7ab2 100644 --- a/MANIFEST +++ b/MANIFEST @@ -50,6 +50,7 @@ doc/visudo.mdoc.in examples/Makefile.in examples/pam.conf examples/sudo.conf +examples/sudo_logsrvd.conf examples/sudoers examples/syslog.conf include/Makefile.in @@ -236,6 +237,7 @@ logsrvd/log_server.proto logsrvd/iolog_writer.c logsrvd/logsrvd.c logsrvd/logsrvd.h +logsrvd/logsrvd_conf.c logsrvd/protobuf-c/protobuf-c.c logsrvd/protobuf-c/protobuf-c.h logsrvd/sendlog.c diff --git a/examples/sudo_logsrvd.conf b/examples/sudo_logsrvd.conf new file mode 100644 index 000000000..23213e22c --- /dev/null +++ b/examples/sudo_logsrvd.conf @@ -0,0 +1,49 @@ +# +# sudo logsrv configuration +# + +# The top-level directory to use when constructing the path name for the +# I/O log directory. The session sequence number, if any, is stored here. +iolog_dir = /var/log/sudo-io + +# The path name, relative to iolog_dir, in which to store I/O logs. +# Note that iolog_file may contain directory components. +iolog_file = %{seq} + +# If set, I/O log data is flushed to disk after each write instead of +# buffering it. This makes it possible to view the logs in real-time +# as the program is executing but may significantly reduce the effectiveness +# of I/O log compression. +iolog_flush = true + +# If set, I/O logs will be compressed using zlib. Enabling compression can +# make it harder to view the logs in real-time as the program is executing. +iolog_compress = false + +# The group name to look up when setting the group-ID on new I/O log files +# and directories. If iolog_group is not set, the primary group-ID of the +# user specified by iolog_user is used. If neither iolog_group nor iolog_user +# are set, I/O log files and directories are created with group-ID 0. +#iolog_group = wheel + +# The user name to look up when setting the user and group-IDs on new I/O +# log files and directories. If iolog_group is set, it will be used instead +# of the user's primary group-ID. By default, I/O log files and directories +# are created with user and group-ID 0. +#iolog_user = root + +# The file mode to use when creating I/O log files. Mode bits for read and +# write permissions for owner, group or other are honored, everything else +# is ignored. The file permissions will always include the owner read and +# write bits, even if they are not present in the specified mode. When +# creating I/O log directories, search (execute) bits are added to match +# the read and write bits specified by iolog_mode. +iolog_mode = 0600 + +# The maximum sequence number that will be substituted for the %{seq} +# escape in the I/O log file (see the iolog_dir description below for +# more information). While the value substituted for %{seq} is in +# base 36, maxseq itself should be expressed in decimal. Values larger +# than 2176782336 (which corresponds to the base 36 sequence number +# ZZZZZZ) will be silently truncated to 2176782336. +maxseq = 2176782336 diff --git a/logsrvd/Makefile.in b/logsrvd/Makefile.in index 776079fae..a0bacf74d 100644 --- a/logsrvd/Makefile.in +++ b/logsrvd/Makefile.in @@ -108,8 +108,8 @@ SHELL = @SHELL@ PROGS = logsrvd sendlog -LOGSRVD_OBJS = iolog_util.o iolog_writer.o logsrvd.o log_server.pb-c.o \ - protobuf-c.o +LOGSRVD_OBJS = iolog_util.o iolog_writer.o logsrvd.o logsrvd_conf.o \ + log_server.pb-c.o protobuf-c.o SENDLOG_OBJS = sendlog.o iolog_util.o log_server.pb-c.o protobuf-c.o @@ -272,6 +272,24 @@ logsrvd.i: $(srcdir)/logsrvd.c $(devdir)/log_server.pb-c.h \ $(CC) -E -o $@ $(CPPFLAGS) $< logsrvd.plog: logsrvd.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logsrvd.c --i-file $< --output-file $@ +logsrvd_conf.o: $(srcdir)/logsrvd_conf.c $(devdir)/log_server.pb-c.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \ + $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/logsrvd.h \ + $(srcdir)/protobuf-c/protobuf-c.h $(sudoers_srcdir)/iolog.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/logsrvd_conf.c +logsrvd_conf.i: $(srcdir)/logsrvd_conf.c $(devdir)/log_server.pb-c.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \ + $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/logsrvd.h \ + $(srcdir)/protobuf-c/protobuf-c.h $(sudoers_srcdir)/iolog.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(CC) -E -o $@ $(CPPFLAGS) $< +logsrvd_conf.plog: logsrvd_conf.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/logsrvd_conf.c --i-file $< --output-file $@ protobuf-c.o: $(srcdir)/protobuf-c/protobuf-c.c $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/protobuf-c/protobuf-c.c protobuf-c.i: $(srcdir)/protobuf-c/protobuf-c.c diff --git a/logsrvd/iolog_writer.c b/logsrvd/iolog_writer.c index ba7fa1d53..7914cb362 100644 --- a/logsrvd/iolog_writer.c +++ b/logsrvd/iolog_writer.c @@ -231,7 +231,8 @@ iolog_details_fill(struct iolog_details *details, ExecMessage *msg) /* * Create I/O log path - * Set iolog_dir and iolog_dir_fd in the closure + * Sets iolog_dir and iolog_dir_fd in the closure + * XXX - use iolog_dir and iolog_file code from sudoers/iolog.c */ static bool create_iolog_dir(struct iolog_details *details, struct connection_closure *closure) diff --git a/logsrvd/logsrvd.c b/logsrvd/logsrvd.c index 4a0931ed4..f46ec6f76 100644 --- a/logsrvd/logsrvd.c +++ b/logsrvd/logsrvd.c @@ -53,16 +53,23 @@ #include "pathnames.h" #include "logsrvd.h" +/* + * Sudo I/O audit server. + */ + TAILQ_HEAD(connection_list, connection_closure); static struct connection_list connections = TAILQ_HEAD_INITIALIZER(connections); static const char server_id[] = "Sudo Audit Server 0.1"; +static const char *conf_file = _PATH_SUDO_LOGSRVD_CONF; static double random_drop; -/* - * Proof of concept audit server. - * Currently only handle a single connection at a time. - * TODO: use atomic I/O when we know the packed buffer size. - */ +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; +#ifdef HAVE_STRUCT_IN6_ADDR + struct sockaddr_in6 sin6; +#endif +}; /* * Free a struct connection_closure container and its contents. @@ -648,8 +655,8 @@ signal_cb(int signo, int what, void *v) switch (signo) { case SIGHUP: - /* TODO: reload config */ sudo_debug_printf(SUDO_DEBUG_INFO, "received SIGHUP"); + logsrvd_conf_read(conf_file); break; case SIGINT: case SIGTERM: @@ -903,7 +910,8 @@ daemonize(void) static void usage(void) { - fprintf(stderr, "usage: %s [-n] [-R percentage]\n", getprogname()); + fprintf(stderr, "usage: %s [-n] [-f conf_file] [-R percentage]\n", + getprogname()); exit(1); } @@ -934,12 +942,16 @@ main(int argc, char *argv[]) sudo_fatalx("Protobuf-C version 1.3 or higher required"); /* XXX - getopt_long option handling */ - while ((ch = getopt(argc, argv, "nR:")) != -1) { + while ((ch = getopt(argc, argv, "f:nR:")) != -1) { switch (ch) { + case 'f': + conf_file = optarg; + break; case 'n': nofork = true; break; case 'R': + /* random connection drop probability as a percentage (debug) */ errno = 0; random_drop = strtod(optarg, &ep); if (*ep != '\0' || errno != 0) @@ -953,6 +965,9 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; + /* Read sudo_logsrvd.conf */ + logsrvd_conf_read(conf_file); + signal(SIGPIPE, SIG_IGN); if (!nofork) daemonize(); diff --git a/logsrvd/logsrvd.h b/logsrvd/logsrvd.h index 88af63331..ef83b016c 100644 --- a/logsrvd/logsrvd.h +++ b/logsrvd/logsrvd.h @@ -72,14 +72,6 @@ enum connection_status { ERROR }; -union sockaddr_union { - struct sockaddr sa; - struct sockaddr_in sin; -#ifdef HAVE_STRUCT_IN6_ADDR - struct sockaddr_in6 sin6; -#endif -}; - struct connection_buffer { uint8_t *data; /* pre-allocated data buffer */ unsigned int size; /* currently always UINT16_MAX + 2 */ @@ -114,3 +106,14 @@ int store_iobuf(int iofd, IoBuffer *msg, struct connection_closure *closure); int store_suspend(CommandSuspend *msg, struct connection_closure *closure); int store_winsize(ChangeWindowSize *msg, struct connection_closure *closure); void iolog_close(struct connection_closure *closure); + +/* logsrvd_conf.c */ +bool logsrvd_conf_iolog_compress(void); +bool logsrvd_conf_iolog_flush(void); +const char *logsrvd_conf_iolog_dir(void); +const char *logsrvd_conf_iolog_file(void); +const char *logsrvd_conf_iolog_group(void); +const char *logsrvd_conf_iolog_user(void); +mode_t logsrvd_conf_iolog_mode(void); +unsigned int logsrvd_conf_maxseq(void); +void logsrvd_conf_read(const char *path); diff --git a/logsrvd/logsrvd_conf.c b/logsrvd/logsrvd_conf.c new file mode 100644 index 000000000..c5b76cbc0 --- /dev/null +++ b/logsrvd/logsrvd_conf.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2019 Todd C. Miller + * + * 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 "config.h" + +#include +#include + +#include +#include +#include +#include +#ifdef HAVE_STDBOOL_H +# include +#else +# include "compat/stdbool.h" +#endif +#include +#include +#include +#include + +#include "log_server.pb-c.h" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" +#include "sudo_fatal.h" +#include "pathnames.h" +#include "logsrvd.h" +#include "iolog.h" + +enum config_type { + CONF_BOOL, + CONF_INT, + CONF_UINT, + CONF_MODE, + CONF_STR +}; + +union config_value { + char *strval; + int intval; + unsigned int uintval; + mode_t modeval; + bool boolval; +}; + +struct logsrvd_config_table { + char *conf_str; + enum config_type conf_type; + union config_value conf_val; +}; + +/* Indexes into conf_table */ +#define LOGSRVD_CONF_IOLOG_DIR 0 +#define LOGSRVD_CONF_IOLOG_FILE 1 +#define LOGSRVD_CONF_IOLOG_FLUSH 2 +#define LOGSRVD_CONF_IOLOG_COMPRESS 3 +#define LOGSRVD_CONF_IOLOG_USER 4 +#define LOGSRVD_CONF_IOLOG_GROUP 5 +#define LOGSRVD_CONF_IOLOG_MODE 6 +#define LOGSRVD_CONF_MAXSEQ 7 + +/* XXX - use callbacks into iolog.c instead */ +static struct logsrvd_config_table conf_table[] = { + { "iolog_dir", CONF_STR, { .strval = _PATH_SUDO_IO_LOGDIR } }, + { "iolog_file", CONF_STR, { .strval = "%{seq}" } }, + { "iolog_flush", CONF_BOOL, { .boolval = true } }, + { "iolog_compress", CONF_BOOL, { .boolval = false } }, + { "iolog_user", CONF_STR, { .strval = NULL } }, + { "iolog_group", CONF_STR, { .strval = NULL } }, + { "iolog_mode", CONF_MODE, { .intval = S_IRUSR|S_IWUSR } }, + { "maxseq", CONF_UINT, { .intval = SESSID_MAX } }, + { NULL } +}; + +static bool +parse_value(struct logsrvd_config_table *ct, const char *val) +{ + int ival; + unsigned int uval; + mode_t mode; + const char *errstr; + debug_decl(parse_value, SUDO_DEBUG_UTIL) + + switch (ct->conf_type) { + case CONF_BOOL: + ival = sudo_strtobool(val); + if (ival == -1) + debug_return_bool(false); + ct->conf_val.boolval = ival; + break; + case CONF_INT: + ival = sudo_strtonum(val, INT_MIN, INT_MAX, &errstr); + if (errstr != NULL) + debug_return_bool(false); + ct->conf_val.intval = ival; + break; + case CONF_UINT: + uval = sudo_strtonum(val, 0, UINT_MAX, &errstr); + if (errstr != NULL) + debug_return_bool(false); + ct->conf_val.uintval = uval; + break; + case CONF_MODE: + mode = sudo_strtomode(val, &errstr); + if (errstr != NULL) + debug_return_bool(false); + ct->conf_val.modeval = mode; + break; + case CONF_STR: + ct->conf_val.strval = strdup(val); + if (ct->conf_val.strval == NULL) + debug_return_bool(false); + break; + default: + debug_return_bool(false); + } + + debug_return_bool(true); +} + +void +logsrvd_conf_read(const char *path) +{ + unsigned int lineno = 0; + size_t linesize = 0; + char *line = NULL; + FILE *fp; + debug_decl(read_config, SUDO_DEBUG_UTIL) + + if ((fp = fopen(path, "r")) == NULL) { + if (errno != ENOENT) + sudo_warn("%s", path); + debug_return; + } + + while (sudo_parseln(&line, &linesize, &lineno, fp, 0) != -1) { + struct logsrvd_config_table *ct; + char *ep, *val; + + // XXX - warn about bogus lines + if ((ep = strchr(line, '=')) == NULL) + continue; + val = ep + 1; + while (isspace((unsigned char)*val)) + val++; + while (ep > line && isspace((unsigned char)ep[-1])) + ep--; + *ep = '\0'; + for (ct = conf_table; ct->conf_str != NULL; ct++) { + if (strcmp(line, ct->conf_str) == 0) { + if (!parse_value(ct, val)) + sudo_warnx("invalid value for %s: %s", ct->conf_str, val); + break; + } + } + } + +#if 0 + /* + * TODO: iolog_dir, iolog_file, iolog_flush, iolog_compress + */ + iolog_set_user(conf_table[LOGSRVD_CONF_IOLOG_USER].conf_val.strval); + iolog_set_group(conf_table[LOGSRVD_CONF_IOLOG_GROUP].conf_val.strval); + iolog_set_mode(conf_table[LOGSRVD_CONF_IOLOG_MODE].conf_val.modeval); + /* XXX - expects a string */ + iolog_set_max_sessid(conf_table[LOGSRVD_CONF_MAXSEQ].conf_val.uintval); +#endif + + debug_return; +} + +/* XXX - use callbacks instead */ +const char * +logsrvd_conf_iolog_dir(void) +{ + return conf_table[LOGSRVD_CONF_IOLOG_DIR].conf_val.strval; +} + +const char * +logsrvd_conf_iolog_file(void) +{ + return conf_table[LOGSRVD_CONF_IOLOG_FILE].conf_val.strval; +} + +const char * +logsrvd_conf_iolog_user(void) +{ + return conf_table[LOGSRVD_CONF_IOLOG_USER].conf_val.strval; +} + +const char * +logsrvd_conf_iolog_group(void) +{ + return conf_table[LOGSRVD_CONF_IOLOG_GROUP].conf_val.strval; +} + +bool +logsrvd_conf_iolog_flush(void) +{ + return conf_table[LOGSRVD_CONF_IOLOG_FLUSH].conf_val.boolval; +} + +bool +logsrvd_conf_iolog_compress(void) +{ + return conf_table[LOGSRVD_CONF_IOLOG_COMPRESS].conf_val.boolval; +} + +mode_t +logsrvd_conf_iolog_mode(void) +{ + return conf_table[LOGSRVD_CONF_IOLOG_MODE].conf_val.modeval; +} + +unsigned int +logsrvd_conf_maxseq(void) +{ + return conf_table[LOGSRVD_CONF_MAXSEQ].conf_val.uintval; +} diff --git a/pathnames.h.in b/pathnames.h.in index 7a8a9d252..da9ff829a 100644 --- a/pathnames.h.in +++ b/pathnames.h.in @@ -78,6 +78,13 @@ # define _PATH_CVTSUDOERS_CONF "/etc/cvtsudoers.conf" #endif /* _PATH_CVTSUDOERS_CONF */ +/* + * NOTE: _PATH_SUDO_LOGSRVD_CONF is usually overridden by the Makefile. + */ +#ifndef _PATH_SUDO_LOGSRVD_CONF +# define _PATH_SUDO_LOGSRVD_CONF "/etc/sudo_logsrvd.conf" +#endif /* _PATH_SUDO_LOGSRVD_CONF */ + /* * The following paths are controlled via the configure script. */