Adding upstream version 1.9.16p2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
parent
ebbaee52bc
commit
182f151a13
1342 changed files with 621215 additions and 0 deletions
1619
plugins/python/regress/check_python_examples.c
Normal file
1619
plugins/python/regress/check_python_examples.c
Normal file
File diff suppressed because it is too large
Load diff
182
plugins/python/regress/iohelpers.c
Normal file
182
plugins/python/regress/iohelpers.c
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
*/
|
||||
|
||||
#include "iohelpers.h"
|
||||
#include <sudo_fatal.h>
|
||||
|
||||
int
|
||||
rmdir_recursive(const char *path)
|
||||
{
|
||||
char *cmd = NULL;
|
||||
int success = false;
|
||||
|
||||
if (asprintf(&cmd, "rm -rf \"%s\"", path) < 0)
|
||||
return false;
|
||||
|
||||
if (system(cmd) == 0)
|
||||
success = true;
|
||||
|
||||
free(cmd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
int
|
||||
fwriteall(const char *file_path, const char *string)
|
||||
{
|
||||
int success = false;
|
||||
|
||||
FILE *file = fopen(file_path, "w+");
|
||||
if (file == NULL)
|
||||
goto cleanup;
|
||||
|
||||
size_t size = strlen(string);
|
||||
if (fwrite(string, 1, size, file) < size) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
cleanup:
|
||||
if (file)
|
||||
fclose(file);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
int
|
||||
freadall(const char *file_path, char *output, size_t max_len)
|
||||
{
|
||||
int rc = false;
|
||||
FILE *file = fopen(file_path, "rb");
|
||||
if (file == NULL) {
|
||||
sudo_warn_nodebug("failed to open file '%s'", file_path);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
size_t len = fread(output, 1, max_len - 1, file);
|
||||
output[len] = '\0';
|
||||
|
||||
if (ferror(file) != 0) {
|
||||
sudo_warn_nodebug("failed to read file '%s'", file_path);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!feof(file)) {
|
||||
sudo_warn_nodebug("file '%s' was bigger than allocated buffer %zu",
|
||||
file_path, max_len);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = true;
|
||||
|
||||
cleanup:
|
||||
if (file)
|
||||
fclose(file);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
vsnprintf_append(char * restrict output, size_t max_output_len, const char * restrict fmt, va_list args)
|
||||
{
|
||||
va_list args2;
|
||||
va_copy(args2, args);
|
||||
|
||||
size_t output_len = strlen(output);
|
||||
int rc = vsnprintf(output + output_len, max_output_len - output_len, fmt, args2);
|
||||
|
||||
va_end(args2);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
snprintf_append(char * restrict output, size_t max_output_len, const char * restrict fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int rc = vsnprintf_append(output, max_output_len, fmt, args);
|
||||
va_end(args);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
str_array_count(char **str_array)
|
||||
{
|
||||
int result = 0;
|
||||
for (; str_array[result] != NULL; ++result) {}
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
str_array_snprint(char *out_str, size_t max_len, char **str_array, int array_len)
|
||||
{
|
||||
if (array_len < 0)
|
||||
array_len = str_array_count(str_array);
|
||||
|
||||
for (int pos = 0; pos < array_len; ++pos) {
|
||||
snprintf_append(out_str, max_len, "%s%s", pos > 0 ? ", " : "", str_array[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
str_replaced(const char *source, size_t dest_len, const char *old, const char *new)
|
||||
{
|
||||
char *result = malloc(dest_len);
|
||||
char *dest = result;
|
||||
char *pos = NULL;
|
||||
size_t old_len = strlen(old);
|
||||
|
||||
if (result == NULL)
|
||||
return NULL;
|
||||
|
||||
while ((pos = strstr(source, old)) != NULL) {
|
||||
size_t len = (size_t)snprintf(dest, dest_len,
|
||||
"%.*s%s", (int)(pos - source), source, new);
|
||||
if (len >= dest_len)
|
||||
goto fail;
|
||||
|
||||
dest_len -= len;
|
||||
dest += len;
|
||||
source = pos + old_len;
|
||||
}
|
||||
|
||||
if (strlcpy(dest, source, dest_len) >= dest_len)
|
||||
goto fail;
|
||||
|
||||
return result;
|
||||
|
||||
fail:
|
||||
free(result);
|
||||
return strdup("str_replace_all failed, string too long");
|
||||
}
|
||||
|
||||
void
|
||||
str_replace_in_place(char *string, size_t max_length, const char *old, const char *new)
|
||||
{
|
||||
char *replaced = str_replaced(string, max_length, old, new);
|
||||
if (replaced != NULL) {
|
||||
strlcpy(string, replaced, max_length);
|
||||
free(replaced);
|
||||
}
|
||||
}
|
58
plugins/python/regress/iohelpers.h
Normal file
58
plugins/python/regress/iohelpers.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef PYTHON_IO_HELPERS
|
||||
#define PYTHON_IO_HELPERS
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef HAVE_STDBOOL_H
|
||||
# include <stdbool.h>
|
||||
#else
|
||||
# include <compat/stdbool.h>
|
||||
#endif /* HAVE_STDBOOL_H */
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <signal.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include <sudo_compat.h>
|
||||
|
||||
#define MAX_OUTPUT (2 << 16)
|
||||
|
||||
int rmdir_recursive(const char *path);
|
||||
|
||||
int fwriteall(const char *file_path, const char *string);
|
||||
int freadall(const char *file_path, char *output, size_t max_len);
|
||||
|
||||
// allocates new string with the content of 'string' but 'old' replaced to 'new'
|
||||
// The allocated array will be dest_length size and null terminated correctly.
|
||||
char *str_replaced(const char *string, size_t dest_length, const char *old, const char *new);
|
||||
|
||||
// same, but "string" must be able to store 'max_length' number of characters including the null terminator
|
||||
void str_replace_in_place(char *string, size_t max_length, const char *old, const char *new);
|
||||
|
||||
int vsnprintf_append(char * restrict output, size_t max_output_len, const char * restrict fmt, va_list args);
|
||||
int snprintf_append(char * restrict output, size_t max_output_len, const char * restrict fmt, ...);
|
||||
|
||||
int str_array_count(char **str_array);
|
||||
void str_array_snprint(char *out_str, size_t max_len, char **str_array, int array_len);
|
||||
|
||||
#endif
|
22
plugins/python/regress/plugin_approval_test.py
Normal file
22
plugins/python/regress/plugin_approval_test.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import sudo
|
||||
import json
|
||||
|
||||
|
||||
class ApprovalTestPlugin(sudo.Plugin):
|
||||
def __init__(self, plugin_options, **kwargs):
|
||||
id = sudo.options_as_dict(plugin_options).get("Id", "")
|
||||
super().__init__(plugin_options=plugin_options, **kwargs)
|
||||
self._id = "(APPROVAL {})".format(id)
|
||||
sudo.log_info("{} Constructed:".format(self._id))
|
||||
sudo.log_info(json.dumps(self.__dict__, indent=4, sort_keys=True))
|
||||
|
||||
def __del__(self):
|
||||
sudo.log_info("{} Destructed successfully".format(self._id))
|
||||
|
||||
def check(self, *args):
|
||||
sudo.log_info("{} Check was called with arguments: "
|
||||
"{}".format(self._id, args))
|
||||
|
||||
def show_version(self, *args):
|
||||
sudo.log_info("{} Show version was called with arguments: "
|
||||
"{}".format(self._id, args))
|
11
plugins/python/regress/plugin_conflict.py
Normal file
11
plugins/python/regress/plugin_conflict.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
import sudo
|
||||
|
||||
import sys
|
||||
|
||||
sys.path = []
|
||||
|
||||
class ConflictPlugin(sudo.Plugin):
|
||||
def __init__(self, plugin_options, **kwargs):
|
||||
sudo.log_info("PATH before: {} (should be empty)".format(sys.path))
|
||||
sys.path = [sudo.options_as_dict(plugin_options).get("Path")]
|
||||
sudo.log_info("PATH set: {}".format(sys.path))
|
18
plugins/python/regress/plugin_errorstr.py
Normal file
18
plugins/python/regress/plugin_errorstr.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import sudo
|
||||
|
||||
|
||||
# The purpose of this class is that all methods you call on its object
|
||||
# raises a PluginError with a message containing the name of the called method.
|
||||
# Eg. if you call "ErrorMsgPlugin().some_method()" it will raise
|
||||
# "Something wrong in some_method"
|
||||
class ErrorMsgPlugin(sudo.Plugin):
|
||||
def __getattr__(self, name):
|
||||
def raiser_func(*args):
|
||||
raise sudo.PluginError("Something wrong in " + name)
|
||||
|
||||
return raiser_func
|
||||
|
||||
|
||||
class ConstructErrorPlugin(sudo.Plugin):
|
||||
def __init__(self, **kwargs):
|
||||
raise sudo.PluginError("Something wrong in plugin constructor")
|
7
plugins/python/regress/testdata/check_example_audit_plugin_receives_accept.stdout
vendored
Normal file
7
plugins/python/regress/testdata/check_example_audit_plugin_receives_accept.stdout
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
(AUDIT) -- Started by user testuser1 (123) --
|
||||
(AUDIT) Requested command: id --help
|
||||
(AUDIT) Accepted command: /sbin/id --help
|
||||
(AUDIT) By the plugin: accepter plugin name (type=POLICY)
|
||||
(AUDIT) Environment: KEY1=VALUE1 KEY2=VALUE2
|
||||
(AUDIT) Command returned with exit code 2
|
||||
(AUDIT) -- Finished --
|
5
plugins/python/regress/testdata/check_example_audit_plugin_receives_error.stdout
vendored
Normal file
5
plugins/python/regress/testdata/check_example_audit_plugin_receives_error.stdout
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
(AUDIT) -- Started by user ??? (???) --
|
||||
(AUDIT) Requested command: id
|
||||
(AUDIT) Plugin errorer plugin name (type=AUDIT) got an error: Some error has happened
|
||||
(AUDIT) Sudo has run into an error: 222
|
||||
(AUDIT) -- Finished --
|
5
plugins/python/regress/testdata/check_example_audit_plugin_receives_reject.stdout
vendored
Normal file
5
plugins/python/regress/testdata/check_example_audit_plugin_receives_reject.stdout
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
(AUDIT) -- Started by user root (0) --
|
||||
(AUDIT) Requested command: passwd
|
||||
(AUDIT) Rejected by plugin rejecter plugin name (type=IO): Rejected just because!
|
||||
(AUDIT) The command was not executed
|
||||
(AUDIT) -- Finished --
|
6
plugins/python/regress/testdata/check_example_audit_plugin_version_display.stdout
vendored
Normal file
6
plugins/python/regress/testdata/check_example_audit_plugin_version_display.stdout
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
(AUDIT) -- Started by user root (0) --
|
||||
Python Example Audit Plugin
|
||||
Python audit plugin (API 1.0): SudoAuditPlugin (loaded from 'SRC_DIR/example_audit_plugin.py')
|
||||
Python Example Audit Plugin (version=1.0)
|
||||
(AUDIT) Sudo has run into an error: 222
|
||||
(AUDIT) -- Finished --
|
1
plugins/python/regress/testdata/check_example_audit_plugin_workflow_multiple.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_audit_plugin_workflow_multiple.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
sudo: loading more than 8 sudo python audit plugins is not supported
|
14
plugins/python/regress/testdata/check_example_audit_plugin_workflow_multiple.stdout
vendored
Normal file
14
plugins/python/regress/testdata/check_example_audit_plugin_workflow_multiple.stdout
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
(AUDIT1) -- Started by user default (1000) --
|
||||
(AUDIT1) Requested command: id --help
|
||||
(AUDIT2) -- Started by user default (1000) --
|
||||
(AUDIT2) Requested command: id --help
|
||||
(AUDIT1) Accepted command: /sbin/id --help
|
||||
(AUDIT1) By the plugin: accepter plugin name (type=POLICY)
|
||||
(AUDIT1) Environment: KEY1=VALUE1 KEY2=VALUE2
|
||||
(AUDIT2) Accepted command: /sbin/id --help
|
||||
(AUDIT2) By the plugin: accepter plugin name (type=POLICY)
|
||||
(AUDIT2) Environment: KEY1=VALUE1 KEY2=VALUE2
|
||||
(AUDIT1) Command exited due to signal 11
|
||||
(AUDIT1) -- Finished --
|
||||
(AUDIT2) Command exited due to signal 11
|
||||
(AUDIT2) -- Finished --
|
|
@ -0,0 +1,3 @@
|
|||
Question count: 2
|
||||
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
|
||||
Question 1: <<Secret reason: >> (timeout: 120, msg_type=5)
|
|
@ -0,0 +1,3 @@
|
|||
Please provide your reason for executing ('/bin/whoami',)
|
||||
conversation suspend: signal SIGTSTP
|
||||
conversation resume: signal was SIGCONT
|
|
@ -0,0 +1,3 @@
|
|||
Executed /bin/whoami
|
||||
Reason: my fake reason
|
||||
Hidden reason: my real secret reason
|
|
@ -0,0 +1,3 @@
|
|||
Question count: 2
|
||||
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
|
||||
Question 1: <<Secret reason: >> (timeout: 120, msg_type=5)
|
|
@ -0,0 +1 @@
|
|||
Please provide your reason for executing ('/bin/whoami',)
|
|
@ -0,0 +1,3 @@
|
|||
Executed /bin/whoami
|
||||
Reason: my fake reason
|
||||
Hidden reason: my real secret reason
|
2
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.conv
vendored
Normal file
2
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.conv
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Question count: 2
|
||||
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
|
|
@ -0,0 +1,2 @@
|
|||
Question count: 2
|
||||
Question 0: <<Reason: >> (timeout: 120, msg_type=2)
|
1
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
You did not answer in time
|
1
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_conversation_plugin_user_interrupts.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Please provide your reason for executing ('/bin/whoami',)
|
6
plugins/python/regress/testdata/check_example_debugging_c_calls@diag.log
vendored
Normal file
6
plugins/python/regress/testdata/check_example_debugging_c_calls@diag.log
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
sudo.debug was called with arguments: (DEBUG.ERROR, 'My demo purpose plugin shows this ERROR level debug message')
|
||||
sudo.debug was called with arguments: (DEBUG.INFO, 'My demo purpose plugin shows this INFO level debug message')
|
||||
LogHandler.emit was called
|
||||
LogHandler.emit was called
|
||||
sudo.options_as_dict was called with arguments: (('ModulePath=SRC_DIR/example_debugging.py', 'ClassName=DebugDemoPlugin'),)
|
||||
sudo.options_as_dict returned result: [('ClassName', 'DebugDemoPlugin'), ('ModulePath', 'SRC_DIR/example_debugging.py')]
|
11
plugins/python/regress/testdata/check_example_debugging_c_calls@info.log
vendored
Normal file
11
plugins/python/regress/testdata/check_example_debugging_c_calls@info.log
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
__init__ @ SRC_DIR/example_debugging.py:58 calls C function:
|
||||
sudo.debug was called with arguments: (DEBUG.ERROR, 'My demo purpose plugin shows this ERROR level debug message')
|
||||
__init__ @ SRC_DIR/example_debugging.py:63 calls C function:
|
||||
sudo.debug was called with arguments: (DEBUG.INFO, 'My demo purpose plugin shows this INFO level debug message')
|
||||
handle @ logging/__init__.py calls C function:
|
||||
LogHandler.emit was called
|
||||
handle @ logging/__init__.py calls C function:
|
||||
LogHandler.emit was called
|
||||
__init__ @ SRC_DIR/example_debugging.py:85 calls C function:
|
||||
sudo.options_as_dict was called with arguments: (('ModulePath=SRC_DIR/example_debugging.py', 'ClassName=DebugDemoPlugin'),)
|
||||
sudo.options_as_dict returned result: [('ClassName', 'DebugDemoPlugin'), ('ModulePath', 'SRC_DIR/example_debugging.py')]
|
3
plugins/python/regress/testdata/check_example_debugging_load@diag.log
vendored
Normal file
3
plugins/python/regress/testdata/check_example_debugging_load@diag.log
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
importing module: SRC_DIR/example_debugging.py
|
||||
Extending python 'path' with 'SRC_DIR'
|
||||
Deinit was called for a python plugin
|
2
plugins/python/regress/testdata/check_example_debugging_plugin@err.log
vendored
Normal file
2
plugins/python/regress/testdata/check_example_debugging_plugin@err.log
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
My demo purpose plugin shows this ERROR level debug message
|
||||
Python log system shows this ERROR level debug message
|
8
plugins/python/regress/testdata/check_example_debugging_plugin@info.log
vendored
Normal file
8
plugins/python/regress/testdata/check_example_debugging_plugin@info.log
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
__init__ @ SRC_DIR/example_debugging.py:58 debugs:
|
||||
My demo purpose plugin shows this ERROR level debug message
|
||||
__init__ @ SRC_DIR/example_debugging.py:63 debugs:
|
||||
My demo purpose plugin shows this INFO level debug message
|
||||
handle @ logging/__init__.py debugs:
|
||||
Python log system shows this ERROR level debug message
|
||||
handle @ logging/__init__.py debugs:
|
||||
Python log system shows this INFO level debug message
|
2
plugins/python/regress/testdata/check_example_debugging_py_calls@diag.log
vendored
Normal file
2
plugins/python/regress/testdata/check_example_debugging_py_calls@diag.log
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
DebugDemoPlugin.__init__ was called with arguments: () [('plugin_options', ('ModulePath=SRC_DIR/example_debugging.py', 'ClassName=DebugDemoPlugin')), ('settings', ('debug_flags=/tmp/sudo_check_python_exampleXXXXXX/debug.log py_calls@diag', 'plugin_path=python_plugin.so')), ('user_env', ()), ('user_info', ()), ('version', '1.0')]
|
||||
DebugDemoPlugin.__init__ returned result: <example_debugging.DebugDemoPlugin object>
|
9
plugins/python/regress/testdata/check_example_debugging_py_calls@info.log
vendored
Normal file
9
plugins/python/regress/testdata/check_example_debugging_py_calls@info.log
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
DebugDemoPlugin.__init__ was called with arguments: () [('plugin_options', ('ModulePath=SRC_DIR/example_debugging.py', 'ClassName=DebugDemoPlugin')), ('settings', ('debug_flags=/tmp/sudo_check_python_exampleXXXXXX/debug.log py_calls@info', 'plugin_path=python_plugin.so')), ('user_env', ()), ('user_info', ()), ('version', '1.0')]
|
||||
DebugDemoPlugin.__init__ returned result: <example_debugging.DebugDemoPlugin object>
|
||||
DebugDemoPlugin function 'log_ttyin' is not implemented
|
||||
DebugDemoPlugin function 'log_ttyout' is not implemented
|
||||
DebugDemoPlugin function 'log_stdin' is not implemented
|
||||
DebugDemoPlugin function 'log_stdout' is not implemented
|
||||
DebugDemoPlugin function 'log_stderr' is not implemented
|
||||
DebugDemoPlugin function 'change_winsize' is not implemented
|
||||
DebugDemoPlugin function 'log_suspend' is not implemented
|
1
plugins/python/regress/testdata/check_example_debugging_sudo_cb@info.log
vendored
Normal file
1
plugins/python/regress/testdata/check_example_debugging_sudo_cb@info.log
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Skipping close call, because there was no command run
|
4
plugins/python/regress/testdata/check_example_group_plugin_is_able_to_debug.log
vendored
Normal file
4
plugins/python/regress/testdata/check_example_group_plugin_is_able_to_debug.log
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
SudoGroupPlugin.__init__ was called with arguments: () [('args', ('ModulePath=SRC_DIR/example_group_plugin.py', 'ClassName=SudoGroupPlugin')), ('version', '1.0')]
|
||||
SudoGroupPlugin.__init__ returned result: <example_group_plugin.SudoGroupPlugin object>
|
||||
SudoGroupPlugin.query was called with arguments: ('user', 'group', ('pw_name', 'pw_passwd', 1001, 101, 'pw_gecos', 'pw_dir', 'pw_shell'))
|
||||
SudoGroupPlugin.query returned result: 0
|
0
plugins/python/regress/testdata/check_example_io_plugin_command_log.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_io_plugin_command_log.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_command_log.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_command_log.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
|
16
plugins/python/regress/testdata/check_example_io_plugin_command_log.stored
vendored
Normal file
16
plugins/python/regress/testdata/check_example_io_plugin_command_log.stored
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
-- Plugin STARTED --
|
||||
EXEC id --help
|
||||
EXEC info [
|
||||
"command=/bin/id",
|
||||
"runas_uid=0"
|
||||
]
|
||||
STD IN some standard input
|
||||
STD OUT some standard output
|
||||
STD ERR some standard error
|
||||
SUSPEND SIGTSTP
|
||||
SUSPEND SIGCONT
|
||||
WINSIZE 200x100
|
||||
TTY IN some tty input
|
||||
TTY OUT some tty output
|
||||
CLOSE Command returned 1
|
||||
-- Plugin DESTROYED --
|
1
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
sudo: loading more than 8 sudo python IO plugins is not supported
|
2
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple.stdout
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
|
||||
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX2/sudo.log
|
16
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple1.stored
vendored
Normal file
16
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple1.stored
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
-- Plugin STARTED --
|
||||
EXEC id --help
|
||||
EXEC info [
|
||||
"command=/bin/id",
|
||||
"runas_uid=0"
|
||||
]
|
||||
STD IN stdin for plugin 1
|
||||
STD OUT stdout for plugin 1
|
||||
STD ERR stderr for plugin 1
|
||||
SUSPEND SIGTSTP
|
||||
SUSPEND SIGCONT
|
||||
WINSIZE 20x10
|
||||
TTY IN tty input for plugin 1
|
||||
TTY OUT tty output for plugin 1
|
||||
CLOSE Command returned 1
|
||||
-- Plugin DESTROYED --
|
16
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple2.stored
vendored
Normal file
16
plugins/python/regress/testdata/check_example_io_plugin_command_log_multiple2.stored
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
-- Plugin STARTED --
|
||||
EXEC whoami
|
||||
EXEC info [
|
||||
"command=/bin/whoami",
|
||||
"runas_uid=1"
|
||||
]
|
||||
STD IN stdin for plugin 2
|
||||
STD OUT stdout for plugin 2
|
||||
STD ERR stderr for plugin 2
|
||||
SUSPEND SIGSTOP
|
||||
SUSPEND SIGCONT
|
||||
WINSIZE 30x40
|
||||
TTY IN tty input for plugin 2
|
||||
TTY OUT tty output for plugin 2
|
||||
CLOSE Command returned 2
|
||||
-- Plugin DESTROYED --
|
0
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
|
8
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stored
vendored
Normal file
8
plugins/python/regress/testdata/check_example_io_plugin_failed_to_start_command.stored
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
-- Plugin STARTED --
|
||||
EXEC cmd
|
||||
EXEC info [
|
||||
"command=/usr/share/cmd",
|
||||
"runas_uid=0"
|
||||
]
|
||||
CLOSE Failed to execute, execve returned 1 (EPERM)
|
||||
-- Plugin DESTROYED --
|
1
plugins/python/regress/testdata/check_example_io_plugin_fails_with_python_backtrace.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_io_plugin_fails_with_python_backtrace.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Failed to construct plugin instance: [Errno 2] No such file or directory: '/some/not/writable/directory/sudo.log'
|
7
plugins/python/regress/testdata/check_example_io_plugin_fails_with_python_backtrace.stdout
vendored
Normal file
7
plugins/python/regress/testdata/check_example_io_plugin_fails_with_python_backtrace.stdout
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
Example sudo python plugin will log to /some/not/writable/directory/sudo.log
|
||||
Traceback:
|
||||
File "SRC_DIR/example_io_plugin.py", line 64, in __init__
|
||||
self._open_log_file(path.join(log_path, "sudo.log"))
|
||||
File "SRC_DIR/example_io_plugin.py", line 134, in _open_log_file
|
||||
self._log_file = open(log_path, "a")
|
||||
|
0
plugins/python/regress/testdata/check_example_io_plugin_version_display.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_io_plugin_version_display.stderr
vendored
Normal file
2
plugins/python/regress/testdata/check_example_io_plugin_version_display.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_example_io_plugin_version_display.stdout
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
|
||||
Python Example IO Plugin version: 1.0
|
2
plugins/python/regress/testdata/check_example_io_plugin_version_display.stored
vendored
Normal file
2
plugins/python/regress/testdata/check_example_io_plugin_version_display.stored
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- Plugin STARTED --
|
||||
-- Plugin DESTROYED --
|
3
plugins/python/regress/testdata/check_example_io_plugin_version_display_full.stdout
vendored
Normal file
3
plugins/python/regress/testdata/check_example_io_plugin_version_display_full.stdout
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
Example sudo python plugin will log to /tmp/sudo_check_python_exampleXXXXXX/sudo.log
|
||||
Python io plugin (API 1.0): SudoIOPlugin (loaded from 'SRC_DIR/example_io_plugin.py')
|
||||
Python Example IO Plugin version: 1.0
|
0
plugins/python/regress/testdata/check_example_policy_plugin_accepted_execution.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_accepted_execution.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_accepted_execution.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_accepted_execution.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
The command returned with exit_status 3
|
1
plugins/python/regress/testdata/check_example_policy_plugin_denied_execution.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_denied_execution.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
You are not allowed to run this command!
|
0
plugins/python/regress/testdata/check_example_policy_plugin_denied_execution.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_denied_execution.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_failed_execution.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_failed_execution.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Failed to execute command, execve syscall returned 2 (ENOENT)
|
0
plugins/python/regress/testdata/check_example_policy_plugin_failed_execution.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_failed_execution.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_list.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_list.stderr
vendored
Normal file
25
plugins/python/regress/testdata/check_example_policy_plugin_list.stdout
vendored
Normal file
25
plugins/python/regress/testdata/check_example_policy_plugin_list.stdout
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
-- minimal --
|
||||
Only the following commands are allowed: id, whoami
|
||||
|
||||
-- minimal (verbose) --
|
||||
Only the following commands are allowed: id, whoami
|
||||
|
||||
-- with user --
|
||||
Only the following commands are allowed: id, whoami as user 'testuser'
|
||||
|
||||
-- with user (verbose) --
|
||||
Only the following commands are allowed: id, whoami as user 'testuser'
|
||||
|
||||
-- with allowed program --
|
||||
You are allowed to execute command '/bin/id'
|
||||
|
||||
-- with allowed program (verbose) --
|
||||
You are allowed to execute command '/bin/id'
|
||||
Only the following commands are allowed: id, whoami
|
||||
|
||||
-- with denied program --
|
||||
You are NOT allowed to execute command '/bin/passwd'
|
||||
|
||||
-- with denied program (verbose) --
|
||||
You are NOT allowed to execute command '/bin/passwd'
|
||||
Only the following commands are allowed: id, whoami
|
8
plugins/python/regress/testdata/check_example_policy_plugin_validate_invalidate.log
vendored
Normal file
8
plugins/python/regress/testdata/check_example_policy_plugin_validate_invalidate.log
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
SudoPolicyPlugin.__init__ was called with arguments: () [('plugin_options', ('ModulePath=SRC_DIR/example_policy_plugin.py', 'ClassName=SudoPolicyPlugin')), ('settings', ()), ('user_env', ()), ('user_info', ()), ('version', '1.0')]
|
||||
SudoPolicyPlugin.__init__ returned result: <example_policy_plugin.SudoPolicyPlugin object>
|
||||
SudoPolicyPlugin.validate was called with arguments: ()
|
||||
SudoPolicyPlugin.validate returned result: None
|
||||
SudoPolicyPlugin.invalidate was called with arguments: (1,)
|
||||
SudoPolicyPlugin.invalidate returned result: None
|
||||
SudoPolicyPlugin.invalidate was called with arguments: (0,)
|
||||
SudoPolicyPlugin.invalidate returned result: None
|
0
plugins/python/regress/testdata/check_example_policy_plugin_version_display.stderr
vendored
Normal file
0
plugins/python/regress/testdata/check_example_policy_plugin_version_display.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_version_display.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_example_policy_plugin_version_display.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Python Example Policy Plugin version: 1.0
|
2
plugins/python/regress/testdata/check_example_policy_plugin_version_display_full.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_example_policy_plugin_version_display_full.stdout
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Python policy plugin (API 1.0): SudoPolicyPlugin (loaded from 'SRC_DIR/example_policy_plugin.py')
|
||||
Python Example Policy Plugin version: 1.0
|
3
plugins/python/regress/testdata/check_loading_fails_missing_classname.stderr
vendored
Normal file
3
plugins/python/regress/testdata/check_loading_fails_missing_classname.stderr
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
No plugin class is specified for python module 'SRC_DIR/regress/plugin_errorstr.py'. Use 'ClassName' configuration option in 'sudo.conf'
|
||||
Possible plugins: ConstructErrorPlugin, ErrorMsgPlugin
|
||||
Failed during loading plugin class
|
0
plugins/python/regress/testdata/check_loading_fails_missing_classname.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_missing_classname.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_loading_fails_missing_path.stderr
vendored
Normal file
2
plugins/python/regress/testdata/check_loading_fails_missing_path.stderr
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
No python module path is specified. Use 'ModulePath' plugin config option in 'sudo.conf'
|
||||
Failed during loading plugin class
|
0
plugins/python/regress/testdata/check_loading_fails_missing_path.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_missing_path.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_fails_not_owned_by_root.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_fails_not_owned_by_root.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Failed during loading plugin class: File 'SRC_DIR/example_debugging.py' must be owned by uid 0
|
0
plugins/python/regress/testdata/check_loading_fails_not_owned_by_root.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_not_owned_by_root.stdout
vendored
Normal file
2
plugins/python/regress/testdata/check_loading_fails_wrong_classname.stderr
vendored
Normal file
2
plugins/python/regress/testdata/check_loading_fails_wrong_classname.stderr
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Failed to find plugin class 'MispelledPluginName'
|
||||
Failed during loading plugin class
|
0
plugins/python/regress/testdata/check_loading_fails_wrong_classname.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_wrong_classname.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_fails_wrong_path.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_fails_wrong_path.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Failed during loading plugin class: No module named 'wrong_path'
|
0
plugins/python/regress/testdata/check_loading_fails_wrong_path.stdout
vendored
Normal file
0
plugins/python/regress/testdata/check_loading_fails_wrong_path.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_succeeds_with_missing_classname.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_succeeds_with_missing_classname.stdout
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Python io plugin (API 1.0): DebugDemoPlugin (loaded from 'SRC_DIR/example_debugging.py')
|
1
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
sudo: loading more than 8 sudo python approval plugins is not supported
|
67
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout
vendored
Normal file
67
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
(APPROVAL 1) Constructed:
|
||||
{
|
||||
"_id": "(APPROVAL 1)",
|
||||
"plugin_options": [
|
||||
"ModulePath=SRC_DIR/regress/plugin_approval_test.py",
|
||||
"ClassName=ApprovalTestPlugin",
|
||||
"Id=1"
|
||||
],
|
||||
"settings": [
|
||||
"SETTING1=VALUE1",
|
||||
"setting2=value2"
|
||||
],
|
||||
"submit_argv": [
|
||||
"sudo",
|
||||
"-u",
|
||||
"user",
|
||||
"whoami",
|
||||
"--help"
|
||||
],
|
||||
"submit_optind": 3,
|
||||
"user_env": [
|
||||
"USER_ENV1=VALUE1",
|
||||
"USER_ENV2=value2"
|
||||
],
|
||||
"user_info": [
|
||||
"INFO1=VALUE1",
|
||||
"info2=value2"
|
||||
],
|
||||
"version": "1.22"
|
||||
}
|
||||
(APPROVAL 2) Constructed:
|
||||
{
|
||||
"_id": "(APPROVAL 2)",
|
||||
"plugin_options": [
|
||||
"ModulePath=SRC_DIR/regress/plugin_approval_test.py",
|
||||
"ClassName=ApprovalTestPlugin",
|
||||
"Id=2"
|
||||
],
|
||||
"settings": [
|
||||
"SETTING1=VALUE1",
|
||||
"setting2=value2"
|
||||
],
|
||||
"submit_argv": [
|
||||
"sudo",
|
||||
"-u",
|
||||
"user",
|
||||
"whoami",
|
||||
"--help"
|
||||
],
|
||||
"submit_optind": 3,
|
||||
"user_env": [
|
||||
"USER_ENV1=VALUE1",
|
||||
"USER_ENV2=value2"
|
||||
],
|
||||
"user_info": [
|
||||
"INFO1=VALUE1",
|
||||
"info2=value2"
|
||||
],
|
||||
"version": "1.22"
|
||||
}
|
||||
(APPROVAL 1) Show version was called with arguments: (0,)
|
||||
Python approval plugin (API 1.0): ApprovalTestPlugin (loaded from 'SRC_DIR/regress/plugin_approval_test.py')
|
||||
(APPROVAL 2) Show version was called with arguments: (1,)
|
||||
(APPROVAL 1) Check was called with arguments: (('CMDINFO1=value1', 'CMDINFO2=VALUE2'), ('whoami', '--help'), ('USER_ENV1=VALUE1', 'USER_ENV2=value2'))
|
||||
(APPROVAL 2) Check was called with arguments: (('CMDINFO1=value1', 'CMDINFO2=VALUE2'), ('whoami', '--help'), ('USER_ENV1=VALUE1', 'USER_ENV2=value2'))
|
||||
(APPROVAL 1) Destructed successfully
|
||||
(APPROVAL 2) Destructed successfully
|
4
plugins/python/regress/testdata/check_python_plugins_do_not_affect_each_other.stdout
vendored
Normal file
4
plugins/python/regress/testdata/check_python_plugins_do_not_affect_each_other.stdout
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
PATH before: [] (should be empty)
|
||||
PATH set: ['path_for_first_plugin']
|
||||
PATH before: [] (should be empty)
|
||||
PATH set: ['path_for_second_plugin']
|
356
plugins/python/regress/testhelpers.c
Normal file
356
plugins/python/regress/testhelpers.c
Normal file
|
@ -0,0 +1,356 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
*/
|
||||
|
||||
#include "testhelpers.h"
|
||||
|
||||
struct TestData data;
|
||||
|
||||
/*
|
||||
* Starting with Python 3.11, backtraces may contain a line with
|
||||
* '~' and '^' characters to bring attention to the important part
|
||||
* of the line.
|
||||
*/
|
||||
static void
|
||||
remove_underline(char *output)
|
||||
{
|
||||
char *cp, *ep;
|
||||
|
||||
// Remove lines that only consist of '~', '^' and white space.
|
||||
cp = output;
|
||||
ep = output + strlen(output);
|
||||
for (;;) {
|
||||
size_t len = strspn(cp, "~^ \t");
|
||||
if (len > 0 && cp[len] == '\n') {
|
||||
/* Prune out lines that are "underlining". */
|
||||
memmove(cp, cp + len + 1, (size_t)(ep - cp));
|
||||
if (*cp == '\0')
|
||||
break;
|
||||
} else {
|
||||
/* No match, move to the next line. */
|
||||
cp = strchr(cp, '\n');
|
||||
if (cp == NULL)
|
||||
break;
|
||||
cp++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clean_output(char *output)
|
||||
{
|
||||
// we replace some output which otherwise would be test run dependent
|
||||
str_replace_in_place(output, MAX_OUTPUT, data.tmp_dir, TEMP_PATH_TEMPLATE);
|
||||
|
||||
if (data.tmp_dir2)
|
||||
str_replace_in_place(output, MAX_OUTPUT, data.tmp_dir2, TEMP_PATH_TEMPLATE "2");
|
||||
|
||||
str_replace_in_place(output, MAX_OUTPUT, SRC_DIR, "SRC_DIR");
|
||||
|
||||
remove_underline(output);
|
||||
}
|
||||
|
||||
const char *
|
||||
expected_path(const char *format, ...)
|
||||
{
|
||||
static char expected_output_file[PATH_MAX];
|
||||
size_t dirlen = strlcpy(expected_output_file, TESTDATA_DIR, sizeof(expected_output_file));
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(expected_output_file + dirlen, PATH_MAX - dirlen, 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 *));
|
||||
if (result != NULL) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const char *str = va_arg(args, char *);
|
||||
if (str != NULL) {
|
||||
result[i] = strdup(str);
|
||||
if (result[i] == NULL) {
|
||||
while (i > 0) {
|
||||
free(result[--i]);
|
||||
}
|
||||
free(result);
|
||||
result = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_dir, const char *actual_file_name, const char *reference_path)
|
||||
{
|
||||
char actual_path[PATH_MAX];
|
||||
snprintf(actual_path, sizeof(actual_path), "%s/%s", actual_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]);
|
||||
if (replies[i].reply == NULL)
|
||||
return 1; // memory allocation error
|
||||
}
|
||||
|
||||
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 * restrict 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) {
|
||||
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) {
|
||||
snprintf(line_end, sizeof(line) - (size_t)(line_end - line),
|
||||
" object>\n");
|
||||
}
|
||||
|
||||
if (strncmp(line_data, "handle @ /", sizeof("handle @ /") - 1) == 0) {
|
||||
char *start = line_data + sizeof("handle @ ") - 1;
|
||||
|
||||
// normalize path to logging/__init__.py
|
||||
char *logging = strstr(start, "logging/");
|
||||
if (logging != NULL) {
|
||||
memmove(start, logging, strlen(logging) + 1);
|
||||
}
|
||||
|
||||
// remove line number
|
||||
char *colon = strchr(start, ':');
|
||||
if (colon != NULL) {
|
||||
size_t len = strspn(colon + 1, "0123456789");
|
||||
if (len != 0)
|
||||
memmove(colon, colon + len + 1, strlen(colon + len + 1) + 1);
|
||||
}
|
||||
} else if (strncmp(line_data, "LogHandler.emit was called ", 27) == 0) {
|
||||
// LogHandler.emit argument details vary based on python version
|
||||
line_data[26] = '\n';
|
||||
line_data[27] = '\0';
|
||||
} else {
|
||||
// Python 3.11 uses 0 instead of the symbolic REJECT in backtraces
|
||||
char *cp = strstr(line_data, ": REJECT");
|
||||
if (cp != NULL) {
|
||||
// Convert ": REJECT" to ": 0" + rest of line
|
||||
memcpy(cp, ": 0", 3);
|
||||
memmove(cp + 3, cp + 8, strlen(cp + 8) + 1);
|
||||
} else {
|
||||
// Python 3.12 may use <RC.REJECT: 0> instead of 0
|
||||
cp = strstr(line_data, "<RC.REJECT: 0>");
|
||||
if (cp != NULL) {
|
||||
*cp = '0';
|
||||
memmove(cp + 1, cp + 14, strlen(cp + 14) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VERIFY_TRUE(strlcat(stored_str, line_data, sizeof(stored_str)) < sizeof(stored_str)); // we have enough space in buffer
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int
|
||||
mock_python_datetime_now(const char *plugin_name, const char *date_str)
|
||||
{
|
||||
char *cmd = NULL;
|
||||
int len;
|
||||
len = asprintf(&cmd,
|
||||
"import %s\n" // the plugin has its own submodule
|
||||
"from datetime import datetime\n" // store the real datetime
|
||||
"import time\n"
|
||||
"from unittest.mock import Mock\n"
|
||||
"%s.datetime = Mock()\n" // replace plugin's datetime
|
||||
"%s.datetime.now = lambda: datetime.strptime('%s', '%%Y-%%m-%%dT%%H:%%M:%%S')\n",
|
||||
plugin_name, plugin_name, plugin_name, date_str);
|
||||
if (len == -1)
|
||||
return false;
|
||||
VERIFY_PTR_NE(cmd, NULL);
|
||||
VERIFY_INT(PyRun_SimpleString(cmd), 0);
|
||||
free(cmd);
|
||||
return true;
|
||||
}
|
175
plugins/python/regress/testhelpers.h
Normal file
175
plugins/python/regress/testhelpers.h
Normal file
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef PYTHON_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/"
|
||||
|
||||
#define TEMP_PATH_TEMPLATE "/tmp/sudo_check_python_exampleXXXXXX"
|
||||
|
||||
extern struct TestData {
|
||||
char *tmp_dir;
|
||||
char *tmp_dir2;
|
||||
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 { \
|
||||
int success = 1; \
|
||||
ntests++; \
|
||||
if (verbose) { \
|
||||
printf("Running test " #testcase " ... \n"); \
|
||||
} \
|
||||
if (!init()) { \
|
||||
printf("FAILED: initialization of testcase %s at %s:%d\n", #testcase, __FILE__, __LINE__); \
|
||||
success = 0; \
|
||||
} else \
|
||||
if (!testcase) { \
|
||||
printf("FAILED: testcase %s at %s:%d\n", #testcase, __FILE__, __LINE__); \
|
||||
success = 0; \
|
||||
} \
|
||||
if (!cleanup(success)) { \
|
||||
printf("FAILED: deinitialization of testcase %s at %s:%d\n", #testcase, __FILE__, __LINE__); \
|
||||
success = 0; \
|
||||
} \
|
||||
if (!success) { \
|
||||
errors++; \
|
||||
} \
|
||||
} 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_dir, const char *actual_file_name, const char *reference_path);
|
||||
|
||||
#define VERIFY_FILE(actual_file_name, reference_path) \
|
||||
VERIFY_TRUE(verify_file(data.tmp_dir, 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 * restrict fmt, ...);
|
||||
|
||||
int verify_log_lines(const char *reference_path);
|
||||
|
||||
int mock_python_datetime_now(const char *plugin_name, const char *date_str);
|
||||
|
||||
#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)); \
|
||||
str_array_free(&expected_set); \
|
||||
} while(false)
|
||||
|
||||
#endif // PYTHON_TESTHELPERS
|
Loading…
Add table
Add a link
Reference in a new issue