Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/python/pyhelpers.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 "pyhelpers.h"
20
21
#include <pwd.h>
22
#include <signal.h>
23
#include <pathnames.h>
24
25
static int
26
_sudo_printf_default(int msg_type, const char * restrict fmt, ...)
27
{
28
FILE *fp = stdout;
29
FILE *ttyfp = NULL;
30
va_list ap;
31
int len;
32
33
if (ISSET(msg_type, SUDO_CONV_PREFER_TTY)) {
34
/* Try writing to /dev/tty first. */
35
ttyfp = fopen(_PATH_TTY, "w");
36
}
37
38
switch (msg_type & 0xff) {
39
case SUDO_CONV_ERROR_MSG:
40
fp = stderr;
41
FALLTHROUGH;
42
case SUDO_CONV_INFO_MSG:
43
va_start(ap, fmt);
44
len = vfprintf(ttyfp ? ttyfp : fp, fmt, ap);
45
va_end(ap);
46
break;
47
default:
48
len = -1;
49
errno = EINVAL;
50
break;
51
}
52
53
if (ttyfp != NULL)
54
fclose(ttyfp);
55
56
return len;
57
}
58
59
60
struct PythonContext py_ctx = {
61
.sudo_log = &_sudo_printf_default,
62
};
63
64
65
char *
66
py_join_str_list(PyObject *py_str_list, const char *separator)
67
{
68
debug_decl(py_join_str_list, PYTHON_DEBUG_INTERNAL);
69
70
char *result = NULL;
71
PyObject *py_separator = NULL;
72
PyObject *py_str = NULL;
73
74
py_separator = PyUnicode_FromString(separator);
75
if (py_separator == NULL)
76
goto cleanup;
77
78
py_str = PyObject_CallMethod(py_separator, "join", "(O)", py_str_list);
79
if (py_str == NULL) {
80
goto cleanup;
81
}
82
83
const char *str = PyUnicode_AsUTF8(py_str);
84
if (str != NULL) {
85
result = strdup(str);
86
}
87
88
cleanup:
89
Py_XDECREF(py_str);
90
Py_XDECREF(py_separator);
91
92
debug_return_str(result);
93
}
94
95
static char *
96
py_create_traceback_string(PyObject *py_traceback)
97
{
98
debug_decl(py_create_traceback_string, PYTHON_DEBUG_INTERNAL);
99
if (py_traceback == NULL)
100
debug_return_str(strdup(""));
101
102
char* traceback = NULL;
103
104
105
PyObject *py_traceback_module = PyImport_ImportModule("traceback");
106
if (py_traceback_module == NULL) {
107
PyErr_Clear(); // do not care, we just won't show backtrace
108
} else {
109
PyObject *py_traceback_str_list = PyObject_CallMethod(py_traceback_module, "format_tb", "(O)", py_traceback);
110
111
if (py_traceback_str_list != NULL) {
112
traceback = py_join_str_list(py_traceback_str_list, "");
113
Py_DECREF(py_traceback_str_list);
114
}
115
116
Py_CLEAR(py_traceback_module);
117
}
118
119
debug_return_str(traceback ? traceback : strdup(""));
120
}
121
122
void
123
py_log_last_error(const char *context_message)
124
{
125
debug_decl(py_log_last_error, PYTHON_DEBUG_INTERNAL);
126
if (!PyErr_Occurred()) {
127
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s\n", context_message);
128
debug_return;
129
}
130
131
PyObject *py_type = NULL, *py_message = NULL, *py_traceback = NULL;
132
PyErr_Fetch(&py_type, &py_message, &py_traceback);
133
134
char *message = py_message ? py_create_string_rep(py_message) : NULL;
135
136
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s%s%s\n",
137
context_message ? context_message : "",
138
context_message && *context_message ? ": " : "",
139
message ? message : "(NULL)");
140
free(message);
141
142
if (py_traceback != NULL) {
143
char *traceback = py_create_traceback_string(py_traceback);
144
py_sudo_log(SUDO_CONV_INFO_MSG, "Traceback:\n%s\n", traceback);
145
free(traceback);
146
}
147
148
Py_XDECREF(py_type);
149
Py_XDECREF(py_message);
150
Py_XDECREF(py_traceback);
151
debug_return;
152
}
153
154
PyObject *
155
py_str_array_to_tuple_with_count(Py_ssize_t count, char * const strings[])
156
{
157
debug_decl(py_str_array_to_tuple_with_count, PYTHON_DEBUG_INTERNAL);
158
159
PyObject *py_argv = PyTuple_New(count);
160
if (py_argv == NULL)
161
debug_return_ptr(NULL);
162
163
for (int i = 0; i < count; ++i) {
164
PyObject *py_arg = PyUnicode_FromString(strings[i]);
165
if (py_arg == NULL || PyTuple_SetItem(py_argv, i, py_arg) != 0) {
166
Py_CLEAR(py_argv);
167
break;
168
}
169
}
170
171
debug_return_ptr(py_argv);
172
}
173
174
PyObject *
175
py_str_array_to_tuple(char * const strings[])
176
{
177
debug_decl(py_str_array_to_tuple, PYTHON_DEBUG_INTERNAL);
178
179
// find the item count ("strings" ends with NULL terminator):
180
Py_ssize_t count = 0;
181
if (strings != NULL) {
182
while (strings[count] != NULL)
183
++count;
184
}
185
186
debug_return_ptr(py_str_array_to_tuple_with_count(count, strings));
187
}
188
189
char **
190
py_str_array_from_tuple(PyObject *py_tuple)
191
{
192
debug_decl(py_str_array_from_tuple, PYTHON_DEBUG_INTERNAL);
193
194
if (!PyTuple_Check(py_tuple)) {
195
PyErr_Format(PyExc_ValueError, "%s: value error, argument should be a tuple but it is '%s'",
196
__func__, Py_TYPENAME(py_tuple));
197
debug_return_ptr(NULL);
198
}
199
200
Py_ssize_t tuple_size = PyTuple_Size(py_tuple);
201
202
// we need an extra 0 at the end
203
char **result = calloc((size_t)tuple_size + 1, sizeof(char *));
204
if (result == NULL) {
205
debug_return_ptr(NULL);
206
}
207
208
for (int i = 0; i < tuple_size; ++i) {
209
PyObject *py_value = PyTuple_GetItem(py_tuple, i);
210
if (py_value == NULL) {
211
str_array_free(&result);
212
debug_return_ptr(NULL);
213
}
214
215
// Note that it can be an "int" or something else as well
216
char *value = py_create_string_rep(py_value);
217
if (value == NULL) {
218
// conversion error is already set
219
str_array_free(&result);
220
debug_return_ptr(NULL);
221
}
222
result[i] = value;
223
}
224
225
debug_return_ptr(result);
226
}
227
228
PyObject *
229
py_tuple_get(PyObject *py_tuple, Py_ssize_t idx, PyTypeObject *expected_type)
230
{
231
debug_decl(py_tuple_get, PYTHON_DEBUG_INTERNAL);
232
233
PyObject *py_item = PyTuple_GetItem(py_tuple, idx);
234
if (py_item == NULL) {
235
debug_return_ptr(NULL);
236
}
237
238
if (!PyObject_TypeCheck(py_item, expected_type)) {
239
PyErr_Format(PyExc_ValueError, "Value error: tuple element %d should "
240
"be a '%s' (but it is '%s')",
241
idx, expected_type->tp_name, Py_TYPENAME(py_item));
242
debug_return_ptr(NULL);
243
}
244
245
debug_return_ptr(py_item);
246
}
247
248
PyObject *
249
py_create_version(unsigned int version)
250
{
251
debug_decl(py_create_version, PYTHON_DEBUG_INTERNAL);
252
debug_return_ptr(PyUnicode_FromFormat("%d.%d", SUDO_API_VERSION_GET_MAJOR(version),
253
SUDO_API_VERSION_GET_MINOR(version)));
254
}
255
256
PyObject *
257
py_from_passwd(const struct passwd *pwd)
258
{
259
debug_decl(py_from_passwd, PYTHON_DEBUG_INTERNAL);
260
261
if (pwd == NULL) {
262
debug_return_ptr_pynone;
263
}
264
265
// Create a tuple similar and convertible to python "struct_passwd" of "pwd" module
266
debug_return_ptr(
267
Py_BuildValue("(zziizzz)", pwd->pw_name, pwd->pw_passwd,
268
pwd->pw_uid, pwd->pw_gid, pwd->pw_gecos,
269
pwd->pw_dir, pwd->pw_shell)
270
);
271
}
272
273
char *
274
py_create_string_rep(PyObject *py_object)
275
{
276
debug_decl(py_create_string_rep, PYTHON_DEBUG_INTERNAL);
277
char *result = NULL;
278
279
if (py_object == NULL)
280
debug_return_ptr(NULL);
281
282
PyObject *py_string = PyObject_Str(py_object);
283
if (py_string != NULL) {
284
const char *bytes = PyUnicode_AsUTF8(py_string);
285
if (bytes != NULL) {
286
/*
287
* Convert from old format w/ numeric value to new without it.
288
* Old: (<DEBUG.ERROR: 2>, 'ERROR level debug message')
289
* New: (DEBUG.ERROR, 'ERROR level debug message')
290
*/
291
if (bytes[0] == '(' && bytes[1] == '<') {
292
const char *colon = strchr(bytes + 2, ':');
293
if (colon != NULL && colon[1] == ' ') {
294
const char *cp = colon + 2;
295
while (isdigit((unsigned char)*cp))
296
cp++;
297
if (cp[0] == '>' && (cp[1] == ',' || cp[1] == '\0')) {
298
bytes += 2;
299
if (asprintf(&result, "(%.*s%s", (int)(colon - bytes),
300
bytes, cp + 1) == -1) {
301
result = NULL;
302
goto done;
303
}
304
}
305
}
306
}
307
if (result == NULL)
308
result = strdup(bytes);
309
}
310
}
311
312
done:
313
Py_XDECREF(py_string);
314
debug_return_ptr(result);
315
}
316
317
static void
318
_py_debug_python_function(const char *class_name, const char *function_name, const char *message,
319
PyObject *py_args, PyObject *py_kwargs, unsigned int subsystem_id)
320
{
321
debug_decl_vars(_py_debug_python_function, subsystem_id);
322
323
if (sudo_debug_needed(SUDO_DEBUG_DIAG)) {
324
char *args_str = NULL;
325
char *kwargs_str = NULL;
326
if (py_args != NULL) {
327
/* Sort by key for consistent output on Python < 3.6 */
328
PyObject *py_args_sorted = NULL;
329
if (PyDict_Check(py_args)) {
330
py_args_sorted = PyDict_Items(py_args);
331
if (py_args_sorted != NULL) {
332
if (PyList_Sort(py_args_sorted) == 0) {
333
py_args = py_args_sorted;
334
}
335
}
336
}
337
args_str = py_create_string_rep(py_args);
338
if (args_str != NULL && strncmp(args_str, "RC.", 3) == 0) {
339
/* Strip leading RC. to match python 3.10 behavior. */
340
memmove(args_str, args_str + 3, strlen(args_str + 3) + 1);
341
}
342
Py_XDECREF(py_args_sorted);
343
}
344
if (py_kwargs != NULL) {
345
/* Sort by key for consistent output on Python < 3.6 */
346
PyObject *py_kwargs_sorted = NULL;
347
if (PyDict_Check(py_kwargs)) {
348
py_kwargs_sorted = PyDict_Items(py_kwargs);
349
if (py_kwargs_sorted != NULL) {
350
if (PyList_Sort(py_kwargs_sorted) == 0) {
351
py_kwargs = py_kwargs_sorted;
352
}
353
}
354
}
355
kwargs_str = py_create_string_rep(py_kwargs);
356
Py_XDECREF(py_kwargs_sorted);
357
}
358
359
sudo_debug_printf(SUDO_DEBUG_DIAG, "%s.%s %s: %s%s%s\n", class_name,
360
function_name, message, args_str ? args_str : "()",
361
kwargs_str ? " " : "", kwargs_str ? kwargs_str : "");
362
free(args_str);
363
free(kwargs_str);
364
}
365
}
366
367
void
368
py_debug_python_call(const char *class_name, const char *function_name,
369
PyObject *py_args, PyObject *py_kwargs,
370
unsigned int subsystem_id)
371
{
372
debug_decl_vars(py_debug_python_call, subsystem_id);
373
374
if (subsystem_id == PYTHON_DEBUG_C_CALLS && sudo_debug_needed(SUDO_DEBUG_INFO)) {
375
// at this level we also output the callee python script
376
char *callee_func_name = NULL, *callee_file_name = NULL;
377
long callee_line_number = -1;
378
379
if (py_get_current_execution_frame(&callee_file_name, &callee_line_number, &callee_func_name) == SUDO_RC_OK) {
380
sudo_debug_printf(SUDO_DEBUG_INFO, "%s @ %s:%ld calls C function:\n",
381
callee_func_name, callee_file_name, callee_line_number);
382
}
383
384
free(callee_func_name);
385
free(callee_file_name);
386
}
387
388
_py_debug_python_function(class_name, function_name, "was called with arguments",
389
py_args, py_kwargs, subsystem_id);
390
}
391
392
void
393
py_debug_python_result(const char *class_name, const char *function_name,
394
PyObject *py_result, unsigned int subsystem_id)
395
{
396
if (py_result == NULL) {
397
debug_decl_vars(py_debug_python_result, subsystem_id);
398
sudo_debug_printf(SUDO_CONV_ERROR_MSG, "%s.%s call failed\n",
399
class_name, function_name);
400
} else {
401
_py_debug_python_function(class_name, function_name, "returned result",
402
py_result, NULL, subsystem_id);
403
}
404
}
405
406
void
407
str_array_free(char ***array)
408
{
409
debug_decl(str_array_free, PYTHON_DEBUG_INTERNAL);
410
411
if (*array == NULL)
412
debug_return;
413
414
for (char **item_ptr = *array; *item_ptr != NULL; ++item_ptr)
415
free(*item_ptr);
416
417
free(*array);
418
*array = NULL;
419
420
debug_return;
421
}
422
423
int
424
py_get_current_execution_frame(char **file_name, long *line_number, char **function_name)
425
{
426
*file_name = NULL;
427
*line_number = (long)-1;
428
*function_name = NULL;
429
430
PyObject *py_err_type = NULL, *py_err_value = NULL, *py_err_traceback = NULL;
431
PyErr_Fetch(&py_err_type, &py_err_value, &py_err_traceback);
432
433
PyObject *py_frame = NULL, *py_f_code = NULL,
434
*py_filename = NULL, *py_function_name = NULL;
435
436
PyObject *py_getframe = PySys_GetObject("_getframe");
437
if (py_getframe == NULL)
438
goto cleanup;
439
440
py_frame = PyObject_CallFunction(py_getframe, "i", 0);
441
if (py_frame == NULL)
442
goto cleanup;
443
444
*line_number = py_object_get_optional_attr_number(py_frame, "f_lineno");
445
446
py_f_code = py_object_get_optional_attr(py_frame, "f_code", NULL);
447
if (py_f_code != NULL) {
448
py_filename = py_object_get_optional_attr(py_f_code, "co_filename", NULL);
449
if (py_filename != NULL)
450
*file_name = strdup(PyUnicode_AsUTF8(py_filename));
451
452
py_function_name = py_object_get_optional_attr(py_f_code, "co_name", NULL);
453
if (py_function_name != NULL)
454
*function_name = strdup(PyUnicode_AsUTF8(py_function_name));
455
}
456
457
cleanup:
458
Py_CLEAR(py_frame);
459
Py_CLEAR(py_f_code);
460
Py_CLEAR(py_filename);
461
Py_CLEAR(py_function_name);
462
463
// we hide every error happening inside this function
464
PyErr_Restore(py_err_type, py_err_value, py_err_traceback);
465
466
return (*file_name && *function_name && (*line_number >= 0)) ?
467
SUDO_RC_OK : SUDO_RC_ERROR;
468
}
469
470
void
471
py_ctx_reset()
472
{
473
memset(&py_ctx, 0, sizeof(py_ctx));
474
py_ctx.sudo_log = &_sudo_printf_default;
475
}
476
477
int
478
py_sudo_conv(int num_msgs, const struct sudo_conv_message msgs[],
479
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
480
{
481
/* Enable suspend during password entry. */
482
struct sigaction sa, saved_sigtstp;
483
sigemptyset(&sa.sa_mask);
484
sa.sa_flags = SA_RESTART;
485
sa.sa_handler = SIG_DFL;
486
(void) sigaction(SIGTSTP, &sa, &saved_sigtstp);
487
488
int rc = SUDO_RC_ERROR;
489
if (py_ctx.sudo_conv != NULL)
490
rc = py_ctx.sudo_conv((int)num_msgs, msgs, replies, callback);
491
492
/* Restore signal handlers and signal mask. */
493
(void) sigaction(SIGTSTP, &saved_sigtstp, NULL);
494
495
return rc;
496
}
497
498
PyObject *
499
py_object_get_optional_attr(PyObject *py_object, const char *attr, PyObject *py_default)
500
{
501
if (PyObject_HasAttrString(py_object, attr)) {
502
return PyObject_GetAttrString(py_object, attr);
503
}
504
Py_XINCREF(py_default); // whatever we return will have its refcount incremented
505
return py_default;
506
}
507
508
const char *
509
py_object_get_optional_attr_string(PyObject *py_object, const char *attr_name)
510
{
511
PyObject *py_value = py_object_get_optional_attr(py_object, attr_name, NULL);
512
if (py_value == NULL)
513
return NULL;
514
515
const char *value = PyUnicode_AsUTF8(py_value);
516
Py_CLEAR(py_value); // Note, the object still has reference to the attribute
517
return value;
518
}
519
520
long
521
py_object_get_optional_attr_number(PyObject *py_object, const char *attr_name)
522
{
523
PyObject *py_value = py_object_get_optional_attr(py_object, attr_name, NULL);
524
if (py_value == NULL)
525
return -1;
526
527
long value = PyLong_AsLong(py_value);
528
Py_CLEAR(py_value);
529
return value;
530
}
531
532
void
533
py_object_set_attr_number(PyObject *py_object, const char *attr_name, long number)
534
{
535
PyObject *py_number = PyLong_FromLong(number);
536
if (py_number == NULL)
537
return;
538
539
PyObject_SetAttrString(py_object, attr_name, py_number);
540
Py_CLEAR(py_number);
541
}
542
543
void
544
py_object_set_attr_string(PyObject *py_object, const char *attr_name, const char *value)
545
{
546
PyObject *py_value = PyUnicode_FromString(value);
547
if (py_value == NULL)
548
return;
549
550
PyObject_SetAttrString(py_object, attr_name, py_value);
551
Py_CLEAR(py_value);
552
}
553
554
PyObject *
555
py_dict_create_string_int(size_t count, struct key_value_str_int *key_values)
556
{
557
debug_decl(py_dict_create_string_int, PYTHON_DEBUG_INTERNAL);
558
559
PyObject *py_value = NULL;
560
PyObject *py_dict = PyDict_New();
561
if (py_dict == NULL)
562
goto cleanup;
563
564
for (size_t i = 0; i < count; ++i) {
565
py_value = PyLong_FromLong(key_values[i].value);
566
if (py_value == NULL)
567
goto cleanup;
568
569
if (PyDict_SetItemString(py_dict, key_values[i].key, py_value) < 0)
570
goto cleanup;
571
572
Py_CLEAR(py_value);
573
}
574
575
cleanup:
576
if (PyErr_Occurred()) {
577
Py_CLEAR(py_dict);
578
}
579
Py_CLEAR(py_value);
580
581
debug_return_ptr(py_dict);
582
}
583
584