plugins/python/regress: adds tests for python plugin feature and examples

This commit is contained in:
Robert Manner
2020-01-03 17:50:14 +01:00
committed by Todd C. Miller
parent 0b2d0334b7
commit 5da7bd562c
9 changed files with 1351 additions and 5 deletions

View File

@@ -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

View File

@@ -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 $@

View 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;
}

View 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);
}

View 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

View File

@@ -0,0 +1 @@
Set developer_mode true

View File

@@ -0,0 +1 @@
Set developer_mode false

View 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;
}

View 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