Adding upstream version 1.9.16p2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
parent
ebbaee52bc
commit
182f151a13
1342 changed files with 621215 additions and 0 deletions
543
plugins/python/Makefile.in
Normal file
543
plugins/python/Makefile.in
Normal 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 $@
|
18
plugins/python/example_approval_plugin.py
Normal file
18
plugins/python/example_approval_plugin.py
Normal 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)
|
84
plugins/python/example_audit_plugin.py
Normal file
84
plugins/python/example_audit_plugin.py
Normal 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
|
98
plugins/python/example_conversation.py
Normal file
98
plugins/python/example_conversation.py
Normal 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")
|
85
plugins/python/example_debugging.py
Normal file
85
plugins/python/example_debugging.py
Normal 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)
|
45
plugins/python/example_group_plugin.py
Normal file
45
plugins/python/example_group_plugin.py
Normal 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
|
153
plugins/python/example_io_plugin.py
Normal file
153
plugins/python/example_io_plugin.py
Normal 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)
|
172
plugins/python/example_policy_plugin.py
Normal file
172
plugins/python/example_policy_plugin.py
Normal 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))
|
1
plugins/python/lsan_suppr.txt
Normal file
1
plugins/python/lsan_suppr.txt
Normal file
|
@ -0,0 +1 @@
|
|||
leak:libpython
|
587
plugins/python/pyhelpers.c
Normal file
587
plugins/python/pyhelpers.c
Normal 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
108
plugins/python/pyhelpers.h
Normal 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
|
45
plugins/python/pyhelpers_cpychecker.h
Normal file
45
plugins/python/pyhelpers_cpychecker.h
Normal 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
|
88
plugins/python/python_baseplugin.c
Normal file
88
plugins/python/python_baseplugin.c
Normal 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);
|
||||
}
|
155
plugins/python/python_convmessage.c
Normal file
155
plugins/python/python_convmessage.c
Normal 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);
|
||||
}
|
||||
|
182
plugins/python/python_loghandler.c
Normal file
182
plugins/python/python_loghandler.c
Normal 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);
|
||||
}
|
8
plugins/python/python_plugin.exp
Normal file
8
plugins/python/python_plugin.exp
Normal file
|
@ -0,0 +1,8 @@
|
|||
group_plugin
|
||||
python_approval
|
||||
python_approval_clone
|
||||
python_audit
|
||||
python_audit_clone
|
||||
python_io
|
||||
python_io_clone
|
||||
python_policy
|
196
plugins/python/python_plugin_approval.c
Normal file
196
plugins/python/python_plugin_approval.c
Normal 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;
|
||||
}
|
57
plugins/python/python_plugin_approval_multi.inc
Normal file
57
plugins/python/python_plugin_approval_multi.inc
Normal 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
|
281
plugins/python/python_plugin_audit.c
Normal file
281
plugins/python/python_plugin_audit.c
Normal 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;
|
||||
}
|
78
plugins/python/python_plugin_audit_multi.inc
Normal file
78
plugins/python/python_plugin_audit_multi.inc
Normal 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
|
781
plugins/python/python_plugin_common.c
Normal file
781
plugins/python/python_plugin_common.c
Normal 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;
|
||||
}
|
85
plugins/python/python_plugin_common.h
Normal file
85
plugins/python/python_plugin_common.h
Normal 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
|
114
plugins/python/python_plugin_group.c
Normal file
114
plugins/python/python_plugin_group.c
Normal 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)
|
||||
};
|
276
plugins/python/python_plugin_io.c
Normal file
276
plugins/python/python_plugin_io.c
Normal 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;
|
||||
}
|
99
plugins/python/python_plugin_io_multi.inc
Normal file
99
plugins/python/python_plugin_io_multi.inc
Normal 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
|
289
plugins/python/python_plugin_policy.c
Normal file
289
plugins/python/python_plugin_policy.c
Normal 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 */
|
||||
};
|
1619
plugins/python/regress/check_python_examples.c
Normal file
1619
plugins/python/regress/check_python_examples.c
Normal file
File diff suppressed because it is too large
Load diff
182
plugins/python/regress/iohelpers.c
Normal file
182
plugins/python/regress/iohelpers.c
Normal 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);
|
||||
}
|
||||
}
|
58
plugins/python/regress/iohelpers.h
Normal file
58
plugins/python/regress/iohelpers.h
Normal 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
|
22
plugins/python/regress/plugin_approval_test.py
Normal file
22
plugins/python/regress/plugin_approval_test.py
Normal 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))
|
11
plugins/python/regress/plugin_conflict.py
Normal file
11
plugins/python/regress/plugin_conflict.py
Normal 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))
|
18
plugins/python/regress/plugin_errorstr.py
Normal file
18
plugins/python/regress/plugin_errorstr.py
Normal 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")
|
7
plugins/python/regress/testdata/check_example_audit_plugin_receives_accept.stdout
vendored
Normal file
7
plugins/python/regress/testdata/check_example_audit_plugin_receives_accept.stdout
vendored
Normal 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 --
|
5
plugins/python/regress/testdata/check_example_audit_plugin_receives_error.stdout
vendored
Normal file
5
plugins/python/regress/testdata/check_example_audit_plugin_receives_error.stdout
vendored
Normal 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 --
|
5
plugins/python/regress/testdata/check_example_audit_plugin_receives_reject.stdout
vendored
Normal file
5
plugins/python/regress/testdata/check_example_audit_plugin_receives_reject.stdout
vendored
Normal 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 --
|
6
plugins/python/regress/testdata/check_example_audit_plugin_version_display.stdout
vendored
Normal file
6
plugins/python/regress/testdata/check_example_audit_plugin_version_display.stdout
vendored
Normal 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 --
|
1
plugins/python/regress/testdata/check_example_audit_plugin_workflow_multiple.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_audit_plugin_workflow_multiple.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
sudo: loading more than 8 sudo python audit plugins is not supported
|
14
plugins/python/regress/testdata/check_example_audit_plugin_workflow_multiple.stdout
vendored
Normal file
14
plugins/python/regress/testdata/check_example_audit_plugin_workflow_multiple.stdout
vendored
Normal 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 --
|
|
@ -0,0 +1,3 @@
|
|||
Question count: 2
|
||||
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
|
||||
Question 1: <<Secret reason: >> (timeout: 120, msg_type=5)
|
|
@ -0,0 +1,3 @@
|
|||
Please provide your reason for executing ('/bin/whoami',)
|
||||
conversation suspend: signal SIGTSTP
|
||||
conversation resume: signal was SIGCONT
|
|
@ -0,0 +1,3 @@
|
|||
Executed /bin/whoami
|
||||
Reason: my fake reason
|
||||
Hidden reason: my real secret reason
|
|
@ -0,0 +1,3 @@
|
|||
Question count: 2
|
||||
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
|
||||
Question 1: <<Secret reason: >> (timeout: 120, msg_type=5)
|
|
@ -0,0 +1 @@
|
|||
Please provide your reason for executing ('/bin/whoami',)
|
|
@ -0,0 +1,3 @@
|
|||
Executed /bin/whoami
|
||||
Reason: my fake reason
|
||||
Hidden reason: my real secret reason
|
2
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.conv
vendored
Normal file
2
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.conv
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Question count: 2
|
||||
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
|
|
@ -0,0 +1,2 @@
|
|||
Question count: 2
|
||||
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
|
1
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
You did not answer in time
|
1
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Please provide your reason for executing ('/bin/whoami',)
|
6
plugins/python/regress/testdata/check_example_debugging_c_calls@diag.log
vendored
Normal file
6
plugins/python/regress/testdata/check_example_debugging_c_calls@diag.log
vendored
Normal 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')]
|
11
plugins/python/regress/testdata/check_example_debugging_c_calls@info.log
vendored
Normal file
11
plugins/python/regress/testdata/check_example_debugging_c_calls@info.log
vendored
Normal 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')]
|
3
plugins/python/regress/testdata/check_example_debugging_load@diag.log
vendored
Normal file
3
plugins/python/regress/testdata/check_example_debugging_load@diag.log
vendored
Normal 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
|
2
plugins/python/regress/testdata/check_example_debugging_plugin@err.log
vendored
Normal file
2
plugins/python/regress/testdata/check_example_debugging_plugin@err.log
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
My demo purpose plugin shows this ERROR level debug message
|
||||
Python log system shows this ERROR level debug message
|
8
plugins/python/regress/testdata/check_example_debugging_plugin@info.log
vendored
Normal file
8
plugins/python/regress/testdata/check_example_debugging_plugin@info.log
vendored
Normal 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
|
2
plugins/python/regress/testdata/check_example_debugging_py_calls@diag.log
vendored
Normal file
2
plugins/python/regress/testdata/check_example_debugging_py_calls@diag.log
vendored
Normal 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>
|
9
plugins/python/regress/testdata/check_example_debugging_py_calls@info.log
vendored
Normal file
9
plugins/python/regress/testdata/check_example_debugging_py_calls@info.log
vendored
Normal 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
|
1
plugins/python/regress/testdata/check_example_debugging_sudo_cb@info.log
vendored
Normal file
1
plugins/python/regress/testdata/check_example_debugging_sudo_cb@info.log
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Skipping close call, because there was no command run
|
4
plugins/python/regress/testdata/check_example_group_plugin_is_able_to_debug.log
vendored
Normal file
4
plugins/python/regress/testdata/check_example_group_plugin_is_able_to_debug.log
vendored
Normal 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
|
0
plugins/python/regress/testdata/check_example_io_plugin_command_log.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_io_plugin_command_log.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_command_log.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_command_log.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
|
16
plugins/python/regress/testdata/check_example_io_plugin_command_log.stored
vendored
Normal file
16
plugins/python/regress/testdata/check_example_io_plugin_command_log.stored
vendored
Normal 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 --
|
1
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
sudo: loading more than 8 sudo python IO plugins is not supported
|
2
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple.stdout
vendored
Normal 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
|
16
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple1.stored
vendored
Normal file
16
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple1.stored
vendored
Normal 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 --
|
16
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple2.stored
vendored
Normal file
16
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple2.stored
vendored
Normal 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 --
|
0
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
|
8
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stored
vendored
Normal file
8
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stored
vendored
Normal 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 --
|
1
plugins/python/regress/testdata/check_example_io_plugin_fails_with_python_backtrace.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_fails_with_python_backtrace.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Failed to construct plugin instance: [Errno 2] No such file or directory: '/some/not/writable/directory/sudo.log'
|
7
plugins/python/regress/testdata/check_example_io_plugin_fails_with_python_backtrace.stdout
vendored
Normal file
7
plugins/python/regress/testdata/check_example_io_plugin_fails_with_python_backtrace.stdout
vendored
Normal 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")
|
||||
|
0
plugins/python/regress/testdata/check_example_io_plugin_version_display.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_io_plugin_version_display.stderr
vendored
Normal file
2
plugins/python/regress/testdata/check_example_io_plugin_version_display.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_example_io_plugin_version_display.stdout
vendored
Normal 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
|
2
plugins/python/regress/testdata/check_example_io_plugin_version_display.stored
vendored
Normal file
2
plugins/python/regress/testdata/check_example_io_plugin_version_display.stored
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- Plugin STARTED --
|
||||
-- Plugin DESTROYED --
|
3
plugins/python/regress/testdata/check_example_io_plugin_version_display_full.stdout
vendored
Normal file
3
plugins/python/regress/testdata/check_example_io_plugin_version_display_full.stdout
vendored
Normal 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
|
0
plugins/python/regress/testdata/check_example_policy_plugin_accepted_execution.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_accepted_execution.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_accepted_execution.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_accepted_execution.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
The command returned with exit_status 3
|
1
plugins/python/regress/testdata/check_example_policy_plugin_denied_execution.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_denied_execution.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
You are not allowed to run this command!
|
0
plugins/python/regress/testdata/check_example_policy_plugin_denied_execution.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_denied_execution.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_failed_execution.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_failed_execution.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Failed to execute command, execve syscall returned 2 (ENOENT)
|
0
plugins/python/regress/testdata/check_example_policy_plugin_failed_execution.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_failed_execution.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_list.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_list.stderr
vendored
Normal file
25
plugins/python/regress/testdata/check_example_policy_plugin_list.stdout
vendored
Normal file
25
plugins/python/regress/testdata/check_example_policy_plugin_list.stdout
vendored
Normal 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
|
8
plugins/python/regress/testdata/check_example_policy_plugin_validate_invalidate.log
vendored
Normal file
8
plugins/python/regress/testdata/check_example_policy_plugin_validate_invalidate.log
vendored
Normal 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
|
0
plugins/python/regress/testdata/check_example_policy_plugin_version_display.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_version_display.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_version_display.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_version_display.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Python Example Policy Plugin version: 1.0
|
2
plugins/python/regress/testdata/check_example_policy_plugin_version_display_full.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_example_policy_plugin_version_display_full.stdout
vendored
Normal 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
|
3
plugins/python/regress/testdata/check_loading_fails_missing_classname.stderr
vendored
Normal file
3
plugins/python/regress/testdata/check_loading_fails_missing_classname.stderr
vendored
Normal 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
|
0
plugins/python/regress/testdata/check_loading_fails_missing_classname.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_missing_classname.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_loading_fails_missing_path.stderr
vendored
Normal file
2
plugins/python/regress/testdata/check_loading_fails_missing_path.stderr
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
No python module path is specified. Use 'ModulePath' plugin config option in 'sudo.conf'
|
||||
Failed during loading plugin class
|
0
plugins/python/regress/testdata/check_loading_fails_missing_path.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_missing_path.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_fails_not_owned_by_root.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_fails_not_owned_by_root.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Failed during loading plugin class: File 'SRC_DIR/example_debugging.py' must be owned by uid 0
|
0
plugins/python/regress/testdata/check_loading_fails_not_owned_by_root.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_not_owned_by_root.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_loading_fails_wrong_classname.stderr
vendored
Normal file
2
plugins/python/regress/testdata/check_loading_fails_wrong_classname.stderr
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Failed to find plugin class 'MispelledPluginName'
|
||||
Failed during loading plugin class
|
0
plugins/python/regress/testdata/check_loading_fails_wrong_classname.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_wrong_classname.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_fails_wrong_path.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_fails_wrong_path.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Failed during loading plugin class: No module named 'wrong_path'
|
0
plugins/python/regress/testdata/check_loading_fails_wrong_path.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_wrong_path.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_succeeds_with_missing_classname.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_succeeds_with_missing_classname.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Python io plugin (API 1.0): DebugDemoPlugin (loaded from 'SRC_DIR/example_debugging.py')
|
1
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
sudo: loading more than 8 sudo python approval plugins is not supported
|
67
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout
vendored
Normal file
67
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout
vendored
Normal 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
Loading…
Add table
Add a link
Reference in a new issue