plugins/python: autodetect ClassName field

If "ClassName" is not specified, load the one and only sudo.Plugin from
the module (if so), otherwise display which plugins are available from
which the system admin can choose.
This commit is contained in:
Robert Manner
2020-02-26 16:59:18 +01:00
committed by Todd C. Miller
parent 5c96b4407d
commit 34b4bb72d6
6 changed files with 115 additions and 22 deletions

View File

@@ -186,7 +186,11 @@ It must be either an absolute path or a path relative to the sudo Python plugin
directory: "@plugindir@/python". directory: "@plugindir@/python".
.TP 6n .TP 6n
ClassName ClassName
The name of the class implementing the sudo Python plugin. (Optional.) The name of the class implementing the sudo Python plugin.
If not supplied, the one and only sudo.Plugin that is present in the module
will be used.
If there are multiple such plugins in the module (or none), it
will result in an error.
.SS "Policy plugin API" .SS "Policy plugin API"
Policy plugins must be registered in Policy plugins must be registered in
sudo.conf(@mansectform@). sudo.conf(@mansectform@).

View File

@@ -156,7 +156,11 @@ The path of a python file which contains the class of the sudo Python plugin.
It must be either an absolute path or a path relative to the sudo Python plugin It must be either an absolute path or a path relative to the sudo Python plugin
directory: "@plugindir@/python". directory: "@plugindir@/python".
.It ClassName .It ClassName
The name of the class implementing the sudo Python plugin. (Optional.) The name of the class implementing the sudo Python plugin.
If not supplied, the one and only sudo.Plugin that is present in the module
will be used.
If there are multiple such plugins in the module (or none), it
will result in an error.
.El .El
.Ss Policy plugin API .Ss Policy plugin API
Policy plugins must be registered in Policy plugins must be registered in

View File

@@ -399,6 +399,82 @@ _python_plugin_set_path(struct PluginContext *plugin_ctx, const char *path)
return SUDO_RC_OK; return SUDO_RC_OK;
} }
/* Returns the list of sudo.Plugins in a module */
static PyObject *
_python_plugin_class_list(PyObject *py_module) {
PyObject *py_module_dict = PyModule_GetDict(py_module); // Note: borrowed
PyObject *key, *value; // Note: borrowed
Py_ssize_t pos = 0;
PyObject *py_plugin_list = PyList_New(0);
while (PyDict_Next(py_module_dict, &pos, &key, &value)) {
if (PyObject_IsSubclass(value, (PyObject *)sudo_type_Plugin) == 1) {
if (PyList_Append(py_plugin_list, key) != 0)
goto cleanup;
} else {
PyErr_Clear();
}
}
cleanup:
if (PyErr_Occurred()) {
Py_CLEAR(py_plugin_list);
}
return py_plugin_list;
}
/* Gets a sudo.Plugin class from the specified module. The argument "plugin_class"
* can be NULL in which case it loads the one and only "sudo.Plugin" present
* in the module (if so), or displays helpful error message. */
static PyObject *
_python_plugin_get_class(const char *plugin_path, PyObject *py_module, const char *plugin_class)
{
debug_decl(python_plugin_init, PYTHON_DEBUG_PLUGIN_LOAD);
PyObject *py_plugin_list = NULL, *py_class = NULL;
if (plugin_class == NULL) {
py_plugin_list = _python_plugin_class_list(py_module);
if (py_plugin_list == NULL) {
goto cleanup;
}
if (PyList_Size(py_plugin_list) == 1) {
PyObject *py_plugin_name = PyList_GetItem(py_plugin_list, 0); // Note: borrowed
plugin_class = PyUnicode_AsUTF8(py_plugin_name);
}
}
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", plugin_path);
if (py_plugin_list != NULL) {
char *possible_plugins = py_join_str_list(py_plugin_list, ", ");
if (possible_plugins != NULL) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "Possible plugins: %s\n", possible_plugins);
free(possible_plugins);
}
}
goto cleanup;
}
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Using plugin class '%s'", plugin_class);
py_class = PyObject_GetAttrString(py_module, plugin_class);
if (py_class == NULL) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to find plugin class '%s'\n", plugin_class);
goto cleanup;
}
if (!PyObject_IsSubclass(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(py_class);
goto cleanup;
}
cleanup:
Py_CLEAR(py_plugin_list);
debug_return_ptr(py_class);
}
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) unsigned int version)
@@ -419,8 +495,7 @@ python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options
PyThreadState_Swap(plugin_ctx->py_interpreter); PyThreadState_Swap(plugin_ctx->py_interpreter);
if (!sudo_conf_developer_mode() && sudo_module_register_importblocker() < 0) { if (!sudo_conf_developer_mode() && sudo_module_register_importblocker() < 0) {
py_log_last_error(NULL); goto cleanup;
debug_return_int(SUDO_RC_ERROR);
} }
if (_python_plugin_set_path(plugin_ctx, _lookup_value(plugin_options, "ModulePath")) != SUDO_RC_OK) { if (_python_plugin_set_path(plugin_ctx, _lookup_value(plugin_options, "ModulePath")) != SUDO_RC_OK) {
@@ -433,23 +508,9 @@ python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options
goto cleanup; goto cleanup;
} }
const char *plugin_class = _lookup_value(plugin_options, "ClassName"); plugin_ctx->py_class = _python_plugin_get_class(plugin_ctx->plugin_path, plugin_ctx->py_module,
if (plugin_class == NULL) { _lookup_value(plugin_options, "ClassName"));
py_sudo_log(SUDO_CONV_ERROR_MSG, "No plugin class is specified for python module '%s'. "
"Use 'ClassName' configuration option in 'sudo.conf'\n", plugin_ctx->plugin_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) { 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; goto cleanup;
} }

View File

@@ -584,10 +584,31 @@ check_loading_fails_with_missing_path(void)
} }
int int
check_loading_fails_with_missing_classname(void) check_loading_succeeds_with_missing_classname(void)
{ {
str_array_free(&data.plugin_options); str_array_free(&data.plugin_options);
data.plugin_options = create_str_array(2, "ModulePath=" SRC_DIR "/example_debugging.py", NULL); data.plugin_options = create_str_array(2, "ModulePath=" SRC_DIR "/example_debugging.py", NULL);
const char *errstr = NULL;
VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
VERIFY_PTR(errstr, NULL);
VERIFY_INT(python_io->show_version(1), SUDO_RC_OK);
python_io->close(0, 0);
VERIFY_STDOUT(expected_path("check_loading_succeeds_with_missing_classname.stdout"));
VERIFY_STR(data.stderr_str, "");
return true;
}
int
check_loading_fails_with_missing_classname(void)
{
str_array_free(&data.plugin_options);
data.plugin_options = create_str_array(2, "ModulePath=" SRC_DIR "/regress/plugin_errorstr.py", NULL);
return check_loading_fails("missing_classname"); return check_loading_fails("missing_classname");
} }
@@ -1511,6 +1532,7 @@ main(int argc, char *argv[])
RUN_TEST(check_plugin_unload()); RUN_TEST(check_plugin_unload());
RUN_TEST(check_loading_fails_with_missing_path()); RUN_TEST(check_loading_fails_with_missing_path());
RUN_TEST(check_loading_succeeds_with_missing_classname());
RUN_TEST(check_loading_fails_with_missing_classname()); RUN_TEST(check_loading_fails_with_missing_classname());
RUN_TEST(check_loading_fails_with_wrong_classname()); RUN_TEST(check_loading_fails_with_wrong_classname());
RUN_TEST(check_loading_fails_with_wrong_path()); RUN_TEST(check_loading_fails_with_wrong_path());

View File

@@ -1,2 +1,3 @@
No plugin class is specified for python module 'SRC_DIR/example_debugging.py'. Use 'ClassName' configuration option in 'sudo.conf' No plugin class is specified for python module 'SRC_DIR/regress/plugin_errorstr.py'. Use 'ClassName' configuration option in 'sudo.conf'
Possible plugins: ErrorMsgPlugin, ConstructErrorPlugin
Failed during loading plugin class Failed during loading plugin class

View File

@@ -0,0 +1 @@
Python io plugin (API 1.0): DebugDemoPlugin (loaded from 'SRC_DIR/example_debugging.py')