1 /*
2 * SPDX-License-Identifier: ISC
3 *
4 * Copyright (c) 2020 Robert Manner <robert.manner@oneidentity.com>
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 /*
20 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22 */
23
24 #include "testhelpers.h"
25
26 #include "sudo_dso.h"
27
28 #define DECL_PLUGIN(type, variable_name) \
29 static struct type *variable_name = NULL; \
30 static struct type variable_name ## _original
31
32 #define RESTORE_PYTHON_PLUGIN(variable_name) \
33 memcpy(variable_name, &(variable_name ## _original), sizeof(variable_name ## _original))
34
35 #define SAVE_PYTHON_PLUGIN(variable_name) \
36 memcpy(&(variable_name ## _original), variable_name, sizeof(variable_name ## _original))
37
38 static const char *python_plugin_so_path = NULL;
39 static void *python_plugin_handle = NULL;
40 DECL_PLUGIN(io_plugin, python_io);
41 DECL_PLUGIN(policy_plugin, python_policy);
42 DECL_PLUGIN(approval_plugin, python_approval);
43 DECL_PLUGIN(audit_plugin, python_audit);
44 DECL_PLUGIN(sudoers_group_plugin, group_plugin);
45
46 static struct passwd example_pwd;
47
48 static int _init_symbols(void);
49 static int _unlink_symbols(void);
50
51 void
create_plugin_options(const char * module_name,const char * class_name,const char * extra_option)52 create_plugin_options(const char *module_name, const char *class_name, const char *extra_option)
53 {
54 char opt_module_path[PATH_MAX + 256];
55 char opt_classname[PATH_MAX + 256];
56 snprintf(opt_module_path, sizeof(opt_module_path),
57 "ModulePath=" SRC_DIR "/%s.py", module_name);
58
59 snprintf(opt_classname, sizeof(opt_classname), "ClassName=%s", class_name);
60
61 str_array_free(&data.plugin_options);
62 size_t count = 3 + (extra_option != NULL);
63 data.plugin_options = create_str_array(count, opt_module_path,
64 opt_classname, extra_option, NULL);
65 }
66
67 void
create_io_plugin_options(const char * log_path)68 create_io_plugin_options(const char *log_path)
69 {
70 char opt_logpath[PATH_MAX + 16];
71 snprintf(opt_logpath, sizeof(opt_logpath), "LogPath=%s", log_path);
72 create_plugin_options("example_io_plugin", "SudoIOPlugin", opt_logpath);
73 }
74
75 void
create_debugging_plugin_options(void)76 create_debugging_plugin_options(void)
77 {
78 create_plugin_options("example_debugging", "DebugDemoPlugin", NULL);
79 }
80
81 void
create_audit_plugin_options(const char * extra_argument)82 create_audit_plugin_options(const char *extra_argument)
83 {
84 create_plugin_options("example_audit_plugin", "SudoAuditPlugin", extra_argument);
85 }
86
87 void
create_conversation_plugin_options(void)88 create_conversation_plugin_options(void)
89 {
90 char opt_logpath[PATH_MAX + 16];
91 snprintf(opt_logpath, sizeof(opt_logpath), "LogPath=%s", data.tmp_dir);
92 create_plugin_options("example_conversation", "ReasonLoggerIOPlugin", opt_logpath);
93 }
94
95 void
create_policy_plugin_options(void)96 create_policy_plugin_options(void)
97 {
98 create_plugin_options("example_policy_plugin", "SudoPolicyPlugin", NULL);
99 }
100
101 int
init(void)102 init(void)
103 {
104 // always start each test from clean state
105 memset(&data, 0, sizeof(data));
106
107 memset(&example_pwd, 0, sizeof(example_pwd));
108 example_pwd.pw_name = "pw_name";
109 example_pwd.pw_passwd = "pw_passwd";
110 example_pwd.pw_gecos = "pw_gecos";
111 example_pwd.pw_shell ="pw_shell";
112 example_pwd.pw_dir = "pw_dir";
113 example_pwd.pw_uid = (uid_t)1001;
114 example_pwd.pw_gid = (gid_t)101;
115
116 VERIFY_TRUE(asprintf(&data.tmp_dir, TEMP_PATH_TEMPLATE) >= 0);
117 VERIFY_NOT_NULL(mkdtemp(data.tmp_dir));
118
119 // by default we test in developer mode, so the python plugin can be loaded
120 sudo_conf_clear_paths();
121 VERIFY_INT(sudo_conf_read(sudo_conf_developer_mode, SUDO_CONF_ALL), true);
122 VERIFY_TRUE(sudo_conf_developer_mode());
123
124 // some default values for the plugin open:
125 data.settings = create_str_array(1, NULL);
126 data.user_info = create_str_array(1, NULL);
127 data.command_info = create_str_array(1, NULL);
128 data.plugin_argc = 0;
129 data.plugin_argv = create_str_array(1, NULL);
130 data.user_env = create_str_array(1, NULL);
131
132 VERIFY_TRUE(_init_symbols());
133 return true;
134 }
135
136 int
cleanup(int success)137 cleanup(int success)
138 {
139 if (!success) {
140 printf("\nThe output of the plugin:\n%s", data.stdout_str);
141 printf("\nThe error output of the plugin:\n%s", data.stderr_str);
142 }
143
144 VERIFY_TRUE(rmdir_recursive(data.tmp_dir));
145 if (data.tmp_dir2) {
146 VERIFY_TRUE(rmdir_recursive(data.tmp_dir2));
147 }
148
149 free(data.tmp_dir);
150 free(data.tmp_dir2);
151
152 str_array_free(&data.settings);
153 str_array_free(&data.user_info);
154 str_array_free(&data.command_info);
155 str_array_free(&data.plugin_argv);
156 str_array_free(&data.user_env);
157 str_array_free(&data.plugin_options);
158
159 return true;
160 }
161
162 int
check_example_io_plugin_version_display(int is_verbose)163 check_example_io_plugin_version_display(int is_verbose)
164 {
165 const char *errstr = NULL;
166 create_io_plugin_options(data.tmp_dir);
167
168 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
169 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv, data.user_env,
170 data.plugin_options, &errstr), SUDO_RC_OK);
171 VERIFY_INT(python_io->show_version(is_verbose), SUDO_RC_OK);
172
173 python_io->close(0, 0); // this should not call the python plugin close as there was no command run invocation
174
175 if (is_verbose) {
176 // Note: the exact python version is environment dependent
177 VERIFY_STR_CONTAINS(data.stdout_str, "Python interpreter version:");
178 *strstr(data.stdout_str, "Python interpreter version:") = '\0';
179 VERIFY_STDOUT(expected_path("check_example_io_plugin_version_display_full.stdout"));
180 } else {
181 VERIFY_STDOUT(expected_path("check_example_io_plugin_version_display.stdout"));
182 }
183
184 VERIFY_STDERR(expected_path("check_example_io_plugin_version_display.stderr"));
185 VERIFY_FILE("sudo.log", expected_path("check_example_io_plugin_version_display.stored"));
186
187 return true;
188 }
189
190 int
check_example_io_plugin_command_log(void)191 check_example_io_plugin_command_log(void)
192 {
193 const char *errstr = NULL;
194 create_io_plugin_options(data.tmp_dir);
195
196 str_array_free(&data.plugin_argv);
197 data.plugin_argc = 2;
198 data.plugin_argv = create_str_array(3, "id", "--help", NULL);
199
200 str_array_free(&data.command_info);
201 data.command_info = create_str_array(3, "command=/bin/id", "runas_uid=0", NULL);
202
203 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
204 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
205 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
206 VERIFY_PTR(errstr, NULL);
207 VERIFY_INT(python_io->log_stdin("some standard input", strlen("some standard input"), &errstr), SUDO_RC_OK);
208 VERIFY_PTR(errstr, NULL);
209 VERIFY_INT(python_io->log_stdout("some standard output", strlen("some standard output"), &errstr), SUDO_RC_OK);
210 VERIFY_PTR(errstr, NULL);
211 VERIFY_INT(python_io->log_stderr("some standard error", strlen("some standard error"), &errstr), SUDO_RC_OK);
212 VERIFY_PTR(errstr, NULL);
213 VERIFY_INT(python_io->log_suspend(SIGTSTP, &errstr), SUDO_RC_OK);
214 VERIFY_PTR(errstr, NULL);
215 VERIFY_INT(python_io->log_suspend(SIGCONT, &errstr), SUDO_RC_OK);
216 VERIFY_PTR(errstr, NULL);
217 VERIFY_INT(python_io->change_winsize(200, 100, &errstr), SUDO_RC_OK);
218 VERIFY_PTR(errstr, NULL);
219 VERIFY_INT(python_io->log_ttyin("some tty input", strlen("some tty input"), &errstr), SUDO_RC_OK);
220 VERIFY_PTR(errstr, NULL);
221 VERIFY_INT(python_io->log_ttyout("some tty output", strlen("some tty output"), &errstr), SUDO_RC_OK);
222 VERIFY_PTR(errstr, NULL);
223
224 python_io->close(1, 0); // successful execution, command returned 1
225
226 VERIFY_STDOUT(expected_path("check_example_io_plugin_command_log.stdout"));
227 VERIFY_STDERR(expected_path("check_example_io_plugin_command_log.stderr"));
228 VERIFY_FILE("sudo.log", expected_path("check_example_io_plugin_command_log.stored"));
229
230 return true;
231 }
232
233 typedef struct io_plugin * (io_clone_func)(void);
234
235 int
236 check_example_io_plugin_command_log_multiple(void)
237 {
238 const char *errstr = NULL;
239
240 // verify multiple python io plugin symbols are available
241 io_clone_func *python_io_clone = (io_clone_func *)sudo_dso_findsym(python_plugin_handle, "python_io_clone");
242 VERIFY_PTR_NE(python_io_clone, NULL);
243
244 struct io_plugin *python_io2 = NULL;
245
246 for (int i = 0; i < 7; ++i) {
247 python_io2 = (*python_io_clone)();
248 VERIFY_PTR_NE(python_io2, NULL);
249 VERIFY_PTR_NE(python_io2, python_io);
250 }
251
252 // open the first plugin and let it log to tmp_dir
253 create_io_plugin_options(data.tmp_dir);
254
255 str_array_free(&data.plugin_argv);
256 data.plugin_argc = 2;
257 data.plugin_argv = create_str_array(3, "id", "--help", NULL);
258
259 str_array_free(&data.command_info);
260 data.command_info = create_str_array(3, "command=/bin/id", "runas_uid=0", NULL);
261
262 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
263 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
264 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
265 VERIFY_PTR(errstr, NULL);
266
267 // For verifying the error message of no more plugin. It should be displayed only once.
268 VERIFY_PTR((*python_io_clone)(), NULL);
269 VERIFY_PTR((*python_io_clone)(), NULL);
270
271 // open the second plugin with another log directory
272 VERIFY_TRUE(asprintf(&data.tmp_dir2, TEMP_PATH_TEMPLATE) >= 0);
273 VERIFY_NOT_NULL(mkdtemp(data.tmp_dir2));
274 create_io_plugin_options(data.tmp_dir2);
275
276 str_array_free(&data.plugin_argv);
277 data.plugin_argc = 1;
278 data.plugin_argv = create_str_array(2, "whoami", NULL);
279
280 str_array_free(&data.command_info);
281 data.command_info = create_str_array(3, "command=/bin/whoami", "runas_uid=1", NULL);
282
283 VERIFY_INT(python_io2->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
284 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
285 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
286 VERIFY_PTR(errstr, NULL);
287
288 VERIFY_INT(python_io->log_stdin("stdin for plugin 1", strlen("stdin for plugin 1"), &errstr), SUDO_RC_OK);
289 VERIFY_PTR(errstr, NULL);
290 VERIFY_INT(python_io2->log_stdin("stdin for plugin 2", strlen("stdin for plugin 2"), &errstr), SUDO_RC_OK);
291 VERIFY_PTR(errstr, NULL);
292 VERIFY_INT(python_io->log_stdout("stdout for plugin 1", strlen("stdout for plugin 1"), &errstr), SUDO_RC_OK);
293 VERIFY_PTR(errstr, NULL);
294 VERIFY_INT(python_io2->log_stdout("stdout for plugin 2", strlen("stdout for plugin 2"), &errstr), SUDO_RC_OK);
295 VERIFY_PTR(errstr, NULL);
296 VERIFY_INT(python_io->log_stderr("stderr for plugin 1", strlen("stderr for plugin 1"), &errstr), SUDO_RC_OK);
297 VERIFY_PTR(errstr, NULL);
298 VERIFY_INT(python_io2->log_stderr("stderr for plugin 2", strlen("stderr for plugin 2"), &errstr), SUDO_RC_OK);
299 VERIFY_PTR(errstr, NULL);
300 VERIFY_INT(python_io->log_suspend(SIGTSTP, &errstr), SUDO_RC_OK);
301 VERIFY_PTR(errstr, NULL);
302 VERIFY_INT(python_io2->log_suspend(SIGSTOP, &errstr), SUDO_RC_OK);
303 VERIFY_PTR(errstr, NULL);
304 VERIFY_INT(python_io->log_suspend(SIGCONT, &errstr), SUDO_RC_OK);
305 VERIFY_PTR(errstr, NULL);
306 VERIFY_INT(python_io2->log_suspend(SIGCONT, &errstr), SUDO_RC_OK);
307 VERIFY_PTR(errstr, NULL);
308 VERIFY_INT(python_io->change_winsize(20, 10, &errstr), SUDO_RC_OK);
309 VERIFY_PTR(errstr, NULL);
310 VERIFY_INT(python_io2->change_winsize(30, 40, &errstr), SUDO_RC_OK);
311 VERIFY_PTR(errstr, NULL);
312 VERIFY_INT(python_io->log_ttyin("tty input for plugin 1", strlen("tty input for plugin 1"), &errstr), SUDO_RC_OK);
313 VERIFY_PTR(errstr, NULL);
314 VERIFY_INT(python_io2->log_ttyin("tty input for plugin 2", strlen("tty input for plugin 2"), &errstr), SUDO_RC_OK);
315 VERIFY_PTR(errstr, NULL);
316 VERIFY_INT(python_io->log_ttyout("tty output for plugin 1", strlen("tty output for plugin 1"), &errstr), SUDO_RC_OK);
317 VERIFY_PTR(errstr, NULL);
318 VERIFY_INT(python_io2->log_ttyout("tty output for plugin 2", strlen("tty output for plugin 2"), &errstr), SUDO_RC_OK);
319 VERIFY_PTR(errstr, NULL);
320
321 python_io->close(1, 0); // successful execution, command returned 1
322 python_io2->close(2, 0); // command returned 2
323
324 VERIFY_STDOUT(expected_path("check_example_io_plugin_command_log_multiple.stdout"));
325 VERIFY_STDERR(expected_path("check_example_io_plugin_command_log_multiple.stderr"));
326 VERIFY_FILE("sudo.log", expected_path("check_example_io_plugin_command_log_multiple1.stored"));
327 VERIFY_TRUE(verify_file(data.tmp_dir2, "sudo.log", expected_path("check_example_io_plugin_command_log_multiple2.stored")));
328
329 return true;
330 }
331
332 int
333 check_example_io_plugin_failed_to_start_command(void)
334 {
335 const char *errstr = NULL;
336
337 create_io_plugin_options(data.tmp_dir);
338
339 str_array_free(&data.plugin_argv);
340 data.plugin_argc = 1;
341 data.plugin_argv = create_str_array(2, "cmd", NULL);
342
343 str_array_free(&data.command_info);
344 data.command_info = create_str_array(3, "command=/usr/share/cmd", "runas_uid=0", NULL);
345
346 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
347 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
348 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
349 VERIFY_PTR(errstr, NULL);
350
351 python_io->close(0, EPERM); // execve returned with error
352
353 VERIFY_STDOUT(expected_path("check_example_io_plugin_failed_to_start_command.stdout"));
354 VERIFY_STDERR(expected_path("check_example_io_plugin_failed_to_start_command.stderr"));
355 VERIFY_FILE("sudo.log", expected_path("check_example_io_plugin_failed_to_start_command.stored"));
356
357 return true;
358 }
359
360 int
361 check_example_io_plugin_fails_with_python_backtrace(void)
362 {
363 const char *errstr = NULL;
364
365 create_io_plugin_options("/some/not/writable/directory");
366
367 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
368 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
369 data.user_env, data.plugin_options, &errstr), SUDO_RC_ERROR);
370 VERIFY_PTR(errstr, NULL);
371
372 VERIFY_STDOUT(expected_path("check_example_io_plugin_fails_with_python_backtrace.stdout"));
373 VERIFY_STDERR(expected_path("check_example_io_plugin_fails_with_python_backtrace.stderr"));
374
375 python_io->close(0, 0);
376 return true;
377 }
378
379 int
380 check_io_plugin_reports_error(void)
381 {
382 const char *errstr = NULL;
383 str_array_free(&data.plugin_options);
384 data.plugin_options = create_str_array(
385 3,
386 "ModulePath=" SRC_DIR "/regress/plugin_errorstr.py",
387 "ClassName=ConstructErrorPlugin",
388 NULL
389 );
390
391 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
392 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
393 data.user_env, data.plugin_options, &errstr), SUDO_RC_ERROR);
394
395 VERIFY_STR(errstr, "Something wrong in plugin constructor");
396 errstr = NULL;
397
398 python_io->close(0, 0);
399
400 str_array_free(&data.plugin_options);
401 data.plugin_options = create_str_array(
402 3,
403 "ModulePath=" SRC_DIR "/regress/plugin_errorstr.py",
404 "ClassName=ErrorMsgPlugin",
405 NULL
406 );
407
408 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
409 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
410 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
411 VERIFY_PTR(errstr, NULL);
412
413 VERIFY_INT(python_io->log_stdin("", 0, &errstr), SUDO_RC_ERROR);
414 VERIFY_STR(errstr, "Something wrong in log_stdin");
415
416 errstr = (void *)13;
417 VERIFY_INT(python_io->log_stdout("", 0, &errstr), SUDO_RC_ERROR);
418 VERIFY_STR(errstr, "Something wrong in log_stdout");
419
420 errstr = NULL;
421 VERIFY_INT(python_io->log_stderr("", 0, &errstr), SUDO_RC_ERROR);
422 VERIFY_STR(errstr, "Something wrong in log_stderr");
423
424 errstr = NULL;
425 VERIFY_INT(python_io->log_ttyin("", 0, &errstr), SUDO_RC_ERROR);
426 VERIFY_STR(errstr, "Something wrong in log_ttyin");
427
428 errstr = NULL;
429 VERIFY_INT(python_io->log_ttyout("", 0, &errstr), SUDO_RC_ERROR);
430 VERIFY_STR(errstr, "Something wrong in log_ttyout");
431
432 errstr = NULL;
433 VERIFY_INT(python_io->log_suspend(SIGTSTP, &errstr), SUDO_RC_ERROR);
434 VERIFY_STR(errstr, "Something wrong in log_suspend");
435
436 errstr = NULL;
437 VERIFY_INT(python_io->change_winsize(200, 100, &errstr), SUDO_RC_ERROR);
438 VERIFY_STR(errstr, "Something wrong in change_winsize");
439
440 python_io->close(0, 0);
441
442 VERIFY_STR(data.stderr_str, "");
443 VERIFY_STR(data.stdout_str, "");
444 return true;
445 }
446
447 int
448 check_example_group_plugin(void)
449 {
450 create_plugin_options("example_group_plugin", "SudoGroupPlugin", NULL);
451
452 VERIFY_INT(group_plugin->init(GROUP_API_VERSION, fake_printf, data.plugin_options), SUDO_RC_OK);
453
454 VERIFY_INT(group_plugin->query("test", "mygroup", NULL), SUDO_RC_OK);
455 VERIFY_INT(group_plugin->query("testuser2", "testgroup", NULL), SUDO_RC_OK);
456 VERIFY_INT(group_plugin->query("testuser2", "mygroup", NULL), SUDO_RC_REJECT);
457 VERIFY_INT(group_plugin->query("test", "testgroup", NULL), SUDO_RC_REJECT);
458
459 group_plugin->cleanup();
460 VERIFY_STR(data.stderr_str, "");
461 VERIFY_STR(data.stdout_str, "");
462 return true;
463 }
464
465 const char *
466 create_debug_config(const char *debug_spec)
467 {
468 char *result = NULL;
469
470 static char config_path[PATH_MAX] = "/";
471 snprintf(config_path, sizeof(config_path), "%s/sudo.conf", data.tmp_dir);
472
473 char *content = NULL;
474 if (asprintf(&content, "Set developer_mode true\n"
475 "Debug %s %s/debug.log %s\n",
476 "python_plugin.so", data.tmp_dir, debug_spec) < 0)
477 {
478 printf("Failed to allocate string\n");
479 goto cleanup;
480 }
481
482 if (fwriteall(config_path, content) != true) {
483 printf("Failed to write '%s'\n", config_path);
484 goto cleanup;
485 }
486
487 result = config_path;
488
489 cleanup:
490 free(content);
491
492 return result;
493 }
494
495 int
496 check_example_group_plugin_is_able_to_debug(void)
497 {
498 const char *config_path = create_debug_config("py_calls@diag");
499 VERIFY_NOT_NULL(config_path);
500 VERIFY_INT(sudo_conf_read(config_path, SUDO_CONF_ALL), true);
501
502 create_plugin_options("example_group_plugin", "SudoGroupPlugin", NULL);
503
504 group_plugin->init(GROUP_API_VERSION, fake_printf, data.plugin_options);
505
506 group_plugin->query("user", "group", &example_pwd);
507
508 group_plugin->cleanup();
509
510 VERIFY_STR(data.stderr_str, "");
511 VERIFY_STR(data.stdout_str, "");
512
513 VERIFY_LOG_LINES(expected_path("check_example_group_plugin_is_able_to_debug.log"));
514
515 return true;
516 }
517
518 int
519 check_plugin_unload(void)
520 {
521 // You can call this test to avoid having a lot of subinterpreters
522 // (each plugin->open starts one, and only plugin unlink closes)
523 // It only verifies that python was shut down correctly.
524 VERIFY_TRUE(Py_IsInitialized());
525 VERIFY_TRUE(_unlink_symbols());
526 VERIFY_FALSE(Py_IsInitialized()); // python interpreter could be stopped
527 return true;
528 }
529
530 int
531 check_example_debugging(const char *debug_spec)
532 {
533 const char *errstr = NULL;
534 const char *config_path = create_debug_config(debug_spec);
535 VERIFY_NOT_NULL(config_path);
536 VERIFY_INT(sudo_conf_read(config_path, SUDO_CONF_ALL), true);
537
538 create_debugging_plugin_options();
539
540 str_array_free(&data.settings);
541 char *debug_flags_setting = NULL;
542 VERIFY_TRUE(asprintf(&debug_flags_setting, "debug_flags=%s/debug.log %s", data.tmp_dir, debug_spec) >= 0);
543
544 data.settings = create_str_array(3, debug_flags_setting, "plugin_path=python_plugin.so", NULL);
545
546 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
547 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
548 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
549 VERIFY_PTR(errstr, NULL);
550 python_io->close(0, 0);
551
552 VERIFY_STR(data.stderr_str, "");
553 VERIFY_STR(data.stdout_str, "");
554
555 VERIFY_LOG_LINES(expected_path("check_example_debugging_%s.log", debug_spec));
556
557 free(debug_flags_setting);
558 return true;
559 }
560
561 int
562 check_loading_fails(const char *name)
563 {
564 const char *errstr = NULL;
565
566 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
567 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
568 data.user_env, data.plugin_options, &errstr), SUDO_RC_ERROR);
569 VERIFY_PTR(errstr, NULL);
570 python_io->close(0, 0);
571
572 VERIFY_STDOUT(expected_path("check_loading_fails_%s.stdout", name));
573 VERIFY_STDERR(expected_path("check_loading_fails_%s.stderr", name));
574
575 return true;
576 }
577
578 int
579 check_loading_fails_with_missing_path(void)
580 {
581 str_array_free(&data.plugin_options);
582 data.plugin_options = create_str_array(2, "ClassName=DebugDemoPlugin", NULL);
583 return check_loading_fails("missing_path");
584 }
585
586 int
587 check_loading_succeeds_with_missing_classname(void)
588 {
589 str_array_free(&data.plugin_options);
590 data.plugin_options = create_str_array(2, "ModulePath=" SRC_DIR "/example_debugging.py", NULL);
591
592 const char *errstr = NULL;
593
594 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
595 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
596 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
597 VERIFY_PTR(errstr, NULL);
598 VERIFY_INT(python_io->show_version(1), SUDO_RC_OK);
599 python_io->close(0, 0);
600
601 VERIFY_STDOUT(expected_path("check_loading_succeeds_with_missing_classname.stdout"));
602 VERIFY_STR(data.stderr_str, "");
603
604 return true;
605 }
606
607 int
608 check_loading_fails_with_missing_classname(void)
609 {
610 str_array_free(&data.plugin_options);
611 data.plugin_options = create_str_array(2, "ModulePath=" SRC_DIR "/regress/plugin_errorstr.py", NULL);
612 return check_loading_fails("missing_classname");
613 }
614
615 int
616 check_loading_fails_with_wrong_classname(void)
617 {
618 create_plugin_options("example_debugging", "MispelledPluginName", NULL);
619 return check_loading_fails("wrong_classname");
620 }
621
622 int
623 check_loading_fails_with_wrong_path(void)
624 {
625 str_array_free(&data.plugin_options);
626 data.plugin_options = create_str_array(3, "ModulePath=/wrong_path.py", "ClassName=PluginName", NULL);
627 return check_loading_fails("wrong_path");
628 }
629
630 int
631 check_loading_fails_plugin_is_not_owned_by_root(void)
632 {
633 sudo_conf_clear_paths();
634 VERIFY_INT(sudo_conf_read(sudo_conf_normal_mode, SUDO_CONF_ALL), true);
635
636 create_debugging_plugin_options();
637 return check_loading_fails("not_owned_by_root");
638 }
639
640 int
641 check_example_conversation_plugin_reason_log(int simulate_suspend, const char *description)
642 {
643 const char *errstr = NULL;
644
645 create_conversation_plugin_options();
646
647 str_array_free(&data.plugin_argv); // have a command run
648 data.plugin_argc = 1;
649 data.plugin_argv = create_str_array(2, "/bin/whoami", NULL);
650
651 data.conv_replies[0] = "my fake reason";
652 data.conv_replies[1] = "my real secret reason";
653
654 sudo_conv_t conversation = simulate_suspend ? fake_conversation_with_suspend : fake_conversation;
655
656 VERIFY_INT(python_io->open(SUDO_API_VERSION, conversation, fake_printf, data.settings,
657 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
658 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
659 VERIFY_PTR(errstr, NULL);
660 python_io->close(0, 0);
661
662 VERIFY_STDOUT(expected_path("check_example_conversation_plugin_reason_log_%s.stdout", description));
663 VERIFY_STDERR(expected_path("check_example_conversation_plugin_reason_log_%s.stderr", description));
664 VERIFY_CONV(expected_path("check_example_conversation_plugin_reason_log_%s.conversation", description));
665 VERIFY_FILE("sudo_reasons.txt", expected_path("check_example_conversation_plugin_reason_log_%s.stored", description));
666 return true;
667 }
668
669 int
670 check_example_conversation_plugin_user_interrupts(void)
671 {
672 const char *errstr = NULL;
673
674 create_conversation_plugin_options();
675
676 str_array_free(&data.plugin_argv); // have a command run
677 data.plugin_argc = 1;
678 data.plugin_argv = create_str_array(2, "/bin/whoami", NULL);
679
680 data.conv_replies[0] = NULL; // this simulates user interrupt for the first question
681
682 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
683 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
684 data.user_env, data.plugin_options, &errstr), SUDO_RC_REJECT);
685 VERIFY_PTR(errstr, NULL);
686 python_io->close(0, 0);
687
688 VERIFY_STDOUT(expected_path("check_example_conversation_plugin_user_interrupts.stdout"));
689 VERIFY_STDERR(expected_path("check_example_conversation_plugin_user_interrupts.stderr"));
690 VERIFY_CONV(expected_path("check_example_conversation_plugin_user_interrupts.conversation"));
691 return true;
692 }
693
694 int
695 check_example_policy_plugin_version_display(int is_verbose)
696 {
697 const char *errstr = NULL;
698
699 create_policy_plugin_options();
700
701 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
702 data.user_info, data.user_env, data.plugin_options, &errstr),
703 SUDO_RC_OK);
704 VERIFY_PTR(errstr, NULL);
705 VERIFY_INT(python_policy->show_version(is_verbose), SUDO_RC_OK);
706
707 python_policy->close(0, 0); // this should not call the python plugin close as there was no command run invocation
708
709 if (is_verbose) {
710 // Note: the exact python version is environment dependent
711 VERIFY_STR_CONTAINS(data.stdout_str, "Python interpreter version:");
712 *strstr(data.stdout_str, "Python interpreter version:") = '\0';
713 VERIFY_STDOUT(expected_path("check_example_policy_plugin_version_display_full.stdout"));
714 } else {
715 VERIFY_STDOUT(expected_path("check_example_policy_plugin_version_display.stdout"));
716 }
717
718 VERIFY_STDERR(expected_path("check_example_policy_plugin_version_display.stderr"));
719
720 return true;
721 }
722
723 int
724 check_example_policy_plugin_accepted_execution(void)
725 {
726 const char *errstr = NULL;
727
728 create_policy_plugin_options();
729
730 str_array_free(&data.plugin_argv);
731 data.plugin_argc = 2;
732 data.plugin_argv = create_str_array(3, "/bin/whoami", "--help", NULL);
733
734 str_array_free(&data.user_env);
735 data.user_env = create_str_array(3, "USER_ENV1=VALUE1", "USER_ENV2=value2", NULL);
736
737 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
738 data.user_info, data.user_env, data.plugin_options, &errstr),
739 SUDO_RC_OK);
740 VERIFY_PTR(errstr, NULL);
741
742 char **env_add = create_str_array(3, "REQUESTED_ENV1=VALUE1", "REQUESTED_ENV2=value2", NULL);
743
744 char **argv_out, **user_env_out, **command_info_out; // free to contain garbage
745
746 VERIFY_INT(python_policy->check_policy(data.plugin_argc, data.plugin_argv, env_add,
747 &command_info_out, &argv_out, &user_env_out, &errstr),
748 SUDO_RC_ACCEPT);
749 VERIFY_PTR(errstr, NULL);
750
751 VERIFY_STR_SET(command_info_out, 4, "command=/bin/whoami", "runas_uid=0", "runas_gid=0", NULL);
752 VERIFY_STR_SET(user_env_out, 5, "USER_ENV1=VALUE1", "USER_ENV2=value2",
753 "REQUESTED_ENV1=VALUE1", "REQUESTED_ENV2=value2", NULL);
754 VERIFY_STR_SET(argv_out, 3, "/bin/whoami", "--help", NULL);
755
756 VERIFY_INT(python_policy->init_session(&example_pwd, &user_env_out, &errstr), SUDO_RC_ACCEPT);
757 VERIFY_PTR(errstr, NULL);
758
759 // init session is able to modify the user env:
760 VERIFY_STR_SET(user_env_out, 6, "USER_ENV1=VALUE1", "USER_ENV2=value2",
761 "REQUESTED_ENV1=VALUE1", "REQUESTED_ENV2=value2", "PLUGIN_EXAMPLE_ENV=1", NULL);
762
763 python_policy->close(3, 0); // successful execution returned exit code 3
764
765 VERIFY_STDOUT(expected_path("check_example_policy_plugin_accepted_execution.stdout"));
766 VERIFY_STDERR(expected_path("check_example_policy_plugin_accepted_execution.stderr"));
767
768 str_array_free(&env_add);
769 str_array_free(&user_env_out);
770 str_array_free(&command_info_out);
771 str_array_free(&argv_out);
772 return true;
773 }
774
775 int
776 check_example_policy_plugin_failed_execution(void)
777 {
778 const char *errstr = NULL;
779
780 create_policy_plugin_options();
781
782 str_array_free(&data.plugin_argv);
783 data.plugin_argc = 2;
784 data.plugin_argv = create_str_array(3, "/bin/id", "--help", NULL);
785
786 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
787 data.user_info, data.user_env, data.plugin_options, &errstr),
788 SUDO_RC_OK);
789 VERIFY_PTR(errstr, NULL);
790
791 char **argv_out, **user_env_out, **command_info_out; // free to contain garbage
792
793 VERIFY_INT(python_policy->check_policy(data.plugin_argc, data.plugin_argv, NULL,
794 &command_info_out, &argv_out, &user_env_out, &errstr),
795 SUDO_RC_ACCEPT);
796 VERIFY_PTR(errstr, NULL);
797
798 // pwd is unset (user is not part of /etc/passwd)
799 VERIFY_INT(python_policy->init_session(NULL, &user_env_out, &errstr), SUDO_RC_ACCEPT);
800 VERIFY_PTR(errstr, NULL);
801
802 python_policy->close(12345, ENOENT); // failed to execute
803
804 VERIFY_STDOUT(expected_path("check_example_policy_plugin_failed_execution.stdout"));
805 VERIFY_STDERR(expected_path("check_example_policy_plugin_failed_execution.stderr"));
806
807 str_array_free(&user_env_out);
808 str_array_free(&command_info_out);
809 str_array_free(&argv_out);
810 return true;
811 }
812
813 int
814 check_example_policy_plugin_denied_execution(void)
815 {
816 const char *errstr = NULL;
817
818 create_policy_plugin_options();
819
820 str_array_free(&data.plugin_argv);
821 data.plugin_argc = 1;
822 data.plugin_argv = create_str_array(2, "/bin/passwd", NULL);
823
824 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
825 data.user_info, data.user_env, data.plugin_options, &errstr),
826 SUDO_RC_OK);
827 VERIFY_PTR(errstr, NULL);
828
829 char **argv_out, **user_env_out, **command_info_out; // free to contain garbage
830
831 VERIFY_INT(python_policy->check_policy(data.plugin_argc, data.plugin_argv, NULL,
832 &command_info_out, &argv_out, &user_env_out, &errstr),
833 SUDO_RC_REJECT);
834 VERIFY_PTR(errstr, NULL);
835
836 VERIFY_PTR(command_info_out, NULL);
837 VERIFY_PTR(argv_out, NULL);
838 VERIFY_PTR(user_env_out, NULL);
839
840 python_policy->close(0, 0); // there was no execution
841
842 VERIFY_STDOUT(expected_path("check_example_policy_plugin_denied_execution.stdout"));
843 VERIFY_STDERR(expected_path("check_example_policy_plugin_denied_execution.stderr"));
844
845 return true;
846 }
847
848 int
849 check_example_policy_plugin_list(void)
850 {
851 const char *errstr = NULL;
852
853 create_policy_plugin_options();
854
855 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
856 data.user_info, data.user_env, data.plugin_options, &errstr),
857 SUDO_RC_OK);
858 VERIFY_PTR(errstr, NULL);
859
860 snprintf_append(data.stdout_str, MAX_OUTPUT, "-- minimal --\n");
861 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, false, NULL, &errstr), SUDO_RC_OK);
862 VERIFY_PTR(errstr, NULL);
863
864 snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- minimal (verbose) --\n");
865 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, true, NULL, &errstr), SUDO_RC_OK);
866 VERIFY_PTR(errstr, NULL);
867
868 snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with user --\n");
869 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, false, "testuser", &errstr), SUDO_RC_OK);
870 VERIFY_PTR(errstr, NULL);
871
872 snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with user (verbose) --\n");
873 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, true, "testuser", &errstr), SUDO_RC_OK);
874 VERIFY_PTR(errstr, NULL);
875
876 snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with allowed program --\n");
877 str_array_free(&data.plugin_argv);
878 data.plugin_argc = 3;
879 data.plugin_argv = create_str_array(4, "/bin/id", "some", "arguments", NULL);
880 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, false, NULL, &errstr), SUDO_RC_OK);
881 VERIFY_PTR(errstr, NULL);
882
883 snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with allowed program (verbose) --\n");
884 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, true, NULL, &errstr), SUDO_RC_OK);
885 VERIFY_PTR(errstr, NULL);
886
887 snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with denied program --\n");
888 str_array_free(&data.plugin_argv);
889 data.plugin_argc = 1;
890 data.plugin_argv = create_str_array(2, "/bin/passwd", NULL);
891 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, false, NULL, &errstr), SUDO_RC_OK);
892 VERIFY_PTR(errstr, NULL);
893
894 snprintf_append(data.stdout_str, MAX_OUTPUT, "\n-- with denied program (verbose) --\n");
895 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, true, NULL, &errstr), SUDO_RC_OK);
896 VERIFY_PTR(errstr, NULL);
897
898 python_policy->close(0, 0); // there was no execution
899
900 VERIFY_STDOUT(expected_path("check_example_policy_plugin_list.stdout"));
901 VERIFY_STDERR(expected_path("check_example_policy_plugin_list.stderr"));
902
903 return true;
904 }
905
906 int
907 check_example_policy_plugin_validate_invalidate(void)
908 {
909 const char *errstr = NULL;
910
911 // the plugin does not do any meaningful for these, so using log to validate instead
912 const char *config_path = create_debug_config("py_calls@diag");
913 VERIFY_NOT_NULL(config_path);
914 VERIFY_INT(sudo_conf_read(config_path, SUDO_CONF_ALL), true);
915
916 create_policy_plugin_options();
917
918 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
919 data.user_info, data.user_env, data.plugin_options, &errstr),
920 SUDO_RC_OK);
921 VERIFY_PTR(errstr, NULL);
922
923 VERIFY_INT(python_policy->validate(&errstr), SUDO_RC_OK);
924 VERIFY_PTR(errstr, NULL);
925
926 python_policy->invalidate(true);
927 python_policy->invalidate(false);
928
929 python_policy->close(0, 0); // no command execution
930
931 VERIFY_LOG_LINES(expected_path("check_example_policy_plugin_validate_invalidate.log"));
932 VERIFY_STR(data.stderr_str, "");
933 VERIFY_STR(data.stdout_str, "");
934 return true;
935 }
936
937 int
938 check_policy_plugin_callbacks_are_optional(void)
939 {
940 const char *errstr = NULL;
941
942 create_debugging_plugin_options();
943
944 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
945 data.user_info, data.user_env, data.plugin_options, &errstr),
946 SUDO_RC_OK);
947 VERIFY_PTR(errstr, NULL);
948
949 VERIFY_PTR(python_policy->list, NULL);
950 VERIFY_PTR(python_policy->validate, NULL);
951 VERIFY_PTR(python_policy->invalidate, NULL);
952 VERIFY_PTR_NE(python_policy->check_policy, NULL); // (not optional)
953 VERIFY_PTR(python_policy->init_session, NULL);
954
955 // show_version always displays the plugin, but it is optional in the python layer
956 VERIFY_PTR_NE(python_policy->show_version, NULL);
957 VERIFY_INT(python_policy->show_version(1), SUDO_RC_OK);
958
959 python_policy->close(0, 0);
960 return true;
961 }
962
963 int
964 check_policy_plugin_reports_error(void)
965 {
966 const char *errstr = NULL;
967 str_array_free(&data.plugin_options);
968 data.plugin_options = create_str_array(
969 3,
970 "ModulePath=" SRC_DIR "/regress/plugin_errorstr.py",
971 "ClassName=ConstructErrorPlugin",
972 NULL
973 );
974
975 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
976 data.user_info, data.user_env, data.plugin_options, &errstr), SUDO_RC_ERROR);
977 VERIFY_STR(errstr, "Something wrong in plugin constructor");
978 errstr = NULL;
979
980 python_policy->close(0, 0);
981
982 str_array_free(&data.plugin_options);
983 data.plugin_options = create_str_array(
984 3,
985 "ModulePath=" SRC_DIR "/regress/plugin_errorstr.py",
986 "ClassName=ErrorMsgPlugin",
987 NULL
988 );
989
990 data.plugin_argc = 1;
991 str_array_free(&data.plugin_argv);
992 data.plugin_argv = create_str_array(2, "id", NULL);
993
994 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
995 data.user_info, data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
996 VERIFY_PTR(errstr, NULL);
997
998 char **command_info_out = NULL;
999 char **argv_out = NULL;
1000 char **user_env_out = NULL;
1001
1002 VERIFY_INT(python_policy->list(data.plugin_argc, data.plugin_argv, true, NULL, &errstr), SUDO_RC_ERROR);
1003 VERIFY_STR(errstr, "Something wrong in list");
1004
1005 errstr = NULL;
1006 VERIFY_INT(python_policy->validate(&errstr), SUDO_RC_ERROR);
1007 VERIFY_STR(errstr, "Something wrong in validate");
1008
1009 errstr = NULL;
1010 VERIFY_INT(python_policy->check_policy(data.plugin_argc, data.plugin_argv, data.user_env,
1011 &command_info_out, &argv_out, &user_env_out, &errstr),
1012 SUDO_RC_ERROR);
1013 VERIFY_STR(errstr, "Something wrong in check_policy");
1014
1015 errstr = NULL;
1016 VERIFY_INT(python_policy->init_session(&example_pwd, &user_env_out, &errstr), SUDO_RC_ERROR);
1017 VERIFY_STR(errstr, "Something wrong in init_session");
1018
1019 python_policy->close(0, 0);
1020
1021 VERIFY_STR(data.stderr_str, "");
1022 VERIFY_STR(data.stdout_str, "");
1023 return true;
1024 }
1025
1026 int
1027 check_io_plugin_callbacks_are_optional(void)
1028 {
1029 const char *errstr = NULL;
1030
1031 create_debugging_plugin_options();
1032
1033 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
1034 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
1035 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1036 VERIFY_PTR(errstr, NULL);
1037
1038 VERIFY_PTR(python_io->log_stdin, NULL);
1039 VERIFY_PTR(python_io->log_stdout, NULL);
1040 VERIFY_PTR(python_io->log_stderr, NULL);
1041 VERIFY_PTR(python_io->log_ttyin, NULL);
1042 VERIFY_PTR(python_io->log_ttyout, NULL);
1043 VERIFY_PTR(python_io->change_winsize, NULL);
1044
1045 // show_version always displays the plugin, but it is optional in the python layer
1046 VERIFY_PTR_NE(python_io->show_version, NULL);
1047 VERIFY_INT(python_io->show_version(1), SUDO_RC_OK);
1048
1049 python_io->close(0, 0);
1050 return true;
1051 }
1052
1053 int
1054 check_python_plugins_do_not_affect_each_other(void)
1055 {
1056 const char *errstr = NULL;
1057
1058 // We test here that one plugin is not able to effect the environment of another
1059 // This is important so they do not ruin or depend on each other's state.
1060 create_plugin_options("regress/plugin_conflict", "ConflictPlugin", "Path=path_for_first_plugin");
1061
1062 VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
1063 data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
1064 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1065 VERIFY_PTR(errstr, NULL);
1066
1067 create_plugin_options("regress/plugin_conflict", "ConflictPlugin", "Path=path_for_second_plugin");
1068 VERIFY_INT(python_policy->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
1069 data.user_info, data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1070 VERIFY_PTR(errstr, NULL);
1071
1072 python_io->close(0, 0);
1073 python_policy->close(0, 0);
1074
1075 VERIFY_STDOUT(expected_path("check_python_plugins_do_not_affect_each_other.stdout"));
1076 VERIFY_STR(data.stderr_str, "");
1077 return true;
1078 }
1079
1080 int
1081 check_example_audit_plugin_receives_accept(void)
1082 {
1083 create_audit_plugin_options("");
1084 const char *errstr = NULL;
1085
1086 str_array_free(&data.plugin_argv);
1087 data.plugin_argv = create_str_array(6, "sudo", "-u", "user", "id", "--help", NULL);
1088
1089 str_array_free(&data.user_env);
1090 data.user_env = create_str_array(3, "KEY1=VALUE1", "KEY2=VALUE2", NULL);
1091
1092 str_array_free(&data.user_info);
1093 data.user_info = create_str_array(3, "user=testuser1", "uid=123", NULL);
1094
1095 VERIFY_INT(python_audit->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1096 data.settings, data.user_info, 3, data.plugin_argv,
1097 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1098 VERIFY_PTR(errstr, NULL);
1099
1100 str_array_free(&data.command_info);
1101 data.command_info = create_str_array(2, "command=/sbin/id", NULL);
1102
1103 str_array_free(&data.plugin_argv);
1104 data.plugin_argv = create_str_array(3, "id", "--help", NULL);
1105
1106 VERIFY_INT(python_audit->accept("accepter plugin name", SUDO_POLICY_PLUGIN,
1107 data.command_info, data.plugin_argv,
1108 data.user_env, &errstr), SUDO_RC_OK);
1109 VERIFY_PTR(errstr, NULL);
1110
1111 python_audit->close(SUDO_PLUGIN_WAIT_STATUS, W_EXITCODE(2, 0)); // process exited with 2
1112
1113 VERIFY_STDOUT(expected_path("check_example_audit_plugin_receives_accept.stdout"));
1114 VERIFY_STR(data.stderr_str, "");
1115
1116 return true;
1117 }
1118
1119 int
1120 check_example_audit_plugin_receives_reject(void)
1121 {
1122 create_audit_plugin_options(NULL);
1123 const char *errstr = NULL;
1124
1125 str_array_free(&data.plugin_argv);
1126 data.plugin_argv = create_str_array(3, "sudo", "passwd", NULL);
1127
1128 str_array_free(&data.user_info);
1129 data.user_info = create_str_array(3, "user=root", "uid=0", NULL);
1130
1131 VERIFY_INT(python_audit->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1132 data.settings, data.user_info, 1, data.plugin_argv,
1133 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1134 VERIFY_PTR(errstr, NULL);
1135
1136 VERIFY_INT(python_audit->reject("rejecter plugin name", SUDO_IO_PLUGIN,
1137 "Rejected just because!", data.command_info,
1138 &errstr), SUDO_RC_OK);
1139 VERIFY_PTR(errstr, NULL);
1140
1141 python_audit->close(SUDO_PLUGIN_NO_STATUS, 0); // program was not run
1142
1143 VERIFY_STDOUT(expected_path("check_example_audit_plugin_receives_reject.stdout"));
1144 VERIFY_STR(data.stderr_str, "");
1145
1146 return true;
1147 }
1148
1149 int
1150 check_example_audit_plugin_receives_error(void)
1151 {
1152 create_audit_plugin_options("");
1153 const char *errstr = NULL;
1154
1155 str_array_free(&data.plugin_argv);
1156 data.plugin_argv = create_str_array(5, "sudo", "-u", "user", "id", NULL);
1157
1158 VERIFY_INT(python_audit->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1159 data.settings, data.user_info, 3, data.plugin_argv,
1160 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1161 VERIFY_PTR(errstr, NULL);
1162
1163 str_array_free(&data.command_info);
1164 data.command_info = create_str_array(2, "command=/sbin/id", NULL);
1165
1166 VERIFY_INT(python_audit->error("errorer plugin name", SUDO_AUDIT_PLUGIN,
1167 "Some error has happened", data.command_info,
1168 &errstr), SUDO_RC_OK);
1169 VERIFY_PTR(errstr, NULL);
1170
1171 python_audit->close(SUDO_PLUGIN_SUDO_ERROR, 222);
1172
1173 VERIFY_STDOUT(expected_path("check_example_audit_plugin_receives_error.stdout"));
1174 VERIFY_STR(data.stderr_str, "");
1175
1176 return true;
1177 }
1178
1179 typedef struct audit_plugin * (audit_clone_func)(void);
1180
1181 int
check_example_audit_plugin_workflow_multiple(void)1182 check_example_audit_plugin_workflow_multiple(void)
1183 {
1184 // verify multiple python audit plugins are available
1185 audit_clone_func *python_audit_clone = (audit_clone_func *)sudo_dso_findsym(
1186 python_plugin_handle, "python_audit_clone");
1187 VERIFY_PTR_NE(python_audit_clone, NULL);
1188
1189 struct audit_plugin *python_audit2 = NULL;
1190
1191 for (int i = 0; i < 7; ++i) {
1192 python_audit2 = (*python_audit_clone)();
1193 VERIFY_PTR_NE(python_audit2, NULL);
1194 VERIFY_PTR_NE(python_audit2, python_audit);
1195 }
1196
1197 const char *errstr = NULL;
1198
1199 str_array_free(&data.plugin_argv);
1200 data.plugin_argv = create_str_array(6, "sudo", "-u", "user", "id", "--help", NULL);
1201
1202 str_array_free(&data.user_env);
1203 data.user_env = create_str_array(3, "KEY1=VALUE1", "KEY2=VALUE2", NULL);
1204
1205 str_array_free(&data.user_info);
1206 data.user_info = create_str_array(3, "user=default", "uid=1000", NULL);
1207
1208 create_audit_plugin_options("Id=1");
1209 VERIFY_INT(python_audit->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1210 data.settings, data.user_info, 3, data.plugin_argv,
1211 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1212 VERIFY_PTR(errstr, NULL);
1213
1214 // For verifying the error message of no more plugin. It should be displayed only once.
1215 VERIFY_PTR((*python_audit_clone)(), NULL);
1216 VERIFY_PTR((*python_audit_clone)(), NULL);
1217
1218 create_audit_plugin_options("Id=2");
1219 VERIFY_INT(python_audit2->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1220 data.settings, data.user_info, 3, data.plugin_argv,
1221 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1222 VERIFY_PTR(errstr, NULL);
1223
1224 str_array_free(&data.command_info);
1225 data.command_info = create_str_array(2, "command=/sbin/id", NULL);
1226
1227 str_array_free(&data.plugin_argv);
1228 data.plugin_argv = create_str_array(3, "id", "--help", NULL);
1229
1230 VERIFY_INT(python_audit->accept("accepter plugin name", SUDO_POLICY_PLUGIN,
1231 data.command_info, data.plugin_argv,
1232 data.user_env, &errstr), SUDO_RC_OK);
1233 VERIFY_PTR(errstr, NULL);
1234
1235 VERIFY_INT(python_audit2->accept("accepter plugin name", SUDO_POLICY_PLUGIN,
1236 data.command_info, data.plugin_argv,
1237 data.user_env, &errstr), SUDO_RC_OK);
1238 VERIFY_PTR(errstr, NULL);
1239
1240 python_audit->close(SUDO_PLUGIN_WAIT_STATUS, W_EXITCODE(0, 11)); // process got signal 11
1241 python_audit2->close(SUDO_PLUGIN_WAIT_STATUS, W_EXITCODE(0, 11));
1242
1243 VERIFY_STDOUT(expected_path("check_example_audit_plugin_workflow_multiple.stdout"));
1244 VERIFY_STDERR(expected_path("check_example_audit_plugin_workflow_multiple.stderr"));
1245
1246 return true;
1247 }
1248
1249 int
check_example_audit_plugin_version_display(void)1250 check_example_audit_plugin_version_display(void)
1251 {
1252 create_audit_plugin_options(NULL);
1253 const char *errstr = NULL;
1254
1255 str_array_free(&data.user_info);
1256 data.user_info = create_str_array(3, "user=root", "uid=0", NULL);
1257
1258 str_array_free(&data.plugin_argv);
1259 data.plugin_argv = create_str_array(3, "sudo", "-V", NULL);
1260
1261 VERIFY_INT(python_audit->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1262 data.settings, data.user_info, 2, data.plugin_argv,
1263 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1264 VERIFY_PTR(errstr, NULL);
1265
1266 VERIFY_INT(python_audit->show_version(false), SUDO_RC_OK);
1267 VERIFY_INT(python_audit->show_version(true), SUDO_RC_OK);
1268
1269 python_audit->close(SUDO_PLUGIN_SUDO_ERROR, 222);
1270
1271 VERIFY_STDOUT(expected_path("check_example_audit_plugin_version_display.stdout"));
1272 VERIFY_STR(data.stderr_str, "");
1273
1274 return true;
1275 }
1276
1277 int
check_audit_plugin_callbacks_are_optional(void)1278 check_audit_plugin_callbacks_are_optional(void)
1279 {
1280 const char *errstr = NULL;
1281
1282 create_debugging_plugin_options();
1283
1284 VERIFY_INT(python_audit->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1285 data.settings, data.user_info, 2, data.plugin_argv,
1286 data.user_env, data.plugin_options, &errstr),
1287 SUDO_RC_OK);
1288 VERIFY_PTR(errstr, NULL);
1289
1290 VERIFY_PTR(python_audit->accept, NULL);
1291 VERIFY_PTR(python_audit->reject, NULL);
1292 VERIFY_PTR(python_audit->error, NULL);
1293
1294 // show_version always displays the plugin, but it is optional in the python layer
1295 VERIFY_PTR_NE(python_audit->show_version, NULL);
1296 VERIFY_INT(python_audit->show_version(1), SUDO_RC_OK);
1297
1298 python_audit->close(SUDO_PLUGIN_NO_STATUS, 0);
1299 return true;
1300 }
1301
1302 int
check_audit_plugin_reports_error(void)1303 check_audit_plugin_reports_error(void)
1304 {
1305 const char *errstr = NULL;
1306 create_plugin_options("regress/plugin_errorstr", "ConstructErrorPlugin", NULL);
1307
1308 VERIFY_INT(python_audit->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1309 data.settings, data.user_info, 0, data.plugin_argv,
1310 data.user_env, data.plugin_options, &errstr), SUDO_RC_ERROR);
1311
1312 VERIFY_STR(errstr, "Something wrong in plugin constructor");
1313 errstr = NULL;
1314
1315 python_audit->close(SUDO_PLUGIN_NO_STATUS, 0);
1316
1317 create_plugin_options("regress/plugin_errorstr", "ErrorMsgPlugin", NULL);
1318
1319 VERIFY_INT(python_audit->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1320 data.settings, data.user_info, 0, data.plugin_argv,
1321 data.user_env, data.plugin_options, &errstr), SUDO_RC_ERROR);
1322 VERIFY_STR(errstr, "Something wrong in open");
1323
1324 errstr = NULL;
1325 VERIFY_INT(python_audit->accept("plugin name", SUDO_POLICY_PLUGIN,
1326 data.command_info, data.plugin_argv,
1327 data.user_env, &errstr), SUDO_RC_ERROR);
1328 VERIFY_STR(errstr, "Something wrong in accept");
1329
1330 errstr = NULL;
1331 VERIFY_INT(python_audit->reject("plugin name", SUDO_POLICY_PLUGIN,
1332 "audit message", data.command_info,
1333 &errstr), SUDO_RC_ERROR);
1334 VERIFY_STR(errstr, "Something wrong in reject");
1335
1336 errstr = NULL;
1337 VERIFY_INT(python_audit->error("plugin name", SUDO_POLICY_PLUGIN,
1338 "audit message", data.command_info,
1339 &errstr), SUDO_RC_ERROR);
1340 VERIFY_STR(errstr, "Something wrong in error");
1341
1342 python_audit->close(SUDO_PLUGIN_NO_STATUS, 0);
1343
1344 VERIFY_STR(data.stderr_str, "");
1345 VERIFY_STR(data.stdout_str, "");
1346 return true;
1347 }
1348
1349 static int
check_example_approval_plugin(const char * date_str,const char * expected_error)1350 check_example_approval_plugin(const char *date_str, const char *expected_error)
1351 {
1352 const char *errstr = NULL;
1353
1354 create_plugin_options("example_approval_plugin", "BusinessHoursApprovalPlugin", NULL);
1355
1356 VERIFY_INT(python_approval->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1357 data.settings, data.user_info, 0, data.plugin_argv,
1358 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1359
1360 VERIFY_TRUE(mock_python_datetime_now("example_approval_plugin", date_str));
1361
1362 int expected_rc = (expected_error == NULL) ? SUDO_RC_ACCEPT : SUDO_RC_REJECT;
1363
1364 VERIFY_INT(python_approval->check(data.command_info, data.plugin_argv, data.user_env, &errstr),
1365 expected_rc);
1366
1367 if (expected_error == NULL) {
1368 VERIFY_PTR(errstr, NULL);
1369 VERIFY_STR(data.stdout_str, "");
1370 } else {
1371 VERIFY_STR(errstr, expected_error);
1372 VERIFY_STR_CONTAINS(data.stdout_str, expected_error); // (ends with \n)
1373 }
1374 VERIFY_STR(data.stderr_str, "");
1375
1376 python_approval->close();
1377
1378 return true;
1379 }
1380
1381 typedef struct approval_plugin * (approval_clone_func)(void);
1382
1383 static int
1384 check_multiple_approval_plugin_and_arguments(void)
1385 {
1386 // verify multiple python approval plugins are available
1387 approval_clone_func *python_approval_clone = (approval_clone_func *)sudo_dso_findsym(
1388 python_plugin_handle, "python_approval_clone");
1389 VERIFY_PTR_NE(python_approval_clone, NULL);
1390
1391 struct approval_plugin *python_approval2 = NULL;
1392
1393 for (int i = 0; i < 7; ++i) {
1394 python_approval2 = (*python_approval_clone)();
1395 VERIFY_PTR_NE(python_approval2, NULL);
1396 VERIFY_PTR_NE(python_approval2, python_approval);
1397 }
1398
1399 const char *errstr = NULL;
1400 create_plugin_options("regress/plugin_approval_test", "ApprovalTestPlugin", "Id=1");
1401
1402 str_array_free(&data.plugin_argv);
1403 data.plugin_argv = create_str_array(6, "sudo", "-u", "user", "whoami", "--help", NULL);
1404
1405 str_array_free(&data.user_env);
1406 data.user_env = create_str_array(3, "USER_ENV1=VALUE1", "USER_ENV2=value2", NULL);
1407
1408 str_array_free(&data.user_info);
1409 data.user_info = create_str_array(3, "INFO1=VALUE1", "info2=value2", NULL);
1410
1411 str_array_free(&data.settings);
1412 data.settings = create_str_array(3, "SETTING1=VALUE1", "setting2=value2", NULL);
1413
1414 VERIFY_INT(python_approval->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1415 data.settings, data.user_info, 3, data.plugin_argv,
1416 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1417 VERIFY_PTR(errstr, NULL);
1418
1419 // For verifying the error message of no more plugin. It should be displayed only once.
1420 VERIFY_PTR((*python_approval_clone)(), NULL);
1421 VERIFY_PTR((*python_approval_clone)(), NULL);
1422
1423 create_plugin_options("regress/plugin_approval_test", "ApprovalTestPlugin", "Id=2");
1424 VERIFY_INT(python_approval2->open(SUDO_API_VERSION, fake_conversation, fake_printf,
1425 data.settings, data.user_info, 3, data.plugin_argv,
1426 data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
1427 VERIFY_PTR(errstr, NULL);
1428
1429 VERIFY_INT(python_approval->show_version(false), SUDO_RC_OK);
1430 VERIFY_INT(python_approval2->show_version(true), SUDO_RC_OK);
1431
1432 str_array_free(&data.command_info);
1433 data.command_info = create_str_array(3, "CMDINFO1=value1", "CMDINFO2=VALUE2", NULL);
1434
1435 str_array_free(&data.plugin_argv);
1436 data.plugin_argv = create_str_array(3, "whoami", "--help", NULL);
1437
1438 VERIFY_INT(python_approval->check(data.command_info, data.plugin_argv, data.user_env, &errstr),
1439 SUDO_RC_OK);
1440 VERIFY_PTR(errstr, NULL);
1441
1442 VERIFY_INT(python_approval2->check(data.command_info, data.plugin_argv, data.user_env, &errstr),
1443 SUDO_RC_OK);
1444 VERIFY_PTR(errstr, NULL);
1445
1446 python_approval->close();
1447 python_approval2->close();
1448
1449 VERIFY_STDOUT(expected_path("check_multiple_approval_plugin_and_arguments.stdout"));
1450 VERIFY_STDERR(expected_path("check_multiple_approval_plugin_and_arguments.stderr"));
1451
1452 return true;
1453 }
1454
1455
1456 static int
_init_symbols(void)1457 _init_symbols(void)
1458 {
1459 if (python_plugin_handle != NULL) {
1460 // symbols are already loaded, we just restore
1461 RESTORE_PYTHON_PLUGIN(python_io);
1462 RESTORE_PYTHON_PLUGIN(python_policy);
1463 RESTORE_PYTHON_PLUGIN(python_approval);
1464 RESTORE_PYTHON_PLUGIN(python_audit);
1465 RESTORE_PYTHON_PLUGIN(group_plugin);
1466 return true;
1467 }
1468
1469 // we load the symbols
1470 python_plugin_handle = sudo_dso_load(python_plugin_so_path, SUDO_DSO_LAZY|SUDO_DSO_GLOBAL);
1471 VERIFY_PTR_NE(python_plugin_handle, NULL);
1472
1473 python_io = sudo_dso_findsym(python_plugin_handle, "python_io");
1474 VERIFY_PTR_NE(python_io, NULL);
1475
1476 group_plugin = sudo_dso_findsym(python_plugin_handle, "group_plugin");
1477 VERIFY_PTR_NE(group_plugin, NULL);
1478
1479 python_policy = sudo_dso_findsym(python_plugin_handle, "python_policy");
1480 VERIFY_PTR_NE(python_policy, NULL);
1481
1482 python_audit = sudo_dso_findsym(python_plugin_handle, "python_audit");
1483 VERIFY_PTR_NE(python_audit, NULL);
1484
1485 python_approval = sudo_dso_findsym(python_plugin_handle, "python_approval");
1486 VERIFY_PTR_NE(python_approval, NULL);
1487
1488 SAVE_PYTHON_PLUGIN(python_io);
1489 SAVE_PYTHON_PLUGIN(python_policy);
1490 SAVE_PYTHON_PLUGIN(python_approval);
1491 SAVE_PYTHON_PLUGIN(python_audit);
1492 SAVE_PYTHON_PLUGIN(group_plugin);
1493
1494 return true;
1495 }
1496
1497 static int
_unlink_symbols(void)1498 _unlink_symbols(void)
1499 {
1500 python_io = NULL;
1501 group_plugin = NULL;
1502 python_policy = NULL;
1503 python_approval = NULL;
1504 python_audit = NULL;
1505 VERIFY_INT(sudo_dso_unload(python_plugin_handle), 0);
1506 python_plugin_handle = NULL;
1507 VERIFY_FALSE(Py_IsInitialized());
1508 return true;
1509 }
1510
1511 int
main(int argc,char * argv[])1512 main(int argc, char *argv[])
1513 {
1514 int errors = 0;
1515
1516 if (argc != 2) {
1517 printf("Please specify the python_plugin.so as argument!\n");
1518 return EXIT_FAILURE;
1519 }
1520 python_plugin_so_path = argv[1];
1521
1522 RUN_TEST(check_example_io_plugin_version_display(true));
1523 RUN_TEST(check_example_io_plugin_version_display(false));
1524 RUN_TEST(check_example_io_plugin_command_log());
1525 RUN_TEST(check_example_io_plugin_command_log_multiple());
1526 RUN_TEST(check_example_io_plugin_failed_to_start_command());
1527 RUN_TEST(check_example_io_plugin_fails_with_python_backtrace());
1528 RUN_TEST(check_io_plugin_callbacks_are_optional());
1529 RUN_TEST(check_io_plugin_reports_error());
1530 RUN_TEST(check_plugin_unload());
1531
1532 RUN_TEST(check_example_group_plugin());
1533 RUN_TEST(check_example_group_plugin_is_able_to_debug());
1534 RUN_TEST(check_plugin_unload());
1535
1536 RUN_TEST(check_loading_fails_with_missing_path());
1537 RUN_TEST(check_loading_succeeds_with_missing_classname());
1538 RUN_TEST(check_loading_fails_with_missing_classname());
1539 RUN_TEST(check_loading_fails_with_wrong_classname());
1540 RUN_TEST(check_loading_fails_with_wrong_path());
1541 RUN_TEST(check_loading_fails_plugin_is_not_owned_by_root());
1542 RUN_TEST(check_plugin_unload());
1543
1544 RUN_TEST(check_example_conversation_plugin_reason_log(false, "without_suspend"));
1545 RUN_TEST(check_example_conversation_plugin_reason_log(true, "with_suspend"));
1546 RUN_TEST(check_example_conversation_plugin_user_interrupts());
1547 RUN_TEST(check_plugin_unload());
1548
1549 RUN_TEST(check_example_policy_plugin_version_display(true));
1550 RUN_TEST(check_example_policy_plugin_version_display(false));
1551 RUN_TEST(check_example_policy_plugin_accepted_execution());
1552 RUN_TEST(check_example_policy_plugin_failed_execution());
1553 RUN_TEST(check_example_policy_plugin_denied_execution());
1554 RUN_TEST(check_example_policy_plugin_list());
1555 RUN_TEST(check_example_policy_plugin_validate_invalidate());
1556 RUN_TEST(check_policy_plugin_callbacks_are_optional());
1557 RUN_TEST(check_policy_plugin_reports_error());
1558 RUN_TEST(check_plugin_unload());
1559
1560 RUN_TEST(check_example_audit_plugin_receives_accept());
1561 RUN_TEST(check_example_audit_plugin_receives_reject());
1562 RUN_TEST(check_example_audit_plugin_receives_error());
1563 RUN_TEST(check_example_audit_plugin_workflow_multiple());
1564 RUN_TEST(check_example_audit_plugin_version_display());
1565 RUN_TEST(check_audit_plugin_callbacks_are_optional());
1566 RUN_TEST(check_audit_plugin_reports_error());
1567 RUN_TEST(check_plugin_unload());
1568
1569 // Monday, too early
1570 RUN_TEST(check_example_approval_plugin(
1571 "2020-02-10T07:55:23", "That is not allowed outside the business hours!"));
1572 // Monday, good time
1573 RUN_TEST(check_example_approval_plugin("2020-02-10T08:05:23", NULL));
1574 // Friday, good time
1575 RUN_TEST(check_example_approval_plugin("2020-02-14T17:59:23", NULL));
1576 // Friday, too late
1577 RUN_TEST(check_example_approval_plugin(
1578 "2020-02-10T18:05:23", "That is not allowed outside the business hours!"));
1579 // Saturday
1580 RUN_TEST(check_example_approval_plugin(
1581 "2020-02-15T08:05:23", "That is not allowed on the weekend!"));
1582 RUN_TEST(check_multiple_approval_plugin_and_arguments());
1583
1584 RUN_TEST(check_python_plugins_do_not_affect_each_other());
1585 RUN_TEST(check_plugin_unload());
1586
1587 RUN_TEST(check_example_debugging("plugin@err"));
1588 RUN_TEST(check_example_debugging("plugin@info"));
1589 RUN_TEST(check_example_debugging("load@diag"));
1590 RUN_TEST(check_example_debugging("sudo_cb@info"));
1591 RUN_TEST(check_example_debugging("c_calls@diag"));
1592 RUN_TEST(check_example_debugging("c_calls@info"));
1593 RUN_TEST(check_example_debugging("py_calls@diag"));
1594 RUN_TEST(check_example_debugging("py_calls@info"));
1595 RUN_TEST(check_example_debugging("plugin@err"));
1596 RUN_TEST(check_plugin_unload());
1597
1598 return errors;
1599 }
1600