Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/python/sudo_python_module.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 "sudo_python_module.h"
20
21
#define EXC_VAR(exception_name) sudo_exc_ ## exception_name
22
#define TYPE_VAR(type_name) &sudo_type_ ## type_name
23
24
// exceptions:
25
PyObject *sudo_exc_SudoException;
26
PyObject *sudo_exc_PluginException;
27
PyObject *sudo_exc_PluginError;
28
PyObject *sudo_exc_PluginReject;
29
static PyObject *sudo_exc_ConversationInterrupted;
30
31
// the methods exposed in the "sudo" python module
32
// "args" is a tuple (~= const list) containing all the unnamed arguments
33
// "kwargs" is a dict of the keyword arguments or NULL if there are none
34
static PyObject *python_sudo_log_info(PyObject *py_self, PyObject *py_args, PyObject *py_kwargs);
35
static PyObject *python_sudo_log_error(PyObject *py_self, PyObject *py_args, PyObject *py_kwargs);
36
static PyObject *python_sudo_conversation(PyObject *py_self, PyObject *py_args, PyObject *py_kwargs);
37
static PyObject *python_sudo_options_as_dict(PyObject *py_self, PyObject *py_args);
38
static PyObject *python_sudo_options_from_dict(PyObject *py_self, PyObject *py_args);
39
40
// Called on module teardown.
41
static void sudo_module_free(void *self);
42
43
static PyMethodDef sudo_methods[] = {
44
{"debug", (PyCFunction)python_sudo_debug, METH_VARARGS, "Debug messages which can be saved to file in sudo.conf."},
45
{"log_info", (PyCFunction)python_sudo_log_info, METH_VARARGS | METH_KEYWORDS, "Display informational messages."},
46
{"log_error", (PyCFunction)python_sudo_log_error, METH_VARARGS | METH_KEYWORDS, "Display error messages."},
47
{"conv", (PyCFunction)python_sudo_conversation, METH_VARARGS | METH_KEYWORDS, "Interact with the user"},
48
{"options_as_dict", python_sudo_options_as_dict, METH_VARARGS, "Convert a string tuple in key=value format to a dictionary."},
49
{"options_from_dict", python_sudo_options_from_dict, METH_VARARGS, "Convert a dictionary to a tuple of strings in key=value format."},
50
{NULL, NULL, 0, NULL} /* Sentinel */
51
};
52
53
static struct PyModuleDef sudo_module = {
54
PyModuleDef_HEAD_INIT,
55
"sudo", /* name of module */
56
NULL, /* module documentation, may be NULL */
57
-1, /* size of per-interpreter state of the module,
58
or -1 if the module keeps state in global variables. */
59
sudo_methods,
60
NULL, /* slots */
61
NULL, /* traverse */
62
NULL, /* clear */
63
sudo_module_free
64
};
65
66
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
67
static int
68
_parse_log_function_args(PyObject *py_args, PyObject *py_kwargs, char **args_joined, const char ** end)
69
{
70
debug_decl(python_sudo_log, PYTHON_DEBUG_INTERNAL);
71
72
int rc = SUDO_RC_ERROR;
73
PyObject *py_empty = NULL;
74
75
const char *sep = NULL;
76
py_empty = PyTuple_New(0);
77
if (py_empty == NULL)
78
goto cleanup;
79
80
static const char *keywords[] = { "sep", "end", NULL };
81
if (py_kwargs != NULL && !PyArg_ParseTupleAndKeywords(py_empty, py_kwargs, "|zz:sudo.log", (char **)keywords, &sep, end))
82
goto cleanup;
83
84
if (sep == NULL)
85
sep = " ";
86
87
if (*end == NULL)
88
*end = "\n";
89
90
// this is to mimic the behaviour of python "print" / "log"
91
*args_joined = py_join_str_list(py_args, sep);
92
if (!PyErr_Occurred()) // == (*args_joined != NULL), but cpychecker does not understand that
93
rc = SUDO_RC_OK;
94
95
cleanup:
96
Py_CLEAR(py_empty);
97
debug_return_int(rc);
98
}
99
100
static PyObject *
101
python_sudo_log(int msg_type, PyObject *Py_UNUSED(py_self), PyObject *py_args, PyObject *py_kwargs)
102
{
103
debug_decl(python_sudo_log, PYTHON_DEBUG_C_CALLS);
104
py_debug_python_call("sudo", "log", py_args, py_kwargs, PYTHON_DEBUG_C_CALLS);
105
106
int rc = SUDO_RC_ERROR;
107
108
char *args_joined = NULL;
109
const char *end = NULL;
110
if (_parse_log_function_args(py_args, py_kwargs, &args_joined, &end) != SUDO_RC_OK)
111
goto cleanup;
112
113
rc = py_ctx.sudo_log(msg_type, "%s%s", args_joined, end);
114
if (rc < 0) {
115
PyErr_Format(sudo_exc_SudoException, "sudo.log: Error displaying message");
116
goto cleanup;
117
}
118
119
cleanup:
120
free(args_joined);
121
122
PyObject *py_result = PyErr_Occurred() ? NULL : PyLong_FromLong(rc);
123
124
py_debug_python_result("sudo", "log", py_result, PYTHON_DEBUG_C_CALLS);
125
debug_return_ptr(py_result);
126
}
127
128
static PyObject *
129
python_sudo_options_as_dict(PyObject *py_self, PyObject *py_args)
130
{
131
(void) py_self;
132
133
debug_decl(python_sudo_options_as_dict, PYTHON_DEBUG_C_CALLS);
134
py_debug_python_call("sudo", "options_as_dict", py_args, NULL, PYTHON_DEBUG_C_CALLS);
135
136
PyObject *py_config_tuple = NULL,
137
*py_result = NULL,
138
*py_config_tuple_iterator = NULL,
139
*py_config = NULL,
140
*py_splitted = NULL,
141
*py_separator = NULL;
142
143
if (!PyArg_ParseTuple(py_args, "O:sudo.options_as_dict", &py_config_tuple))
144
goto cleanup;
145
146
py_config_tuple_iterator = PyObject_GetIter(py_config_tuple);
147
if (py_config_tuple_iterator == NULL)
148
goto cleanup;
149
150
py_result = PyDict_New();
151
if (py_result == NULL)
152
goto cleanup;
153
154
py_separator = PyUnicode_FromString("=");
155
if (py_separator == NULL)
156
goto cleanup;
157
158
while ((py_config = PyIter_Next(py_config_tuple_iterator)) != NULL) {
159
py_splitted = PyUnicode_Split(py_config, py_separator, 1);
160
if (py_splitted == NULL)
161
goto cleanup;
162
163
PyObject *py_key = PyList_GetItem(py_splitted, 0); // borrowed ref
164
if (py_key == NULL)
165
goto cleanup;
166
167
PyObject *py_value = PyList_GetItem(py_splitted, 1);
168
if (py_value == NULL) { // skip values without a key
169
Py_CLEAR(py_config);
170
Py_CLEAR(py_splitted);
171
PyErr_Clear();
172
continue;
173
}
174
175
if (PyDict_SetItem(py_result, py_key, py_value) != 0) {
176
goto cleanup;
177
}
178
179
Py_CLEAR(py_config);
180
Py_CLEAR(py_splitted);
181
}
182
183
cleanup:
184
Py_CLEAR(py_config_tuple_iterator);
185
Py_CLEAR(py_config);
186
Py_CLEAR(py_splitted);
187
Py_CLEAR(py_separator);
188
189
if (PyErr_Occurred()) {
190
Py_CLEAR(py_result);
191
}
192
193
py_debug_python_result("sudo", "options_as_dict", py_result, PYTHON_DEBUG_C_CALLS);
194
debug_return_ptr(py_result);
195
}
196
197
static PyObject *
198
python_sudo_options_from_dict(PyObject *py_self, PyObject *py_args)
199
{
200
(void) py_self;
201
debug_decl(python_sudo_options_from_dict, PYTHON_DEBUG_C_CALLS);
202
py_debug_python_call("sudo", "options_from_dict", py_args, NULL, PYTHON_DEBUG_C_CALLS);
203
204
PyObject *py_config_dict = NULL,
205
*py_result = NULL;
206
207
if (!PyArg_ParseTuple(py_args, "O!:sudo.options_from_dict", &PyDict_Type, &py_config_dict))
208
goto cleanup;
209
210
Py_ssize_t dict_size = PyDict_Size(py_config_dict);
211
py_result = PyTuple_New(dict_size);
212
if (py_result == NULL)
213
goto cleanup;
214
215
PyObject *py_key = NULL, *py_value = NULL; // -> borrowed references
216
Py_ssize_t i, pos = 0;
217
for (i = 0; PyDict_Next(py_config_dict, &pos, &py_key, &py_value); i++) {
218
PyObject *py_config = PyUnicode_FromFormat("%S%s%S", py_key, "=", py_value);
219
if (py_config == NULL)
220
goto cleanup;
221
222
/* Dictionaries are sparse so we cannot use pos as an index. */
223
if (PyTuple_SetItem(py_result, i, py_config) != 0) { // this steals a reference, even on error
224
goto cleanup;
225
}
226
}
227
228
cleanup:
229
if (PyErr_Occurred()) {
230
Py_CLEAR(py_result);
231
}
232
233
py_debug_python_result("sudo", "options_from_dict", py_result, PYTHON_DEBUG_C_CALLS);
234
debug_return_ptr(py_result);
235
}
236
237
static PyObject *
238
python_sudo_log_info(PyObject *py_self, PyObject *py_args, PyObject *py_kwargs)
239
{
240
return python_sudo_log(SUDO_CONV_INFO_MSG, py_self, py_args, py_kwargs);
241
}
242
243
static PyObject *
244
python_sudo_log_error(PyObject *py_self, PyObject *py_args, PyObject *py_kwargs)
245
{
246
return python_sudo_log(SUDO_CONV_ERROR_MSG, py_self, py_args, py_kwargs);
247
}
248
249
CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
250
static int py_expect_arg_callable(PyObject *py_callable,
251
const char *func_name, const char *arg_name)
252
{
253
debug_decl(py_expect_arg_callable, PYTHON_DEBUG_INTERNAL);
254
255
if (!PyCallable_Check(py_callable)) {
256
PyErr_Format(PyExc_ValueError, "%s: %s argument must be python callable (got %s) ",
257
func_name, arg_name, Py_TYPENAME(py_callable));
258
debug_return_int(-1);
259
}
260
261
debug_return_int(0);
262
}
263
264
struct py_conv_callback_closure
265
{
266
PyObject *py_on_suspend;
267
PyObject *py_on_resume;
268
};
269
270
static int
271
_call_conversation_callback(PyObject *py_callback, int signo)
272
{
273
debug_decl(_call_conversation_callback, PYTHON_DEBUG_INTERNAL);
274
275
if (py_callback == NULL || py_callback == Py_None)
276
debug_return_int(0); // nothing to do
277
278
PyObject *py_result = PyObject_CallFunction(py_callback, "(i)", signo);
279
280
int rc = -1;
281
282
// We treat sudo.RC_OK (1) and None (no exception occurred) as success as well to avoid confusion
283
if (py_result && (py_result == Py_None || PyLong_AsLong(py_result) >= 0))
284
rc = 0;
285
286
Py_CLEAR(py_result);
287
288
if (rc != 0)
289
py_log_last_error("Error during conversation callback");
290
291
debug_return_int(rc);
292
}
293
294
static int
295
python_sudo_conversation_suspend_cb(int signo, struct py_conv_callback_closure *closure)
296
{
297
return _call_conversation_callback(closure->py_on_suspend, signo);
298
}
299
300
static int
301
python_sudo_conversation_resume_cb(int signo, struct py_conv_callback_closure *closure)
302
{
303
return _call_conversation_callback(closure->py_on_resume, signo);
304
}
305
306
static PyObject *
307
python_sudo_conversation(PyObject *Py_UNUSED(self), PyObject *py_args, PyObject *py_kwargs)
308
{
309
debug_decl(python_sudo_conversation, PYTHON_DEBUG_C_CALLS);
310
py_debug_python_call("sudo", "conv", py_args, py_kwargs, PYTHON_DEBUG_C_CALLS);
311
312
PyObject *py_result = NULL, *py_empty = NULL;
313
Py_ssize_t num_msgs = 0;
314
struct sudo_conv_message *msgs = NULL;
315
struct sudo_conv_reply *replies = NULL;
316
317
// Note, they are both borrowed references of py_kwargs
318
struct py_conv_callback_closure callback_closure = { NULL, NULL };
319
320
struct sudo_conv_callback callback = {
321
SUDO_CONV_CALLBACK_VERSION,
322
&callback_closure,
323
(sudo_conv_callback_fn_t)python_sudo_conversation_suspend_cb,
324
(sudo_conv_callback_fn_t)python_sudo_conversation_resume_cb
325
};
326
327
py_empty = PyTuple_New(0);
328
if (py_empty == NULL)
329
goto cleanup;
330
331
static const char *keywords[] = { "on_suspend", "on_resume", NULL };
332
if (py_kwargs != NULL && !PyArg_ParseTupleAndKeywords(py_empty, py_kwargs, "|OO:sudo.conv", (char **)keywords,
333
&callback_closure.py_on_suspend,
334
&callback_closure.py_on_resume))
335
goto cleanup;
336
337
if (callback_closure.py_on_suspend != NULL &&
338
py_expect_arg_callable(callback_closure.py_on_suspend, "sudo.conv", "on_suspend") < 0) {
339
goto cleanup;
340
}
341
342
if (callback_closure.py_on_resume != NULL &&
343
py_expect_arg_callable(callback_closure.py_on_resume, "sudo.conv", "on_resume") < 0) {
344
goto cleanup;
345
}
346
347
/* sudo_module_ConvMessages_to_c() returns error if no messages. */
348
if (sudo_module_ConvMessages_to_c(py_args, &num_msgs, &msgs) < 0) {
349
goto cleanup;
350
}
351
352
replies = calloc((size_t)num_msgs, sizeof(struct sudo_conv_reply));
353
if (replies == NULL)
354
goto cleanup;
355
py_result = PyTuple_New(num_msgs);
356
if (py_result == NULL)
357
goto cleanup;
358
359
if (py_ctx.sudo_conv == NULL) {
360
PyErr_Format(sudo_exc_SudoException, "%s: conversation is unavailable",
361
__func__);
362
goto cleanup;
363
}
364
365
int rc = py_sudo_conv((int)num_msgs, msgs, replies, &callback);
366
if (rc != 0) {
367
PyErr_Format(sudo_exc_ConversationInterrupted,
368
"%s: conversation was interrupted", __func__, rc);
369
goto cleanup;
370
}
371
372
for (Py_ssize_t i = 0; i < num_msgs; ++i) {
373
char *reply = replies[i].reply;
374
if (reply != NULL) {
375
PyObject *py_reply = PyUnicode_FromString(reply);
376
if (py_reply == NULL) {
377
goto cleanup;
378
}
379
380
if (PyTuple_SetItem(py_result, i, py_reply) != 0) { // this steals a reference even on error
381
PyErr_Format(sudo_exc_SudoException, "%s: failed to set tuple item", __func__);
382
goto cleanup;
383
}
384
385
sudo_debug_printf(SUDO_DEBUG_DIAG, "user reply for conversation: '%s'\n", reply);
386
}
387
}
388
389
cleanup:
390
Py_CLEAR(py_empty);
391
if (replies != NULL) {
392
for (int i = 0; i < num_msgs; ++i)
393
free(replies[i].reply);
394
}
395
free(msgs);
396
free(replies);
397
398
if (PyErr_Occurred()) {
399
Py_CLEAR(py_result); // we return NULL
400
}
401
402
py_debug_python_result("sudo", "conv", py_result, PYTHON_DEBUG_C_CALLS);
403
404
debug_return_ptr(py_result);
405
}
406
407
/*
408
* Create a python class.
409
* Class name must be a full name including module, eg. "sudo.MyFavouriteClass".
410
* The resulting class object can be added to a module using PyModule_AddObject.
411
*/
412
PyObject *
413
sudo_module_create_class(const char *class_name, PyMethodDef *class_methods,
414
PyObject *base_class)
415
{
416
debug_decl(sudo_module_create_class, PYTHON_DEBUG_INTERNAL);
417
418
PyObject *py_base_classes = NULL, *py_class = NULL, *py_member_dict = NULL;
419
420
if (base_class == NULL) {
421
py_base_classes = PyTuple_New(0);
422
} else {
423
py_base_classes = Py_BuildValue("(O)", base_class);
424
}
425
426
if (py_base_classes == NULL)
427
goto cleanup;
428
429
py_member_dict = PyDict_New();
430
if (py_member_dict == NULL)
431
goto cleanup;
432
433
for (PyMethodDef *py_def = class_methods; py_def->ml_name != NULL; ++py_def) {
434
PyObject *py_func = PyCFunction_New(py_def, NULL);
435
if (py_func == NULL) {
436
goto cleanup;
437
}
438
439
// this wrapping makes the function get the 'self' as argument
440
PyObject *py_method = PyInstanceMethod_New(py_func);
441
if (py_method == NULL) {
442
Py_DECREF(py_func);
443
goto cleanup;
444
}
445
446
int rc = PyDict_SetItemString(py_member_dict, py_def->ml_name, py_method);
447
448
Py_XDECREF(py_func);
449
Py_XDECREF(py_method);
450
451
if (rc != 0)
452
goto cleanup;
453
}
454
455
py_class = PyObject_CallFunction((PyObject *)&PyType_Type, "(sOO)",
456
class_name,
457
py_base_classes,
458
py_member_dict);
459
460
cleanup:
461
Py_CLEAR(py_base_classes);
462
Py_CLEAR(py_member_dict);
463
464
debug_return_ptr(py_class);
465
}
466
467
CPYCHECKER_STEALS_REFERENCE_TO_ARG(3)
468
static void
469
sudo_module_register_enum(PyObject *py_module, const char *enum_name, PyObject *py_constants_dict)
470
{
471
// pseudo code:
472
// return enum.IntEnum('MyEnum', {'DEFINITION_NAME': DEFINITION_VALUE, ...})
473
474
debug_decl(sudo_module_register_enum, PYTHON_DEBUG_INTERNAL);
475
476
if (py_constants_dict == NULL)
477
return;
478
479
PyObject *py_enum_class = NULL;
480
PyObject *py_enum_module = PyImport_ImportModule("enum");
481
if (py_enum_module == NULL) {
482
Py_CLEAR(py_constants_dict);
483
debug_return;
484
}
485
486
py_enum_class = PyObject_CallMethod(py_enum_module,
487
"IntEnum", "sO", enum_name,
488
py_constants_dict);
489
490
Py_CLEAR(py_constants_dict);
491
Py_CLEAR(py_enum_module);
492
493
if (py_enum_class == NULL) {
494
debug_return;
495
}
496
497
// PyModule_AddObject steals the reference to py_enum_class on success
498
if (PyModule_AddObject(py_module, enum_name, py_enum_class) < 0) {
499
Py_CLEAR(py_enum_class);
500
}
501
502
debug_return;
503
}
504
505
PyMODINIT_FUNC
506
sudo_module_init(void)
507
{
508
debug_decl(sudo_module_init, PYTHON_DEBUG_C_CALLS);
509
510
PyObject *py_module = PyModule_Create(&sudo_module);
511
512
if (py_module == NULL)
513
debug_return_ptr(NULL);
514
515
// Note: "PyModule_AddObject()" decrements the refcount only on success
516
517
// exceptions
518
#define MODULE_ADD_EXCEPTION(exception_name, base_exception) \
519
do { \
520
EXC_VAR(exception_name) = PyErr_NewException("sudo." # exception_name, base_exception, NULL); \
521
if (EXC_VAR(exception_name) == NULL || PyModule_AddObject(py_module, # exception_name, EXC_VAR(exception_name)) < 0) { \
522
Py_CLEAR(EXC_VAR(exception_name)); \
523
goto cleanup; \
524
} \
525
Py_INCREF(EXC_VAR(exception_name)); \
526
} while(0);
527
528
MODULE_ADD_EXCEPTION(SudoException, NULL);
529
530
MODULE_ADD_EXCEPTION(PluginException, NULL);
531
MODULE_ADD_EXCEPTION(PluginError, EXC_VAR(PluginException));
532
MODULE_ADD_EXCEPTION(PluginReject, EXC_VAR(PluginException));
533
534
MODULE_ADD_EXCEPTION(ConversationInterrupted, EXC_VAR(SudoException));
535
536
#define MODULE_REGISTER_ENUM(name, key_values) \
537
sudo_module_register_enum(py_module, name, py_dict_create_string_int(\
538
sizeof(key_values) / sizeof(struct key_value_str_int), key_values))
539
540
// constants
541
struct key_value_str_int constants_rc[] = {
542
{"OK", SUDO_RC_OK},
543
{"ACCEPT", SUDO_RC_ACCEPT},
544
{"REJECT", SUDO_RC_REJECT},
545
{"ERROR", SUDO_RC_ERROR},
546
{"USAGE_ERROR", SUDO_RC_USAGE_ERROR}
547
};
548
MODULE_REGISTER_ENUM("RC", constants_rc);
549
550
struct key_value_str_int constants_conv[] = {
551
{"PROMPT_ECHO_OFF", SUDO_CONV_PROMPT_ECHO_OFF},
552
{"PROMPT_ECHO_ON", SUDO_CONV_PROMPT_ECHO_ON},
553
{"INFO_MSG", SUDO_CONV_INFO_MSG},
554
{"PROMPT_MASK", SUDO_CONV_PROMPT_MASK},
555
{"PROMPT_ECHO_OK", SUDO_CONV_PROMPT_ECHO_OK},
556
{"PREFER_TTY", SUDO_CONV_PREFER_TTY}
557
};
558
MODULE_REGISTER_ENUM("CONV", constants_conv);
559
560
struct key_value_str_int constants_debug[] = {
561
{"CRIT", SUDO_DEBUG_CRIT},
562
{"ERROR", SUDO_DEBUG_ERROR},
563
{"WARN", SUDO_DEBUG_WARN},
564
{"NOTICE", SUDO_DEBUG_NOTICE},
565
{"DIAG", SUDO_DEBUG_DIAG},
566
{"INFO", SUDO_DEBUG_INFO},
567
{"TRACE", SUDO_DEBUG_TRACE},
568
{"DEBUG", SUDO_DEBUG_DEBUG}
569
};
570
MODULE_REGISTER_ENUM("DEBUG", constants_debug);
571
572
struct key_value_str_int constants_exit_reason[] = {
573
{"NO_STATUS", SUDO_PLUGIN_NO_STATUS},
574
{"WAIT_STATUS", SUDO_PLUGIN_WAIT_STATUS},
575
{"EXEC_ERROR", SUDO_PLUGIN_EXEC_ERROR},
576
{"SUDO_ERROR", SUDO_PLUGIN_SUDO_ERROR}
577
};
578
MODULE_REGISTER_ENUM("EXIT_REASON", constants_exit_reason);
579
580
struct key_value_str_int constants_plugin_types[] = {
581
{"POLICY", SUDO_POLICY_PLUGIN},
582
{"AUDIT", SUDO_AUDIT_PLUGIN},
583
{"IO", SUDO_IO_PLUGIN},
584
{"APPROVAL", SUDO_APPROVAL_PLUGIN},
585
{"SUDO", SUDO_FRONT_END}
586
};
587
MODULE_REGISTER_ENUM("PLUGIN_TYPE", constants_plugin_types);
588
589
// classes
590
if (sudo_module_register_conv_message(py_module) != SUDO_RC_OK)
591
goto cleanup;
592
593
if (sudo_module_register_baseplugin(py_module) != SUDO_RC_OK)
594
goto cleanup;
595
596
cleanup:
597
if (PyErr_Occurred()) {
598
Py_CLEAR(py_module);
599
Py_CLEAR(sudo_exc_SudoException);
600
Py_CLEAR(sudo_exc_PluginError);
601
Py_CLEAR(sudo_exc_PluginReject);
602
Py_CLEAR(sudo_exc_ConversationInterrupted);
603
}
604
605
debug_return_ptr(py_module);
606
}
607
608
static void
609
sudo_module_free(void *self)
610
{
611
debug_decl(sudo_module_free, PYTHON_DEBUG_C_CALLS);
612
613
// Free exceptions
614
Py_CLEAR(sudo_exc_SudoException);
615
Py_CLEAR(sudo_exc_PluginError);
616
Py_CLEAR(sudo_exc_PluginReject);
617
Py_CLEAR(sudo_exc_ConversationInterrupted);
618
619
// Free base plugin
620
Py_CLEAR(sudo_type_Plugin);
621
622
// Free conversation message type
623
Py_CLEAR(sudo_type_ConvMessage);
624
625
debug_return;
626
}
627
628