Example audit plugin that writes JSON output to a log file.

This commit is contained in:
Todd C. Miller
2020-01-30 13:25:52 -07:00
parent a88a05c1eb
commit bf85ea2bf7
9 changed files with 1009 additions and 51 deletions

View File

@@ -299,6 +299,9 @@ mkdep.pl
mkinstalldirs mkinstalldirs
mkpkg mkpkg
pathnames.h.in pathnames.h.in
plugins/audit_json/Makefile.in
plugins/audit_json/audit_json.c
plugins/audit_json/audit_json.exp
plugins/group_file/Makefile.in plugins/group_file/Makefile.in
plugins/group_file/getgrent.c plugins/group_file/getgrent.c
plugins/group_file/group_file.c plugins/group_file/group_file.c
@@ -322,15 +325,15 @@ plugins/python/python_plugin_common.h
plugins/python/python_plugin_group.c plugins/python/python_plugin_group.c
plugins/python/python_plugin_io.c plugins/python/python_plugin_io.c
plugins/python/python_plugin_policy.c plugins/python/python_plugin_policy.c
plugins/python/sudo_python_debug.c
plugins/python/sudo_python_debug.h
plugins/python/sudo_python_module.c
plugins/python/sudo_python_module.h
plugins/python/regress/check_python_examples.c plugins/python/regress/check_python_examples.c
plugins/python/regress/iohelpers.c plugins/python/regress/iohelpers.c
plugins/python/regress/iohelpers.h plugins/python/regress/iohelpers.h
plugins/python/regress/testhelpers.c plugins/python/regress/testhelpers.c
plugins/python/regress/testhelpers.h plugins/python/regress/testhelpers.h
plugins/python/sudo_python_debug.c
plugins/python/sudo_python_debug.h
plugins/python/sudo_python_module.c
plugins/python/sudo_python_module.h
plugins/sample/Makefile.in plugins/sample/Makefile.in
plugins/sample/README plugins/sample/README
plugins/sample/sample_plugin.c plugins/sample/sample_plugin.c

View File

@@ -52,7 +52,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@ lib/iolog lib/logsrv logsrvd \ SUBDIRS = lib/util @ZLIB_SRC@ lib/iolog lib/logsrv logsrvd plugins/audit_json \
plugins/group_file plugins/sudoers plugins/system_group \ plugins/group_file plugins/sudoers plugins/system_group \
@PYTHON_PLUGIN_SRC@ src include doc examples @PYTHON_PLUGIN_SRC@ src include doc examples

73
configure vendored
View File

@@ -733,6 +733,8 @@ password_timeout
timeout timeout
vardir vardir
rundir rundir
logpath
log_dir
iolog_dir iolog_dir
PYTHON_PLUGIN_SRC PYTHON_PLUGIN_SRC
SIGNAME SIGNAME
@@ -780,7 +782,6 @@ NOEXECFILE
mansrcdir mansrcdir
mansectform mansectform
mansectsu mansectsu
logpath
devdir devdir
SEMAN SEMAN
PSMAN PSMAN
@@ -3109,13 +3110,14 @@ $as_echo "$as_me: Configuring Sudo version $PACKAGE_VERSION" >&6;}
# #
# Begin initial values for man page substitution # Begin initial values for man page substitution
# #
iolog_dir=/var/log/sudo-io iolog_dir=/var/log/sudo-io
log_dir=/var/log
logpath=/var/log/sudo.log
rundir=/var/run/sudo rundir=/var/run/sudo
vardir=/var/adm/sudo vardir=/var/adm/sudo
timeout=5 timeout=5
@@ -4570,7 +4572,7 @@ fi
# Check whether --with-CC was given. # Check whether --with-CC was given.
if test "${with_CC+set}" = set; then : if test "${with_CC+set}" = set; then :
withval=$with_CC; case $with_CC in withval=$with_CC; case $with_CC in
*) as_fn_error $? "the --with-CC option is no longer supported, please set the CC environment variable instead." "$LINENO" 5 *) as_fn_error $? "the --with-CC option is no longer supported, please pass CC=$with_CC to configure instead." "$LINENO" 5
;; ;;
esac esac
fi fi
@@ -26380,23 +26382,45 @@ $as_echo "not found" >&6; }
fi fi
fi fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for log dir location" >&5
$as_echo_n "checking for log dir location... " >&6; }
if test "${with_logdir-yes}" != "yes"; then
log_dir="$with_logdir"
else
# Default value of log_dir set in configure.ac
for d in /var/log /var/adm /usr/adm; do
if test -d "$d"; then
log_dir="$d"
break
fi
done
fi
if test "${with_logdir}" != "no"; then
cat >>confdefs.h <<EOF
#define _PATH_SUDO_LOGDIR "$log_dir"
EOF
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $log_dir" >&5
$as_echo "$log_dir" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for log file location" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for log file location" >&5
$as_echo_n "checking for log file location... " >&6; } $as_echo_n "checking for log file location... " >&6; }
if test -n "$with_logpath"; then if test "${with_logpath-yes}" != "yes"; then
logpath="$with_logpath" logpath="$with_logpath"
elif test -d "/var/log"; then else
logpath="/var/log/sudo.log" # Default value of logpath set in configure.ac
elif test -d "/var/adm"; then for d in /var/log /var/adm /usr/adm; do
logpath="/var/adm/sudo.log" if test -d "$d"; then
elif test -d "/usr/adm"; then logpath="$d/sudo.log"
logpath="/usr/adm/sudo.log" break
else fi
# Assume a modern system done
logpath="/var/log/sudo.log" fi
fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $logpath" >&5
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $logpath" >&5
$as_echo "$logpath" >&6; } $as_echo "$logpath" >&6; }
cat >>confdefs.h <<EOF cat >>confdefs.h <<EOF
#define _PATH_SUDO_LOGFILE "$logpath" #define _PATH_SUDO_LOGFILE "$logpath"
EOF EOF
@@ -26442,12 +26466,14 @@ EOF
$as_echo_n "checking for I/O log dir location... " >&6; } $as_echo_n "checking for I/O log dir location... " >&6; }
if test "${with_iologdir-yes}" != "yes"; then if test "${with_iologdir-yes}" != "yes"; then
iolog_dir="$with_iologdir" iolog_dir="$with_iologdir"
elif test -d "/var/log"; then
iolog_dir="/var/log/sudo-io"
elif test -d "/var/adm"; then
iolog_dir="/var/adm/sudo-io"
else else
iolog_dir="/usr/adm/sudo-io" # Default value of iolog_dir set in configure.ac
for d in /var/log /var/adm /usr/adm; do
if test -d "$d"; then
iolog_dir="$d/sudo-io"
break
fi
done
fi fi
if test "${with_iologdir}" != "no"; then if test "${with_iologdir}" != "no"; then
cat >>confdefs.h <<EOF cat >>confdefs.h <<EOF
@@ -27717,7 +27743,7 @@ elif test X"$TMPFILES_D" != X""; then
fi fi
ac_config_files="$ac_config_files Makefile doc/Makefile examples/Makefile examples/sudo.conf include/Makefile lib/iolog/Makefile lib/logsrv/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_config_files="$ac_config_files Makefile doc/Makefile examples/Makefile examples/sudo.conf include/Makefile lib/iolog/Makefile lib/logsrv/Makefile lib/util/Makefile lib/util/util.exp logsrvd/Makefile src/sudo_usage.h src/Makefile plugins/audit_json/Makefile plugins/sample/Makefile plugins/group_file/Makefile plugins/system_group/Makefile plugins/sudoers/Makefile plugins/sudoers/sudoers"
cat >confcache <<\_ACEOF cat >confcache <<\_ACEOF
@@ -28725,6 +28751,7 @@ do
"logsrvd/Makefile") CONFIG_FILES="$CONFIG_FILES logsrvd/Makefile" ;; "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/audit_json/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/audit_json/Makefile" ;;
"plugins/sample/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/sample/Makefile" ;; "plugins/sample/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/sample/Makefile" ;;
"plugins/group_file/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/group_file/Makefile" ;; "plugins/group_file/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/group_file/Makefile" ;;
"plugins/system_group/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/system_group/Makefile" ;; "plugins/system_group/Makefile") CONFIG_FILES="$CONFIG_FILES plugins/system_group/Makefile" ;;

View File

@@ -3,7 +3,7 @@ dnl Use the top-level autogen.sh script to generate configure and config.h.in
dnl dnl
dnl SPDX-License-Identifier: ISC dnl SPDX-License-Identifier: ISC
dnl dnl
dnl Copyright (c) 1994-1996, 1998-2019 Todd C. Miller <Todd.Miller@sudo.ws> dnl Copyright (c) 1994-1996, 1998-2020 Todd C. Miller <Todd.Miller@sudo.ws>
dnl dnl
dnl Permission to use, copy, modify, and distribute this software for any dnl Permission to use, copy, modify, and distribute this software for any
dnl purpose with or without fee is hereby granted, provided that the above dnl purpose with or without fee is hereby granted, provided that the above
@@ -70,7 +70,6 @@ AC_SUBST([LCMAN])
AC_SUBST([PSMAN]) AC_SUBST([PSMAN])
AC_SUBST([SEMAN]) AC_SUBST([SEMAN])
AC_SUBST([devdir]) AC_SUBST([devdir])
AC_SUBST([logpath])
AC_SUBST([mansectsu]) AC_SUBST([mansectsu])
AC_SUBST([mansectform]) AC_SUBST([mansectform])
AC_SUBST([mansrcdir]) AC_SUBST([mansrcdir])
@@ -122,6 +121,8 @@ dnl
dnl Variables that get substituted in docs (not overridden by environment) dnl Variables that get substituted in docs (not overridden by environment)
dnl dnl
AC_SUBST([iolog_dir])dnl real initial value from SUDO_IO_LOGDIR AC_SUBST([iolog_dir])dnl real initial value from SUDO_IO_LOGDIR
AC_SUBST([log_dir])dnl real initial value from SUDO_LOGDIR
AC_SUBST([logpath])dnl real initial value from SUDO_LOGFILE
AC_SUBST([rundir])dnl real initial value from SUDO_RUNDIR AC_SUBST([rundir])dnl real initial value from SUDO_RUNDIR
AC_SUBST([vardir])dnl real initial value from SUDO_VARDIR AC_SUBST([vardir])dnl real initial value from SUDO_VARDIR
AC_SUBST([timeout]) AC_SUBST([timeout])
@@ -165,6 +166,8 @@ AC_SUBST([plugindir])
# Begin initial values for man page substitution # Begin initial values for man page substitution
# #
iolog_dir=/var/log/sudo-io iolog_dir=/var/log/sudo-io
log_dir=/var/log
logpath=/var/log/sudo.log
rundir=/var/run/sudo rundir=/var/run/sudo
vardir=/var/adm/sudo vardir=/var/adm/sudo
timeout=5 timeout=5
@@ -338,7 +341,7 @@ esac])
AC_ARG_WITH(CC, [AS_HELP_STRING([--with-CC], [C compiler to use])], AC_ARG_WITH(CC, [AS_HELP_STRING([--with-CC], [C compiler to use])],
[case $with_CC in [case $with_CC in
*) AC_MSG_ERROR([the --with-CC option is no longer supported, please set the CC environment variable instead.]) *) AC_MSG_ERROR([the --with-CC option is no longer supported, please pass CC=$with_CC to configure instead.])
;; ;;
esac]) esac])
@@ -4133,6 +4136,7 @@ dnl
if test "$utmp_style" = "LEGACY"; then if test "$utmp_style" = "LEGACY"; then
SUDO_PATH_UTMP SUDO_PATH_UTMP
fi fi
SUDO_LOGDIR
SUDO_LOGFILE SUDO_LOGFILE
SUDO_RUNDIR SUDO_RUNDIR
SUDO_VARDIR SUDO_VARDIR
@@ -4601,7 +4605,7 @@ elif test X"$TMPFILES_D" != X""; then
AC_CONFIG_FILES([etc/init.d/sudo.conf]) AC_CONFIG_FILES([etc/init.d/sudo.conf])
fi fi
AC_CONFIG_FILES([Makefile doc/Makefile examples/Makefile examples/sudo.conf include/Makefile lib/iolog/Makefile lib/logsrv/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_CONFIG_FILES([Makefile doc/Makefile examples/Makefile examples/sudo.conf include/Makefile lib/iolog/Makefile lib/logsrv/Makefile lib/util/Makefile lib/util/util.exp logsrvd/Makefile src/sudo_usage.h src/Makefile plugins/audit_json/Makefile plugins/sample/Makefile plugins/group_file/Makefile plugins/system_group/Makefile plugins/sudoers/Makefile plugins/sudoers/sudoers])
AC_OUTPUT AC_OUTPUT

View File

@@ -79,20 +79,19 @@ dnl
dnl Where the log file goes, use /var/log if it exists, else /{var,usr}/adm dnl Where the log file goes, use /var/log if it exists, else /{var,usr}/adm
dnl dnl
AC_DEFUN([SUDO_LOGFILE], [AC_MSG_CHECKING(for log file location) AC_DEFUN([SUDO_LOGFILE], [AC_MSG_CHECKING(for log file location)
if test -n "$with_logpath"; then if test "${with_logpath-yes}" != "yes"; then
logpath="$with_logpath" logpath="$with_logpath"
elif test -d "/var/log"; then else
logpath="/var/log/sudo.log" # Default value of logpath set in configure.ac
elif test -d "/var/adm"; then for d in /var/log /var/adm /usr/adm; do
logpath="/var/adm/sudo.log" if test -d "$d"; then
elif test -d "/usr/adm"; then logpath="$d/sudo.log"
logpath="/usr/adm/sudo.log" break
else fi
# Assume a modern system done
logpath="/var/log/sudo.log" fi
fi AC_MSG_RESULT($logpath)
AC_MSG_RESULT($logpath) SUDO_DEFINE_UNQUOTED(_PATH_SUDO_LOGFILE, "$logpath")
SUDO_DEFINE_UNQUOTED(_PATH_SUDO_LOGFILE, "$logpath")
])dnl ])dnl
dnl dnl
@@ -157,12 +156,14 @@ AC_DEFUN([SUDO_IO_LOGDIR], [
AC_MSG_CHECKING(for I/O log dir location) AC_MSG_CHECKING(for I/O log dir location)
if test "${with_iologdir-yes}" != "yes"; then if test "${with_iologdir-yes}" != "yes"; then
iolog_dir="$with_iologdir" iolog_dir="$with_iologdir"
elif test -d "/var/log"; then
iolog_dir="/var/log/sudo-io"
elif test -d "/var/adm"; then
iolog_dir="/var/adm/sudo-io"
else else
iolog_dir="/usr/adm/sudo-io" # Default value of iolog_dir set in configure.ac
for d in /var/log /var/adm /usr/adm; do
if test -d "$d"; then
iolog_dir="$d/sudo-io"
break
fi
done
fi fi
if test "${with_iologdir}" != "no"; then if test "${with_iologdir}" != "no"; then
SUDO_DEFINE_UNQUOTED(_PATH_SUDO_IO_LOGDIR, "$iolog_dir") SUDO_DEFINE_UNQUOTED(_PATH_SUDO_IO_LOGDIR, "$iolog_dir")
@@ -170,6 +171,28 @@ AC_DEFUN([SUDO_IO_LOGDIR], [
AC_MSG_RESULT($iolog_dir) AC_MSG_RESULT($iolog_dir)
])dnl ])dnl
dnl
dnl Where the log files go, use /var/log if it exists, else /{var,usr}/adm
dnl
AC_DEFUN([SUDO_LOGDIR], [
AC_MSG_CHECKING(for log dir location)
if test "${with_logdir-yes}" != "yes"; then
log_dir="$with_logdir"
else
# Default value of log_dir set in configure.ac
for d in /var/log /var/adm /usr/adm; do
if test -d "$d"; then
log_dir="$d"
break
fi
done
fi
if test "${with_logdir}" != "no"; then
SUDO_DEFINE_UNQUOTED(_PATH_SUDO_LOGDIR, "$log_dir")
fi
AC_MSG_RESULT($log_dir)
])dnl
dnl dnl
dnl check for working fnmatch(3) dnl check for working fnmatch(3)
dnl dnl

View File

@@ -115,6 +115,14 @@
# undef _PATH_SUDO_IO_LOGDIR # undef _PATH_SUDO_IO_LOGDIR
#endif /* _PATH_SUDO_IO_LOGDIR */ #endif /* _PATH_SUDO_IO_LOGDIR */
/*
* Where to put the audit and other log files. Defaults to /var/log,
* /var/adm or /usr/adm depending on what exists.
*/
#ifndef _PATH_SUDO_LOGDIR
# undef _PATH_SUDO_LOGDIR
#endif /* _PATH_SUDO_LOGDIR */
/* /*
* Where to put the sudo log file when logging to a file. Defaults to * Where to put the sudo log file when logging to a file. Defaults to
* /var/log/sudo.log if /var/log exists, else /var/adm/sudo.log. * /var/log/sudo.log if /var/log exists, else /var/adm/sudo.log.

View File

@@ -0,0 +1,219 @@
#
# SPDX-License-Identifier: ISC
#
# Copyright (c) 2020 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
cross_compiling = @CROSS_COMPILING@
# Compiler & tools to use
CC = @CC@
LIBTOOL = @LIBTOOL@
SED = @SED@
AWK = @AWK@
# 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 = $(LT_LIBS)
# C preprocessor flags
CPPFLAGS = -I$(incdir) -I$(top_builddir) @CPPFLAGS@
# Usually -O and/or -g
CFLAGS = @CFLAGS@
# Flags to pass to the link stage
LDFLAGS = @LDFLAGS@
LT_LDFLAGS = @LT_LDFLAGS@ @LT_LDEXPORTS@
# 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@
localstatedir = @localstatedir@
plugindir = @plugindir@
# File mode and map file to use for shared libraries/objects
shlib_enable = @SHLIB_ENABLE@
shlib_mode = @SHLIB_MODE@
shlib_exp = $(srcdir)/audit_json.exp
shlib_map = audit_json.map
shlib_opt = audit_json.opt
# User and group ids the installed files should be "owned" by
install_uid = 0
install_gid = 0
#### End of system configuration section. ####
SHELL = @SHELL@
OBJS = audit_json.lo
IOBJS = $(OBJS:.lo=.i)
POBJS = $(IOBJS:.i=.plog)
LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
VERSION = @PACKAGE_VERSION@
all: audit_json.la
depend:
$(top_srcdir)/mkdep.pl --srcdir=$(top_srcdir) \
--builddir=`pwd`/$(top_builddir) plugins/audit_json/Makefile.in
cd $(top_builddir) && ./config.status --file plugins/audit_json/Makefile
Makefile: $(srcdir)/Makefile.in
cd $(top_builddir) && ./config.status --file plugins/audit_json/Makefile
.SUFFIXES: .c .h .i .lo .plog
.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 $@
$(shlib_map): $(shlib_exp)
@$(AWK) 'BEGIN { print "{\n\tglobal:" } { print "\t\t"$$0";" } END { print "\tlocal:\n\t\t*;\n};" }' $(shlib_exp) > $@
$(shlib_opt): $(shlib_exp)
@$(SED) 's/^/+e /' $(shlib_exp) > $@
audit_json.la: $(OBJS) $(LT_LIBS) @LT_LDDEP@
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LDFLAGS) $(ASAN_LDFLAGS) $(SSP_LDFLAGS) $(LT_LDFLAGS) -o $@ $(OBJS) $(LIBS) -module -avoid-version -rpath $(plugindir) -shrext .so
pre-install:
install: install-plugin
install-dirs:
$(SHELL) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(plugindir)
install-binaries:
install-includes:
install-doc:
install-plugin: install-dirs audit_json.la
if [ X"$(shlib_enable)" = X"yes" ]; then \
INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --mode=install $(INSTALL) $(INSTALL_OWNER) -m $(shlib_mode) audit_json.la $(DESTDIR)$(plugindir); \
fi
uninstall:
-$(LIBTOOL) $(LTFLAGS) --mode=uninstall rm -f $(DESTDIR)$(plugindir)/audit_json.la
-test -z "$(INSTALL_BACKUP)" || \
rm -f $(DESTDIR)$(plugindir)/audit_json.so$(INSTALL_BACKUP)
splint:
splint $(SPLINT_OPTS) -I$(incdir) -I$(top_builddir) $(srcdir)/*.c
cppcheck:
cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) $(srcdir)/*.c
pvs-log-files: $(POBJS)
pvs-studio: $(POBJS)
plog-converter $(PVS_LOG_OPTS) $(POBJS)
check:
clean:
-$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f *.lo *.o *.la *.a *.i *.plog \
stamp-* core *.core core.*
mostlyclean: clean
distclean: clean
-rm -rf Makefile .libs $(shlib_map) $(shlib_opt)
clobber: distclean
realclean: distclean
rm -f TAGS tags
cleandir: realclean
# Autogenerated dependencies, do not modify
getgrent.lo: $(srcdir)/getgrent.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/getgrent.c
getgrent.i: $(srcdir)/getgrent.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $<
getgrent.plog: getgrent.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getgrent.c --i-file $< --output-file $@
audit_json.lo: $(srcdir)/audit_json.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_plugin.h \
$(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/audit_json.c
audit_json.i: $(srcdir)/audit_json.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_plugin.h \
$(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $<
audit_json.plog: audit_json.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/audit_json.c --i-file $< --output-file $@

View File

@@ -0,0 +1,673 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 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.
*/
/*
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
*/
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif /* HAVE_STDBOOL_H */
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <signal.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <time.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_dso.h"
#include "sudo_fatal.h"
#include "sudo_json.h"
#include "sudo_plugin.h"
#include "sudo_util.h"
#include "pathnames.h"
#ifndef HAVE_FSEEKO
# define fseeko(f, o, w) fseek((f), (o), (w))
# define ftello(f) ftell(f)
#endif
static int audit_debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER;
static sudo_conv_t audit_conv;
static sudo_printf_t audit_printf;
static struct audit_state {
int submit_optind;
char uuid_str[37];
bool accepted;
FILE *log_fp;
char *logfile;
char * const * settings;
char * const * user_info;
char * const * submit_argv;
char * const * submit_envp;
} state = { -1 };
/* Filter out entries in settings[] that are not really options. */
char * const settings_filter[] = {
"debug_flags",
"max_groups",
"network_addrs",
"plugin_dir",
"plugin_path",
"progname",
NULL
};
/*
* Parse the "filename flags,..." debug_flags entry and insert a new
* sudo_debug_file struct into debug_files.
* XXX - move to libsudoutil
*/
static bool
sudo_debug_parse_flags(struct sudo_conf_debug_file_list *debug_files,
const char *entry)
{
struct sudo_debug_file *debug_file;
const char *filename, *flags;
size_t namelen;
/* Only process new-style debug flags: filename flags,... */
filename = entry;
if (*filename != '/' || (flags = strpbrk(filename, " \t")) == NULL)
return true;
namelen = (size_t)(flags - filename);
while (isblank((unsigned char)*flags))
flags++;
if (*flags != '\0') {
if ((debug_file = calloc(1, sizeof(*debug_file))) == NULL)
goto oom;
if ((debug_file->debug_file = strndup(filename, namelen)) == NULL)
goto oom;
if ((debug_file->debug_flags = strdup(flags)) == NULL)
goto oom;
TAILQ_INSERT_TAIL(debug_files, debug_file, entries);
}
return true;
oom:
if (debug_file != NULL) {
free(debug_file->debug_file);
free(debug_file->debug_flags);
free(debug_file);
}
return false;
}
static int
audit_open(unsigned int version, sudo_conv_t conversation,
sudo_printf_t plugin_printf, char * const settings[],
char * const user_info[], int submit_optind, char * const submit_argv[],
char * const submit_envp[], char * const plugin_options[],
const char **errstr)
{
struct sudo_conf_debug_file_list debug_files =
TAILQ_HEAD_INITIALIZER(debug_files);
struct sudo_debug_file *debug_file;
const char *cp, *plugin_path = NULL;
unsigned char uuid[16];
char * const *cur;
mode_t oldmask;
int fd, ret = -1;
debug_decl(audit_open, SUDO_DEBUG_PLUGIN);
audit_conv = conversation;
audit_printf = plugin_printf;
/*
* Stash initial values.
*/
state.submit_optind = submit_optind;
state.settings = settings;
state.user_info = user_info;
state.submit_argv = submit_argv;
state.submit_envp = submit_envp;
/* Initialize the debug subsystem. */
for (cur = settings; (cp = *cur) != NULL; cur++) {
if (strncmp(cp, "debug_flags=", sizeof("debug_flags=") - 1) == 0) {
cp += sizeof("debug_flags=") - 1;
if (!sudo_debug_parse_flags(&debug_files, cp)) {
goto oom;
}
continue;
}
if (strncmp(cp, "plugin_path=", sizeof("plugin_path=") - 1) == 0) {
plugin_path = cp + sizeof("plugin_path=") - 1;
continue;
}
}
if (plugin_path != NULL && !TAILQ_EMPTY(&debug_files)) {
audit_debug_instance =
sudo_debug_register(plugin_path, NULL, NULL, &debug_files);
if (audit_debug_instance == SUDO_DEBUG_INSTANCE_ERROR) {
*errstr = U_("unable to initialize debugging");
goto bad;
}
}
/* Create a UUID for this command for use with audit records. */
sudo_uuid_create(uuid);
if (sudo_uuid_to_string(uuid, state.uuid_str, sizeof(state.uuid_str)) == NULL) {
*errstr = U_("unable to generate UUID");
goto bad;
}
/* Parse plugin_options to check for logfile option. */
if (plugin_options != NULL) {
for (cur = plugin_options; (cp = *cur) != NULL; cur++) {
if (strncmp(cp, "logfile=", sizeof("logfile=") - 1) == 0) {
state.logfile = strdup(cp + sizeof("logfile=") - 1);
if (state.logfile == NULL)
goto oom;
}
}
}
if (state.logfile == NULL) {
if (asprintf(&state.logfile, "%s/sudo_audit.json", _PATH_SUDO_LOGDIR) == -1)
goto oom;
}
/* open log file */
/* TODO: suport pipe */
oldmask = umask(S_IRWXG|S_IRWXO);
fd = open(state.logfile, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
(void)umask(oldmask);
if (fd == -1 || (state.log_fp = fdopen(fd, "w")) == NULL) {
*errstr = U_("unable to open audit system");
if (fd != -1)
close(fd);
goto bad;
}
ret = 1;
goto done;
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
*errstr = U_("unable to allocate memory");
bad:
if (state.log_fp != NULL) {
fclose(state.log_fp);
state.log_fp = NULL;
}
done:
while ((debug_file = TAILQ_FIRST(&debug_files))) {
TAILQ_REMOVE(&debug_files, debug_file, entries);
free(debug_file->debug_file);
free(debug_file->debug_flags);
free(debug_file);
}
debug_return_int(ret);
}
static bool
print_key_value(struct json_container *json, const char *str)
{
struct json_value json_value;
const char *cp, *errstr;
char name[256];
size_t len;
debug_decl(print_key_value, SUDO_DEBUG_PLUGIN);
if ((cp = strchr(str, '=')) == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"ignoring bad command info string \"%s\"", str);
debug_return_bool(false);
}
len = (size_t)(cp - str);
cp++;
/* Variable name currently limited to 256 chars */
if (len >= sizeof(name)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"ignoring long command info name \"%.*s\"", (int)len, str);
debug_return_bool(false);
}
memcpy(name, str, len);
name[len] = '\0';
/* Check for bool or number. */
json_value.type = JSON_NULL;
switch (*cp) {
case '+': case '-': case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7': case '8': case '9':
json_value.u.number = sudo_strtonum(cp, INT_MIN, INT_MAX, &errstr);
if (errstr == NULL)
json_value.type = JSON_NUMBER;
break;
case 't':
if (strcmp(cp, "true") == 0) {
json_value.type = JSON_BOOL;
json_value.u.boolean = true;
}
break;
case 'f':
if (strcmp(cp, "false") == 0) {
json_value.type = JSON_BOOL;
json_value.u.boolean = false;
}
break;
}
/* Default to string type. */
if (json_value.type == JSON_NULL) {
json_value.type = JSON_STRING;
json_value.u.string = cp;
}
sudo_json_add_value(json, name, &json_value);
debug_return_bool(true);
}
static void
print_array(struct json_container *json, const char *name, char * const * array)
{
struct json_value json_value;
debug_decl(print_array, SUDO_DEBUG_PLUGIN);
json_value.type = JSON_ARRAY;
json_value.u.array = array;
sudo_json_add_value(json, name, &json_value);
debug_return;
}
static bool
filter_key_value(const char *kv, char * const * filter)
{
char * const *cur;
const char *cp;
size_t namelen;
if (filter != NULL) {
namelen = strcspn(kv, "=");
for (cur = filter; (cp = *cur) != NULL; cur++) {
if (strncmp(kv, cp, namelen) == 0 && cp[namelen] == '\0')
return true;
}
}
return false;
}
static void
print_key_value_object(struct json_container *json, const char *name,
char * const * array, char * const * filter)
{
char * const *cur;
const char *cp;
bool empty = false;
debug_decl(print_key_value_object, SUDO_DEBUG_PLUGIN);
if (filter != NULL) {
/* Avoid printing an empty object if everything is filtered. */
empty = true;
for (cur = array; (cp = *cur) != NULL; cur++) {
if (!filter_key_value(cp, filter)) {
empty = false;
break;
}
}
}
if (!empty) {
sudo_json_open_object(json, name);
for (cur = array; (cp = *cur) != NULL; cur++) {
if (filter_key_value(cp, filter))
continue;
print_key_value(json, cp);
}
sudo_json_close_object(json);
}
debug_return;
}
static bool
print_timestamp(struct json_container *json, struct timespec *ts)
{
struct json_value json_value;
time_t secs = ts->tv_sec;
char timebuf[1024];
struct tm *tm;
debug_decl(print_timestamp, SUDO_DEBUG_PLUGIN);
if ((tm = gmtime(&secs)) == NULL)
debug_return_bool(false);
sudo_json_open_object(json, "timestamp");
json_value.type = JSON_NUMBER;
json_value.u.number = ts->tv_sec;
sudo_json_add_value(json, "seconds", &json_value);
json_value.type = JSON_NUMBER;
json_value.u.number = ts->tv_nsec;
sudo_json_add_value(json, "nanoseconds", &json_value);
strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", tm);
json_value.type = JSON_STRING;
json_value.u.string = timebuf;
sudo_json_add_value(json, "iso8601", &json_value);
strftime(timebuf, sizeof(timebuf), "%a %b %e %H:%M:%S %Z %Y", tm);
json_value.type = JSON_STRING;
json_value.u.string = timebuf;
sudo_json_add_value(json, "localtime", &json_value);
sudo_json_close_object(json);
debug_return_bool(true);
}
static int
audit_write_exit_record(int exit_status, int error)
{
struct timespec now;
struct json_container json;
struct json_value json_value;
int ret = -1;
debug_decl(audit_write_exit_record, SUDO_DEBUG_PLUGIN);
if (sudo_gettime_real(&now) == -1) {
sudo_warn(U_("unable to read the clock"));
goto done;
}
if (!sudo_lock_file(fileno(state.log_fp), SUDO_LOCK)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to lock %s", state.logfile);
goto done;
}
/* Note: assumes file ends in "\n}\n" */
fseeko(state.log_fp, 0, SEEK_END);
if (ftello(state.log_fp) == 0) {
/* New file */
putc('{', state.log_fp);
} else {
/* Continue file, overwrite the final "\n}\n" */
fseeko(state.log_fp, -3, SEEK_END);
putc(',', state.log_fp);
}
sudo_json_init(&json, state.log_fp, 4);
sudo_json_open_object(&json, "exit");
/* Write UUID */
json_value.type = JSON_STRING;
json_value.u.string = state.uuid_str;
sudo_json_add_value(&json, "uuid", &json_value);
/* Write time stamp */
if (!print_timestamp(&json, &now))
sudo_warnx(U_("unable to format timestamp"));
if (error != 0) {
/* Error executing command */
json_value.type = JSON_STRING;
json_value.u.string = strerror(error);
sudo_json_add_value(&json, "error", &json_value);
} else {
if (WIFEXITED(exit_status)) {
/* Command exited normally. */
json_value.type = JSON_NUMBER;
json_value.u.number = WEXITSTATUS(exit_status);
sudo_json_add_value(&json, "exit_value", &json_value);
} else if (WIFSIGNALED(exit_status)) {
/* Command killed by signal. */
char signame[SIG2STR_MAX];
int signo = WTERMSIG(exit_status);
if (signo <= 0 || sig2str(signo, signame) == -1) {
json_value.type = JSON_NUMBER;
json_value.u.number = signo;
sudo_json_add_value(&json, "signal", &json_value);
} else {
json_value.type = JSON_STRING;
json_value.u.string = signame;
sudo_json_add_value(&json, "signal", &json_value);
}
/* Core dump? */
json_value.type = JSON_BOOL;
json_value.u.boolean = WCOREDUMP(exit_status);
sudo_json_add_value(&json, "dumped_core", &json_value);
/* Exit value */
json_value.type = JSON_NUMBER;
json_value.u.number = WTERMSIG(exit_status) | 128;
sudo_json_add_value(&json, "exit_value", &json_value);
}
}
sudo_json_close_object(&json); /* close record */
fputs("\n}\n", state.log_fp); /* close JSON */
fflush(state.log_fp);
(void)sudo_lock_file(fileno(state.log_fp), SUDO_UNLOCK);
ret = true;
done:
debug_return_int(ret);
}
static void
audit_close(int exit_status, int error)
{
debug_decl(audit_close, SUDO_DEBUG_PLUGIN);
if (state.accepted) {
/* Write log entry for exit_status, or error. */
audit_write_exit_record(exit_status, error);
}
free(state.logfile);
if (state.log_fp != NULL)
fclose(state.log_fp);
debug_return;
}
static int
audit_write_record(const char *audit_str, const char *plugin_name,
unsigned int plugin_type, const char *reason, char * const command_info[],
char * const run_argv[], char * const run_envp[])
{
struct timespec now;
struct json_container json;
struct json_value json_value;
int ret = -1;
debug_decl(audit_write_record, SUDO_DEBUG_PLUGIN);
if (sudo_gettime_real(&now) == -1) {
sudo_warn(U_("unable to read the clock"));
goto done;
}
if (!sudo_lock_file(fileno(state.log_fp), SUDO_LOCK)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to lock %s", state.logfile);
goto done;
}
/* Note: assumes file ends in "\n}\n" */
fseeko(state.log_fp, 0, SEEK_END);
if (ftello(state.log_fp) == 0) {
/* New file */
putc('{', state.log_fp);
} else {
/* Continue file, overwrite the final "\n}\n" */
fseeko(state.log_fp, -3, SEEK_END);
putc(',', state.log_fp);
}
sudo_json_init(&json, state.log_fp, 4);
sudo_json_open_object(&json, audit_str);
json_value.type = JSON_STRING;
json_value.u.string = plugin_name;
sudo_json_add_value(&json, "plugin_name", &json_value);
switch (plugin_type) {
case SUDO_POLICY_PLUGIN:
json_value.u.string = "policy";
break;
case SUDO_IO_PLUGIN:
json_value.u.string = "io";
break;
case SUDO_AUDIT_PLUGIN:
json_value.u.string = "audit";
break;
default:
json_value.u.string = "unknown";
break;
}
json_value.type = JSON_STRING;
sudo_json_add_value(&json, "plugin_type", &json_value);
/* error and reject audit events usually contain a reason. */
if (reason != NULL) {
json_value.type = JSON_STRING;
json_value.u.string = reason;
sudo_json_add_value(&json, "reason", &json_value);
}
json_value.type = JSON_STRING;
json_value.u.string = state.uuid_str;
sudo_json_add_value(&json, "uuid", &json_value);
if (!print_timestamp(&json, &now))
sudo_warnx(U_("unable to format timestamp"));
/* Write key=value objects. */
print_key_value_object(&json, "options", state.settings, settings_filter);
print_key_value_object(&json, "user_info", state.user_info, NULL);
if (command_info != NULL)
print_key_value_object(&json, "command_info", command_info, NULL);
/* Write submit_optind before submit_argv */
json_value.type = JSON_NUMBER;
json_value.u.number = state.submit_optind;
sudo_json_add_value(&json, "submit_optind", &json_value);
print_array(&json, "submit_argv", state.submit_argv);
print_array(&json, "submit_envp", state.submit_envp);
if (run_argv != NULL)
print_array(&json, "run_argv", run_argv);
if (run_envp != NULL)
print_array(&json, "run_envp", run_envp);
sudo_json_close_object(&json); /* close audit_str */
fputs("\n}\n", state.log_fp); /* close JSON */
fflush(state.log_fp);
(void)sudo_lock_file(fileno(state.log_fp), SUDO_UNLOCK);
ret = true;
done:
debug_return_int(ret);
}
static int
audit_accept(const char *plugin_name, unsigned int plugin_type,
char * const command_info[], char * const run_argv[],
char * const run_envp[], const char **errstr)
{
int ret;
debug_decl(audit_accept, SUDO_DEBUG_PLUGIN);
state.accepted = true;
ret = audit_write_record("accept", plugin_name, plugin_type, NULL,
command_info, run_argv, run_envp);
debug_return_int(ret);
}
static int
audit_reject(const char *plugin_name, unsigned int plugin_type,
const char *reason, char * const command_info[], const char **errstr)
{
int ret;
debug_decl(audit_reject, SUDO_DEBUG_PLUGIN);
ret = audit_write_record("reject", plugin_name, plugin_type,
reason, command_info, NULL, NULL);
debug_return_int(ret);
}
static int
audit_error(const char *plugin_name, unsigned int plugin_type,
const char *reason, char * const command_info[], const char **errstr)
{
int ret;
debug_decl(audit_error, SUDO_DEBUG_PLUGIN);
ret = audit_write_record("error", plugin_name, plugin_type,
reason, command_info, NULL, NULL);
debug_return_int(ret);
}
static int
audit_show_version(int verbose)
{
debug_decl(audit_show_version, SUDO_DEBUG_PLUGIN);
audit_printf(SUDO_CONV_INFO_MSG, "JSON audit plugin version %s\n",
PACKAGE_VERSION);
debug_return_int(true);
}
__dso_public struct audit_plugin audit_json = {
SUDO_AUDIT_PLUGIN,
SUDO_API_VERSION,
audit_open,
audit_close,
audit_accept,
audit_reject,
audit_error,
audit_show_version,
NULL, /* register_hooks */
NULL /* deregister_hooks */
};

View File

@@ -0,0 +1 @@
audit_plugin