From 2272430716ed2a59a41af6ac71f48aaee22da14d Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Thu, 24 Oct 2019 20:04:29 -0600 Subject: [PATCH] Import proof of concept sudo log server. --- MANIFEST | 11 + Makefile.in | 9 +- configure | 21 +- configure.ac | 4 +- logsrvd/Makefile.in | 279 +++++++ logsrvd/iolog.h | 55 ++ logsrvd/iolog_reader.c | 452 ++++++++++++ logsrvd/iolog_writer.c | 508 +++++++++++++ logsrvd/log_server.pb-c.c | 1452 +++++++++++++++++++++++++++++++++++++ logsrvd/log_server.pb-c.h | 663 +++++++++++++++++ logsrvd/log_server.proto | 113 +++ logsrvd/logsrvd.c | 827 +++++++++++++++++++++ logsrvd/logsrvd.h | 96 +++ logsrvd/sendlog.c | 930 ++++++++++++++++++++++++ logsrvd/sendlog.h | 85 +++ sudo.pp | 6 + 16 files changed, 5495 insertions(+), 16 deletions(-) create mode 100644 logsrvd/Makefile.in create mode 100644 logsrvd/iolog.h create mode 100644 logsrvd/iolog_reader.c create mode 100644 logsrvd/iolog_writer.c create mode 100644 logsrvd/log_server.pb-c.c create mode 100644 logsrvd/log_server.pb-c.h create mode 100644 logsrvd/log_server.proto create mode 100644 logsrvd/logsrvd.c create mode 100644 logsrvd/logsrvd.h create mode 100644 logsrvd/sendlog.c create mode 100644 logsrvd/sendlog.h diff --git a/MANIFEST b/MANIFEST index e03fef633..7666dbcfa 100644 --- a/MANIFEST +++ b/MANIFEST @@ -227,6 +227,17 @@ lib/zlib/zlib.h lib/zlib/zutil.c lib/zlib/zutil.h log2cl.pl +logsrvd/Makefile.in +logsrvd/log_server.pb-c.c +logsrvd/log_server.pb-c.h +logsrvd/log_server.proto +logsrvd/iolog.h +logsrvd/iolog_reader.c +logsrvd/iolog_writer.c +logsrvd/logsrvd.c +logsrvd/logsrvd.h +logsrvd/sendlog.c +logsrvd/sendlog.h ltmain.sh m4/ax_append_flag.m4 m4/ax_check_compile_flag.m4 diff --git a/Makefile.in b/Makefile.in index b6babf7f9..0dc345bf6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -49,7 +49,7 @@ sudoers_gid = @SUDOERS_GID@ sudoers_mode = @SUDOERS_MODE@ shlib_mode = @SHLIB_MODE@ -SUBDIRS = lib/util @ZLIB_SRC@ plugins/group_file plugins/sudoers \ +SUBDIRS = lib/util @ZLIB_SRC@ logsrvd plugins/group_file plugins/sudoers \ plugins/system_group src include doc examples SAMPLES = plugins/sample @@ -188,17 +188,18 @@ siglist.c signame.c: depend: siglist.c signame.c $(top_srcdir)/mkdep.pl --builddir=`pwd` --srcdir=$(top_srcdir) \ - lib/util/Makefile.in lib/zlib/Makefile.in \ + lib/util/Makefile.in lib/zlib/Makefile.in logsrvd/Makefile.in \ plugins/group_file/Makefile.in plugins/sample/Makefile.in \ plugins/sudoers/Makefile.in plugins/system_group/Makefile.in \ src/Makefile.in && \ $(top_builddir)/config.status --file $(top_builddir)/lib/util/Makefile \ + --file $(top_builddir)/lib/zlib/Makefile \ + --file $(top_builddir)/logsrvd/Makefile \ --file $(top_builddir)/plugins/sample/Makefile \ --file $(top_builddir)/plugins/group_file/Makefile \ --file $(top_builddir)/plugins/sudoers/Makefile \ --file $(top_builddir)/plugins/system_group/Makefile \ - --file $(top_builddir)/src/Makefile \ - --file $(top_builddir)/lib/zlib/Makefile + --file $(top_builddir)/src/Makefile ChangeLog: if test -d $(srcdir)/.hg && cd $(srcdir); then \ diff --git a/configure b/configure index 414b0abec..1d8c8cb41 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for sudo 1.8.29. +# Generated by GNU Autoconf 2.69 for sudo 1.9.0. # # Report bugs to . # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sudo' PACKAGE_TARNAME='sudo' -PACKAGE_VERSION='1.8.29' -PACKAGE_STRING='sudo 1.8.29' +PACKAGE_VERSION='1.9.0' +PACKAGE_STRING='sudo 1.9.0' PACKAGE_BUGREPORT='https://bugzilla.sudo.ws/' PACKAGE_URL='' @@ -1544,7 +1544,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures sudo 1.8.29 to adapt to many kinds of systems. +\`configure' configures sudo 1.9.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1609,7 +1609,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sudo 1.8.29:";; + short | recursive ) echo "Configuration of sudo 1.9.0:";; esac cat <<\_ACEOF @@ -1875,7 +1875,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sudo configure 1.8.29 +sudo configure 1.9.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2584,7 +2584,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by sudo $as_me 1.8.29, which was +It was created by sudo $as_me 1.9.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -27005,7 +27005,7 @@ elif test X"$TMPFILES_D" != X""; then ac_config_files="$ac_config_files init.d/sudo.conf" fi -ac_config_files="$ac_config_files Makefile doc/Makefile examples/Makefile include/Makefile lib/util/Makefile lib/util/util.exp src/sudo_usage.h src/Makefile plugins/sample/Makefile plugins/group_file/Makefile plugins/system_group/Makefile plugins/sudoers/Makefile plugins/sudoers/sudoers" +ac_config_files="$ac_config_files Makefile doc/Makefile examples/Makefile include/Makefile lib/util/Makefile lib/util/util.exp logsrvd/Makefile src/sudo_usage.h src/Makefile plugins/sample/Makefile plugins/group_file/Makefile plugins/system_group/Makefile plugins/sudoers/Makefile plugins/sudoers/sudoers" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -27513,7 +27513,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by sudo $as_me 1.8.29, which was +This file was extended by sudo $as_me 1.9.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -27579,7 +27579,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sudo config.status 1.8.29 +sudo config.status 1.9.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" @@ -28005,6 +28005,7 @@ do "include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;; "lib/util/Makefile") CONFIG_FILES="$CONFIG_FILES lib/util/Makefile" ;; "lib/util/util.exp") CONFIG_FILES="$CONFIG_FILES lib/util/util.exp" ;; + "logsrvd/Makefile") CONFIG_FILES="$CONFIG_FILES logsrvd/Makefile" ;; "src/sudo_usage.h") CONFIG_FILES="$CONFIG_FILES src/sudo_usage.h" ;; "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; "plugins/sample/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/sample/Makefile" ;; diff --git a/configure.ac b/configure.ac index a28b3b282..24f391b71 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ dnl ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF dnl OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. dnl AC_PREREQ([2.59]) -AC_INIT([sudo], [1.8.29], [https://bugzilla.sudo.ws/], [sudo]) +AC_INIT([sudo], [1.9.0], [https://bugzilla.sudo.ws/], [sudo]) AC_CONFIG_HEADER([config.h pathnames.h]) AC_CONFIG_SRCDIR([src/sudo.c]) dnl @@ -4480,7 +4480,7 @@ if test X"$INIT_SCRIPT" != X""; then elif test X"$TMPFILES_D" != X""; then AC_CONFIG_FILES([init.d/sudo.conf]) fi -AC_CONFIG_FILES([Makefile doc/Makefile examples/Makefile include/Makefile lib/util/Makefile lib/util/util.exp src/sudo_usage.h src/Makefile plugins/sample/Makefile plugins/group_file/Makefile plugins/system_group/Makefile plugins/sudoers/Makefile plugins/sudoers/sudoers]) +AC_CONFIG_FILES([Makefile doc/Makefile examples/Makefile include/Makefile lib/util/Makefile lib/util/util.exp logsrvd/Makefile src/sudo_usage.h src/Makefile plugins/sample/Makefile plugins/group_file/Makefile plugins/system_group/Makefile plugins/sudoers/Makefile plugins/sudoers/sudoers]) AC_OUTPUT dnl diff --git a/logsrvd/Makefile.in b/logsrvd/Makefile.in new file mode 100644 index 000000000..d312f5845 --- /dev/null +++ b/logsrvd/Makefile.in @@ -0,0 +1,279 @@ +# +# SPDX-License-Identifier: ISC +# +# 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. +# +# @configure_input@ +# + +#### Start of system configuration section. #### + +srcdir = @srcdir@ +devdir = @devdir@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +incdir = $(top_srcdir)/include +rundir = @rundir@ +cross_compiling = @CROSS_COMPILING@ + +# Compiler & tools to use +CC = @CC@ +LIBTOOL = @LIBTOOL@ +SED = @SED@ + +# Our install program supports extra flags... +INSTALL = $(SHELL) $(top_srcdir)/install-sh -c +INSTALL_OWNER = -o $(install_uid) -g $(install_gid) +INSTALL_BACKUP = @INSTALL_BACKUP@ + +# Libraries +LT_LIBS = $(top_builddir)/lib/util/libsudo_util.la +LIBS = -lprotobuf-c $(LT_LIBS) + +# C preprocessor defines +CPPDEFS = -D_PATH_SUDO_LOGSRVD_CONF=\"$(sysconfdir)/sudo_logsrvd.conf\" \ + -DLOCALEDIR=\"$(localedir)\" + +# C preprocessor flags +CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(devdir) -I$(srcdir) \ + -I$(top_srcdir) $(CPPDEFS) @CPPFLAGS@ + +# Usually -O and/or -g +CFLAGS = @CFLAGS@ + +# Flags to pass to the link stage +LDFLAGS = @LDFLAGS@ +LT_LDFLAGS = @LT_LDFLAGS@ + +# Flags to pass to libtool +LTFLAGS = --tag=disable-static + +# Address sanitizer flags +ASAN_CFLAGS = @ASAN_CFLAGS@ +ASAN_LDFLAGS = @ASAN_LDFLAGS@ + +# PIE flags +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ + +# Stack smashing protection flags +SSP_CFLAGS = @SSP_CFLAGS@ +SSP_LDFLAGS = @SSP_LDFLAGS@ + +# cppcheck options, usually set in the top-level Makefile +CPPCHECK_OPTS = -q --force --enable=warning,performance,portability --suppress=constStatement --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64 + +# splint options, usually set in the top-level Makefile +SPLINT_OPTS = -D__restrict= -checks + +# PVS-studio options +PVS_CFG = $(top_srcdir)/PVS-Studio.cfg +PVS_IGNORE = 'V707,V011,V002,V536' +PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE) + +# Where to install things... +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +sbindir = @sbindir@ +sysconfdir = @sysconfdir@ +libexecdir = @libexecdir@ +datarootdir = @datarootdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ + +# User and group IDs the installed files should be "owned" by +install_uid = 0 +install_gid = 0 + +# Set to non-empty for development mode +DEVEL = @DEVEL@ + +#### End of system configuration section. #### + +SHELL = @SHELL@ + +PROGS = logsrvd sendlog + +LOGSRVD_OBJS = iolog_writer.o logsrvd.o log_server.pb-c.o + +SENDLOG_OBJS = sendlog.o iolog_reader.o log_server.pb-c.o + +IOBJS = $(LOGSRVD_OBJS:.o=.i) $(SENDLOG_OBJS:.o=.i) + +POBJS = $(IOBJS:.i=.plog) + +LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/ + +VERSION = @PACKAGE_VERSION@ + +all: $(PROGS) + +Makefile: $(srcdir)/Makefile.in + cd $(top_builddir) && ./config.status --file logsrvd/Makefile + +.SUFFIXES: .c .h .i .lo .o .plog + +.c.o: + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $< + +.c.lo: + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $< + +.c.i: + $(CC) -E -o $@ $(CPPFLAGS) $< + +.i.plog: + ifile=$<; rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $${ifile%i}c --i-file $< --output-file $@ + +logsrvd: $(LOGSRVD_OBJS) $(LT_LIBS) + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(LOGSRVD_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) + +sendlog: $(SENDLOG_OBJS) $(LT_LIBS) + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(SENDLOG_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) -lz + +GENERATED = log_server.pb-c.h log_server.pb-c.c + +$(devdir)/log_server.pb-c.c $(devdir)/log_server.pb-c.h: $(srcdir)/log_server.proto + @if [ -n "$(DEVEL)" ]; then \ + protoc-c --c_out=$(devdir) --proto_path=$(srcdir) $(srcdir)/log_server.proto; \ + fi + +pre-install: + +install: install-binaries + +install-dirs: + $(SHELL) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(sbindir) + +install-binaries: install-dirs $(PROGS) + INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --mode=install $(INSTALL) $(INSTALL_OWNER) -m 0755 logsrvd $(DESTDIR)$(sbindir)/logsrvd + INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --mode=install $(INSTALL) $(INSTALL_OWNER) -m 0755 sendlog $(DESTDIR)$(sbindir)/sendlog + +install-doc: + +install-includes: + +install-plugin: + +uninstall: + -rm -f $(DESTDIR)$(sbindir)/logsrvd \ + $(DESTDIR)$(sbindir)/sendlog + -test -z "$(INSTALL_BACKUP)" || \ + rm -f $(DESTDIR)$(sbindir)/logsrvd$(INSTALL_BACKUP) \ + $(DESTDIR)$(sbindir)/sendlog + +splint: + splint $(SPLINT_OPTS) -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) -I$(top_srcdir) $(srcdir)/*.c + +cppcheck: + cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) -I$(top_srcdir) $(srcdir)/*.c + +pvs-log-files: $(POBJS) + +pvs-studio: $(POBJS) + plog-converter $(PVS_LOG_OPTS) $(POBJS) + +check: + +clean: + -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f $(PROGS) \ + *.lo *.o *.la *.a *.i *.plog stamp-* core *.core core.* nohup.out + +mostlyclean: clean + +distclean: clean + -rm -rf Makefile .libs + @if [ -n "$(DEVEL)" -a "$(devdir)" != "$(srcdir)" ]; then \ + cmd='rm -rf $(GENERATED)'; \ + echo "$$cmd"; eval $$cmd; \ + fi + +clobber: distclean + +realclean: distclean + rm -f TAGS tags + +cleandir: realclean + +# Autogenerated dependencies, do not modify +iolog_reader.o: $(srcdir)/iolog_reader.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_queue.h $(incdir)/sudo_util.h $(srcdir)/iolog.h \ + $(srcdir)/sendlog.h $(top_builddir)/config.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_reader.c +iolog_reader.i: $(srcdir)/iolog_reader.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_queue.h $(incdir)/sudo_util.h $(srcdir)/iolog.h \ + $(srcdir)/sendlog.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +iolog_reader.plog: iolog_reader.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_reader.c --i-file $< --output-file $@ +iolog_writer.o: $(srcdir)/iolog_writer.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_queue.h $(incdir)/sudo_util.h $(srcdir)/iolog.h \ + $(srcdir)/logsrvd.h $(top_builddir)/config.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_writer.c +iolog_writer.i: $(srcdir)/iolog_writer.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_queue.h $(incdir)/sudo_util.h $(srcdir)/iolog.h \ + $(srcdir)/logsrvd.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +iolog_writer.plog: iolog_writer.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_writer.c --i-file $< --output-file $@ +log_server.pb-c.o: $(srcdir)/log_server.pb-c.c $(devdir)/log_server.pb-c.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/log_server.pb-c.c +log_server.pb-c.i: $(srcdir)/log_server.pb-c.c $(devdir)/log_server.pb-c.h + $(CC) -E -o $@ $(CPPFLAGS) $< +log_server.pb-c.plog: log_server.pb-c.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/log_server.pb-c.c --i-file $< --output-file $@ +logsrvd.o: $(srcdir)/logsrvd.c $(devdir)/log_server.pb-c.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/logsrvd.h \ + $(top_builddir)/config.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/logsrvd.c +logsrvd.i: $(srcdir)/logsrvd.c $(devdir)/log_server.pb-c.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/logsrvd.h \ + $(top_builddir)/config.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 $@ +sendlog.o: $(srcdir)/sendlog.c $(devdir)/log_server.pb-c.h \ + $(incdir)/compat/getaddrinfo.h $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/iolog.h \ + $(srcdir)/sendlog.h $(top_builddir)/config.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sendlog.c +sendlog.i: $(srcdir)/sendlog.c $(devdir)/log_server.pb-c.h \ + $(incdir)/compat/getaddrinfo.h $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/iolog.h \ + $(srcdir)/sendlog.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +sendlog.plog: sendlog.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sendlog.c --i-file $< --output-file $@ diff --git a/logsrvd/iolog.h b/logsrvd/iolog.h new file mode 100644 index 000000000..161c0f01a --- /dev/null +++ b/logsrvd/iolog.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef LOGSRVD_IOLOG_H +#define LOGSRVD_IOLOG_H + +#include + +/* + * I/O log event types as stored as the first field in the timing file. + * Changing existing values will result in incompatible I/O log files. + */ +#define IO_EVENT_STDIN 0 +#define IO_EVENT_STDOUT 1 +#define IO_EVENT_STDERR 2 +#define IO_EVENT_TTYIN 3 +#define IO_EVENT_TTYOUT 4 +#define IO_EVENT_WINSIZE 5 +#define IO_EVENT_TTYOUT_1_8_7 6 +#define IO_EVENT_SUSPEND 7 +#define IO_EVENT_COUNT 8 + +/* + * Contents of the sudo I/O info log + */ +struct log_info { + char *command; + char *cwd; + char *iolog_dir; + char *rungroup; + char *runuser; + char *submithost; + char *submituser; + char *ttyname; + char **argv; + time_t start_time; + int argc; + int lines; + int columns; +}; + +#endif /* LOGSRVD_IOLOG_H */ diff --git a/logsrvd/iolog_reader.c b/logsrvd/iolog_reader.c new file mode 100644 index 000000000..67d89ff66 --- /dev/null +++ b/logsrvd/iolog_reader.c @@ -0,0 +1,452 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "log_server.pb-c.h" +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" +#include "sudo_fatal.h" +#include "iolog.h" +#include "sendlog.h" + +static int timing_event_adj; +static gzFile io_fds[IOFD_MAX]; + +/* I/O log file names relative to iolog_dir. */ +/* XXX - duplicated with server */ +static const char *iolog_names[] = { + "stdin", /* IOFD_STDIN */ + "stdout", /* IOFD_STDOUT */ + "stderr", /* IOFD_STDERR */ + "ttyin", /* IOFD_TTYIN */ + "ttyout", /* IOFD_TTYOUT */ + "timing", /* IOFD_TIMING */ + NULL /* IOFD_MAX */ +}; + +void +free_log_info(struct log_info *li) +{ + if (li != NULL) { + free(li->cwd); + free(li->submituser); + free(li->runuser); + free(li->rungroup); + free(li->ttyname); + free(li->command); + free(li); + } +} + +/* + * Open any I/O log files that are present. + * The timing file must always exist. + */ +bool +iolog_open(const char *iolog_path) +{ + char fname[PATH_MAX]; + int i, len; + debug_decl(iolog_open, SUDO_DEBUG_UTIL) + + for (i = 0; iolog_names[i] != NULL; i++) { + len = snprintf(fname, sizeof(fname), "%s/%s", iolog_path, + iolog_names[i]); + if (len < 0 || len >= ssizeof(fname)) { + errno = ENAMETOOLONG; + sudo_warn("%s/%s", iolog_path, iolog_names[i]); + } + io_fds[i] = gzopen(fname, "r"); + if (io_fds[i] == NULL && i == IOFD_TIMING) { + /* The timing file is not optional. */ + sudo_warn("unable to open %s/%s", iolog_path, iolog_names[i]); + debug_return_bool(false); + } + } + debug_return_bool(true); +} + +struct log_info * +parse_logfile(const char *logfile) +{ + FILE *fp; + char *buf = NULL, *cp, *ep; + size_t bufsize = 0, cwdsize = 0, cmdsize = 0; + long long llval; + struct log_info *li = NULL; + debug_decl(parse_logfile, SUDO_DEBUG_UTIL) + + fp = fopen(logfile, "r"); + if (fp == NULL) { + sudo_warn("unable to open %s", logfile); + goto bad; + } + + /* + * ID file has three lines: + * 1) a log info line + * 2) cwd + * 3) command with args + */ + if ((li = calloc(1, sizeof(*li))) == NULL) + sudo_fatalx("%s: %s", __func__, "unable to allocate memory"); + if (getdelim(&buf, &bufsize, '\n', fp) == -1 || + getdelim(&li->cwd, &cwdsize, '\n', fp) == -1 || + getdelim(&li->command, &cmdsize, '\n', fp) == -1) { + sudo_warn("%s: invalid log file", logfile); + goto bad; + } + + /* Strip the newline from the cwd and command. */ + li->cwd[strcspn(li->cwd, "\n")] = '\0'; + li->command[strcspn(li->command, "\n")] = '\0'; + + /* + * Crack the log line (lines and columns not present in old versions). + * timestamp:submituser:runuser:rungroup:ttyname:lines:columns + * XXX - probably better to use strtok and switch on the state. + */ + buf[strcspn(buf, "\n")] = '\0'; + cp = buf; + + /* timestamp */ + errno = 0; + llval = strtoll(cp, &ep, 10); + if (cp == ep || *ep != ':') { + sudo_warn("%s: time stamp field is missing", logfile); + goto bad; + } + if (errno == ERANGE && (llval == LLONG_MAX || llval == LLONG_MIN)) { + sudo_warn("%s: time stamp %s: out of range", logfile, cp); + goto bad; + } + li->start_time = llval; + + /* submituser */ + cp = ep + 1; + if ((ep = strchr(cp, ':')) == NULL) { + sudo_warn("%s: submituser field is missing", logfile); + goto bad; + } + if ((li->submituser = strndup(cp, (size_t)(ep - cp))) == NULL) + sudo_fatalx("%s: %s", __func__, "unable to allocate memory"); + + /* runuser */ + cp = ep + 1; + if ((ep = strchr(cp, ':')) == NULL) { + sudo_warn("%s: runuser field is missing", logfile); + goto bad; + } + if ((li->runuser = strndup(cp, (size_t)(ep - cp))) == NULL) + sudo_fatalx("%s: %s", __func__, "unable to allocate memory"); + + /* rungroup */ + cp = ep + 1; + if ((ep = strchr(cp, ':')) == NULL) { + sudo_warn("%s: rungroup field is missing", logfile); + goto bad; + } + if (cp != ep) { + if ((li->rungroup = strndup(cp, (size_t)(ep - cp))) == NULL) + sudo_fatalx("%s: %s", __func__, "unable to allocate memory"); + } + + /* ttyname, followed by optional lines + columns */ + cp = ep + 1; + if ((ep = strchr(cp, ':')) == NULL) { + /* just the ttyname */ + if ((li->ttyname = strdup(cp)) == NULL) + sudo_fatalx("%s: %s", __func__, "unable to allocate memory"); + } else { + /* ttyname followed by lines + columns */ + unsigned long ulval; + + if ((li->ttyname = strndup(cp, (size_t)(ep - cp))) == NULL) + sudo_fatalx("%s: %s", __func__, "unable to allocate memory"); + + /* lines */ + cp = ep + 1; + errno = 0; + ulval = strtoul(cp, &ep, 10); + if (cp == ep || *ep != ':') { + sudo_warn("%s: terminal lines field is missing", logfile); + goto bad; + } + if ((errno == ERANGE && ulval == ULONG_MAX) || ulval > INT_MAX) { + sudo_warn("%s: terminal lines %s: out of range", logfile, cp); + goto bad; + } + li->lines = (int)ulval; + + /* columns */ + cp = ep + 1; + errno = 0; + ulval = strtoul(cp, &ep, 10); + if (cp == ep || (*ep != ':' && *ep != '\0')) { + sudo_warn("%s: terminal columns field is missing", logfile); + goto bad; + } + if ((errno == ERANGE && ulval == ULONG_MAX) || ulval > INT_MAX) { + sudo_warn("%s: terminal columns %s: out of range", logfile, cp); + goto bad; + } + li->columns = (int)ulval; + } + fclose(fp); + free(buf); + debug_return_ptr(li); + +bad: + if (fp != NULL) + fclose(fp); + free(buf); + free_log_info(li); + debug_return_ptr(NULL); +} + +/* + * Parse the delay as seconds and nanoseconds: %lld.%09ld + * Sudo used to write this as a double, but since timing data is logged + * in the C locale this may not match the current locale. + */ +static char * +parse_delay(const char *cp, struct timespec *delay) +{ + long long llval; + size_t len; + char *ep; + debug_decl(parse_delay, SUDO_DEBUG_UTIL) + + /* Parse seconds (whole number portion). */ + errno = 0; + llval = strtoll(cp, &ep, 10); + /* Radix may be in user's locale for sudo < 1.7.4 so accept that too. */ + if (cp == ep || *ep != '.') { + sudo_warnx("invalid characters after seconds: %s", ep); + debug_return_ptr(NULL); + } + if (errno == ERANGE && (llval == LLONG_MAX || llval == LLONG_MIN)) { + sudo_warnx("%s: number of seconds out of range", cp); + debug_return_ptr(NULL); + } + delay->tv_sec = (time_t)llval; + cp = ep + 1; + + /* Parse fractional part, we may read more precision than we can store. */ + errno = 0; + llval = strtoll(cp, &ep, 10); + if (cp == ep || (*ep != ' ' && *ep != '\0')) { + sudo_warnx("invalid characters after nanoseconds: %s", ep); + debug_return_ptr(NULL); + } + if (errno == ERANGE && (llval == LLONG_MAX || llval == LLONG_MIN)) { + sudo_warnx("%s: number of nanoseconds out of range", cp); + debug_return_ptr(NULL); + } + + /* Adjust fractional part to nanosecond precision. */ + len = (size_t)(ep - cp); + if (len < 9) { + /* Convert to nanosecond precision. */ + do { + llval *= 10; + } while (++len < 9); + } else if (len > 9) { + /* Clamp to nanoseconds. */ + do { + llval /= 10; + } while (--len > 9); + } + delay->tv_nsec = (long)llval; + + /* Advance to the next field. */ + while (isspace((unsigned char)*ep)) + ep++; + + debug_return_str((char *)ep); +} + +/* + * Parse a timing line, which is formatted as: + * IO_EVENT_TTYOUT sleep_time num_bytes + * IO_EVENT_WINSIZE sleep_time lines columns + * IO_EVENT_SUSPEND sleep_time signal + * Where type is IO_EVENT_*, sleep_time is the number of seconds to sleep + * before writing the data and num_bytes is the number of bytes to output. + * Returns true on success and false on failure. + */ +static bool +parse_timing(const char *buf, struct timing_closure *timing) +{ + unsigned long ulval; + char *cp, *ep; + debug_decl(parse_timing, SUDO_DEBUG_UTIL) + + /* Parse event type. */ + ulval = strtoul(buf, &ep, 10); + if (ep == buf || !isspace((unsigned char) *ep)) + goto bad; + if (ulval >= IO_EVENT_COUNT) + goto bad; + if (ulval == IO_EVENT_TTYOUT_1_8_7) { + /* work around a bug in timing files generated by sudo 1.8.7 */ + timing_event_adj = 2; + } + timing->event = (int)ulval - timing_event_adj; + for (cp = ep + 1; isspace((unsigned char) *cp); cp++) + continue; + + /* Parse delay, returns the next field or NULL on error. */ + if ((cp = parse_delay(cp, &timing->delay)) == NULL) + goto bad; + + switch (timing->event) { + case IO_EVENT_SUSPEND: + /* Signal name (no leading SIG prefix) or number. */ + if (isdigit((unsigned char)*cp)) { + /* Signal number, convert to name. */ + ulval = strtoul(cp, &ep, 10); + if (ep == cp || *ep != '\0') + goto bad; + if (ulval > INT_MAX) + goto bad; + if (sig2str(ulval, timing->buf) == -1) + goto bad; + } else { + /* Signal name. */ + if (strlcpy(timing->buf, cp, timing->bufsize) >= timing->bufsize) + goto bad; + } + break; + case IO_EVENT_WINSIZE: + ulval = strtoul(cp, &ep, 10); + if (ep == cp || !isspace((unsigned char) *ep)) + goto bad; + if (ulval > INT_MAX) + goto bad; + timing->u.winsize.lines = (int)ulval; + for (cp = ep + 1; isspace((unsigned char) *cp); cp++) + continue; + + ulval = strtoul(cp, &ep, 10); + if (ep == cp || *ep != '\0') + goto bad; + if (ulval > INT_MAX) + goto bad; + timing->u.winsize.columns = (int)ulval; + break; + default: + errno = 0; + ulval = strtoul(cp, &ep, 10); + if (ep == cp || *ep != '\0') + goto bad; + /* Note: assumes SIZE_MAX == ULONG_MAX */ + if (errno == ERANGE && ulval == ULONG_MAX) + goto bad; + timing->u.nbytes = (size_t)ulval; + break; + } + + debug_return_bool(true); +bad: + debug_return_bool(false); +} + +/* + * Read the next record from the timing file. + * Return 0 on success, 1 on EOF and -1 on error. + */ +int +read_timing_record(struct timing_closure *timing) +{ + const char *errstr; + char line[LINE_MAX]; + int errnum; + debug_decl(read_timing_record, SUDO_DEBUG_UTIL) + + /* Read next record from timing file. */ + if (gzgets(io_fds[IOFD_TIMING], line, sizeof(line)) == NULL) { + /* EOF or error reading timing file, we are done. */ + if (gzeof(io_fds[IOFD_TIMING])) + debug_return_int(1); /* EOF */ + if ((errstr = gzerror(io_fds[IOFD_TIMING], &errnum)) == NULL) + errstr = strerror(errno); + sudo_warnx("error reading timing file: %s", errstr); + debug_return_int(-1); + } + + /* Parse timing file record. */ + line[strcspn(line, "\n")] = '\0'; + if (!parse_timing(line, timing)) { + sudo_warnx("invalid timing file line: %s", line); + debug_return_int(-1); + } + + debug_return_int(0); +} + +bool +read_io_buf(struct timing_closure *timing) +{ + size_t nread; + debug_decl(read_io_buf, SUDO_DEBUG_UTIL) + + if (io_fds[timing->event] == NULL) { + sudo_warnx("%s file not open", iolog_names[timing->event]); + debug_return_bool(false); + } + + /* Expand buf as needed. */ + if (timing->u.nbytes > timing->bufsize) { + free(timing->buf); + do { + timing->bufsize *= 2; + } while (timing->u.nbytes > timing->bufsize); + if ((timing->buf = malloc(timing->bufsize)) == NULL) { + sudo_warn("malloc %zu", timing->bufsize); + timing->u.nbytes = 0; + debug_return_bool(false); + } + } + + nread = gzread(io_fds[timing->event], timing->buf, timing->u.nbytes); + if (nread != timing->u.nbytes) { + int errnum; + const char *errstr; + + if ((errstr = gzerror(io_fds[timing->event], &errnum)) == NULL) + errstr = strerror(errno); + sudo_warnx("unable to read %s file: %s", iolog_names[timing->event], errstr); + debug_return_bool(false); + } + debug_return_bool(true); +} diff --git a/logsrvd/iolog_writer.c b/logsrvd/iolog_writer.c new file mode 100644 index 000000000..caa21bc2e --- /dev/null +++ b/logsrvd/iolog_writer.c @@ -0,0 +1,508 @@ +/* + * 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 +#include +#include +#ifdef HAVE_STDBOOL_H +# include +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#include +#include +#include +#include +#include + +#include "log_server.pb-c.h" +#include "sudo_compat.h" +#include "sudo_queue.h" +#include "sudo_debug.h" +#include "sudo_util.h" +#include "sudo_fatal.h" +#include "iolog.h" +#include "logsrvd.h" + +/* I/O log file names relative to iolog_dir. */ +static const char *iolog_names[] = { + "stdin", /* IOFD_STDIN */ + "stdout", /* IOFD_STDOUT */ + "stderr", /* IOFD_STDERR */ + "ttyin", /* IOFD_TTYIN */ + "ttyout", /* IOFD_TTYOUT */ + "timing", /* IOFD_TIMING */ + NULL /* IOFD_MAX */ +}; + +static inline bool +has_numval(InfoMessage *info) +{ + return info->value_case == INFO_MESSAGE__VALUE_NUMVAL; +} + +static inline bool +has_strval(InfoMessage *info) +{ + return info->value_case == INFO_MESSAGE__VALUE_STRVAL; +} + +static inline bool +has_strlistval(InfoMessage *info) +{ + return info->value_case == INFO_MESSAGE__VALUE_STRLISTVAL; +} + +/* + * Fill in log info from an ExecMessage + * Only makes a shallow copy of strings and string lists. + */ +static bool +log_info_fill(struct log_info *log_info, ExecMessage *msg) +{ + size_t idx; + bool ret = true; + debug_decl(log_info_fill, SUDO_DEBUG_UTIL) + + memset(log_info, 0, sizeof(*log_info)); + + /* Start time. */ + log_info->start_time = msg->start_time->tv_sec; + + /* Default values */ + log_info->lines = 24; + log_info->columns = 80; + + /* Pull out values by key from info array. */ + for (idx = 0; idx < msg->n_info_msgs; idx++) { + InfoMessage *info = msg->info_msgs[idx]; + const char *key = info->key; + switch (key[0]) { + case 'c': + if (strcmp(key, "columns") == 0) { + if (!has_numval(info)) { + sudo_warnx("columns specified but not a number"); + } else if (info->numval <= 0 || info->numval > INT_MAX) { + sudo_warnx("columns (%" PRId64 ") out of range", info->numval); + } else { + log_info->columns = info->numval; + } + continue; + } + if (strcmp(key, "command") == 0) { + if (has_strval(info)) { + log_info->command = info->strval; + } else { + sudo_warnx("command specified but not a string"); + } + continue; + } + if (strcmp(key, "cwd") == 0) { + if (has_strval(info)) { + log_info->cwd = info->strval; + } else { + sudo_warnx("cwd specified but not a string"); + } + continue; + } + break; + case 'l': + if (strcmp(key, "lines") == 0) { + if (!has_numval(info)) { + sudo_warnx("lines specified but not a number"); + } else if (info->numval <= 0 || info->numval > INT_MAX) { + sudo_warnx("lines (%" PRId64 ") out of range", info->numval); + } else { + log_info->lines = info->numval; + } + continue; + } + break; + case 'r': + if (strcmp(key, "runargv") == 0) { + if (has_strlistval(info)) { + log_info->argv = info->strlistval->strings; + log_info->argc = info->strlistval->n_strings; + } else { + sudo_warnx("runargv specified but not a string list"); + } + continue; + } + if (strcmp(key, "rungroup") == 0) { + if (has_strval(info)) { + log_info->rungroup = info->strval; + } else { + sudo_warnx("rungroup specified but not a string"); + } + continue; + } + if (strcmp(key, "runuser") == 0) { + if (has_strval(info)) { + log_info->runuser = info->strval; + } else { + sudo_warnx("runuser specified but not a string"); + } + continue; + } + break; + case 's': + if (strcmp(key, "submithost") == 0) { + if (has_strval(info)) { + log_info->submithost = info->strval; + } else { + sudo_warnx("submithost specified but not a string"); + } + continue; + } + if (strcmp(key, "submituser") == 0) { + if (has_strval(info)) { + log_info->submituser = info->strval; + } else { + sudo_warnx("submituser specified but not a string"); + } + continue; + } + break; + case 't': + if (strcmp(key, "ttyname") == 0) { + if (has_strval(info)) { + log_info->ttyname = info->strval; + } else { + sudo_warnx("ttyname specified but not a string"); + } + continue; + } + break; + } + } + + /* Check for required settings */ + if (log_info->submituser == NULL) { + sudo_warnx("missing user in ExecMessage"); + ret = false; + } + if (log_info->submithost == NULL) { + sudo_warnx("missing host in ExecMessage"); + ret = false; + } + if (log_info->command == NULL) { + sudo_warnx("missing command in ExecMessage"); + ret = false; + } + + debug_return_bool(ret); +} + +/* + * Create I/O log path + * Set iolog_dir and iolog_dir_fd in the closure + */ +static bool +create_iolog_dir(struct log_info *log_info, struct connection_closure *closure) +{ + char path[PATH_MAX]; + int len; + debug_decl(create_iolog_dir, SUDO_DEBUG_UTIL) + + /* Create IOLOG_DIR/host/user/XXXXXX directory */ + if (mkdir(IOLOG_DIR, 0755) == -1 && errno != EEXIST) { + sudo_warn("mkdir %s", path); + goto bad; + } + len = snprintf(path, sizeof(path), "%s/%s", IOLOG_DIR, + log_info->submithost); + if (len < 0 || len >= ssizeof(path)) { + sudo_warn("snprintf"); + goto bad; + } + if (mkdir(path, 0755) == -1 && errno != EEXIST) { + sudo_warn("mkdir %s", path); + goto bad; + } + len = snprintf(path, sizeof(path), "%s/%s/%s", IOLOG_DIR, + log_info->submithost, log_info->submituser); + if (len < 0 || len >= ssizeof(path)) { + sudo_warn("snprintf"); + goto bad; + } + if (mkdir(path, 0755) == -1 && errno != EEXIST) { + sudo_warn("mkdir %s", path); + goto bad; + } + len = snprintf(path, sizeof(path), "%s/%s/%s/XXXXXX", IOLOG_DIR, + log_info->submithost, log_info->submituser); + if (len < 0 || len >= ssizeof(path)) { + sudo_warn("snprintf"); + goto bad; + } + if (mkdtemp(path) == NULL) { + sudo_warn("mkdtemp %s", path); + goto bad; + } + sudo_warnx("I/O log path %s", path); // XXX + + /* Make a copy of iolog_dir for error messages. */ + if ((closure->iolog_dir = strdup(path)) == NULL) { + sudo_warn("strdup"); + goto bad; + } + + /* We use iolog_dir_fd in calls to openat(2) */ + closure->iolog_dir_fd = open(closure->iolog_dir, O_RDONLY); + if (closure->iolog_dir_fd == -1) { + sudo_warn("%s", path); + goto bad; + } + + debug_return_bool(true); +bad: + free(closure->iolog_dir); + debug_return_bool(false); +} + +/* + * Write the sudo-style I/O log info file containing user and command info. + */ +static bool +log_info_write(struct log_info *log_info, struct connection_closure *closure) +{ + int fd, i; + FILE *fp; + int error; + debug_decl(log_info_write, SUDO_DEBUG_UTIL) + + fd = openat(closure->iolog_dir_fd, "log", O_CREAT|O_EXCL|O_WRONLY, 0600); + if (fd == -1 || (fp = fdopen(fd, "w")) == NULL) { + sudo_warn("unable to open %s", closure->iolog_dir); + if (fd != -1) + close(fd); + debug_return_bool(false); + } + + fprintf(fp, "%lld:%s:%s:%s:%s:%d:%d\n%s\n", + (long long)log_info->start_time, log_info->submituser, + log_info->runuser ? log_info->runuser : RUNAS_DEFAULT, + log_info->rungroup ? log_info->rungroup : "", + log_info->ttyname ? log_info->ttyname : "unknown", + log_info->lines, log_info->columns, + log_info->cwd ? log_info->cwd : "unknown"); + fputs(log_info->command, fp); + for (i = 1; i < log_info->argc; i++) { + fputc(' ', fp); + fputs(log_info->argv[i], fp); + } + fputc('\n', fp); + fflush(fp); + if ((error = ferror(fp))) + sudo_warn("unable to write to I/O log file %s", closure->iolog_dir); + fclose(fp); + + debug_return_bool(!error); +} + +static bool +iolog_open(int iofd, struct connection_closure *closure) +{ + debug_decl(iolog_open, SUDO_DEBUG_UTIL) + + if (iofd < 0 || iofd >= IOFD_MAX) { + sudo_warnx("invalid iofd %d", iofd); + debug_return_bool(false); + } + + closure->io_fds[iofd] = openat(closure->iolog_dir_fd, + iolog_names[iofd], O_CREAT|O_EXCL|O_WRONLY, 0600); + debug_return_bool(closure->io_fds[iofd] != -1); +} + +void +iolog_close(struct connection_closure *closure) +{ + int i; + debug_decl(iolog_close, SUDO_DEBUG_UTIL) + + for (i = 0; i < IOFD_MAX; i++) { + if (closure->io_fds[i] == -1) + continue; + close(closure->io_fds[i]); + } + if (closure->iolog_dir_fd != -1) + close(closure->iolog_dir_fd); + + debug_return; +} + +bool +iolog_init(ExecMessage *msg, struct connection_closure *closure) +{ + struct log_info log_info; + int i; + debug_decl(iolog_init, SUDO_DEBUG_UTIL) + + /* Init io_fds in closure. */ + for (i = 0; i < IOFD_MAX; i++) + closure->io_fds[i] = -1; + + /* Fill in log_info */ + if (!log_info_fill(&log_info, msg)) + debug_return_bool(false); + + /* Create I/O log dir */ + if (!create_iolog_dir(&log_info, closure)) + debug_return_bool(false); + + /* Write sudo I/O log info file */ + if (!log_info_write(&log_info, closure)) + debug_return_bool(false); + + /* Create timing, stdout, stderr and ttyout files for sudoreplay. */ + if (!iolog_open(IOFD_TIMING, closure) || + !iolog_open(IOFD_STDOUT, closure) || + !iolog_open(IOFD_STDERR, closure) || + !iolog_open(IOFD_TTYOUT, closure)) + debug_return_bool(false); + + /* Ready to log I/O buffers. */ + debug_return_bool(true); +} + +static bool +iolog_write(int iofd, void *buf, size_t len, struct connection_closure *closure) +{ + debug_decl(iolog_write, SUDO_DEBUG_UTIL) + size_t nread; + + if (iofd < 0 || iofd >= IOFD_MAX) { + sudo_warnx("invalid iofd %d", iofd); + debug_return_bool(false); + } + nread = write(closure->io_fds[iofd], buf, len); + debug_return_bool(nread == len); +} + +/* + * Add given delta to elapsed time. + * We cannot use timespecadd here since delta is not struct timespec. + */ +static void +update_elapsed_time(TimeSpec *delta, struct timespec *elapsed) +{ + debug_decl(update_elapsed_time, SUDO_DEBUG_UTIL) + + /* Cannot use timespecadd since msg doesn't use struct timespec. */ + elapsed->tv_sec += delta->tv_sec; + elapsed->tv_nsec += delta->tv_nsec; + while (elapsed->tv_nsec >= 1000000000) { + elapsed->tv_sec++; + elapsed->tv_nsec -= 1000000000; + } + + debug_return; +} + +int +store_iobuf(int iofd, IoBuffer *msg, struct connection_closure *closure) +{ + char tbuf[1024]; + int len; + debug_decl(store_iobuf, SUDO_DEBUG_UTIL) + + /* Open log file as needed. */ + if (closure->io_fds[iofd] == -1) { + if (!iolog_open(iofd, closure)) + debug_return_int(-1); + } + + /* Format timing data. */ + /* FIXME - assumes IOFD_* matches IO_EVENT_* */ + len = snprintf(tbuf, sizeof(tbuf), "%d %lld.%09d %zu\n", + iofd, (long long)msg->delay->tv_sec, (int)msg->delay->tv_nsec, + msg->data.len); + if (len < 0 || len >= ssizeof(tbuf)) { + sudo_warnx("unable to format timing buffer"); + debug_return_int(-1); + } + + /* Write to specified I/O log file. */ + if (!iolog_write(iofd, msg->data.data, msg->data.len, closure)) + debug_return_int(-1); + + /* Write timing data. */ + if (!iolog_write(IOFD_TIMING, tbuf, len, closure)) + debug_return_int(-1); + + update_elapsed_time(msg->delay, &closure->elapsed_time); + + debug_return_int(0); +} + +int +store_suspend(CommandSuspend *msg, struct connection_closure *closure) +{ + char tbuf[1024]; + int len; + debug_decl(store_suspend, SUDO_DEBUG_UTIL) + + /* Format timing data including suspend signal. */ + len = snprintf(tbuf, sizeof(tbuf), "%d %lld.%09d %s\n", IO_EVENT_SUSPEND, + (long long)msg->delay->tv_sec, (int)msg->delay->tv_nsec, + msg->signal); + if (len < 0 || len >= ssizeof(tbuf)) { + sudo_warnx("unable to format timing buffer"); + debug_return_int(-1); + } + + /* Write timing data. */ + if (!iolog_write(IOFD_TIMING, tbuf, len, closure)) + debug_return_int(-1); + + update_elapsed_time(msg->delay, &closure->elapsed_time); + + debug_return_int(0); +} + +int +store_winsize(ChangeWindowSize *msg, struct connection_closure *closure) +{ + char tbuf[1024]; + int len; + debug_decl(store_winsize, SUDO_DEBUG_UTIL) + + /* Format timing data including new window size. */ + len = snprintf(tbuf, sizeof(tbuf), "%d %lld.%09d %d %d\n", IO_EVENT_WINSIZE, + (long long)msg->delay->tv_sec, (int)msg->delay->tv_nsec, + msg->rows, msg->cols); + if (len < 0 || len >= ssizeof(tbuf)) { + sudo_warnx("unable to format timing buffer"); + debug_return_int(-1); + } + + /* Write timing data. */ + if (!iolog_write(IOFD_TIMING, tbuf, len, closure)) + debug_return_int(-1); + + update_elapsed_time(msg->delay, &closure->elapsed_time); + + debug_return_int(0); +} diff --git a/logsrvd/log_server.pb-c.c b/logsrvd/log_server.pb-c.c new file mode 100644 index 000000000..8a5039329 --- /dev/null +++ b/logsrvd/log_server.pb-c.c @@ -0,0 +1,1452 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: log_server.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "log_server.pb-c.h" +void client_message__init + (ClientMessage *message) +{ + static const ClientMessage init_value = CLIENT_MESSAGE__INIT; + *message = init_value; +} +size_t client_message__get_packed_size + (const ClientMessage *message) +{ + assert(message->base.descriptor == &client_message__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t client_message__pack + (const ClientMessage *message, + uint8_t *out) +{ + assert(message->base.descriptor == &client_message__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t client_message__pack_to_buffer + (const ClientMessage *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &client_message__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +ClientMessage * + client_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (ClientMessage *) + protobuf_c_message_unpack (&client_message__descriptor, + allocator, len, data); +} +void client_message__free_unpacked + (ClientMessage *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &client_message__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void time_spec__init + (TimeSpec *message) +{ + static const TimeSpec init_value = TIME_SPEC__INIT; + *message = init_value; +} +size_t time_spec__get_packed_size + (const TimeSpec *message) +{ + assert(message->base.descriptor == &time_spec__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t time_spec__pack + (const TimeSpec *message, + uint8_t *out) +{ + assert(message->base.descriptor == &time_spec__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t time_spec__pack_to_buffer + (const TimeSpec *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &time_spec__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +TimeSpec * + time_spec__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (TimeSpec *) + protobuf_c_message_unpack (&time_spec__descriptor, + allocator, len, data); +} +void time_spec__free_unpacked + (TimeSpec *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &time_spec__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void io_buffer__init + (IoBuffer *message) +{ + static const IoBuffer init_value = IO_BUFFER__INIT; + *message = init_value; +} +size_t io_buffer__get_packed_size + (const IoBuffer *message) +{ + assert(message->base.descriptor == &io_buffer__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t io_buffer__pack + (const IoBuffer *message, + uint8_t *out) +{ + assert(message->base.descriptor == &io_buffer__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t io_buffer__pack_to_buffer + (const IoBuffer *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &io_buffer__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +IoBuffer * + io_buffer__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (IoBuffer *) + protobuf_c_message_unpack (&io_buffer__descriptor, + allocator, len, data); +} +void io_buffer__free_unpacked + (IoBuffer *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &io_buffer__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void info_message__string_list__init + (InfoMessage__StringList *message) +{ + static const InfoMessage__StringList init_value = INFO_MESSAGE__STRING_LIST__INIT; + *message = init_value; +} +void info_message__init + (InfoMessage *message) +{ + static const InfoMessage init_value = INFO_MESSAGE__INIT; + *message = init_value; +} +size_t info_message__get_packed_size + (const InfoMessage *message) +{ + assert(message->base.descriptor == &info_message__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t info_message__pack + (const InfoMessage *message, + uint8_t *out) +{ + assert(message->base.descriptor == &info_message__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t info_message__pack_to_buffer + (const InfoMessage *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &info_message__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +InfoMessage * + info_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (InfoMessage *) + protobuf_c_message_unpack (&info_message__descriptor, + allocator, len, data); +} +void info_message__free_unpacked + (InfoMessage *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &info_message__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void exec_message__init + (ExecMessage *message) +{ + static const ExecMessage init_value = EXEC_MESSAGE__INIT; + *message = init_value; +} +size_t exec_message__get_packed_size + (const ExecMessage *message) +{ + assert(message->base.descriptor == &exec_message__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t exec_message__pack + (const ExecMessage *message, + uint8_t *out) +{ + assert(message->base.descriptor == &exec_message__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t exec_message__pack_to_buffer + (const ExecMessage *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &exec_message__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +ExecMessage * + exec_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (ExecMessage *) + protobuf_c_message_unpack (&exec_message__descriptor, + allocator, len, data); +} +void exec_message__free_unpacked + (ExecMessage *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &exec_message__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void exit_message__init + (ExitMessage *message) +{ + static const ExitMessage init_value = EXIT_MESSAGE__INIT; + *message = init_value; +} +size_t exit_message__get_packed_size + (const ExitMessage *message) +{ + assert(message->base.descriptor == &exit_message__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t exit_message__pack + (const ExitMessage *message, + uint8_t *out) +{ + assert(message->base.descriptor == &exit_message__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t exit_message__pack_to_buffer + (const ExitMessage *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &exit_message__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +ExitMessage * + exit_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (ExitMessage *) + protobuf_c_message_unpack (&exit_message__descriptor, + allocator, len, data); +} +void exit_message__free_unpacked + (ExitMessage *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &exit_message__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void alert_message__init + (AlertMessage *message) +{ + static const AlertMessage init_value = ALERT_MESSAGE__INIT; + *message = init_value; +} +size_t alert_message__get_packed_size + (const AlertMessage *message) +{ + assert(message->base.descriptor == &alert_message__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t alert_message__pack + (const AlertMessage *message, + uint8_t *out) +{ + assert(message->base.descriptor == &alert_message__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t alert_message__pack_to_buffer + (const AlertMessage *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &alert_message__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +AlertMessage * + alert_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (AlertMessage *) + protobuf_c_message_unpack (&alert_message__descriptor, + allocator, len, data); +} +void alert_message__free_unpacked + (AlertMessage *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &alert_message__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void restart_message__init + (RestartMessage *message) +{ + static const RestartMessage init_value = RESTART_MESSAGE__INIT; + *message = init_value; +} +size_t restart_message__get_packed_size + (const RestartMessage *message) +{ + assert(message->base.descriptor == &restart_message__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t restart_message__pack + (const RestartMessage *message, + uint8_t *out) +{ + assert(message->base.descriptor == &restart_message__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t restart_message__pack_to_buffer + (const RestartMessage *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &restart_message__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +RestartMessage * + restart_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (RestartMessage *) + protobuf_c_message_unpack (&restart_message__descriptor, + allocator, len, data); +} +void restart_message__free_unpacked + (RestartMessage *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &restart_message__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void change_window_size__init + (ChangeWindowSize *message) +{ + static const ChangeWindowSize init_value = CHANGE_WINDOW_SIZE__INIT; + *message = init_value; +} +size_t change_window_size__get_packed_size + (const ChangeWindowSize *message) +{ + assert(message->base.descriptor == &change_window_size__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t change_window_size__pack + (const ChangeWindowSize *message, + uint8_t *out) +{ + assert(message->base.descriptor == &change_window_size__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t change_window_size__pack_to_buffer + (const ChangeWindowSize *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &change_window_size__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +ChangeWindowSize * + change_window_size__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (ChangeWindowSize *) + protobuf_c_message_unpack (&change_window_size__descriptor, + allocator, len, data); +} +void change_window_size__free_unpacked + (ChangeWindowSize *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &change_window_size__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void command_suspend__init + (CommandSuspend *message) +{ + static const CommandSuspend init_value = COMMAND_SUSPEND__INIT; + *message = init_value; +} +size_t command_suspend__get_packed_size + (const CommandSuspend *message) +{ + assert(message->base.descriptor == &command_suspend__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t command_suspend__pack + (const CommandSuspend *message, + uint8_t *out) +{ + assert(message->base.descriptor == &command_suspend__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t command_suspend__pack_to_buffer + (const CommandSuspend *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &command_suspend__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CommandSuspend * + command_suspend__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CommandSuspend *) + protobuf_c_message_unpack (&command_suspend__descriptor, + allocator, len, data); +} +void command_suspend__free_unpacked + (CommandSuspend *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &command_suspend__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void server_message__init + (ServerMessage *message) +{ + static const ServerMessage init_value = SERVER_MESSAGE__INIT; + *message = init_value; +} +size_t server_message__get_packed_size + (const ServerMessage *message) +{ + assert(message->base.descriptor == &server_message__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t server_message__pack + (const ServerMessage *message, + uint8_t *out) +{ + assert(message->base.descriptor == &server_message__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t server_message__pack_to_buffer + (const ServerMessage *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &server_message__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +ServerMessage * + server_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (ServerMessage *) + protobuf_c_message_unpack (&server_message__descriptor, + allocator, len, data); +} +void server_message__free_unpacked + (ServerMessage *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &server_message__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void server_hello__init + (ServerHello *message) +{ + static const ServerHello init_value = SERVER_HELLO__INIT; + *message = init_value; +} +size_t server_hello__get_packed_size + (const ServerHello *message) +{ + assert(message->base.descriptor == &server_hello__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t server_hello__pack + (const ServerHello *message, + uint8_t *out) +{ + assert(message->base.descriptor == &server_hello__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t server_hello__pack_to_buffer + (const ServerHello *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &server_hello__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +ServerHello * + server_hello__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (ServerHello *) + protobuf_c_message_unpack (&server_hello__descriptor, + allocator, len, data); +} +void server_hello__free_unpacked + (ServerHello *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &server_hello__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +static const ProtobufCFieldDescriptor client_message__field_descriptors[11] = +{ + { + "exec_msg", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, exec_msg), + &exec_message__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "exit_msg", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, exit_msg), + &exit_message__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "restart_msg", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, restart_msg), + &restart_message__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "alert_msg", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, alert_msg), + &alert_message__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "ttyin_buf", + 5, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, ttyin_buf), + &io_buffer__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "ttyout_buf", + 6, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, ttyout_buf), + &io_buffer__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "stdin_buf", + 7, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, stdin_buf), + &io_buffer__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "stdout_buf", + 8, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, stdout_buf), + &io_buffer__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "stderr_buf", + 9, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, stderr_buf), + &io_buffer__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "winsize_event", + 10, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, winsize_event), + &change_window_size__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "suspend_event", + 11, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ClientMessage, type_case), + offsetof(ClientMessage, suspend_event), + &command_suspend__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned client_message__field_indices_by_name[] = { + 3, /* field[3] = alert_msg */ + 0, /* field[0] = exec_msg */ + 1, /* field[1] = exit_msg */ + 2, /* field[2] = restart_msg */ + 8, /* field[8] = stderr_buf */ + 6, /* field[6] = stdin_buf */ + 7, /* field[7] = stdout_buf */ + 10, /* field[10] = suspend_event */ + 4, /* field[4] = ttyin_buf */ + 5, /* field[5] = ttyout_buf */ + 9, /* field[9] = winsize_event */ +}; +static const ProtobufCIntRange client_message__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 11 } +}; +const ProtobufCMessageDescriptor client_message__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "ClientMessage", + "ClientMessage", + "ClientMessage", + "", + sizeof(ClientMessage), + 11, + client_message__field_descriptors, + client_message__field_indices_by_name, + 1, client_message__number_ranges, + (ProtobufCMessageInit) client_message__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor time_spec__field_descriptors[2] = +{ + { + "tv_sec", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT64, + 0, /* quantifier_offset */ + offsetof(TimeSpec, tv_sec), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "tv_nsec", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(TimeSpec, tv_nsec), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned time_spec__field_indices_by_name[] = { + 1, /* field[1] = tv_nsec */ + 0, /* field[0] = tv_sec */ +}; +static const ProtobufCIntRange time_spec__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor time_spec__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "TimeSpec", + "TimeSpec", + "TimeSpec", + "", + sizeof(TimeSpec), + 2, + time_spec__field_descriptors, + time_spec__field_indices_by_name, + 1, time_spec__number_ranges, + (ProtobufCMessageInit) time_spec__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor io_buffer__field_descriptors[2] = +{ + { + "delay", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + 0, /* quantifier_offset */ + offsetof(IoBuffer, delay), + &time_spec__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "data", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(IoBuffer, data), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned io_buffer__field_indices_by_name[] = { + 1, /* field[1] = data */ + 0, /* field[0] = delay */ +}; +static const ProtobufCIntRange io_buffer__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor io_buffer__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "IoBuffer", + "IoBuffer", + "IoBuffer", + "", + sizeof(IoBuffer), + 2, + io_buffer__field_descriptors, + io_buffer__field_indices_by_name, + 1, io_buffer__number_ranges, + (ProtobufCMessageInit) io_buffer__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor info_message__string_list__field_descriptors[1] = +{ + { + "strings", + 1, + PROTOBUF_C_LABEL_REPEATED, + PROTOBUF_C_TYPE_STRING, + offsetof(InfoMessage__StringList, n_strings), + offsetof(InfoMessage__StringList, strings), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned info_message__string_list__field_indices_by_name[] = { + 0, /* field[0] = strings */ +}; +static const ProtobufCIntRange info_message__string_list__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 1 } +}; +const ProtobufCMessageDescriptor info_message__string_list__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "InfoMessage.StringList", + "StringList", + "InfoMessage__StringList", + "", + sizeof(InfoMessage__StringList), + 1, + info_message__string_list__field_descriptors, + info_message__string_list__field_indices_by_name, + 1, info_message__string_list__number_ranges, + (ProtobufCMessageInit) info_message__string_list__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor info_message__field_descriptors[4] = +{ + { + "key", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(InfoMessage, key), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "numval", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT64, + offsetof(InfoMessage, value_case), + offsetof(InfoMessage, numval), + NULL, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "strval", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + offsetof(InfoMessage, value_case), + offsetof(InfoMessage, strval), + NULL, + &protobuf_c_empty_string, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "strlistval", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(InfoMessage, value_case), + offsetof(InfoMessage, strlistval), + &info_message__string_list__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned info_message__field_indices_by_name[] = { + 0, /* field[0] = key */ + 1, /* field[1] = numval */ + 3, /* field[3] = strlistval */ + 2, /* field[2] = strval */ +}; +static const ProtobufCIntRange info_message__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 4 } +}; +const ProtobufCMessageDescriptor info_message__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "InfoMessage", + "InfoMessage", + "InfoMessage", + "", + sizeof(InfoMessage), + 4, + info_message__field_descriptors, + info_message__field_indices_by_name, + 1, info_message__number_ranges, + (ProtobufCMessageInit) info_message__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor exec_message__field_descriptors[2] = +{ + { + "start_time", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + 0, /* quantifier_offset */ + offsetof(ExecMessage, start_time), + &time_spec__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "info_msgs", + 2, + PROTOBUF_C_LABEL_REPEATED, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ExecMessage, n_info_msgs), + offsetof(ExecMessage, info_msgs), + &info_message__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned exec_message__field_indices_by_name[] = { + 1, /* field[1] = info_msgs */ + 0, /* field[0] = start_time */ +}; +static const ProtobufCIntRange exec_message__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor exec_message__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "ExecMessage", + "ExecMessage", + "ExecMessage", + "", + sizeof(ExecMessage), + 2, + exec_message__field_descriptors, + exec_message__field_indices_by_name, + 1, exec_message__number_ranges, + (ProtobufCMessageInit) exec_message__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor exit_message__field_descriptors[5] = +{ + { + "run_time", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + 0, /* quantifier_offset */ + offsetof(ExitMessage, run_time), + &time_spec__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "exit_value", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(ExitMessage, exit_value), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "dumped_core", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BOOL, + 0, /* quantifier_offset */ + offsetof(ExitMessage, dumped_core), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "signal", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(ExitMessage, signal), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "error", + 5, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(ExitMessage, error), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned exit_message__field_indices_by_name[] = { + 2, /* field[2] = dumped_core */ + 4, /* field[4] = error */ + 1, /* field[1] = exit_value */ + 0, /* field[0] = run_time */ + 3, /* field[3] = signal */ +}; +static const ProtobufCIntRange exit_message__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 5 } +}; +const ProtobufCMessageDescriptor exit_message__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "ExitMessage", + "ExitMessage", + "ExitMessage", + "", + sizeof(ExitMessage), + 5, + exit_message__field_descriptors, + exit_message__field_indices_by_name, + 1, exit_message__number_ranges, + (ProtobufCMessageInit) exit_message__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor alert_message__field_descriptors[2] = +{ + { + "alert_time", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + 0, /* quantifier_offset */ + offsetof(AlertMessage, alert_time), + &time_spec__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "reason", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(AlertMessage, reason), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned alert_message__field_indices_by_name[] = { + 0, /* field[0] = alert_time */ + 1, /* field[1] = reason */ +}; +static const ProtobufCIntRange alert_message__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor alert_message__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "AlertMessage", + "AlertMessage", + "AlertMessage", + "", + sizeof(AlertMessage), + 2, + alert_message__field_descriptors, + alert_message__field_indices_by_name, + 1, alert_message__number_ranges, + (ProtobufCMessageInit) alert_message__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor restart_message__field_descriptors[2] = +{ + { + "log_id", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(RestartMessage, log_id), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "resume_point", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + 0, /* quantifier_offset */ + offsetof(RestartMessage, resume_point), + &time_spec__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned restart_message__field_indices_by_name[] = { + 0, /* field[0] = log_id */ + 1, /* field[1] = resume_point */ +}; +static const ProtobufCIntRange restart_message__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor restart_message__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "RestartMessage", + "RestartMessage", + "RestartMessage", + "", + sizeof(RestartMessage), + 2, + restart_message__field_descriptors, + restart_message__field_indices_by_name, + 1, restart_message__number_ranges, + (ProtobufCMessageInit) restart_message__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor change_window_size__field_descriptors[3] = +{ + { + "delay", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + 0, /* quantifier_offset */ + offsetof(ChangeWindowSize, delay), + &time_spec__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "rows", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(ChangeWindowSize, rows), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "cols", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(ChangeWindowSize, cols), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned change_window_size__field_indices_by_name[] = { + 2, /* field[2] = cols */ + 0, /* field[0] = delay */ + 1, /* field[1] = rows */ +}; +static const ProtobufCIntRange change_window_size__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 3 } +}; +const ProtobufCMessageDescriptor change_window_size__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "ChangeWindowSize", + "ChangeWindowSize", + "ChangeWindowSize", + "", + sizeof(ChangeWindowSize), + 3, + change_window_size__field_descriptors, + change_window_size__field_indices_by_name, + 1, change_window_size__number_ranges, + (ProtobufCMessageInit) change_window_size__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor command_suspend__field_descriptors[2] = +{ + { + "delay", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + 0, /* quantifier_offset */ + offsetof(CommandSuspend, delay), + &time_spec__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "signal", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(CommandSuspend, signal), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned command_suspend__field_indices_by_name[] = { + 0, /* field[0] = delay */ + 1, /* field[1] = signal */ +}; +static const ProtobufCIntRange command_suspend__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor command_suspend__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CommandSuspend", + "CommandSuspend", + "CommandSuspend", + "", + sizeof(CommandSuspend), + 2, + command_suspend__field_descriptors, + command_suspend__field_indices_by_name, + 1, command_suspend__number_ranges, + (ProtobufCMessageInit) command_suspend__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor server_message__field_descriptors[5] = +{ + { + "hello", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ServerMessage, type_case), + offsetof(ServerMessage, hello), + &server_hello__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "commit_point", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(ServerMessage, type_case), + offsetof(ServerMessage, commit_point), + &time_spec__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "log_id", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + offsetof(ServerMessage, type_case), + offsetof(ServerMessage, log_id), + NULL, + &protobuf_c_empty_string, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "error", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + offsetof(ServerMessage, type_case), + offsetof(ServerMessage, error), + NULL, + &protobuf_c_empty_string, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "abort", + 5, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + offsetof(ServerMessage, type_case), + offsetof(ServerMessage, abort), + NULL, + &protobuf_c_empty_string, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned server_message__field_indices_by_name[] = { + 4, /* field[4] = abort */ + 1, /* field[1] = commit_point */ + 3, /* field[3] = error */ + 0, /* field[0] = hello */ + 2, /* field[2] = log_id */ +}; +static const ProtobufCIntRange server_message__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 5 } +}; +const ProtobufCMessageDescriptor server_message__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "ServerMessage", + "ServerMessage", + "ServerMessage", + "", + sizeof(ServerMessage), + 5, + server_message__field_descriptors, + server_message__field_indices_by_name, + 1, server_message__number_ranges, + (ProtobufCMessageInit) server_message__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor server_hello__field_descriptors[3] = +{ + { + "server_id", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(ServerHello, server_id), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "redirect", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(ServerHello, redirect), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "servers", + 3, + PROTOBUF_C_LABEL_REPEATED, + PROTOBUF_C_TYPE_STRING, + offsetof(ServerHello, n_servers), + offsetof(ServerHello, servers), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned server_hello__field_indices_by_name[] = { + 1, /* field[1] = redirect */ + 0, /* field[0] = server_id */ + 2, /* field[2] = servers */ +}; +static const ProtobufCIntRange server_hello__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 3 } +}; +const ProtobufCMessageDescriptor server_hello__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "ServerHello", + "ServerHello", + "ServerHello", + "", + sizeof(ServerHello), + 3, + server_hello__field_descriptors, + server_hello__field_indices_by_name, + 1, server_hello__number_ranges, + (ProtobufCMessageInit) server_hello__init, + NULL,NULL,NULL /* reserved[123] */ +}; diff --git a/logsrvd/log_server.pb-c.h b/logsrvd/log_server.pb-c.h new file mode 100644 index 000000000..00a07926b --- /dev/null +++ b/logsrvd/log_server.pb-c.h @@ -0,0 +1,663 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: log_server.proto */ + +#ifndef PROTOBUF_C_audit_5fserver_2eproto__INCLUDED +#define PROTOBUF_C_audit_5fserver_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003002 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + + +typedef struct _ClientMessage ClientMessage; +typedef struct _TimeSpec TimeSpec; +typedef struct _IoBuffer IoBuffer; +typedef struct _InfoMessage InfoMessage; +typedef struct _InfoMessage__StringList InfoMessage__StringList; +typedef struct _ExecMessage ExecMessage; +typedef struct _ExitMessage ExitMessage; +typedef struct _AlertMessage AlertMessage; +typedef struct _RestartMessage RestartMessage; +typedef struct _ChangeWindowSize ChangeWindowSize; +typedef struct _CommandSuspend CommandSuspend; +typedef struct _ServerMessage ServerMessage; +typedef struct _ServerHello ServerHello; + + +/* --- enums --- */ + + +/* --- messages --- */ + +typedef enum { + CLIENT_MESSAGE__TYPE__NOT_SET = 0, + CLIENT_MESSAGE__TYPE_EXEC_MSG = 1, + CLIENT_MESSAGE__TYPE_EXIT_MSG = 2, + CLIENT_MESSAGE__TYPE_RESTART_MSG = 3, + CLIENT_MESSAGE__TYPE_ALERT_MSG = 4, + CLIENT_MESSAGE__TYPE_TTYIN_BUF = 5, + CLIENT_MESSAGE__TYPE_TTYOUT_BUF = 6, + CLIENT_MESSAGE__TYPE_STDIN_BUF = 7, + CLIENT_MESSAGE__TYPE_STDOUT_BUF = 8, + CLIENT_MESSAGE__TYPE_STDERR_BUF = 9, + CLIENT_MESSAGE__TYPE_WINSIZE_EVENT = 10, + CLIENT_MESSAGE__TYPE_SUSPEND_EVENT = 11 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(CLIENT_MESSAGE__TYPE) +} ClientMessage__TypeCase; + +/* + * Client message to the server. + * Messages on the wire are prefixed with a 16-bit size in network byte order. + */ +struct _ClientMessage +{ + ProtobufCMessage base; + ClientMessage__TypeCase type_case; + union { + ExecMessage *exec_msg; + ExitMessage *exit_msg; + RestartMessage *restart_msg; + AlertMessage *alert_msg; + IoBuffer *ttyin_buf; + IoBuffer *ttyout_buf; + IoBuffer *stdin_buf; + IoBuffer *stdout_buf; + IoBuffer *stderr_buf; + ChangeWindowSize *winsize_event; + CommandSuspend *suspend_event; + }; +}; +#define CLIENT_MESSAGE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&client_message__descriptor) \ + , CLIENT_MESSAGE__TYPE__NOT_SET, {0} } + + +/* + * Equivalent of POSIX struct timespec + */ +struct _TimeSpec +{ + ProtobufCMessage base; + /* + * seconds + */ + int64_t tv_sec; + /* + * nanoseconds + */ + int32_t tv_nsec; +}; +#define TIME_SPEC__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&time_spec__descriptor) \ + , 0, 0 } + + +/* + * I/O buffer with keystroke data + */ +struct _IoBuffer +{ + ProtobufCMessage base; + /* + * elapsed time since last record (monotonic) + */ + TimeSpec *delay; + /* + * keystroke data + */ + ProtobufCBinaryData data; +}; +#define IO_BUFFER__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&io_buffer__descriptor) \ + , NULL, {0,NULL} } + + +struct _InfoMessage__StringList +{ + ProtobufCMessage base; + size_t n_strings; + char **strings; +}; +#define INFO_MESSAGE__STRING_LIST__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&info_message__string_list__descriptor) \ + , 0,NULL } + + +typedef enum { + INFO_MESSAGE__VALUE__NOT_SET = 0, + INFO_MESSAGE__VALUE_NUMVAL = 2, + INFO_MESSAGE__VALUE_STRVAL = 3, + INFO_MESSAGE__VALUE_STRLISTVAL = 4 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(INFO_MESSAGE__VALUE) +} InfoMessage__ValueCase; + +/* + * Key/value pairs, like Privilege Manager struct info. + * The value may be a number, a string, or a list of strings. + */ +struct _InfoMessage +{ + ProtobufCMessage base; + char *key; + InfoMessage__ValueCase value_case; + union { + int64_t numval; + char *strval; + InfoMessage__StringList *strlistval; + }; +}; +#define INFO_MESSAGE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&info_message__descriptor) \ + , (char *)protobuf_c_empty_string, INFO_MESSAGE__VALUE__NOT_SET, {0} } + + +/* + * Event log data for executed command. + */ +struct _ExecMessage +{ + ProtobufCMessage base; + /* + * wallclock time when command began + */ + TimeSpec *start_time; + /* + * key,value event log data + */ + size_t n_info_msgs; + InfoMessage **info_msgs; +}; +#define EXEC_MESSAGE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&exec_message__descriptor) \ + , NULL, 0,NULL } + + +/* + * Might revisit runtime and use end_time instead + */ +struct _ExitMessage +{ + ProtobufCMessage base; + /* + * total elapsed run time (monotonic) + */ + TimeSpec *run_time; + /* + * 0-255 + */ + int32_t exit_value; + /* + * true if command dumped core + */ + protobuf_c_boolean dumped_core; + /* + * signal name if killed by signal + */ + char *signal; + /* + * if killed due to other error + */ + char *error; +}; +#define EXIT_MESSAGE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&exit_message__descriptor) \ + , NULL, 0, 0, (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string } + + +/* + * Alert message, policy module-specific. + */ +struct _AlertMessage +{ + ProtobufCMessage base; + /* + * time alert message ocurred + */ + TimeSpec *alert_time; + /* + * description of policy violation + */ + char *reason; +}; +#define ALERT_MESSAGE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&alert_message__descriptor) \ + , NULL, (char *)protobuf_c_empty_string } + + +/* + * Used to restart an existing I/O log on the server. + */ +struct _RestartMessage +{ + ProtobufCMessage base; + /* + * ID of log being restarted + */ + char *log_id; + /* + * resume point in ellaped time (monotonic) + */ + TimeSpec *resume_point; +}; +#define RESTART_MESSAGE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&restart_message__descriptor) \ + , (char *)protobuf_c_empty_string, NULL } + + +/* + * Window size change event. + */ +struct _ChangeWindowSize +{ + ProtobufCMessage base; + /* + * elapsed time since last record (monotonic) + */ + TimeSpec *delay; + /* + * new number of rows + */ + int32_t rows; + /* + * new number of columns + */ + int32_t cols; +}; +#define CHANGE_WINDOW_SIZE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&change_window_size__descriptor) \ + , NULL, 0, 0 } + + +/* + * Command suspend/resume event. + */ +struct _CommandSuspend +{ + ProtobufCMessage base; + /* + * elapsed time since last record (monotonic) + */ + TimeSpec *delay; + /* + * signal that caused the suspend/resume + */ + char *signal; +}; +#define COMMAND_SUSPEND__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&command_suspend__descriptor) \ + , NULL, (char *)protobuf_c_empty_string } + + +typedef enum { + SERVER_MESSAGE__TYPE__NOT_SET = 0, + SERVER_MESSAGE__TYPE_HELLO = 1, + SERVER_MESSAGE__TYPE_COMMIT_POINT = 2, + SERVER_MESSAGE__TYPE_LOG_ID = 3, + SERVER_MESSAGE__TYPE_ERROR = 4, + SERVER_MESSAGE__TYPE_ABORT = 5 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(SERVER_MESSAGE__TYPE) +} ServerMessage__TypeCase; + +/* + * Server messages to the client. + * Messages on the wire are prefixed with a 16-bit size in network byte order. + */ +struct _ServerMessage +{ + ProtobufCMessage base; + ServerMessage__TypeCase type_case; + union { + /* + * server hello message + */ + ServerHello *hello; + /* + * cumulative time of records stored + */ + TimeSpec *commit_point; + /* + * ID of new I/O log (ExecMessage ACK) + */ + char *log_id; + /* + * error message from server (restartable) + */ + char *error; + /* + * abort message from server (kill session) + */ + char *abort; + }; +}; +#define SERVER_MESSAGE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&server_message__descriptor) \ + , SERVER_MESSAGE__TYPE__NOT_SET, {0} } + + +/* + * Hello message from server when client connects. + */ +struct _ServerHello +{ + ProtobufCMessage base; + /* + * free-form server description + */ + char *server_id; + /* + * optional redirect to other server if busy + */ + char *redirect; + /* + * optional list of known audit servers + */ + size_t n_servers; + char **servers; +}; +#define SERVER_HELLO__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&server_hello__descriptor) \ + , (char *)protobuf_c_empty_string, (char *)protobuf_c_empty_string, 0,NULL } + + +/* ClientMessage methods */ +void client_message__init + (ClientMessage *message); +size_t client_message__get_packed_size + (const ClientMessage *message); +size_t client_message__pack + (const ClientMessage *message, + uint8_t *out); +size_t client_message__pack_to_buffer + (const ClientMessage *message, + ProtobufCBuffer *buffer); +ClientMessage * + client_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void client_message__free_unpacked + (ClientMessage *message, + ProtobufCAllocator *allocator); +/* TimeSpec methods */ +void time_spec__init + (TimeSpec *message); +size_t time_spec__get_packed_size + (const TimeSpec *message); +size_t time_spec__pack + (const TimeSpec *message, + uint8_t *out); +size_t time_spec__pack_to_buffer + (const TimeSpec *message, + ProtobufCBuffer *buffer); +TimeSpec * + time_spec__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void time_spec__free_unpacked + (TimeSpec *message, + ProtobufCAllocator *allocator); +/* IoBuffer methods */ +void io_buffer__init + (IoBuffer *message); +size_t io_buffer__get_packed_size + (const IoBuffer *message); +size_t io_buffer__pack + (const IoBuffer *message, + uint8_t *out); +size_t io_buffer__pack_to_buffer + (const IoBuffer *message, + ProtobufCBuffer *buffer); +IoBuffer * + io_buffer__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void io_buffer__free_unpacked + (IoBuffer *message, + ProtobufCAllocator *allocator); +/* InfoMessage__StringList methods */ +void info_message__string_list__init + (InfoMessage__StringList *message); +/* InfoMessage methods */ +void info_message__init + (InfoMessage *message); +size_t info_message__get_packed_size + (const InfoMessage *message); +size_t info_message__pack + (const InfoMessage *message, + uint8_t *out); +size_t info_message__pack_to_buffer + (const InfoMessage *message, + ProtobufCBuffer *buffer); +InfoMessage * + info_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void info_message__free_unpacked + (InfoMessage *message, + ProtobufCAllocator *allocator); +/* ExecMessage methods */ +void exec_message__init + (ExecMessage *message); +size_t exec_message__get_packed_size + (const ExecMessage *message); +size_t exec_message__pack + (const ExecMessage *message, + uint8_t *out); +size_t exec_message__pack_to_buffer + (const ExecMessage *message, + ProtobufCBuffer *buffer); +ExecMessage * + exec_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void exec_message__free_unpacked + (ExecMessage *message, + ProtobufCAllocator *allocator); +/* ExitMessage methods */ +void exit_message__init + (ExitMessage *message); +size_t exit_message__get_packed_size + (const ExitMessage *message); +size_t exit_message__pack + (const ExitMessage *message, + uint8_t *out); +size_t exit_message__pack_to_buffer + (const ExitMessage *message, + ProtobufCBuffer *buffer); +ExitMessage * + exit_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void exit_message__free_unpacked + (ExitMessage *message, + ProtobufCAllocator *allocator); +/* AlertMessage methods */ +void alert_message__init + (AlertMessage *message); +size_t alert_message__get_packed_size + (const AlertMessage *message); +size_t alert_message__pack + (const AlertMessage *message, + uint8_t *out); +size_t alert_message__pack_to_buffer + (const AlertMessage *message, + ProtobufCBuffer *buffer); +AlertMessage * + alert_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void alert_message__free_unpacked + (AlertMessage *message, + ProtobufCAllocator *allocator); +/* RestartMessage methods */ +void restart_message__init + (RestartMessage *message); +size_t restart_message__get_packed_size + (const RestartMessage *message); +size_t restart_message__pack + (const RestartMessage *message, + uint8_t *out); +size_t restart_message__pack_to_buffer + (const RestartMessage *message, + ProtobufCBuffer *buffer); +RestartMessage * + restart_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void restart_message__free_unpacked + (RestartMessage *message, + ProtobufCAllocator *allocator); +/* ChangeWindowSize methods */ +void change_window_size__init + (ChangeWindowSize *message); +size_t change_window_size__get_packed_size + (const ChangeWindowSize *message); +size_t change_window_size__pack + (const ChangeWindowSize *message, + uint8_t *out); +size_t change_window_size__pack_to_buffer + (const ChangeWindowSize *message, + ProtobufCBuffer *buffer); +ChangeWindowSize * + change_window_size__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void change_window_size__free_unpacked + (ChangeWindowSize *message, + ProtobufCAllocator *allocator); +/* CommandSuspend methods */ +void command_suspend__init + (CommandSuspend *message); +size_t command_suspend__get_packed_size + (const CommandSuspend *message); +size_t command_suspend__pack + (const CommandSuspend *message, + uint8_t *out); +size_t command_suspend__pack_to_buffer + (const CommandSuspend *message, + ProtobufCBuffer *buffer); +CommandSuspend * + command_suspend__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void command_suspend__free_unpacked + (CommandSuspend *message, + ProtobufCAllocator *allocator); +/* ServerMessage methods */ +void server_message__init + (ServerMessage *message); +size_t server_message__get_packed_size + (const ServerMessage *message); +size_t server_message__pack + (const ServerMessage *message, + uint8_t *out); +size_t server_message__pack_to_buffer + (const ServerMessage *message, + ProtobufCBuffer *buffer); +ServerMessage * + server_message__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void server_message__free_unpacked + (ServerMessage *message, + ProtobufCAllocator *allocator); +/* ServerHello methods */ +void server_hello__init + (ServerHello *message); +size_t server_hello__get_packed_size + (const ServerHello *message); +size_t server_hello__pack + (const ServerHello *message, + uint8_t *out); +size_t server_hello__pack_to_buffer + (const ServerHello *message, + ProtobufCBuffer *buffer); +ServerHello * + server_hello__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void server_hello__free_unpacked + (ServerHello *message, + ProtobufCAllocator *allocator); +/* --- per-message closures --- */ + +typedef void (*ClientMessage_Closure) + (const ClientMessage *message, + void *closure_data); +typedef void (*TimeSpec_Closure) + (const TimeSpec *message, + void *closure_data); +typedef void (*IoBuffer_Closure) + (const IoBuffer *message, + void *closure_data); +typedef void (*InfoMessage__StringList_Closure) + (const InfoMessage__StringList *message, + void *closure_data); +typedef void (*InfoMessage_Closure) + (const InfoMessage *message, + void *closure_data); +typedef void (*ExecMessage_Closure) + (const ExecMessage *message, + void *closure_data); +typedef void (*ExitMessage_Closure) + (const ExitMessage *message, + void *closure_data); +typedef void (*AlertMessage_Closure) + (const AlertMessage *message, + void *closure_data); +typedef void (*RestartMessage_Closure) + (const RestartMessage *message, + void *closure_data); +typedef void (*ChangeWindowSize_Closure) + (const ChangeWindowSize *message, + void *closure_data); +typedef void (*CommandSuspend_Closure) + (const CommandSuspend *message, + void *closure_data); +typedef void (*ServerMessage_Closure) + (const ServerMessage *message, + void *closure_data); +typedef void (*ServerHello_Closure) + (const ServerHello *message, + void *closure_data); + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCMessageDescriptor client_message__descriptor; +extern const ProtobufCMessageDescriptor time_spec__descriptor; +extern const ProtobufCMessageDescriptor io_buffer__descriptor; +extern const ProtobufCMessageDescriptor info_message__descriptor; +extern const ProtobufCMessageDescriptor info_message__string_list__descriptor; +extern const ProtobufCMessageDescriptor exec_message__descriptor; +extern const ProtobufCMessageDescriptor exit_message__descriptor; +extern const ProtobufCMessageDescriptor alert_message__descriptor; +extern const ProtobufCMessageDescriptor restart_message__descriptor; +extern const ProtobufCMessageDescriptor change_window_size__descriptor; +extern const ProtobufCMessageDescriptor command_suspend__descriptor; +extern const ProtobufCMessageDescriptor server_message__descriptor; +extern const ProtobufCMessageDescriptor server_hello__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_audit_5fserver_2eproto__INCLUDED */ diff --git a/logsrvd/log_server.proto b/logsrvd/log_server.proto new file mode 100644 index 000000000..266d6ff02 --- /dev/null +++ b/logsrvd/log_server.proto @@ -0,0 +1,113 @@ +syntax = "proto3"; + +/* + * Client message to the server. + * Messages on the wire are prefixed with a 16-bit size in network byte order. + */ +message ClientMessage { + oneof type { + ExecMessage exec_msg = 1; + ExitMessage exit_msg = 2; + RestartMessage restart_msg = 3; + AlertMessage alert_msg = 4; + IoBuffer ttyin_buf = 5; + IoBuffer ttyout_buf = 6; + IoBuffer stdin_buf = 7; + IoBuffer stdout_buf = 8; + IoBuffer stderr_buf = 9; + ChangeWindowSize winsize_event = 10; + CommandSuspend suspend_event = 11; + } +} + +/* Equivalent of POSIX struct timespec */ +message TimeSpec { + int64 tv_sec = 1; /* seconds */ + int32 tv_nsec = 2; /* nanoseconds */ +} + +/* I/O buffer with keystroke data */ +message IoBuffer { + TimeSpec delay = 1; /* elapsed time since last record (monotonic) */ + bytes data = 2; /* keystroke data */ +} + +/* + * Key/value pairs, like Privilege Manager struct info. + * The value may be a number, a string, or a list of strings. + */ +message InfoMessage { + message StringList { + repeated string strings = 1; + } + string key = 1; + oneof value { + int64 numval = 2; + string strval = 3; + StringList strlistval = 4; + } +} + +/* + * Event log data for executed command. + */ +message ExecMessage { + TimeSpec start_time = 1; /* wallclock time when command began */ + repeated InfoMessage info_msgs = 2; /* key,value event log data */ +} + +/* Message sent by client when command exits. */ +/* Might revisit runtime and use end_time instead */ +message ExitMessage { + TimeSpec run_time = 1; /* total elapsed run time (monotonic) */ + int32 exit_value = 2; /* 0-255 */ + bool dumped_core = 3; /* true if command dumped core */ + string signal = 4; /* signal name if killed by signal */ + string error = 5; /* if killed due to other error */ +} + +/* Alert message, policy module-specific. */ +message AlertMessage { + TimeSpec alert_time = 1; /* time alert message ocurred */ + string reason = 2; /* description of policy violation */ +} + +/* Used to restart an existing I/O log on the server. */ +message RestartMessage { + string log_id = 1; /* ID of log being restarted */ + TimeSpec resume_point = 2; /* resume point in ellaped time (monotonic) */ +} + +/* Window size change event. */ +message ChangeWindowSize { + TimeSpec delay = 1; /* elapsed time since last record (monotonic) */ + int32 rows = 2; /* new number of rows */ + int32 cols = 3; /* new number of columns */ +} + +/* Command suspend/resume event. */ +message CommandSuspend { + TimeSpec delay = 1; /* elapsed time since last record (monotonic) */ + string signal = 2; /* signal that caused the suspend/resume */ +} + +/* + * Server messages to the client. + * Messages on the wire are prefixed with a 16-bit size in network byte order. + */ +message ServerMessage { + oneof type { + ServerHello hello = 1; /* server hello message */ + TimeSpec commit_point = 2; /* cumulative time of records stored */ + string log_id = 3; /* ID of new I/O log (ExecMessage ACK) */ + string error = 4; /* error message from server (restartable) */ + string abort = 5; /* abort message from server (kill session) */ + } +} + +/* Hello message from server when client connects. */ +message ServerHello { + string server_id = 1; /* free-form server description */ + string redirect = 2; /* optional redirect to other server if busy */ + repeated string servers = 3; /* optional list of known audit servers */ +} diff --git a/logsrvd/logsrvd.c b/logsrvd/logsrvd.c new file mode 100644 index 000000000..db35b7549 --- /dev/null +++ b/logsrvd/logsrvd.c @@ -0,0 +1,827 @@ +/* + * 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 +#include +#ifdef HAVE_STDBOOL_H +# include +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#if defined(HAVE_STDINT_H) +# include +#elif defined(HAVE_INTTYPES_H) +# include +#endif +#include +#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_conf.h" +#include "sudo_debug.h" +#include "sudo_event.h" +#include "sudo_queue.h" +#include "sudo_util.h" +#include "sudo_fatal.h" +#include "logsrvd.h" + +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"; + +/* + * 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. + */ + +/* + * Free a struct connection_closure container and its contents. + */ +static void +connection_closure_free(struct connection_closure *closure) +{ + debug_decl(connection_closure_free, SUDO_DEBUG_UTIL) + + if (closure != NULL) { + bool shutting_down = closure->state == SHUTDOWN; + + TAILQ_REMOVE(&connections, closure, entries); + close(closure->sock); + iolog_close(closure); + sudo_ev_free(closure->commit_ev); + sudo_ev_free(closure->read_ev); + sudo_ev_free(closure->write_ev); + free(closure->read_buf.data); + free(closure->write_buf.data); + free(closure->iolog_dir); + free(closure); + + if (shutting_down && TAILQ_EMPTY(&connections)) + sudo_ev_loopbreak(NULL); + } + + debug_return; +} + +static bool +fmt_server_message(struct connection_buffer *buf, ServerMessage *msg) +{ + uint16_t msg_len; + bool ret = false; + size_t len; + debug_decl(fmt_server_message, SUDO_DEBUG_UTIL) + + if (buf->len != 0) { + sudo_warnx("pending write!"); + debug_return_bool(false); + } + + len = server_message__get_packed_size(msg); + if (len > UINT16_MAX) { + sudo_warnx("server message too large: %zu\n", len); + goto done; + } + /* Wire message size is used for length encoding, precedes message. */ + msg_len = htons((uint16_t)len); + len += sizeof(msg_len); + + if (len > buf->size) { + sudo_warnx("server message too big for buffer, %zu > %u", len, buf->size); + goto done; + } + + memcpy(buf->data, &msg_len, sizeof(msg_len)); + server_message__pack(msg, buf->data + sizeof(msg_len)); + buf->len = len; + ret = true; + +done: + debug_return_bool(ret); +} + +static bool +fmt_hello_message(struct connection_buffer *buf) +{ + ServerMessage msg = SERVER_MESSAGE__INIT; + ServerHello hello = SERVER_HELLO__INIT; + debug_decl(fmt_hello_message, SUDO_DEBUG_UTIL) + + /* TODO: implement redirect and servers array. */ + hello.server_id = (char *)server_id; + msg.hello = &hello; + msg.type_case = SERVER_MESSAGE__TYPE_HELLO; + + debug_return_bool(fmt_server_message(buf, &msg)); +} + +static bool +fmt_log_id_message(const char *id, struct connection_buffer *buf) +{ + ServerMessage msg = SERVER_MESSAGE__INIT; + debug_decl(fmt_log_id_message, SUDO_DEBUG_UTIL) + + msg.log_id = (char *)id; + msg.type_case = SERVER_MESSAGE__TYPE_LOG_ID; + + debug_return_bool(fmt_server_message(buf, &msg)); +} + +/* + * Parse an ExecMessage + */ +static bool +handle_exec(ExecMessage *msg, struct connection_closure *closure) +{ + debug_decl(handle_exec, SUDO_DEBUG_UTIL) + + if (closure->state != INITIAL) { + sudo_warnx("unexpected state %d", closure->state); + debug_return_bool(false); + } + + /* Sanity check message. */ + if (msg->start_time == NULL || msg->n_info_msgs == 0) { + sudo_warnx("invalid ExecMessage"); + debug_return_bool(false); + } + sudo_warnx("received ExecMessage"); // XXX + + /* Save start time. */ + closure->start_time.tv_sec = msg->start_time->tv_sec; + closure->start_time.tv_nsec = msg->start_time->tv_nsec; + + /* Create I/O log info file and parent directories. */ + if (!iolog_init(msg, closure)) + debug_return_bool(false); + + /* Send log ID to client for restarting connectoins. */ + if (!fmt_log_id_message(closure->iolog_dir, &closure->write_buf)) + debug_return_bool(false); + if (sudo_ev_add(NULL, closure->write_ev, NULL, false) == -1) + debug_return_bool(false); + + closure->state = RUNNING; + debug_return_bool(true); +} + +static bool +handle_exit(ExitMessage *msg, struct connection_closure *closure) +{ + struct timespec tv = { 0, 0 }; + debug_decl(handle_exit, SUDO_DEBUG_UTIL) + + if (closure->state != RUNNING) { + sudo_warnx("unexpected state %d", closure->state); + debug_return_bool(false); + } + + /* Sudo I/O logs don't store this info. */ + if (msg->signal != NULL && msg->signal[0] != '\0') { + sudo_warnx("command was killed by SIG%s%s", msg->signal, + msg->dumped_core ? " (core dumped)" : ""); + } else { + sudo_warnx("command exited with %d", msg->exit_value); + } + + /* No more data, command exited. */ + closure->state = EXITED; + sudo_ev_del(NULL, closure->read_ev); + + sudo_warnx("elapsed time: %lld, %ld", (long long)closure->elapsed_time.tv_sec, + closure->elapsed_time.tv_nsec); // XXX + + /* Schedule the final commit point event immediately. */ + if (sudo_ev_add(NULL, closure->commit_ev, &tv, false) == -1) + debug_return_bool(false); + + debug_return_bool(true); +} + +static bool +handle_restart(RestartMessage *msg, struct connection_closure *closure) +{ + debug_decl(handle_restart, SUDO_DEBUG_UTIL) + + if (closure->state != INITIAL) { + sudo_warnx("unexpected state %d", closure->state); + debug_return_bool(false); + } + + /* TODO */ + closure->state = RESTARTING; + sudo_warnx("server restart not implemented"); + debug_return_bool(false); +} + +static bool +handle_alert(AlertMessage *msg, struct connection_closure *closure) +{ + debug_decl(handle_alert, SUDO_DEBUG_UTIL) + + /* TODO */ + debug_return_bool(false); +} + +static bool +handle_iobuf(int iofd, IoBuffer *msg, struct connection_closure *closure) +{ + debug_decl(handle_iobuf, SUDO_DEBUG_UTIL) + + if (closure->state != RUNNING) { + sudo_warnx("unexpected state %d", closure->state); + debug_return_bool(false); + } + + sudo_warnx("received IoBuffer"); // XXX + + /* Store IoBuffer in log. */ + if (store_iobuf(iofd, msg, closure) == -1) { + sudo_warnx("failed to store IoBuffer"); // XXX + debug_return_bool(false); + } + +#ifdef SERVER_DEBUG + if (rand() % 4 == 3) + kill(getpid(), SIGTERM); +#endif + + /* Schedule a commit point in 10 sec if one is not already pending. */ + if (!ISSET(closure->commit_ev->flags, SUDO_EVQ_INSERTED)) { + struct timespec tv = { ACK_FREQUENCY, 0 }; + if (sudo_ev_add(NULL, closure->commit_ev, &tv, false) == -1) + debug_return_bool(false); + } + + debug_return_bool(true); +} + +static bool +handle_winsize(ChangeWindowSize *msg, struct connection_closure *closure) +{ + debug_decl(handle_winsize, SUDO_DEBUG_UTIL) + + if (closure->state != RUNNING) { + sudo_warnx("unexpected state %d", closure->state); + debug_return_bool(false); + } + + sudo_warnx("received ChangeWindowSize"); // XXX + + /* Store new window size in log. */ + if (store_winsize(msg, closure) == -1) { + sudo_warnx("failed to store ChangeWindowSize"); // XXX + debug_return_bool(false); + } + + debug_return_bool(true); +} + +static bool +handle_suspend(CommandSuspend *msg, struct connection_closure *closure) +{ + debug_decl(handle_suspend, SUDO_DEBUG_UTIL) + + if (closure->state != RUNNING) { + sudo_warnx("unexpected state %d", closure->state); + debug_return_bool(false); + } + + sudo_warnx("received CommandSuspend"); // XXX + + /* Store suspend siganl in log. */ + if (store_suspend(msg, closure) == -1) { + sudo_warnx("failed to store CommandSuspend"); // XXX + debug_return_bool(false); + } + + debug_return_bool(true); +} + +static bool +handle_client_message(uint8_t *buf, size_t len, + struct connection_closure *closure) +{ + ClientMessage *msg; + bool ret = false; + debug_decl(handle_client_message, SUDO_DEBUG_UTIL) + + msg = client_message__unpack(NULL, len, buf); + if (msg == NULL) { + sudo_warnx("unable to unpack ClientMessage size %zu", len); + debug_return_bool(false); + } + + switch (msg->type_case) { + case CLIENT_MESSAGE__TYPE_EXEC_MSG: + ret = handle_exec(msg->exec_msg, closure); + break; + case CLIENT_MESSAGE__TYPE_EXIT_MSG: + ret = handle_exit(msg->exit_msg, closure); + break; + case CLIENT_MESSAGE__TYPE_RESTART_MSG: + ret = handle_restart(msg->restart_msg, closure); + break; + case CLIENT_MESSAGE__TYPE_ALERT_MSG: + ret = handle_alert(msg->alert_msg, closure); + break; + case CLIENT_MESSAGE__TYPE_TTYIN_BUF: + ret = handle_iobuf(IOFD_TTYIN, msg->ttyin_buf, closure); + break; + case CLIENT_MESSAGE__TYPE_TTYOUT_BUF: + ret = handle_iobuf(IOFD_TTYOUT, msg->ttyout_buf, closure); + break; + case CLIENT_MESSAGE__TYPE_STDIN_BUF: + ret = handle_iobuf(IOFD_STDIN, msg->stdin_buf, closure); + break; + case CLIENT_MESSAGE__TYPE_STDOUT_BUF: + ret = handle_iobuf(IOFD_STDOUT, msg->stdout_buf, closure); + break; + case CLIENT_MESSAGE__TYPE_STDERR_BUF: + ret = handle_iobuf(IOFD_STDERR, msg->stderr_buf, closure); + break; + case CLIENT_MESSAGE__TYPE_WINSIZE_EVENT: + ret = handle_winsize(msg->winsize_event, closure); + break; + case CLIENT_MESSAGE__TYPE_SUSPEND_EVENT: + ret = handle_suspend(msg->suspend_event, closure); + break; + default: + sudo_warnx("unexpected type_case value %d", msg->type_case); + break; + } + client_message__free_unpacked(msg, NULL); + + debug_return_bool(ret); +} + +static void +shutdown_cb(int unused, int what, void *v) +{ + struct sudo_event_base *base = v; + debug_decl(shutdown_cb, SUDO_DEBUG_UTIL) + + sudo_ev_loopbreak(base); + + debug_return; +} + +/* + * Shut down active client connections if any, or exit immediately. + */ +static void +server_shutdown(struct sudo_event_base *base) +{ + struct connection_closure *closure; + struct sudo_event *ev; + struct timespec tv = { 0, 0 }; + debug_decl(server_shutdown, SUDO_DEBUG_UTIL) + + if (TAILQ_EMPTY(&connections)) { + sudo_ev_loopbreak(base); + debug_return; + } + + /* Schedule final commit point for each active connection. */ + TAILQ_FOREACH(closure, &connections, entries) { + closure->state = SHUTDOWN; + sudo_ev_del(base, closure->read_ev); + if (sudo_ev_add(base, closure->commit_ev, &tv, false) == -1) + sudo_warnx("unable to add commit point event"); + } + + /* We need a timed event to exit even if clients time out. */ + ev = sudo_ev_alloc(-1, SUDO_EV_TIMEOUT, shutdown_cb, base); + if (ev != NULL) { + tv.tv_sec = SHUTDOWN_TIMEO; + if (sudo_ev_add(base, ev, &tv, false) == -1) + sudo_warnx("unable to add commit point event"); + } + + debug_return; +} + +/* + * Send a server message to the client. + */ +static void +server_msg_cb(int fd, int what, void *v) +{ + struct connection_closure *closure = v; + struct connection_buffer *buf = &closure->write_buf; + ssize_t nwritten; + debug_decl(server_msg_cb, SUDO_DEBUG_UTIL) + + sudo_warnx("sending %u bytes to client", buf->len - buf->off); + + nwritten = send(fd, buf->data + buf->off, buf->len - buf->off, 0); + if (nwritten == -1) { + sudo_warn("send"); + goto finished; + } + buf->off += nwritten; + + if (buf->off == buf->len) { + /* sent entire message */ + sudo_warnx("finished sending %u bytes to client", buf->len); // XXX + buf->off = 0; + buf->len = 0; + sudo_ev_del(NULL, closure->write_ev); + if (closure->state == FLUSHED || closure->state == SHUTDOWN) + goto finished; + } + debug_return; + +finished: + connection_closure_free(closure); + debug_return; +} + +/* + * Receive client message(s). + */ +static void +client_msg_cb(int fd, int what, void *v) +{ + struct connection_closure *closure = v; + struct connection_buffer *buf = &closure->read_buf; + uint16_t msg_len; + ssize_t nread; + debug_decl(client_msg_cb, SUDO_DEBUG_UTIL) + + nread = recv(fd, buf->data + buf->len, buf->size - buf->len, 0); + sudo_warnx("received %zd bytes from client", nread); + switch (nread) { + case -1: + if (errno == EAGAIN) + debug_return; + sudo_warn("recv"); + goto finished; + case 0: + sudo_warnx("unexpected EOF"); + goto finished; + default: + break; + } + buf->len += nread; + + while (buf->len - buf->off >= sizeof(msg_len)) { + /* Read wire message size (uint16_t in network byte order). */ + memcpy(&msg_len, buf->data + buf->off, sizeof(msg_len)); + msg_len = ntohs(msg_len); + + if (msg_len + sizeof(msg_len) > buf->len - buf->off) { + /* Incomplete message, we'll read the rest next time. */ + /* TODO: realloc if max message size increases */ + if (buf->off > 0) + memmove(buf->data, buf->data + buf->off, buf->len - buf->off); + break; + } + + /* Parse ClientMessage, could be zero bytes. */ + sudo_warnx("parsing ClientMessage, size %hu", msg_len); // XXX + buf->off += sizeof(msg_len); + if (!handle_client_message(buf->data + buf->off, msg_len, closure)) { + /* XXX - do something on error */ + goto finished; + } + buf->off += msg_len; + } + buf->len -= buf->off; + buf->off = 0; + debug_return; +finished: + connection_closure_free(closure); + debug_return; +} + +/* + * Format and schedule a commit_point message. + */ +static void +server_commit_cb(int unused, int what, void *v) +{ + ServerMessage msg = SERVER_MESSAGE__INIT; + TimeSpec commit_point = TIME_SPEC__INIT; + struct connection_closure *closure = v; + debug_decl(server_commit_cb, SUDO_DEBUG_UTIL) + + /* Send the client an acknowledgement of what has been committed to disk. */ + commit_point.tv_sec = closure->elapsed_time.tv_sec; + commit_point.tv_nsec = closure->elapsed_time.tv_nsec; + msg.commit_point = &commit_point; + msg.type_case = SERVER_MESSAGE__TYPE_COMMIT_POINT; + + sudo_warnx("sending commit point [%lld, %ld]", (long long)closure->elapsed_time.tv_sec, closure->elapsed_time.tv_nsec); // XXX + + /* XXX - assumes no other server message pending, use a queue instead? */ + /* XXX - close connection on error */ + if (!fmt_server_message(&closure->write_buf, &msg)) { + sudo_warnx("unable to format server message (commit point)"); + goto bad; + } + if (sudo_ev_add(NULL, closure->write_ev, NULL, false) == -1) { + sudo_warnx("unable to add server write event"); + goto bad; + } + + if (closure->state == EXITED) + closure->state = FLUSHED; + debug_return; +bad: + connection_closure_free(closure); + debug_return; +} + +static void +signal_cb(int signo, int what, void *v) +{ + struct sudo_event_base *base = v; + debug_decl(signal_cb, SUDO_DEBUG_UTIL) + + switch (signo) { + case SIGHUP: + /* TODO: reload config */ + sudo_warnx("Received SIGHUP"); + break; + case SIGINT: + case SIGTERM: + /* Shut down active connections. */ + server_shutdown(base); + break; + default: + sudo_warnx("unexpected signal %d", signo); + break; + } + + debug_return; +} + +/* + * Allocate a new connection closure. + */ +static struct connection_closure * +connection_closure_alloc(int sock) +{ + struct connection_closure *closure; + debug_decl(connection_closure_alloc, SUDO_DEBUG_UTIL) + + if ((closure = calloc(1, sizeof(*closure))) == NULL) + debug_return_ptr(NULL); + + closure->sock = sock; + + closure->read_buf.size = UINT16_MAX + sizeof(uint16_t); + closure->read_buf.data = malloc(closure->read_buf.size); + if (closure->read_buf.data == NULL) + goto bad; + + closure->write_buf.size = UINT16_MAX + sizeof(uint16_t); + closure->write_buf.data = malloc(closure->write_buf.size); + if (closure->write_buf.data == NULL) + goto bad; + + closure->commit_ev = sudo_ev_alloc(-1, SUDO_EV_TIMEOUT, + server_commit_cb, closure); + if (closure->commit_ev == NULL) + goto bad; + + closure->read_ev = sudo_ev_alloc(sock, SUDO_EV_READ|SUDO_EV_PERSIST, + client_msg_cb, closure); + if (closure->read_ev == NULL) + goto bad; + + closure->write_ev = sudo_ev_alloc(sock, SUDO_EV_WRITE|SUDO_EV_PERSIST, + server_msg_cb, closure); + if (closure->write_ev == NULL) + goto bad; + + TAILQ_INSERT_TAIL(&connections, closure, entries); + debug_return_ptr(closure); +bad: + connection_closure_free(closure); + debug_return_ptr(NULL); +} + +/* + * New connection. + * Allocate a connection closure and send a server hello message. + */ +static bool +new_connection(int sock, struct sudo_event_base *base) +{ + struct connection_closure *closure; + debug_decl(new_connection, SUDO_DEBUG_UTIL) + + if ((closure = connection_closure_alloc(sock)) == NULL) + goto bad; + + /* Format and write ServerHello message. */ + if (!fmt_hello_message(&closure->write_buf)) + goto bad; + if (sudo_ev_add(base, closure->write_ev, NULL, false) == -1) + goto bad; + + /* Enable reader for ClientMessage*/ + if (sudo_ev_add(base, closure->read_ev, NULL, false) == -1) + goto bad; + + debug_return_bool(true); +bad: + connection_closure_free(closure); + debug_return_bool(false); +} + +static int +create_listener(sa_family_t family, in_port_t port) +{ + union sockaddr_union s_un; + socklen_t salen; + int flags, i, sock; + debug_decl(create_listener, SUDO_DEBUG_UTIL) + + memset(&s_un, 0, sizeof(s_un)); + switch (family) { + case AF_INET: + s_un.sin.sin_family = AF_INET; + s_un.sin.sin_addr.s_addr = INADDR_ANY; + s_un.sin.sin_port = htons(port); + salen = sizeof(s_un.sin); + break; +#ifdef HAVE_STRUCT_IN6_ADDR + case AF_INET6: + s_un.sin6.sin6_family = AF_INET6; + s_un.sin6.sin6_addr = in6addr_any; + s_un.sin6.sin6_port = htons(port); + salen = sizeof(s_un.sin6); + break; +#endif + default: + debug_return_int(-1); + } + + if ((sock = socket(family, SOCK_STREAM, 0)) == -1) { + sudo_warn("socket"); + goto bad; + } + i = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) == -1) + sudo_warn("SO_REUSEADDR"); + if (bind(sock, &s_un.sa, salen) == -1) { + sudo_warn("bind"); + goto bad; + } + if (listen(sock, SOMAXCONN) == -1) { + sudo_warn("listen"); + goto bad; + } + flags = fcntl(sock, F_GETFL, 0); + if (flags == -1 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { + sudo_warn("fcntl(O_NONBLOCK)"); + goto bad; + } + + debug_return_int(sock); +bad: + if (sock != -1) + close(sock); + debug_return_int(-1); +} + +static void +listener_cb(int fd, int what, void *v) +{ + struct sudo_event_base *base = v; + union sockaddr_union s_un; + socklen_t salen = sizeof(s_un); + int sock; + debug_decl(listener_cb, SUDO_DEBUG_UTIL) + + sock = accept(fd, &s_un.sa, &salen); + if (sock != -1) { + if (!new_connection(sock, base)) { + /* TODO: pause accepting on ENOMEM */ + sudo_warnx("unable to start new connection"); + } + } else { + if (errno != EAGAIN) + sudo_warn("accept"); + /* TODO: pause accepting on ENFILE and EMFILE */ + } + debug_return; +} + +static void +register_listener(sa_family_t family, in_port_t port, struct sudo_event_base *base) +{ + struct sudo_event *ev; + int sock; + debug_decl(register_listener, SUDO_DEBUG_UTIL) + + sock = create_listener(family, port); + if (sock != -1) { + ev = sudo_ev_alloc(sock, SUDO_EV_READ|SUDO_EV_PERSIST, listener_cb, base); + if (ev == NULL) + sudo_fatal(NULL); + if (sudo_ev_add(base, ev, NULL, false) == -1) + sudo_fatal("unable to add listener event to queue"); + } + + debug_return; +} + +static void +register_signal(int signo, struct sudo_event_base *base) +{ + struct sudo_event *ev; + debug_decl(register_listener, SUDO_DEBUG_UTIL) + + ev = sudo_ev_alloc(signo, SUDO_EV_SIGNAL, signal_cb, base); + if (ev == NULL) + sudo_fatal(NULL); + if (sudo_ev_add(base, ev, NULL, false) == -1) + sudo_fatal("unable to add signal event to queue"); + + debug_return; +} + +static void +logsrvd_cleanup(void) +{ + /* TODO: cleanup like on signal */ + return; +} + +int +main(int argc, char *argv[]) +{ + struct sudo_event_base *evbase; + debug_decl_vars(main, SUDO_DEBUG_MAIN) + + initprogname(argc > 0 ? argv[0] : "sudo_logsrvd"); + setlocale(LC_ALL, ""); + bindtextdomain("sudo", LOCALEDIR); /* XXX - add logsrvd domain */ + textdomain("sudo"); + + /* Register fatal/fatalx callback. */ + sudo_fatal_callback_register(logsrvd_cleanup); + + /* Read sudo.conf and initialize the debug subsystem. */ + if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1) + exit(EXIT_FAILURE); + sudo_debug_register(getprogname(), NULL, NULL, + sudo_conf_debug_files(getprogname())); + + /* XXX - getopt_long handling */ + + if (protobuf_c_version_number() < 1003000) + sudo_fatalx("Protobuf-C version 1.3 or higher required"); + + signal(SIGPIPE, SIG_IGN); + + if ((evbase = sudo_ev_base_alloc()) == NULL) + sudo_fatal(NULL); + sudo_ev_base_setdef(evbase); + + register_listener(AF_INET, DEFAULT_PORT, evbase); +#ifdef HAVE_STRUCT_IN6_ADDR + register_listener(AF_INET6, DEFAULT_PORT, evbase); +#endif + if (TAILQ_EMPTY(&evbase->events)) + debug_return_int(-1); + + register_signal(SIGHUP, evbase); + register_signal(SIGINT, evbase); + register_signal(SIGTERM, evbase); + + sudo_ev_dispatch(evbase); + + /* NOTREACHED */ + debug_return_int(1); +} diff --git a/logsrvd/logsrvd.h b/logsrvd/logsrvd.h new file mode 100644 index 000000000..94e11de19 --- /dev/null +++ b/logsrvd/logsrvd.h @@ -0,0 +1,96 @@ +/* + * 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. + */ + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error protobuf-c version 1.30 or higher required +#endif + +#define DEFAULT_PORT 30344 +#define IOLOG_DIR "/var/tmp/iologs" +#define RUNAS_DEFAULT "root" + +/* How often to send an ACK to the client (commit point) in seconds */ +#define ACK_FREQUENCY 10 + +/* Shutdown timeout (in seconds) in case client connections time out. */ +#define SHUTDOWN_TIMEO 10 + +/* + * Indexes into io_fds[] and iolog_names[] + * The first five must match the IO_EVENT_ defines in iolog.h. + */ +#define IOFD_STDIN 0 +#define IOFD_STDOUT 1 +#define IOFD_STDERR 2 +#define IOFD_TTYIN 3 +#define IOFD_TTYOUT 4 +#define IOFD_TIMING 5 +#define IOFD_MAX 6 + +/* + * Connection status. + * In the RUNNING state we expect I/O log buffers. + */ +enum connection_status { + INITIAL, + RUNNING, + RESTARTING, + EXITED, + SHUTDOWN, + FLUSHED +}; + +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 */ + unsigned int len; + unsigned int off; +}; + +/* + * Per-connection state. + * TODO: iolog_compress + */ +struct connection_closure { + TAILQ_ENTRY(connection_closure) entries; + struct timespec start_time; + struct timespec elapsed_time; + struct connection_buffer read_buf; + struct connection_buffer write_buf; + struct sudo_event *commit_ev; + struct sudo_event *read_ev; + struct sudo_event *write_ev; + char *iolog_dir; + int iolog_dir_fd; + int io_fds[IOFD_MAX]; + int sock; + enum connection_status state; +}; + +/* iolog.c */ +bool iolog_init(ExecMessage *msg, struct connection_closure *closure); +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); diff --git a/logsrvd/sendlog.c b/logsrvd/sendlog.c new file mode 100644 index 000000000..aa1b956dc --- /dev/null +++ b/logsrvd/sendlog.c @@ -0,0 +1,930 @@ +/* + * 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 +#include +#include +#include +#ifdef HAVE_STDBOOL_H +# include +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#if defined(HAVE_STDINT_H) +# include +#elif defined(HAVE_INTTYPES_H) +# include +#endif +#include +#include +#include +#include +#include + +#ifndef HAVE_GETADDRINFO +# include "compat/getaddrinfo.h" +#endif + +#include "log_server.pb-c.h" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ +#include "sudo_compat.h" +#include "sudo_conf.h" +#include "sudo_debug.h" +#include "sudo_util.h" +#include "sudo_event.h" +#include "sudo_fatal.h" +#include "iolog.h" +#include "sendlog.h" + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-h host] [-p port] /path/to/iolog\n", + getprogname()); + exit(1); +} + +/* + * Connect to specified host:port + * If host has multiple addresses, the first one that connects is used. + * Returns open socket or -1 on error. + */ +static int +connect_server(const char *host, const char *port) +{ + struct addrinfo hints, *res, *res0; + const char *cause = "getaddrinfo"; + int error, sock, save_errno; + debug_decl(connect_server, SUDO_DEBUG_UTIL) + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + if (error != 0) { + sudo_warnx("unable to resolve %s:%s: %s", host, port, gai_strerror(error)); + debug_return_int(-1); + } + + sock = -1; + for (res = res0; res; res = res->ai_next) { + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock == -1) { + cause = "socket"; + continue; + } + if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) { + cause = "connect"; + save_errno = errno; + close(sock); + errno = save_errno; + sock = -1; + continue; + } + break; /* success */ + } + if (sock != -1) { + int flags = fcntl(sock, F_GETFL, 0); + if (flags == -1 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { + cause = "fcntl(O_NONBLOCK)"; + save_errno = errno; + close(sock); + errno = save_errno; + sock = -1; + } + } + + if (sock == -1) + sudo_warn("%s", cause); + freeaddrinfo(res0); + + debug_return_int(sock); +} + +/* + * Free client closure allocated by client_closure_alloc() + */ +static void +client_closure_free(struct client_closure *closure) +{ + debug_decl(client_closure_free, SUDO_DEBUG_UTIL) + + if (closure != NULL) { + sudo_ev_free(closure->read_ev); + sudo_ev_free(closure->write_ev); + free(closure->read_buf.data); + free(closure->write_buf.data); + free(closure->timing.buf); + free(closure); + } + + debug_return; +} + +/* + * Format a ClientMessage and store the wire format message in buf. + * Returns true on success, false on failure. + */ +static bool +fmt_client_message(struct connection_buffer *buf, ClientMessage *msg) +{ + uint16_t msg_len; + bool ret = false; + size_t len; + debug_decl(fmt_client_message, SUDO_DEBUG_UTIL) + + len = client_message__get_packed_size(msg); + if (len > UINT16_MAX) { + sudo_warnx("client message too large: %zu\n", len); + goto done; + } + /* Wire message size is used for length encoding, precedes message. */ + msg_len = htons((uint16_t)len); + len += sizeof(msg_len); + + if (len > buf->size) { + sudo_warnx("client message too big for buffer, %zu > %u", len, buf->size); + goto done; + } + + memcpy(buf->data, &msg_len, sizeof(msg_len)); + client_message__pack(msg, buf->data + sizeof(msg_len)); + buf->len = len; + ret = true; + +done: + debug_return_bool(ret); +} + +/* + * Build and format an ExecMessage wrapped in a ClientMessage. + * Stores the wire format message in the closure's write buffer. + * Returns true on success, false on failure. + */ +static bool +fmt_exec_message(struct client_closure *closure) +{ + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + ExecMessage exec_msg = EXEC_MESSAGE__INIT; + TimeSpec tv = TIME_SPEC__INIT; + InfoMessage__StringList runargv = INFO_MESSAGE__STRING_LIST__INIT; + struct log_info *log_info = closure->log_info; + char hostname[1024]; + bool ret = false; + size_t n; + debug_decl(fmt_exec_message, SUDO_DEBUG_UTIL) + + /* + * Fill in ExecMessage and add it to ClientMessage. + * TODO: handle buf large than 64K? + */ + if (gethostname(hostname, sizeof(hostname)) == -1) { + sudo_warn("gethostname"); + debug_return_bool(false); + } + hostname[sizeof(hostname) - 1] = '\0'; + + /* Format argv/argc as a StringList */ + runargv.strings = log_info->argv; + runargv.n_strings = log_info->argc; + + /* Sudo I/O logs only store start time in seconds. */ + tv.tv_sec = log_info->start_time; + tv.tv_nsec = 0; + exec_msg.start_time = &tv; + + /* The sudo I/O log info file has limited info. */ + exec_msg.n_info_msgs = 10; + exec_msg.info_msgs = calloc(exec_msg.n_info_msgs, sizeof(InfoMessage *)); + if (exec_msg.info_msgs == NULL) + debug_return_bool(false); + for (n = 0; n < exec_msg.n_info_msgs; n++) { + exec_msg.info_msgs[n] = malloc(sizeof(InfoMessage)); + if (exec_msg.info_msgs[n] == NULL) { + exec_msg.n_info_msgs = n; + goto done; + } + info_message__init(exec_msg.info_msgs[n]); + } + + /* Fill in info_msgs */ + n = 0; + exec_msg.info_msgs[n]->key = "command"; + exec_msg.info_msgs[n]->strval = log_info->command; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRVAL; + + n++; + exec_msg.info_msgs[n]->key = "columns"; + exec_msg.info_msgs[n]->numval = log_info->columns; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_NUMVAL; + + n++; + exec_msg.info_msgs[n]->key = "cwd"; + exec_msg.info_msgs[n]->strval = log_info->cwd; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRVAL; + + n++; + exec_msg.info_msgs[n]->key = "lines"; + exec_msg.info_msgs[n]->numval = log_info->lines; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_NUMVAL; + + n++; + exec_msg.info_msgs[n]->key = "runargv"; + exec_msg.info_msgs[n]->strlistval = &runargv; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRLISTVAL; + + if (log_info->rungroup != NULL) { + n++; + exec_msg.info_msgs[n]->key = "rungroup"; + exec_msg.info_msgs[n]->strval = log_info->rungroup; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRVAL; + } + + n++; + exec_msg.info_msgs[n]->key = "runuser"; + exec_msg.info_msgs[n]->strval = log_info->runuser; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRVAL; + + n++; + exec_msg.info_msgs[n]->key = "submithost"; + exec_msg.info_msgs[n]->strval = hostname; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRVAL; + + n++; + exec_msg.info_msgs[n]->key = "submituser"; + exec_msg.info_msgs[n]->strval = log_info->submituser; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRVAL; + + n++; + exec_msg.info_msgs[n]->key = "ttyname"; + exec_msg.info_msgs[n]->strval = log_info->ttyname; + exec_msg.info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRVAL; + + /* Update n_info_msgs. */ + exec_msg.n_info_msgs = n; + + sudo_warnx("sending ExecMessage array length %zu", n); // XXX + + /* Schedule ClientMessage */ + client_msg.exec_msg = &exec_msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_EXEC_MSG; + ret = fmt_client_message(&closure->write_buf, &client_msg); + if (ret) { + if (sudo_ev_add(NULL, closure->write_ev, NULL, false) == -1) + ret = false; + } + +done: + for (n = 0; n < exec_msg.n_info_msgs; n++) { + free(exec_msg.info_msgs[n]); + } + free(exec_msg.info_msgs); + + debug_return_bool(ret); +} + +/* + * Build and format an ExitMessage wrapped in a ClientMessage. + * Stores the wire format message in the closure's write buffer. + * Returns true on success, false on failure. + */ +static bool +fmt_exit_message(struct client_closure *closure) +{ + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + ExitMessage exit_msg = EXIT_MESSAGE__INIT; + bool ret = false; + debug_decl(fmt_exit_message, SUDO_DEBUG_UTIL) + + /* + * We don't have enough data in a sudo I/O log to create a real + * exit message. For example, the exit value and run time are + * not known. This results in a zero-sized message. + */ + exit_msg.exit_value = 0; + + sudo_warnx("sending ExitMessage"); // XXX + + /* Send ClientMessage */ + client_msg.exit_msg = &exit_msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_EXIT_MSG; + if (!fmt_client_message(&closure->write_buf, &client_msg)) + goto done; + + ret = true; + +done: + debug_return_bool(ret); +} + +/* + * Build and format an IoBuffer wrapped in a ClientMessage. + * Stores the wire format message in buf. + * Returns true on success, false on failure. + */ +static bool +fmt_io_buf(int type, struct timing_closure *timing, + struct connection_buffer *buf) +{ + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + IoBuffer iobuf_msg = IO_BUFFER__INIT; + TimeSpec delay = TIME_SPEC__INIT; + bool ret = false; + debug_decl(fmt_io_buf, SUDO_DEBUG_UTIL) + + if (!read_io_buf(timing)) + goto done; + + /* Fill in IoBuffer. */ + delay.tv_sec = timing->delay.tv_sec; + delay.tv_nsec = timing->delay.tv_nsec; + iobuf_msg.delay = &delay; + iobuf_msg.data.data = (void *)timing->buf; + iobuf_msg.data.len = timing->u.nbytes; + + /* TODO: split buffer if it is too large */ + sudo_warnx("sending IoBuffer length %zu, type %d, size %zu", iobuf_msg.data.len, type, io_buffer__get_packed_size(&iobuf_msg)); // XXX + + /* Send ClientMessage, it doesn't matter which IoBuffer we set. */ + client_msg.ttyout_buf = &iobuf_msg; + client_msg.type_case = type; + if (!fmt_client_message(buf, &client_msg)) + goto done; + + ret = true; + +done: + debug_return_bool(ret); +} + +/* + * Build and format a ChangeWindowSize message wrapped in a ClientMessage. + * Stores the wire format message in buf. + * Returns true on success, false on failure. + */ +static bool +fmt_winsize(struct timing_closure *timing, struct connection_buffer *buf) +{ + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + ChangeWindowSize winsize_msg = CHANGE_WINDOW_SIZE__INIT; + TimeSpec delay = TIME_SPEC__INIT; + bool ret = false; + debug_decl(fmt_winsize, SUDO_DEBUG_UTIL) + + /* Fill in ChangeWindowSize message. */ + delay.tv_sec = timing->delay.tv_sec; + delay.tv_nsec = timing->delay.tv_nsec; + winsize_msg.delay = &delay; + winsize_msg.rows = timing->u.winsize.lines; + winsize_msg.cols = timing->u.winsize.columns; + + sudo_warnx("sending ChangeWindowSize, %dx%d, size %zu", winsize_msg.rows, winsize_msg.cols, change_window_size__get_packed_size(&winsize_msg)); // XXX + + /* Send ClientMessage */ + client_msg.winsize_event = &winsize_msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_WINSIZE_EVENT; + if (!fmt_client_message(buf, &client_msg)) + goto done; + + ret = true; + +done: + debug_return_bool(ret); +} + +/* + * Build and format a CommandSuspend message wrapped in a ClientMessage. + * Stores the wire format message in buf. + * Returns true on success, false on failure. + */ +static bool +fmt_suspend(struct timing_closure *timing, struct connection_buffer *buf) +{ + ClientMessage client_msg = CLIENT_MESSAGE__INIT; + CommandSuspend suspend_msg = COMMAND_SUSPEND__INIT; + TimeSpec delay = TIME_SPEC__INIT; + bool ret = false; + debug_decl(fmt_suspend, SUDO_DEBUG_UTIL) + + /* Fill in CommandSuspend message. */ + delay.tv_sec = timing->delay.tv_sec; + delay.tv_nsec = timing->delay.tv_nsec; + suspend_msg.delay = &delay; + suspend_msg.signal = timing->buf; + + sudo_warnx("sending CommandSuspend, %s, size %zu", suspend_msg.signal, command_suspend__get_packed_size(&suspend_msg)); // XXX + + /* Send ClientMessage */ + client_msg.suspend_event = &suspend_msg; + client_msg.type_case = CLIENT_MESSAGE__TYPE_SUSPEND_EVENT; + if (!fmt_client_message(buf, &client_msg)) + goto done; + + ret = true; + +done: + debug_return_bool(ret); +} + +/* + * Read the next entry for the I/O log timing file and format a ClientMessage. + * Stores the wire format message in the closure's write buffer. + * Returns true on success, false on failure. + */ +static bool +fmt_next_iolog(struct client_closure *closure) +{ + struct timing_closure *timing = &closure->timing; + struct connection_buffer *buf = &closure->write_buf; + bool ret = false; + debug_decl(fmt_next_iolog, SUDO_DEBUG_UTIL) + + if (buf->len != 0) { + sudo_warnx("%s: write buffer already in use", __func__); + debug_return_bool(false); + } + + /* TODO: fill write buffer with multiple messages */ + switch (read_timing_record(timing)) { + case 0: + /* OK */ + break; + case 1: + /* no more IO buffers */ + closure->state = SEND_EXIT; + debug_return_bool(fmt_exit_message(closure)); + case -1: + default: + debug_return_bool(false); + } + + switch (timing->event) { + case IO_EVENT_STDIN: + ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_STDIN_BUF, timing, buf); + break; + case IO_EVENT_STDOUT: + ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_STDOUT_BUF, timing, buf); + break; + case IO_EVENT_STDERR: + ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_STDERR_BUF, timing, buf); + break; + case IO_EVENT_TTYIN: + ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_TTYIN_BUF, timing, buf); + break; + case IO_EVENT_TTYOUT: + ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_TTYOUT_BUF, timing, buf); + break; + case IO_EVENT_WINSIZE: + ret = fmt_winsize(timing, buf); + break; + case IO_EVENT_SUSPEND: + ret = fmt_suspend(timing, buf); + break; + default: + sudo_warnx("unexpected I/O event %d", timing->event); + break; + } + + /* Track elapsed time for comparison with commit points. */ + sudo_timespecadd(&timing->delay, &closure->elapsed, &closure->elapsed); + + debug_return_bool(ret); +} + +/* + * Additional work to do after a ClientMessage was sent to the server. + * Advances state and formats the next ClientMessage (if any). + */ +static bool +client_message_completion(struct client_closure *closure) +{ + debug_decl(client_message_completion, SUDO_DEBUG_UTIL) + + switch (closure->state) { + case SEND_EXEC: + closure->state = SEND_IO; + /* FALLTHROUGH */ + case SEND_IO: + /* fmt_next_iolog() will advance state on EOF. */ + if (!fmt_next_iolog(closure)) + debug_return_bool(false); + break; + case SEND_EXIT: + /* Done writing, just waiting for final commit point. */ + sudo_ev_del(NULL, closure->write_ev); + closure->state = CLOSING; + break; + default: + sudo_warnx("%s: unexpected state %d", __func__, closure->state); + debug_return_bool(false); + } + debug_return_bool(true); +} + +/* + * Respond to a ServerHello message from the server. + * Returns true on success, false on error. + */ +static bool +handle_server_hello(ServerHello *msg, struct client_closure *closure) +{ + size_t n; + debug_decl(handle_server_hello, SUDO_DEBUG_UTIL) + + if (closure->state != RECV_HELLO) { + sudo_warnx("%s: unexpected state %d", __func__, closure->state); + debug_return_bool(false); + } + + /* Sanity check ServerHello message. */ + if (msg->server_id == NULL || msg->server_id[0] == '\0') { + sudo_warnx("invalid ServerHello"); + debug_return_bool(false); + } + + printf("Server: %s\n", msg->server_id); + /* TODO: handle redirect */ + if (msg->redirect != NULL && msg->redirect[0] != '\0') + printf("Redirect: %s\n", msg->redirect); + for (n = 0; n < msg->n_servers; n++) { + printf("Server %zu: %s\n", n + 1, msg->servers[n]); + } + + debug_return_bool(true); +} + +/* + * Respond to a CommitPoint message from the server. + * Returns true on success, false on error. + */ +static bool +handle_commit_point(TimeSpec *commit_point, struct client_closure *closure) +{ + debug_decl(handle_commit_point, SUDO_DEBUG_UTIL) + + /* Only valid after we have sent an IO buffer. */ + if (closure->state < SEND_IO) { + sudo_warnx("%s: unexpected state %d", __func__, closure->state); + debug_return_bool(false); + } + + closure->committed.tv_sec = commit_point->tv_sec; + closure->committed.tv_nsec = commit_point->tv_nsec; + + printf("commit point: %lld %d\n", (long long)commit_point->tv_sec, + commit_point->tv_nsec); /* XXX */ + debug_return_bool(true); +} + +/* + * Respond to a LogId message from the server. + * Always returns true. + */ +static bool +handle_log_id(char *id, struct client_closure *closure) +{ + debug_decl(handle_log_id, SUDO_DEBUG_UTIL) + + sudo_warnx("remote log ID: %s", id); + if ((closure->log_info->iolog_dir = strdup(id)) == NULL) + sudo_fatal(NULL); + debug_return_bool(true); +} + +/* + * Respond to a ServerError message from the server. + * Always returns false. + */ +static bool +handle_server_error(char *errmsg, struct client_closure *closure) +{ + debug_decl(handle_server_error, SUDO_DEBUG_UTIL) + + sudo_warnx("server error: %s", errmsg); + debug_return_bool(false); +} + +/* + * Respond to a ServerAbort message from the server. + * Always returns false. + */ +static bool +handle_server_abort(char *errmsg, struct client_closure *closure) +{ + debug_decl(handle_server_abort, SUDO_DEBUG_UTIL) + + sudo_warnx("server abort: %s", errmsg); + debug_return_bool(false); +} + +/* + * Respond to a ServerMessage from the server. + * Returns true on success, false on error. + */ +static bool +handle_server_message(uint8_t *buf, size_t len, + struct client_closure *closure) +{ + ServerMessage *msg; + bool ret = false; + debug_decl(handle_server_message, SUDO_DEBUG_UTIL) + + sudo_warnx("unpacking ServerMessage"); // XXX + msg = server_message__unpack(NULL, len, buf); + if (msg == NULL) { + sudo_warnx("unable to unpack ServerMessage"); + debug_return_bool(false); + } + + switch (msg->type_case) { + case SERVER_MESSAGE__TYPE_HELLO: + if ((ret = handle_server_hello(msg->hello, closure))) { + closure->state = SEND_EXEC; + ret = fmt_exec_message(closure); + } + break; + case SERVER_MESSAGE__TYPE_COMMIT_POINT: + ret = handle_commit_point(msg->commit_point, closure); + if (sudo_timespeccmp(&closure->elapsed, &closure->committed, ==)) { + sudo_ev_del(NULL, closure->read_ev); + sudo_ev_loopexit(NULL); + closure->state = FINISHED; + } + break; + case SERVER_MESSAGE__TYPE_LOG_ID: + ret = handle_log_id(msg->log_id, closure); + break; + case SERVER_MESSAGE__TYPE_ERROR: + ret = handle_server_error(msg->error, closure); + closure->state = ERROR; + break; + case SERVER_MESSAGE__TYPE_ABORT: + ret = handle_server_abort(msg->abort, closure); + closure->state = ERROR; + break; + default: + /* XXX */ + sudo_warnx("unexpected type_case value %d", msg->type_case); + break; + } + + server_message__free_unpacked(msg, NULL); + debug_return_bool(ret); +} + +/* + * Read and unpack a ServerMessage (read callback). + */ +static void +server_msg_cb(int fd, int what, void *v) +{ + struct client_closure *closure = v; + struct connection_buffer *buf = &closure->read_buf; + ssize_t nread; + uint16_t msg_len; + debug_decl(server_msg_cb, SUDO_DEBUG_UTIL) + + sudo_warnx("reading server message"); // XXX + + nread = recv(fd, buf->data + buf->len, buf->size - buf->len, 0); + sudo_warnx("received %zd bytes from server", nread); + switch (nread) { + case -1: + if (errno == EAGAIN) + debug_return; + sudo_warn("recv"); + goto bad; + case 0: + sudo_warnx("premature EOF"); + goto bad; + default: + break; + } + buf->len += nread; + + while (buf->len - buf->off >= sizeof(msg_len)) { + /* Read wire message size (uint16_t in network byte order). */ + memcpy(&msg_len, buf->data + buf->off, sizeof(msg_len)); + msg_len = ntohs(msg_len); + + if (msg_len + sizeof(msg_len) > buf->len - buf->off) { + /* Incomplete message, we'll read the rest next time. */ + /* TODO: realloc if max message size increases */ + if (buf->off > 0) + memmove(buf->data, buf->data + buf->off, buf->len - buf->off); + break; + } + + /* Parse ServerMessage, could be zero bytes. */ + sudo_warnx("parsing ServerMessage, size %hu", msg_len); // XXX + buf->off += sizeof(msg_len); + if (!handle_server_message(buf->data + buf->off, msg_len, closure)) { + /* XXX - do something on error */ + goto bad; + } + buf->off += msg_len; + } + buf->len -= buf->off; + buf->off = 0; + debug_return; +bad: + close(fd); + client_closure_free(closure); + debug_return; +} + +/* + * Send a ClientMessage to the server (write callback). + */ +static void +client_msg_cb(int fd, int what, void *v) +{ + struct client_closure *closure = v; + struct connection_buffer *buf = &closure->write_buf; + ssize_t nwritten; + debug_decl(client_msg_cb, SUDO_DEBUG_UTIL) + + sudo_warnx("sending %u bytes to server", buf->len - buf->off); + + nwritten = send(fd, buf->data + buf->off, buf->len - buf->off, 0); + if (nwritten == -1) { + sudo_warn("send"); + goto bad; + } + buf->off += nwritten; + + if (buf->off == buf->len) { + /* sent entire message */ + sudo_warnx("finished sending %u bytes to server", buf->len); // XXX + buf->off = 0; + buf->len = 0; + if (!client_message_completion(closure)) + goto bad; + } + debug_return; + +bad: + close(fd); + client_closure_free(closure); + debug_return; +} + +/* + * Allocate a new connection closure. + */ +static struct client_closure * +client_closure_alloc(int sock, struct log_info *log_info) +{ + struct client_closure *closure; + debug_decl(client_closure_alloc, SUDO_DEBUG_UTIL) + + if ((closure = calloc(1, sizeof(*closure))) == NULL) + debug_return_ptr(NULL); + + closure->state = RECV_HELLO; + closure->log_info = log_info; + + closure->timing.bufsize = 10240; + closure->timing.buf = malloc(closure->timing.bufsize); + if (closure->timing.buf == NULL) + goto bad; + + closure->read_buf.size = UINT16_MAX + sizeof(uint16_t); + closure->read_buf.data = malloc(closure->read_buf.size); + if (closure->read_buf.data == NULL) + goto bad; + + closure->write_buf.size = UINT16_MAX + sizeof(uint16_t); + closure->write_buf.data = malloc(closure->write_buf.size); + if (closure->write_buf.data == NULL) + goto bad; + + closure->read_ev = sudo_ev_alloc(sock, SUDO_EV_READ|SUDO_EV_PERSIST, + server_msg_cb, closure); + if (closure->read_ev == NULL) + goto bad; + + closure->write_ev = sudo_ev_alloc(sock, SUDO_EV_WRITE|SUDO_EV_PERSIST, + client_msg_cb, closure); + if (closure->write_ev == NULL) + goto bad; + + debug_return_ptr(closure); +bad: + client_closure_free(closure); + debug_return_ptr(NULL); +} + +int +main(int argc, char *argv[]) +{ + struct client_closure *closure; + struct sudo_event_base *evbase; + struct log_info *log_info; + const char *host = "localhost"; + const char *port = DEFAULT_PORT_STR; + char fname[PATH_MAX]; + char *iolog_path; + int ch, sock; + debug_decl_vars(main, SUDO_DEBUG_MAIN) + + initprogname(argc > 0 ? argv[0] : "sendlog"); + setlocale(LC_ALL, ""); + bindtextdomain("sudo", LOCALEDIR); /* XXX - add logsrvd domain */ + textdomain("sudo"); + + /* Read sudo.conf and initialize the debug subsystem. */ + if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1) + exit(EXIT_FAILURE); + sudo_debug_register(getprogname(), NULL, NULL, + sudo_conf_debug_files(getprogname())); + + if (protobuf_c_version_number() < 1003000) + sudo_fatalx("Protobuf-C version 1.3 or higher required"); + + while ((ch = getopt(argc, argv, "h:p:")) != -1) { + switch (ch) { + case 'h': + host = optarg; + break; + case 'p': + port = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* Remaining arg should be path to I/O log file to send. */ + if (argc != 1) + usage(); + iolog_path = argv[0]; + + signal(SIGPIPE, SIG_IGN); + + /* Parse I/O info log file. */ + snprintf(fname, sizeof(fname), "%s/log", iolog_path); + if ((log_info = parse_logfile(fname)) == NULL) + goto bad; + sudo_warnx("parsed log file %s", fname); // XXX + + /* Open the I/O log files. */ + if (!iolog_open(iolog_path)) + goto bad; + + /* Connect to server, setup events. */ + sock = connect_server(host, port); + if (sock == -1) + goto bad; + sudo_warnx("connected to %s:%s", host, port); // XXX + + if ((evbase = sudo_ev_base_alloc()) == NULL) + sudo_fatal(NULL); + sudo_ev_base_setdef(evbase); + + if ((closure = client_closure_alloc(sock, log_info)) == NULL) + goto bad; + + /* Add read event for the server hello message and enter event loop. */ + if (sudo_ev_add(evbase, closure->read_ev, NULL, false) == -1) + goto bad; + sudo_ev_dispatch(evbase); + + if (!sudo_timespeccmp(&closure->elapsed, &closure->committed, ==)) { + sudo_warnx("commit point mismatch, expected [%lld, %ld], got [%lld, %ld]", + (long long)closure->elapsed.tv_sec, closure->elapsed.tv_nsec, + (long long)closure->committed.tv_sec, closure->committed.tv_nsec); + } + + if (closure->state != FINISHED) { + sudo_warnx("exited prematurely with state %d", closure->state); + goto bad; + } + + debug_return_int(EXIT_SUCCESS); +bad: + debug_return_int(EXIT_FAILURE); +} diff --git a/logsrvd/sendlog.h b/logsrvd/sendlog.h new file mode 100644 index 000000000..0154d4cf1 --- /dev/null +++ b/logsrvd/sendlog.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error protobuf-c version 1.30 or higher required +#endif + +#define DEFAULT_PORT_STR "30344" + +/* + * Indexes into io_fds[] and iolog_names[] + * The first five must match the IO_EVENT_ defines in iolog.h. + * XXX - needed? + */ +#define IOFD_STDIN 0 +#define IOFD_STDOUT 1 +#define IOFD_STDERR 2 +#define IOFD_TTYIN 3 +#define IOFD_TTYOUT 4 +#define IOFD_TIMING 5 +#define IOFD_MAX 6 + +struct timing_closure { + struct timespec delay; + int event; + union { + struct { + int lines; + int columns; + } winsize; + size_t nbytes; + } u; + char *buf; + size_t bufsize; +}; + +enum client_state { + ERROR, + RECV_HELLO, + SEND_EXEC, + SEND_IO, + SEND_EXIT, + CLOSING, + FINISHED +}; + +/* TODO: share with server */ +struct connection_buffer { + uint8_t *data; /* pre-allocated data buffer */ + unsigned int size; /* currently always UINT16_MAX + 2 */ + unsigned int len; + unsigned int off; +}; + +struct client_closure { + struct timespec elapsed; + struct timespec committed; + struct timing_closure timing; + struct connection_buffer read_buf; + struct connection_buffer write_buf; + struct sudo_event *read_ev; + struct sudo_event *write_ev; + struct log_info *log_info; + enum client_state state; +}; + +/* iolog_reader.c */ +bool iolog_open(const char *iolog_path); +bool read_io_buf(struct timing_closure *timing); +int read_timing_record(struct timing_closure *timing); +struct log_info *parse_logfile(const char *logfile); +void free_log_info(struct log_info *li); diff --git a/sudo.pp b/sudo.pp index 3f046329a..4d2924c5b 100644 --- a/sudo.pp +++ b/sudo.pp @@ -284,6 +284,8 @@ still allow people to get their work done." ln -s -f ${bindir}/sudo ${pp_destdir}/usr/bin ln -s -f ${bindir}/sudoedit ${pp_destdir}/usr/bin ln -s -f ${bindir}/sudoreplay ${pp_destdir}/usr/bin + ln -s -f ${sbindir}/logsrvd ${pp_destdir}/usr/sbin + ln -s -f ${sbindir}/sendlog ${pp_destdir}/usr/sbin ln -s -f ${sbindir}/visudo ${pp_destdir}/usr/sbin %endif @@ -331,6 +333,8 @@ still allow people to get their work done." $bindir/sudo 4755 root: $bindir/sudoedit 0755 root: symlink sudo $bindir/sudoreplay 0755 + $sbindir/logsrvd 0755 + $sbindir/sendlog 0755 $sbindir/visudo 0755 $includedir/sudo_plugin.h 0644 $libexecdir/sudo/ 0755 @@ -363,6 +367,8 @@ still allow people to get their work done." /usr/bin/sudo 0755 root: symlink $bindir/sudo /usr/bin/sudoedit 0755 root: symlink $bindir/sudoedit /usr/bin/sudoreplay 0755 root: symlink $bindir/sudoreplay + /usr/sbin/logsrvd 0755 root: symlink $sbindir/logsrvd + /usr/sbin/sendlog 0755 root: symlink $sbindir/sendlog /usr/sbin/visudo 0755 root: symlink $sbindir/visudo %endif %if [rpm]