Import proof of concept sudo log server.

This commit is contained in:
Todd C. Miller
2019-10-24 20:04:29 -06:00
parent aa99594575
commit 2272430716
16 changed files with 5495 additions and 16 deletions

View File

@@ -227,6 +227,17 @@ lib/zlib/zlib.h
lib/zlib/zutil.c lib/zlib/zutil.c
lib/zlib/zutil.h lib/zlib/zutil.h
log2cl.pl 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 ltmain.sh
m4/ax_append_flag.m4 m4/ax_append_flag.m4
m4/ax_check_compile_flag.m4 m4/ax_check_compile_flag.m4

View File

@@ -49,7 +49,7 @@ sudoers_gid = @SUDOERS_GID@
sudoers_mode = @SUDOERS_MODE@ sudoers_mode = @SUDOERS_MODE@
shlib_mode = @SHLIB_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 plugins/system_group src include doc examples
SAMPLES = plugins/sample SAMPLES = plugins/sample
@@ -188,17 +188,18 @@ siglist.c signame.c:
depend: siglist.c signame.c depend: siglist.c signame.c
$(top_srcdir)/mkdep.pl --builddir=`pwd` --srcdir=$(top_srcdir) \ $(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/group_file/Makefile.in plugins/sample/Makefile.in \
plugins/sudoers/Makefile.in plugins/system_group/Makefile.in \ plugins/sudoers/Makefile.in plugins/system_group/Makefile.in \
src/Makefile.in && \ src/Makefile.in && \
$(top_builddir)/config.status --file $(top_builddir)/lib/util/Makefile \ $(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/sample/Makefile \
--file $(top_builddir)/plugins/group_file/Makefile \ --file $(top_builddir)/plugins/group_file/Makefile \
--file $(top_builddir)/plugins/sudoers/Makefile \ --file $(top_builddir)/plugins/sudoers/Makefile \
--file $(top_builddir)/plugins/system_group/Makefile \ --file $(top_builddir)/plugins/system_group/Makefile \
--file $(top_builddir)/src/Makefile \ --file $(top_builddir)/src/Makefile
--file $(top_builddir)/lib/zlib/Makefile
ChangeLog: ChangeLog:
if test -d $(srcdir)/.hg && cd $(srcdir); then \ if test -d $(srcdir)/.hg && cd $(srcdir); then \

21
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh #! /bin/sh
# Guess values for system-dependent variables and create Makefiles. # 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 <https://bugzilla.sudo.ws/>. # Report bugs to <https://bugzilla.sudo.ws/>.
# #
@@ -590,8 +590,8 @@ MAKEFLAGS=
# Identity of this package. # Identity of this package.
PACKAGE_NAME='sudo' PACKAGE_NAME='sudo'
PACKAGE_TARNAME='sudo' PACKAGE_TARNAME='sudo'
PACKAGE_VERSION='1.8.29' PACKAGE_VERSION='1.9.0'
PACKAGE_STRING='sudo 1.8.29' PACKAGE_STRING='sudo 1.9.0'
PACKAGE_BUGREPORT='https://bugzilla.sudo.ws/' PACKAGE_BUGREPORT='https://bugzilla.sudo.ws/'
PACKAGE_URL='' 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. # 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. # This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF 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]... Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1609,7 +1609,7 @@ fi
if test -n "$ac_init_help"; then if test -n "$ac_init_help"; then
case $ac_init_help in case $ac_init_help in
short | recursive ) echo "Configuration of sudo 1.8.29:";; short | recursive ) echo "Configuration of sudo 1.9.0:";;
esac esac
cat <<\_ACEOF cat <<\_ACEOF
@@ -1875,7 +1875,7 @@ fi
test -n "$ac_init_help" && exit $ac_status test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then if $ac_init_version; then
cat <<\_ACEOF cat <<\_ACEOF
sudo configure 1.8.29 sudo configure 1.9.0
generated by GNU Autoconf 2.69 generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc. Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2584,7 +2584,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake. 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 generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@ $ $0 $@
@@ -27005,7 +27005,7 @@ elif test X"$TMPFILES_D" != X""; then
ac_config_files="$ac_config_files init.d/sudo.conf" ac_config_files="$ac_config_files init.d/sudo.conf"
fi 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 cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure # 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 # report actual input values of CONFIG_FILES etc. instead of their
# values after options handling. # values after options handling.
ac_log=" 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 generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES CONFIG_FILES = $CONFIG_FILES
@@ -27579,7 +27579,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\ ac_cs_version="\\
sudo config.status 1.8.29 sudo config.status 1.9.0
configured by $0, generated by GNU Autoconf 2.69, configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\" with options \\"\$ac_cs_config\\"
@@ -28005,6 +28005,7 @@ do
"include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;; "include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
"lib/util/Makefile") CONFIG_FILES="$CONFIG_FILES lib/util/Makefile" ;; "lib/util/Makefile") CONFIG_FILES="$CONFIG_FILES lib/util/Makefile" ;;
"lib/util/util.exp") CONFIG_FILES="$CONFIG_FILES lib/util/util.exp" ;; "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/sudo_usage.h") CONFIG_FILES="$CONFIG_FILES src/sudo_usage.h" ;;
"src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;;
"plugins/sample/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/sample/Makefile" ;; "plugins/sample/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/sample/Makefile" ;;

View File

@@ -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 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
dnl dnl
AC_PREREQ([2.59]) 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_HEADER([config.h pathnames.h])
AC_CONFIG_SRCDIR([src/sudo.c]) AC_CONFIG_SRCDIR([src/sudo.c])
dnl dnl
@@ -4480,7 +4480,7 @@ if test X"$INIT_SCRIPT" != X""; then
elif test X"$TMPFILES_D" != X""; then elif test X"$TMPFILES_D" != X""; then
AC_CONFIG_FILES([init.d/sudo.conf]) AC_CONFIG_FILES([init.d/sudo.conf])
fi 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 AC_OUTPUT
dnl dnl

279
logsrvd/Makefile.in Normal file
View File

@@ -0,0 +1,279 @@
#
# SPDX-License-Identifier: ISC
#
# Copyright (c) 2019 Todd C. Miller <Todd.Miller@sudo.ws>
#
# 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 $@

55
logsrvd/iolog.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef LOGSRVD_IOLOG_H
#define LOGSRVD_IOLOG_H
#include <zlib.h>
/*
* 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 */

452
logsrvd/iolog_reader.c Normal file
View File

@@ -0,0 +1,452 @@
/*
* Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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);
}

508
logsrvd/iolog_writer.c Normal file
View File

@@ -0,0 +1,508 @@
/*
* Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif /* HAVE_STDBOOL_H */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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);
}

1452
logsrvd/log_server.pb-c.c Normal file

File diff suppressed because it is too large Load Diff

663
logsrvd/log_server.pb-c.h Normal file
View File

@@ -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/protobuf-c.h>
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 */

113
logsrvd/log_server.proto Normal file
View File

@@ -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 */
}

827
logsrvd/logsrvd.c Normal file
View File

@@ -0,0 +1,827 @@
/*
* Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif /* HAVE_STDBOOL_H */
#if defined(HAVE_STDINT_H)
# include <stdint.h>
#elif defined(HAVE_INTTYPES_H)
# include <inttypes.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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);
}

96
logsrvd/logsrvd.h Normal file
View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#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);

930
logsrvd/sendlog.c Normal file
View File

@@ -0,0 +1,930 @@
/*
* Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif /* HAVE_STDBOOL_H */
#if defined(HAVE_STDINT_H)
# include <stdint.h>
#elif defined(HAVE_INTTYPES_H)
# include <inttypes.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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);
}

85
logsrvd/sendlog.h Normal file
View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#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);

View File

@@ -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}/sudo ${pp_destdir}/usr/bin
ln -s -f ${bindir}/sudoedit ${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 ${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 ln -s -f ${sbindir}/visudo ${pp_destdir}/usr/sbin
%endif %endif
@@ -331,6 +333,8 @@ still allow people to get their work done."
$bindir/sudo 4755 root: $bindir/sudo 4755 root:
$bindir/sudoedit 0755 root: symlink sudo $bindir/sudoedit 0755 root: symlink sudo
$bindir/sudoreplay 0755 $bindir/sudoreplay 0755
$sbindir/logsrvd 0755
$sbindir/sendlog 0755
$sbindir/visudo 0755 $sbindir/visudo 0755
$includedir/sudo_plugin.h 0644 $includedir/sudo_plugin.h 0644
$libexecdir/sudo/ 0755 $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/sudo 0755 root: symlink $bindir/sudo
/usr/bin/sudoedit 0755 root: symlink $bindir/sudoedit /usr/bin/sudoedit 0755 root: symlink $bindir/sudoedit
/usr/bin/sudoreplay 0755 root: symlink $bindir/sudoreplay /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 /usr/sbin/visudo 0755 root: symlink $sbindir/visudo
%endif %endif
%if [rpm] %if [rpm]