1
0
Fork 0

Adding upstream version 1.9.16p2.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
Daniel Baumann 2025-06-22 09:52:37 +02:00
parent ebbaee52bc
commit 182f151a13
Signed by: daniel.baumann
GPG key ID: BCC918A2ABD66424
1342 changed files with 621215 additions and 0 deletions

543
plugins/python/Makefile.in Normal file
View file

@ -0,0 +1,543 @@
#
# SPDX-License-Identifier: ISC
#
# Copyright (c) 2019-2024 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. ####
PACKAGE_TARNAME = @PACKAGE_TARNAME@
srcdir = @srcdir@
abs_srcdir = @abs_srcdir@
top_builddir = @top_builddir@
abs_top_builddir = @abs_top_builddir@
top_srcdir = @top_srcdir@
abs_top_srcdir = @abs_top_srcdir@
devdir = @devdir@
scriptdir = $(top_srcdir)/scripts
incdir = $(top_srcdir)/include
cross_compiling = @CROSS_COMPILING@
# Compiler & tools to use
CC = @CC@
CPP = @CPP@
LIBTOOL = @LIBTOOL@
EGREP = @EGREP@
SED = @SED@
AWK = @AWK@
# Our install program supports extra flags...
INSTALL = $(SHELL) $(scriptdir)/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)
LIBPYTHONPLUGIN = python_plugin.la
# C preprocessor flags
CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(top_srcdir) -DPLUGIN_DIR=\"$(plugindir)\" -DSRC_DIR=\"$(abs_srcdir)\" @CPPFLAGS@ @PYTHON_INCLUDE@
# Usually -O and/or -g
CFLAGS = @CFLAGS@
# Flags to pass to the link stage
LDFLAGS = @LDFLAGS@ @PYTHON_LIBS@
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
HARDENING_CFLAGS = @HARDENING_CFLAGS@
HARDENING_LDFLAGS = @HARDENING_LDFLAGS@
# cppcheck options, usually set in the top-level Makefile
CPPCHECK_OPTS = -q --enable=warning,performance,portability --suppress=constStatement --suppress=compareBoolExpressionWithInt --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@
adminconfdir = @adminconfdir@
libexecdir = @libexecdir@
datarootdir = @datarootdir@
localstatedir = @localstatedir@
plugindir = @plugindir@
docdir = @docdir@
exampledir = @exampledir@
# File mode and map file to use for shared libraries/objects
shlib_enable = @SHLIB_ENABLE@
shlib_mode = @SHLIB_MODE@
shlib_exp = $(srcdir)/python_plugin.exp
shlib_map = python_plugin.map
shlib_opt = python_plugin.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@
EXAMPLES = example_approval_plugin.py example_audit_plugin.py \
example_conversation.py example_debugging.py \
example_group_plugin.py example_io_plugin.py example_policy_plugin.py
OBJS = python_plugin_common.lo python_plugin_policy.lo python_plugin_io.lo \
python_plugin_group.lo pyhelpers.lo python_loghandler.lo \
python_convmessage.lo sudo_python_module.lo sudo_python_debug.lo \
python_baseplugin.lo python_plugin_audit.lo python_plugin_approval.lo
IOBJS = $(OBJS:.lo=.i)
POBJS = $(IOBJS:.i=.plog)
LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
VERSION = @PACKAGE_VERSION@
TEST_PROGS = check_python_examples
TEST_VERBOSE =
CHECK_PYTHON_EXAMPLES_OBJS = check_python_examples.o iohelpers.o testhelpers.o pyhelpers.o sudo_python_debug.o
all: python_plugin.la
depend:
$(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
--builddir=$(abs_top_builddir) plugins/python/Makefile.in
cd $(top_builddir) && ./config.status --file plugins/python/Makefile
Makefile: $(srcdir)/Makefile.in
cd $(top_builddir) && ./config.status --file plugins/python/Makefile
.SUFFIXES: .c .h .i .lo .plog .o
.c.o:
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $<
.c.lo:
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $<
.c.i:
$(CPP) $(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) > $@
python_plugin.la: $(OBJS) $(LT_LIBS) @LT_LDDEP@
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LDFLAGS) $(ASAN_LDFLAGS) $(HARDENING_LDFLAGS) $(LT_LDFLAGS) -o $@ $(OBJS) $(LIBS) -module -avoid-version -rpath $(plugindir) -shrext .so
pre-install:
install: install-plugin install-doc
install-dirs:
$(SHELL) $(scriptdir)/mkinstalldirs $(DESTDIR)$(plugindir) $(DESTDIR)$(exampledir)
install-binaries:
install-includes:
install-doc: install-dirs
for f in $(EXAMPLES); do $(INSTALL) $(INSTALL_OWNER) -m 0644 $(srcdir)/$$f $(DESTDIR)$(exampledir); done
install-plugin: install-dirs python_plugin.la
if [ X"$(shlib_enable)" = X"yes" ]; then \
INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --mode=install $(INSTALL) $(INSTALL_OWNER) -m $(shlib_mode) python_plugin.la $(DESTDIR)$(plugindir); \
fi
install-fuzzer:
uninstall:
-$(LIBTOOL) $(LTFLAGS) --mode=uninstall rm -f $(DESTDIR)$(plugindir)/python_plugin.la
-test -z "$(INSTALL_BACKUP)" || \
rm -f $(DESTDIR)$(plugindir)/python_plugin.so$(INSTALL_BACKUP)
splint:
splint $(SPLINT_OPTS) -I$(incdir) -I$(top_builddir) -I$(top_srcdir) $(srcdir)/*.c
cppcheck:
cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) -I$(top_srcdir) $(srcdir)/*.c
pvs-log-files: $(POBJS)
pvs-studio: $(POBJS)
plog-converter $(PVS_LOG_OPTS) $(POBJS)
clean:
-$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f *.lo *.o *.la
-rm -f *.i *.plog stamp-* core *.core core.* $(TEST_PROGS)
mostlyclean: clean
distclean: clean
-rm -rf Makefile .libs $(shlib_map) $(shlib_opt)
clobber: distclean
realclean: distclean
rm -f TAGS tags
cleandir: realclean
fuzz:
check-fuzzer:
check: $(TEST_PROGS) check-fuzzer
@if test X"$(cross_compiling)" != X"yes"; then \
l=`locale -a 2>&1 | $(EGREP) -i '^C\.UTF-?8$$' | $(SED) 1q` || true; \
test -n "$$l" || l="C"; \
LC_ALL="$$l"; export LC_ALL; \
unset LANG || LANG=; \
unset LANGUAGE || LANGUAGE=; \
MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \
MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \
LSAN_OPTIONS=suppressions=$(srcdir)/lsan_suppr.txt \
./check_python_examples $(TEST_VERBOSE) \
".libs/python_plugin.so"; \
fi
check-verbose:
exec $(MAKE) $(MFLAGS) TEST_VERBOSE=-v FUZZ_VERBOSE=-verbosity=1 check
update_test_data: $(TEST_PROGS)
@if test X"$(cross_compiling)" != X"yes"; then \
UPDATE_TESTDATA=1 ./check_python_examples ".libs/python_plugin.so"; \
fi
check_python_examples: $(CHECK_PYTHON_EXAMPLES_OBJS) $(LIBPYTHONPLUGIN)
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_PYTHON_EXAMPLES_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(LIBS)
.PHONY: clean mostlyclean distclean cleandir clobber realclean
# Autogenerated dependencies, do not modify
check_python_examples.o: $(srcdir)/regress/check_python_examples.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_dso.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/regress/iohelpers.h \
$(srcdir)/regress/testhelpers.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/check_python_examples.c
check_python_examples.i: $(srcdir)/regress/check_python_examples.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_dso.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/regress/iohelpers.h \
$(srcdir)/regress/testhelpers.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/regress/check_python_examples.c > $@
check_python_examples.plog: check_python_examples.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/check_python_examples.c --i-file check_python_examples.i --output-file $@
iohelpers.o: $(srcdir)/regress/iohelpers.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_plugin.h $(srcdir)/regress/iohelpers.h \
$(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/iohelpers.c
iohelpers.i: $(srcdir)/regress/iohelpers.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_plugin.h $(srcdir)/regress/iohelpers.h \
$(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/regress/iohelpers.c > $@
iohelpers.plog: iohelpers.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/iohelpers.c --i-file iohelpers.i --output-file $@
pyhelpers.lo: $(srcdir)/pyhelpers.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/pyhelpers.c
pyhelpers.i: $(srcdir)/pyhelpers.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(CPP) $(CPPFLAGS) $(srcdir)/pyhelpers.c > $@
pyhelpers.plog: pyhelpers.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/pyhelpers.c --i-file pyhelpers.i --output-file $@
pyhelpers.o: $(srcdir)/pyhelpers.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/pyhelpers.c
python_baseplugin.lo: $(srcdir)/python_baseplugin.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_baseplugin.c
python_baseplugin.i: $(srcdir)/python_baseplugin.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_baseplugin.c > $@
python_baseplugin.plog: python_baseplugin.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_baseplugin.c --i-file python_baseplugin.i --output-file $@
python_convmessage.lo: $(srcdir)/python_convmessage.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_convmessage.c
python_convmessage.i: $(srcdir)/python_convmessage.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_convmessage.c > $@
python_convmessage.plog: python_convmessage.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_convmessage.c --i-file python_convmessage.i --output-file $@
python_loghandler.lo: $(srcdir)/python_loghandler.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_loghandler.c
python_loghandler.i: $(srcdir)/python_loghandler.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_loghandler.c > $@
python_loghandler.plog: python_loghandler.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_loghandler.c --i-file python_loghandler.i --output-file $@
python_plugin_approval.lo: $(srcdir)/python_plugin_approval.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_approval_multi.inc \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h \
$(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_plugin_approval.c
python_plugin_approval.i: $(srcdir)/python_plugin_approval.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_approval_multi.inc \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h \
$(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_plugin_approval.c > $@
python_plugin_approval.plog: python_plugin_approval.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_plugin_approval.c --i-file python_plugin_approval.i --output-file $@
python_plugin_audit.lo: $(srcdir)/python_plugin_audit.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_audit_multi.inc \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_plugin_audit.c
python_plugin_audit.i: $(srcdir)/python_plugin_audit.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_audit_multi.inc \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_plugin_audit.c > $@
python_plugin_audit.plog: python_plugin_audit.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_plugin_audit.c --i-file python_plugin_audit.i --output-file $@
python_plugin_common.lo: $(srcdir)/python_plugin_common.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_plugin_common.c
python_plugin_common.i: $(srcdir)/python_plugin_common.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_plugin_common.c > $@
python_plugin_common.plog: python_plugin_common.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_plugin_common.c --i-file python_plugin_common.i --output-file $@
python_plugin_group.lo: $(srcdir)/python_plugin_group.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_plugin_group.c
python_plugin_group.i: $(srcdir)/python_plugin_group.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_plugin_group.c > $@
python_plugin_group.plog: python_plugin_group.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_plugin_group.c --i-file python_plugin_group.i --output-file $@
python_plugin_io.lo: $(srcdir)/python_plugin_io.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_common.h \
$(srcdir)/python_plugin_io_multi.inc \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_plugin_io.c
python_plugin_io.i: $(srcdir)/python_plugin_io.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(srcdir)/pyhelpers.h $(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_common.h \
$(srcdir)/python_plugin_io_multi.inc \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_plugin_io.c > $@
python_plugin_io.plog: python_plugin_io.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_plugin_io.c --i-file python_plugin_io.i --output-file $@
python_plugin_policy.lo: $(srcdir)/python_plugin_policy.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/python_plugin_policy.c
python_plugin_policy.i: $(srcdir)/python_plugin_policy.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/python_plugin_common.h \
$(srcdir)/sudo_python_debug.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/python_plugin_policy.c > $@
python_plugin_policy.plog: python_plugin_policy.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_plugin_policy.c --i-file python_plugin_policy.i --output-file $@
sudo_python_debug.lo: $(srcdir)/sudo_python_debug.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/sudo_python_debug.h \
$(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sudo_python_debug.c
sudo_python_debug.i: $(srcdir)/sudo_python_debug.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/sudo_python_debug.h \
$(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/sudo_python_debug.c > $@
sudo_python_debug.plog: sudo_python_debug.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sudo_python_debug.c --i-file sudo_python_debug.i --output-file $@
sudo_python_debug.o: $(srcdir)/sudo_python_debug.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/sudo_python_debug.h \
$(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sudo_python_debug.c
sudo_python_module.lo: $(srcdir)/sudo_python_module.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sudo_python_module.c
sudo_python_module.i: $(srcdir)/sudo_python_module.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h \
$(srcdir)/sudo_python_debug.h \
$(srcdir)/sudo_python_module.h $(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/sudo_python_module.c > $@
sudo_python_module.plog: sudo_python_module.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sudo_python_module.c --i-file sudo_python_module.i --output-file $@
testhelpers.o: $(srcdir)/regress/testhelpers.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h $(srcdir)/regress/iohelpers.h \
$(srcdir)/regress/testhelpers.h $(srcdir)/sudo_python_debug.h \
$(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/regress/testhelpers.c
testhelpers.i: $(srcdir)/regress/testhelpers.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(srcdir)/pyhelpers.h \
$(srcdir)/pyhelpers_cpychecker.h $(srcdir)/regress/iohelpers.h \
$(srcdir)/regress/testhelpers.h $(srcdir)/sudo_python_debug.h \
$(top_builddir)/config.h
$(CPP) $(CPPFLAGS) $(srcdir)/regress/testhelpers.c > $@
testhelpers.plog: testhelpers.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/testhelpers.c --i-file testhelpers.i --output-file $@

View file

@ -0,0 +1,18 @@
import sudo
from datetime import datetime
class BusinessHoursApprovalPlugin(sudo.Plugin):
def check(self, command_info: tuple, run_argv: tuple,
run_env: tuple) -> int:
error_msg = ""
now = datetime.now()
if now.weekday() >= 5:
error_msg = "That is not allowed on the weekend!"
if now.hour < 8 or now.hour > 17:
error_msg = "That is not allowed outside the business hours!"
if error_msg:
sudo.log_info(error_msg)
raise sudo.PluginReject(error_msg)

View file

@ -0,0 +1,84 @@
import sudo
import os
VERSION = 1.0
class SudoAuditPlugin(sudo.Plugin):
def __init__(self, plugin_options, user_info, **kwargs):
# For loading multiple times, an optional "Id" can be specified
# as argument to identify the log lines
plugin_id = sudo.options_as_dict(plugin_options).get("Id", "")
self._log_line_prefix = "(AUDIT{}) ".format(plugin_id)
user_info_dict = sudo.options_as_dict(user_info)
user = user_info_dict.get("user", "???")
uid = user_info_dict.get("uid", "???")
self._log("-- Started by user {} ({}) --".format(user, uid))
def __del__(self):
self._log("-- Finished --")
def open(self, submit_optind: int, submit_argv: tuple) -> int:
# To cut out the sudo options, use "submit_optind":
program_args = submit_argv[submit_optind:]
if program_args:
self._log("Requested command: " + " ".join(program_args))
def accept(self, plugin_name, plugin_type,
command_info, run_argv, run_envp) -> int:
info = sudo.options_as_dict(command_info)
cmd = list(run_argv)
cmd[0] = info.get("command")
self._log("Accepted command: {}".format(" ".join(cmd)))
self._log(" By the plugin: {} (type={})".format(
plugin_name, self.__plugin_type_str(plugin_type)))
self._log(" Environment: " + " ".join(run_envp))
def reject(self, plugin_name, plugin_type, audit_msg, command_info) -> int:
self._log("Rejected by plugin {} (type={}): {}".format(
plugin_name, self.__plugin_type_str(plugin_type), audit_msg))
def error(self, plugin_name, plugin_type, audit_msg, command_info) -> int:
self._log("Plugin {} (type={}) got an error: {}".format(
plugin_name, self.__plugin_type_str(plugin_type), audit_msg))
def close(self, status_kind: int, status: int) -> None:
if status_kind == sudo.EXIT_REASON.NO_STATUS:
self._log("The command was not executed")
elif status_kind == sudo.EXIT_REASON.WAIT_STATUS:
if os.WIFEXITED(status):
self._log("Command returned with exit code "
"{}".format(os.WEXITSTATUS(status)))
elif os.WIFSIGNALED(status):
self._log("Command exited due to signal "
"{}".format(os.WTERMSIG(status)))
else:
raise sudo.PluginError("Failed to understand wait exit status")
elif status_kind == sudo.EXIT_REASON.EXEC_ERROR:
self._log("Sudo has failed to execute the command, "
"execve returned {}".format(status))
elif status_kind == sudo.EXIT_REASON.SUDO_ERROR:
self._log("Sudo has run into an error: {}".format(status))
else:
raise Exception("Command returned unknown status kind {}".format(
status_kind))
def show_version(self, is_verbose: bool) -> int:
version_str = " (version=1.0)" if is_verbose else ""
sudo.log_info("Python Example Audit Plugin" + version_str)
def _log(self, string):
# For the example, we just log to output (this could be a file)
sudo.log_info(self._log_line_prefix, string)
@staticmethod
def __plugin_type_str(plugin_type):
return sudo.PLUGIN_TYPE(plugin_type).name

View file

@ -0,0 +1,98 @@
import sudo
import signal
from os import path
class ReasonLoggerIOPlugin(sudo.Plugin):
"""
An example sudo plugin demonstrating how to use the sudo conversation API.
From the python plugin, you can ask something from the user using the
"sudo.conv" function. It expects one or more "sudo.ConvMessage" instances
which specifies how the interaction has to look like.
sudo.ConvMessage has the following fields (see help(sudo.ConvMessage)):
msg_type: int Specifies the type of the conversation.
See sudo.CONV.* constants below.
timeout: int The maximum amount of time for the conversation
in seconds. After the timeout exceeds, the "sudo.conv"
function will raise sudo.ConversationInterrupted
exception.
msg: str The message to display for the user.
To specify the conversion type you can use the following constants:
sudo.CONV.PROMPT_ECHO_OFF
sudo.CONV.PROMPT_ECHO_ON
sudo.CONV.ERROR_MSG
sudo.CONV.INFO_MSG
sudo.CONV.PROMPT_MASK
sudo.CONV.PROMPT_ECHO_OK
sudo.CONV.PREFER_TTY
"""
def open(self, argv, command_info):
try:
conv_timeout = 120 # in seconds
sudo.log_info("Please provide your reason "
"for executing {}".format(argv))
# We ask two questions, the second is not visible on screen,
# so the user can hide a hidden message in case of criminals are
# forcing him for running the command.
# You can either specify the arguments in strict order (timeout
# being optional), or use named arguments.
message1 = sudo.ConvMessage(sudo.CONV.PROMPT_ECHO_ON,
"Reason: ",
conv_timeout)
message2 = sudo.ConvMessage(msg="Secret reason: ",
timeout=conv_timeout,
msg_type=sudo.CONV.PROMPT_MASK)
reply1, reply2 = sudo.conv(message1, message2,
on_suspend=self.on_conversation_suspend,
on_resume=self.on_conversation_resume)
with open(self._log_file_path(), "a") as file:
print("Executed", ' '.join(argv), file=file)
print("Reason:", reply1, file=file)
print("Hidden reason:", reply2, file=file)
except sudo.ConversationInterrupted:
sudo.log_error("You did not answer in time")
return sudo.RC.REJECT
def on_conversation_suspend(self, signum):
# This is just an example of how to do something on conversation
# suspend. You can skip specifying 'on_suspend' argument if there
# is no need
sudo.log_info("conversation suspend: signal",
self._signal_name(signum))
def on_conversation_resume(self, signum):
# This is just an example of how to do something on conversation
# resume. You can skip specifying 'on_resume' argument if there
# is no need
sudo.log_info("conversation resume: signal was",
self._signal_name(signum))
# helper functions:
if hasattr(signal, "Signals"):
@classmethod
def _signal_name(cls, signum: int):
try:
return signal.Signals(signum).name
except Exception:
return "{}".format(signum)
else:
@classmethod
def _signal_name(cls, signum: int):
for n, v in sorted(signal.__dict__.items()):
if v != signum:
continue
if n.startswith("SIG") and not n.startswith("SIG_"):
return n
return "{}".format(signum)
def _log_file_path(self):
options_dict = sudo.options_as_dict(self.plugin_options)
log_path = options_dict.get("LogPath", "/tmp")
return path.join(log_path, "sudo_reasons.txt")

View file

@ -0,0 +1,85 @@
import sudo
import logging
class DebugDemoPlugin(sudo.Plugin):
"""
An example sudo plugin demonstrating the debugging capabilities.
You can install it as an extra IO plugin for example by adding the
following line to sudo.conf:
Plugin python_io python_plugin.so \
ModulePath=<path>/example_debugging.py \
ClassName=DebugDemoPlugin
To see the plugin's debug output, use the following line in sudo.conf:
Debug python_plugin.so \
/var/log/sudo_python_debug plugin@trace,c_calls@trace
^ ^-- the options for the logging
^----- the output will be placed here
The options for the logging is in format of multiple "subsystem@level"
separated by commas (",").
The most interesting subsystems are:
plugin Shows each call of sudo.debug API in the log
- py_calls Logs whenever a C function calls into the python module.
(For example calling this __init__ function.)
c_calls Logs whenever python calls into a C sudo API function
You can also specify "all" as subsystem name to get the debug messages of
all subsystems.
Other subsystems available:
internal logs internal functions of the python language wrapper
sudo_cb logs when sudo calls into its plugin API
load logs python plugin loading / unloading
Log levels
crit sudo.DEBUG.CRIT --> only critical messages
err sudo.DEBUG.ERROR
warn sudo.DEBUG.WARN
notice sudo.DEBUG.NOTICE
diag sudo.DEBUG.DIAG
info sudo.DEBUG.INFO
trace sudo.DEBUG.TRACE
debug sudo.DEBUG.DEBUG --> very extreme verbose debugging
See the sudo.conf manual for more details ("man sudo.conf").
"""
def __init__(self, plugin_options, **kwargs):
# Specify: "py_calls@info" debug option to show the call to this
# constructor and the arguments passed in
# Specifying "plugin@err" debug option will show this message
# (or any more verbose level)
sudo.debug(sudo.DEBUG.ERROR, "My demo purpose plugin shows "
"this ERROR level debug message")
# Specifying "plugin@info" debug option will show this message
# (or any more verbose level)
sudo.debug(sudo.DEBUG.INFO, "My demo purpose plugin shows "
"this INFO level debug message")
# You can also use python log system, because sudo sets its log handler
# on the root logger.
# Note that the level of python logging is separate than the one set in
# sudo.conf. If using the python logger, each will have effect.
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.error("Python log system shows this ERROR level debug message")
logger.info("Python log system shows this INFO level debug message")
# If you raise the level to info or below, the call of the debug
# will also be logged.
# An example output you will see in the debug log file:
# Dec 5 15:19:19 sudo[123040] __init__ @ /.../example_debugging.py:54 debugs:
# Dec 5 15:19:19 sudo[123040] My demo purpose plugin shows this ERROR level debug message
# Specify: "c_calls@diag" debug option to show this call and its
# arguments. If you specify info debug level instead ("c_calls@info"),
# you will also see the python function and line from which you called
# the 'options_as_dict' function.
self.plugin_options = sudo.options_as_dict(plugin_options)

View file

@ -0,0 +1,45 @@
import sudo
class SudoGroupPlugin(sudo.Plugin):
"""Example sudo input/output plugin
Demonstrates how to use the sudo group plugin API. Typing annotations are
just here for the help on the syntax (requires python >= 3.5).
On detailed description of the functions refer to sudo_plugin manual (man
sudo_plugin).
Most functions can express error or reject through their "int" return value
as documented in the manual. The sudo module also has constants for these:
sudo.RC.ACCEPT / sudo.RC.OK 1
sudo.RC.REJECT 0
sudo.RC.ERROR -1
sudo.RC.USAGE_ERROR -2
If the plugin encounters an error, instead of just returning sudo.RC.ERROR
result code it can also add a message describing the problem.
This can be done by raising the special exception:
raise sudo.PluginError("Message")
This added message will be used by the audit plugins.
If the function returns "None" (for example does not call return), it will
be considered sudo.RC.OK. If an exception other than sudo.PluginError is
raised, its backtrace will be shown to the user and the plugin function
returns sudo.RC.ERROR. If that is not acceptable, catch it.
"""
# -- Plugin API functions --
def query(self, user: str, group: str, user_pwd: tuple):
"""Query if user is part of the specified group.
Beware that user_pwd can be None if user is not present in the password
database. Otherwise it is a tuple convertible to pwd.struct_passwd.
"""
hardcoded_user_groups = {
"testgroup": ["testuser1", "testuser2"],
"mygroup": ["test"]
}
group_has_user = user in hardcoded_user_groups.get(group, [])
return sudo.RC.ACCEPT if group_has_user else sudo.RC.REJECT

View file

@ -0,0 +1,153 @@
import sudo
from os import path
import errno
import signal
import sys
import json
VERSION = 1.0
class SudoIOPlugin(sudo.Plugin):
"""Example sudo input/output plugin
Demonstrates how to use the sudo IO plugin API. All functions are added as
an example on their syntax, but note that all of them are optional.
On detailed description of the functions refer to sudo_plugin manual (man
sudo_plugin).
Most functions can express error or reject through their "int" return value
as documented in the manual. The sudo module also has constants for these:
sudo.RC.ACCEPT / sudo.RC.OK 1
sudo.RC.REJECT 0
sudo.RC.ERROR -1
sudo.RC.USAGE_ERROR -2
If the plugin encounters an error, instead of just returning sudo.RC.ERROR
result code it can also add a message describing the problem.
This can be done by raising the special exception:
raise sudo.PluginError("Message")
This added message will be used by the audit plugins.
If the function returns "None" (for example does not call return), it will
be considered sudo.RC.OK. If an exception other than sudo.PluginError is
raised, its backtrace will be shown to the user and the plugin function
returns sudo.RC.ERROR. If that is not acceptable, catch it.
"""
# -- Plugin API functions --
def __init__(self, version: str,
plugin_options: tuple, **kwargs):
"""The constructor of the IO plugin.
Other variables you can currently use as arguments are:
user_env: tuple
settings: tuple
user_info: tuple
For their detailed description, see the open() call of the C plugin API
in the sudo manual ("man sudo").
"""
if not version.startswith("1."):
raise sudo.SudoException(
"This plugin is not compatible with python plugin"
"API version {}".format(version))
# convert tuple of "key=value"s to dict
plugin_options = sudo.options_as_dict(plugin_options)
log_path = plugin_options.get("LogPath", "/tmp")
self._open_log_file(path.join(log_path, "sudo.log"))
self._log("", "-- Plugin STARTED --")
def __del__(self):
if hasattr(self, "_log_file"):
self._log("", "-- Plugin DESTROYED --")
self._log_file.close()
def open(self, argv: tuple,
command_info: tuple) -> int:
"""Receives the command the user wishes to run.
This function works the same as open() call of the C IO plugin API (see
sudo manual), except that:
- It only gets called before the user would execute some command (and
not for a version query for example).
- Other arguments of the C open() call are received through the
constructor.
"""
self._log("EXEC", " ".join(argv))
self._log("EXEC info", json.dumps(command_info, indent=4))
return sudo.RC.ACCEPT
def log_ttyout(self, buf: str) -> int:
return self._log("TTY OUT", buf.strip())
def log_ttyin(self, buf: str) -> int:
return self._log("TTY IN", buf.strip())
def log_stdin(self, buf: str) -> int:
return self._log("STD IN", buf.strip())
def log_stdout(self, buf: str) -> int:
return self._log("STD OUT", buf.strip())
def log_stderr(self, buf: str) -> int:
return self._log("STD ERR", buf.strip())
def change_winsize(self, line: int, cols: int) -> int:
self._log("WINSIZE", "{}x{}".format(line, cols))
def log_suspend(self, signo: int) -> int:
signal_description = self._signal_name(signo)
self._log("SUSPEND", signal_description)
def show_version(self, is_verbose: int) -> int:
sudo.log_info("Python Example IO Plugin version: {}".format(VERSION))
if is_verbose:
sudo.log_info("Python interpreter version:", sys.version)
def close(self, exit_status: int, error: int) -> None:
"""Called when a command execution finished.
Works the same as close() from C API (see sudo_plugin manual), except
that it only gets called if there was a command execution trial (open()
returned with sudo.RC.ACCEPT).
"""
if error == 0:
self._log("CLOSE", "Command returned {}".format(exit_status))
else:
error_name = errno.errorcode.get(error, "???")
self._log("CLOSE", "Failed to execute, execve returned {} ({})"
.format(error, error_name))
# -- Helper functions --
def _open_log_file(self, log_path):
sudo.log_info("Example sudo python plugin will log to", log_path)
self._log_file = open(log_path, "a")
def _log(self, type, message):
print(type, message, file=self._log_file)
return sudo.RC.ACCEPT
if hasattr(signal, "Signals"):
def _signal_name(cls, signo: int):
try:
return signal.Signals(signo).name
except ValueError:
return "signal {}".format(signo)
else:
def _signal_name(cls, signo: int):
for n, v in sorted(signal.__dict__.items()):
if v != signo:
continue;
if n.startswith("SIG") and not n.startswith("SIG_"):
return n
return "signal {}".format(signo)

View file

@ -0,0 +1,172 @@
import sudo
import errno
import sys
import os
import pwd
import grp
import shutil
VERSION = 1.0
class SudoPolicyPlugin(sudo.Plugin):
"""Example sudo policy plugin
Demonstrates how to use the sudo policy plugin API. All functions are added
as an example on their syntax, but note that most of them are optional
(except check_policy).
On detailed description of the functions refer to sudo_plugin manual (man
sudo_plugin).
Most functions can express error or reject through their "int" return value
as documented in the manual. The sudo module also has constants for these:
sudo.RC.ACCEPT / sudo.RC.OK 1
sudo.RC.REJECT 0
sudo.RC.ERROR -1
sudo.RC.USAGE_ERROR -2
If the plugin encounters an error, instead of just returning sudo.RC.ERROR
result code it can also add a message describing the problem.
This can be done by raising the special exception:
raise sudo.PluginError("Message")
This added message will be used by the audit plugins.
If the function returns "None" (for example does not call return), it will
be considered sudo.RC.OK. If an exception other than sudo.PluginError is
raised, its backtrace will be shown to the user and the plugin function
returns sudo.RC.ERROR. If that is not acceptable, catch it.
"""
_allowed_commands = ("id", "whoami")
_safe_password = "12345"
# -- Plugin API functions --
def __init__(self, user_env: tuple, settings: tuple,
version: str, **kwargs):
"""The constructor matches the C sudo plugin API open() call
Other variables you can currently use as arguments are:
user_info: tuple
plugin_options: tuple
For their detailed description, see the open() call of the C plugin API
in the sudo manual ("man sudo").
"""
if not version.startswith("1."):
raise sudo.PluginError(
"This plugin is not compatible with python plugin"
"API version {}".format(version))
self.user_env = sudo.options_as_dict(user_env)
self.settings = sudo.options_as_dict(settings)
def check_policy(self, argv: tuple, env_add: tuple):
cmd = argv[0]
# Example for a simple reject:
if not self._is_command_allowed(cmd):
sudo.log_error("You are not allowed to run this command!")
return sudo.RC.REJECT
raise sudo.PluginError("You are not allowed to run this command!")
# The environment the command will be executed with (we allow any here)
user_env_out = sudo.options_from_dict(self.user_env) + env_add
command_info_out = sudo.options_from_dict({
"command": self._find_on_path(cmd), # Absolute path of command
"runas_uid": self._runas_uid(), # The user id
"runas_gid": self._runas_gid(), # The group id
})
return (sudo.RC.ACCEPT, command_info_out, argv, user_env_out)
def init_session(self, user_pwd: tuple, user_env: tuple):
"""Perform session setup
Beware that user_pwd can be None if user is not present in the password
database. Otherwise it is a tuple convertible to pwd.struct_passwd.
"""
# conversion example:
user_pwd = pwd.struct_passwd(user_pwd) if user_pwd else None
# This is how you change the user_env:
return (sudo.RC.OK, user_env + ("PLUGIN_EXAMPLE_ENV=1",))
# If you do not want to change user_env, you can just return (or None):
# return sudo.RC.OK
def list(self, argv: tuple, is_verbose: int, user: str):
cmd = argv[0] if argv else None
as_user_text = "as user '{}'".format(user) if user else ""
if cmd:
allowed_text = "" if self._is_command_allowed(cmd) else "NOT "
sudo.log_info("You are {}allowed to execute command '{}'{}"
.format(allowed_text, cmd, as_user_text))
if not cmd or is_verbose:
sudo.log_info("Only the following commands are allowed:",
", ".join(self._allowed_commands), as_user_text)
def validate(self):
pass # we have no cache
def invalidate(self, remove: int):
pass # we have no cache
def show_version(self, is_verbose: int):
sudo.log_info("Python Example Policy Plugin "
"version: {}".format(VERSION))
if is_verbose:
sudo.log_info("Python interpreter version:", sys.version)
def close(self, exit_status: int, error: int) -> None:
if error == 0:
sudo.log_info("The command returned with exit_status {}".format(
exit_status))
else:
error_name = errno.errorcode.get(error, "???")
sudo.log_error(
"Failed to execute command, execve syscall returned "
"{} ({})".format(error, error_name))
# -- Helper functions --
def _is_command_allowed(self, cmd):
return os.path.basename(cmd) in self._allowed_commands
def _find_on_path(self, cmd):
if os.path.isabs(cmd):
return cmd
path = self.user_env.get("PATH", "/usr/bin:/bin")
absolute_cmd = shutil.which(cmd, path=path)
if not absolute_cmd:
raise sudo.PluginError("Can not find cmd '{}' on PATH".format(cmd))
return absolute_cmd
def _runas_pwd(self):
runas_user = self.settings.get("runas_user") or "root"
try:
return pwd.getpwnam(runas_user)
except KeyError:
raise sudo.PluginError("Could not find user "
"'{}'".format(runas_user))
def _runas_uid(self):
return self._runas_pwd().pw_uid
def _runas_gid(self):
runas_group = self.settings.get("runas_group")
if runas_group is None:
return self._runas_pwd().pw_gid
try:
return grp.getgrnam(runas_group).gr_gid
except KeyError:
raise sudo.PluginError(
"Could not find group '{}'".format(runas_group))

View file

@ -0,0 +1 @@
leak:libpython

587
plugins/python/pyhelpers.c Normal file
View file

@ -0,0 +1,587 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "pyhelpers.h"
#include <pwd.h>
#include <signal.h>
#include <pathnames.h>
static int
_sudo_printf_default(int msg_type, const char * restrict fmt, ...)
{
FILE *fp = stdout;
FILE *ttyfp = NULL;
va_list ap;
int len;
if (ISSET(msg_type, SUDO_CONV_PREFER_TTY)) {
/* Try writing to /dev/tty first. */
ttyfp = fopen(_PATH_TTY, "w");
}
switch (msg_type & 0xff) {
case SUDO_CONV_ERROR_MSG:
fp = stderr;
FALLTHROUGH;
case SUDO_CONV_INFO_MSG:
va_start(ap, fmt);
len = vfprintf(ttyfp ? ttyfp : fp, fmt, ap);
va_end(ap);
break;
default:
len = -1;
errno = EINVAL;
break;
}
if (ttyfp != NULL)
fclose(ttyfp);
return len;
}
struct PythonContext py_ctx = {
.sudo_log = &_sudo_printf_default,
};
char *
py_join_str_list(PyObject *py_str_list, const char *separator)
{
debug_decl(py_join_str_list, PYTHON_DEBUG_INTERNAL);
char *result = NULL;
PyObject *py_separator = NULL;
PyObject *py_str = NULL;
py_separator = PyUnicode_FromString(separator);
if (py_separator == NULL)
goto cleanup;
py_str = PyObject_CallMethod(py_separator, "join", "(O)", py_str_list);
if (py_str == NULL) {
goto cleanup;
}
const char *str = PyUnicode_AsUTF8(py_str);
if (str != NULL) {
result = strdup(str);
}
cleanup:
Py_XDECREF(py_str);
Py_XDECREF(py_separator);
debug_return_str(result);
}
static char *
py_create_traceback_string(PyObject *py_traceback)
{
debug_decl(py_create_traceback_string, PYTHON_DEBUG_INTERNAL);
if (py_traceback == NULL)
debug_return_str(strdup(""));
char* traceback = NULL;
PyObject *py_traceback_module = PyImport_ImportModule("traceback");
if (py_traceback_module == NULL) {
PyErr_Clear(); // do not care, we just won't show backtrace
} else {
PyObject *py_traceback_str_list = PyObject_CallMethod(py_traceback_module, "format_tb", "(O)", py_traceback);
if (py_traceback_str_list != NULL) {
traceback = py_join_str_list(py_traceback_str_list, "");
Py_DECREF(py_traceback_str_list);
}
Py_CLEAR(py_traceback_module);
}
debug_return_str(traceback ? traceback : strdup(""));
}
void
py_log_last_error(const char *context_message)
{
debug_decl(py_log_last_error, PYTHON_DEBUG_INTERNAL);
if (!PyErr_Occurred()) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s\n", context_message);
debug_return;
}
PyObject *py_type = NULL, *py_message = NULL, *py_traceback = NULL;
PyErr_Fetch(&py_type, &py_message, &py_traceback);
char *message = py_message ? py_create_string_rep(py_message) : NULL;
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s%s%s\n",
context_message ? context_message : "",
context_message && *context_message ? ": " : "",
message ? message : "(NULL)");
free(message);
if (py_traceback != NULL) {
char *traceback = py_create_traceback_string(py_traceback);
py_sudo_log(SUDO_CONV_INFO_MSG, "Traceback:\n%s\n", traceback);
free(traceback);
}
Py_XDECREF(py_type);
Py_XDECREF(py_message);
Py_XDECREF(py_traceback);
debug_return;
}
PyObject *
py_str_array_to_tuple_with_count(Py_ssize_t count, char * const strings[])
{
debug_decl(py_str_array_to_tuple_with_count, PYTHON_DEBUG_INTERNAL);
PyObject *py_argv = PyTuple_New(count);
if (py_argv == NULL)
debug_return_ptr(NULL);
for (int i = 0; i < count; ++i) {
PyObject *py_arg = PyUnicode_FromString(strings[i]);
if (py_arg == NULL || PyTuple_SetItem(py_argv, i, py_arg) != 0) {
Py_CLEAR(py_argv);
break;
}
}
debug_return_ptr(py_argv);
}
PyObject *
py_str_array_to_tuple(char * const strings[])
{
debug_decl(py_str_array_to_tuple, PYTHON_DEBUG_INTERNAL);
// find the item count ("strings" ends with NULL terminator):
Py_ssize_t count = 0;
if (strings != NULL) {
while (strings[count] != NULL)
++count;
}
debug_return_ptr(py_str_array_to_tuple_with_count(count, strings));
}
char **
py_str_array_from_tuple(PyObject *py_tuple)
{
debug_decl(py_str_array_from_tuple, PYTHON_DEBUG_INTERNAL);
if (!PyTuple_Check(py_tuple)) {
PyErr_Format(PyExc_ValueError, "%s: value error, argument should be a tuple but it is '%s'",
__func__, Py_TYPENAME(py_tuple));
debug_return_ptr(NULL);
}
Py_ssize_t tuple_size = PyTuple_Size(py_tuple);
// we need an extra 0 at the end
char **result = calloc((size_t)tuple_size + 1, sizeof(char *));
if (result == NULL) {
debug_return_ptr(NULL);
}
for (int i = 0; i < tuple_size; ++i) {
PyObject *py_value = PyTuple_GetItem(py_tuple, i);
if (py_value == NULL) {
str_array_free(&result);
debug_return_ptr(NULL);
}
// Note that it can be an "int" or something else as well
char *value = py_create_string_rep(py_value);
if (value == NULL) {
// conversion error is already set
str_array_free(&result);
debug_return_ptr(NULL);
}
result[i] = value;
}
debug_return_ptr(result);
}
PyObject *
py_tuple_get(PyObject *py_tuple, Py_ssize_t idx, PyTypeObject *expected_type)
{
debug_decl(py_tuple_get, PYTHON_DEBUG_INTERNAL);
PyObject *py_item = PyTuple_GetItem(py_tuple, idx);
if (py_item == NULL) {
debug_return_ptr(NULL);
}
if (!PyObject_TypeCheck(py_item, expected_type)) {
PyErr_Format(PyExc_ValueError, "Value error: tuple element %d should "
"be a '%s' (but it is '%s')",
idx, expected_type->tp_name, Py_TYPENAME(py_item));
debug_return_ptr(NULL);
}
debug_return_ptr(py_item);
}
PyObject *
py_create_version(unsigned int version)
{
debug_decl(py_create_version, PYTHON_DEBUG_INTERNAL);
debug_return_ptr(PyUnicode_FromFormat("%d.%d", SUDO_API_VERSION_GET_MAJOR(version),
SUDO_API_VERSION_GET_MINOR(version)));
}
PyObject *
py_from_passwd(const struct passwd *pwd)
{
debug_decl(py_from_passwd, PYTHON_DEBUG_INTERNAL);
if (pwd == NULL) {
debug_return_ptr_pynone;
}
// Create a tuple similar and convertible to python "struct_passwd" of "pwd" module
debug_return_ptr(
Py_BuildValue("(zziizzz)", pwd->pw_name, pwd->pw_passwd,
pwd->pw_uid, pwd->pw_gid, pwd->pw_gecos,
pwd->pw_dir, pwd->pw_shell)
);
}
char *
py_create_string_rep(PyObject *py_object)
{
debug_decl(py_create_string_rep, PYTHON_DEBUG_INTERNAL);
char *result = NULL;
if (py_object == NULL)
debug_return_ptr(NULL);
PyObject *py_string = PyObject_Str(py_object);
if (py_string != NULL) {
const char *bytes = PyUnicode_AsUTF8(py_string);
if (bytes != NULL) {
/*
* Convert from old format w/ numeric value to new without it.
* Old: (<DEBUG.ERROR: 2>, 'ERROR level debug message')
* New: (DEBUG.ERROR, 'ERROR level debug message')
*/
if (bytes[0] == '(' && bytes[1] == '<') {
const char *colon = strchr(bytes + 2, ':');
if (colon != NULL && colon[1] == ' ') {
const char *cp = colon + 2;
while (isdigit((unsigned char)*cp))
cp++;
if (cp[0] == '>' && (cp[1] == ',' || cp[1] == '\0')) {
bytes += 2;
if (asprintf(&result, "(%.*s%s", (int)(colon - bytes),
bytes, cp + 1) == -1) {
result = NULL;
goto done;
}
}
}
}
if (result == NULL)
result = strdup(bytes);
}
}
done:
Py_XDECREF(py_string);
debug_return_ptr(result);
}
static void
_py_debug_python_function(const char *class_name, const char *function_name, const char *message,
PyObject *py_args, PyObject *py_kwargs, unsigned int subsystem_id)
{
debug_decl_vars(_py_debug_python_function, subsystem_id);
if (sudo_debug_needed(SUDO_DEBUG_DIAG)) {
char *args_str = NULL;
char *kwargs_str = NULL;
if (py_args != NULL) {
/* Sort by key for consistent output on Python < 3.6 */
PyObject *py_args_sorted = NULL;
if (PyDict_Check(py_args)) {
py_args_sorted = PyDict_Items(py_args);
if (py_args_sorted != NULL) {
if (PyList_Sort(py_args_sorted) == 0) {
py_args = py_args_sorted;
}
}
}
args_str = py_create_string_rep(py_args);
if (args_str != NULL && strncmp(args_str, "RC.", 3) == 0) {
/* Strip leading RC. to match python 3.10 behavior. */
memmove(args_str, args_str + 3, strlen(args_str + 3) + 1);
}
Py_XDECREF(py_args_sorted);
}
if (py_kwargs != NULL) {
/* Sort by key for consistent output on Python < 3.6 */
PyObject *py_kwargs_sorted = NULL;
if (PyDict_Check(py_kwargs)) {
py_kwargs_sorted = PyDict_Items(py_kwargs);
if (py_kwargs_sorted != NULL) {
if (PyList_Sort(py_kwargs_sorted) == 0) {
py_kwargs = py_kwargs_sorted;
}
}
}
kwargs_str = py_create_string_rep(py_kwargs);
Py_XDECREF(py_kwargs_sorted);
}
sudo_debug_printf(SUDO_DEBUG_DIAG, "%s.%s %s: %s%s%s\n", class_name,
function_name, message, args_str ? args_str : "()",
kwargs_str ? " " : "", kwargs_str ? kwargs_str : "");
free(args_str);
free(kwargs_str);
}
}
void
py_debug_python_call(const char *class_name, const char *function_name,
PyObject *py_args, PyObject *py_kwargs,
unsigned int subsystem_id)
{
debug_decl_vars(py_debug_python_call, subsystem_id);
if (subsystem_id == PYTHON_DEBUG_C_CALLS && sudo_debug_needed(SUDO_DEBUG_INFO)) {
// at this level we also output the callee python script
char *callee_func_name = NULL, *callee_file_name = NULL;
long callee_line_number = -1;
if (py_get_current_execution_frame(&callee_file_name, &callee_line_number, &callee_func_name) == SUDO_RC_OK) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s @ %s:%ld calls C function:\n",
callee_func_name, callee_file_name, callee_line_number);
}
free(callee_func_name);
free(callee_file_name);
}
_py_debug_python_function(class_name, function_name, "was called with arguments",
py_args, py_kwargs, subsystem_id);
}
void
py_debug_python_result(const char *class_name, const char *function_name,
PyObject *py_result, unsigned int subsystem_id)
{
if (py_result == NULL) {
debug_decl_vars(py_debug_python_result, subsystem_id);
sudo_debug_printf(SUDO_CONV_ERROR_MSG, "%s.%s call failed\n",
class_name, function_name);
} else {
_py_debug_python_function(class_name, function_name, "returned result",
py_result, NULL, subsystem_id);
}
}
void
str_array_free(char ***array)
{
debug_decl(str_array_free, PYTHON_DEBUG_INTERNAL);
if (*array == NULL)
debug_return;
for (char **item_ptr = *array; *item_ptr != NULL; ++item_ptr)
free(*item_ptr);
free(*array);
*array = NULL;
debug_return;
}
int
py_get_current_execution_frame(char **file_name, long *line_number, char **function_name)
{
*file_name = NULL;
*line_number = (long)-1;
*function_name = NULL;
PyObject *py_err_type = NULL, *py_err_value = NULL, *py_err_traceback = NULL;
PyErr_Fetch(&py_err_type, &py_err_value, &py_err_traceback);
PyObject *py_frame = NULL, *py_f_code = NULL,
*py_filename = NULL, *py_function_name = NULL;
PyObject *py_getframe = PySys_GetObject("_getframe");
if (py_getframe == NULL)
goto cleanup;
py_frame = PyObject_CallFunction(py_getframe, "i", 0);
if (py_frame == NULL)
goto cleanup;
*line_number = py_object_get_optional_attr_number(py_frame, "f_lineno");
py_f_code = py_object_get_optional_attr(py_frame, "f_code", NULL);
if (py_f_code != NULL) {
py_filename = py_object_get_optional_attr(py_f_code, "co_filename", NULL);
if (py_filename != NULL)
*file_name = strdup(PyUnicode_AsUTF8(py_filename));
py_function_name = py_object_get_optional_attr(py_f_code, "co_name", NULL);
if (py_function_name != NULL)
*function_name = strdup(PyUnicode_AsUTF8(py_function_name));
}
cleanup:
Py_CLEAR(py_frame);
Py_CLEAR(py_f_code);
Py_CLEAR(py_filename);
Py_CLEAR(py_function_name);
// we hide every error happening inside this function
PyErr_Restore(py_err_type, py_err_value, py_err_traceback);
return (*file_name && *function_name && (*line_number >= 0)) ?
SUDO_RC_OK : SUDO_RC_ERROR;
}
void
py_ctx_reset()
{
memset(&py_ctx, 0, sizeof(py_ctx));
py_ctx.sudo_log = &_sudo_printf_default;
}
int
py_sudo_conv(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
{
/* Enable suspend during password entry. */
struct sigaction sa, saved_sigtstp;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_DFL;
(void) sigaction(SIGTSTP, &sa, &saved_sigtstp);
int rc = SUDO_RC_ERROR;
if (py_ctx.sudo_conv != NULL)
rc = py_ctx.sudo_conv((int)num_msgs, msgs, replies, callback);
/* Restore signal handlers and signal mask. */
(void) sigaction(SIGTSTP, &saved_sigtstp, NULL);
return rc;
}
PyObject *
py_object_get_optional_attr(PyObject *py_object, const char *attr, PyObject *py_default)
{
if (PyObject_HasAttrString(py_object, attr)) {
return PyObject_GetAttrString(py_object, attr);
}
Py_XINCREF(py_default); // whatever we return will have its refcount incremented
return py_default;
}
const char *
py_object_get_optional_attr_string(PyObject *py_object, const char *attr_name)
{
PyObject *py_value = py_object_get_optional_attr(py_object, attr_name, NULL);
if (py_value == NULL)
return NULL;
const char *value = PyUnicode_AsUTF8(py_value);
Py_CLEAR(py_value); // Note, the object still has reference to the attribute
return value;
}
long
py_object_get_optional_attr_number(PyObject *py_object, const char *attr_name)
{
PyObject *py_value = py_object_get_optional_attr(py_object, attr_name, NULL);
if (py_value == NULL)
return -1;
long value = PyLong_AsLong(py_value);
Py_CLEAR(py_value);
return value;
}
void
py_object_set_attr_number(PyObject *py_object, const char *attr_name, long number)
{
PyObject *py_number = PyLong_FromLong(number);
if (py_number == NULL)
return;
PyObject_SetAttrString(py_object, attr_name, py_number);
Py_CLEAR(py_number);
}
void
py_object_set_attr_string(PyObject *py_object, const char *attr_name, const char *value)
{
PyObject *py_value = PyUnicode_FromString(value);
if (py_value == NULL)
return;
PyObject_SetAttrString(py_object, attr_name, py_value);
Py_CLEAR(py_value);
}
PyObject *
py_dict_create_string_int(size_t count, struct key_value_str_int *key_values)
{
debug_decl(py_dict_create_string_int, PYTHON_DEBUG_INTERNAL);
PyObject *py_value = NULL;
PyObject *py_dict = PyDict_New();
if (py_dict == NULL)
goto cleanup;
for (size_t i = 0; i < count; ++i) {
py_value = PyLong_FromLong(key_values[i].value);
if (py_value == NULL)
goto cleanup;
if (PyDict_SetItemString(py_dict, key_values[i].key, py_value) < 0)
goto cleanup;
Py_CLEAR(py_value);
}
cleanup:
if (PyErr_Occurred()) {
Py_CLEAR(py_dict);
}
Py_CLEAR(py_value);
debug_return_ptr(py_dict);
}

108
plugins/python/pyhelpers.h Normal file
View file

@ -0,0 +1,108 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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 SUDO_PLUGIN_PYHELPERS_H
#define SUDO_PLUGIN_PYHELPERS_H
#define PY_SSIZE_T_CLEAN
#include <Python.h>
/* Python may be built with 32-bit time_t support on some platforms. */
#undef SIZEOF_TIME_T
#include <config.h>
#include <sudo_compat.h>
#include <sudo_plugin.h>
#include "pyhelpers_cpychecker.h"
#include "sudo_python_debug.h"
enum SudoPluginFunctionReturnCode {
SUDO_RC_OK = 1,
SUDO_RC_ACCEPT = 1,
SUDO_RC_REJECT = 0,
SUDO_RC_ERROR = -1,
SUDO_RC_USAGE_ERROR = -2,
};
#define INTERPRETER_MAX 32
struct PythonContext
{
sudo_printf_t sudo_log;
sudo_conv_t sudo_conv;
PyThreadState *py_main_interpreter;
size_t interpreter_count;
PyThreadState *py_subinterpreters[INTERPRETER_MAX];
};
extern struct PythonContext py_ctx;
#define Py_TYPENAME(object) (object ? Py_TYPE(object)->tp_name : "NULL")
#define py_sudo_log(...) py_ctx.sudo_log(__VA_ARGS__)
int py_sudo_conv(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback);
void py_log_last_error(const char *context_message);
char *py_create_string_rep(PyObject *py_object);
char *py_join_str_list(PyObject *py_str_list, const char *separator);
struct key_value_str_int
{
const char *key;
int value;
};
PyObject *py_dict_create_string_int(size_t count, struct key_value_str_int *key_values);
PyObject *py_from_passwd(const struct passwd *pwd);
PyObject *py_str_array_to_tuple_with_count(Py_ssize_t count, char * const strings[]);
PyObject *py_str_array_to_tuple(char * const strings[]);
char **py_str_array_from_tuple(PyObject *py_tuple);
CPYCHECKER_RETURNS_BORROWED_REF
PyObject *py_tuple_get(PyObject *py_tuple, Py_ssize_t index, PyTypeObject *expected_type);
PyObject *py_object_get_optional_attr(PyObject *py_object, const char *attr, PyObject *py_default);
long py_object_get_optional_attr_number(PyObject *py_object, const char *attr_name);
const char *py_object_get_optional_attr_string(PyObject *py_object, const char *attr_name);
void py_object_set_attr_number(PyObject *py_object, const char *attr_name, long number);
void py_object_set_attr_string(PyObject *py_object, const char *attr_name, const char *value);
PyObject *py_create_version(unsigned int version);
void py_debug_python_call(const char *class_name, const char *function_name,
PyObject *py_args, PyObject *py_kwargs,
unsigned int subsystem_id);
void py_debug_python_result(const char *class_name, const char *function_name,
PyObject *py_args, unsigned int subsystem_id);
void str_array_free(char ***array);
int py_get_current_execution_frame(char **file_name, long *line_number, char **function_name);
void py_ctx_reset(void);
#endif // SUDO_PLUGIN_PYHELPERS_H

View file

@ -0,0 +1,45 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.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 SUDO_PLUGIN_PYHELPERS_CPYCHECKER_H
#define SUDO_PLUGIN_PYHELPERS_CPYCHECKER_H
/* Helper macros for cpychecker */
#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE)
#define CPYCHECKER_RETURNS_BORROWED_REF \
__attribute__((cpychecker_returns_borrowed_ref))
#else
#define CPYCHECKER_RETURNS_BORROWED_REF
#endif
#ifdef WITH_CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION_ATTRIBUTE
#define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION \
__attribute__ ((cpychecker_negative_result_sets_exception))
#else
#define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
#endif
#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
#define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n) \
__attribute__((cpychecker_steals_reference_to_arg(n)))
#else
#define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n)
#endif
#endif // SUDO_PLUGIN_PYHELPERS_CPYCHECKER_H

View file

@ -0,0 +1,88 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "sudo_python_module.h"
PyTypeObject *sudo_type_Plugin = NULL;
static PyObject *
_sudo_Plugin__Init(PyObject *py_self, PyObject *py_args, PyObject *py_kwargs)
{
debug_decl(_sudo_Plugin__Init, PYTHON_DEBUG_C_CALLS);
py_debug_python_call("Plugin", "__init__", py_args, NULL, PYTHON_DEBUG_C_CALLS);
if (!PyArg_UnpackTuple(py_args, "sudo.Plugin.__init__", 1, 1, &py_self))
goto cleanup;
Py_ssize_t pos = 0;
PyObject *py_key = NULL, *py_value = NULL; // -> borrowed references
while (PyDict_Next(py_kwargs, &pos, &py_key, &py_value)) {
if (PyObject_SetAttr(py_self, py_key, py_value) != 0)
goto cleanup;
}
cleanup:
if (PyErr_Occurred())
debug_return_ptr(NULL);
debug_return_ptr_pynone;
}
static PyMethodDef _sudo_Plugin_class_methods[] = {
{"__init__", (PyCFunction)_sudo_Plugin__Init,
METH_VARARGS | METH_KEYWORDS,
"Base sudo plugin constructor"},
{NULL, NULL, 0, NULL}
};
int
sudo_module_register_baseplugin(PyObject *py_module)
{
debug_decl(sudo_module_register_baseplugin, PYTHON_DEBUG_INTERNAL);
int rc = SUDO_RC_ERROR;
PyObject *py_class = NULL;
py_class = sudo_module_create_class("sudo.Plugin", _sudo_Plugin_class_methods, NULL);
if (py_class == NULL)
goto cleanup;
if (PyModule_AddObject(py_module, "Plugin", py_class) < 0) {
goto cleanup;
}
// PyModule_AddObject steals a reference to py_class on success
Py_INCREF(py_class);
rc = SUDO_RC_OK;
Py_CLEAR(sudo_type_Plugin);
sudo_type_Plugin = (PyTypeObject *)py_class;
Py_INCREF(sudo_type_Plugin);
cleanup:
Py_CLEAR(py_class);
debug_return_int(rc);
}

View file

@ -0,0 +1,155 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "sudo_python_module.h"
PyTypeObject *sudo_type_ConvMessage;
static PyObject *
_sudo_ConvMessage__Init(PyObject *py_self, PyObject *py_args, PyObject *py_kwargs)
{
debug_decl(_sudo_ConvMessage__Init, PYTHON_DEBUG_C_CALLS);
py_debug_python_call("ConvMessage", "__init__", py_args, py_kwargs, PYTHON_DEBUG_C_CALLS);
PyObject *py_empty = PyTuple_New(0);
struct sudo_conv_message conv_message = { 0, 0, NULL };
static const char *keywords[] = { "self", "msg_type", "msg", "timeout", NULL };
if (!PyArg_ParseTupleAndKeywords(py_args ? py_args : py_empty, py_kwargs, "Ois|i:sudo.ConvMessage", (char **)keywords,
&py_self, &(conv_message.msg_type), &(conv_message.msg),
&(conv_message.timeout)))
goto cleanup;
sudo_debug_printf(SUDO_DEBUG_TRACE, "Parsed arguments: self='%p' msg_type='%d' timeout='%d' msg='%s'",
(void *)py_self, conv_message.msg_type, conv_message.timeout, conv_message.msg);
py_object_set_attr_number(py_self, "msg_type", conv_message.msg_type);
if (PyErr_Occurred())
goto cleanup;
py_object_set_attr_number(py_self, "timeout", conv_message.timeout);
if (PyErr_Occurred()) // -V547
goto cleanup;
py_object_set_attr_string(py_self, "msg", conv_message.msg);
if (PyErr_Occurred()) // -V547
goto cleanup;
cleanup:
Py_CLEAR(py_empty);
if (PyErr_Occurred())
debug_return_ptr(NULL);
debug_return_ptr_pynone;
}
static PyMethodDef _sudo_ConvMessage_class_methods[] =
{
{"__init__", (PyCFunction)_sudo_ConvMessage__Init,
METH_VARARGS | METH_KEYWORDS,
"Conversation message (same as C type sudo_conv_message)"},
{NULL, NULL, 0, NULL}
};
int
sudo_module_register_conv_message(PyObject *py_module)
{
debug_decl(sudo_module_register_conv_message, PYTHON_DEBUG_INTERNAL);
int rc = SUDO_RC_ERROR;
PyObject *py_class = NULL;
py_class = sudo_module_create_class("sudo.ConvMessage", _sudo_ConvMessage_class_methods, NULL);
if (py_class == NULL)
goto cleanup;
if (PyModule_AddObject(py_module, "ConvMessage", py_class) < 0) {
goto cleanup;
}
// PyModule_AddObject steals the reference to py_class on success
Py_INCREF(py_class);
rc = SUDO_RC_OK;
Py_CLEAR(sudo_type_ConvMessage);
sudo_type_ConvMessage = (PyTypeObject *)py_class;
Py_INCREF(sudo_type_ConvMessage);
cleanup:
Py_CLEAR(py_class);
debug_return_int(rc);
}
int
sudo_module_ConvMessage_to_c(PyObject *py_conv_message, struct sudo_conv_message *conv_message)
{
debug_decl(sudo_module_ConvMessage_to_c, PYTHON_DEBUG_C_CALLS);
conv_message->msg_type = (int)py_object_get_optional_attr_number(py_conv_message, "msg_type");
if (PyErr_Occurred())
debug_return_int(SUDO_RC_ERROR);
conv_message->timeout = (int)py_object_get_optional_attr_number(py_conv_message, "timeout");
if (PyErr_Occurred()) // -V547
debug_return_int(SUDO_RC_ERROR);
conv_message->msg = py_object_get_optional_attr_string(py_conv_message, "msg");
if (PyErr_Occurred()) // -V547
debug_return_int(SUDO_RC_ERROR);
debug_return_int(SUDO_RC_OK);
}
int
sudo_module_ConvMessages_to_c(PyObject *py_tuple, Py_ssize_t *num_msgs, struct sudo_conv_message **msgs)
{
debug_decl(sudo_module_ConvMessages_to_c, PYTHON_DEBUG_C_CALLS);
*num_msgs = PyTuple_Size(py_tuple);
*msgs = NULL;
if (*num_msgs <= 0) {
*num_msgs = 0;
PyErr_Format(sudo_exc_SudoException, "Expected at least one ConvMessage");
debug_return_int(SUDO_RC_ERROR);
}
*msgs = calloc((size_t)*num_msgs, sizeof(struct sudo_conv_message));
if (*msgs == NULL) {
debug_return_int(SUDO_RC_ERROR);
}
for (Py_ssize_t i = 0; i < *num_msgs; ++i) {
PyObject *py_msg = py_tuple_get(py_tuple, i, sudo_type_ConvMessage);
if (py_msg == NULL || sudo_module_ConvMessage_to_c(py_msg, &(*msgs)[i]) < 0) {
debug_return_int(SUDO_RC_ERROR);
}
}
debug_return_int(SUDO_RC_OK);
}

View file

@ -0,0 +1,182 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "sudo_python_module.h"
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 9
# define PyObject_CallNoArgs(_o) PyObject_CallObject((_o), NULL)
#endif
static void
_debug_plugin(unsigned int log_level, const char *log_message)
{
debug_decl_vars(python_sudo_debug, PYTHON_DEBUG_PLUGIN);
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
// at trace level we output the position for the python log as well
char *func_name = NULL, *file_name = NULL;
long line_number = -1;
if (py_get_current_execution_frame(&file_name, &line_number, &func_name) == SUDO_RC_OK) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s @ %s:%ld debugs:\n",
func_name, file_name, line_number);
}
free(func_name);
free(file_name);
}
sudo_debug_printf(log_level, "%s\n", log_message);
}
PyObject *
python_sudo_debug(PyObject *Py_UNUSED(py_self), PyObject *py_args)
{
debug_decl(python_sudo_debug, PYTHON_DEBUG_C_CALLS);
py_debug_python_call("sudo", "debug", py_args, NULL, PYTHON_DEBUG_C_CALLS);
unsigned int log_level = SUDO_DEBUG_DEBUG;
const char *log_message = NULL;
if (!PyArg_ParseTuple(py_args, "is:sudo.debug", &log_level, &log_message)) {
debug_return_ptr(NULL);
}
_debug_plugin(log_level, log_message);
debug_return_ptr_pynone;
}
static unsigned int
_sudo_log_level_from_python(long level)
{
if (level >= 50)
return SUDO_DEBUG_CRIT;
if (level >= 40)
return SUDO_DEBUG_ERROR;
if (level >= 30)
return SUDO_DEBUG_WARN;
if (level >= 20)
return SUDO_DEBUG_INFO;
return SUDO_DEBUG_TRACE;
}
static PyObject *
_sudo_LogHandler__emit(PyObject *py_self, PyObject *py_args)
{
debug_decl(_sudo_LogHandler__emit, PYTHON_DEBUG_C_CALLS);
PyObject *py_record = NULL; // borrowed
PyObject *py_message = NULL;
py_debug_python_call("LogHandler", "emit", py_args, NULL, PYTHON_DEBUG_C_CALLS);
if (!PyArg_UnpackTuple(py_args, "sudo.LogHandler.emit", 2, 2, &py_self, &py_record))
goto cleanup;
long python_loglevel = py_object_get_optional_attr_number(py_record, "levelno");
if (PyErr_Occurred()) {
PyErr_Format(sudo_exc_SudoException, "sudo.LogHandler: Failed to determine log level");
goto cleanup;
}
unsigned int sudo_loglevel = _sudo_log_level_from_python(python_loglevel);
py_message = PyObject_CallMethod(py_self, "format", "O", py_record);
if (py_message == NULL)
goto cleanup;
_debug_plugin(sudo_loglevel, PyUnicode_AsUTF8(py_message));
cleanup:
Py_CLEAR(py_message);
if (PyErr_Occurred()) {
debug_return_ptr(NULL);
}
debug_return_ptr_pynone;
}
/* The sudo.LogHandler class can be used to make the default python logger
* use sudo's built in log system. */
static PyMethodDef _sudo_LogHandler_class_methods[] =
{
{"emit", _sudo_LogHandler__emit, METH_VARARGS, ""},
{NULL, NULL, 0, NULL}
};
// This function creates the sudo.LogHandler class and adds it
// to the root logger.
int
sudo_module_set_default_loghandler()
{
debug_decl(sudo_module_set_default_loghandler, PYTHON_DEBUG_INTERNAL);
PyObject *py_sudo, *py_logging_module = NULL, *py_logger = NULL,
*py_streamhandler = NULL, *py_class = NULL,
*py_loghandler = NULL, *py_result = NULL;
py_sudo = PyImport_ImportModule("sudo");
if (py_sudo == NULL)
goto cleanup;
py_logging_module = PyImport_ImportModule("logging");
if (py_logging_module == NULL)
goto cleanup;
// Get the root logger which all loggers descend from.
py_logger = PyObject_CallMethod(py_logging_module, "getLogger", NULL);
if (py_logger == NULL)
goto cleanup;
py_streamhandler = PyObject_GetAttrString(py_logging_module, "StreamHandler");
if (py_streamhandler == NULL)
goto cleanup;
// Create our own handler that is a sub-class of StreamHandler
py_class = sudo_module_create_class("sudo.LogHandler",
_sudo_LogHandler_class_methods, py_streamhandler);
if (py_class == NULL)
goto cleanup;
// PyModule_AddObject steals a reference to py_class on success
if (PyModule_AddObject(py_sudo, "LogHandler", py_class) < 0)
goto cleanup;
Py_INCREF(py_class);
py_loghandler = PyObject_CallNoArgs(py_class);
if (py_loghandler == NULL)
goto cleanup;
py_result = PyObject_CallMethod(py_logger, "addHandler", "O", py_loghandler);
cleanup:
Py_CLEAR(py_result);
Py_CLEAR(py_loghandler);
Py_CLEAR(py_class);
Py_CLEAR(py_streamhandler);
Py_CLEAR(py_logger);
Py_CLEAR(py_logging_module);
Py_CLEAR(py_sudo);
debug_return_int(PyErr_Occurred() ? SUDO_RC_ERROR : SUDO_RC_OK);
}

View file

@ -0,0 +1,8 @@
group_plugin
python_approval
python_approval_clone
python_audit
python_audit_clone
python_io
python_io_clone
python_policy

View file

@ -0,0 +1,196 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "python_plugin_common.h"
struct ApprovalPluginContext
{
struct PluginContext base_ctx;
struct approval_plugin *plugin;
};
#define BASE_CTX(approval_ctx) (&(approval_ctx->base_ctx))
#define PY_APPROVAL_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
#define CALLBACK_PLUGINFUNC(func_name) approval_ctx->plugin->func_name
// This also verifies compile time that the name matches the sudo plugin API.
#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
sudo_dso_public struct approval_plugin *python_approval_clone(void);
static int
python_plugin_approval_open(struct ApprovalPluginContext *approval_ctx,
unsigned int version, sudo_conv_t conversation, sudo_printf_t sudo_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)
{
debug_decl(python_plugin_approval_open, PYTHON_DEBUG_CALLBACKS);
(void) version;
int rc = python_plugin_register_logging(conversation, sudo_printf, settings);
if (rc != SUDO_RC_OK) {
debug_return_int(rc);
}
struct PluginContext *plugin_ctx = BASE_CTX(approval_ctx);
rc = python_plugin_init(plugin_ctx, plugin_options, version);
if (rc != SUDO_RC_OK) {
debug_return_int(rc);
}
PyObject *py_kwargs = NULL, *py_submit_optind = NULL,
*py_submit_argv = NULL;
if ((py_kwargs = python_plugin_construct_args(version, settings, user_info,
submit_envp, plugin_options)) == NULL ||
(py_submit_optind = PyLong_FromLong(submit_optind)) == NULL ||
(py_submit_argv = py_str_array_to_tuple(submit_argv)) == NULL)
{
py_log_last_error("Failed to construct plugin instance");
rc = SUDO_RC_ERROR;
} else {
PyDict_SetItemString(py_kwargs, "submit_optind", py_submit_optind);
PyDict_SetItemString(py_kwargs, "submit_argv", py_submit_argv);
rc = python_plugin_construct_custom(plugin_ctx, py_kwargs);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
}
Py_CLEAR(py_kwargs);
Py_CLEAR(py_submit_argv);
Py_CLEAR(py_submit_optind);
if (rc != SUDO_RC_OK) {
debug_return_int(rc);
}
debug_return_int(rc);
}
static void
python_plugin_approval_close(struct ApprovalPluginContext *approval_ctx)
{
debug_decl(python_plugin_approval_close, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(approval_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
python_plugin_deinit(plugin_ctx);
debug_return;
}
static int
python_plugin_approval_check(struct ApprovalPluginContext *approval_ctx,
char * const command_info[], char * const run_argv[],
char * const run_envp[], const char **errstr)
{
debug_decl(python_plugin_approval_check, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(approval_ctx);
PyObject *py_command_info = NULL, *py_run_argv = NULL, *py_run_envp = NULL,
*py_args = NULL;
int rc = SUDO_RC_ERROR;
if ((py_command_info = py_str_array_to_tuple(command_info)) != NULL &&
(py_run_argv = py_str_array_to_tuple(run_argv)) != NULL &&
(py_run_envp = py_str_array_to_tuple(run_envp)) != NULL)
{
py_args = Py_BuildValue("(OOO)", py_command_info, py_run_argv, py_run_envp);
}
// Note, py_args gets cleared by api_rc_call
rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(check), py_args);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
Py_CLEAR(py_command_info);
Py_CLEAR(py_run_argv);
Py_CLEAR(py_run_envp);
debug_return_int(rc);
}
static int
python_plugin_approval_show_version(struct ApprovalPluginContext *approval_ctx, int verbose)
{
debug_decl(python_plugin_approval_show_version, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(approval_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
debug_return_int(python_plugin_show_version(plugin_ctx,
CALLBACK_PYNAME(show_version), verbose, PY_APPROVAL_PLUGIN_VERSION, "approval"));
}
sudo_dso_public struct approval_plugin python_approval;
// generate symbols for loading multiple approval plugins:
#define APPROVAL_SYMBOL_NAME(symbol) symbol
#include "python_plugin_approval_multi.inc"
#define APPROVAL_SYMBOL_NAME(symbol) symbol##1
#include "python_plugin_approval_multi.inc"
#define APPROVAL_SYMBOL_NAME(symbol) symbol##2
#include "python_plugin_approval_multi.inc"
#define APPROVAL_SYMBOL_NAME(symbol) symbol##3
#include "python_plugin_approval_multi.inc"
#define APPROVAL_SYMBOL_NAME(symbol) symbol##4
#include "python_plugin_approval_multi.inc"
#define APPROVAL_SYMBOL_NAME(symbol) symbol##5
#include "python_plugin_approval_multi.inc"
#define APPROVAL_SYMBOL_NAME(symbol) symbol##6
#include "python_plugin_approval_multi.inc"
#define APPROVAL_SYMBOL_NAME(symbol) symbol##7
#include "python_plugin_approval_multi.inc"
static struct approval_plugin *extra_approval_plugins[] = {
&python_approval1,
&python_approval2,
&python_approval3,
&python_approval4,
&python_approval5,
&python_approval6,
&python_approval7
};
struct approval_plugin *
python_approval_clone(void)
{
static size_t counter = 0;
struct approval_plugin *next_plugin = NULL;
size_t max = sizeof(extra_approval_plugins) / sizeof(*extra_approval_plugins);
if (counter < max) {
next_plugin = extra_approval_plugins[counter];
++counter;
} else if (counter == max) {
++counter;
py_sudo_log(SUDO_CONV_ERROR_MSG,
"sudo: loading more than %d sudo python approval plugins is not supported\n", counter);
}
return next_plugin;
}

View file

@ -0,0 +1,57 @@
/* The purpose of this file is to generate a approval_plugin symbols,
* with an I/O plugin context which is unique to it and its functions.
* The callbacks inside are just wrappers around the real functions in python_plugin_approval.c,
* their only purpose is to add the unique context to each separate approval_plugin call.
*/
#define PLUGIN_CTX APPROVAL_SYMBOL_NAME(plugin_ctx)
#define CALLBACK_CFUNC(func_name) APPROVAL_SYMBOL_NAME(_python_plugin_approval_ ## func_name)
extern struct approval_plugin APPROVAL_SYMBOL_NAME(python_approval);
static struct ApprovalPluginContext PLUGIN_CTX = { { NULL }, &APPROVAL_SYMBOL_NAME(python_approval) };
static int
CALLBACK_CFUNC(open)(unsigned int version, sudo_conv_t conversation,
sudo_printf_t sudo_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)
{
return python_plugin_approval_open(&PLUGIN_CTX, version, conversation,
sudo_printf, settings, user_info, submit_optind, submit_argv,
submit_envp, plugin_options, errstr);
}
static void
CALLBACK_CFUNC(close)(void)
{
python_plugin_approval_close(&PLUGIN_CTX);
}
static int
CALLBACK_CFUNC(check)(char * const command_info[], char * const run_argv[],
char * const run_envp[], const char **errstr)
{
return python_plugin_approval_check(&PLUGIN_CTX, command_info, run_argv,
run_envp, errstr);
}
static int
CALLBACK_CFUNC(show_version)(int verbose)
{
return python_plugin_approval_show_version(&PLUGIN_CTX, verbose);
}
struct approval_plugin APPROVAL_SYMBOL_NAME(python_approval) = {
SUDO_APPROVAL_PLUGIN,
SUDO_API_VERSION,
CALLBACK_CFUNC(open),
CALLBACK_CFUNC(close),
CALLBACK_CFUNC(check),
CALLBACK_CFUNC(show_version)
};
#undef PLUGIN_CTX
#undef CALLBACK_CFUNC
#undef APPROVAL_SYMBOL_NAME

View file

@ -0,0 +1,281 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "python_plugin_common.h"
struct AuditPluginContext
{
struct PluginContext base_ctx;
struct audit_plugin *plugin;
};
#define BASE_CTX(audit_ctx) (&(audit_ctx->base_ctx))
#define PY_AUDIT_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
#define CALLBACK_PLUGINFUNC(func_name) audit_ctx->plugin->func_name
// This also verifies compile time that the name matches the sudo plugin API.
#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
#define MARK_CALLBACK_OPTIONAL(function_name) \
do { \
python_plugin_mark_callback_optional(plugin_ctx, CALLBACK_PYNAME(function_name), \
(void **)&CALLBACK_PLUGINFUNC(function_name)); \
} while(0)
sudo_dso_public struct audit_plugin *python_audit_clone(void);
static int
_call_plugin_open(struct AuditPluginContext *audit_ctx, int submit_optind, char * const submit_argv[])
{
debug_decl(_call_plugin_open, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx);
if (!PyObject_HasAttrString(plugin_ctx->py_instance, CALLBACK_PYNAME(open))) {
debug_return_int(SUDO_RC_OK);
}
int rc = SUDO_RC_ERROR;
PyObject *py_submit_argv = py_str_array_to_tuple(submit_argv);
if (py_submit_argv != NULL) {
rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(open),
Py_BuildValue("(iO)", submit_optind, py_submit_argv));
}
Py_XDECREF(py_submit_argv);
debug_return_int(rc);
}
static int
python_plugin_audit_open(struct AuditPluginContext *audit_ctx,
unsigned int version, sudo_conv_t conversation,
sudo_printf_t sudo_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)
{
debug_decl(python_plugin_audit_open, PYTHON_DEBUG_CALLBACKS);
(void) version;
int rc = python_plugin_register_logging(conversation, sudo_printf, settings);
if (rc != SUDO_RC_OK) {
debug_return_int(rc);
}
struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx);
rc = python_plugin_init(plugin_ctx, plugin_options, version);
if (rc != SUDO_RC_OK) {
debug_return_int(rc);
}
rc = python_plugin_construct(plugin_ctx, PY_AUDIT_PLUGIN_VERSION, settings,
user_info, submit_envp, plugin_options);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
if (rc != SUDO_RC_OK) {
debug_return_int(rc);
}
// skip plugin callbacks which are not mandatory
MARK_CALLBACK_OPTIONAL(accept);
MARK_CALLBACK_OPTIONAL(reject);
MARK_CALLBACK_OPTIONAL(error);
plugin_ctx->call_close = 1;
rc = _call_plugin_open(audit_ctx, submit_optind, submit_argv);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
if (PyErr_Occurred()) {
py_log_last_error("Error during calling audit open");
}
debug_return_int(rc);
}
static void
python_plugin_audit_close(struct AuditPluginContext *audit_ctx, int status_type, int status)
{
debug_decl(python_plugin_audit_close, PYTHON_DEBUG_CALLBACKS);
python_plugin_close(BASE_CTX(audit_ctx), CALLBACK_PYNAME(close),
Py_BuildValue("(ii)", status_type, status));
debug_return;
}
static int
python_plugin_audit_accept(struct AuditPluginContext *audit_ctx,
const char *plugin_name, unsigned int plugin_type,
char * const command_info[], char * const run_argv[],
char * const run_envp[], const char **errstr)
{
debug_decl(python_plugin_audit_accept, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
PyObject *py_command_info = NULL, *py_run_argv = NULL, *py_run_envp = NULL;
int rc = SUDO_RC_ERROR;
py_run_argv = py_str_array_to_tuple(run_argv);
if (py_run_argv == NULL)
goto cleanup;
py_command_info = py_str_array_to_tuple(command_info);
if (py_command_info == NULL)
goto cleanup;
py_run_envp = py_str_array_to_tuple(run_envp);
if (py_run_envp == NULL)
goto cleanup;
PyObject *py_args = Py_BuildValue("(ziOOO)", plugin_name, plugin_type, py_command_info, py_run_argv, py_run_envp);
rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(accept), py_args);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
cleanup:
Py_CLEAR(py_command_info);
Py_CLEAR(py_run_argv);
Py_CLEAR(py_run_envp);
debug_return_int(rc);
}
static int
python_plugin_audit_reject(struct AuditPluginContext *audit_ctx,
const char *plugin_name, unsigned int plugin_type,
const char *audit_msg, char * const command_info[], const char **errstr)
{
debug_decl(python_plugin_audit_reject, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
PyObject *py_command_info = NULL;
int rc = SUDO_RC_ERROR;
py_command_info = py_str_array_to_tuple(command_info);
if (PyErr_Occurred())
goto cleanup;
PyObject *py_args = Py_BuildValue("(zizO)", plugin_name, plugin_type, audit_msg, py_command_info);
rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(reject), py_args);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
cleanup:
Py_CLEAR(py_command_info);
if (PyErr_Occurred())
py_log_last_error("Error during calling audit reject");
debug_return_int(rc);
}
static int
python_plugin_audit_error(struct AuditPluginContext *audit_ctx,
const char *plugin_name, unsigned int plugin_type,
const char *audit_msg, char * const command_info[], const char **errstr)
{
debug_decl(python_plugin_audit_error, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
PyObject *py_command_info = NULL;
int rc = SUDO_RC_ERROR;
py_command_info = py_str_array_to_tuple(command_info);
if (PyErr_Occurred())
goto cleanup;
PyObject *py_args = Py_BuildValue("(zizO)", plugin_name, plugin_type, audit_msg, py_command_info);
rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(error), py_args);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
cleanup:
Py_CLEAR(py_command_info);
debug_return_int(rc);
}
static int
python_plugin_audit_show_version(struct AuditPluginContext *audit_ctx, int verbose)
{
debug_decl(python_plugin_audit_show_version, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
debug_return_int(python_plugin_show_version(plugin_ctx,
CALLBACK_PYNAME(show_version), verbose, PY_AUDIT_PLUGIN_VERSION, "audit"));
}
sudo_dso_public struct audit_plugin python_audit;
// generate symbols for loading multiple audit plugins:
#define AUDIT_SYMBOL_NAME(symbol) symbol
#include "python_plugin_audit_multi.inc"
#define AUDIT_SYMBOL_NAME(symbol) symbol##1
#include "python_plugin_audit_multi.inc"
#define AUDIT_SYMBOL_NAME(symbol) symbol##2
#include "python_plugin_audit_multi.inc"
#define AUDIT_SYMBOL_NAME(symbol) symbol##3
#include "python_plugin_audit_multi.inc"
#define AUDIT_SYMBOL_NAME(symbol) symbol##4
#include "python_plugin_audit_multi.inc"
#define AUDIT_SYMBOL_NAME(symbol) symbol##5
#include "python_plugin_audit_multi.inc"
#define AUDIT_SYMBOL_NAME(symbol) symbol##6
#include "python_plugin_audit_multi.inc"
#define AUDIT_SYMBOL_NAME(symbol) symbol##7
#include "python_plugin_audit_multi.inc"
static struct audit_plugin *extra_audit_plugins[] = {
&python_audit1,
&python_audit2,
&python_audit3,
&python_audit4,
&python_audit5,
&python_audit6,
&python_audit7
};
struct audit_plugin *
python_audit_clone(void)
{
static size_t counter = 0;
struct audit_plugin *next_plugin = NULL;
size_t max = sizeof(extra_audit_plugins) / sizeof(*extra_audit_plugins);
if (counter < max) {
next_plugin = extra_audit_plugins[counter];
++counter;
} else if (counter == max) {
++counter;
py_sudo_log(SUDO_CONV_ERROR_MSG, "sudo: loading more than %d sudo python audit plugins is not supported\n", counter);
}
return next_plugin;
}

View file

@ -0,0 +1,78 @@
/* The purpose of this file is to generate a audit_plugin symbols,
* with an I/O plugin context which is unique to it and its functions.
* The callbacks inside are just wrappers around the real functions in python_plugin_audit.c,
* their only purpose is to add the unique context to each separate audit_plugin call.
*/
#define PLUGIN_CTX AUDIT_SYMBOL_NAME(plugin_ctx)
#define CALLBACK_CFUNC(func_name) AUDIT_SYMBOL_NAME(_python_plugin_audit_ ## func_name)
extern struct audit_plugin AUDIT_SYMBOL_NAME(python_audit);
static struct AuditPluginContext PLUGIN_CTX = { { NULL }, &AUDIT_SYMBOL_NAME(python_audit) };
static int
CALLBACK_CFUNC(open)(unsigned int version, sudo_conv_t conversation,
sudo_printf_t sudo_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)
{
return python_plugin_audit_open(&PLUGIN_CTX, version, conversation, sudo_printf,
settings, user_info, submit_optind, submit_argv, submit_envp,
plugin_options, errstr);
}
static void
CALLBACK_CFUNC(close)(int status_type, int status)
{
python_plugin_audit_close(&PLUGIN_CTX, status_type, status);
}
static int
CALLBACK_CFUNC(accept)(const char *plugin_name, unsigned int plugin_type,
char * const command_info[], char * const run_argv[],
char * const run_envp[], const char **errstr)
{
return python_plugin_audit_accept(&PLUGIN_CTX, plugin_name, plugin_type,
command_info, run_argv, run_envp, errstr);
}
static int
CALLBACK_CFUNC(reject)(const char *plugin_name, unsigned int plugin_type,
const char *audit_msg, char * const command_info[], const char **errstr)
{
return python_plugin_audit_reject(&PLUGIN_CTX, plugin_name, plugin_type,
audit_msg, command_info, errstr);
}
static int
CALLBACK_CFUNC(error)(const char *plugin_name, unsigned int plugin_type,
const char *audit_msg, char * const command_info[], const char **errstr)
{
return python_plugin_audit_error(&PLUGIN_CTX, plugin_name, plugin_type,
audit_msg, command_info, errstr);
}
static int
CALLBACK_CFUNC(show_version)(int verbose)
{
return python_plugin_audit_show_version(&PLUGIN_CTX, verbose);
}
struct audit_plugin AUDIT_SYMBOL_NAME(python_audit) = {
SUDO_AUDIT_PLUGIN,
SUDO_API_VERSION,
CALLBACK_CFUNC(open),
CALLBACK_CFUNC(close),
CALLBACK_CFUNC(accept),
CALLBACK_CFUNC(reject),
CALLBACK_CFUNC(error),
CALLBACK_CFUNC(show_version),
NULL, /* register_hooks */
NULL /* deregister_hooks */
};
#undef PLUGIN_CTX
#undef CALLBACK_CFUNC
#undef AUDIT_SYMBOL_NAME

View file

@ -0,0 +1,781 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "python_plugin_common.h"
#include "sudo_python_module.h"
#include <sudo_queue.h>
#include <sudo_conf.h>
#include <limits.h>
#include <string.h>
static struct _inittab * python_inittab_copy = NULL;
static size_t python_inittab_copy_len = 0;
#ifndef PLUGIN_DIR
#define PLUGIN_DIR ""
#endif
/* Py_FinalizeEx is new in version 3.6 */
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 6
# define Py_FinalizeEx() (Py_Finalize(), 0)
#endif
static const char *
_lookup_value(char * const keyvalues[], const char *key)
{
debug_decl(_lookup_value, PYTHON_DEBUG_INTERNAL);
if (keyvalues == NULL)
debug_return_const_str(NULL);
size_t keylen = strlen(key);
for (; *keyvalues != NULL; ++keyvalues) {
const char *keyvalue = *keyvalues;
if (strncmp(keyvalue, key, keylen) == 0 && keyvalue[keylen] == '=')
debug_return_const_str(keyvalue + keylen + 1);
}
debug_return_const_str(NULL);
}
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
static int
_append_python_path(const char *module_dir)
{
debug_decl(_append_python_path, PYTHON_DEBUG_PLUGIN_LOAD);
int rc = -1;
PyObject *py_sys_path = PySys_GetObject("path"); // borrowed
if (py_sys_path == NULL) {
PyErr_Format(sudo_exc_SudoException, "Failed to get python 'path'");
debug_return_int(rc);
}
sudo_debug_printf(SUDO_DEBUG_DIAG, "Extending python 'path' with '%s'\n", module_dir);
PyObject *py_module_dir = PyUnicode_FromString(module_dir);
if (py_module_dir == NULL || PyList_Append(py_sys_path, py_module_dir) != 0) {
Py_XDECREF(py_module_dir);
debug_return_int(rc);
}
Py_DECREF(py_module_dir);
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
char *path = py_join_str_list(py_sys_path, ":");
sudo_debug_printf(SUDO_DEBUG_INFO, "Python path became: %s\n", path);
free(path);
}
rc = 0;
debug_return_int(rc);
}
static PyObject *
_import_module(const char *path)
{
PyObject *module;
debug_decl(_import_module, PYTHON_DEBUG_PLUGIN_LOAD);
sudo_debug_printf(SUDO_DEBUG_DIAG, "importing module: %s\n", path);
char path_copy[PATH_MAX];
if (strlcpy(path_copy, path, sizeof(path_copy)) >= sizeof(path_copy))
debug_return_ptr(NULL);
const char *module_dir = path_copy;
char *module_name = strrchr(path_copy, '/');
if (module_name == NULL) {
module_name = path_copy;
module_dir = "";
} else {
*module_name++ = '\0';
}
size_t len = strlen(module_name);
if (len >= 3 && strcmp(".py", module_name + len - 3) == 0)
module_name[len - 3] = '\0';
sudo_debug_printf(SUDO_DEBUG_INFO, "module_name: '%s', module_dir: '%s'\n", module_name, module_dir);
if (_append_python_path(module_dir) < 0)
debug_return_ptr(NULL);
module = PyImport_ImportModule(module_name);
if (module != NULL) {
PyObject *py_loaded_path = PyObject_GetAttrString(module, "__file__");
if (py_loaded_path != NULL) {
const char *loaded_path = PyUnicode_AsUTF8(py_loaded_path);
/* If path is a directory, loaded_path may be a file inside it. */
if (strncmp(loaded_path, path, strlen(path)) != 0) {
PyErr_Format(PyExc_Exception,
"module name conflict, tried to load %s, got %s",
path, loaded_path);
Py_CLEAR(module);
}
Py_DECREF(py_loaded_path);
}
}
debug_return_ptr(module);
}
// Create a new sub-interpreter and switch to it.
static PyThreadState *
_python_plugin_new_interpreter(void)
{
debug_decl(_python_plugin_new_interpreter, PYTHON_DEBUG_INTERNAL);
if (py_ctx.interpreter_count >= INTERPRETER_MAX) {
PyErr_Format(PyExc_Exception, "Too many interpreters");
debug_return_ptr(NULL);
}
PyThreadState *py_interpreter = Py_NewInterpreter();
if (py_interpreter != NULL) {
py_ctx.py_subinterpreters[py_ctx.interpreter_count] = py_interpreter;
++py_ctx.interpreter_count;
}
debug_return_ptr(py_interpreter);
}
static int
_save_inittab(void)
{
debug_decl(_save_inittab, PYTHON_DEBUG_INTERNAL);
free(python_inittab_copy); // just to be sure (it is always NULL)
for (python_inittab_copy_len = 0;
PyImport_Inittab[python_inittab_copy_len].name != NULL;
++python_inittab_copy_len) {
}
++python_inittab_copy_len; // for the null mark
python_inittab_copy = malloc(sizeof(struct _inittab) * python_inittab_copy_len);
if (python_inittab_copy == NULL) {
debug_return_int(SUDO_RC_ERROR);
}
memcpy(python_inittab_copy, PyImport_Inittab, python_inittab_copy_len * sizeof(struct _inittab));
debug_return_int(SUDO_RC_OK);
}
static void
_restore_inittab(void)
{
debug_decl(_restore_inittab, PYTHON_DEBUG_INTERNAL);
if (python_inittab_copy != NULL)
memcpy(PyImport_Inittab, python_inittab_copy, python_inittab_copy_len * sizeof(struct _inittab));
free(python_inittab_copy);
python_inittab_copy = NULL;
python_inittab_copy_len = 0;
debug_return;
}
static void
python_plugin_handle_plugin_error_exception(PyObject **py_result, struct PluginContext *plugin_ctx)
{
debug_decl(python_plugin_handle_plugin_error_exception, PYTHON_DEBUG_INTERNAL);
free(plugin_ctx->callback_error);
plugin_ctx->callback_error = NULL;
if (PyErr_Occurred()) {
int rc = SUDO_RC_ERROR;
if (PyErr_ExceptionMatches(sudo_exc_PluginReject)) {
rc = SUDO_RC_REJECT;
} else if (!PyErr_ExceptionMatches(sudo_exc_PluginError)) {
debug_return;
}
if (py_result != NULL) {
Py_CLEAR(*py_result);
*py_result = PyLong_FromLong(rc);
}
PyObject *py_type = NULL, *py_message = NULL, *py_traceback = NULL;
PyErr_Fetch(&py_type, &py_message, &py_traceback);
char *message = py_message ? py_create_string_rep(py_message) : NULL;
sudo_debug_printf(SUDO_DEBUG_INFO, "received sudo.PluginError exception with message '%s'",
message == NULL ? "(null)" : message);
plugin_ctx->callback_error = message;
Py_CLEAR(py_type);
Py_CLEAR(py_message);
Py_CLEAR(py_traceback);
}
debug_return;
}
int
python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs)
{
debug_decl(python_plugin_construct_custom, PYTHON_DEBUG_PLUGIN_LOAD);
int rc = SUDO_RC_ERROR;
PyObject *py_args = PyTuple_New(0);
if (py_args == NULL)
goto cleanup;
py_debug_python_call(python_plugin_name(plugin_ctx), "__init__",
py_args, py_kwargs, PYTHON_DEBUG_PY_CALLS);
plugin_ctx->py_instance = PyObject_Call(plugin_ctx->py_class, py_args, py_kwargs);
python_plugin_handle_plugin_error_exception(NULL, plugin_ctx);
py_debug_python_result(python_plugin_name(plugin_ctx), "__init__",
plugin_ctx->py_instance, PYTHON_DEBUG_PY_CALLS);
if (plugin_ctx->py_instance)
rc = SUDO_RC_OK;
cleanup:
if (PyErr_Occurred()) {
py_log_last_error("Failed to construct plugin instance");
Py_CLEAR(plugin_ctx->py_instance);
rc = SUDO_RC_ERROR;
}
Py_XDECREF(py_args);
debug_return_int(rc);
}
PyObject *
python_plugin_construct_args(unsigned int version,
char *const settings[], char *const user_info[],
char *const user_env[], char *const plugin_options[])
{
PyObject *py_settings = NULL;
PyObject *py_user_info = NULL;
PyObject *py_user_env = NULL;
PyObject *py_plugin_options = NULL;
PyObject *py_version = NULL;
PyObject *py_kwargs = NULL;
if ((py_settings = py_str_array_to_tuple(settings)) == NULL ||
(py_user_info = py_str_array_to_tuple(user_info)) == NULL ||
(py_user_env = py_str_array_to_tuple(user_env)) == NULL ||
(py_plugin_options = py_str_array_to_tuple(plugin_options)) == NULL ||
(py_version = py_create_version(version)) == NULL ||
(py_kwargs = PyDict_New()) == NULL ||
PyDict_SetItemString(py_kwargs, "version", py_version) != 0 ||
PyDict_SetItemString(py_kwargs, "settings", py_settings) != 0 ||
PyDict_SetItemString(py_kwargs, "user_env", py_user_env) != 0 ||
PyDict_SetItemString(py_kwargs, "user_info", py_user_info) != 0 ||
PyDict_SetItemString(py_kwargs, "plugin_options", py_plugin_options) != 0)
{
Py_CLEAR(py_kwargs);
}
Py_CLEAR(py_settings);
Py_CLEAR(py_user_info);
Py_CLEAR(py_user_env);
Py_CLEAR(py_plugin_options);
Py_CLEAR(py_version);
return py_kwargs;
}
int
python_plugin_construct(struct PluginContext *plugin_ctx, unsigned int version,
char *const settings[], char *const user_info[],
char *const user_env[], char *const plugin_options[])
{
debug_decl(python_plugin_construct, PYTHON_DEBUG_PLUGIN_LOAD);
int rc = SUDO_RC_ERROR;
PyObject *py_kwargs = python_plugin_construct_args(
version, settings, user_info, user_env, plugin_options);
if (py_kwargs == NULL) {
py_log_last_error("Failed to construct plugin instance");
} else {
rc = python_plugin_construct_custom(plugin_ctx, py_kwargs);
}
Py_CLEAR(py_kwargs);
debug_return_int(rc);
}
int
python_plugin_register_logging(sudo_conv_t conversation,
sudo_printf_t sudo_printf,
char * const settings[])
{
debug_decl(python_plugin_register_logging, PYTHON_DEBUG_INTERNAL);
int rc = SUDO_RC_ERROR;
if (conversation != NULL)
py_ctx.sudo_conv = conversation;
if (sudo_printf)
py_ctx.sudo_log = sudo_printf;
struct sudo_conf_debug_file_list debug_files = TAILQ_HEAD_INITIALIZER(debug_files);
struct sudo_conf_debug_file_list *debug_files_ptr = &debug_files;
const char *plugin_path = _lookup_value(settings, "plugin_path");
if (plugin_path == NULL)
plugin_path = "python_plugin.so";
const char *debug_flags = _lookup_value(settings, "debug_flags");
if (debug_flags == NULL) { // the group plugin does not have this information, so try to look it up
debug_files_ptr = sudo_conf_debug_files(plugin_path);
} else {
if (!python_debug_parse_flags(&debug_files, debug_flags))
goto cleanup;
}
if (debug_files_ptr != NULL) {
if (!python_debug_register(plugin_path, debug_files_ptr))
goto cleanup;
}
rc = SUDO_RC_OK;
cleanup:
debug_return_int(rc);
}
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
static int
_python_plugin_register_plugin_in_py_ctx(void)
{
debug_decl(_python_plugin_register_plugin_in_py_ctx, PYTHON_DEBUG_PLUGIN_LOAD);
if (!Py_IsInitialized()) {
// Disable environment variables effecting the python interpreter
// This is important since we are running code here as root, the
// user should not be able to alter what is running any how.
#if (PY_MAJOR_VERSION > 3) || (PY_MINOR_VERSION >= 8)
PyStatus status;
PyPreConfig preconfig;
PyConfig config;
PyPreConfig_InitPythonConfig(&preconfig);
preconfig.isolated = 1;
preconfig.use_environment = 0;
status = Py_PreInitialize(&preconfig);
if (PyStatus_Exception(status))
debug_return_int(SUDO_RC_ERROR);
/* Inittab changes happen after pre-init but before init. */
if (_save_inittab() != SUDO_RC_OK)
debug_return_int(SUDO_RC_ERROR);
PyImport_AppendInittab("sudo", sudo_module_init);
PyConfig_InitPythonConfig(&config);
config.isolated = 1;
status = Py_InitializeFromConfig(&config);
PyConfig_Clear(&config);
if (PyStatus_Exception(status))
debug_return_int(SUDO_RC_ERROR);
#else
Py_IgnoreEnvironmentFlag = 1;
Py_IsolatedFlag = 1;
Py_NoUserSiteDirectory = 1;
if (_save_inittab() != SUDO_RC_OK)
debug_return_int(SUDO_RC_ERROR);
PyImport_AppendInittab("sudo", sudo_module_init);
Py_InitializeEx(0);
#endif
py_ctx.py_main_interpreter = PyThreadState_Get();
// This ensures we import "sudo" module in the main interpreter,
// each subinterpreter will have a shallow copy.
// (This makes the C sudo module able to eg. import other modules.)
PyObject *py_sudo = NULL;
if ((py_sudo = PyImport_ImportModule("sudo")) == NULL) {
debug_return_int(SUDO_RC_ERROR);
}
Py_CLEAR(py_sudo);
} else {
PyThreadState_Swap(py_ctx.py_main_interpreter);
}
debug_return_int(SUDO_RC_OK);
}
static int
_python_plugin_set_path(struct PluginContext *plugin_ctx, const char *path)
{
if (path == NULL) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "No python module path is specified. "
"Use 'ModulePath' plugin config option in 'sudo.conf'\n");
return SUDO_RC_ERROR;
}
if (*path == '/') { // absolute path
plugin_ctx->plugin_path = strdup(path);
} else {
if (asprintf(&plugin_ctx->plugin_path, PLUGIN_DIR "/python/%s", path) < 0)
plugin_ctx->plugin_path = NULL;
}
if (plugin_ctx->plugin_path == NULL) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to allocate memory");
return SUDO_RC_ERROR;
}
return SUDO_RC_OK;
}
/* Returns the list of sudo.Plugins in a module */
static PyObject *
_python_plugin_class_list(PyObject *py_module) {
PyObject *py_module_dict = PyModule_GetDict(py_module); // Note: borrowed
PyObject *key, *value; // Note: borrowed
Py_ssize_t pos = 0;
PyObject *py_plugin_list = PyList_New(0);
while (PyDict_Next(py_module_dict, &pos, &key, &value)) {
if (PyObject_IsSubclass(value, (PyObject *)sudo_type_Plugin) == 1) {
if (PyList_Append(py_plugin_list, key) != 0)
goto cleanup;
} else {
PyErr_Clear();
}
}
cleanup:
if (PyErr_Occurred()) {
Py_CLEAR(py_plugin_list);
}
return py_plugin_list;
}
/* Gets a sudo.Plugin class from the specified module. The argument "plugin_class"
* can be NULL in which case it loads the one and only "sudo.Plugin" present
* in the module (if so), or displays helpful error message. */
static PyObject *
_python_plugin_get_class(const char *plugin_path, PyObject *py_module, const char *plugin_class)
{
debug_decl(_python_plugin_get_class, PYTHON_DEBUG_PLUGIN_LOAD);
PyObject *py_plugin_list = NULL, *py_class = NULL;
if (plugin_class == NULL) {
py_plugin_list = _python_plugin_class_list(py_module);
if (py_plugin_list == NULL) {
goto cleanup;
}
if (PyList_Size(py_plugin_list) == 1) {
PyObject *py_plugin_name = PyList_GetItem(py_plugin_list, 0); // Note: borrowed
plugin_class = PyUnicode_AsUTF8(py_plugin_name);
}
}
if (plugin_class == NULL) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "No plugin class is specified for python module '%s'. "
"Use 'ClassName' configuration option in 'sudo.conf'\n", plugin_path);
if (py_plugin_list != NULL) {
/* Sorting the plugin list makes regress test output consistent. */
PyObject *py_obj = PyObject_CallMethod(py_plugin_list, "sort", "");
Py_CLEAR(py_obj);
char *possible_plugins = py_join_str_list(py_plugin_list, ", ");
if (possible_plugins != NULL) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "Possible plugins: %s\n", possible_plugins);
free(possible_plugins);
}
}
goto cleanup;
}
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Using plugin class '%s'", plugin_class);
py_class = PyObject_GetAttrString(py_module, plugin_class);
if (py_class == NULL) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to find plugin class '%s'\n", plugin_class);
PyErr_Clear();
goto cleanup;
}
if (!PyObject_IsSubclass(py_class, (PyObject *)sudo_type_Plugin)) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "Plugin class '%s' does not inherit from 'sudo.Plugin'\n", plugin_class);
Py_CLEAR(py_class);
goto cleanup;
}
cleanup:
Py_CLEAR(py_plugin_list);
debug_return_ptr(py_class);
}
int
python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[],
unsigned int version)
{
debug_decl(python_plugin_init, PYTHON_DEBUG_PLUGIN_LOAD);
int rc = SUDO_RC_ERROR;
if (_python_plugin_register_plugin_in_py_ctx() != SUDO_RC_OK)
goto cleanup;
plugin_ctx->sudo_api_version = version;
plugin_ctx->py_interpreter = _python_plugin_new_interpreter();
if (plugin_ctx->py_interpreter == NULL) {
goto cleanup;
}
if (sudo_module_set_default_loghandler() != SUDO_RC_OK) {
goto cleanup;
}
if (_python_plugin_set_path(plugin_ctx, _lookup_value(plugin_options, "ModulePath")) != SUDO_RC_OK) {
goto cleanup;
}
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Loading python module from path '%s'", plugin_ctx->plugin_path);
plugin_ctx->py_module = _import_module(plugin_ctx->plugin_path);
if (plugin_ctx->py_module == NULL) {
goto cleanup;
}
plugin_ctx->py_class = _python_plugin_get_class(plugin_ctx->plugin_path, plugin_ctx->py_module,
_lookup_value(plugin_options, "ClassName"));
if (plugin_ctx->py_class == NULL) {
goto cleanup;
}
rc = SUDO_RC_OK;
cleanup:
if (plugin_ctx->py_class == NULL) {
py_log_last_error("Failed during loading plugin class");
rc = SUDO_RC_ERROR;
}
debug_return_int(rc);
}
void
python_plugin_deinit(struct PluginContext *plugin_ctx)
{
debug_decl(python_plugin_deinit, PYTHON_DEBUG_PLUGIN_LOAD);
sudo_debug_printf(SUDO_DEBUG_DIAG, "Deinit was called for a python plugin\n");
Py_CLEAR(plugin_ctx->py_instance);
Py_CLEAR(plugin_ctx->py_class);
Py_CLEAR(plugin_ctx->py_module);
// Note: we are preserving the interpreters here until the unlink because
// of bugs like (strptime does not work after python interpreter reinit):
// https://bugs.python.org/issue27400
// These potentially effect a lot more python functions, simply because
// it is a rare tested scenario.
free(plugin_ctx->callback_error);
free(plugin_ctx->plugin_path);
memset(plugin_ctx, 0, sizeof(*plugin_ctx));
python_debug_deregister();
debug_return;
}
PyObject *
python_plugin_api_call(struct PluginContext *plugin_ctx, const char *func_name, PyObject *py_args)
{
debug_decl(python_plugin_api_call, PYTHON_DEBUG_PY_CALLS);
// Note: call fails if py_args is an empty tuple. Passing no arguments works passing NULL
// instead. So having such must be handled as valid. (See policy_plugin.validate())
if (py_args == NULL && PyErr_Occurred()) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to build arguments for python plugin API call '%s'\n", func_name);
py_log_last_error(NULL);
debug_return_ptr(NULL);
}
PyObject *py_callable = NULL;
py_callable = PyObject_GetAttrString(plugin_ctx->py_instance, func_name);
if (py_callable == NULL) {
Py_CLEAR(py_args);
debug_return_ptr(NULL);
}
py_debug_python_call(python_plugin_name(plugin_ctx), func_name,
py_args, NULL, PYTHON_DEBUG_PY_CALLS);
PyObject *py_result = PyObject_CallObject(py_callable, py_args);
Py_CLEAR(py_args);
Py_CLEAR(py_callable);
py_debug_python_result(python_plugin_name(plugin_ctx), func_name,
py_result, PYTHON_DEBUG_PY_CALLS);
python_plugin_handle_plugin_error_exception(&py_result, plugin_ctx);
if (PyErr_Occurred()) {
py_log_last_error(NULL);
}
debug_return_ptr(py_result);
}
int
python_plugin_rc_to_int(PyObject *py_result)
{
debug_decl(python_plugin_rc_to_int, PYTHON_DEBUG_PY_CALLS);
if (py_result == NULL)
debug_return_int(SUDO_RC_ERROR);
if (py_result == Py_None)
debug_return_int(SUDO_RC_OK);
debug_return_int((int)PyLong_AsLong(py_result));
}
int
python_plugin_api_rc_call(struct PluginContext *plugin_ctx, const char *func_name, PyObject *py_args)
{
debug_decl(python_plugin_api_rc_call, PYTHON_DEBUG_PY_CALLS);
PyObject *py_result = python_plugin_api_call(plugin_ctx, func_name, py_args);
int rc = python_plugin_rc_to_int(py_result);
Py_XDECREF(py_result);
debug_return_int(rc);
}
int
python_plugin_show_version(struct PluginContext *plugin_ctx, const char *python_callback_name,
int is_verbose, unsigned int plugin_api_version, const char *plugin_api_name)
{
debug_decl(python_plugin_show_version, PYTHON_DEBUG_CALLBACKS);
if (is_verbose) {
py_sudo_log(SUDO_CONV_INFO_MSG, "Python %s plugin (API %d.%d): %s (loaded from '%s')\n",
plugin_api_name,
SUDO_API_VERSION_GET_MAJOR(plugin_api_version),
SUDO_API_VERSION_GET_MINOR(plugin_api_version),
python_plugin_name(plugin_ctx),
plugin_ctx->plugin_path);
}
int rc = SUDO_RC_OK;
if (PyObject_HasAttrString(plugin_ctx->py_instance, python_callback_name)) {
rc = python_plugin_api_rc_call(plugin_ctx, python_callback_name,
Py_BuildValue("(i)", is_verbose));
}
debug_return_int(rc);
}
void
python_plugin_close(struct PluginContext *plugin_ctx, const char *callback_name,
PyObject *py_args)
{
debug_decl(python_plugin_close, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(plugin_ctx->py_interpreter);
// Note, this should handle the case when init has failed
if (plugin_ctx->py_instance != NULL) {
if (!plugin_ctx->call_close) {
sudo_debug_printf(SUDO_DEBUG_INFO, "Skipping close call, because there was no command run\n");
} else if (!PyObject_HasAttrString(plugin_ctx->py_instance, callback_name)) {
sudo_debug_printf(SUDO_DEBUG_INFO, "Python plugin function 'close' is skipped (not present)\n");
} else {
PyObject *py_result = python_plugin_api_call(plugin_ctx, callback_name, py_args);
py_args = NULL; // api call already freed it
Py_XDECREF(py_result);
}
}
Py_CLEAR(py_args);
if (PyErr_Occurred()) {
py_log_last_error(NULL);
}
python_plugin_deinit(plugin_ctx);
PyThreadState_Swap(py_ctx.py_main_interpreter);
debug_return;
}
void
python_plugin_mark_callback_optional(struct PluginContext *plugin_ctx,
const char *function_name, void **function)
{
if (!PyObject_HasAttrString(plugin_ctx->py_instance, function_name)) {
debug_decl_vars(python_plugin_mark_callback_optional, PYTHON_DEBUG_PY_CALLS);
sudo_debug_printf(SUDO_DEBUG_INFO, "%s function '%s' is not implemented\n",
Py_TYPENAME(plugin_ctx->py_instance), function_name);
*function = NULL;
}
}
const char *
python_plugin_name(struct PluginContext *plugin_ctx)
{
debug_decl(python_plugin_name, PYTHON_DEBUG_INTERNAL);
const char *name = "(NULL)";
if (plugin_ctx == NULL || !PyType_Check(plugin_ctx->py_class))
debug_return_const_str(name);
debug_return_const_str(((PyTypeObject *)(plugin_ctx->py_class))->tp_name);
}
void python_plugin_unlink(void) __attribute__((destructor));
// this gets run only when sudo unlinks the python_plugin.so
void
python_plugin_unlink(void)
{
debug_decl(python_plugin_unlink, PYTHON_DEBUG_INTERNAL);
if (py_ctx.py_main_interpreter == NULL)
return;
if (Py_IsInitialized()) {
sudo_debug_printf(SUDO_DEBUG_NOTICE, "Closing: deinit python %zu subinterpreters\n",
py_ctx.interpreter_count);
while (py_ctx.interpreter_count != 0) {
PyThreadState *py_interpreter =
py_ctx.py_subinterpreters[--py_ctx.interpreter_count];
PyThreadState_Swap(py_interpreter);
Py_EndInterpreter(py_interpreter);
}
sudo_debug_printf(SUDO_DEBUG_NOTICE, "Closing: deinit main interpreter\n");
// we need to call finalize from the main interpreter
PyThreadState_Swap(py_ctx.py_main_interpreter);
if (Py_FinalizeEx() != 0) {
sudo_debug_printf(SUDO_DEBUG_WARN, "Closing: failed to deinit python interpreter\n");
}
// Restore inittab so "sudo" module does not remain there (as garbage)
_restore_inittab();
}
py_ctx_reset();
debug_return;
}

View file

@ -0,0 +1,85 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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 SUDO_PYTHON_PLUGIN_COMMON_H
#define SUDO_PYTHON_PLUGIN_COMMON_H
#include "pyhelpers.h"
struct PluginContext {
PyThreadState *py_interpreter;
PyObject *py_module;
PyObject *py_class;
PyObject *py_instance;
int call_close;
unsigned int sudo_api_version;
char *plugin_path;
// We use this to let the error string live until sudo and the audit plugins
// are using it.
char *callback_error;
};
int python_plugin_register_logging(sudo_conv_t conversation, sudo_printf_t sudo_printf, char * const settings[]);
int python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[], unsigned int version);
int python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs);
PyObject *python_plugin_construct_args(unsigned int version, char *const settings[],
char *const user_info[], char *const user_env[], char *const plugin_options[]);
int python_plugin_construct(struct PluginContext *plugin_ctx, unsigned int version,
char *const settings[], char *const user_info[],
char *const user_env[], char *const plugin_options[]);
void python_plugin_deinit(struct PluginContext *plugin_ctx);
int python_plugin_show_version(struct PluginContext *plugin_ctx,
const char *python_callback_name, int isVerbose, unsigned int plugin_api_version, const char *plugin_api_name);
CPYCHECKER_STEALS_REFERENCE_TO_ARG(3)
void python_plugin_close(struct PluginContext *plugin_ctx, const char *callback_name,
PyObject *py_args);
CPYCHECKER_STEALS_REFERENCE_TO_ARG(3)
PyObject *python_plugin_api_call(struct PluginContext *plugin_ctx,
const char *func_name, PyObject *py_args);
CPYCHECKER_STEALS_REFERENCE_TO_ARG(3)
int python_plugin_api_rc_call(struct PluginContext *plugin_ctx,
const char *func_name, PyObject *py_args);
int python_plugin_rc_to_int(PyObject *py_result);
void python_plugin_mark_callback_optional(struct PluginContext *plugin_ctx,
const char *function_name, void **function);
const char *python_plugin_name(struct PluginContext *plugin_ctx);
// sets the callback error stored in plugin_ctx into "errstr" but only if API
// version is enough and "errstr" is valid
#define CALLBACK_SET_ERROR(plugin_ctx, errstr) \
do { \
if ((plugin_ctx)->sudo_api_version >= SUDO_API_MKVERSION(1, 15)) { \
if (errstr != NULL) \
*errstr = (plugin_ctx)->callback_error; \
} \
} while(0)
#endif // SUDO_PYTHON_PLUGIN_COMMON_H

View file

@ -0,0 +1,114 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "python_plugin_common.h"
static struct PluginContext plugin_ctx;
extern struct sudoers_group_plugin group_plugin;
#define PY_GROUP_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
#define CALLBACK_PLUGINFUNC(func_name) group_plugin.func_name
#define CALLBACK_CFUNC(func_name) python_plugin_group_ ## func_name
// This also verifies compile time that the name matches the sudo plugin API.
#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
static int
python_plugin_group_init(int version, sudo_printf_t sudo_printf, char *const plugin_options[])
{
debug_decl(python_plugin_group_init, PYTHON_DEBUG_CALLBACKS);
if (version < SUDO_API_MKVERSION(1, 0)) {
sudo_printf(SUDO_CONV_ERROR_MSG,
"Error: Python group plugin requires at least plugin API version 1.0\n");
debug_return_int(SUDO_RC_ERROR);
}
int rc = SUDO_RC_ERROR;
rc = python_plugin_register_logging(NULL, sudo_printf, NULL);
if (rc != SUDO_RC_OK)
debug_return_int(rc);
rc = python_plugin_init(&plugin_ctx, plugin_options, (unsigned int)version);
if (rc != SUDO_RC_OK)
debug_return_int(rc);
PyObject *py_version = NULL,
*py_plugin_options = NULL,
*py_kwargs = NULL;
if ((py_kwargs = PyDict_New()) == NULL ||
(py_version = py_create_version(PY_GROUP_PLUGIN_VERSION)) == NULL ||
(py_plugin_options = py_str_array_to_tuple(plugin_options)) == NULL ||
PyDict_SetItemString(py_kwargs, "args", py_plugin_options) != 0 ||
PyDict_SetItemString(py_kwargs, "version", py_version))
{
py_log_last_error("Failed to construct arguments for plugin constructor call.");
rc = SUDO_RC_ERROR;
} else {
rc = python_plugin_construct_custom(&plugin_ctx, py_kwargs);
}
Py_XDECREF(py_version);
Py_XDECREF(py_plugin_options);
Py_XDECREF(py_kwargs);
debug_return_int(rc);
}
static void
python_plugin_group_cleanup(void)
{
debug_decl(python_plugin_group_cleanup, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(plugin_ctx.py_interpreter);
python_plugin_deinit(&plugin_ctx);
}
static int
python_plugin_group_query(const char *user, const char *group, const struct passwd *pwd)
{
debug_decl(python_plugin_group_query, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(plugin_ctx.py_interpreter);
PyObject *py_pwd = py_from_passwd(pwd);
if (py_pwd == NULL) {
debug_return_int(SUDO_RC_ERROR);
}
int rc = python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(query),
Py_BuildValue("(zzO)", user, group, py_pwd));
Py_XDECREF(py_pwd);
debug_return_int(rc);
}
sudo_dso_public struct sudoers_group_plugin group_plugin = {
GROUP_API_VERSION,
CALLBACK_CFUNC(init),
CALLBACK_CFUNC(cleanup),
CALLBACK_CFUNC(query)
};

View file

@ -0,0 +1,276 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "python_plugin_common.h"
struct IOPluginContext
{
struct PluginContext base_ctx;
struct io_plugin *io_plugin;
};
#define BASE_CTX(io_ctx) (&(io_ctx->base_ctx))
#define PY_IO_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
#define CALLBACK_PLUGINFUNC(func_name) io_ctx->io_plugin->func_name
// This also verifies compile time that the name matches the sudo plugin API.
#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
#define MARK_CALLBACK_OPTIONAL(function_name) \
do { \
python_plugin_mark_callback_optional(plugin_ctx, CALLBACK_PYNAME(function_name), \
(void **)&CALLBACK_PLUGINFUNC(function_name)); \
} while(0)
sudo_dso_public struct io_plugin *python_io_clone(void);
static int
_call_plugin_open(struct IOPluginContext *io_ctx, int argc, char * const argv[], char * const command_info[])
{
debug_decl(_call_plugin_open, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
plugin_ctx->call_close = 1;
if (!PyObject_HasAttrString(plugin_ctx->py_instance, CALLBACK_PYNAME(open))) {
debug_return_int(SUDO_RC_OK);
}
int rc = SUDO_RC_ERROR;
PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv);
PyObject *py_command_info = py_str_array_to_tuple(command_info);
if (py_argv != NULL && py_command_info != NULL) {
rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(open),
Py_BuildValue("(OO)", py_argv, py_command_info));
}
if (rc != SUDO_RC_OK)
plugin_ctx->call_close = 0;
Py_XDECREF(py_argv);
Py_XDECREF(py_command_info);
debug_return_int(rc);
}
static int
python_plugin_io_open(struct IOPluginContext *io_ctx,
unsigned int version, sudo_conv_t conversation,
sudo_printf_t sudo_printf, char * const settings[],
char * const user_info[], char * const command_info[],
int argc, char * const argv[], char * const user_env[],
char * const plugin_options[], const char **errstr)
{
debug_decl(python_plugin_io_open, PYTHON_DEBUG_CALLBACKS);
if (version < SUDO_API_MKVERSION(1, 2)) {
sudo_printf(SUDO_CONV_ERROR_MSG,
"Error: Python IO plugin requires at least plugin API version 1.2\n");
debug_return_int(SUDO_RC_ERROR);
}
int rc = python_plugin_register_logging(conversation, sudo_printf, settings);
if (rc != SUDO_RC_OK)
debug_return_int(rc);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
rc = python_plugin_init(plugin_ctx, plugin_options, version);
if (rc != SUDO_RC_OK)
debug_return_int(rc);
rc = python_plugin_construct(plugin_ctx, PY_IO_PLUGIN_VERSION,
settings, user_info, user_env, plugin_options);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
if (rc != SUDO_RC_OK) {
debug_return_int(rc);
}
// skip plugin callbacks which are not mandatory
MARK_CALLBACK_OPTIONAL(log_ttyin);
MARK_CALLBACK_OPTIONAL(log_ttyout);
MARK_CALLBACK_OPTIONAL(log_stdin);
MARK_CALLBACK_OPTIONAL(log_stdout);
MARK_CALLBACK_OPTIONAL(log_stderr);
MARK_CALLBACK_OPTIONAL(change_winsize);
MARK_CALLBACK_OPTIONAL(log_suspend);
// open and close are mandatory
if (argc > 0) // we only call open if there is request for running sg
rc = _call_plugin_open(io_ctx, argc, argv, command_info);
CALLBACK_SET_ERROR(plugin_ctx, errstr);
debug_return_int(rc);
}
static void
python_plugin_io_close(struct IOPluginContext *io_ctx, int exit_status, int error)
{
debug_decl(python_plugin_io_close, PYTHON_DEBUG_CALLBACKS);
python_plugin_close(BASE_CTX(io_ctx), CALLBACK_PYNAME(close),
Py_BuildValue("(ii)", error == 0 ? exit_status : -1, error));
debug_return;
}
static int
python_plugin_io_show_version(struct IOPluginContext *io_ctx, int verbose)
{
debug_decl(python_plugin_io_show_version, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
debug_return_int(python_plugin_show_version(BASE_CTX(io_ctx), CALLBACK_PYNAME(show_version),
verbose, PY_IO_PLUGIN_VERSION, "io"));
}
static int
python_plugin_io_log_ttyin(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
{
debug_decl(python_plugin_io_log_ttyin, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_ttyin),
Py_BuildValue("(s#)", buf, len));
CALLBACK_SET_ERROR(plugin_ctx, errstr);
debug_return_int(rc);
}
static int
python_plugin_io_log_ttyout(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
{
debug_decl(python_plugin_io_log_ttyout, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_ttyout),
Py_BuildValue("(s#)", buf, len));
CALLBACK_SET_ERROR(plugin_ctx, errstr);
debug_return_int(rc);
}
static int
python_plugin_io_log_stdin(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
{
debug_decl(python_plugin_io_log_stdin, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_stdin),
Py_BuildValue("(s#)", buf, len));
CALLBACK_SET_ERROR(plugin_ctx, errstr);
debug_return_int(rc);
}
static int
python_plugin_io_log_stdout(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
{
debug_decl(python_plugin_io_log_stdout, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_stdout),
Py_BuildValue("(s#)", buf, len));
CALLBACK_SET_ERROR(plugin_ctx, errstr);
debug_return_int(rc);
}
static int
python_plugin_io_log_stderr(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
{
debug_decl(python_plugin_io_log_stderr, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_stderr),
Py_BuildValue("(s#)", buf, len));
CALLBACK_SET_ERROR(plugin_ctx, errstr);
debug_return_int(rc);
}
static int
python_plugin_io_change_winsize(struct IOPluginContext *io_ctx, unsigned int line, unsigned int cols, const char **errstr)
{
debug_decl(python_plugin_io_change_winsize, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(change_winsize),
Py_BuildValue("(ii)", line, cols));
CALLBACK_SET_ERROR(plugin_ctx, errstr);
debug_return_int(rc);
}
static int
python_plugin_io_log_suspend(struct IOPluginContext *io_ctx, int signo, const char **errstr)
{
debug_decl(python_plugin_io_log_suspend, PYTHON_DEBUG_CALLBACKS);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
PyThreadState_Swap(plugin_ctx->py_interpreter);
int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_suspend),
Py_BuildValue("(i)", signo));
CALLBACK_SET_ERROR(plugin_ctx, errstr);
debug_return_int(rc);
}
// generate symbols for loading multiple io plugins:
sudo_dso_public struct io_plugin python_io;
#define IO_SYMBOL_NAME(symbol) symbol
#include "python_plugin_io_multi.inc"
#define IO_SYMBOL_NAME(symbol) symbol##1
#include "python_plugin_io_multi.inc"
#define IO_SYMBOL_NAME(symbol) symbol##2
#include "python_plugin_io_multi.inc"
#define IO_SYMBOL_NAME(symbol) symbol##3
#include "python_plugin_io_multi.inc"
#define IO_SYMBOL_NAME(symbol) symbol##4
#include "python_plugin_io_multi.inc"
#define IO_SYMBOL_NAME(symbol) symbol##5
#include "python_plugin_io_multi.inc"
#define IO_SYMBOL_NAME(symbol) symbol##6
#include "python_plugin_io_multi.inc"
#define IO_SYMBOL_NAME(symbol) symbol##7
#include "python_plugin_io_multi.inc"
static struct io_plugin *extra_io_plugins[] = {
&python_io1,
&python_io2,
&python_io3,
&python_io4,
&python_io5,
&python_io6,
&python_io7
};
struct io_plugin *
python_io_clone(void)
{
static size_t counter = 0;
struct io_plugin *next_plugin = NULL;
size_t max = sizeof(extra_io_plugins) / sizeof(*extra_io_plugins);
if (counter < max) {
next_plugin = extra_io_plugins[counter];
++counter;
} else if (counter == max) {
++counter;
py_sudo_log(SUDO_CONV_ERROR_MSG, "sudo: loading more than %d sudo python IO plugins is not supported\n", counter);
}
return next_plugin;
}

View file

@ -0,0 +1,99 @@
/* The purpose of this file is to generate a io_plugin symbols,
* with an I/O plugin context which is unique to it and its functions.
* The callbacks inside are just wrappers around the real functions in python_plugin_io.c,
* their only purpose is to add the unique context to each separate io_plugin call.
*/
#define PLUGIN_CTX IO_SYMBOL_NAME(plugin_ctx)
#define CALLBACK_CFUNC(func_name) IO_SYMBOL_NAME(_python_plugin_io_ ## func_name)
extern struct io_plugin IO_SYMBOL_NAME(python_io);
static struct IOPluginContext PLUGIN_CTX = { { NULL }, &IO_SYMBOL_NAME(python_io) };
static int
CALLBACK_CFUNC(open)(
unsigned int version, sudo_conv_t conversation,
sudo_printf_t sudo_printf, char * const settings[],
char * const user_info[], char * const command_info[],
int argc, char * const argv[], char * const user_env[],
char * const plugin_options[], const char **errstr)
{
return python_plugin_io_open(&PLUGIN_CTX, version, conversation,
sudo_printf, settings, user_info, command_info, argc, argv, user_env, plugin_options, errstr);
}
static void
CALLBACK_CFUNC(close)(int exit_status, int error)
{
python_plugin_io_close(&PLUGIN_CTX, exit_status, error);
}
static int
CALLBACK_CFUNC(show_version)(int verbose)
{
return python_plugin_io_show_version(&PLUGIN_CTX, verbose);
}
static int
CALLBACK_CFUNC(log_ttyin)(const char *buf, unsigned int len, const char **errstr)
{
return python_plugin_io_log_ttyin(&PLUGIN_CTX, buf, len, errstr);
}
static int
CALLBACK_CFUNC(log_ttyout)(const char *buf, unsigned int len, const char **errstr)
{
return python_plugin_io_log_ttyout(&PLUGIN_CTX, buf, len, errstr);
}
static int
CALLBACK_CFUNC(log_stdin)(const char *buf, unsigned int len, const char **errstr)
{
return python_plugin_io_log_stdin(&PLUGIN_CTX, buf, len, errstr);
}
static int
CALLBACK_CFUNC(log_stdout)(const char *buf, unsigned int len, const char **errstr)
{
return python_plugin_io_log_stdout(&PLUGIN_CTX, buf, len, errstr);
}
static int
CALLBACK_CFUNC(log_stderr)(const char *buf, unsigned int len, const char **errstr)
{
return python_plugin_io_log_stderr(&PLUGIN_CTX, buf, len, errstr);
}
static int
CALLBACK_CFUNC(change_winsize)(unsigned int line, unsigned int cols, const char **errstr)
{
return python_plugin_io_change_winsize(&PLUGIN_CTX, line, cols, errstr);
}
static int
CALLBACK_CFUNC(log_suspend)(int signo, const char **errstr)
{
return python_plugin_io_log_suspend(&PLUGIN_CTX, signo, errstr);
}
struct io_plugin IO_SYMBOL_NAME(python_io) = {
SUDO_IO_PLUGIN,
SUDO_API_VERSION,
CALLBACK_CFUNC(open),
CALLBACK_CFUNC(close),
CALLBACK_CFUNC(show_version),
CALLBACK_CFUNC(log_ttyin),
CALLBACK_CFUNC(log_ttyout),
CALLBACK_CFUNC(log_stdin),
CALLBACK_CFUNC(log_stdout),
CALLBACK_CFUNC(log_stderr),
NULL, // register_hooks,
NULL, // deregister_hooks,
CALLBACK_CFUNC(change_winsize),
CALLBACK_CFUNC(log_suspend),
NULL // event_alloc
};
#undef PLUGIN_CTX
#undef CALLBACK_CFUNC
#undef IO_SYMBOL_NAME

View file

@ -0,0 +1,289 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "python_plugin_common.h"
static struct PluginContext plugin_ctx;
extern struct policy_plugin python_policy;
#define PY_POLICY_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
#define CALLBACK_PLUGINFUNC(func_name) python_policy.func_name
#define CALLBACK_CFUNC(func_name) python_plugin_policy_ ## func_name
// This also verifies compile time that the name matches the sudo plugin API.
#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
#define MARK_CALLBACK_OPTIONAL(function_name) \
do { \
python_plugin_mark_callback_optional(&plugin_ctx, CALLBACK_PYNAME(function_name), \
(void **)&CALLBACK_PLUGINFUNC(function_name)); \
} while(0)
static int
python_plugin_policy_open(unsigned int version, sudo_conv_t conversation,
sudo_printf_t sudo_printf, char * const settings[],
char * const user_info[], char * const user_env[],
char * const plugin_options[], const char **errstr)
{
debug_decl(python_plugin_policy_open, PYTHON_DEBUG_CALLBACKS);
if (version < SUDO_API_MKVERSION(1, 2)) {
sudo_printf(SUDO_CONV_ERROR_MSG,
"Error: Python policy plugin requires at least plugin API version 1.2\n");
debug_return_int(SUDO_RC_ERROR);
}
int rc = python_plugin_register_logging(conversation, sudo_printf, settings);
if (rc != SUDO_RC_OK)
debug_return_int(rc);
rc = python_plugin_init(&plugin_ctx, plugin_options, version);
if (rc != SUDO_RC_OK)
debug_return_int(rc);
rc = python_plugin_construct(&plugin_ctx, PY_POLICY_PLUGIN_VERSION, settings,
user_info, user_env, plugin_options);
CALLBACK_SET_ERROR(&plugin_ctx, errstr);
if (rc != SUDO_RC_OK) {
debug_return_int(rc);
}
// skip plugin callbacks which are not mandatory
MARK_CALLBACK_OPTIONAL(list);
MARK_CALLBACK_OPTIONAL(validate);
MARK_CALLBACK_OPTIONAL(invalidate);
MARK_CALLBACK_OPTIONAL(init_session);
// check_policy, open and close are mandatory
debug_return_int(rc);
}
static void
python_plugin_policy_close(int exit_status, int error)
{
debug_decl(python_plugin_policy_close, PYTHON_DEBUG_CALLBACKS);
python_plugin_close(&plugin_ctx, CALLBACK_PYNAME(close),
Py_BuildValue("(ii)", error == 0 ? exit_status : -1, error));
debug_return;
}
static int
python_plugin_policy_check(int argc, char * const argv[],
char *env_add[], char **command_info_out[],
char **argv_out[], char **user_env_out[], const char **errstr)
{
debug_decl(python_plugin_policy_check, PYTHON_DEBUG_CALLBACKS);
int rc = SUDO_RC_ERROR;
PyThreadState_Swap(plugin_ctx.py_interpreter);
*command_info_out = *argv_out = *user_env_out = NULL;
PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv);
PyObject *py_env_add = py_str_array_to_tuple(env_add);
PyObject *py_result = NULL;
if (py_argv == NULL || py_env_add == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR, "Failed to create some of the arguments for the python call "
"(py_argv=%p py_env_add=%p)\n", (void *)py_argv, (void *)py_env_add);
goto cleanup;
}
py_result = python_plugin_api_call(&plugin_ctx, CALLBACK_PYNAME(check_policy),
Py_BuildValue("(OO)", py_argv, py_env_add));
CALLBACK_SET_ERROR(&plugin_ctx, errstr);
if (py_result == NULL)
goto cleanup;
PyObject *py_rc = NULL,
*py_command_info_out = NULL,
*py_argv_out = NULL,
*py_user_env_out = NULL;
if (PyTuple_Check(py_result))
{
if (!PyArg_ParseTuple(py_result, "O!|O!O!O!:python_plugin.check_policy",
&PyLong_Type, &py_rc,
&PyTuple_Type, &py_command_info_out,
&PyTuple_Type, &py_argv_out,
&PyTuple_Type, &py_user_env_out))
{
goto cleanup;
}
} else {
py_rc = py_result;
}
if (py_command_info_out != NULL)
*command_info_out = py_str_array_from_tuple(py_command_info_out);
if (py_argv_out != NULL)
*argv_out = py_str_array_from_tuple(py_argv_out);
if (py_user_env_out != NULL)
*user_env_out = py_str_array_from_tuple(py_user_env_out);
rc = python_plugin_rc_to_int(py_rc);
cleanup:
if (PyErr_Occurred()) {
py_log_last_error(NULL);
rc = SUDO_RC_ERROR;
free(*command_info_out);
free(*argv_out);
free(*user_env_out);
*command_info_out = *argv_out = *user_env_out = NULL;
}
Py_XDECREF(py_argv);
Py_XDECREF(py_env_add);
Py_XDECREF(py_result);
if (rc == SUDO_RC_ACCEPT)
plugin_ctx.call_close = 1;
debug_return_int(rc);
}
static int
python_plugin_policy_list(int argc, char * const argv[], int verbose, const char *list_user, const char **errstr)
{
debug_decl(python_plugin_policy_list, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(plugin_ctx.py_interpreter);
PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv);
if (py_argv == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: Failed to create argv argument for the python call\n", __func__);
debug_return_int(SUDO_RC_ERROR);
}
int rc = python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(list),
Py_BuildValue("(Oiz)", py_argv, verbose, list_user));
Py_XDECREF(py_argv);
CALLBACK_SET_ERROR(&plugin_ctx, errstr);
debug_return_int(rc);
}
static int
python_plugin_policy_version(int verbose)
{
debug_decl(python_plugin_policy_version, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(plugin_ctx.py_interpreter);
debug_return_int(python_plugin_show_version(&plugin_ctx, CALLBACK_PYNAME(show_version),
verbose, PY_POLICY_PLUGIN_VERSION, "policy"));
}
static int
python_plugin_policy_validate(const char **errstr)
{
debug_decl(python_plugin_policy_validate, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(plugin_ctx.py_interpreter);
int rc = python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(validate), NULL);
CALLBACK_SET_ERROR(&plugin_ctx, errstr);
debug_return_int(rc);
}
static void
python_plugin_policy_invalidate(int unlinkit)
{
debug_decl(python_plugin_policy_invalidate, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(plugin_ctx.py_interpreter);
python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(invalidate),
Py_BuildValue("(i)", unlinkit));
debug_return;
}
static int
python_plugin_policy_init_session(struct passwd *pwd, char **user_env[], const char **errstr)
{
debug_decl(python_plugin_policy_init_session, PYTHON_DEBUG_CALLBACKS);
int rc = SUDO_RC_ERROR;
PyThreadState_Swap(plugin_ctx.py_interpreter);
PyObject *py_pwd = NULL, *py_user_env = NULL, *py_result = NULL;
py_pwd = py_from_passwd(pwd);
if (py_pwd == NULL)
goto cleanup;
py_user_env = py_str_array_to_tuple(*user_env);
if (py_user_env == NULL)
goto cleanup;
py_result = python_plugin_api_call(&plugin_ctx, CALLBACK_PYNAME(init_session),
Py_BuildValue("(OO)", py_pwd, py_user_env));
CALLBACK_SET_ERROR(&plugin_ctx, errstr);
if (py_result == NULL)
goto cleanup;
PyObject *py_user_env_out = NULL, *py_rc = NULL;
if (PyTuple_Check(py_result)) {
if (!PyArg_ParseTuple(py_result, "O!|O!:python_plugin.init_session",
&PyLong_Type, &py_rc,
&PyTuple_Type, &py_user_env_out)) {
goto cleanup;
}
} else {
py_rc = py_result;
}
if (py_user_env_out != NULL) {
str_array_free(user_env);
*user_env = py_str_array_from_tuple(py_user_env_out);
if (*user_env == NULL)
goto cleanup;
}
rc = python_plugin_rc_to_int(py_rc);
cleanup:
Py_XDECREF(py_pwd);
Py_XDECREF(py_user_env);
Py_XDECREF(py_result);
debug_return_int(rc);
}
sudo_dso_public struct policy_plugin python_policy = {
SUDO_POLICY_PLUGIN,
SUDO_API_VERSION,
CALLBACK_CFUNC(open),
CALLBACK_CFUNC(close),
CALLBACK_CFUNC(version),
CALLBACK_CFUNC(check),
CALLBACK_CFUNC(list),
CALLBACK_CFUNC(validate),
CALLBACK_CFUNC(invalidate),
CALLBACK_CFUNC(init_session),
NULL, /* register_hooks */
NULL, /* deregister_hooks */
NULL /* event_alloc */
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,182 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.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.
*/
/*
* 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 "iohelpers.h"
#include <sudo_fatal.h>
int
rmdir_recursive(const char *path)
{
char *cmd = NULL;
int success = false;
if (asprintf(&cmd, "rm -rf \"%s\"", path) < 0)
return false;
if (system(cmd) == 0)
success = true;
free(cmd);
return success;
}
int
fwriteall(const char *file_path, const char *string)
{
int success = false;
FILE *file = fopen(file_path, "w+");
if (file == NULL)
goto cleanup;
size_t size = strlen(string);
if (fwrite(string, 1, size, file) < size) {
goto cleanup;
}
success = true;
cleanup:
if (file)
fclose(file);
return success;
}
int
freadall(const char *file_path, char *output, size_t max_len)
{
int rc = false;
FILE *file = fopen(file_path, "rb");
if (file == NULL) {
sudo_warn_nodebug("failed to open file '%s'", file_path);
goto cleanup;
}
size_t len = fread(output, 1, max_len - 1, file);
output[len] = '\0';
if (ferror(file) != 0) {
sudo_warn_nodebug("failed to read file '%s'", file_path);
goto cleanup;
}
if (!feof(file)) {
sudo_warn_nodebug("file '%s' was bigger than allocated buffer %zu",
file_path, max_len);
goto cleanup;
}
rc = true;
cleanup:
if (file)
fclose(file);
return rc;
}
int
vsnprintf_append(char * restrict output, size_t max_output_len, const char * restrict fmt, va_list args)
{
va_list args2;
va_copy(args2, args);
size_t output_len = strlen(output);
int rc = vsnprintf(output + output_len, max_output_len - output_len, fmt, args2);
va_end(args2);
return rc;
}
int
snprintf_append(char * restrict output, size_t max_output_len, const char * restrict fmt, ...)
{
va_list args;
va_start(args, fmt);
int rc = vsnprintf_append(output, max_output_len, fmt, args);
va_end(args);
return rc;
}
int
str_array_count(char **str_array)
{
int result = 0;
for (; str_array[result] != NULL; ++result) {}
return result;
}
void
str_array_snprint(char *out_str, size_t max_len, char **str_array, int array_len)
{
if (array_len < 0)
array_len = str_array_count(str_array);
for (int pos = 0; pos < array_len; ++pos) {
snprintf_append(out_str, max_len, "%s%s", pos > 0 ? ", " : "", str_array[pos]);
}
}
char *
str_replaced(const char *source, size_t dest_len, const char *old, const char *new)
{
char *result = malloc(dest_len);
char *dest = result;
char *pos = NULL;
size_t old_len = strlen(old);
if (result == NULL)
return NULL;
while ((pos = strstr(source, old)) != NULL) {
size_t len = (size_t)snprintf(dest, dest_len,
"%.*s%s", (int)(pos - source), source, new);
if (len >= dest_len)
goto fail;
dest_len -= len;
dest += len;
source = pos + old_len;
}
if (strlcpy(dest, source, dest_len) >= dest_len)
goto fail;
return result;
fail:
free(result);
return strdup("str_replace_all failed, string too long");
}
void
str_replace_in_place(char *string, size_t max_length, const char *old, const char *new)
{
char *replaced = str_replaced(string, max_length, old, new);
if (replaced != NULL) {
strlcpy(string, replaced, max_length);
free(replaced);
}
}

View file

@ -0,0 +1,58 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.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 PYTHON_IO_HELPERS
#define PYTHON_IO_HELPERS
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include <compat/stdbool.h>
#endif /* HAVE_STDBOOL_H */
#include <string.h>
#include <stdarg.h>
#include <signal.h>
#include <pwd.h>
#include <sudo_compat.h>
#define MAX_OUTPUT (2 << 16)
int rmdir_recursive(const char *path);
int fwriteall(const char *file_path, const char *string);
int freadall(const char *file_path, char *output, size_t max_len);
// allocates new string with the content of 'string' but 'old' replaced to 'new'
// The allocated array will be dest_length size and null terminated correctly.
char *str_replaced(const char *string, size_t dest_length, const char *old, const char *new);
// same, but "string" must be able to store 'max_length' number of characters including the null terminator
void str_replace_in_place(char *string, size_t max_length, const char *old, const char *new);
int vsnprintf_append(char * restrict output, size_t max_output_len, const char * restrict fmt, va_list args);
int snprintf_append(char * restrict output, size_t max_output_len, const char * restrict fmt, ...);
int str_array_count(char **str_array);
void str_array_snprint(char *out_str, size_t max_len, char **str_array, int array_len);
#endif

View file

@ -0,0 +1,22 @@
import sudo
import json
class ApprovalTestPlugin(sudo.Plugin):
def __init__(self, plugin_options, **kwargs):
id = sudo.options_as_dict(plugin_options).get("Id", "")
super().__init__(plugin_options=plugin_options, **kwargs)
self._id = "(APPROVAL {})".format(id)
sudo.log_info("{} Constructed:".format(self._id))
sudo.log_info(json.dumps(self.__dict__, indent=4, sort_keys=True))
def __del__(self):
sudo.log_info("{} Destructed successfully".format(self._id))
def check(self, *args):
sudo.log_info("{} Check was called with arguments: "
"{}".format(self._id, args))
def show_version(self, *args):
sudo.log_info("{} Show version was called with arguments: "
"{}".format(self._id, args))

View file

@ -0,0 +1,11 @@
import sudo
import sys
sys.path = []
class ConflictPlugin(sudo.Plugin):
def __init__(self, plugin_options, **kwargs):
sudo.log_info("PATH before: {} (should be empty)".format(sys.path))
sys.path = [sudo.options_as_dict(plugin_options).get("Path")]
sudo.log_info("PATH set: {}".format(sys.path))

View file

@ -0,0 +1,18 @@
import sudo
# The purpose of this class is that all methods you call on its object
# raises a PluginError with a message containing the name of the called method.
# Eg. if you call "ErrorMsgPlugin().some_method()" it will raise
# "Something wrong in some_method"
class ErrorMsgPlugin(sudo.Plugin):
def __getattr__(self, name):
def raiser_func(*args):
raise sudo.PluginError("Something wrong in " + name)
return raiser_func
class ConstructErrorPlugin(sudo.Plugin):
def __init__(self, **kwargs):
raise sudo.PluginError("Something wrong in plugin constructor")

View file

@ -0,0 +1,7 @@
(AUDIT) -- Started by user testuser1 (123) --
(AUDIT) Requested command: id --help
(AUDIT) Accepted command: /sbin/id --help
(AUDIT) By the plugin: accepter plugin name (type=POLICY)
(AUDIT) Environment: KEY1=VALUE1 KEY2=VALUE2
(AUDIT) Command returned with exit code 2
(AUDIT) -- Finished --

View file

@ -0,0 +1,5 @@
(AUDIT) -- Started by user ??? (???) --
(AUDIT) Requested command: id
(AUDIT) Plugin errorer plugin name (type=AUDIT) got an error: Some error has happened
(AUDIT) Sudo has run into an error: 222
(AUDIT) -- Finished --

View file

@ -0,0 +1,5 @@
(AUDIT) -- Started by user root (0) --
(AUDIT) Requested command: passwd
(AUDIT) Rejected by plugin rejecter plugin name (type=IO): Rejected just because!
(AUDIT) The command was not executed
(AUDIT) -- Finished --

View file

@ -0,0 +1,6 @@
(AUDIT) -- Started by user root (0) --
Python Example Audit Plugin
Python audit plugin (API 1.0): SudoAuditPlugin (loaded from 'SRC_DIR/example_audit_plugin.py')
Python Example Audit Plugin (version=1.0)
(AUDIT) Sudo has run into an error: 222
(AUDIT) -- Finished --

View file

@ -0,0 +1 @@
sudo: loading more than 8 sudo python audit plugins is not supported

View file

@ -0,0 +1,14 @@
(AUDIT1) -- Started by user default (1000) --
(AUDIT1) Requested command: id --help
(AUDIT2) -- Started by user default (1000) --
(AUDIT2) Requested command: id --help
(AUDIT1) Accepted command: /sbin/id --help
(AUDIT1) By the plugin: accepter plugin name (type=POLICY)
(AUDIT1) Environment: KEY1=VALUE1 KEY2=VALUE2
(AUDIT2) Accepted command: /sbin/id --help
(AUDIT2) By the plugin: accepter plugin name (type=POLICY)
(AUDIT2) Environment: KEY1=VALUE1 KEY2=VALUE2
(AUDIT1) Command exited due to signal 11
(AUDIT1) -- Finished --
(AUDIT2) Command exited due to signal 11
(AUDIT2) -- Finished --

View file

@ -0,0 +1,3 @@
Question count: 2
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
Question 1: <<Secret reason: >> (timeout: 120, msg_type=5)

View file

@ -0,0 +1,3 @@
Please provide your reason for executing ('/bin/whoami',)
conversation suspend: signal SIGTSTP
conversation resume: signal was SIGCONT

View file

@ -0,0 +1,3 @@
Executed /bin/whoami
Reason: my fake reason
Hidden reason: my real secret reason

View file

@ -0,0 +1,3 @@
Question count: 2
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
Question 1: <<Secret reason: >> (timeout: 120, msg_type=5)

View file

@ -0,0 +1 @@
Please provide your reason for executing ('/bin/whoami',)

View file

@ -0,0 +1,3 @@
Executed /bin/whoami
Reason: my fake reason
Hidden reason: my real secret reason

View file

@ -0,0 +1,2 @@
Question count: 2
Question 0: <<Reason: >> (timeout: 120, msg_type=2)

View file

@ -0,0 +1,2 @@
Question count: 2
Question 0: <<Reason: >> (timeout: 120, msg_type=2)

View file

@ -0,0 +1 @@
You did not answer in time

View file

@ -0,0 +1 @@
Please provide your reason for executing ('/bin/whoami',)

View file

@ -0,0 +1,6 @@
sudo.debug was called with arguments: (DEBUG.ERROR, 'My demo purpose plugin shows this ERROR level debug message')
sudo.debug was called with arguments: (DEBUG.INFO, 'My demo purpose plugin shows this INFO level debug message')
LogHandler.emit was called
LogHandler.emit was called
sudo.options_as_dict was called with arguments: (('ModulePath=SRC_DIR/example_debugging.py', 'ClassName=DebugDemoPlugin'),)
sudo.options_as_dict returned result: [('ClassName', 'DebugDemoPlugin'), ('ModulePath', 'SRC_DIR/example_debugging.py')]

View file

@ -0,0 +1,11 @@
__init__ @ SRC_DIR/example_debugging.py:58 calls C function:
sudo.debug was called with arguments: (DEBUG.ERROR, 'My demo purpose plugin shows this ERROR level debug message')
__init__ @ SRC_DIR/example_debugging.py:63 calls C function:
sudo.debug was called with arguments: (DEBUG.INFO, 'My demo purpose plugin shows this INFO level debug message')
handle @ logging/__init__.py calls C function:
LogHandler.emit was called
handle @ logging/__init__.py calls C function:
LogHandler.emit was called
__init__ @ SRC_DIR/example_debugging.py:85 calls C function:
sudo.options_as_dict was called with arguments: (('ModulePath=SRC_DIR/example_debugging.py', 'ClassName=DebugDemoPlugin'),)
sudo.options_as_dict returned result: [('ClassName', 'DebugDemoPlugin'), ('ModulePath', 'SRC_DIR/example_debugging.py')]

View file

@ -0,0 +1,3 @@
importing module: SRC_DIR/example_debugging.py
Extending python 'path' with 'SRC_DIR'
Deinit was called for a python plugin

View file

@ -0,0 +1,2 @@
My demo purpose plugin shows this ERROR level debug message
Python log system shows this ERROR level debug message

View file

@ -0,0 +1,8 @@
__init__ @ SRC_DIR/example_debugging.py:58 debugs:
My demo purpose plugin shows this ERROR level debug message
__init__ @ SRC_DIR/example_debugging.py:63 debugs:
My demo purpose plugin shows this INFO level debug message
handle @ logging/__init__.py debugs:
Python log system shows this ERROR level debug message
handle @ logging/__init__.py debugs:
Python log system shows this INFO level debug message

View file

@ -0,0 +1,2 @@
DebugDemoPlugin.__init__ was called with arguments: () [('plugin_options', ('ModulePath=SRC_DIR/example_debugging.py', 'ClassName=DebugDemoPlugin')), ('settings', ('debug_flags=/tmp/sudo_check_python_exampleXXXXXX/debug.log py_calls@diag', 'plugin_path=python_plugin.so')), ('user_env', ()), ('user_info', ()), ('version', '1.0')]
DebugDemoPlugin.__init__ returned result: <example_debugging.DebugDemoPlugin object>

View file

@ -0,0 +1,9 @@
DebugDemoPlugin.__init__ was called with arguments: () [('plugin_options', ('ModulePath=SRC_DIR/example_debugging.py', 'ClassName=DebugDemoPlugin')), ('settings', ('debug_flags=/tmp/sudo_check_python_exampleXXXXXX/debug.log py_calls@info', 'plugin_path=python_plugin.so')), ('user_env', ()), ('user_info', ()), ('version', '1.0')]
DebugDemoPlugin.__init__ returned result: <example_debugging.DebugDemoPlugin object>
DebugDemoPlugin function 'log_ttyin' is not implemented
DebugDemoPlugin function 'log_ttyout' is not implemented
DebugDemoPlugin function 'log_stdin' is not implemented
DebugDemoPlugin function 'log_stdout' is not implemented
DebugDemoPlugin function 'log_stderr' is not implemented
DebugDemoPlugin function 'change_winsize' is not implemented
DebugDemoPlugin function 'log_suspend' is not implemented

View file

@ -0,0 +1 @@
Skipping close call, because there was no command run

View file

@ -0,0 +1,4 @@
SudoGroupPlugin.__init__ was called with arguments: () [('args', ('ModulePath=SRC_DIR/example_group_plugin.py', 'ClassName=SudoGroupPlugin')), ('version', '1.0')]
SudoGroupPlugin.__init__ returned result: <example_group_plugin.SudoGroupPlugin object>
SudoGroupPlugin.query was called with arguments: ('user', 'group', ('pw_name', 'pw_passwd', 1001, 101, 'pw_gecos', 'pw_dir', 'pw_shell'))
SudoGroupPlugin.query returned result: 0

View file

@ -0,0 +1 @@
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log

View file

@ -0,0 +1,16 @@
-- Plugin STARTED --
EXEC id --help
EXEC info [
"command=/bin/id",
"runas_uid=0"
]
STD IN some standard input
STD OUT some standard output
STD ERR some standard error
SUSPEND SIGTSTP
SUSPEND SIGCONT
WINSIZE 200x100
TTY IN some tty input
TTY OUT some tty output
CLOSE Command returned 1
-- Plugin DESTROYED --

View file

@ -0,0 +1 @@
sudo: loading more than 8 sudo python IO plugins is not supported

View file

@ -0,0 +1,2 @@
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX2/sudo.log

View file

@ -0,0 +1,16 @@
-- Plugin STARTED --
EXEC id --help
EXEC info [
"command=/bin/id",
"runas_uid=0"
]
STD IN stdin for plugin 1
STD OUT stdout for plugin 1
STD ERR stderr for plugin 1
SUSPEND SIGTSTP
SUSPEND SIGCONT
WINSIZE 20x10
TTY IN tty input for plugin 1
TTY OUT tty output for plugin 1
CLOSE Command returned 1
-- Plugin DESTROYED --

View file

@ -0,0 +1,16 @@
-- Plugin STARTED --
EXEC whoami
EXEC info [
"command=/bin/whoami",
"runas_uid=1"
]
STD IN stdin for plugin 2
STD OUT stdout for plugin 2
STD ERR stderr for plugin 2
SUSPEND SIGSTOP
SUSPEND SIGCONT
WINSIZE 30x40
TTY IN tty input for plugin 2
TTY OUT tty output for plugin 2
CLOSE Command returned 2
-- Plugin DESTROYED --

View file

@ -0,0 +1 @@
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log

View file

@ -0,0 +1,8 @@
-- Plugin STARTED --
EXEC cmd
EXEC info [
"command=/usr/share/cmd",
"runas_uid=0"
]
CLOSE Failed to execute, execve returned 1 (EPERM)
-- Plugin DESTROYED --

View file

@ -0,0 +1 @@
Failed to construct plugin instance: [Errno 2] No such file or directory: '/some/not/writable/directory/sudo.log'

View file

@ -0,0 +1,7 @@
Example sudo python plugin will log to /some/not/writable/directory/sudo.log
Traceback:
File "SRC_DIR/example_io_plugin.py", line 64, in __init__
self._open_log_file(path.join(log_path, "sudo.log"))
File "SRC_DIR/example_io_plugin.py", line 134, in _open_log_file
self._log_file = open(log_path, "a")

View file

@ -0,0 +1,2 @@
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
Python Example IO Plugin version: 1.0

View file

@ -0,0 +1,2 @@
-- Plugin STARTED --
-- Plugin DESTROYED --

View file

@ -0,0 +1,3 @@
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
Python io plugin (API 1.0): SudoIOPlugin (loaded from 'SRC_DIR/example_io_plugin.py')
Python Example IO Plugin version: 1.0

View file

@ -0,0 +1 @@
The command returned with exit_status 3

View file

@ -0,0 +1 @@
You are not allowed to run this command!

View file

@ -0,0 +1 @@
Failed to execute command, execve syscall returned 2 (ENOENT)

View file

@ -0,0 +1,25 @@
-- minimal --
Only the following commands are allowed: id, whoami
-- minimal (verbose) --
Only the following commands are allowed: id, whoami
-- with user --
Only the following commands are allowed: id, whoami as user 'testuser'
-- with user (verbose) --
Only the following commands are allowed: id, whoami as user 'testuser'
-- with allowed program --
You are allowed to execute command '/bin/id'
-- with allowed program (verbose) --
You are allowed to execute command '/bin/id'
Only the following commands are allowed: id, whoami
-- with denied program --
You are NOT allowed to execute command '/bin/passwd'
-- with denied program (verbose) --
You are NOT allowed to execute command '/bin/passwd'
Only the following commands are allowed: id, whoami

View file

@ -0,0 +1,8 @@
SudoPolicyPlugin.__init__ was called with arguments: () [('plugin_options', ('ModulePath=SRC_DIR/example_policy_plugin.py', 'ClassName=SudoPolicyPlugin')), ('settings', ()), ('user_env', ()), ('user_info', ()), ('version', '1.0')]
SudoPolicyPlugin.__init__ returned result: <example_policy_plugin.SudoPolicyPlugin object>
SudoPolicyPlugin.validate was called with arguments: ()
SudoPolicyPlugin.validate returned result: None
SudoPolicyPlugin.invalidate was called with arguments: (1,)
SudoPolicyPlugin.invalidate returned result: None
SudoPolicyPlugin.invalidate was called with arguments: (0,)
SudoPolicyPlugin.invalidate returned result: None

View file

@ -0,0 +1 @@
Python Example Policy Plugin version: 1.0

View file

@ -0,0 +1,2 @@
Python policy plugin (API 1.0): SudoPolicyPlugin (loaded from 'SRC_DIR/example_policy_plugin.py')
Python Example Policy Plugin version: 1.0

View file

@ -0,0 +1,3 @@
No plugin class is specified for python module 'SRC_DIR/regress/plugin_errorstr.py'. Use 'ClassName' configuration option in 'sudo.conf'
Possible plugins: ConstructErrorPlugin, ErrorMsgPlugin
Failed during loading plugin class

View file

@ -0,0 +1,2 @@
No python module path is specified. Use 'ModulePath' plugin config option in 'sudo.conf'
Failed during loading plugin class

View file

@ -0,0 +1 @@
Failed during loading plugin class: File 'SRC_DIR/example_debugging.py' must be owned by uid 0

View file

@ -0,0 +1,2 @@
Failed to find plugin class 'MispelledPluginName'
Failed during loading plugin class

View file

@ -0,0 +1 @@
Failed during loading plugin class: No module named 'wrong_path'

View file

@ -0,0 +1 @@
Python io plugin (API 1.0): DebugDemoPlugin (loaded from 'SRC_DIR/example_debugging.py')

View file

@ -0,0 +1 @@
sudo: loading more than 8 sudo python approval plugins is not supported

View file

@ -0,0 +1,67 @@
(APPROVAL 1) Constructed:
{
"_id": "(APPROVAL 1)",
"plugin_options": [
"ModulePath=SRC_DIR/regress/plugin_approval_test.py",
"ClassName=ApprovalTestPlugin",
"Id=1"
],
"settings": [
"SETTING1=VALUE1",
"setting2=value2"
],
"submit_argv": [
"sudo",
"-u",
"user",
"whoami",
"--help"
],
"submit_optind": 3,
"user_env": [
"USER_ENV1=VALUE1",
"USER_ENV2=value2"
],
"user_info": [
"INFO1=VALUE1",
"info2=value2"
],
"version": "1.22"
}
(APPROVAL 2) Constructed:
{
"_id": "(APPROVAL 2)",
"plugin_options": [
"ModulePath=SRC_DIR/regress/plugin_approval_test.py",
"ClassName=ApprovalTestPlugin",
"Id=2"
],
"settings": [
"SETTING1=VALUE1",
"setting2=value2"
],
"submit_argv": [
"sudo",
"-u",
"user",
"whoami",
"--help"
],
"submit_optind": 3,
"user_env": [
"USER_ENV1=VALUE1",
"USER_ENV2=value2"
],
"user_info": [
"INFO1=VALUE1",
"info2=value2"
],
"version": "1.22"
}
(APPROVAL 1) Show version was called with arguments: (0,)
Python approval plugin (API 1.0): ApprovalTestPlugin (loaded from 'SRC_DIR/regress/plugin_approval_test.py')
(APPROVAL 2) Show version was called with arguments: (1,)
(APPROVAL 1) Check was called with arguments: (('CMDINFO1=value1', 'CMDINFO2=VALUE2'), ('whoami', '--help'), ('USER_ENV1=VALUE1', 'USER_ENV2=value2'))
(APPROVAL 2) Check was called with arguments: (('CMDINFO1=value1', 'CMDINFO2=VALUE2'), ('whoami', '--help'), ('USER_ENV1=VALUE1', 'USER_ENV2=value2'))
(APPROVAL 1) Destructed successfully
(APPROVAL 2) Destructed successfully

Some files were not shown because too many files have changed in this diff Show more