plugins/python: add support for callback errstr arguments

Plugins can raise a sudo.PluginError exception to add context message
for the failure.

The callback's errstr gets filled up with the specified message.
But, as sudo expects a string constant (will not free the string),
we store it in the plugin context at least until next callback
invocation.
This commit is contained in:
Robert Manner
2020-02-05 17:17:16 +01:00
committed by Todd C. Miller
parent 45d2638571
commit 3dd5f37af7
7 changed files with 120 additions and 25 deletions

View File

@@ -110,6 +110,37 @@ _import_module(const char *path)
debug_return_ptr(PyImport_ImportModule(module_name)); debug_return_ptr(PyImport_ImportModule(module_name));
} }
void
python_plugin_handle_plugin_error_exception(struct PluginContext *plugin_ctx)
{
debug_decl(python_plugin_handle_plugin_error_exception, PYTHON_DEBUG_INTERNAL);
free(plugin_ctx->callback_error);
plugin_ctx->callback_error = NULL;
if (PyErr_Occurred() && PyErr_ExceptionMatches(sudo_exc_PluginError)) {
PyObject *py_type = NULL, *py_message = NULL, *py_traceback = NULL;
PyErr_Fetch(&py_type, &py_message, &py_traceback);
char *message = py_message ? py_create_string_rep(py_message) : NULL;
sudo_debug_printf(SUDO_DEBUG_INFO, "received sudo.PluginError exception with message '%s'",
message == NULL ? "(null)" : message);
if (message != NULL && plugin_ctx->sudo_api_version < SUDO_API_MKVERSION(1, 15)) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s", message);
free(message);
} else {
plugin_ctx->callback_error = message;
}
Py_CLEAR(py_type);
Py_CLEAR(py_message);
Py_CLEAR(py_traceback);
}
debug_return;
}
int int
python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs) python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs)
{ {
@@ -124,15 +155,18 @@ python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kw
py_args, py_kwargs, PYTHON_DEBUG_PY_CALLS); py_args, py_kwargs, PYTHON_DEBUG_PY_CALLS);
plugin_ctx->py_instance = PyObject_Call(plugin_ctx->py_class, py_args, py_kwargs); plugin_ctx->py_instance = PyObject_Call(plugin_ctx->py_class, py_args, py_kwargs);
python_plugin_handle_plugin_error_exception(plugin_ctx);
py_debug_python_result(python_plugin_name(plugin_ctx), "__init__", py_debug_python_result(python_plugin_name(plugin_ctx), "__init__",
plugin_ctx->py_instance, PYTHON_DEBUG_PY_CALLS); plugin_ctx->py_instance, PYTHON_DEBUG_PY_CALLS);
rc = SUDO_RC_OK; if (plugin_ctx->py_instance)
rc = SUDO_RC_OK;
cleanup: cleanup:
if (plugin_ctx->py_instance == NULL) { if (PyErr_Occurred()) {
py_log_last_error("Failed to construct plugin instance"); py_log_last_error("Failed to construct plugin instance");
Py_CLEAR(plugin_ctx->py_instance);
rc = SUDO_RC_ERROR; rc = SUDO_RC_ERROR;
} }
@@ -261,7 +295,8 @@ _python_plugin_register_plugin_in_py_ctx(void)
} }
int int
python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[]) python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[],
unsigned int version)
{ {
debug_decl(python_plugin_init, PYTHON_DEBUG_PLUGIN_LOAD); debug_decl(python_plugin_init, PYTHON_DEBUG_PLUGIN_LOAD);
@@ -270,6 +305,8 @@ python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options
if (_python_plugin_register_plugin_in_py_ctx() != SUDO_RC_OK) if (_python_plugin_register_plugin_in_py_ctx() != SUDO_RC_OK)
goto cleanup; goto cleanup;
plugin_ctx->sudo_api_version = version;
plugin_ctx->py_interpreter = Py_NewInterpreter(); plugin_ctx->py_interpreter = Py_NewInterpreter();
if (plugin_ctx->py_interpreter == NULL) { if (plugin_ctx->py_interpreter == NULL) {
goto cleanup; goto cleanup;
@@ -341,6 +378,7 @@ python_plugin_deinit(struct PluginContext *plugin_ctx)
Py_EndInterpreter(plugin_ctx->py_interpreter); Py_EndInterpreter(plugin_ctx->py_interpreter);
} }
free(plugin_ctx->callback_error);
memset(plugin_ctx, 0, sizeof(*plugin_ctx)); memset(plugin_ctx, 0, sizeof(*plugin_ctx));
if (py_ctx.open_plugin_count <= 0) { if (py_ctx.open_plugin_count <= 0) {
@@ -390,7 +428,10 @@ python_plugin_api_call(struct PluginContext *plugin_ctx, const char *func_name,
py_debug_python_result(python_plugin_name(plugin_ctx), func_name, py_debug_python_result(python_plugin_name(plugin_ctx), func_name,
py_result, PYTHON_DEBUG_PY_CALLS); py_result, PYTHON_DEBUG_PY_CALLS);
if (py_result == NULL) {
python_plugin_handle_plugin_error_exception(plugin_ctx);
if (PyErr_Occurred()) {
py_log_last_error(NULL); py_log_last_error(NULL);
} }

View File

@@ -27,11 +27,16 @@ struct PluginContext {
PyObject *py_class; PyObject *py_class;
PyObject *py_instance; PyObject *py_instance;
int call_close; int call_close;
unsigned int sudo_api_version;
// We use this to let the error string live until sudo and the audit plugins
// are using it. Only set for sudo API >= 1.15, otherwise NULL
char *callback_error;
}; };
int python_plugin_register_logging(sudo_conv_t conversation, sudo_printf_t sudo_printf, char * const settings[]); 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_init(struct PluginContext *plugin_ctx, char * const plugin_options[], unsigned int version);
int python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs); int python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs);

View File

@@ -53,7 +53,7 @@ python_plugin_group_init(int version, sudo_printf_t sudo_printf, char *const plu
if (rc != SUDO_RC_OK) if (rc != SUDO_RC_OK)
debug_return_int(rc); debug_return_int(rc);
rc = python_plugin_init(&plugin_ctx, plugin_options); rc = python_plugin_init(&plugin_ctx, plugin_options, (unsigned int)version);
if (rc != SUDO_RC_OK) if (rc != SUDO_RC_OK)
debug_return_int(rc); debug_return_int(rc);

View File

@@ -44,6 +44,15 @@ struct IOPluginContext
(void **)&CALLBACK_PLUGINFUNC(function_name)); \ (void **)&CALLBACK_PLUGINFUNC(function_name)); \
} while(0) } while(0)
#define IO_CB_SET_ERROR(errstr) \
do { \
const char *cb_error = io_ctx->base_ctx.callback_error; \
if (cb_error != NULL && errstr != NULL) { \
*errstr = cb_error; \
} \
} while(false)
static int static int
_call_plugin_open(struct IOPluginContext *io_ctx, int argc, char * const argv[], char * const command_info[]) _call_plugin_open(struct IOPluginContext *io_ctx, int argc, char * const argv[], char * const command_info[])
{ {
@@ -95,14 +104,17 @@ python_plugin_io_open(struct IOPluginContext *io_ctx,
debug_return_int(rc); debug_return_int(rc);
struct PluginContext *plugin_ctx = BASE_CTX(io_ctx); struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
rc = python_plugin_init(plugin_ctx, plugin_options); rc = python_plugin_init(plugin_ctx, plugin_options, version);
if (rc != SUDO_RC_OK) if (rc != SUDO_RC_OK)
debug_return_int(rc); debug_return_int(rc);
rc = python_plugin_construct(plugin_ctx, PY_IO_PLUGIN_VERSION, rc = python_plugin_construct(plugin_ctx, PY_IO_PLUGIN_VERSION,
settings, user_info, user_env, plugin_options); settings, user_info, user_env, plugin_options);
if (rc != SUDO_RC_OK) if (rc != SUDO_RC_OK) {
IO_CB_SET_ERROR(errstr);
debug_return_int(rc); debug_return_int(rc);
}
// skip plugin callbacks which are not mandatory // skip plugin callbacks which are not mandatory
MARK_CALLBACK_OPTIONAL(show_version); MARK_CALLBACK_OPTIONAL(show_version);
@@ -118,6 +130,7 @@ python_plugin_io_open(struct IOPluginContext *io_ctx,
if (argc > 0) // we only call open if there is request for running sg if (argc > 0) // we only call open if there is request for running sg
rc = _call_plugin_open(io_ctx, argc, argv, command_info); rc = _call_plugin_open(io_ctx, argc, argv, command_info);
IO_CB_SET_ERROR(errstr);
debug_return_int(rc); debug_return_int(rc);
} }
@@ -151,8 +164,10 @@ python_plugin_io_log_ttyin(struct IOPluginContext *io_ctx, const char *buf, unsi
{ {
debug_decl(python_plugin_io_log_ttyin, PYTHON_DEBUG_CALLBACKS); debug_decl(python_plugin_io_log_ttyin, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_ttyin), int rc = python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_ttyin),
Py_BuildValue("(s#)", buf, len))); Py_BuildValue("(s#)", buf, len));
IO_CB_SET_ERROR(errstr);
debug_return_int(rc);
} }
int int
@@ -160,8 +175,10 @@ python_plugin_io_log_ttyout(struct IOPluginContext *io_ctx, const char *buf, uns
{ {
debug_decl(python_plugin_io_log_ttyout, PYTHON_DEBUG_CALLBACKS); debug_decl(python_plugin_io_log_ttyout, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_ttyout), int rc = python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_ttyout),
Py_BuildValue("(s#)", buf, len))); Py_BuildValue("(s#)", buf, len));
IO_CB_SET_ERROR(errstr);
debug_return_int(rc);
} }
int int
@@ -169,8 +186,10 @@ python_plugin_io_log_stdin(struct IOPluginContext *io_ctx, const char *buf, unsi
{ {
debug_decl(python_plugin_io_log_stdin, PYTHON_DEBUG_CALLBACKS); debug_decl(python_plugin_io_log_stdin, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stdin), int rc = python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stdin),
Py_BuildValue("(s#)", buf, len))); Py_BuildValue("(s#)", buf, len));
IO_CB_SET_ERROR(errstr);
debug_return_int(rc);
} }
int int
@@ -178,8 +197,10 @@ python_plugin_io_log_stdout(struct IOPluginContext *io_ctx, const char *buf, uns
{ {
debug_decl(python_plugin_io_log_stdout, PYTHON_DEBUG_CALLBACKS); debug_decl(python_plugin_io_log_stdout, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stdout), int rc = python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stdout),
Py_BuildValue("(s#)", buf, len))); Py_BuildValue("(s#)", buf, len));
IO_CB_SET_ERROR(errstr);
debug_return_int(rc);
} }
int int
@@ -187,8 +208,10 @@ python_plugin_io_log_stderr(struct IOPluginContext *io_ctx, const char *buf, uns
{ {
debug_decl(python_plugin_io_log_stderr, PYTHON_DEBUG_CALLBACKS); debug_decl(python_plugin_io_log_stderr, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stderr), int rc = python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stderr),
Py_BuildValue("(s#)", buf, len))); Py_BuildValue("(s#)", buf, len));
IO_CB_SET_ERROR(errstr);
debug_return_int(rc);
} }
int int
@@ -196,8 +219,10 @@ python_plugin_io_change_winsize(struct IOPluginContext *io_ctx, unsigned int lin
{ {
debug_decl(python_plugin_io_change_winsize, PYTHON_DEBUG_CALLBACKS); debug_decl(python_plugin_io_change_winsize, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(change_winsize), int rc = python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(change_winsize),
Py_BuildValue("(ii)", line, cols))); Py_BuildValue("(ii)", line, cols));
IO_CB_SET_ERROR(errstr);
debug_return_int(rc);
} }
int int
@@ -205,8 +230,10 @@ python_plugin_io_log_suspend(struct IOPluginContext *io_ctx, int signo, const ch
{ {
debug_decl(python_plugin_io_log_suspend, PYTHON_DEBUG_CALLBACKS); debug_decl(python_plugin_io_log_suspend, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_suspend), int rc = python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_suspend),
Py_BuildValue("(i)", signo))); Py_BuildValue("(i)", signo));
IO_CB_SET_ERROR(errstr);
debug_return_int(rc);
} }
// generate symbols for loading multiple io plugins: // generate symbols for loading multiple io plugins:

View File

@@ -42,6 +42,14 @@ extern struct policy_plugin python_policy;
(void **)&CALLBACK_PLUGINFUNC(function_name)); \ (void **)&CALLBACK_PLUGINFUNC(function_name)); \
} while(0) } while(0)
#define CB_SET_ERROR(errstr) \
do { \
const char *cb_error = plugin_ctx.callback_error; \
if (cb_error != NULL && errstr != NULL) { \
*errstr = cb_error; \
} \
} while(0)
static int static int
python_plugin_policy_open(unsigned int version, sudo_conv_t conversation, python_plugin_policy_open(unsigned int version, sudo_conv_t conversation,
sudo_printf_t sudo_printf, char * const settings[], sudo_printf_t sudo_printf, char * const settings[],
@@ -60,12 +68,13 @@ python_plugin_policy_open(unsigned int version, sudo_conv_t conversation,
if (rc != SUDO_RC_OK) if (rc != SUDO_RC_OK)
debug_return_int(rc); debug_return_int(rc);
rc = python_plugin_init(&plugin_ctx, plugin_options); rc = python_plugin_init(&plugin_ctx, plugin_options, version);
if (rc != SUDO_RC_OK) if (rc != SUDO_RC_OK)
debug_return_int(rc); debug_return_int(rc);
rc = python_plugin_construct(&plugin_ctx, PY_POLICY_PLUGIN_VERSION, settings, rc = python_plugin_construct(&plugin_ctx, PY_POLICY_PLUGIN_VERSION, settings,
user_info, user_env, plugin_options); user_info, user_env, plugin_options);
CB_SET_ERROR(errstr);
if (rc != SUDO_RC_OK) { if (rc != SUDO_RC_OK) {
debug_return_int(rc); debug_return_int(rc);
} }
@@ -147,6 +156,7 @@ python_plugin_policy_check(int argc, char * const argv[],
*user_env_out = py_str_array_from_tuple(py_user_env_out); *user_env_out = py_str_array_from_tuple(py_user_env_out);
rc = python_plugin_rc_to_int(py_rc); rc = python_plugin_rc_to_int(py_rc);
CB_SET_ERROR(errstr);
cleanup: cleanup:
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
@@ -185,6 +195,8 @@ python_plugin_policy_list(int argc, char * const argv[], int verbose, const char
Py_BuildValue("(Oiz)", py_argv, verbose, list_user)); Py_BuildValue("(Oiz)", py_argv, verbose, list_user));
Py_XDECREF(py_argv); Py_XDECREF(py_argv);
CB_SET_ERROR(errstr);
debug_return_int(rc); debug_return_int(rc);
} }
@@ -209,7 +221,9 @@ python_plugin_policy_validate(const char **errstr)
{ {
debug_decl(python_plugin_policy_validate, PYTHON_DEBUG_CALLBACKS); debug_decl(python_plugin_policy_validate, PYTHON_DEBUG_CALLBACKS);
PyThreadState_Swap(plugin_ctx.py_interpreter); PyThreadState_Swap(plugin_ctx.py_interpreter);
debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(validate), NULL)); int rc = python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(validate), NULL);
CB_SET_ERROR(errstr);
debug_return_int(rc);
} }
void void
@@ -262,6 +276,7 @@ python_plugin_policy_init_session(struct passwd *pwd, char **user_env[], const c
} }
rc = python_plugin_rc_to_int(py_rc); rc = python_plugin_rc_to_int(py_rc);
CB_SET_ERROR(errstr);
cleanup: cleanup:
Py_XDECREF(py_pwd); Py_XDECREF(py_pwd);

View File

@@ -31,6 +31,7 @@ PyAPI_FUNC(PyObject *) PyStructSequence_GetItem(PyObject *, Py_ssize_t);
// exceptions: // exceptions:
PyObject *sudo_exc_SudoException; PyObject *sudo_exc_SudoException;
PyObject *sudo_exc_PluginError;
static PyObject *sudo_exc_ConversationInterrupted; static PyObject *sudo_exc_ConversationInterrupted;
// the methods exposed in the "sudo" python module // the methods exposed in the "sudo" python module
@@ -562,6 +563,7 @@ sudo_module_init(void)
} while(0); } while(0);
MODULE_ADD_EXCEPTION(SudoException, NULL); MODULE_ADD_EXCEPTION(SudoException, NULL);
MODULE_ADD_EXCEPTION(PluginError, NULL);
MODULE_ADD_EXCEPTION(ConversationInterrupted, EXC_VAR(SudoException)); MODULE_ADD_EXCEPTION(ConversationInterrupted, EXC_VAR(SudoException));
#define MODULE_REGISTER_ENUM(name, key_values) \ #define MODULE_REGISTER_ENUM(name, key_values) \
@@ -611,6 +613,7 @@ cleanup:
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
Py_CLEAR(py_module); Py_CLEAR(py_module);
Py_CLEAR(sudo_exc_SudoException); Py_CLEAR(sudo_exc_SudoException);
Py_CLEAR(sudo_exc_PluginError);
Py_CLEAR(sudo_exc_ConversationInterrupted); Py_CLEAR(sudo_exc_ConversationInterrupted);
} }

View File

@@ -21,7 +21,11 @@
#include "pyhelpers.h" #include "pyhelpers.h"
extern PyObject *sudo_exc_SudoException; extern PyObject *sudo_exc_SudoException; // Base exception for the sudo module problems
// This is for the python plugins to report errors for us
extern PyObject *sudo_exc_PluginError;
extern PyTypeObject *sudo_type_Plugin; extern PyTypeObject *sudo_type_Plugin;
extern PyTypeObject *sudo_type_ConvMessage; extern PyTypeObject *sudo_type_ConvMessage;