plugins/python/regress: adds tests for python plugin feature and examples
This commit is contained in:

committed by
Todd C. Miller

parent
0b2d0334b7
commit
5da7bd562c
5
MANIFEST
5
MANIFEST
@@ -323,6 +323,11 @@ plugins/python/sudo_python_debug.c
|
|||||||
plugins/python/sudo_python_debug.h
|
plugins/python/sudo_python_debug.h
|
||||||
plugins/python/sudo_python_module.c
|
plugins/python/sudo_python_module.c
|
||||||
plugins/python/sudo_python_module.h
|
plugins/python/sudo_python_module.h
|
||||||
|
plugins/python/regress/check_python_examples.c
|
||||||
|
plugins/python/regress/iohelpers.c
|
||||||
|
plugins/python/regress/iohelpers.h
|
||||||
|
plugins/python/regress/testhelpers.c
|
||||||
|
plugins/python/regress/testhelpers.h
|
||||||
plugins/sample/Makefile.in
|
plugins/sample/Makefile.in
|
||||||
plugins/sample/README
|
plugins/sample/README
|
||||||
plugins/sample/sample_plugin.c
|
plugins/sample/sample_plugin.c
|
||||||
|
@@ -26,6 +26,7 @@ srcdir = @srcdir@
|
|||||||
devdir = @devdir@
|
devdir = @devdir@
|
||||||
top_builddir = @top_builddir@
|
top_builddir = @top_builddir@
|
||||||
top_srcdir = @top_srcdir@
|
top_srcdir = @top_srcdir@
|
||||||
|
abs_srcdir = @abs_srcdir@
|
||||||
incdir = $(top_srcdir)/include
|
incdir = $(top_srcdir)/include
|
||||||
cross_compiling = @CROSS_COMPILING@
|
cross_compiling = @CROSS_COMPILING@
|
||||||
|
|
||||||
@@ -44,8 +45,10 @@ INSTALL_BACKUP = @INSTALL_BACKUP@
|
|||||||
LT_LIBS = $(top_builddir)/lib/util/libsudo_util.la
|
LT_LIBS = $(top_builddir)/lib/util/libsudo_util.la
|
||||||
LIBS = $(LT_LIBS)
|
LIBS = $(LT_LIBS)
|
||||||
|
|
||||||
|
LIBPYTHONPLUGIN = python_plugin.la
|
||||||
|
|
||||||
# C preprocessor flags
|
# C preprocessor flags
|
||||||
CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(top_srcdir) @CPPFLAGS@ @PYTHON_INCLUDE@
|
CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(top_srcdir) -DSRC_DIR=\"$(abs_srcdir)\" @CPPFLAGS@ @PYTHON_INCLUDE@
|
||||||
|
|
||||||
# Usually -O and/or -g
|
# Usually -O and/or -g
|
||||||
CFLAGS = @CFLAGS@
|
CFLAGS = @CFLAGS@
|
||||||
@@ -122,12 +125,19 @@ LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
|
|||||||
|
|
||||||
VERSION = @PACKAGE_VERSION@
|
VERSION = @PACKAGE_VERSION@
|
||||||
|
|
||||||
|
TEST_PROGS = check_python_examples
|
||||||
|
|
||||||
|
CHECK_PYTHON_EXAMPLES_OBJS = check_python_examples.o iohelpers.o testhelpers.o
|
||||||
|
|
||||||
all: python_plugin.la
|
all: python_plugin.la
|
||||||
|
|
||||||
Makefile: $(srcdir)/Makefile.in
|
Makefile: $(srcdir)/Makefile.in
|
||||||
cd $(top_builddir) && ./config.status --file plugins/python/Makefile
|
cd $(top_builddir) && ./config.status --file plugins/python/Makefile
|
||||||
|
|
||||||
.SUFFIXES: .c .h .i .lo .plog
|
.SUFFIXES: .c .h .i .lo .plog .o
|
||||||
|
|
||||||
|
.c.o:
|
||||||
|
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $<
|
||||||
|
|
||||||
.c.lo:
|
.c.lo:
|
||||||
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $<
|
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $<
|
||||||
@@ -182,11 +192,9 @@ pvs-log-files: $(POBJS)
|
|||||||
pvs-studio: $(POBJS)
|
pvs-studio: $(POBJS)
|
||||||
plog-converter $(PVS_LOG_OPTS) $(POBJS)
|
plog-converter $(PVS_LOG_OPTS) $(POBJS)
|
||||||
|
|
||||||
check:
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f *.lo *.o *.la
|
-$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f *.lo *.o *.la
|
||||||
-rm -f *.i *.plog stamp-* core *.core core.*
|
-rm -f *.i *.plog stamp-* core *.core core.* $(TEST_PROGS)
|
||||||
|
|
||||||
mostlyclean: clean
|
mostlyclean: clean
|
||||||
|
|
||||||
@@ -200,7 +208,27 @@ realclean: distclean
|
|||||||
|
|
||||||
cleandir: realclean
|
cleandir: realclean
|
||||||
|
|
||||||
|
check: $(TEST_PROGS)
|
||||||
|
@if test X"$(cross_compiling)" != X"yes"; then \
|
||||||
|
./check_python_examples; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_python_examples: $(CHECK_PYTHON_EXAMPLES_OBJS) $(LIBPYTHONPLUGIN)
|
||||||
|
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_PYTHON_EXAMPLES_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) $(LIBPYTHONPLUGIN)
|
||||||
|
|
||||||
# Autogenerated dependencies, do not modify
|
# Autogenerated dependencies, do not modify
|
||||||
|
check_python_examples.o: $(srcdir)/regress/check_python_examples.c
|
||||||
|
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/check_python_examples.c
|
||||||
|
check_python_examples.i: $(srcdir)/regress/check_python_examples.c
|
||||||
|
$(CC) -E -o $@ $(CPPFLAGS) $<
|
||||||
|
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 $< --output-file $@
|
||||||
|
iohelpers.o: $(srcdir)/regress/iohelpers.c
|
||||||
|
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iohelpers.c
|
||||||
|
iohelpers.i: $(srcdir)/regress/iohelpers.c
|
||||||
|
$(CC) -E -o $@ $(CPPFLAGS) $<
|
||||||
|
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 $< --output-file $@
|
||||||
pyhelpers.lo: $(srcdir)/pyhelpers.c
|
pyhelpers.lo: $(srcdir)/pyhelpers.c
|
||||||
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/pyhelpers.c
|
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/pyhelpers.c
|
||||||
pyhelpers.i: $(srcdir)/pyhelpers.c
|
pyhelpers.i: $(srcdir)/pyhelpers.c
|
||||||
@@ -271,3 +299,9 @@ sudo_python_module.i: $(srcdir)/sudo_python_module.c
|
|||||||
$(CC) -E -o $@ $(CPPFLAGS) $<
|
$(CC) -E -o $@ $(CPPFLAGS) $<
|
||||||
sudo_python_module.plog: sudo_python_module.i
|
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 $< --output-file $@
|
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/sudo_python_module.c --i-file $< --output-file $@
|
||||||
|
testhelpers.o: $(srcdir)/regress/testhelpers.c
|
||||||
|
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/testhelpers.c
|
||||||
|
testhelpers.i: $(srcdir)/regress/testhelpers.c
|
||||||
|
$(CC) -E -o $@ $(CPPFLAGS) $<
|
||||||
|
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 $< --output-file $@
|
||||||
|
718
plugins/python/regress/check_python_examples.c
Normal file
718
plugins/python/regress/check_python_examples.c
Normal file
@@ -0,0 +1,718 @@
|
|||||||
|
#include "testhelpers.h"
|
||||||
|
|
||||||
|
extern struct io_plugin python_io;
|
||||||
|
extern struct policy_plugin python_policy;
|
||||||
|
extern struct sudoers_group_plugin group_plugin;
|
||||||
|
|
||||||
|
void
|
||||||
|
create_io_plugin_options(const char *log_path)
|
||||||
|
{
|
||||||
|
static char logpath_keyvalue[PATH_MAX + 16];
|
||||||
|
snprintf(logpath_keyvalue, sizeof(logpath_keyvalue), "LogPath=%s", log_path);
|
||||||
|
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(
|
||||||
|
4,
|
||||||
|
"ModulePath=" SRC_DIR "/example_io_plugin.py",
|
||||||
|
"ClassName=SudoIOPlugin",
|
||||||
|
logpath_keyvalue,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
create_group_plugin_options(void)
|
||||||
|
{
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(
|
||||||
|
3,
|
||||||
|
"ModulePath=" SRC_DIR "/example_group_plugin.py",
|
||||||
|
"ClassName=SudoGroupPlugin",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
create_debugging_plugin_options(void)
|
||||||
|
{
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(
|
||||||
|
3,
|
||||||
|
"ModulePath=" SRC_DIR "/example_debugging.py",
|
||||||
|
"ClassName=DebugDemoPlugin",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
create_conversation_plugin_options(void)
|
||||||
|
{
|
||||||
|
static char logpath_keyvalue[PATH_MAX + 16];
|
||||||
|
snprintf(logpath_keyvalue, sizeof(logpath_keyvalue), "LogPath=%s", data.tmp_dir);
|
||||||
|
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(
|
||||||
|
4,
|
||||||
|
"ModulePath=" SRC_DIR "/example_conversation.py",
|
||||||
|
"ClassName=ReasonLoggerIOPlugin",
|
||||||
|
logpath_keyvalue,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
create_policy_plugin_options(void)
|
||||||
|
{
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(
|
||||||
|
3,
|
||||||
|
"ModulePath=" SRC_DIR "/example_policy_plugin.py",
|
||||||
|
"ClassName=SudoPolicyPlugin",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
init(void)
|
||||||
|
{
|
||||||
|
// always start each test from clean state
|
||||||
|
memset(&data, 0, sizeof(data));
|
||||||
|
|
||||||
|
VERIFY_TRUE(asprintf(&data.tmp_dir, TEMP_PATH_TEMPLATE) >= 0);
|
||||||
|
VERIFY_NOT_NULL(mkdtemp(data.tmp_dir));
|
||||||
|
|
||||||
|
// by default we test in developer mode, so the python plugin can be loaded
|
||||||
|
sudo_conf_clear_paths();
|
||||||
|
VERIFY_INT(sudo_conf_read(sudo_conf_developer_mode, SUDO_CONF_ALL), true);
|
||||||
|
|
||||||
|
// some default values for the plugin open:
|
||||||
|
data.settings = create_str_array(1, NULL);
|
||||||
|
data.user_info = create_str_array(1, NULL);
|
||||||
|
data.command_info = create_str_array(1, NULL);
|
||||||
|
data.command_info = create_str_array(1, NULL);
|
||||||
|
data.plugin_argc = 0;
|
||||||
|
data.plugin_argv = create_str_array(1, NULL);
|
||||||
|
data.user_env = create_str_array(1, NULL);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cleanup(int success)
|
||||||
|
{
|
||||||
|
if (!success) {
|
||||||
|
printf("\nThe output of the plugin:\n%s", data.stdout_str);
|
||||||
|
printf("\nThe error output of the plugin:\n%s", data.stderr_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_TRUE(rmdir_recursive(data.tmp_dir));
|
||||||
|
|
||||||
|
free(data.settings);
|
||||||
|
free(data.user_info);
|
||||||
|
free(data.command_info);
|
||||||
|
free(data.plugin_argv);
|
||||||
|
free(data.user_env);
|
||||||
|
free(data.plugin_options);
|
||||||
|
|
||||||
|
VERIFY_FALSE(Py_IsInitialized());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_io_plugin_version_display(int is_verbose)
|
||||||
|
{
|
||||||
|
create_io_plugin_options(data.tmp_dir);
|
||||||
|
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv, data.user_env,
|
||||||
|
data.plugin_options), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.show_version(is_verbose), SUDO_RC_OK);
|
||||||
|
|
||||||
|
python_io.close(0, 0); // this should not call the python plugin close as there was no command run invocation
|
||||||
|
|
||||||
|
if (is_verbose) {
|
||||||
|
// Note: the exact python version is environment dependant
|
||||||
|
VERIFY_STR_CONTAINS(data.stdout_str, "Python interpreter version:");
|
||||||
|
VERIFY_STR_CONTAINS(data.stdout_str, "Python io plugin API version");
|
||||||
|
} else {
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_io_plugin_version_display.stdout"));
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_STDERR(expected_path("check_example_io_plugin_version_display.stderr"));
|
||||||
|
VERIFY_FILE("sudo.log", expected_path("check_example_io_plugin_version_display.stored"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_io_plugin_command_log(void)
|
||||||
|
{
|
||||||
|
create_io_plugin_options(data.tmp_dir);
|
||||||
|
|
||||||
|
free(data.plugin_argv);
|
||||||
|
data.plugin_argc = 2;
|
||||||
|
data.plugin_argv = create_str_array(3, "id", "--help", NULL);
|
||||||
|
|
||||||
|
free(data.command_info);
|
||||||
|
data.command_info = create_str_array(3, "command=/bin/id", "runas_uid=0", NULL);
|
||||||
|
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||||
|
data.user_env, data.plugin_options), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.log_stdin("some standard input", strlen("some standard input")), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.log_stdout("some standard output", strlen("some standard output")), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.log_stderr("some standard error", strlen("some standard error")), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.log_suspend(SIGTSTP), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.log_suspend(SIGCONT), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.change_winsize(200, 100), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.log_ttyin("some tty input", strlen("some tty input")), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_io.log_ttyout("some tty output", strlen("some tty output")), SUDO_RC_OK);
|
||||||
|
|
||||||
|
python_io.close(1, 0); // successful execution, command returned 1
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_io_plugin_command_log.stdout"));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_io_plugin_command_log.stderr"));
|
||||||
|
VERIFY_FILE("sudo.log", expected_path("check_example_io_plugin_command_log.stored"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_io_plugin_failed_to_start_command(void)
|
||||||
|
{
|
||||||
|
create_io_plugin_options(data.tmp_dir);
|
||||||
|
|
||||||
|
free(data.plugin_argv);
|
||||||
|
data.plugin_argc = 1;
|
||||||
|
data.plugin_argv = create_str_array(2, "cmd", NULL);
|
||||||
|
|
||||||
|
free(data.command_info);
|
||||||
|
data.command_info = create_str_array(3, "command=/usr/share/cmd", "runas_uid=0", NULL);
|
||||||
|
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||||
|
data.user_env, data.plugin_options), SUDO_RC_OK);
|
||||||
|
|
||||||
|
python_io.close(0, EPERM); // execve returned with error
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_io_plugin_failed_to_start_command.stdout"));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_io_plugin_failed_to_start_command.stderr"));
|
||||||
|
VERIFY_FILE("sudo.log", expected_path("check_example_io_plugin_failed_to_start_command.stored"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_io_plugin_fails_with_python_backtrace(void)
|
||||||
|
{
|
||||||
|
create_io_plugin_options("/some/not/writable/directory");
|
||||||
|
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||||
|
data.user_env, data.plugin_options), SUDO_RC_ERROR);
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_io_plugin_fails_with_python_backtrace.stdout"));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_io_plugin_fails_with_python_backtrace.stderr"));
|
||||||
|
|
||||||
|
python_io.close(0, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_group_plugin(void)
|
||||||
|
{
|
||||||
|
create_group_plugin_options();
|
||||||
|
|
||||||
|
VERIFY_INT(group_plugin.init(GROUP_API_VERSION, fake_printf, data.plugin_options), SUDO_RC_OK);
|
||||||
|
|
||||||
|
VERIFY_INT(group_plugin.query("test", "mygroup", NULL), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(group_plugin.query("testuser2", "testgroup", NULL), SUDO_RC_OK);
|
||||||
|
VERIFY_INT(group_plugin.query("testuser2", "mygroup", NULL), SUDO_RC_REJECT);
|
||||||
|
VERIFY_INT(group_plugin.query("test", "testgroup", NULL), SUDO_RC_REJECT);
|
||||||
|
|
||||||
|
group_plugin.cleanup();
|
||||||
|
VERIFY_STR(data.stderr_str, "");
|
||||||
|
VERIFY_STR(data.stdout_str, "");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
create_debug_config(const char *debug_spec)
|
||||||
|
{
|
||||||
|
char *result = NULL;
|
||||||
|
|
||||||
|
static char config_path[PATH_MAX] = "/";
|
||||||
|
snprintf(config_path, sizeof(config_path), "%s/sudo.conf", data.tmp_dir);
|
||||||
|
|
||||||
|
char *content = NULL;
|
||||||
|
if (asprintf(&content, "Set developer_mode true\n"
|
||||||
|
"Debug %s %s/debug.log %s\n",
|
||||||
|
"python_plugin.so", data.tmp_dir, debug_spec) < 0)
|
||||||
|
{
|
||||||
|
printf("Failed to allocate string\n");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fwriteall(config_path, content) != true) {
|
||||||
|
printf("Failed to write '%s'\n", config_path);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = config_path;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
free(content);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_group_plugin_is_able_to_debug(void)
|
||||||
|
{
|
||||||
|
const char *config_path = create_debug_config("py_calls@diag");
|
||||||
|
VERIFY_NOT_NULL(config_path);
|
||||||
|
VERIFY_INT(sudo_conf_read(config_path, SUDO_CONF_ALL), true);
|
||||||
|
|
||||||
|
create_group_plugin_options();
|
||||||
|
|
||||||
|
group_plugin.init(GROUP_API_VERSION, fake_printf, data.plugin_options);
|
||||||
|
|
||||||
|
group_plugin.query("user", "group", &example_pwd);
|
||||||
|
|
||||||
|
group_plugin.cleanup();
|
||||||
|
|
||||||
|
VERIFY_STR(data.stderr_str, "");
|
||||||
|
VERIFY_STR(data.stdout_str, "");
|
||||||
|
|
||||||
|
VERIFY_LOG_LINES(expected_path("check_example_group_plugin_is_able_to_debug.log"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_debugging(const char *debug_spec)
|
||||||
|
{
|
||||||
|
const char *config_path = create_debug_config(debug_spec);
|
||||||
|
VERIFY_NOT_NULL(config_path);
|
||||||
|
VERIFY_INT(sudo_conf_read(config_path, SUDO_CONF_ALL), true);
|
||||||
|
|
||||||
|
create_debugging_plugin_options();
|
||||||
|
|
||||||
|
free(data.settings);
|
||||||
|
char *debug_flags_setting = NULL;
|
||||||
|
VERIFY_TRUE(asprintf(&debug_flags_setting, "debug_flags=%s/debug.log %s", data.tmp_dir, debug_spec) >= 0);
|
||||||
|
|
||||||
|
data.settings = create_str_array(3, debug_flags_setting, "plugin_path=python_plugin.so", NULL);
|
||||||
|
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||||
|
data.user_env, data.plugin_options), SUDO_RC_OK);
|
||||||
|
python_io.close(0, 0);
|
||||||
|
|
||||||
|
VERIFY_STR(data.stderr_str, "");
|
||||||
|
VERIFY_STR(data.stdout_str, "");
|
||||||
|
|
||||||
|
VERIFY_LOG_LINES(expected_path("check_example_debugging_%s.log", debug_spec));
|
||||||
|
|
||||||
|
free(debug_flags_setting);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_loading_fails(const char *name)
|
||||||
|
{
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||||
|
data.user_env, data.plugin_options), SUDO_RC_ERROR);
|
||||||
|
python_io.close(0, 0);
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_loading_fails_%s.stdout", name));
|
||||||
|
VERIFY_STDERR(expected_path("check_loading_fails_%s.stderr", name));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_loading_fails_with_missing_path(void)
|
||||||
|
{
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(2, "ClassName=DebugDemoPlugin", NULL);
|
||||||
|
return check_loading_fails("missing_path");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_loading_fails_with_missing_classname(void)
|
||||||
|
{
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(2, "ModulePath=" SRC_DIR "/example_debugging.py", NULL);
|
||||||
|
return check_loading_fails("missing_classname");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_loading_fails_with_wrong_classname(void)
|
||||||
|
{
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(3, "ModulePath=" SRC_DIR "/example_debugging.py",
|
||||||
|
"ClassName=MispelledPluginName", NULL);
|
||||||
|
return check_loading_fails("wrong_classname");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_loading_fails_with_wrong_path(void)
|
||||||
|
{
|
||||||
|
free(data.plugin_options);
|
||||||
|
data.plugin_options = create_str_array(3, "ModulePath=/wrong_path.py", "ClassName=PluginName", NULL);
|
||||||
|
return check_loading_fails("wrong_path");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_loading_fails_plugin_is_not_owned_by_root(void)
|
||||||
|
{
|
||||||
|
sudo_conf_clear_paths();
|
||||||
|
VERIFY_INT(sudo_conf_read(sudo_conf_normal_mode, SUDO_CONF_ALL), true);
|
||||||
|
|
||||||
|
create_debugging_plugin_options();
|
||||||
|
return check_loading_fails("not_owned_by_root");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_conversation_plugin_reason_log(int simulate_suspend, const char *description)
|
||||||
|
{
|
||||||
|
create_conversation_plugin_options();
|
||||||
|
|
||||||
|
free(data.plugin_argv); // have a command run
|
||||||
|
data.plugin_argc = 1;
|
||||||
|
data.plugin_argv = create_str_array(2, "/bin/whoami", NULL);
|
||||||
|
|
||||||
|
data.conv_replies[0] = "my fake reason";
|
||||||
|
data.conv_replies[1] = "my real secret reason";
|
||||||
|
|
||||||
|
sudo_conv_t conversation = simulate_suspend ? fake_conversation_with_suspend : fake_conversation;
|
||||||
|
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||||
|
data.user_env, data.plugin_options), SUDO_RC_OK);
|
||||||
|
python_io.close(0, 0);
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_conversation_plugin_reason_log_%s.stdout", description));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_conversation_plugin_reason_log_%s.stderr", description));
|
||||||
|
VERIFY_CONV(expected_path("check_example_conversation_plugin_reason_log_%s.conversation", description));
|
||||||
|
VERIFY_FILE("sudo_reasons.txt", expected_path("check_example_conversation_plugin_reason_log_%s.stored", description));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_conversation_plugin_user_interrupts(void)
|
||||||
|
{
|
||||||
|
create_conversation_plugin_options();
|
||||||
|
|
||||||
|
free(data.plugin_argv); // have a command run
|
||||||
|
data.plugin_argc = 1;
|
||||||
|
data.plugin_argv = create_str_array(2, "/bin/whoami", NULL);
|
||||||
|
|
||||||
|
data.conv_replies[0] = NULL; // this simulates user interrupt for the first question
|
||||||
|
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||||
|
data.user_env, data.plugin_options), SUDO_RC_REJECT);
|
||||||
|
python_io.close(0, 0);
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_conversation_plugin_user_interrupts.stdout"));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_conversation_plugin_user_interrupts.stderr"));
|
||||||
|
VERIFY_CONV(expected_path("check_example_conversation_plugin_user_interrupts.conversation"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_policy_plugin_version_display(int is_verbose)
|
||||||
|
{
|
||||||
|
create_policy_plugin_options();
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.user_env, data.plugin_options),
|
||||||
|
SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_policy.show_version(is_verbose), SUDO_RC_OK);
|
||||||
|
|
||||||
|
python_policy.close(0, 0); // this should not call the python plugin close as there was no command run invocation
|
||||||
|
|
||||||
|
if (is_verbose) {
|
||||||
|
// Note: the exact python version is environment dependant
|
||||||
|
VERIFY_STR_CONTAINS(data.stdout_str, "Python interpreter version:");
|
||||||
|
VERIFY_STR_CONTAINS(data.stdout_str, "Python policy plugin API version");
|
||||||
|
} else {
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_policy_plugin_version_display.stdout"));
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_STDERR(expected_path("check_example_policy_plugin_version_display.stderr"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_policy_plugin_accepted_execution(void)
|
||||||
|
{
|
||||||
|
create_policy_plugin_options();
|
||||||
|
|
||||||
|
data.plugin_argc = 2;
|
||||||
|
data.plugin_argv = create_str_array(3, "/bin/whoami", "--help", NULL);
|
||||||
|
|
||||||
|
free(data.user_env);
|
||||||
|
data.user_env = create_str_array(3, "USER_ENV1=VALUE1", "USER_ENV2=value2", NULL);
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.user_env, data.plugin_options),
|
||||||
|
SUDO_RC_OK);
|
||||||
|
|
||||||
|
char **env_add = create_str_array(3, "REQUESTED_ENV1=VALUE1", "REQUESTED_ENV2=value2", NULL);
|
||||||
|
|
||||||
|
char **argv_out, **user_env_out, **command_info_out; // free to contain garbage
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.check_policy(data.plugin_argc, data.plugin_argv, env_add,
|
||||||
|
&command_info_out, &argv_out, &user_env_out),
|
||||||
|
SUDO_RC_ACCEPT);
|
||||||
|
|
||||||
|
VERIFY_STR_SET(command_info_out, 4, "command=/bin/whoami", "runas_uid=0", "runas_gid=0", NULL);
|
||||||
|
VERIFY_STR_SET(user_env_out, 5, "USER_ENV1=VALUE1", "USER_ENV2=value2",
|
||||||
|
"REQUESTED_ENV1=VALUE1", "REQUESTED_ENV2=value2", NULL);
|
||||||
|
VERIFY_STR_SET(argv_out, 3, "/bin/whoami", "--help", NULL);
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.init_session(&example_pwd, &user_env_out), SUDO_RC_ACCEPT);
|
||||||
|
|
||||||
|
// init session is able to modify the user env:
|
||||||
|
VERIFY_STR_SET(user_env_out, 6, "USER_ENV1=VALUE1", "USER_ENV2=value2",
|
||||||
|
"REQUESTED_ENV1=VALUE1", "REQUESTED_ENV2=value2", "PLUGIN_EXAMPLE_ENV=1", NULL);
|
||||||
|
|
||||||
|
python_policy.close(3, 0); // successful execution returned exit code 3
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_policy_plugin_accepted_execution.stdout"));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_policy_plugin_accepted_execution.stderr"));
|
||||||
|
|
||||||
|
free(env_add);
|
||||||
|
free(user_env_out);
|
||||||
|
free(command_info_out);
|
||||||
|
free(argv_out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_policy_plugin_failed_execution(void)
|
||||||
|
{
|
||||||
|
create_policy_plugin_options();
|
||||||
|
|
||||||
|
data.plugin_argc = 2;
|
||||||
|
data.plugin_argv = create_str_array(3, "/bin/id", "--help", NULL);
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.user_env, data.plugin_options),
|
||||||
|
SUDO_RC_OK);
|
||||||
|
|
||||||
|
char **argv_out, **user_env_out, **command_info_out; // free to contain garbage
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.check_policy(data.plugin_argc, data.plugin_argv, NULL,
|
||||||
|
&command_info_out, &argv_out, &user_env_out),
|
||||||
|
SUDO_RC_ACCEPT);
|
||||||
|
|
||||||
|
// pwd is unset (user is not part of /etc/passwd)
|
||||||
|
VERIFY_INT(python_policy.init_session(NULL, &user_env_out), SUDO_RC_ACCEPT);
|
||||||
|
|
||||||
|
python_policy.close(12345, ENOENT); // failed to execute
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_policy_plugin_failed_execution.stdout"));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_policy_plugin_failed_execution.stderr"));
|
||||||
|
|
||||||
|
free(user_env_out);
|
||||||
|
free(command_info_out);
|
||||||
|
free(argv_out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_policy_plugin_denied_execution(void)
|
||||||
|
{
|
||||||
|
create_policy_plugin_options();
|
||||||
|
|
||||||
|
data.plugin_argc = 1;
|
||||||
|
data.plugin_argv = create_str_array(2, "/bin/passwd", NULL);
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.user_env, data.plugin_options),
|
||||||
|
SUDO_RC_OK);
|
||||||
|
|
||||||
|
char **argv_out, **user_env_out, **command_info_out; // free to contain garbage
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.check_policy(data.plugin_argc, data.plugin_argv, NULL,
|
||||||
|
&command_info_out, &argv_out, &user_env_out),
|
||||||
|
SUDO_RC_REJECT);
|
||||||
|
|
||||||
|
VERIFY_PTR(command_info_out, NULL);
|
||||||
|
VERIFY_PTR(argv_out, NULL);
|
||||||
|
VERIFY_PTR(user_env_out, NULL);
|
||||||
|
|
||||||
|
python_policy.close(0, 0); // there was no execution
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_policy_plugin_denied_execution.stdout"));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_policy_plugin_denied_execution.stderr"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_policy_plugin_list(void)
|
||||||
|
{
|
||||||
|
create_policy_plugin_options();
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.user_env, data.plugin_options),
|
||||||
|
SUDO_RC_OK);
|
||||||
|
|
||||||
|
snprintf_append(data.stdout_str, MAX_OUTPUT, "-- minimal --\n");
|
||||||
|
VERIFY_INT(python_policy.list(data.plugin_argc, data.plugin_argv, false, NULL), SUDO_RC_OK);
|
||||||
|
|
||||||
|
snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- minimal (verbose) --\n");
|
||||||
|
VERIFY_INT(python_policy.list(data.plugin_argc, data.plugin_argv, true, NULL), SUDO_RC_OK);
|
||||||
|
|
||||||
|
snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with user --\n");
|
||||||
|
VERIFY_INT(python_policy.list(data.plugin_argc, data.plugin_argv, false, "testuser"), SUDO_RC_OK);
|
||||||
|
|
||||||
|
snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with user (verbose) --\n");
|
||||||
|
VERIFY_INT(python_policy.list(data.plugin_argc, data.plugin_argv, true, "testuser"), SUDO_RC_OK);
|
||||||
|
|
||||||
|
snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with allowed program --\n");
|
||||||
|
free(data.plugin_argv);
|
||||||
|
data.plugin_argc = 3;
|
||||||
|
data.plugin_argv = create_str_array(4, "/bin/id", "some", "arguments", NULL);
|
||||||
|
VERIFY_INT(python_policy.list(data.plugin_argc, data.plugin_argv, false, NULL), SUDO_RC_OK);
|
||||||
|
|
||||||
|
snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with allowed program (verbose) --\n");
|
||||||
|
VERIFY_INT(python_policy.list(data.plugin_argc, data.plugin_argv, true, NULL), SUDO_RC_OK);
|
||||||
|
|
||||||
|
snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with denied program --\n");
|
||||||
|
free(data.plugin_argv);
|
||||||
|
data.plugin_argc = 1;
|
||||||
|
data.plugin_argv = create_str_array(2, "/bin/passwd", NULL);
|
||||||
|
VERIFY_INT(python_policy.list(data.plugin_argc, data.plugin_argv, false, NULL), SUDO_RC_OK);
|
||||||
|
|
||||||
|
snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with denied program (verbose) --\n");
|
||||||
|
VERIFY_INT(python_policy.list(data.plugin_argc, data.plugin_argv, true, NULL), SUDO_RC_OK);
|
||||||
|
|
||||||
|
python_policy.close(0, 0); // there was no execution
|
||||||
|
|
||||||
|
VERIFY_STDOUT(expected_path("check_example_policy_plugin_list.stdout"));
|
||||||
|
VERIFY_STDERR(expected_path("check_example_policy_plugin_list.stderr"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_example_policy_plugin_validate_invalidate(void)
|
||||||
|
{
|
||||||
|
// the plugin does not do any meaningful for these, so using log to validate instead
|
||||||
|
const char *config_path = create_debug_config("py_calls@diag");
|
||||||
|
VERIFY_NOT_NULL(config_path);
|
||||||
|
VERIFY_INT(sudo_conf_read(config_path, SUDO_CONF_ALL), true);
|
||||||
|
|
||||||
|
create_policy_plugin_options();
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.user_env, data.plugin_options),
|
||||||
|
SUDO_RC_OK);
|
||||||
|
VERIFY_INT(python_policy.validate(), SUDO_RC_OK);
|
||||||
|
python_policy.invalidate(true);
|
||||||
|
python_policy.invalidate(false);
|
||||||
|
|
||||||
|
python_policy.close(0, 0); // no command execution
|
||||||
|
|
||||||
|
VERIFY_LOG_LINES(expected_path("check_example_policy_plugin_validate_invalidate.log"));
|
||||||
|
VERIFY_STR(data.stderr_str, "");
|
||||||
|
VERIFY_STR(data.stdout_str, "");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_policy_plugin_callbacks_are_optional(void)
|
||||||
|
{
|
||||||
|
create_debugging_plugin_options();
|
||||||
|
|
||||||
|
VERIFY_INT(python_policy.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.user_env, data.plugin_options),
|
||||||
|
SUDO_RC_OK);
|
||||||
|
|
||||||
|
VERIFY_PTR(python_policy.list, NULL);
|
||||||
|
VERIFY_PTR(python_policy.validate, NULL);
|
||||||
|
VERIFY_PTR(python_policy.invalidate, NULL);
|
||||||
|
VERIFY_PTR_NE(python_policy.check_policy, NULL); // (not optional)
|
||||||
|
VERIFY_PTR(python_policy.init_session, NULL);
|
||||||
|
VERIFY_PTR(python_policy.show_version, NULL);
|
||||||
|
|
||||||
|
python_io.close(0, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
check_io_plugin_callbacks_are_optional(void)
|
||||||
|
{
|
||||||
|
create_debugging_plugin_options();
|
||||||
|
|
||||||
|
VERIFY_INT(python_io.open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||||
|
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||||
|
data.user_env, data.plugin_options), SUDO_RC_OK);
|
||||||
|
|
||||||
|
VERIFY_PTR(python_io.log_stdin, NULL);
|
||||||
|
VERIFY_PTR(python_io.log_stdout, NULL);
|
||||||
|
VERIFY_PTR(python_io.log_stderr, NULL);
|
||||||
|
VERIFY_PTR(python_io.log_ttyin, NULL);
|
||||||
|
VERIFY_PTR(python_io.log_ttyout, NULL);
|
||||||
|
VERIFY_PTR(python_io.show_version, NULL);
|
||||||
|
VERIFY_PTR(python_io.change_winsize, NULL);
|
||||||
|
|
||||||
|
python_io.close(0, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
RUN_TEST(check_example_io_plugin_version_display(true));
|
||||||
|
RUN_TEST(check_example_io_plugin_version_display(false));
|
||||||
|
RUN_TEST(check_example_io_plugin_command_log());
|
||||||
|
RUN_TEST(check_example_io_plugin_failed_to_start_command());
|
||||||
|
RUN_TEST(check_example_io_plugin_fails_with_python_backtrace());
|
||||||
|
RUN_TEST(check_io_plugin_callbacks_are_optional());
|
||||||
|
|
||||||
|
RUN_TEST(check_example_group_plugin());
|
||||||
|
RUN_TEST(check_example_group_plugin_is_able_to_debug());
|
||||||
|
|
||||||
|
RUN_TEST(check_loading_fails_with_missing_path());
|
||||||
|
RUN_TEST(check_loading_fails_with_missing_classname());
|
||||||
|
RUN_TEST(check_loading_fails_with_wrong_classname());
|
||||||
|
RUN_TEST(check_loading_fails_with_wrong_path());
|
||||||
|
RUN_TEST(check_loading_fails_plugin_is_not_owned_by_root());
|
||||||
|
|
||||||
|
RUN_TEST(check_example_conversation_plugin_reason_log(false, "without_suspend"));
|
||||||
|
RUN_TEST(check_example_conversation_plugin_reason_log(true, "with_suspend"));
|
||||||
|
RUN_TEST(check_example_conversation_plugin_user_interrupts());
|
||||||
|
|
||||||
|
RUN_TEST(check_example_policy_plugin_version_display(true));
|
||||||
|
RUN_TEST(check_example_policy_plugin_version_display(false));
|
||||||
|
RUN_TEST(check_example_policy_plugin_accepted_execution());
|
||||||
|
RUN_TEST(check_example_policy_plugin_failed_execution());
|
||||||
|
RUN_TEST(check_example_policy_plugin_denied_execution());
|
||||||
|
RUN_TEST(check_example_policy_plugin_list());
|
||||||
|
RUN_TEST(check_example_policy_plugin_validate_invalidate());
|
||||||
|
RUN_TEST(check_policy_plugin_callbacks_are_optional());
|
||||||
|
|
||||||
|
RUN_TEST(check_example_debugging("plugin@err"));
|
||||||
|
RUN_TEST(check_example_debugging("plugin@info"));
|
||||||
|
RUN_TEST(check_example_debugging("load@diag"));
|
||||||
|
RUN_TEST(check_example_debugging("sudo_cb@info"));
|
||||||
|
RUN_TEST(check_example_debugging("c_calls@diag"));
|
||||||
|
RUN_TEST(check_example_debugging("c_calls@info"));
|
||||||
|
RUN_TEST(check_example_debugging("py_calls@diag"));
|
||||||
|
RUN_TEST(check_example_debugging("py_calls@info"));
|
||||||
|
RUN_TEST(check_example_debugging("plugin@err"));
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
160
plugins/python/regress/iohelpers.c
Normal file
160
plugins/python/regress/iohelpers.c
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#include "iohelpers.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) {
|
||||||
|
printf("Failed to open file '%s'\n", file_path);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = fread(output, 1, max_len - 1, file);
|
||||||
|
output[len] = '\0';
|
||||||
|
|
||||||
|
if (ferror(file) != 0) {
|
||||||
|
printf("Failed to read file '%s' (Error %d)\n", file_path, ferror(file));
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!feof(file)) {
|
||||||
|
printf("File '%s' was bigger than allocated buffer %lu", file_path, max_len);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = true;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (file)
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vsnprintf_append(char *output, size_t max_output_len, const char *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 *output, size_t max_output_len, const char *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 = calloc(1, dest_len);
|
||||||
|
char *pos = NULL;
|
||||||
|
size_t old_len = strlen(old);
|
||||||
|
size_t new_len = strlen(new);
|
||||||
|
size_t available_len = dest_len;
|
||||||
|
|
||||||
|
while ((pos = strstr(source, old)) != NULL) {
|
||||||
|
size_t skipped_len = (size_t)(pos - source);
|
||||||
|
if (available_len <= skipped_len + 1)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
available_len -= skipped_len;
|
||||||
|
strncat(result, source, skipped_len);
|
||||||
|
|
||||||
|
if (available_len <= new_len + 1)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
available_len -= new_len;
|
||||||
|
strcat(result, new);
|
||||||
|
|
||||||
|
source = pos + old_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (available_len <= strlen(source) + 1)
|
||||||
|
goto fail;
|
||||||
|
strcat(result, source);
|
||||||
|
|
||||||
|
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);
|
||||||
|
strlcpy(string, replaced, max_length);
|
||||||
|
free(replaced);
|
||||||
|
}
|
36
plugins/python/regress/iohelpers.h
Normal file
36
plugins/python/regress/iohelpers.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef PYTHON_IO_HELPERS
|
||||||
|
#define PYTHON_IO_HELPERS
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "sudo_compat.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
|
#include <stdbool.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 *output, size_t max_output_len, const char *fmt, va_list args);
|
||||||
|
int snprintf_append(char *output, size_t max_output_len, const char *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
|
1
plugins/python/regress/testdata/sudo.conf.developer_mode
vendored
Normal file
1
plugins/python/regress/testdata/sudo.conf.developer_mode
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Set developer_mode true
|
1
plugins/python/regress/testdata/sudo.conf.normal_mode
vendored
Normal file
1
plugins/python/regress/testdata/sudo.conf.normal_mode
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Set developer_mode false
|
236
plugins/python/regress/testhelpers.c
Normal file
236
plugins/python/regress/testhelpers.c
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
#include "testhelpers.h"
|
||||||
|
|
||||||
|
const char *sudo_conf_developer_mode = TESTDATA_DIR "sudo.conf.developer_mode";
|
||||||
|
const char *sudo_conf_normal_mode = TESTDATA_DIR "sudo.conf.normal_mode";
|
||||||
|
|
||||||
|
struct passwd example_pwd = {
|
||||||
|
"pw_name",
|
||||||
|
"pw_passwd",
|
||||||
|
(uid_t)1001,
|
||||||
|
(gid_t)101,
|
||||||
|
"pw_gecos",
|
||||||
|
"pw_dir",
|
||||||
|
"pw_shell"
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestData data;
|
||||||
|
|
||||||
|
static void
|
||||||
|
clean_output(char *output)
|
||||||
|
{
|
||||||
|
// we replace some output which otherwise would be test run dependant
|
||||||
|
str_replace_in_place(output, MAX_OUTPUT, data.tmp_dir, TEMP_PATH_TEMPLATE);
|
||||||
|
str_replace_in_place(output, MAX_OUTPUT, SRC_DIR, "SRC_DIR");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
expected_path(const char *format, ...)
|
||||||
|
{
|
||||||
|
static char expected_output_file[PATH_MAX];
|
||||||
|
int count = snprintf(expected_output_file, PATH_MAX, TESTDATA_DIR);
|
||||||
|
char *filename = expected_output_file + count;
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
vsprintf(filename, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
return expected_output_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
char **
|
||||||
|
create_str_array(size_t count, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
va_start(args, count);
|
||||||
|
|
||||||
|
char ** result = calloc(count, sizeof(char *));
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
result[i] = va_arg(args, char *);
|
||||||
|
}
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
is_update(void)
|
||||||
|
{
|
||||||
|
static int result = -1;
|
||||||
|
if (result < 0) {
|
||||||
|
const char *update = getenv("UPDATE_TESTDATA");
|
||||||
|
result = (update && strcmp(update, "1") == 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
verify_content(char *actual_content, const char *reference_path)
|
||||||
|
{
|
||||||
|
clean_output(actual_content);
|
||||||
|
|
||||||
|
if (is_update()) {
|
||||||
|
VERIFY_TRUE(fwriteall(reference_path, actual_content));
|
||||||
|
} else {
|
||||||
|
char expected_output[MAX_OUTPUT] = "";
|
||||||
|
if (!freadall(reference_path, expected_output, sizeof(expected_output))) {
|
||||||
|
printf("Error: Missing test data at '%s'\n", reference_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VERIFY_STR(actual_content, expected_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
verify_file(const char *actual_file_name, const char *reference_path)
|
||||||
|
{
|
||||||
|
char actual_path[PATH_MAX];
|
||||||
|
snprintf(actual_path, sizeof(actual_path), "%s/%s", data.tmp_dir, actual_file_name);
|
||||||
|
|
||||||
|
char actual_str[MAX_OUTPUT];
|
||||||
|
if (!freadall(actual_path, actual_str, sizeof(actual_str))) {
|
||||||
|
printf("Expected that file '%s' gets created, but it was not\n", actual_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = verify_content(actual_str, reference_path);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
fake_conversation(int num_msgs, const struct sudo_conv_message msgs[],
|
||||||
|
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
|
||||||
|
{
|
||||||
|
(void) callback;
|
||||||
|
snprintf_append(data.conv_str, MAX_OUTPUT, "Question count: %d\n", num_msgs);
|
||||||
|
for (int i = 0; i < num_msgs; ++i) {
|
||||||
|
const struct sudo_conv_message *msg = &msgs[i];
|
||||||
|
snprintf_append(data.conv_str, MAX_OUTPUT, "Question %d: <<%s>> (timeout: %d, msg_type=%d)\n",
|
||||||
|
i, msg->msg, msg->timeout, msg->msg_type);
|
||||||
|
|
||||||
|
if (data.conv_replies[i] == NULL)
|
||||||
|
return 1; // simulates user interruption (conversation error)
|
||||||
|
|
||||||
|
replies[i].reply = strdup(data.conv_replies[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // simulate user answered just fine
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
fake_conversation_with_suspend(int num_msgs, const struct sudo_conv_message msgs[],
|
||||||
|
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
|
||||||
|
{
|
||||||
|
if (callback != NULL) {
|
||||||
|
callback->on_suspend(SIGTSTP, callback->closure);
|
||||||
|
callback->on_resume(SIGCONT, callback->closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fake_conversation(num_msgs, msgs, replies, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
fake_printf(int msg_type, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
int rc = -1;
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
|
||||||
|
char *output = NULL;
|
||||||
|
switch(msg_type) {
|
||||||
|
case SUDO_CONV_INFO_MSG:
|
||||||
|
output = data.stdout_str;
|
||||||
|
break;
|
||||||
|
case SUDO_CONV_ERROR_MSG:
|
||||||
|
output = data.stderr_str;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output)
|
||||||
|
rc = vsnprintf_append(output, MAX_OUTPUT, fmt, args);
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
verify_log_lines(const char *reference_path)
|
||||||
|
{
|
||||||
|
char stored_path[PATH_MAX];
|
||||||
|
snprintf(stored_path, sizeof(stored_path), "%s/%s", data.tmp_dir, "debug.log");
|
||||||
|
|
||||||
|
FILE *file = fopen(stored_path, "rb");
|
||||||
|
if (file == NULL) {
|
||||||
|
printf("Failed to open file '%s'\n", stored_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char line[1024] = "";
|
||||||
|
char stored_str[MAX_OUTPUT] = "";
|
||||||
|
while(fgets(line, sizeof(line), file) != NULL) {
|
||||||
|
const char *line_data = strstr(line, "] "); // this skips the timestamp and pid at the beginning
|
||||||
|
VERIFY_NOT_NULL(line_data); // malformed log line
|
||||||
|
line_data += 2;
|
||||||
|
|
||||||
|
char *line_end = strstr(line_data, " object at "); // this skips checking the pointer hex
|
||||||
|
if (line_end)
|
||||||
|
sprintf(line_end, " object>\n");
|
||||||
|
|
||||||
|
VERIFY_TRUE(strlen(stored_str) + strlen(line_data) + 1 < sizeof(stored_str)); // we have enough space in buffer
|
||||||
|
strcat(stored_str, line_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_output(stored_str);
|
||||||
|
|
||||||
|
VERIFY_TRUE(verify_content(stored_str, reference_path));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
verify_str_set(char **actual_set, char **expected_set, const char *actual_variable_name)
|
||||||
|
{
|
||||||
|
VERIFY_NOT_NULL(actual_set);
|
||||||
|
VERIFY_NOT_NULL(expected_set);
|
||||||
|
|
||||||
|
int actual_len = str_array_count(actual_set);
|
||||||
|
int expected_len = str_array_count(expected_set);
|
||||||
|
|
||||||
|
int matches = false;
|
||||||
|
if (actual_len == expected_len) {
|
||||||
|
int actual_pos = 0;
|
||||||
|
for (; actual_pos < actual_len; ++actual_pos) {
|
||||||
|
char *actual_item = actual_set[actual_pos];
|
||||||
|
|
||||||
|
int expected_pos = 0;
|
||||||
|
for (; expected_pos < expected_len; ++expected_pos) {
|
||||||
|
if (strcmp(actual_item, expected_set[expected_pos]) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected_pos == expected_len) {
|
||||||
|
// matching item was not found
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matches = (actual_pos == actual_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matches) {
|
||||||
|
char actual_set_str[MAX_OUTPUT] = "";
|
||||||
|
char expected_set_str[MAX_OUTPUT] = "";
|
||||||
|
str_array_snprint(actual_set_str, MAX_OUTPUT, actual_set, actual_len);
|
||||||
|
str_array_snprint(expected_set_str, MAX_OUTPUT, expected_set, expected_len);
|
||||||
|
|
||||||
|
VERIFY_PRINT_MSG("%s", actual_variable_name, actual_set_str, "expected",
|
||||||
|
expected_set_str, "expected to contain the same elements as");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
155
plugins/python/regress/testhelpers.h
Normal file
155
plugins/python/regress/testhelpers.h
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
#ifndef PYTHON_TESTHELPERS
|
||||||
|
#define PYTHON_TESTHELPERS
|
||||||
|
|
||||||
|
#include "iohelpers.h"
|
||||||
|
|
||||||
|
#include "../pyhelpers.h"
|
||||||
|
|
||||||
|
#include "sudo_conf.h"
|
||||||
|
|
||||||
|
// just for the IDE
|
||||||
|
#ifndef SRC_DIR
|
||||||
|
#define SRC_DIR ""
|
||||||
|
#endif
|
||||||
|
#define TESTDATA_DIR SRC_DIR "/regress/testdata/"
|
||||||
|
|
||||||
|
extern const char *sudo_conf_developer_mode;
|
||||||
|
extern const char *sudo_conf_normal_mode;
|
||||||
|
|
||||||
|
extern struct passwd example_pwd;
|
||||||
|
|
||||||
|
#define TEMP_PATH_TEMPLATE "/tmp/sudo_check_python_exampleXXXXXX"
|
||||||
|
|
||||||
|
extern struct TestData {
|
||||||
|
char *tmp_dir;
|
||||||
|
char stdout_str[MAX_OUTPUT];
|
||||||
|
char stderr_str[MAX_OUTPUT];
|
||||||
|
|
||||||
|
char conv_str[MAX_OUTPUT];
|
||||||
|
const char *conv_replies[8];
|
||||||
|
|
||||||
|
// some example test data used by multiple test cases:
|
||||||
|
char ** settings;
|
||||||
|
char ** user_info;
|
||||||
|
char ** command_info;
|
||||||
|
char ** plugin_argv;
|
||||||
|
int plugin_argc;
|
||||||
|
char ** user_env;
|
||||||
|
char ** plugin_options;
|
||||||
|
} data;
|
||||||
|
|
||||||
|
const char * expected_path(const char *format, ...);
|
||||||
|
|
||||||
|
char ** create_str_array(size_t count, ...);
|
||||||
|
|
||||||
|
#define RUN_TEST(testcase) \
|
||||||
|
do { \
|
||||||
|
printf("Running test " #testcase " ... \n"); \
|
||||||
|
int rc = EXIT_SUCCESS; \
|
||||||
|
if (!init()) { \
|
||||||
|
printf("FAILED: initialization of testcase %s at %s:%d\n", #testcase, __FILE__, __LINE__); \
|
||||||
|
rc = EXIT_FAILURE; \
|
||||||
|
} else \
|
||||||
|
if (!testcase) { \
|
||||||
|
printf("FAILED: testcase %s at %s:%d\n", #testcase, __FILE__, __LINE__); \
|
||||||
|
rc = EXIT_FAILURE; \
|
||||||
|
} \
|
||||||
|
if (!cleanup(rc == EXIT_SUCCESS)) { \
|
||||||
|
printf("FAILED: deitialization of testcase %s at %s:%d\n", #testcase, __FILE__, __LINE__); \
|
||||||
|
rc = EXIT_FAILURE; \
|
||||||
|
} \
|
||||||
|
if (rc != EXIT_SUCCESS) \
|
||||||
|
return rc; \
|
||||||
|
} while(false)
|
||||||
|
|
||||||
|
#define VERIFY_PRINT_MSG(fmt, actual_str, actual, expected_str, expected, expected_to_be_message) \
|
||||||
|
printf("Expectation failed at %s:%d:\n actual is <<" fmt ">>: %s\n %s <<" fmt ">>: %s\n", \
|
||||||
|
__FILE__, __LINE__, actual, actual_str, expected_to_be_message, expected, expected_str)
|
||||||
|
|
||||||
|
#define VERIFY_CUSTOM(fmt, type, actual, expected, invert) \
|
||||||
|
do { \
|
||||||
|
type actual_value = (type)(actual); \
|
||||||
|
int failed = (actual_value != expected); \
|
||||||
|
if (invert) \
|
||||||
|
failed = !failed; \
|
||||||
|
if (failed) { \
|
||||||
|
VERIFY_PRINT_MSG(fmt, #actual, actual_value, #expected, expected, invert ? "not expected to be" : "expected to be"); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(false)
|
||||||
|
|
||||||
|
#define VERIFY_EQ(fmt, type, actual, expected) VERIFY_CUSTOM(fmt, type, actual, expected, false)
|
||||||
|
#define VERIFY_NE(fmt, type, actual, not_expected) VERIFY_CUSTOM(fmt, type, actual, not_expected, true)
|
||||||
|
|
||||||
|
#define VERIFY_INT(actual, expected) VERIFY_EQ("%d", int, actual, expected)
|
||||||
|
|
||||||
|
#define VERIFY_PTR(actual, expected) VERIFY_EQ("%p", const void *, (const void *)actual, (const void *)expected)
|
||||||
|
#define VERIFY_PTR_NE(actual, not_expected) VERIFY_NE("%p", const void *, (const void *)actual, (const void *)not_expected)
|
||||||
|
|
||||||
|
#define VERIFY_TRUE(actual) VERIFY_NE("%d", int, actual, 0)
|
||||||
|
#define VERIFY_FALSE(actual) VERIFY_INT(actual, false)
|
||||||
|
|
||||||
|
#define VERIFY_NOT_NULL(actual) VERIFY_NE("%p", const void *, actual, NULL)
|
||||||
|
|
||||||
|
#define VERIFY_STR(actual, expected) \
|
||||||
|
do { \
|
||||||
|
const char *actual_str = actual; \
|
||||||
|
if (!actual_str || strcmp(actual_str, expected) != 0) { \
|
||||||
|
VERIFY_PRINT_MSG("%s", #actual, actual_str ? actual_str : "(null)", #expected, expected, "expected to be"); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(false)
|
||||||
|
|
||||||
|
#define VERIFY_STR_CONTAINS(actual, expected) \
|
||||||
|
do { \
|
||||||
|
const char *actual_str = actual; \
|
||||||
|
if (!actual_str || strstr(actual_str, expected) == NULL) { \
|
||||||
|
VERIFY_PRINT_MSG("%s", #actual, actual_str ? actual_str : "(null)", #expected, expected, "expected to contain the string"); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(false)
|
||||||
|
|
||||||
|
int is_update(void);
|
||||||
|
|
||||||
|
int verify_content(char *actual_content, const char *reference_path);
|
||||||
|
|
||||||
|
#define VERIFY_CONTENT(actual_output, reference_path) \
|
||||||
|
VERIFY_TRUE(verify_content(actual_output, reference_path))
|
||||||
|
|
||||||
|
#define VERIFY_STDOUT(reference_path) \
|
||||||
|
VERIFY_CONTENT(data.stdout_str, reference_path)
|
||||||
|
|
||||||
|
#define VERIFY_STDERR(reference_path) \
|
||||||
|
VERIFY_CONTENT(data.stderr_str, reference_path)
|
||||||
|
|
||||||
|
#define VERIFY_CONV(reference_name) \
|
||||||
|
VERIFY_CONTENT(data.conv_str, reference_name)
|
||||||
|
|
||||||
|
int verify_file(const char *actual_file_name, const char *reference_path);
|
||||||
|
|
||||||
|
#define VERIFY_FILE(actual_file_name, reference_path) \
|
||||||
|
VERIFY_TRUE(verify_file(actual_file_name, reference_path))
|
||||||
|
|
||||||
|
int fake_conversation(int num_msgs, const struct sudo_conv_message msgs[],
|
||||||
|
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback);
|
||||||
|
|
||||||
|
int fake_conversation_with_suspend(int num_msgs, const struct sudo_conv_message msgs[],
|
||||||
|
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback);
|
||||||
|
|
||||||
|
int fake_printf(int msg_type, const char *fmt, ...);
|
||||||
|
|
||||||
|
int verify_log_lines(const char *reference_path);
|
||||||
|
|
||||||
|
#define VERIFY_LOG_LINES(reference_path) \
|
||||||
|
VERIFY_TRUE(verify_log_lines(reference_path))
|
||||||
|
|
||||||
|
int verify_str_set(char **actual_set, char **expected_set, const char *actual_variable_name);
|
||||||
|
|
||||||
|
#define VERIFY_STR_SET(actual_set, ...) \
|
||||||
|
do { \
|
||||||
|
char **expected_set = create_str_array(__VA_ARGS__); \
|
||||||
|
VERIFY_TRUE(verify_str_set(actual_set, expected_set, #actual_set)); \
|
||||||
|
free(expected_set); \
|
||||||
|
} while(false)
|
||||||
|
|
||||||
|
#endif // PYTHON_TESTHELPERS
|
Reference in New Issue
Block a user