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