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

@@ -399,6 +399,82 @@ _python_plugin_set_path(struct PluginContext *plugin_ctx, const char *path)
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
python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[],
unsigned int version)
@@ -419,8 +495,7 @@ python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options
PyThreadState_Swap(plugin_ctx->py_interpreter);
if (!sudo_conf_developer_mode() && sudo_module_register_importblocker() < 0) {
py_log_last_error(NULL);
debug_return_int(SUDO_RC_ERROR);
goto cleanup;
}
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;
}
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", 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);
plugin_ctx->py_class = _python_plugin_get_class(plugin_ctx->plugin_path, plugin_ctx->py_module,
_lookup_value(plugin_options, "ClassName"));
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;
}

View File

@@ -584,10 +584,31 @@ check_loading_fails_with_missing_path(void)
}
int
check_loading_fails_with_missing_classname(void)
check_loading_succeeds_with_missing_classname(void)
{
str_array_free(&data.plugin_options);
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");
}
@@ -1511,6 +1532,7 @@ main(int argc, char *argv[])
RUN_TEST(check_plugin_unload());
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_wrong_classname());
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

View File

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