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:

committed by
Todd C. Miller

parent
5c96b4407d
commit
34b4bb72d6
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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());
|
||||
|
@@ -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
|
||||
|
1
plugins/python/regress/testdata/check_loading_succeeds_with_missing_classname.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_succeeds_with_missing_classname.stdout
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Python io plugin (API 1.0): DebugDemoPlugin (loaded from 'SRC_DIR/example_debugging.py')
|
Reference in New Issue
Block a user