Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/python/python_plugin_common.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2019-2020 Robert Manner <[email protected]>
5
*
6
* Permission to use, copy, modify, and distribute this software for any
7
* purpose with or without fee is hereby granted, provided that the above
8
* copyright notice and this permission notice appear in all copies.
9
*
10
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
*/
18
19
#include "python_plugin_common.h"
20
#include "sudo_python_module.h"
21
22
#include <sudo_queue.h>
23
#include <sudo_conf.h>
24
25
#include <limits.h>
26
#include <string.h>
27
28
static struct _inittab * python_inittab_copy = NULL;
29
static size_t python_inittab_copy_len = 0;
30
31
#ifndef PLUGIN_DIR
32
#define PLUGIN_DIR ""
33
#endif
34
35
/* Py_FinalizeEx is new in version 3.6 */
36
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 6
37
# define Py_FinalizeEx() (Py_Finalize(), 0)
38
#endif
39
40
static const char *
41
_lookup_value(char * const keyvalues[], const char *key)
42
{
43
debug_decl(_lookup_value, PYTHON_DEBUG_INTERNAL);
44
if (keyvalues == NULL)
45
debug_return_const_str(NULL);
46
47
size_t keylen = strlen(key);
48
for (; *keyvalues != NULL; ++keyvalues) {
49
const char *keyvalue = *keyvalues;
50
if (strncmp(keyvalue, key, keylen) == 0 && keyvalue[keylen] == '=')
51
debug_return_const_str(keyvalue + keylen + 1);
52
}
53
debug_return_const_str(NULL);
54
}
55
56
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
57
static int
58
_append_python_path(const char *module_dir)
59
{
60
debug_decl(_append_python_path, PYTHON_DEBUG_PLUGIN_LOAD);
61
int rc = -1;
62
PyObject *py_sys_path = PySys_GetObject("path"); // borrowed
63
if (py_sys_path == NULL) {
64
PyErr_Format(sudo_exc_SudoException, "Failed to get python 'path'");
65
debug_return_int(rc);
66
}
67
68
sudo_debug_printf(SUDO_DEBUG_DIAG, "Extending python 'path' with '%s'\n", module_dir);
69
70
PyObject *py_module_dir = PyUnicode_FromString(module_dir);
71
if (py_module_dir == NULL || PyList_Append(py_sys_path, py_module_dir) != 0) {
72
Py_XDECREF(py_module_dir);
73
debug_return_int(rc);
74
}
75
Py_DECREF(py_module_dir);
76
77
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
78
char *path = py_join_str_list(py_sys_path, ":");
79
sudo_debug_printf(SUDO_DEBUG_INFO, "Python path became: %s\n", path);
80
free(path);
81
}
82
83
rc = 0;
84
debug_return_int(rc);
85
}
86
87
static PyObject *
88
_import_module(const char *path)
89
{
90
PyObject *module;
91
debug_decl(_import_module, PYTHON_DEBUG_PLUGIN_LOAD);
92
93
sudo_debug_printf(SUDO_DEBUG_DIAG, "importing module: %s\n", path);
94
95
char path_copy[PATH_MAX];
96
if (strlcpy(path_copy, path, sizeof(path_copy)) >= sizeof(path_copy))
97
debug_return_ptr(NULL);
98
99
const char *module_dir = path_copy;
100
char *module_name = strrchr(path_copy, '/');
101
if (module_name == NULL) {
102
module_name = path_copy;
103
module_dir = "";
104
} else {
105
*module_name++ = '\0';
106
}
107
108
size_t len = strlen(module_name);
109
if (len >= 3 && strcmp(".py", module_name + len - 3) == 0)
110
module_name[len - 3] = '\0';
111
112
sudo_debug_printf(SUDO_DEBUG_INFO, "module_name: '%s', module_dir: '%s'\n", module_name, module_dir);
113
114
if (_append_python_path(module_dir) < 0)
115
debug_return_ptr(NULL);
116
117
module = PyImport_ImportModule(module_name);
118
if (module != NULL) {
119
PyObject *py_loaded_path = PyObject_GetAttrString(module, "__file__");
120
if (py_loaded_path != NULL) {
121
const char *loaded_path = PyUnicode_AsUTF8(py_loaded_path);
122
/* If path is a directory, loaded_path may be a file inside it. */
123
if (strncmp(loaded_path, path, strlen(path)) != 0) {
124
PyErr_Format(PyExc_Exception,
125
"module name conflict, tried to load %s, got %s",
126
path, loaded_path);
127
Py_CLEAR(module);
128
}
129
Py_DECREF(py_loaded_path);
130
}
131
}
132
debug_return_ptr(module);
133
}
134
135
// Create a new sub-interpreter and switch to it.
136
static PyThreadState *
137
_python_plugin_new_interpreter(void)
138
{
139
debug_decl(_python_plugin_new_interpreter, PYTHON_DEBUG_INTERNAL);
140
if (py_ctx.interpreter_count >= INTERPRETER_MAX) {
141
PyErr_Format(PyExc_Exception, "Too many interpreters");
142
debug_return_ptr(NULL);
143
}
144
145
PyThreadState *py_interpreter = Py_NewInterpreter();
146
if (py_interpreter != NULL) {
147
py_ctx.py_subinterpreters[py_ctx.interpreter_count] = py_interpreter;
148
++py_ctx.interpreter_count;
149
}
150
151
debug_return_ptr(py_interpreter);
152
}
153
154
static int
155
_save_inittab(void)
156
{
157
debug_decl(_save_inittab, PYTHON_DEBUG_INTERNAL);
158
free(python_inittab_copy); // just to be sure (it is always NULL)
159
160
for (python_inittab_copy_len = 0;
161
PyImport_Inittab[python_inittab_copy_len].name != NULL;
162
++python_inittab_copy_len) {
163
}
164
++python_inittab_copy_len; // for the null mark
165
166
python_inittab_copy = malloc(sizeof(struct _inittab) * python_inittab_copy_len);
167
if (python_inittab_copy == NULL) {
168
debug_return_int(SUDO_RC_ERROR);
169
}
170
171
memcpy(python_inittab_copy, PyImport_Inittab, python_inittab_copy_len * sizeof(struct _inittab));
172
debug_return_int(SUDO_RC_OK);
173
}
174
175
static void
176
_restore_inittab(void)
177
{
178
debug_decl(_restore_inittab, PYTHON_DEBUG_INTERNAL);
179
180
if (python_inittab_copy != NULL)
181
memcpy(PyImport_Inittab, python_inittab_copy, python_inittab_copy_len * sizeof(struct _inittab));
182
183
free(python_inittab_copy);
184
python_inittab_copy = NULL;
185
python_inittab_copy_len = 0;
186
debug_return;
187
}
188
189
static void
190
python_plugin_handle_plugin_error_exception(PyObject **py_result, struct PluginContext *plugin_ctx)
191
{
192
debug_decl(python_plugin_handle_plugin_error_exception, PYTHON_DEBUG_INTERNAL);
193
194
free(plugin_ctx->callback_error);
195
plugin_ctx->callback_error = NULL;
196
197
if (PyErr_Occurred()) {
198
int rc = SUDO_RC_ERROR;
199
if (PyErr_ExceptionMatches(sudo_exc_PluginReject)) {
200
rc = SUDO_RC_REJECT;
201
} else if (!PyErr_ExceptionMatches(sudo_exc_PluginError)) {
202
debug_return;
203
}
204
205
if (py_result != NULL) {
206
Py_CLEAR(*py_result);
207
*py_result = PyLong_FromLong(rc);
208
}
209
210
PyObject *py_type = NULL, *py_message = NULL, *py_traceback = NULL;
211
PyErr_Fetch(&py_type, &py_message, &py_traceback);
212
213
char *message = py_message ? py_create_string_rep(py_message) : NULL;
214
sudo_debug_printf(SUDO_DEBUG_INFO, "received sudo.PluginError exception with message '%s'",
215
message == NULL ? "(null)" : message);
216
217
plugin_ctx->callback_error = message;
218
219
Py_CLEAR(py_type);
220
Py_CLEAR(py_message);
221
Py_CLEAR(py_traceback);
222
}
223
224
debug_return;
225
}
226
227
int
228
python_plugin_construct_custom(struct PluginContext *plugin_ctx, PyObject *py_kwargs)
229
{
230
debug_decl(python_plugin_construct_custom, PYTHON_DEBUG_PLUGIN_LOAD);
231
int rc = SUDO_RC_ERROR;
232
PyObject *py_args = PyTuple_New(0);
233
234
if (py_args == NULL)
235
goto cleanup;
236
237
py_debug_python_call(python_plugin_name(plugin_ctx), "__init__",
238
py_args, py_kwargs, PYTHON_DEBUG_PY_CALLS);
239
240
plugin_ctx->py_instance = PyObject_Call(plugin_ctx->py_class, py_args, py_kwargs);
241
python_plugin_handle_plugin_error_exception(NULL, plugin_ctx);
242
243
py_debug_python_result(python_plugin_name(plugin_ctx), "__init__",
244
plugin_ctx->py_instance, PYTHON_DEBUG_PY_CALLS);
245
246
if (plugin_ctx->py_instance)
247
rc = SUDO_RC_OK;
248
249
cleanup:
250
if (PyErr_Occurred()) {
251
py_log_last_error("Failed to construct plugin instance");
252
Py_CLEAR(plugin_ctx->py_instance);
253
rc = SUDO_RC_ERROR;
254
}
255
256
Py_XDECREF(py_args);
257
debug_return_int(rc);
258
}
259
260
PyObject *
261
python_plugin_construct_args(unsigned int version,
262
char *const settings[], char *const user_info[],
263
char *const user_env[], char *const plugin_options[])
264
{
265
PyObject *py_settings = NULL;
266
PyObject *py_user_info = NULL;
267
PyObject *py_user_env = NULL;
268
PyObject *py_plugin_options = NULL;
269
PyObject *py_version = NULL;
270
PyObject *py_kwargs = NULL;
271
272
if ((py_settings = py_str_array_to_tuple(settings)) == NULL ||
273
(py_user_info = py_str_array_to_tuple(user_info)) == NULL ||
274
(py_user_env = py_str_array_to_tuple(user_env)) == NULL ||
275
(py_plugin_options = py_str_array_to_tuple(plugin_options)) == NULL ||
276
(py_version = py_create_version(version)) == NULL ||
277
(py_kwargs = PyDict_New()) == NULL ||
278
PyDict_SetItemString(py_kwargs, "version", py_version) != 0 ||
279
PyDict_SetItemString(py_kwargs, "settings", py_settings) != 0 ||
280
PyDict_SetItemString(py_kwargs, "user_env", py_user_env) != 0 ||
281
PyDict_SetItemString(py_kwargs, "user_info", py_user_info) != 0 ||
282
PyDict_SetItemString(py_kwargs, "plugin_options", py_plugin_options) != 0)
283
{
284
Py_CLEAR(py_kwargs);
285
}
286
287
Py_CLEAR(py_settings);
288
Py_CLEAR(py_user_info);
289
Py_CLEAR(py_user_env);
290
Py_CLEAR(py_plugin_options);
291
Py_CLEAR(py_version);
292
return py_kwargs;
293
}
294
295
int
296
python_plugin_construct(struct PluginContext *plugin_ctx, unsigned int version,
297
char *const settings[], char *const user_info[],
298
char *const user_env[], char *const plugin_options[])
299
{
300
debug_decl(python_plugin_construct, PYTHON_DEBUG_PLUGIN_LOAD);
301
302
int rc = SUDO_RC_ERROR;
303
PyObject *py_kwargs = python_plugin_construct_args(
304
version, settings, user_info, user_env, plugin_options);
305
306
if (py_kwargs == NULL) {
307
py_log_last_error("Failed to construct plugin instance");
308
} else {
309
rc = python_plugin_construct_custom(plugin_ctx, py_kwargs);
310
}
311
312
Py_CLEAR(py_kwargs);
313
314
debug_return_int(rc);
315
}
316
317
int
318
python_plugin_register_logging(sudo_conv_t conversation,
319
sudo_printf_t sudo_printf,
320
char * const settings[])
321
{
322
debug_decl(python_plugin_register_logging, PYTHON_DEBUG_INTERNAL);
323
324
int rc = SUDO_RC_ERROR;
325
if (conversation != NULL)
326
py_ctx.sudo_conv = conversation;
327
328
if (sudo_printf)
329
py_ctx.sudo_log = sudo_printf;
330
331
struct sudo_conf_debug_file_list debug_files = TAILQ_HEAD_INITIALIZER(debug_files);
332
struct sudo_conf_debug_file_list *debug_files_ptr = &debug_files;
333
334
const char *plugin_path = _lookup_value(settings, "plugin_path");
335
if (plugin_path == NULL)
336
plugin_path = "python_plugin.so";
337
338
const char *debug_flags = _lookup_value(settings, "debug_flags");
339
340
if (debug_flags == NULL) { // the group plugin does not have this information, so try to look it up
341
debug_files_ptr = sudo_conf_debug_files(plugin_path);
342
} else {
343
if (!python_debug_parse_flags(&debug_files, debug_flags))
344
goto cleanup;
345
}
346
347
if (debug_files_ptr != NULL) {
348
if (!python_debug_register(plugin_path, debug_files_ptr))
349
goto cleanup;
350
}
351
352
rc = SUDO_RC_OK;
353
354
cleanup:
355
debug_return_int(rc);
356
}
357
358
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
359
static int
360
_python_plugin_register_plugin_in_py_ctx(void)
361
{
362
debug_decl(_python_plugin_register_plugin_in_py_ctx, PYTHON_DEBUG_PLUGIN_LOAD);
363
364
if (!Py_IsInitialized()) {
365
// Disable environment variables effecting the python interpreter
366
// This is important since we are running code here as root, the
367
// user should not be able to alter what is running any how.
368
#if (PY_MAJOR_VERSION > 3) || (PY_MINOR_VERSION >= 8)
369
PyStatus status;
370
PyPreConfig preconfig;
371
PyConfig config;
372
373
PyPreConfig_InitPythonConfig(&preconfig);
374
preconfig.isolated = 1;
375
preconfig.use_environment = 0;
376
status = Py_PreInitialize(&preconfig);
377
if (PyStatus_Exception(status))
378
debug_return_int(SUDO_RC_ERROR);
379
380
/* Inittab changes happen after pre-init but before init. */
381
if (_save_inittab() != SUDO_RC_OK)
382
debug_return_int(SUDO_RC_ERROR);
383
PyImport_AppendInittab("sudo", sudo_module_init);
384
385
PyConfig_InitPythonConfig(&config);
386
config.isolated = 1;
387
status = Py_InitializeFromConfig(&config);
388
PyConfig_Clear(&config);
389
if (PyStatus_Exception(status))
390
debug_return_int(SUDO_RC_ERROR);
391
#else
392
Py_IgnoreEnvironmentFlag = 1;
393
Py_IsolatedFlag = 1;
394
Py_NoUserSiteDirectory = 1;
395
396
if (_save_inittab() != SUDO_RC_OK)
397
debug_return_int(SUDO_RC_ERROR);
398
PyImport_AppendInittab("sudo", sudo_module_init);
399
Py_InitializeEx(0);
400
#endif
401
py_ctx.py_main_interpreter = PyThreadState_Get();
402
403
// This ensures we import "sudo" module in the main interpreter,
404
// each subinterpreter will have a shallow copy.
405
// (This makes the C sudo module able to eg. import other modules.)
406
PyObject *py_sudo = NULL;
407
if ((py_sudo = PyImport_ImportModule("sudo")) == NULL) {
408
debug_return_int(SUDO_RC_ERROR);
409
}
410
Py_CLEAR(py_sudo);
411
} else {
412
PyThreadState_Swap(py_ctx.py_main_interpreter);
413
}
414
415
debug_return_int(SUDO_RC_OK);
416
}
417
418
static int
419
_python_plugin_set_path(struct PluginContext *plugin_ctx, const char *path)
420
{
421
if (path == NULL) {
422
py_sudo_log(SUDO_CONV_ERROR_MSG, "No python module path is specified. "
423
"Use 'ModulePath' plugin config option in 'sudo.conf'\n");
424
return SUDO_RC_ERROR;
425
}
426
427
if (*path == '/') { // absolute path
428
plugin_ctx->plugin_path = strdup(path);
429
} else {
430
if (asprintf(&plugin_ctx->plugin_path, PLUGIN_DIR "/python/%s", path) < 0)
431
plugin_ctx->plugin_path = NULL;
432
}
433
434
if (plugin_ctx->plugin_path == NULL) {
435
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to allocate memory");
436
return SUDO_RC_ERROR;
437
}
438
439
return SUDO_RC_OK;
440
}
441
442
/* Returns the list of sudo.Plugins in a module */
443
static PyObject *
444
_python_plugin_class_list(PyObject *py_module) {
445
PyObject *py_module_dict = PyModule_GetDict(py_module); // Note: borrowed
446
PyObject *key, *value; // Note: borrowed
447
Py_ssize_t pos = 0;
448
PyObject *py_plugin_list = PyList_New(0);
449
450
while (PyDict_Next(py_module_dict, &pos, &key, &value)) {
451
if (PyObject_IsSubclass(value, (PyObject *)sudo_type_Plugin) == 1) {
452
if (PyList_Append(py_plugin_list, key) != 0)
453
goto cleanup;
454
} else {
455
PyErr_Clear();
456
}
457
}
458
459
cleanup:
460
if (PyErr_Occurred()) {
461
Py_CLEAR(py_plugin_list);
462
}
463
return py_plugin_list;
464
}
465
466
/* Gets a sudo.Plugin class from the specified module. The argument "plugin_class"
467
* can be NULL in which case it loads the one and only "sudo.Plugin" present
468
* in the module (if so), or displays helpful error message. */
469
static PyObject *
470
_python_plugin_get_class(const char *plugin_path, PyObject *py_module, const char *plugin_class)
471
{
472
debug_decl(_python_plugin_get_class, PYTHON_DEBUG_PLUGIN_LOAD);
473
PyObject *py_plugin_list = NULL, *py_class = NULL;
474
475
if (plugin_class == NULL) {
476
py_plugin_list = _python_plugin_class_list(py_module);
477
if (py_plugin_list == NULL) {
478
goto cleanup;
479
}
480
481
if (PyList_Size(py_plugin_list) == 1) {
482
PyObject *py_plugin_name = PyList_GetItem(py_plugin_list, 0); // Note: borrowed
483
plugin_class = PyUnicode_AsUTF8(py_plugin_name);
484
}
485
}
486
487
if (plugin_class == NULL) {
488
py_sudo_log(SUDO_CONV_ERROR_MSG, "No plugin class is specified for python module '%s'. "
489
"Use 'ClassName' configuration option in 'sudo.conf'\n", plugin_path);
490
if (py_plugin_list != NULL) {
491
/* Sorting the plugin list makes regress test output consistent. */
492
PyObject *py_obj = PyObject_CallMethod(py_plugin_list, "sort", "");
493
Py_CLEAR(py_obj);
494
char *possible_plugins = py_join_str_list(py_plugin_list, ", ");
495
if (possible_plugins != NULL) {
496
py_sudo_log(SUDO_CONV_ERROR_MSG, "Possible plugins: %s\n", possible_plugins);
497
free(possible_plugins);
498
}
499
}
500
goto cleanup;
501
}
502
503
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Using plugin class '%s'", plugin_class);
504
py_class = PyObject_GetAttrString(py_module, plugin_class);
505
if (py_class == NULL) {
506
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to find plugin class '%s'\n", plugin_class);
507
PyErr_Clear();
508
goto cleanup;
509
}
510
511
if (!PyObject_IsSubclass(py_class, (PyObject *)sudo_type_Plugin)) {
512
py_sudo_log(SUDO_CONV_ERROR_MSG, "Plugin class '%s' does not inherit from 'sudo.Plugin'\n", plugin_class);
513
Py_CLEAR(py_class);
514
goto cleanup;
515
}
516
517
cleanup:
518
Py_CLEAR(py_plugin_list);
519
debug_return_ptr(py_class);
520
}
521
522
int
523
python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[],
524
unsigned int version)
525
{
526
debug_decl(python_plugin_init, PYTHON_DEBUG_PLUGIN_LOAD);
527
528
int rc = SUDO_RC_ERROR;
529
530
if (_python_plugin_register_plugin_in_py_ctx() != SUDO_RC_OK)
531
goto cleanup;
532
533
plugin_ctx->sudo_api_version = version;
534
535
plugin_ctx->py_interpreter = _python_plugin_new_interpreter();
536
if (plugin_ctx->py_interpreter == NULL) {
537
goto cleanup;
538
}
539
540
if (sudo_module_set_default_loghandler() != SUDO_RC_OK) {
541
goto cleanup;
542
}
543
544
if (_python_plugin_set_path(plugin_ctx, _lookup_value(plugin_options, "ModulePath")) != SUDO_RC_OK) {
545
goto cleanup;
546
}
547
548
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Loading python module from path '%s'", plugin_ctx->plugin_path);
549
plugin_ctx->py_module = _import_module(plugin_ctx->plugin_path);
550
if (plugin_ctx->py_module == NULL) {
551
goto cleanup;
552
}
553
554
plugin_ctx->py_class = _python_plugin_get_class(plugin_ctx->plugin_path, plugin_ctx->py_module,
555
_lookup_value(plugin_options, "ClassName"));
556
if (plugin_ctx->py_class == NULL) {
557
goto cleanup;
558
}
559
560
rc = SUDO_RC_OK;
561
562
cleanup:
563
if (plugin_ctx->py_class == NULL) {
564
py_log_last_error("Failed during loading plugin class");
565
rc = SUDO_RC_ERROR;
566
}
567
568
debug_return_int(rc);
569
}
570
571
void
572
python_plugin_deinit(struct PluginContext *plugin_ctx)
573
{
574
debug_decl(python_plugin_deinit, PYTHON_DEBUG_PLUGIN_LOAD);
575
sudo_debug_printf(SUDO_DEBUG_DIAG, "Deinit was called for a python plugin\n");
576
577
Py_CLEAR(plugin_ctx->py_instance);
578
Py_CLEAR(plugin_ctx->py_class);
579
Py_CLEAR(plugin_ctx->py_module);
580
581
// Note: we are preserving the interpreters here until the unlink because
582
// of bugs like (strptime does not work after python interpreter reinit):
583
// https://bugs.python.org/issue27400
584
// These potentially effect a lot more python functions, simply because
585
// it is a rare tested scenario.
586
587
free(plugin_ctx->callback_error);
588
free(plugin_ctx->plugin_path);
589
memset(plugin_ctx, 0, sizeof(*plugin_ctx));
590
591
python_debug_deregister();
592
debug_return;
593
}
594
595
PyObject *
596
python_plugin_api_call(struct PluginContext *plugin_ctx, const char *func_name, PyObject *py_args)
597
{
598
debug_decl(python_plugin_api_call, PYTHON_DEBUG_PY_CALLS);
599
600
// Note: call fails if py_args is an empty tuple. Passing no arguments works passing NULL
601
// instead. So having such must be handled as valid. (See policy_plugin.validate())
602
if (py_args == NULL && PyErr_Occurred()) {
603
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to build arguments for python plugin API call '%s'\n", func_name);
604
py_log_last_error(NULL);
605
debug_return_ptr(NULL);
606
}
607
608
PyObject *py_callable = NULL;
609
py_callable = PyObject_GetAttrString(plugin_ctx->py_instance, func_name);
610
611
if (py_callable == NULL) {
612
Py_CLEAR(py_args);
613
debug_return_ptr(NULL);
614
}
615
616
py_debug_python_call(python_plugin_name(plugin_ctx), func_name,
617
py_args, NULL, PYTHON_DEBUG_PY_CALLS);
618
619
PyObject *py_result = PyObject_CallObject(py_callable, py_args);
620
Py_CLEAR(py_args);
621
Py_CLEAR(py_callable);
622
623
py_debug_python_result(python_plugin_name(plugin_ctx), func_name,
624
py_result, PYTHON_DEBUG_PY_CALLS);
625
626
python_plugin_handle_plugin_error_exception(&py_result, plugin_ctx);
627
628
if (PyErr_Occurred()) {
629
py_log_last_error(NULL);
630
}
631
632
debug_return_ptr(py_result);
633
}
634
635
int
636
python_plugin_rc_to_int(PyObject *py_result)
637
{
638
debug_decl(python_plugin_rc_to_int, PYTHON_DEBUG_PY_CALLS);
639
if (py_result == NULL)
640
debug_return_int(SUDO_RC_ERROR);
641
642
if (py_result == Py_None)
643
debug_return_int(SUDO_RC_OK);
644
645
debug_return_int((int)PyLong_AsLong(py_result));
646
}
647
648
int
649
python_plugin_api_rc_call(struct PluginContext *plugin_ctx, const char *func_name, PyObject *py_args)
650
{
651
debug_decl(python_plugin_api_rc_call, PYTHON_DEBUG_PY_CALLS);
652
653
PyObject *py_result = python_plugin_api_call(plugin_ctx, func_name, py_args);
654
int rc = python_plugin_rc_to_int(py_result);
655
Py_XDECREF(py_result);
656
debug_return_int(rc);
657
}
658
659
int
660
python_plugin_show_version(struct PluginContext *plugin_ctx, const char *python_callback_name,
661
int is_verbose, unsigned int plugin_api_version, const char *plugin_api_name)
662
{
663
debug_decl(python_plugin_show_version, PYTHON_DEBUG_CALLBACKS);
664
665
if (is_verbose) {
666
py_sudo_log(SUDO_CONV_INFO_MSG, "Python %s plugin (API %d.%d): %s (loaded from '%s')\n",
667
plugin_api_name,
668
SUDO_API_VERSION_GET_MAJOR(plugin_api_version),
669
SUDO_API_VERSION_GET_MINOR(plugin_api_version),
670
python_plugin_name(plugin_ctx),
671
plugin_ctx->plugin_path);
672
}
673
674
int rc = SUDO_RC_OK;
675
if (PyObject_HasAttrString(plugin_ctx->py_instance, python_callback_name)) {
676
rc = python_plugin_api_rc_call(plugin_ctx, python_callback_name,
677
Py_BuildValue("(i)", is_verbose));
678
}
679
680
debug_return_int(rc);
681
}
682
683
void
684
python_plugin_close(struct PluginContext *plugin_ctx, const char *callback_name,
685
PyObject *py_args)
686
{
687
debug_decl(python_plugin_close, PYTHON_DEBUG_CALLBACKS);
688
689
PyThreadState_Swap(plugin_ctx->py_interpreter);
690
691
// Note, this should handle the case when init has failed
692
if (plugin_ctx->py_instance != NULL) {
693
if (!plugin_ctx->call_close) {
694
sudo_debug_printf(SUDO_DEBUG_INFO, "Skipping close call, because there was no command run\n");
695
696
} else if (!PyObject_HasAttrString(plugin_ctx->py_instance, callback_name)) {
697
sudo_debug_printf(SUDO_DEBUG_INFO, "Python plugin function 'close' is skipped (not present)\n");
698
} else {
699
PyObject *py_result = python_plugin_api_call(plugin_ctx, callback_name, py_args);
700
py_args = NULL; // api call already freed it
701
Py_XDECREF(py_result);
702
}
703
}
704
705
Py_CLEAR(py_args);
706
707
if (PyErr_Occurred()) {
708
py_log_last_error(NULL);
709
}
710
711
python_plugin_deinit(plugin_ctx);
712
PyThreadState_Swap(py_ctx.py_main_interpreter);
713
714
debug_return;
715
}
716
717
void
718
python_plugin_mark_callback_optional(struct PluginContext *plugin_ctx,
719
const char *function_name, void **function)
720
{
721
if (!PyObject_HasAttrString(plugin_ctx->py_instance, function_name)) {
722
debug_decl_vars(python_plugin_mark_callback_optional, PYTHON_DEBUG_PY_CALLS);
723
sudo_debug_printf(SUDO_DEBUG_INFO, "%s function '%s' is not implemented\n",
724
Py_TYPENAME(plugin_ctx->py_instance), function_name);
725
*function = NULL;
726
}
727
}
728
729
const char *
730
python_plugin_name(struct PluginContext *plugin_ctx)
731
{
732
debug_decl(python_plugin_name, PYTHON_DEBUG_INTERNAL);
733
734
const char *name = "(NULL)";
735
736
if (plugin_ctx == NULL || !PyType_Check(plugin_ctx->py_class))
737
debug_return_const_str(name);
738
739
debug_return_const_str(((PyTypeObject *)(plugin_ctx->py_class))->tp_name);
740
}
741
742
void python_plugin_unlink(void) __attribute__((destructor));
743
744
// this gets run only when sudo unlinks the python_plugin.so
745
void
746
python_plugin_unlink(void)
747
{
748
debug_decl(python_plugin_unlink, PYTHON_DEBUG_INTERNAL);
749
if (py_ctx.py_main_interpreter == NULL)
750
return;
751
752
if (Py_IsInitialized()) {
753
sudo_debug_printf(SUDO_DEBUG_NOTICE, "Closing: deinit python %zu subinterpreters\n",
754
py_ctx.interpreter_count);
755
while (py_ctx.interpreter_count != 0) {
756
PyThreadState *py_interpreter =
757
py_ctx.py_subinterpreters[--py_ctx.interpreter_count];
758
PyThreadState_Swap(py_interpreter);
759
Py_EndInterpreter(py_interpreter);
760
}
761
762
sudo_debug_printf(SUDO_DEBUG_NOTICE, "Closing: deinit main interpreter\n");
763
764
// we need to call finalize from the main interpreter
765
PyThreadState_Swap(py_ctx.py_main_interpreter);
766
767
if (Py_FinalizeEx() != 0) {
768
sudo_debug_printf(SUDO_DEBUG_WARN, "Closing: failed to deinit python interpreter\n");
769
}
770
771
// Restore inittab so "sudo" module does not remain there (as garbage)
772
_restore_inittab();
773
}
774
py_ctx_reset();
775
debug_return;
776
}
777
778