plugins/python: a plugin which can load policy/io plugin written in python
This commit is contained in:

committed by
Todd C. Miller

parent
311cf122e2
commit
babdcbd031
432
plugins/python/pyhelpers.c
Normal file
432
plugins/python/pyhelpers.c
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: ISC
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||||
|
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pyhelpers.h"
|
||||||
|
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
static int _sudo_printf_noop(int msg_type, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
(void) msg_type;
|
||||||
|
(void) fmt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PythonContext py_ctx = {
|
||||||
|
&_sudo_printf_noop,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
py_is_sudo_log_available(void)
|
||||||
|
{
|
||||||
|
debug_decl(py_is_sudo_log_available, PYTHON_DEBUG_INTERNAL);
|
||||||
|
debug_return_int(py_ctx.sudo_log != &_sudo_printf_noop);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
py_join_str_list(PyObject *py_str_list, const char *separator)
|
||||||
|
{
|
||||||
|
debug_decl(py_join_str_list, PYTHON_DEBUG_INTERNAL);
|
||||||
|
|
||||||
|
char *result = NULL;
|
||||||
|
PyObject *py_separator = NULL;
|
||||||
|
PyObject *py_str = NULL;
|
||||||
|
|
||||||
|
py_separator = PyUnicode_FromString(separator);
|
||||||
|
if (py_separator == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
py_str = PyObject_CallMethod(py_separator, "join", "(O)", py_str_list);
|
||||||
|
if (py_str == NULL) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *str = PyUnicode_AsUTF8(py_str);
|
||||||
|
if (str != NULL) {
|
||||||
|
result = strdup(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
Py_XDECREF(py_str);
|
||||||
|
Py_XDECREF(py_separator);
|
||||||
|
|
||||||
|
debug_return_str(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
py_create_traceback_string(PyObject *py_traceback)
|
||||||
|
{
|
||||||
|
debug_decl(py_create_traceback_string, PYTHON_DEBUG_INTERNAL);
|
||||||
|
if (py_traceback == NULL)
|
||||||
|
debug_return_str(strdup(""));
|
||||||
|
|
||||||
|
char* traceback = NULL;
|
||||||
|
|
||||||
|
if (py_ctx.py_traceback_module != NULL) {
|
||||||
|
PyObject *py_traceback_str_list = PyObject_CallMethod(py_ctx.py_traceback_module, "format_tb", "(O)", py_traceback);
|
||||||
|
|
||||||
|
if (py_traceback_str_list != NULL) {
|
||||||
|
traceback = py_join_str_list(py_traceback_str_list, "");
|
||||||
|
Py_DECREF(py_traceback_str_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_str(traceback ? traceback : strdup(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
py_log_last_error(const char *context_message)
|
||||||
|
{
|
||||||
|
debug_decl(py_log_last_error, PYTHON_DEBUG_INTERNAL);
|
||||||
|
if (!PyErr_Occurred()) {
|
||||||
|
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s\n", context_message);
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *py_type, *py_message, *py_traceback;
|
||||||
|
PyErr_Fetch(&py_type, &py_message, &py_traceback);
|
||||||
|
|
||||||
|
char *message = py_message ? py_create_string_rep(py_message) : strdup("(NULL)");
|
||||||
|
if (message == NULL)
|
||||||
|
message = strdup("?");
|
||||||
|
|
||||||
|
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s%s(%s) %s\n",
|
||||||
|
context_message ? context_message : "",
|
||||||
|
context_message && *context_message ? ": " : "",
|
||||||
|
py_type ? ((PyTypeObject *)py_type)->tp_name : "None",
|
||||||
|
message);
|
||||||
|
free(message);
|
||||||
|
|
||||||
|
if (py_traceback != NULL) {
|
||||||
|
char *traceback = py_create_traceback_string(py_traceback);
|
||||||
|
py_sudo_log(SUDO_CONV_INFO_MSG, "Traceback:\n%s\n", traceback);
|
||||||
|
free(traceback);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XDECREF(py_type);
|
||||||
|
Py_XDECREF(py_message);
|
||||||
|
Py_XDECREF(py_traceback);
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
py_str_array_to_tuple_with_count(Py_ssize_t count, char * const strings[])
|
||||||
|
{
|
||||||
|
debug_decl(py_str_array_to_tuple_with_count, PYTHON_DEBUG_INTERNAL);
|
||||||
|
|
||||||
|
PyObject *py_argv = PyTuple_New(count);
|
||||||
|
if (py_argv == NULL)
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
PyObject *py_arg = PyUnicode_FromString(strings[i]);
|
||||||
|
if (py_arg == NULL || PyTuple_SetItem(py_argv, i, py_arg) != 0) {
|
||||||
|
Py_CLEAR(py_argv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_ptr(py_argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
py_str_array_to_tuple(char * const strings[])
|
||||||
|
{
|
||||||
|
debug_decl(py_str_array_to_tuple, PYTHON_DEBUG_INTERNAL);
|
||||||
|
|
||||||
|
// find the item count ("strings" ends with NULL terminator):
|
||||||
|
Py_ssize_t count = 0;
|
||||||
|
if (strings != NULL) {
|
||||||
|
while (strings[count] != NULL)
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_ptr(py_str_array_to_tuple_with_count(count, strings));
|
||||||
|
}
|
||||||
|
|
||||||
|
char **
|
||||||
|
py_str_array_from_tuple(PyObject *py_tuple)
|
||||||
|
{
|
||||||
|
debug_decl(py_str_array_from_tuple, PYTHON_DEBUG_INTERNAL);
|
||||||
|
|
||||||
|
if (!PyTuple_Check(py_tuple)) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "%s: value error, argument should be a tuple but it is '%s'",
|
||||||
|
__PRETTY_FUNCTION__, Py_TYPENAME(py_tuple));
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t tuple_size = PyTuple_Size(py_tuple);
|
||||||
|
|
||||||
|
// we need an extra 0 at the end
|
||||||
|
char **result = calloc(Py_SSIZE2SIZE(tuple_size) + 1, sizeof(char*));
|
||||||
|
|
||||||
|
for (int i = 0; i < tuple_size; ++i) {
|
||||||
|
PyObject *py_value = PyTuple_GetItem(py_tuple, i);
|
||||||
|
if (py_value == NULL) {
|
||||||
|
str_array_free(&result);
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that it can be an "int" or something else as well
|
||||||
|
char *value = py_create_string_rep(py_value);
|
||||||
|
if (value == NULL) {
|
||||||
|
// conversion error is already set
|
||||||
|
str_array_free(&result);
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
}
|
||||||
|
result[i] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_ptr(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
py_tuple_get(PyObject *py_tuple, Py_ssize_t index, PyTypeObject *expected_type)
|
||||||
|
{
|
||||||
|
debug_decl(py_tuple_get, PYTHON_DEBUG_INTERNAL);
|
||||||
|
|
||||||
|
PyObject *py_item = PyTuple_GetItem(py_tuple, index);
|
||||||
|
if (py_item == NULL) {
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyObject_TypeCheck(py_item, expected_type)) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "Value error: tuple element %d should "
|
||||||
|
"be a '%s' (but it is '%s')",
|
||||||
|
index, expected_type->tp_name, Py_TYPENAME(py_item));
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_ptr(py_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
py_create_version(unsigned int version)
|
||||||
|
{
|
||||||
|
debug_decl(py_create_version, PYTHON_DEBUG_INTERNAL);
|
||||||
|
debug_return_ptr(PyUnicode_FromFormat("%d.%d", SUDO_API_VERSION_GET_MAJOR(version),
|
||||||
|
SUDO_API_VERSION_GET_MINOR(version)));
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
py_from_passwd(const struct passwd *pwd)
|
||||||
|
{
|
||||||
|
debug_decl(py_from_passwd, PYTHON_DEBUG_INTERNAL);
|
||||||
|
|
||||||
|
if (pwd == NULL) {
|
||||||
|
debug_return_ptr_pynone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a tuple similar and convertible to python "struct_passwd" of "pwd" module
|
||||||
|
debug_return_ptr(
|
||||||
|
Py_BuildValue("(zziizzz)", pwd->pw_name, pwd->pw_passwd,
|
||||||
|
pwd->pw_uid, pwd->pw_gid, pwd->pw_gecos,
|
||||||
|
pwd->pw_dir, pwd->pw_shell)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
py_create_string_rep(PyObject *py_object)
|
||||||
|
{
|
||||||
|
debug_decl(py_create_string_rep, PYTHON_DEBUG_INTERNAL);
|
||||||
|
char *result = NULL;
|
||||||
|
|
||||||
|
if (py_object == NULL)
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
|
||||||
|
PyObject *py_string = PyObject_Str(py_object);
|
||||||
|
if (py_string != NULL) {
|
||||||
|
const char *bytes = PyUnicode_AsUTF8(py_string);
|
||||||
|
if (bytes != NULL) {
|
||||||
|
result = strdup(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XDECREF(py_string);
|
||||||
|
debug_return_ptr(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_py_debug_python_function(const char *class_name, const char *function_name, const char *message,
|
||||||
|
PyObject *py_args, PyObject *py_kwargs, int subsystem_id)
|
||||||
|
{
|
||||||
|
debug_decl_vars(_py_debug_python_function, subsystem_id);
|
||||||
|
|
||||||
|
if (sudo_debug_needed(SUDO_DEBUG_DIAG)) {
|
||||||
|
char *args_str = NULL;
|
||||||
|
char *kwargs_str = NULL;
|
||||||
|
if (py_args != NULL)
|
||||||
|
args_str = py_create_string_rep(py_args);
|
||||||
|
if (py_kwargs != NULL) {
|
||||||
|
kwargs_str = py_create_string_rep(py_kwargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args_str == NULL)
|
||||||
|
args_str = strdup("()");
|
||||||
|
if (kwargs_str == NULL)
|
||||||
|
kwargs_str = strdup("");
|
||||||
|
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_DIAG, "%s.%s %s: %s %s\n", class_name,
|
||||||
|
function_name, message, args_str, kwargs_str);
|
||||||
|
free(args_str);
|
||||||
|
free(kwargs_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
py_debug_python_call(const char *class_name, const char *function_name,
|
||||||
|
PyObject *py_args, PyObject *py_kwargs, int subsystem_id)
|
||||||
|
{
|
||||||
|
debug_decl_vars(_py_debug_python_function, subsystem_id);
|
||||||
|
|
||||||
|
if (subsystem_id == PYTHON_DEBUG_C_CALLS && sudo_debug_needed(SUDO_DEBUG_INFO)) {
|
||||||
|
// at this level we also output the callee python script
|
||||||
|
char *callee_func_name = NULL, *callee_file_name = NULL;
|
||||||
|
long callee_line_number = -1;
|
||||||
|
|
||||||
|
if (py_get_current_execution_frame(&callee_file_name, &callee_line_number, &callee_func_name) == SUDO_RC_OK) {
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_INFO, "%s @ %s:%ld calls C function:\n",
|
||||||
|
callee_func_name, callee_file_name, callee_line_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(callee_func_name);
|
||||||
|
free(callee_file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
_py_debug_python_function(class_name, function_name, "was called with arguments",
|
||||||
|
py_args, py_kwargs, subsystem_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
py_debug_python_result(const char *class_name, const char *function_name,
|
||||||
|
PyObject *py_result, int subsystem_id)
|
||||||
|
{
|
||||||
|
if (py_result == NULL) {
|
||||||
|
debug_decl_vars(py_debug_python_result, subsystem_id);
|
||||||
|
sudo_debug_printf(SUDO_CONV_ERROR_MSG, "%s.%s call failed\n",
|
||||||
|
class_name, function_name);
|
||||||
|
} else {
|
||||||
|
_py_debug_python_function(class_name, function_name, "returned result",
|
||||||
|
py_result, NULL, subsystem_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
str_array_free(char ***array)
|
||||||
|
{
|
||||||
|
debug_decl(str_array_free, PYTHON_DEBUG_INTERNAL);
|
||||||
|
|
||||||
|
if (*array == NULL)
|
||||||
|
debug_return;
|
||||||
|
|
||||||
|
for (char **item_ptr = *array; *item_ptr != NULL; ++item_ptr)
|
||||||
|
free(*item_ptr);
|
||||||
|
|
||||||
|
free(*array);
|
||||||
|
*array = NULL;
|
||||||
|
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
py_get_current_execution_frame(char **file_name, long *line_number, char **function_name)
|
||||||
|
{
|
||||||
|
*file_name = NULL;
|
||||||
|
*line_number = (long)-1;
|
||||||
|
*function_name = NULL;
|
||||||
|
|
||||||
|
PyObject *py_err_type = NULL, *py_err_value = NULL, *py_err_traceback = NULL;
|
||||||
|
PyErr_Fetch(&py_err_type, &py_err_value, &py_err_traceback);
|
||||||
|
|
||||||
|
PyObject *py_frame = NULL, *py_lineno = NULL, *py_f_code = NULL,
|
||||||
|
*py_filename = NULL, *py_function_name = NULL;
|
||||||
|
|
||||||
|
PyObject *py_getframe = PySys_GetObject("_getframe");
|
||||||
|
if (py_getframe == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
py_frame = PyObject_CallFunction(py_getframe, "i", 0);
|
||||||
|
if (py_frame == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
py_lineno = PyObject_GetAttrString(py_frame, "f_lineno");
|
||||||
|
if (py_lineno != NULL) {
|
||||||
|
*line_number = PyLong_AsLong(py_lineno);
|
||||||
|
}
|
||||||
|
|
||||||
|
py_f_code = PyObject_GetAttrString(py_frame, "f_code");
|
||||||
|
if (py_f_code != NULL) {
|
||||||
|
py_filename = PyObject_GetAttrString(py_f_code, "co_filename");
|
||||||
|
if (py_filename != NULL)
|
||||||
|
*file_name = strdup(PyUnicode_AsUTF8(py_filename));
|
||||||
|
|
||||||
|
py_function_name = PyObject_GetAttrString(py_f_code, "co_name");
|
||||||
|
if (py_function_name != NULL)
|
||||||
|
*function_name = strdup(PyUnicode_AsUTF8(py_function_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
Py_CLEAR(py_frame);
|
||||||
|
Py_CLEAR(py_lineno);
|
||||||
|
Py_CLEAR(py_f_code);
|
||||||
|
Py_CLEAR(py_filename);
|
||||||
|
Py_CLEAR(py_function_name);
|
||||||
|
|
||||||
|
// we hide every error happening inside this function
|
||||||
|
PyErr_Restore(py_err_type, py_err_value, py_err_traceback);
|
||||||
|
|
||||||
|
return (*file_name && *function_name && (*line_number >= 0)) ?
|
||||||
|
SUDO_RC_OK : SUDO_RC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
py_ctx_reset()
|
||||||
|
{
|
||||||
|
memset(&py_ctx, 0, sizeof(py_ctx));
|
||||||
|
py_ctx.sudo_log = &_sudo_printf_noop;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
py_sudo_conv(int num_msgs, const struct sudo_conv_message msgs[], struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
|
||||||
|
{
|
||||||
|
/* Enable suspend during password entry. */
|
||||||
|
struct sigaction sa, saved_sigtstp;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = SA_RESTART;
|
||||||
|
sa.sa_handler = SIG_DFL;
|
||||||
|
(void) sigaction(SIGTSTP, &sa, &saved_sigtstp);
|
||||||
|
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
if (py_ctx.sudo_conv != NULL)
|
||||||
|
rc = py_ctx.sudo_conv((int)num_msgs, msgs, replies, callback);
|
||||||
|
|
||||||
|
/* Restore signal handlers and signal mask. */
|
||||||
|
(void) sigaction(SIGTSTP, &saved_sigtstp, NULL);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
90
plugins/python/pyhelpers.h
Normal file
90
plugins/python/pyhelpers.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: ISC
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SUDO_PLUGIN_PYHELPERS_H
|
||||||
|
#define SUDO_PLUGIN_PYHELPERS_H
|
||||||
|
|
||||||
|
#define PY_SSIZE_T_CLEAN
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "sudo_compat.h"
|
||||||
|
#include "sudo_plugin.h"
|
||||||
|
|
||||||
|
#include "pyhelpers_cpychecker.h"
|
||||||
|
|
||||||
|
#include "sudo_python_debug.h"
|
||||||
|
|
||||||
|
enum SudoPluginFunctionReturnCode {
|
||||||
|
SUDO_RC_OK = 1,
|
||||||
|
SUDO_RC_ACCEPT = 1,
|
||||||
|
SUDO_RC_REJECT = 0,
|
||||||
|
SUDO_RC_ERROR = -1,
|
||||||
|
SUDO_RC_USAGE_ERROR = -2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PythonContext
|
||||||
|
{
|
||||||
|
sudo_printf_t sudo_log;
|
||||||
|
sudo_conv_t sudo_conv;
|
||||||
|
int open_plugin_count;
|
||||||
|
|
||||||
|
PyObject *py_traceback_module;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct PythonContext py_ctx;
|
||||||
|
|
||||||
|
#define Py_TYPENAME(object) (object ? Py_TYPE(object)->tp_name : "NULL")
|
||||||
|
#define Py_SSIZE2SIZE(value) ((value) < 0 ? 0 : (size_t)(value))
|
||||||
|
|
||||||
|
#define py_sudo_log(...) py_ctx.sudo_log(__VA_ARGS__)
|
||||||
|
|
||||||
|
int py_sudo_conv(int num_msgs, const struct sudo_conv_message msgs[],
|
||||||
|
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback);
|
||||||
|
|
||||||
|
void py_log_last_error(const char *context_message);
|
||||||
|
|
||||||
|
int py_is_sudo_log_available(void);
|
||||||
|
|
||||||
|
char *py_create_string_rep(PyObject *py_object);
|
||||||
|
|
||||||
|
char *py_join_str_list(PyObject *py_str_list, const char *separator);
|
||||||
|
|
||||||
|
PyObject *py_from_passwd(const struct passwd *pwd);
|
||||||
|
|
||||||
|
PyObject *py_str_array_to_tuple_with_count(Py_ssize_t count, char * const strings[]);
|
||||||
|
PyObject *py_str_array_to_tuple(char * const strings[]);
|
||||||
|
char **py_str_array_from_tuple(PyObject *py_tuple);
|
||||||
|
|
||||||
|
CPYCHECKER_RETURNS_BORROWED_REF
|
||||||
|
PyObject *py_tuple_get(PyObject *py_tuple, Py_ssize_t index, PyTypeObject *expected_type);
|
||||||
|
|
||||||
|
PyObject *py_create_version(unsigned int version);
|
||||||
|
|
||||||
|
void py_debug_python_call(const char *class_name, const char *function_name,
|
||||||
|
PyObject *py_args, PyObject *py_kwargs, int subsystem_id);
|
||||||
|
void py_debug_python_result(const char *class_name, const char *function_name,
|
||||||
|
PyObject *py_args, int subsystem_id);
|
||||||
|
|
||||||
|
void str_array_free(char ***array);
|
||||||
|
|
||||||
|
int py_get_current_execution_frame(char **file_name, long *line_number, char **function_name);
|
||||||
|
|
||||||
|
void py_ctx_reset(void);
|
||||||
|
|
||||||
|
#endif // SUDO_PLUGIN_PYHELPERS_H
|
45
plugins/python/pyhelpers_cpychecker.h
Normal file
45
plugins/python/pyhelpers_cpychecker.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: ISC
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SUDO_PLUGIN_PYHELPERS_CPYCHECKER_H
|
||||||
|
#define SUDO_PLUGIN_PYHELPERS_CPYCHECKER_H
|
||||||
|
|
||||||
|
/* Helper macros for cpychecker */
|
||||||
|
|
||||||
|
#if defined(WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE)
|
||||||
|
#define CPYCHECKER_RETURNS_BORROWED_REF \
|
||||||
|
__attribute__((cpychecker_returns_borrowed_ref))
|
||||||
|
#else
|
||||||
|
#define CPYCHECKER_RETURNS_BORROWED_REF
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION_ATTRIBUTE
|
||||||
|
#define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION \
|
||||||
|
__attribute__ ((cpychecker_negative_result_sets_exception))
|
||||||
|
#else
|
||||||
|
#define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WITH_CPYCHECKER_STEALS_REFERENCE_TO_ARG_ATTRIBUTE)
|
||||||
|
#define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n) \
|
||||||
|
__attribute__((cpychecker_steals_reference_to_arg(n)))
|
||||||
|
#else
|
||||||
|
#define CPYCHECKER_STEALS_REFERENCE_TO_ARG(n)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // SUDO_PLUGIN_PYHELPERS_CPYCHECKER_H
|
444
plugins/python/python_plugin_common.c
Normal file
444
plugins/python/python_plugin_common.c
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: ISC
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||||
|
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "python_plugin_common.h"
|
||||||
|
#include "sudo_python_module.h"
|
||||||
|
|
||||||
|
#include "sudo_queue.h"
|
||||||
|
#include "sudo_conf.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
const char *
|
||||||
|
_lookup_value(char * const keyvalues[], const char *key)
|
||||||
|
{
|
||||||
|
debug_decl(_lookup_value, PYTHON_DEBUG_INTERNAL);
|
||||||
|
if (keyvalues == NULL)
|
||||||
|
debug_return_const_str(NULL);
|
||||||
|
|
||||||
|
size_t keylen = strlen(key);
|
||||||
|
for (; *keyvalues != NULL; ++keyvalues) {
|
||||||
|
const char *keyvalue = *keyvalues;
|
||||||
|
if (strncmp(keyvalue, key, keylen) == 0 && keyvalue[keylen] == '=')
|
||||||
|
debug_return_const_str(keyvalue + keylen + 1);
|
||||||
|
}
|
||||||
|
debug_return_const_str(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
|
||||||
|
int
|
||||||
|
_append_python_path(const char *module_dir)
|
||||||
|
{
|
||||||
|
debug_decl(_py_debug_python_function, PYTHON_DEBUG_PLUGIN_LOAD);
|
||||||
|
int rc = -1;
|
||||||
|
PyObject *py_sys_path = PySys_GetObject("path");
|
||||||
|
if (py_sys_path == NULL) {
|
||||||
|
PyErr_Format(sudo_exc_SudoException, "Failed to get python 'path'");
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_DIAG, "Extending python 'path' with '%s'\n", module_dir);
|
||||||
|
|
||||||
|
PyObject *py_module_dir = PyUnicode_FromString(module_dir);
|
||||||
|
if (py_module_dir == NULL || PyList_Append(py_sys_path, py_module_dir) != 0) {
|
||||||
|
Py_XDECREF(py_module_dir);
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
Py_XDECREF(py_module_dir);
|
||||||
|
|
||||||
|
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
|
||||||
|
char *path = py_join_str_list(py_sys_path, ":");
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_INFO, "Python path became: %s\n", path);
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = 0;
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_import_module(const char *path)
|
||||||
|
{
|
||||||
|
debug_decl(_import_module, PYTHON_DEBUG_PLUGIN_LOAD);
|
||||||
|
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_INFO, "importing module: %s\n", path);
|
||||||
|
|
||||||
|
char path_copy[PATH_MAX];
|
||||||
|
if (strlcpy(path_copy, path, sizeof(path_copy)) >= sizeof(path_copy))
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
|
||||||
|
char *module_dir = path_copy;
|
||||||
|
char *module_name = strrchr(path_copy, '/');
|
||||||
|
if (module_name == NULL) {
|
||||||
|
module_name = path_copy;
|
||||||
|
module_dir = "";
|
||||||
|
} else {
|
||||||
|
*module_name++ = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = strlen(module_name);
|
||||||
|
if (len >= 3 && strcmp(".py", module_name + len - 3) == 0)
|
||||||
|
module_name[len - 3] = '\0';
|
||||||
|
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_INFO, "module_name: '%s', module_dir: '%s'\n", module_name, module_dir);
|
||||||
|
|
||||||
|
if (_append_python_path(module_dir) < 0)
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
|
||||||
|
debug_return_ptr(PyImport_ImportModule(module_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_construct_custom, PYTHON_DEBUG_PLUGIN_LOAD);
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
PyObject *py_args = PyTuple_New(0);
|
||||||
|
|
||||||
|
if (py_args == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
py_debug_python_call(python_plugin_name(plugin_ctx), "__init__",
|
||||||
|
py_args, py_kwargs, PYTHON_DEBUG_PY_CALLS);
|
||||||
|
|
||||||
|
plugin_ctx->py_instance = PyObject_Call(plugin_ctx->py_class, py_args, py_kwargs);
|
||||||
|
|
||||||
|
py_debug_python_result(python_plugin_name(plugin_ctx), "__init__",
|
||||||
|
plugin_ctx->py_instance, PYTHON_DEBUG_PY_CALLS);
|
||||||
|
|
||||||
|
rc = SUDO_RC_OK;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (plugin_ctx->py_instance == NULL) {
|
||||||
|
py_log_last_error("Failed to construct plugin instance");
|
||||||
|
rc = SUDO_RC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XDECREF(py_args);
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_construct(struct PluginContext *plugin_ctx, unsigned int version,
|
||||||
|
char *const settings[], char *const user_info[],
|
||||||
|
char *const user_env[], char *const plugin_options[])
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_construct, PYTHON_DEBUG_PLUGIN_LOAD);
|
||||||
|
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
PyObject *py_settings = NULL;
|
||||||
|
PyObject *py_user_info = NULL;
|
||||||
|
PyObject *py_user_env = NULL;
|
||||||
|
PyObject *py_plugin_options = NULL;
|
||||||
|
PyObject *py_version = NULL;
|
||||||
|
PyObject *py_kwargs = NULL;
|
||||||
|
|
||||||
|
if ((py_settings = py_str_array_to_tuple(settings)) == NULL ||
|
||||||
|
(py_user_info = py_str_array_to_tuple(user_info)) == NULL ||
|
||||||
|
(py_user_env = py_str_array_to_tuple(user_env)) == NULL ||
|
||||||
|
(py_plugin_options = py_str_array_to_tuple(plugin_options)) == NULL ||
|
||||||
|
(py_version = py_create_version(version)) == NULL ||
|
||||||
|
(py_kwargs = PyDict_New()) == NULL ||
|
||||||
|
PyDict_SetItemString(py_kwargs, "version", py_version) != 0 ||
|
||||||
|
PyDict_SetItemString(py_kwargs, "settings", py_settings) != 0 ||
|
||||||
|
PyDict_SetItemString(py_kwargs, "user_env", py_user_env) != 0 ||
|
||||||
|
PyDict_SetItemString(py_kwargs, "user_info", py_user_info) != 0 ||
|
||||||
|
PyDict_SetItemString(py_kwargs, "plugin_options", py_plugin_options) != 0)
|
||||||
|
{
|
||||||
|
py_log_last_error("Failed to construct plugin instance");
|
||||||
|
rc = SUDO_RC_ERROR;
|
||||||
|
} else {
|
||||||
|
rc = python_plugin_construct_custom(plugin_ctx, py_kwargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XDECREF(py_settings);
|
||||||
|
Py_XDECREF(py_user_info);
|
||||||
|
Py_XDECREF(py_user_env);
|
||||||
|
Py_XDECREF(py_plugin_options);
|
||||||
|
Py_XDECREF(py_version);
|
||||||
|
Py_XDECREF(py_kwargs);
|
||||||
|
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_register_logging(sudo_conv_t conversation,
|
||||||
|
sudo_printf_t sudo_printf,
|
||||||
|
char * const settings[])
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_register_logging, PYTHON_DEBUG_INTERNAL)
|
||||||
|
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
if (py_ctx.sudo_conv == NULL)
|
||||||
|
py_ctx.sudo_conv = conversation;
|
||||||
|
|
||||||
|
py_ctx.sudo_log = sudo_printf;
|
||||||
|
|
||||||
|
struct sudo_conf_debug_file_list debug_files = TAILQ_HEAD_INITIALIZER(debug_files);
|
||||||
|
|
||||||
|
const char *debug_flags = _lookup_value(settings, "debug_flags");
|
||||||
|
const char *plugin_path = _lookup_value(settings, "plugin_path");
|
||||||
|
|
||||||
|
if (debug_flags != NULL && plugin_path != NULL) {
|
||||||
|
if (!python_debug_parse_flags(&debug_files, debug_flags))
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!python_debug_register(plugin_path, &debug_files))
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
rc = SUDO_RC_OK;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
|
||||||
|
static int
|
||||||
|
_python_plugin_register_plugin_in_py_ctx(void)
|
||||||
|
{
|
||||||
|
debug_decl(_python_plugin_register_plugin_in_py_ctx, PYTHON_DEBUG_PLUGIN_LOAD);
|
||||||
|
|
||||||
|
if (!Py_IsInitialized()) {
|
||||||
|
py_ctx.open_plugin_count = 0;
|
||||||
|
|
||||||
|
// Disable environment variables effecting the python interpreter
|
||||||
|
// This is important since we are running code here as root, the
|
||||||
|
// user should not be able to alter what is running any how.
|
||||||
|
Py_IgnoreEnvironmentFlag = 1;
|
||||||
|
Py_IsolatedFlag = 1;
|
||||||
|
Py_NoUserSiteDirectory = 1;
|
||||||
|
|
||||||
|
PyImport_AppendInittab("sudo", sudo_module_init);
|
||||||
|
Py_InitializeEx(0);
|
||||||
|
|
||||||
|
if (!sudo_conf_developer_mode() && sudo_module_register_importblocker() < 0) {
|
||||||
|
py_log_last_error(NULL);
|
||||||
|
debug_return_int(SUDO_RC_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
py_ctx.py_traceback_module = PyImport_ImportModule("traceback");
|
||||||
|
// if getting the traceback module fails, we just don't show tracebacks
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
++py_ctx.open_plugin_count;
|
||||||
|
debug_return_int(SUDO_RC_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[])
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_init, PYTHON_DEBUG_PLUGIN_LOAD);
|
||||||
|
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
|
||||||
|
if (_python_plugin_register_plugin_in_py_ctx() != SUDO_RC_OK)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
const char *module_path = _lookup_value(plugin_options, "ModulePath");
|
||||||
|
if (module_path == NULL) {
|
||||||
|
py_sudo_log(SUDO_CONV_ERROR_MSG, "No python module path is specified. "
|
||||||
|
"Use 'ModulePath' plugin config option in 'sudo.conf'\n", module_path);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Loading python module from path '%s'", module_path);
|
||||||
|
plugin_ctx->py_module = _import_module(module_path);
|
||||||
|
if (plugin_ctx->py_module == NULL) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *plugin_class = _lookup_value(plugin_options, "ClassName");
|
||||||
|
if (plugin_class == NULL) {
|
||||||
|
py_sudo_log(SUDO_CONV_ERROR_MSG, "No plugin class is specified for python module '%s'. "
|
||||||
|
"Use 'ClassName' configuration option in 'sudo.conf'\n", module_path);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Using plugin class '%s'", plugin_class);
|
||||||
|
plugin_ctx->py_class = PyObject_GetAttrString(plugin_ctx->py_module, plugin_class);
|
||||||
|
if (plugin_ctx->py_class == NULL) {
|
||||||
|
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to find plugin class '%s'\n", plugin_class);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyObject_IsSubclass(plugin_ctx->py_class, (PyObject *)sudo_type_Plugin)) {
|
||||||
|
py_sudo_log(SUDO_CONV_ERROR_MSG, "Plugin class '%s' does not inherit from 'sudo.Plugin'\n", plugin_class);
|
||||||
|
Py_CLEAR(plugin_ctx->py_class);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = SUDO_RC_OK;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (plugin_ctx->py_class == NULL) {
|
||||||
|
py_log_last_error("Failed during loading plugin class");
|
||||||
|
rc = SUDO_RC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
python_plugin_deinit(struct PluginContext *plugin_ctx)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_deinit, PYTHON_DEBUG_PLUGIN_LOAD);
|
||||||
|
--py_ctx.open_plugin_count;
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_DIAG, "Closing: %d python plugins left open\n", py_ctx.open_plugin_count);
|
||||||
|
|
||||||
|
Py_CLEAR(plugin_ctx->py_instance);
|
||||||
|
Py_CLEAR(plugin_ctx->py_class);
|
||||||
|
Py_CLEAR(plugin_ctx->py_module);
|
||||||
|
memset(plugin_ctx, 0, sizeof(*plugin_ctx));
|
||||||
|
|
||||||
|
if (py_ctx.open_plugin_count <= 0) {
|
||||||
|
Py_CLEAR(py_ctx.py_traceback_module);
|
||||||
|
|
||||||
|
if (Py_IsInitialized()) {
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_NOTICE, "Closing: deinit python interpreter\n");
|
||||||
|
Py_Finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
python_debug_deregister();
|
||||||
|
py_ctx_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
python_plugin_api_call(struct PluginContext *plugin_ctx, const char *func_name, PyObject *py_args)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_api_call, PYTHON_DEBUG_PY_CALLS);
|
||||||
|
|
||||||
|
if (py_args == NULL) {
|
||||||
|
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to build arguments for python plugin API call '%s'\n", func_name);
|
||||||
|
py_log_last_error(NULL);
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *py_callable = NULL;
|
||||||
|
py_callable = PyObject_GetAttrString(plugin_ctx->py_instance, func_name);
|
||||||
|
|
||||||
|
if (py_callable == NULL) {
|
||||||
|
Py_CLEAR(py_args);
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
py_debug_python_call(python_plugin_name(plugin_ctx), func_name,
|
||||||
|
py_args, NULL, PYTHON_DEBUG_PY_CALLS);
|
||||||
|
|
||||||
|
PyObject *py_result = PyObject_CallObject(py_callable, py_args);
|
||||||
|
Py_CLEAR(py_args);
|
||||||
|
Py_CLEAR(py_callable);
|
||||||
|
|
||||||
|
py_debug_python_result(python_plugin_name(plugin_ctx), func_name,
|
||||||
|
py_result, PYTHON_DEBUG_PY_CALLS);
|
||||||
|
if (py_result == NULL) {
|
||||||
|
py_log_last_error(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_ptr(py_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_rc_to_int(PyObject *py_result)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_rc_to_int, PYTHON_DEBUG_PY_CALLS);
|
||||||
|
if (py_result == NULL)
|
||||||
|
debug_return_int(SUDO_RC_ERROR);
|
||||||
|
|
||||||
|
if (py_result == Py_None)
|
||||||
|
debug_return_int(SUDO_RC_OK);
|
||||||
|
|
||||||
|
debug_return_int((int)PyLong_AsLong(py_result));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_api_rc_call(struct PluginContext *plugin_ctx, const char *func_name, PyObject *py_args)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_api_rc_call, PYTHON_DEBUG_PY_CALLS);
|
||||||
|
|
||||||
|
PyObject *py_result = python_plugin_api_call(plugin_ctx, func_name, py_args);
|
||||||
|
int rc = python_plugin_rc_to_int(py_result);
|
||||||
|
Py_XDECREF(py_result);
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_show_version(struct PluginContext *plugin_ctx, const char *python_callback_name, int is_verbose)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_show_version, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
|
||||||
|
py_sudo_log(SUDO_CONV_INFO_MSG, "Python language plugin version %s\n", PACKAGE_VERSION);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(plugin_ctx, python_callback_name,
|
||||||
|
Py_BuildValue("(i)", is_verbose)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
python_plugin_close(struct PluginContext *plugin_ctx, const char *python_callback_name, int exit_status, int error)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_close, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
|
||||||
|
if (!plugin_ctx->call_close) {
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_INFO, "Skipping close call, because there was no command run\n");
|
||||||
|
|
||||||
|
} else if (!PyObject_HasAttrString(plugin_ctx->py_instance, python_callback_name)) {
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_INFO, "Python plugin function 'close' is skipped (not present)\n");
|
||||||
|
} else {
|
||||||
|
PyObject *py_result = python_plugin_api_call(plugin_ctx, python_callback_name,
|
||||||
|
Py_BuildValue("(ii)", error == 0 ? exit_status : -1, error));
|
||||||
|
Py_XDECREF(py_result);
|
||||||
|
}
|
||||||
|
python_plugin_deinit(plugin_ctx);
|
||||||
|
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
python_plugin_mark_callback_optional(struct PluginContext *plugin_ctx,
|
||||||
|
const char *function_name, void **function)
|
||||||
|
{
|
||||||
|
if (!PyObject_HasAttrString(plugin_ctx->py_instance, function_name)) {
|
||||||
|
debug_decl_vars(python_plugin_mark_callback_optional, PYTHON_DEBUG_PY_CALLS);
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_INFO, "%s function '%s' is not implemented\n",
|
||||||
|
Py_TYPENAME(plugin_ctx->py_instance), function_name);
|
||||||
|
*function = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
python_plugin_name(struct PluginContext *plugin_ctx)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_name, PYTHON_DEBUG_INTERNAL);
|
||||||
|
|
||||||
|
const char *name = "(NULL)";
|
||||||
|
|
||||||
|
if (plugin_ctx == NULL || !PyType_Check(plugin_ctx->py_class))
|
||||||
|
debug_return_const_str(name);
|
||||||
|
|
||||||
|
debug_return_const_str(((PyTypeObject *)(plugin_ctx->py_class))->tp_name);
|
||||||
|
}
|
64
plugins/python/python_plugin_common.h
Normal file
64
plugins/python/python_plugin_common.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: ISC
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SUDO_PYTHON_PLUGIN_COMMON_H
|
||||||
|
#define SUDO_PYTHON_PLUGIN_COMMON_H
|
||||||
|
|
||||||
|
#include "pyhelpers.h"
|
||||||
|
|
||||||
|
struct PluginContext {
|
||||||
|
PyObject *py_module;
|
||||||
|
PyObject *py_class;
|
||||||
|
PyObject *py_instance;
|
||||||
|
int call_close;
|
||||||
|
};
|
||||||
|
|
||||||
|
int python_plugin_register_logging(sudo_conv_t conversation, sudo_printf_t sudo_printf, char * const settings[]);
|
||||||
|
|
||||||
|
int python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[]);
|
||||||
|
|
||||||
|
int python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs);
|
||||||
|
|
||||||
|
int python_plugin_construct(struct PluginContext *plugin_ctx, unsigned int version,
|
||||||
|
char *const settings[], char *const user_info[],
|
||||||
|
char *const user_env[], char *const plugin_options[]);
|
||||||
|
|
||||||
|
void python_plugin_deinit(struct PluginContext *plugin_ctx);
|
||||||
|
|
||||||
|
int python_plugin_show_version(struct PluginContext *plugin_ctx,
|
||||||
|
const char *python_callback_name, int isVerbose);
|
||||||
|
|
||||||
|
void python_plugin_close(struct PluginContext *plugin_ctx, const char *python_callback_name,
|
||||||
|
int exit_status, int error);
|
||||||
|
|
||||||
|
CPYCHECKER_STEALS_REFERENCE_TO_ARG(3)
|
||||||
|
PyObject *python_plugin_api_call(struct PluginContext *plugin_ctx,
|
||||||
|
const char *func_name, PyObject *py_args);
|
||||||
|
|
||||||
|
CPYCHECKER_STEALS_REFERENCE_TO_ARG(3)
|
||||||
|
int python_plugin_api_rc_call(struct PluginContext *plugin_ctx,
|
||||||
|
const char *func_name, PyObject *py_args);
|
||||||
|
|
||||||
|
int python_plugin_rc_to_int(PyObject *py_result);
|
||||||
|
|
||||||
|
void python_plugin_mark_callback_optional(struct PluginContext *plugin_ctx,
|
||||||
|
const char *function_name, void **function);
|
||||||
|
|
||||||
|
const char *python_plugin_name(struct PluginContext *plugin_ctx);
|
||||||
|
|
||||||
|
#endif // SUDO_PYTHON_PLUGIN_COMMON_H
|
111
plugins/python/python_plugin_group.c
Normal file
111
plugins/python/python_plugin_group.c
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: ISC
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||||
|
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "python_plugin_common.h"
|
||||||
|
|
||||||
|
static struct PluginContext plugin_ctx;
|
||||||
|
|
||||||
|
extern struct sudoers_group_plugin group_plugin;
|
||||||
|
|
||||||
|
#define PY_GROUP_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
|
||||||
|
|
||||||
|
#define CALLBACK_PLUGINFUNC(func_name) group_plugin.func_name
|
||||||
|
#define CALLBACK_CFUNC(func_name) python_plugin_group_ ## func_name
|
||||||
|
|
||||||
|
// This also verifies compile time that the name matches the sudo plugin API.
|
||||||
|
#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_group_init(int version, sudo_printf_t sudo_printf, char *const plugin_options[])
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_group_init, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
|
||||||
|
if (version < SUDO_API_MKVERSION(1, 0)) {
|
||||||
|
sudo_printf(SUDO_CONV_ERROR_MSG,
|
||||||
|
"Error: Python group plugin requires at least plugin API version 1.0\n");
|
||||||
|
debug_return_int(SUDO_RC_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
|
||||||
|
rc = python_plugin_register_logging(NULL, sudo_printf, NULL);
|
||||||
|
if (rc != SUDO_RC_OK)
|
||||||
|
debug_return_int(rc);
|
||||||
|
|
||||||
|
rc = python_plugin_init(&plugin_ctx, plugin_options);
|
||||||
|
if (rc != SUDO_RC_OK)
|
||||||
|
debug_return_int(rc);
|
||||||
|
|
||||||
|
PyObject *py_version = NULL,
|
||||||
|
*py_plugin_options = NULL,
|
||||||
|
*py_kwargs = NULL;
|
||||||
|
|
||||||
|
if ((py_kwargs = PyDict_New()) == NULL ||
|
||||||
|
(py_version = py_create_version(PY_GROUP_PLUGIN_VERSION)) == NULL ||
|
||||||
|
(py_plugin_options = py_str_array_to_tuple(plugin_options)) == NULL ||
|
||||||
|
PyDict_SetItemString(py_kwargs, "args", py_plugin_options) != 0 ||
|
||||||
|
PyDict_SetItemString(py_kwargs, "version", py_version))
|
||||||
|
{
|
||||||
|
py_log_last_error("Failed to construct arguments for plugin constructor call.");
|
||||||
|
rc = SUDO_RC_ERROR;
|
||||||
|
} else {
|
||||||
|
rc = python_plugin_construct_custom(&plugin_ctx, py_kwargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XDECREF(py_version);
|
||||||
|
Py_XDECREF(py_plugin_options);
|
||||||
|
Py_XDECREF(py_kwargs);
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
python_plugin_group_cleanup(void)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_group_cleanup, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
python_plugin_deinit(&plugin_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_group_query(const char *user, const char *group, const struct passwd *pwd)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_group_query, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
|
||||||
|
PyObject *py_pwd = py_from_passwd(pwd);
|
||||||
|
if (py_pwd == NULL) {
|
||||||
|
debug_return_int(SUDO_RC_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(query),
|
||||||
|
Py_BuildValue("(zzO)", user, group, py_pwd));
|
||||||
|
Py_XDECREF(py_pwd);
|
||||||
|
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
__dso_public struct sudoers_group_plugin group_plugin = {
|
||||||
|
GROUP_API_VERSION,
|
||||||
|
CALLBACK_CFUNC(init),
|
||||||
|
CALLBACK_CFUNC(cleanup),
|
||||||
|
CALLBACK_CFUNC(query)
|
||||||
|
};
|
207
plugins/python/python_plugin_io.c
Normal file
207
plugins/python/python_plugin_io.c
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: ISC
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||||
|
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "python_plugin_common.h"
|
||||||
|
|
||||||
|
|
||||||
|
static struct PluginContext plugin_ctx;
|
||||||
|
|
||||||
|
extern struct io_plugin python_io;
|
||||||
|
|
||||||
|
#define PY_IO_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
|
||||||
|
|
||||||
|
#define CALLBACK_PLUGINFUNC(func_name) python_io.func_name
|
||||||
|
#define CALLBACK_CFUNC(func_name) python_plugin_io_ ## func_name
|
||||||
|
|
||||||
|
// This also verifies compile time that the name matches the sudo plugin API.
|
||||||
|
#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
|
||||||
|
|
||||||
|
#define MARK_CALLBACK_OPTIONAL(function_name) \
|
||||||
|
do { \
|
||||||
|
python_plugin_mark_callback_optional(&plugin_ctx, CALLBACK_PYNAME(function_name), \
|
||||||
|
(void **)&CALLBACK_PLUGINFUNC(function_name)); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
_call_plugin_open(int argc, char * const argv[], char * const command_info[])
|
||||||
|
{
|
||||||
|
debug_decl(_call_plugin_open, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
plugin_ctx.call_close = 1;
|
||||||
|
|
||||||
|
if (!PyObject_HasAttrString(plugin_ctx.py_instance, CALLBACK_PYNAME(open))) {
|
||||||
|
debug_return_int(SUDO_RC_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv);
|
||||||
|
PyObject *py_command_info = py_str_array_to_tuple(command_info);
|
||||||
|
|
||||||
|
if (py_argv != NULL && py_command_info != NULL) {
|
||||||
|
rc = python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(open),
|
||||||
|
Py_BuildValue("(OO)", py_argv, py_command_info));
|
||||||
|
} else {
|
||||||
|
rc = SUDO_RC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc != SUDO_RC_OK)
|
||||||
|
plugin_ctx.call_close = 0;
|
||||||
|
|
||||||
|
Py_XDECREF(py_argv);
|
||||||
|
Py_XDECREF(py_command_info);
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_open(unsigned int version, sudo_conv_t conversation,
|
||||||
|
sudo_printf_t sudo_printf, char * const settings[],
|
||||||
|
char * const user_info[], char * const command_info[],
|
||||||
|
int argc, char * const argv[], char * const user_env[],
|
||||||
|
char * const plugin_options[])
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_open, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
|
||||||
|
if (version < SUDO_API_MKVERSION(1, 2)) {
|
||||||
|
sudo_printf(SUDO_CONV_ERROR_MSG,
|
||||||
|
"Error: Python IO plugin requires at least plugin API version 1.2\n");
|
||||||
|
debug_return_int(SUDO_RC_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = python_plugin_register_logging(conversation, sudo_printf, settings);
|
||||||
|
if (rc != SUDO_RC_OK)
|
||||||
|
debug_return_int(rc);
|
||||||
|
|
||||||
|
rc = python_plugin_init(&plugin_ctx, plugin_options);
|
||||||
|
if (rc != SUDO_RC_OK)
|
||||||
|
debug_return_int(rc);
|
||||||
|
|
||||||
|
rc = python_plugin_construct(&plugin_ctx, PY_IO_PLUGIN_VERSION,
|
||||||
|
settings, user_info, user_env, plugin_options);
|
||||||
|
if (rc != SUDO_RC_OK)
|
||||||
|
debug_return_int(rc);
|
||||||
|
|
||||||
|
// skip plugin callbacks which are not mandatory
|
||||||
|
MARK_CALLBACK_OPTIONAL(show_version);
|
||||||
|
MARK_CALLBACK_OPTIONAL(log_ttyin);
|
||||||
|
MARK_CALLBACK_OPTIONAL(log_ttyout);
|
||||||
|
MARK_CALLBACK_OPTIONAL(log_stdin);
|
||||||
|
MARK_CALLBACK_OPTIONAL(log_stdout);
|
||||||
|
MARK_CALLBACK_OPTIONAL(log_stderr);
|
||||||
|
MARK_CALLBACK_OPTIONAL(change_winsize);
|
||||||
|
MARK_CALLBACK_OPTIONAL(log_suspend);
|
||||||
|
// open and close are mandatory
|
||||||
|
|
||||||
|
if (argc > 0) // we only call open if there is request for running sg
|
||||||
|
rc = _call_plugin_open(argc, argv, command_info);
|
||||||
|
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
python_plugin_io_close(int exit_status, int error)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_close, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
python_plugin_close(&plugin_ctx, CALLBACK_PYNAME(close), exit_status, error);
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_show_version(int verbose)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_show_version, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_show_version(&plugin_ctx, CALLBACK_PYNAME(show_version), verbose));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_log_ttyin(const char *buf, unsigned int len)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_log_ttyin, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(log_ttyin),
|
||||||
|
Py_BuildValue("(s#)", buf, len)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_log_ttyout(const char *buf, unsigned int len)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_log_ttyout, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(log_ttyout),
|
||||||
|
Py_BuildValue("(s#)", buf, len)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_log_stdin(const char *buf, unsigned int len)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_log_stdin, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(log_stdin),
|
||||||
|
Py_BuildValue("(s#)", buf, len)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_log_stdout(const char *buf, unsigned int len)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_log_stdout, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(log_stdout),
|
||||||
|
Py_BuildValue("(s#)", buf, len)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_log_stderr(const char *buf, unsigned int len)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_log_stderr, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(log_stderr),
|
||||||
|
Py_BuildValue("(s#)", buf, len)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_change_winsize(unsigned int line, unsigned int cols)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_change_winsize, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(change_winsize),
|
||||||
|
Py_BuildValue("(ii)", line, cols)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_io_log_suspend(int signo)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_io_log_suspend, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(log_suspend),
|
||||||
|
Py_BuildValue("(i)", signo)));
|
||||||
|
}
|
||||||
|
|
||||||
|
__dso_public struct io_plugin python_io = {
|
||||||
|
SUDO_IO_PLUGIN,
|
||||||
|
SUDO_API_VERSION,
|
||||||
|
CALLBACK_CFUNC(open),
|
||||||
|
CALLBACK_CFUNC(close),
|
||||||
|
CALLBACK_CFUNC(show_version),
|
||||||
|
CALLBACK_CFUNC(log_ttyin),
|
||||||
|
CALLBACK_CFUNC(log_ttyout),
|
||||||
|
CALLBACK_CFUNC(log_stdin),
|
||||||
|
CALLBACK_CFUNC(log_stdout),
|
||||||
|
CALLBACK_CFUNC(log_stderr),
|
||||||
|
NULL, // register_hooks,
|
||||||
|
NULL, // deregister_hooks,
|
||||||
|
CALLBACK_CFUNC(change_winsize),
|
||||||
|
CALLBACK_CFUNC(log_suspend),
|
||||||
|
NULL // event_alloc
|
||||||
|
};
|
272
plugins/python/python_plugin_policy.c
Normal file
272
plugins/python/python_plugin_policy.c
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: ISC
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||||
|
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "python_plugin_common.h"
|
||||||
|
|
||||||
|
|
||||||
|
static struct PluginContext plugin_ctx;
|
||||||
|
|
||||||
|
extern struct policy_plugin python_policy;
|
||||||
|
|
||||||
|
#define PY_POLICY_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
|
||||||
|
|
||||||
|
#define CALLBACK_PLUGINFUNC(func_name) python_policy.func_name
|
||||||
|
#define CALLBACK_CFUNC(func_name) python_plugin_policy_ ## func_name
|
||||||
|
|
||||||
|
// This also verifies compile time that the name matches the sudo plugin API.
|
||||||
|
#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
|
||||||
|
|
||||||
|
#define MARK_CALLBACK_OPTIONAL(function_name) \
|
||||||
|
do { \
|
||||||
|
python_plugin_mark_callback_optional(&plugin_ctx, CALLBACK_PYNAME(function_name), \
|
||||||
|
(void **)&CALLBACK_PLUGINFUNC(function_name)); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
static int
|
||||||
|
python_plugin_policy_open(unsigned int version, sudo_conv_t conversation,
|
||||||
|
sudo_printf_t sudo_printf, char * const settings[],
|
||||||
|
char * const user_info[], char * const user_env[],
|
||||||
|
char * const plugin_options[])
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_policy_open, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
|
||||||
|
if (version < SUDO_API_MKVERSION(1, 2)) {
|
||||||
|
sudo_printf(SUDO_CONV_ERROR_MSG,
|
||||||
|
"Error: Python policy plugin requires at least plugin API version 1.2\n");
|
||||||
|
debug_return_int(SUDO_RC_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = python_plugin_register_logging(conversation, sudo_printf, settings);
|
||||||
|
if (rc != SUDO_RC_OK)
|
||||||
|
debug_return_int(rc);
|
||||||
|
|
||||||
|
rc = python_plugin_init(&plugin_ctx, plugin_options);
|
||||||
|
if (rc != SUDO_RC_OK)
|
||||||
|
debug_return_int(rc);
|
||||||
|
|
||||||
|
rc = python_plugin_construct(&plugin_ctx, PY_POLICY_PLUGIN_VERSION, settings,
|
||||||
|
user_info, user_env, plugin_options);
|
||||||
|
if (rc != SUDO_RC_OK) {
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip plugin callbacks which are not mandatory
|
||||||
|
MARK_CALLBACK_OPTIONAL(show_version);
|
||||||
|
MARK_CALLBACK_OPTIONAL(list);
|
||||||
|
MARK_CALLBACK_OPTIONAL(validate);
|
||||||
|
MARK_CALLBACK_OPTIONAL(invalidate);
|
||||||
|
MARK_CALLBACK_OPTIONAL(init_session);
|
||||||
|
// check_policy, open and close are mandatory
|
||||||
|
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
python_plugin_policy_close(int exit_status, int error)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_policy_close, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
python_plugin_close(&plugin_ctx, CALLBACK_PYNAME(close), exit_status, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
python_plugin_policy_check(int argc, char * const argv[],
|
||||||
|
char *env_add[], char **command_info_out[],
|
||||||
|
char **argv_out[], char **user_env_out[])
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_policy_check, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
|
||||||
|
*command_info_out = *argv_out = *user_env_out = NULL;
|
||||||
|
|
||||||
|
PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv);
|
||||||
|
|
||||||
|
PyObject *py_env_add = py_str_array_to_tuple(env_add);
|
||||||
|
PyObject *py_result = NULL;
|
||||||
|
|
||||||
|
if (py_argv == NULL || py_env_add == NULL) {
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_ERROR, "Failed to create some of the arguments for the python call "
|
||||||
|
"(py_argv=%p py_env_add=%p)\n", (void *)py_argv, (void *)py_env_add);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
py_result = python_plugin_api_call(&plugin_ctx, CALLBACK_PYNAME(check_policy),
|
||||||
|
Py_BuildValue("(OO)", py_argv, py_env_add));
|
||||||
|
if (py_result == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
PyObject *py_rc = NULL,
|
||||||
|
*py_command_info_out = NULL,
|
||||||
|
*py_argv_out = NULL,
|
||||||
|
*py_user_env_out = NULL;
|
||||||
|
if (PyTuple_Check(py_result))
|
||||||
|
{
|
||||||
|
if (!PyArg_ParseTuple(py_result, "O!|O!O!O!:python_plugin.check_policy",
|
||||||
|
&PyLong_Type, &py_rc,
|
||||||
|
&PyTuple_Type, &py_command_info_out,
|
||||||
|
&PyTuple_Type, &py_argv_out,
|
||||||
|
&PyTuple_Type, &py_user_env_out))
|
||||||
|
{
|
||||||
|
rc = SUDO_RC_ERROR;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
py_rc = py_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (py_command_info_out != NULL)
|
||||||
|
*command_info_out = py_str_array_from_tuple(py_command_info_out);
|
||||||
|
|
||||||
|
if (py_argv_out != NULL)
|
||||||
|
*argv_out = py_str_array_from_tuple(py_argv_out);
|
||||||
|
|
||||||
|
if (py_user_env_out != NULL)
|
||||||
|
*user_env_out = py_str_array_from_tuple(py_user_env_out);
|
||||||
|
|
||||||
|
rc = python_plugin_rc_to_int(py_rc);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
py_log_last_error(NULL);
|
||||||
|
rc = SUDO_RC_ERROR;
|
||||||
|
free(*command_info_out);
|
||||||
|
free(*argv_out);
|
||||||
|
free(*user_env_out);
|
||||||
|
*command_info_out = *argv_out = *user_env_out = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XDECREF(py_argv);
|
||||||
|
Py_XDECREF(py_env_add);
|
||||||
|
Py_XDECREF(py_result);
|
||||||
|
|
||||||
|
if (rc == SUDO_RC_ACCEPT)
|
||||||
|
plugin_ctx.call_close = 1;
|
||||||
|
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
python_plugin_policy_list(int argc, char * const argv[], int verbose, const char *list_user)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_policy_list, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
|
||||||
|
PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv);
|
||||||
|
if (py_argv == NULL) {
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: Failed to create argv argument for the python call\n", __PRETTY_FUNCTION__);
|
||||||
|
debug_return_int(SUDO_RC_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(list),
|
||||||
|
Py_BuildValue("(Oiz)", py_argv, verbose, list_user));
|
||||||
|
|
||||||
|
Py_XDECREF(py_argv);
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
python_plugin_policy_version(int verbose)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_policy_version, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
|
||||||
|
debug_return_int(python_plugin_show_version(&plugin_ctx, CALLBACK_PYNAME(show_version), verbose));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_policy_validate(void)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_policy_validate, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(validate),
|
||||||
|
Py_BuildValue("")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
python_plugin_policy_invalidate(int remove)
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_policy_invalidate, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(invalidate),
|
||||||
|
Py_BuildValue("(i)", remove));
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
python_plugin_policy_init_session(struct passwd *pwd, char **user_env[])
|
||||||
|
{
|
||||||
|
debug_decl(python_plugin_policy_init_session, PYTHON_DEBUG_CALLBACKS);
|
||||||
|
int rc = SUDO_RC_ERROR;
|
||||||
|
PyObject *py_pwd = NULL, *py_user_env = NULL, *py_result = NULL;
|
||||||
|
|
||||||
|
py_pwd = py_from_passwd(pwd);
|
||||||
|
if (py_pwd == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
py_user_env = py_str_array_to_tuple(*user_env);
|
||||||
|
if (py_user_env == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
py_result = python_plugin_api_call(&plugin_ctx, CALLBACK_PYNAME(init_session),
|
||||||
|
Py_BuildValue("(OO)", py_pwd, py_user_env));
|
||||||
|
if (py_result == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
PyObject *py_user_env_out = NULL, *py_rc = NULL;
|
||||||
|
if (PyTuple_Check(py_result)) {
|
||||||
|
if (!PyArg_ParseTuple(py_result, "O!|O!:python_plugin.init_session",
|
||||||
|
&PyLong_Type, &py_rc,
|
||||||
|
&PyTuple_Type, &py_user_env_out)) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
py_rc = py_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (py_user_env_out != NULL) {
|
||||||
|
str_array_free(user_env);
|
||||||
|
*user_env = py_str_array_from_tuple(py_user_env_out);
|
||||||
|
if (*user_env == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = python_plugin_rc_to_int(py_rc);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
Py_XDECREF(py_pwd);
|
||||||
|
Py_XDECREF(py_user_env);
|
||||||
|
Py_XDECREF(py_result);
|
||||||
|
|
||||||
|
debug_return_int(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
__dso_public struct policy_plugin python_policy = {
|
||||||
|
SUDO_POLICY_PLUGIN,
|
||||||
|
SUDO_API_VERSION,
|
||||||
|
CALLBACK_CFUNC(open),
|
||||||
|
CALLBACK_CFUNC(close),
|
||||||
|
CALLBACK_CFUNC(version),
|
||||||
|
CALLBACK_CFUNC(check),
|
||||||
|
CALLBACK_CFUNC(list),
|
||||||
|
CALLBACK_CFUNC(validate),
|
||||||
|
CALLBACK_CFUNC(invalidate),
|
||||||
|
CALLBACK_CFUNC(init_session),
|
||||||
|
NULL, /* register_hooks */
|
||||||
|
NULL, /* deregister_hooks */
|
||||||
|
NULL /* event_alloc */
|
||||||
|
};
|
Reference in New Issue
Block a user