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]