Imported Upstream version 1.8.7
[debian/sudo] / src / sudo.c
1 /*
2  * Copyright (c) 2009-2013 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 #ifdef __TANDEM
18 # include <floss.h>
19 #endif
20
21 #include <config.h>
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <sys/socket.h>
27 #include <sys/time.h>
28 #include <sys/resource.h>
29 #include <stdio.h>
30 #ifdef STDC_HEADERS
31 # include <stdlib.h>
32 # include <stddef.h>
33 #else
34 # ifdef HAVE_STDLIB_H
35 #  include <stdlib.h>
36 # endif
37 #endif /* STDC_HEADERS */
38 #ifdef HAVE_STRING_H
39 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
40 #  include <memory.h>
41 # endif
42 # include <string.h>
43 #endif /* HAVE_STRING_H */
44 #ifdef HAVE_STRINGS_H
45 # include <strings.h>
46 #endif /* HAVE_STRINGS_H */
47 #ifdef HAVE_UNISTD_H
48 # include <unistd.h>
49 #endif /* HAVE_UNISTD_H */
50 #include <ctype.h>
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <limits.h>
54 #include <signal.h>
55 #include <grp.h>
56 #include <pwd.h>
57 #if TIME_WITH_SYS_TIME
58 # include <time.h>
59 #endif
60 #ifdef HAVE_LOGIN_CAP_H
61 # include <login_cap.h>
62 # ifndef LOGIN_SETENV
63 #  define LOGIN_SETENV  0
64 # endif
65 #endif
66 #ifdef HAVE_PROJECT_H
67 # include <project.h>
68 # include <sys/task.h>
69 #endif
70 #ifdef HAVE_SELINUX
71 # include <selinux/selinux.h>
72 #endif
73 #ifdef HAVE_SETAUTHDB
74 # include <usersec.h>
75 #endif /* HAVE_SETAUTHDB */
76 #if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS)
77 # ifdef __hpux
78 #  undef MAXINT
79 #  include <hpsecurity.h>
80 # else
81 #  include <sys/security.h>
82 # endif /* __hpux */
83 # include <prot.h>
84 #endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */
85
86 #include "sudo.h"
87 #include "sudo_plugin.h"
88 #include "sudo_plugin_int.h"
89 #include "sudo_usage.h"
90
91 /*
92  * Local variables
93  */
94 struct plugin_container policy_plugin;
95 struct plugin_container_list io_plugins;
96 struct user_details user_details;
97 const char *list_user, *runas_user, *runas_group; /* extern for parse_args.c */
98 static struct command_details command_details;
99 static int sudo_mode;
100
101 /*
102  * Local functions
103  */
104 static void fix_fds(void);
105 static void disable_coredumps(void);
106 static void sudo_check_suid(const char *path);
107 static char **get_user_info(struct user_details *);
108 static void command_info_to_details(char * const info[],
109     struct command_details *details);
110
111 /* Policy plugin convenience functions. */
112 static int policy_open(struct plugin_container *plugin, char * const settings[],
113     char * const user_info[], char * const user_env[]);
114 static void policy_close(struct plugin_container *plugin, int exit_status,
115     int error);
116 static int policy_show_version(struct plugin_container *plugin, int verbose);
117 static int policy_check(struct plugin_container *plugin, int argc,
118     char * const argv[], char *env_add[], char **command_info[],
119     char **argv_out[], char **user_env_out[]);
120 static int policy_list(struct plugin_container *plugin, int argc,
121     char * const argv[], int verbose, const char *list_user);
122 static int policy_validate(struct plugin_container *plugin);
123 static void policy_invalidate(struct plugin_container *plugin, int remove);
124
125 /* I/O log plugin convenience functions. */
126 static int iolog_open(struct plugin_container *plugin, char * const settings[],
127     char * const user_info[], char * const command_details[],
128     int argc, char * const argv[], char * const user_env[]);
129 static void iolog_close(struct plugin_container *plugin, int exit_status,
130     int error);
131 static int iolog_show_version(struct plugin_container *plugin, int verbose);
132 static void iolog_unlink(struct plugin_container *plugin);
133
134 #ifdef RLIMIT_CORE
135 static struct rlimit corelimit;
136 #endif /* RLIMIT_CORE */
137 #if defined(__linux__)
138 static struct rlimit nproclimit;
139 #endif
140
141 __dso_public int main(int argc, char *argv[], char *envp[]);
142
143 int
144 main(int argc, char *argv[], char *envp[])
145 {
146     int nargc, ok, exitcode = 0;
147     char **nargv, **settings, **env_add;
148     char **user_info, **command_info, **argv_out, **user_env_out;
149     struct plugin_container *plugin, *next;
150     sigset_t mask;
151     debug_decl(main, SUDO_DEBUG_MAIN)
152
153     os_init(argc, argv, envp);
154
155     setlocale(LC_ALL, "");
156     bindtextdomain(PACKAGE_NAME, LOCALEDIR);
157     textdomain(PACKAGE_NAME);
158
159 #ifdef HAVE_TZSET
160     (void) tzset();
161 #endif /* HAVE_TZSET */
162
163     /* Must be done before we do any password lookups */
164 #if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS)
165     (void) set_auth_parameters(argc, argv);
166 # ifdef HAVE_INITPRIVS
167     initprivs();
168 # endif
169 #endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */
170
171     /* Make sure we are setuid root. */
172     sudo_check_suid(argv[0]);
173
174     /* Reset signal mask, save signal state and make sure fds 0-2 are open. */
175     (void) sigemptyset(&mask);
176     (void) sigprocmask(SIG_SETMASK, &mask, NULL);
177     save_signals();
178     fix_fds();
179
180     /* Read sudo.conf. */
181     sudo_conf_read(NULL);
182
183     /* Fill in user_info with user name, uid, cwd, etc. */
184     memset(&user_details, 0, sizeof(user_details));
185     user_info = get_user_info(&user_details);
186
187     /* Disable core dumps if not enabled in sudo.conf. */
188     disable_coredumps();
189
190     /* Parse command line arguments. */
191     sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add);
192     sudo_debug_printf(SUDO_DEBUG_DEBUG, "sudo_mode %d", sudo_mode);
193
194     /* Print sudo version early, in case of plugin init failure. */
195     if (ISSET(sudo_mode, MODE_VERSION)) {
196         printf(_("Sudo version %s\n"), PACKAGE_VERSION);
197         if (user_details.uid == ROOT_UID)
198             (void) printf(_("Configure options: %s\n"), CONFIGURE_ARGS);
199     }
200
201     /* Load plugins. */
202     if (!sudo_load_plugins(&policy_plugin, &io_plugins))
203         fatalx(_("fatal error, unable to load plugins"));
204
205     /* Open policy plugin. */
206     ok = policy_open(&policy_plugin, settings, user_info, envp);
207     if (ok != 1) {
208         if (ok == -2)
209             usage(1);
210         else
211             fatalx(_("unable to initialize policy plugin"));
212     }
213
214     init_signals();
215
216     switch (sudo_mode & MODE_MASK) {
217         case MODE_VERSION:
218             policy_show_version(&policy_plugin, !user_details.uid);
219             tq_foreach_fwd(&io_plugins, plugin) {
220                 ok = iolog_open(plugin, settings, user_info, NULL,
221                     nargc, nargv, envp);
222                 if (ok != -1)
223                     iolog_show_version(plugin, !user_details.uid);
224             }
225             break;
226         case MODE_VALIDATE:
227         case MODE_VALIDATE|MODE_INVALIDATE:
228             ok = policy_validate(&policy_plugin);
229             exit(ok != 1);
230         case MODE_KILL:
231         case MODE_INVALIDATE:
232             policy_invalidate(&policy_plugin, sudo_mode == MODE_KILL);
233             exit(0);
234             break;
235         case MODE_CHECK:
236         case MODE_CHECK|MODE_INVALIDATE:
237         case MODE_LIST:
238         case MODE_LIST|MODE_INVALIDATE:
239             ok = policy_list(&policy_plugin, nargc, nargv,
240                 ISSET(sudo_mode, MODE_LONG_LIST), list_user);
241             exit(ok != 1);
242         case MODE_EDIT:
243         case MODE_RUN:
244             ok = policy_check(&policy_plugin, nargc, nargv, env_add,
245                 &command_info, &argv_out, &user_env_out);
246             sudo_debug_printf(SUDO_DEBUG_INFO, "policy plugin returns %d", ok);
247             if (ok != 1) {
248                 if (ok == -2)
249                     usage(1);
250                 exit(1); /* plugin printed error message */
251             }
252             /* Open I/O plugins once policy plugin succeeds. */
253             for (plugin = io_plugins.first; plugin != NULL; plugin = next) {
254                 next = plugin->next;
255                 ok = iolog_open(plugin, settings, user_info,
256                     command_info, nargc, nargv, envp);
257                 switch (ok) {
258                 case 1:
259                     break;
260                 case 0:
261                     /* I/O plugin asked to be disabled, remove and free. */
262                     iolog_unlink(plugin);
263                     break;
264                 case -2:
265                     usage(1);
266                     break;
267                 default:
268                     fatalx(_("error initializing I/O plugin %s"),
269                         plugin->name);
270                 }
271             }
272             /* Setup command details and run command/edit. */
273             command_info_to_details(command_info, &command_details);
274             command_details.argv = argv_out;
275             command_details.envp = user_env_out;
276             if (ISSET(sudo_mode, MODE_BACKGROUND))
277                 SET(command_details.flags, CD_BACKGROUND);
278             /* Become full root (not just setuid) so user cannot kill us. */
279             if (setuid(ROOT_UID) == -1)
280                 warning("setuid(%d)", ROOT_UID);
281             /* Restore coredumpsize resource limit before running. */
282 #ifdef RLIMIT_CORE
283             if (sudo_conf_disable_coredump())
284                 (void) setrlimit(RLIMIT_CORE, &corelimit);
285 #endif /* RLIMIT_CORE */
286             if (ISSET(command_details.flags, CD_SUDOEDIT)) {
287                 exitcode = sudo_edit(&command_details);
288             } else {
289                 exitcode = run_command(&command_details);
290             }
291             /* The close method was called by sudo_edit/run_command. */
292             break;
293         default:
294             fatalx(_("unexpected sudo mode 0x%x"), sudo_mode);
295     }
296     sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode);                
297     exit(exitcode);
298 }
299
300 int
301 os_init_common(int argc, char *argv[], char *envp[])
302 {
303 #if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME)
304     if (argc > 0)
305         setprogname(argv[0]);
306 #endif
307     return 0;
308 }
309
310 /*
311  * Ensure that stdin, stdout and stderr are open; set to /dev/null if not.
312  * Some operating systems do this automatically in the kernel or libc.
313  */
314 static void
315 fix_fds(void)
316 {
317     int miss[3], devnull = -1;
318     debug_decl(fix_fds, SUDO_DEBUG_UTIL)
319
320     /*
321      * stdin, stdout and stderr must be open; set them to /dev/null
322      * if they are closed.
323      */
324     miss[STDIN_FILENO] = fcntl(STDIN_FILENO, F_GETFL, 0) == -1;
325     miss[STDOUT_FILENO] = fcntl(STDOUT_FILENO, F_GETFL, 0) == -1;
326     miss[STDERR_FILENO] = fcntl(STDERR_FILENO, F_GETFL, 0) == -1;
327     if (miss[STDIN_FILENO] || miss[STDOUT_FILENO] || miss[STDERR_FILENO]) {
328         if ((devnull = open(_PATH_DEVNULL, O_RDWR, 0644)) == -1)
329             fatal(_("unable to open %s"), _PATH_DEVNULL);
330         if (miss[STDIN_FILENO] && dup2(devnull, STDIN_FILENO) == -1)
331             fatal("dup2");
332         if (miss[STDOUT_FILENO] && dup2(devnull, STDOUT_FILENO) == -1)
333             fatal("dup2");
334         if (miss[STDERR_FILENO] && dup2(devnull, STDERR_FILENO) == -1)
335             fatal("dup2");
336         if (devnull > STDERR_FILENO)
337             close(devnull);
338     }
339     debug_return;
340 }
341
342 /*
343  * Allocate space for groups and fill in using getgrouplist()
344  * for when we cannot (or don't want to) use getgroups().
345  */
346 static int
347 fill_group_list(struct user_details *ud, int system_maxgroups)
348 {
349     int tries, rval = -1;
350     debug_decl(fill_group_list, SUDO_DEBUG_UTIL)
351
352     /*
353      * If user specified a max number of groups, use it, otherwise keep
354      * trying getgrouplist() until we have enough room in the array.
355      */
356     ud->ngroups = sudo_conf_max_groups();
357     if (ud->ngroups != -1) {
358         ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
359         /* No error on insufficient space if user specified max_groups. */
360         (void)getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups);
361         rval = 0;
362     } else {
363         /*
364          * It is possible to belong to more groups in the group database
365          * than NGROUPS_MAX.  We start off with NGROUPS_MAX * 4 entries
366          * and double this as needed.
367          */
368         ud->groups = NULL;
369         ud->ngroups = system_maxgroups << 1;
370         for (tries = 0; tries < 10 && rval == -1; tries++) {
371             ud->ngroups <<= 1;
372             efree(ud->groups);
373             ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
374             rval = getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups);
375         }
376     }
377     debug_return_int(rval);
378 }
379
380 static char *
381 get_user_groups(struct user_details *ud)
382 {
383     char *cp, *gid_list = NULL;
384     size_t glsize;
385     int i, len, maxgroups, group_source;
386     debug_decl(get_user_groups, SUDO_DEBUG_UTIL)
387
388 #if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX)
389     maxgroups = (int)sysconf(_SC_NGROUPS_MAX);
390     if (maxgroups < 0)
391 #endif
392         maxgroups = NGROUPS_MAX;
393
394     ud->groups = NULL;
395     group_source = sudo_conf_group_source();
396     if (group_source != GROUP_SOURCE_DYNAMIC) {
397         if ((ud->ngroups = getgroups(0, NULL)) > 0) {
398             /* Use groups from kernel if not too many or source is static. */
399             if (ud->ngroups < maxgroups || group_source == GROUP_SOURCE_STATIC) {
400                 ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
401                 if (getgroups(ud->ngroups, ud->groups) < 0) {
402                     efree(ud->groups);
403                     ud->groups = NULL;
404                 }
405             }
406         }
407     }
408     if (ud->groups == NULL) {
409         /*
410          * Query group database if kernel list is too small or disabled.
411          * Typically, this is because NFS can only support up to 16 groups.
412          */
413         if (fill_group_list(ud, maxgroups) == -1)
414             fatal(_("unable to get group vector"));
415     }
416
417     /*
418      * Format group list as a comma-separated string of gids.
419      */
420     glsize = sizeof("groups=") - 1 + (ud->ngroups * (MAX_UID_T_LEN + 1));
421     gid_list = emalloc(glsize);
422     memcpy(gid_list, "groups=", sizeof("groups=") - 1);
423     cp = gid_list + sizeof("groups=") - 1;
424     for (i = 0; i < ud->ngroups; i++) {
425         /* XXX - check rval */
426         len = snprintf(cp, glsize - (cp - gid_list), "%s%u",
427             i ? "," : "", (unsigned int)ud->groups[i]);
428         cp += len;
429     }
430     debug_return_str(gid_list);
431 }
432
433 /*
434  * Return user information as an array of name=value pairs.
435  * and fill in struct user_details (which shares the same strings).
436  */
437 static char **
438 get_user_info(struct user_details *ud)
439 {
440     char *cp, **user_info, cwd[PATH_MAX], host[HOST_NAME_MAX + 1];
441     struct passwd *pw;
442     int fd, i = 0;
443     debug_decl(get_user_info, SUDO_DEBUG_UTIL)
444
445     /* XXX - bound check number of entries */
446     user_info = emalloc2(32, sizeof(char *));
447
448     ud->pid = getpid();
449     ud->ppid = getppid();
450     ud->pgid = getpgid(0);
451     ud->tcpgid = (pid_t)-1;
452     fd = open(_PATH_TTY, O_RDWR|O_NOCTTY|O_NONBLOCK, 0);
453     if (fd != -1) {
454         ud->tcpgid = tcgetpgrp(fd);
455         close(fd);
456     }
457     ud->sid = getsid(0);
458
459     ud->uid = getuid();
460     ud->euid = geteuid();
461     ud->gid = getgid();
462     ud->egid = getegid();
463
464     pw = getpwuid(ud->uid);
465     if (pw == NULL)
466         fatalx(_("unknown uid %u: who are you?"), (unsigned int)ud->uid);
467
468     user_info[i] = fmt_string("user", pw->pw_name);
469     if (user_info[i] == NULL)
470         fatalx(NULL);
471     ud->username = user_info[i] + sizeof("user=") - 1;
472
473     /* Stash user's shell for use with the -s flag; don't pass to plugin. */
474     if ((ud->shell = getenv("SHELL")) == NULL || ud->shell[0] == '\0') {
475         ud->shell = pw->pw_shell[0] ? pw->pw_shell : _PATH_BSHELL;
476     }
477     ud->shell = estrdup(ud->shell);
478
479     easprintf(&user_info[++i], "pid=%d", (int)ud->pid);
480     easprintf(&user_info[++i], "ppid=%d", (int)ud->ppid);
481     easprintf(&user_info[++i], "pgid=%d", (int)ud->pgid);
482     easprintf(&user_info[++i], "tcpgid=%d", (int)ud->tcpgid);
483     easprintf(&user_info[++i], "sid=%d", (int)ud->sid);
484
485     easprintf(&user_info[++i], "uid=%u", (unsigned int)ud->uid);
486     easprintf(&user_info[++i], "euid=%u", (unsigned int)ud->euid);
487     easprintf(&user_info[++i], "gid=%u", (unsigned int)ud->gid);
488     easprintf(&user_info[++i], "egid=%u", (unsigned int)ud->egid);
489
490     if ((cp = get_user_groups(ud)) != NULL)
491         user_info[++i] = cp;
492
493     if (getcwd(cwd, sizeof(cwd)) != NULL) {
494         user_info[++i] = fmt_string("cwd", cwd);
495         if (user_info[i] == NULL)
496             fatalx(NULL);
497         ud->cwd = user_info[i] + sizeof("cwd=") - 1;
498     }
499
500     if ((cp = get_process_ttyname()) != NULL) {
501         user_info[++i] = fmt_string("tty", cp);
502         if (user_info[i] == NULL)
503             fatalx(NULL);
504         ud->tty = user_info[i] + sizeof("tty=") - 1;
505         efree(cp);
506     }
507
508     if (gethostname(host, sizeof(host)) == 0)
509         host[sizeof(host) - 1] = '\0';
510     else
511         strlcpy(host, "localhost", sizeof(host));
512     user_info[++i] = fmt_string("host", host);
513     if (user_info[i] == NULL)
514         fatalx(NULL);
515     ud->host = user_info[i] + sizeof("host=") - 1;
516
517     get_ttysize(&ud->ts_lines, &ud->ts_cols);
518     easprintf(&user_info[++i], "lines=%d", ud->ts_lines);
519     easprintf(&user_info[++i], "cols=%d", ud->ts_cols);
520
521     user_info[++i] = NULL;
522
523     debug_return_ptr(user_info);
524 }
525
526 /*
527  * Convert a command_info array into a command_details structure.
528  */
529 static void
530 command_info_to_details(char * const info[], struct command_details *details)
531 {
532     int i;
533     long lval;
534     unsigned long ulval;
535     char *cp, *ep;
536     debug_decl(command_info_to_details, SUDO_DEBUG_PCOMM)
537
538     memset(details, 0, sizeof(*details));
539     details->closefrom = -1;
540
541 #define SET_STRING(s, n) \
542     if (strncmp(s, info[i], sizeof(s) - 1) == 0 && info[i][sizeof(s) - 1]) { \
543         details->n = info[i] + sizeof(s) - 1; \
544         break; \
545     }
546
547     sudo_debug_printf(SUDO_DEBUG_INFO, "command info from plugin:");
548     for (i = 0; info[i] != NULL; i++) {
549         sudo_debug_printf(SUDO_DEBUG_INFO, "    %d: %s", i, info[i]);
550         switch (info[i][0]) {
551             case 'c':
552                 SET_STRING("chroot=", chroot)
553                 SET_STRING("command=", command)
554                 SET_STRING("cwd=", cwd)
555                 if (strncmp("closefrom=", info[i], sizeof("closefrom=") - 1) == 0) {
556                     cp = info[i] + sizeof("closefrom=") - 1;
557                     if (*cp == '\0')
558                         break;
559                     errno = 0;
560                     lval = strtol(cp, &ep, 0);
561                     if (*cp != '\0' && *ep == '\0' &&
562                         !(errno == ERANGE &&
563                         (lval == LONG_MAX || lval == LONG_MIN)) &&
564                         lval < INT_MAX && lval > INT_MIN) {
565                         details->closefrom = (int)lval;
566                     }
567                     break;
568                 }
569                 break;
570             case 'e':
571                 if (strncmp("exec_background=", info[i], sizeof("exec_background=") - 1) == 0) {
572                     if (atobool(info[i] + sizeof("exec_background=") - 1) == true)
573                         SET(details->flags, CD_EXEC_BG);
574                     break;
575                 }
576                 break;
577             case 'l':
578                 SET_STRING("login_class=", login_class)
579                 break;
580             case 'n':
581                 /* XXX - bounds check  -NZERO to NZERO (inclusive). */
582                 if (strncmp("nice=", info[i], sizeof("nice=") - 1) == 0) {
583                     cp = info[i] + sizeof("nice=") - 1;
584                     if (*cp == '\0')
585                         break;
586                     errno = 0;
587                     lval = strtol(cp, &ep, 0);
588                     if (*cp != '\0' && *ep == '\0' &&
589                         !(errno == ERANGE &&
590                         (lval == LONG_MAX || lval == LONG_MIN)) &&
591                         lval < INT_MAX && lval > INT_MIN) {
592                         details->priority = (int)lval;
593                         SET(details->flags, CD_SET_PRIORITY);
594                     }
595                     break;
596                 }
597                 if (strncmp("noexec=", info[i], sizeof("noexec=") - 1) == 0) {
598                     if (atobool(info[i] + sizeof("noexec=") - 1) == true)
599                         SET(details->flags, CD_NOEXEC);
600                     break;
601                 }
602                 break;
603             case 'p':
604                 if (strncmp("preserve_groups=", info[i], sizeof("preserve_groups=") - 1) == 0) {
605                     if (atobool(info[i] + sizeof("preserve_groups=") - 1) == true)
606                         SET(details->flags, CD_PRESERVE_GROUPS);
607                     break;
608                 }
609                 break;
610             case 'r':
611                 if (strncmp("runas_egid=", info[i], sizeof("runas_egid=") - 1) == 0) {
612                     cp = info[i] + sizeof("runas_egid=") - 1;
613                     if (*cp == '\0')
614                         break;
615                     errno = 0;
616                     ulval = strtoul(cp, &ep, 0);
617                     if (*cp != '\0' && *ep == '\0' &&
618                         (errno != ERANGE || ulval != ULONG_MAX)) {
619                         details->egid = (gid_t)ulval;
620                         SET(details->flags, CD_SET_EGID);
621                     }
622                     break;
623                 }
624                 if (strncmp("runas_euid=", info[i], sizeof("runas_euid=") - 1) == 0) {
625                     cp = info[i] + sizeof("runas_euid=") - 1;
626                     if (*cp == '\0')
627                         break;
628                     errno = 0;
629                     ulval = strtoul(cp, &ep, 0);
630                     if (*cp != '\0' && *ep == '\0' &&
631                         (errno != ERANGE || ulval != ULONG_MAX)) {
632                         details->euid = (uid_t)ulval;
633                         SET(details->flags, CD_SET_EUID);
634                     }
635                     break;
636                 }
637                 if (strncmp("runas_gid=", info[i], sizeof("runas_gid=") - 1) == 0) {
638                     cp = info[i] + sizeof("runas_gid=") - 1;
639                     if (*cp == '\0')
640                         break;
641                     errno = 0;
642                     ulval = strtoul(cp, &ep, 0);
643                     if (*cp != '\0' && *ep == '\0' &&
644                         (errno != ERANGE || ulval != ULONG_MAX)) {
645                         details->gid = (gid_t)ulval;
646                         SET(details->flags, CD_SET_GID);
647                     }
648                     break;
649                 }
650                 if (strncmp("runas_groups=", info[i], sizeof("runas_groups=") - 1) == 0) {
651                     int j;
652
653                     /* count groups, alloc and fill in */
654                     cp = info[i] + sizeof("runas_groups=") - 1;
655                     if (*cp == '\0')
656                         break;
657                     for (;;) {
658                         details->ngroups++;
659                         if ((cp = strchr(cp, ',')) == NULL)
660                             break;
661                         cp++;
662                     }
663                     if (details->ngroups != 0) {
664                         details->groups =
665                             emalloc2(details->ngroups, sizeof(GETGROUPS_T));
666                         cp = info[i] + sizeof("runas_groups=") - 1;
667                         for (j = 0; j < details->ngroups;) {
668                             errno = 0;
669                             ulval = strtoul(cp, &ep, 0);
670                             if (*cp == '\0' || (*ep != ',' && *ep != '\0') ||
671                                 (ulval == ULONG_MAX && errno == ERANGE)) {
672                                 break;
673                             }
674                             details->groups[j++] = (gid_t)ulval;
675                             cp = ep + 1;
676                         }
677                         details->ngroups = j;
678                     }
679                     break;
680                 }
681                 if (strncmp("runas_uid=", info[i], sizeof("runas_uid=") - 1) == 0) {
682                     cp = info[i] + sizeof("runas_uid=") - 1;
683                     if (*cp == '\0')
684                         break;
685                     errno = 0;
686                     ulval = strtoul(cp, &ep, 0);
687                     if (*cp != '\0' && *ep == '\0' &&
688                         (errno != ERANGE || ulval != ULONG_MAX)) {
689                         details->uid = (uid_t)ulval;
690                         SET(details->flags, CD_SET_UID);
691                     }
692                     break;
693                 }
694 #ifdef HAVE_PRIV_SET
695                 if (strncmp("runas_privs=", info[i], sizeof("runas_privs=") - 1) == 0) {
696                     const char *endp;
697                     cp = info[i] + sizeof("runas_privs=") - 1;
698                     if (*cp == '\0')
699                         break;
700                     errno = 0;
701                     details->privs = priv_str_to_set(cp, ",", &endp);
702                     if (details->privs == NULL)
703                             warning("invalid runas_privs %s", endp);
704                 }
705                 if (strncmp("runas_limitprivs=", info[i], sizeof("runas_limitprivs=") - 1) == 0) {
706                     const char *endp;
707                     cp = info[i] + sizeof("runas_limitprivs=") - 1;
708                     if (*cp == '\0')
709                         break;
710                     errno = 0;
711                     details->limitprivs = priv_str_to_set(cp, ",", &endp);
712                     if (details->limitprivs == NULL)
713                             warning("invalid runas_limitprivs %s", endp);
714                 }
715 #endif /* HAVE_PRIV_SET */
716                 break;
717             case 's':
718                 SET_STRING("selinux_role=", selinux_role)
719                 SET_STRING("selinux_type=", selinux_type)
720                 if (strncmp("set_utmp=", info[i], sizeof("set_utmp=") - 1) == 0) {
721                     if (atobool(info[i] + sizeof("set_utmp=") - 1) == true)
722                         SET(details->flags, CD_SET_UTMP);
723                     break;
724                 }
725                 if (strncmp("sudoedit=", info[i], sizeof("sudoedit=") - 1) == 0) {
726                     if (atobool(info[i] + sizeof("sudoedit=") - 1) == true)
727                         SET(details->flags, CD_SUDOEDIT);
728                     break;
729                 }
730                 break;
731             case 't':
732                 if (strncmp("timeout=", info[i], sizeof("timeout=") - 1) == 0) {
733                     cp = info[i] + sizeof("timeout=") - 1;
734                     if (*cp == '\0')
735                         break;
736                     errno = 0;
737                     lval = strtol(cp, &ep, 0);
738                     if (*cp != '\0' && *ep == '\0' &&
739                         !(errno == ERANGE &&
740                         (lval == LONG_MAX || lval == LONG_MIN)) &&
741                         lval <= INT_MAX && lval >= 0) {
742                         details->timeout = (int)lval;
743                         SET(details->flags, CD_SET_TIMEOUT);
744                     }
745                     break;
746                 }
747                 break;
748             case 'u':
749                 if (strncmp("umask=", info[i], sizeof("umask=") - 1) == 0) {
750                     cp = info[i] + sizeof("umask=") - 1;
751                     if (*cp == '\0')
752                         break;
753                     errno = 0;
754                     ulval = strtoul(cp, &ep, 8);
755                     if (*cp != '\0' && *ep == '\0' &&
756                         (errno != ERANGE || ulval != ULONG_MAX)) {
757                         details->umask = (uid_t)ulval;
758                         SET(details->flags, CD_SET_UMASK);
759                     }
760                     break;
761                 }
762                 if (strncmp("use_pty=", info[i], sizeof("use_pty=") - 1) == 0) {
763                     if (atobool(info[i] + sizeof("use_pty=") - 1) == true)
764                         SET(details->flags, CD_USE_PTY);
765                     break;
766                 }
767                 SET_STRING("utmp_user=", utmp_user)
768                 break;
769         }
770     }
771
772     if (!ISSET(details->flags, CD_SET_EUID))
773         details->euid = details->uid;
774
775 #ifdef HAVE_SETAUTHDB
776     aix_setauthdb(IDtouser(details->euid));
777 #endif
778     details->pw = getpwuid(details->euid);
779     if (details->pw != NULL && (details->pw = pw_dup(details->pw)) == NULL)
780         fatalx(NULL);
781 #ifdef HAVE_SETAUTHDB
782     aix_restoreauthdb();
783 #endif
784
785 #ifdef HAVE_SELINUX
786     if (details->selinux_role != NULL && is_selinux_enabled() > 0)
787         SET(details->flags, CD_RBAC_ENABLED);
788 #endif
789     debug_return;
790 }
791
792 static void
793 sudo_check_suid(const char *path)
794 {
795     struct stat sb;
796     debug_decl(sudo_check_suid, SUDO_DEBUG_PCOMM)
797
798     if (geteuid() != 0) {
799         if (strchr(path, '/') != NULL && stat(path, &sb) == 0) {
800             /* Try to determine why sudo was not running as root. */
801             if (sb.st_uid != ROOT_UID || !ISSET(sb.st_mode, S_ISUID)) {
802                 fatalx(
803                     _("%s must be owned by uid %d and have the setuid bit set"),
804                     path, ROOT_UID);
805             } else {
806                 fatalx(_("effective uid is not %d, is %s on a file system "
807                     "with the 'nosuid' option set or an NFS file system without"
808                     " root privileges?"), ROOT_UID, path);
809             }
810         } else {
811             fatalx(
812                 _("effective uid is not %d, is sudo installed setuid root?"),
813                 ROOT_UID);
814         }
815     }
816     debug_return;
817 }
818
819 /*
820  * Disable core dumps to avoid dropping a core with user password in it.
821  * We will reset this limit before executing the command.
822  * Not all operating systems disable core dumps for setuid processes.
823  */
824 static void
825 disable_coredumps(void)
826 {
827 #if defined(__linux__) || defined(RLIMIT_CORE)
828     struct rlimit rl;
829 #endif
830     debug_decl(disable_coredumps, SUDO_DEBUG_UTIL)
831
832 #if defined(__linux__)
833     /*
834      * Unlimit the number of processes since Linux's setuid() will
835      * apply resource limits when changing uid and return EAGAIN if
836      * nproc would be violated by the uid switch.
837      */
838     (void) getrlimit(RLIMIT_NPROC, &nproclimit);
839     rl.rlim_cur = rl.rlim_max = RLIM_INFINITY;
840     if (setrlimit(RLIMIT_NPROC, &rl)) {
841         memcpy(&rl, &nproclimit, sizeof(struct rlimit));
842         rl.rlim_cur = rl.rlim_max;
843         (void)setrlimit(RLIMIT_NPROC, &rl);
844     }
845 #endif /* __linux__ */
846 #ifdef RLIMIT_CORE
847     /*
848      * Turn off core dumps?
849      */
850     if (sudo_conf_disable_coredump()) {
851         (void) getrlimit(RLIMIT_CORE, &corelimit);
852         memcpy(&rl, &corelimit, sizeof(struct rlimit));
853         rl.rlim_cur = 0;
854         (void) setrlimit(RLIMIT_CORE, &rl);
855     }
856 #endif /* RLIMIT_CORE */
857     debug_return;
858 }
859
860 /*
861  * Setup the execution environment immediately prior to the call to execve()
862  * Returns true on success and false on failure.
863  */
864 bool
865 exec_setup(struct command_details *details, const char *ptyname, int ptyfd)
866 {
867     bool rval = false;
868     debug_decl(exec_setup, SUDO_DEBUG_EXEC)
869
870 #ifdef HAVE_SELINUX
871     if (ISSET(details->flags, CD_RBAC_ENABLED)) {
872         if (selinux_setup(details->selinux_role, details->selinux_type,
873             ptyname ? ptyname : user_details.tty, ptyfd) == -1)
874             goto done;
875     }
876 #endif
877
878     if (details->pw != NULL) {
879 #ifdef HAVE_PROJECT_H
880         set_project(details->pw);
881 #endif
882 #ifdef HAVE_PRIV_SET
883         if (details->privs != NULL) {
884             if (setppriv(PRIV_SET, PRIV_INHERITABLE, details->privs) != 0) {
885                 warning("unable to set privileges");
886                 goto done;
887             }
888         }
889         if (details->limitprivs != NULL) {
890             if (setppriv(PRIV_SET, PRIV_LIMIT, details->limitprivs) != 0) {
891                 warning("unable to set limit privileges");
892                 goto done;
893             }
894         } else if (details->privs != NULL) {
895             if (setppriv(PRIV_SET, PRIV_LIMIT, details->privs) != 0) {
896                 warning("unable to set limit privileges");
897                 goto done;
898             }
899         }
900 #endif /* HAVE_PRIV_SET */
901
902 #ifdef HAVE_GETUSERATTR
903         aix_prep_user(details->pw->pw_name, ptyname ? ptyname : user_details.tty);
904 #endif
905 #ifdef HAVE_LOGIN_CAP_H
906         if (details->login_class) {
907             int flags;
908             login_cap_t *lc;
909
910             /*
911              * We only use setusercontext() to set the nice value and rlimits
912              * unless this is a login shell (sudo -i).
913              */
914             lc = login_getclass((char *)details->login_class);
915             if (!lc) {
916                 warningx(_("unknown login class %s"), details->login_class);
917                 errno = ENOENT;
918                 goto done;
919             }
920             if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) {
921                 /* Set everything except user, group and login name. */
922                 flags = LOGIN_SETALL;
923                 CLR(flags, LOGIN_SETGROUP|LOGIN_SETLOGIN|LOGIN_SETUSER|LOGIN_SETENV|LOGIN_SETPATH);
924                 CLR(details->flags, CD_SET_UMASK); /* LOGIN_UMASK instead */
925             } else {
926                 flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
927             }
928             if (setusercontext(lc, details->pw, details->pw->pw_uid, flags)) {
929                 if (details->pw->pw_uid != ROOT_UID) {
930                     warning(_("unable to set user context"));
931                     goto done;
932                 } else
933                     warning(_("unable to set user context"));
934             }
935         }
936 #endif /* HAVE_LOGIN_CAP_H */
937     }
938
939     /*
940      * Set groups, including supplementary group vector.
941      */
942     if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) {
943         if (details->ngroups >= 0) {
944             if (sudo_setgroups(details->ngroups, details->groups) < 0) {
945                 warning(_("unable to set supplementary group IDs"));
946                 goto done;
947             }
948         }
949     }
950 #ifdef HAVE_SETEUID
951     if (ISSET(details->flags, CD_SET_EGID) && setegid(details->egid)) {
952         warning(_("unable to set effective gid to runas gid %u"),
953             (unsigned int)details->egid);
954         goto done;
955     }
956 #endif
957     if (ISSET(details->flags, CD_SET_GID) && setgid(details->gid)) {
958         warning(_("unable to set gid to runas gid %u"),
959             (unsigned int)details->gid);
960         goto done;
961     }
962
963     if (ISSET(details->flags, CD_SET_PRIORITY)) {
964         if (setpriority(PRIO_PROCESS, 0, details->priority) != 0) {
965             warning(_("unable to set process priority"));
966             goto done;
967         }
968     }
969     if (ISSET(details->flags, CD_SET_UMASK))
970         (void) umask(details->umask);
971     if (details->chroot) {
972         if (chroot(details->chroot) != 0 || chdir("/") != 0) {
973             warning(_("unable to change root to %s"), details->chroot);
974             goto done;
975         }
976     }
977
978 #ifdef HAVE_SETRESUID
979     if (setresuid(details->uid, details->euid, details->euid) != 0) {
980         warning(_("unable to change to runas uid (%u, %u)"), details->uid,
981             details->euid);
982         goto done;
983     }
984 #elif HAVE_SETREUID
985     if (setreuid(details->uid, details->euid) != 0) {
986         warning(_("unable to change to runas uid (%u, %u)"),
987             (unsigned int)details->uid, (unsigned int)details->euid);
988         goto done;
989     }
990 #else
991     if (seteuid(details->euid) != 0 || setuid(details->euid) != 0) {
992         warning(_("unable to change to runas uid (%u, %u)"), details->uid,
993             details->euid);
994         goto done;
995     }
996 #endif /* !HAVE_SETRESUID && !HAVE_SETREUID */
997
998     /*
999      * Only change cwd if we have chroot()ed or the policy modules
1000      * specifies a different cwd.  Must be done after uid change.
1001      */
1002     if (details->cwd) {
1003         if (details->chroot || strcmp(details->cwd, user_details.cwd) != 0) {
1004             /* Note: cwd is relative to the new root, if any. */
1005             if (chdir(details->cwd) != 0) {
1006                 warning(_("unable to change directory to %s"), details->cwd);
1007                 goto done;
1008             }
1009         }
1010     }
1011
1012     /*
1013      * SuSE Enterprise Linux uses RLIMIT_NPROC and _SC_CHILD_MAX
1014      * interchangably.  This causes problems when setting RLIMIT_NPROC
1015      * to RLIM_INFINITY due to a bug in bash where bash tries to honor
1016      * the value of _SC_CHILD_MAX but treats a value of -1 as an error,
1017      * and uses a default value of 32 instead.
1018      *
1019      * To work around this problem, we restore the nproc resource limit
1020      * if sysconf(_SC_CHILD_MAX) is negative.  In most cases, pam_limits
1021      * will set RLIMIT_NPROC for us.
1022      *
1023      * We must do this *after* the uid change to avoid potential EAGAIN
1024      * from setuid().
1025      */
1026 #if defined(__linux__) && defined(_SC_CHILD_MAX)
1027     {
1028         struct rlimit rl;
1029         long l;
1030         errno = 0;
1031         l = sysconf(_SC_CHILD_MAX);
1032         if (l == -1 && errno == 0 && getrlimit(RLIMIT_NPROC, &rl) == 0) {
1033             if (rl.rlim_cur == RLIM_INFINITY && rl.rlim_max == RLIM_INFINITY)
1034                 (void) setrlimit(RLIMIT_NPROC, &nproclimit);
1035         }
1036     }
1037 #endif
1038
1039     rval = true;
1040
1041 done:
1042     debug_return_bool(rval);
1043 }
1044
1045 /*
1046  * Run the command and wait for it to complete.
1047  */
1048 int
1049 run_command(struct command_details *details)
1050 {
1051     struct plugin_container *plugin;
1052     struct command_status cstat;
1053     int exitcode = 1;
1054     debug_decl(run_command, SUDO_DEBUG_EXEC)
1055
1056     cstat.type = CMD_INVALID;
1057     cstat.val = 0;
1058
1059     sudo_execute(details, &cstat);
1060
1061     switch (cstat.type) {
1062     case CMD_ERRNO:
1063         /* exec_setup() or execve() returned an error. */
1064         sudo_debug_printf(SUDO_DEBUG_DEBUG,
1065             "calling policy close with errno %d", cstat.val);
1066         policy_close(&policy_plugin, 0, cstat.val);
1067         tq_foreach_fwd(&io_plugins, plugin) {
1068             sudo_debug_printf(SUDO_DEBUG_DEBUG,
1069                 "calling I/O close with errno %d", cstat.val);
1070             iolog_close(plugin, 0, cstat.val);
1071         }
1072         exitcode = 1;
1073         break;
1074     case CMD_WSTATUS:
1075         /* Command ran, exited or was killed. */
1076         sudo_debug_printf(SUDO_DEBUG_DEBUG,
1077             "calling policy close with wait status %d", cstat.val);
1078         policy_close(&policy_plugin, cstat.val, 0);
1079         tq_foreach_fwd(&io_plugins, plugin) {
1080             sudo_debug_printf(SUDO_DEBUG_DEBUG,
1081                 "calling I/O close with wait status %d", cstat.val);
1082             iolog_close(plugin, cstat.val, 0);
1083         }
1084         if (WIFEXITED(cstat.val))
1085             exitcode = WEXITSTATUS(cstat.val);
1086         else if (WIFSIGNALED(cstat.val))
1087             exitcode = WTERMSIG(cstat.val) | 128;
1088         break;
1089     default:
1090         warningx(_("unexpected child termination condition: %d"), cstat.type);
1091         break;
1092     }
1093     debug_return_int(exitcode);
1094 }
1095
1096 static int
1097 policy_open(struct plugin_container *plugin, char * const settings[],
1098     char * const user_info[], char * const user_env[])
1099 {
1100     int rval;
1101     debug_decl(policy_open, SUDO_DEBUG_PCOMM)
1102
1103     /*
1104      * Backwards compatibility for older API versions
1105      */
1106     switch (plugin->u.generic->version) {
1107     case SUDO_API_MKVERSION(1, 0):
1108     case SUDO_API_MKVERSION(1, 1):
1109         rval = plugin->u.policy_1_0->open(plugin->u.io_1_0->version,
1110             sudo_conversation, _sudo_printf, settings, user_info, user_env);
1111         break;
1112     default:
1113         rval = plugin->u.policy->open(SUDO_API_VERSION, sudo_conversation,
1114             _sudo_printf, settings, user_info, user_env, plugin->options);
1115     }
1116
1117     debug_return_bool(rval);
1118 }
1119
1120 static void
1121 policy_close(struct plugin_container *plugin, int exit_status, int error)
1122 {
1123     debug_decl(policy_close, SUDO_DEBUG_PCOMM)
1124     if (plugin->u.policy->close != NULL)
1125         plugin->u.policy->close(exit_status, error);
1126     else
1127         warning(_("unable to execute %s"), command_details.command);
1128     debug_return;
1129 }
1130
1131 static int
1132 policy_show_version(struct plugin_container *plugin, int verbose)
1133 {
1134     debug_decl(policy_show_version, SUDO_DEBUG_PCOMM)
1135     if (plugin->u.policy->show_version == NULL)
1136         debug_return_bool(true);
1137     debug_return_bool(plugin->u.policy->show_version(verbose));
1138 }
1139
1140 static int
1141 policy_check(struct plugin_container *plugin, int argc, char * const argv[],
1142     char *env_add[], char **command_info[], char **argv_out[],
1143     char **user_env_out[])
1144 {
1145     debug_decl(policy_check, SUDO_DEBUG_PCOMM)
1146     if (plugin->u.policy->check_policy == NULL) {
1147         fatalx(_("policy plugin %s is missing the `check_policy' method"),
1148             plugin->name);
1149     }
1150     debug_return_bool(plugin->u.policy->check_policy(argc, argv, env_add,
1151         command_info, argv_out, user_env_out));
1152 }
1153
1154 static int
1155 policy_list(struct plugin_container *plugin, int argc, char * const argv[],
1156     int verbose, const char *list_user)
1157 {
1158     debug_decl(policy_list, SUDO_DEBUG_PCOMM)
1159     if (plugin->u.policy->list == NULL) {
1160         warningx(_("policy plugin %s does not support listing privileges"),
1161             plugin->name);
1162         debug_return_bool(false);
1163     }
1164     debug_return_bool(plugin->u.policy->list(argc, argv, verbose, list_user));
1165 }
1166
1167 static int
1168 policy_validate(struct plugin_container *plugin)
1169 {
1170     debug_decl(policy_validate, SUDO_DEBUG_PCOMM)
1171     if (plugin->u.policy->validate == NULL) {
1172         warningx(_("policy plugin %s does not support the -v option"),
1173             plugin->name);
1174         debug_return_bool(false);
1175     }
1176     debug_return_bool(plugin->u.policy->validate());
1177 }
1178
1179 static void
1180 policy_invalidate(struct plugin_container *plugin, int remove)
1181 {
1182     debug_decl(policy_invalidate, SUDO_DEBUG_PCOMM)
1183     if (plugin->u.policy->invalidate == NULL) {
1184         fatalx(_("policy plugin %s does not support the -k/-K options"),
1185             plugin->name);
1186     }
1187     plugin->u.policy->invalidate(remove);
1188     debug_return;
1189 }
1190
1191 int
1192 policy_init_session(struct command_details *details)
1193 {
1194     int rval = true;
1195     debug_decl(policy_init_session, SUDO_DEBUG_PCOMM)
1196
1197     if (policy_plugin.u.policy->init_session) {
1198         /*
1199          * Backwards compatibility for older API versions
1200          */
1201         switch (policy_plugin.u.generic->version) {
1202         case SUDO_API_MKVERSION(1, 0):
1203         case SUDO_API_MKVERSION(1, 1):
1204             rval = policy_plugin.u.policy_1_0->init_session(details->pw);
1205             break;
1206         default:
1207             rval = policy_plugin.u.policy->init_session(details->pw,
1208                 &details->envp);
1209         }
1210     }
1211     debug_return_bool(rval);
1212 }
1213
1214 static int
1215 iolog_open(struct plugin_container *plugin, char * const settings[],
1216     char * const user_info[], char * const command_info[],
1217     int argc, char * const argv[], char * const user_env[])
1218 {
1219     int rval;
1220     debug_decl(iolog_open, SUDO_DEBUG_PCOMM)
1221
1222     /*
1223      * Backwards compatibility for older API versions
1224      */
1225     switch (plugin->u.generic->version) {
1226     case SUDO_API_MKVERSION(1, 0):
1227         rval = plugin->u.io_1_0->open(plugin->u.io_1_0->version,
1228             sudo_conversation, _sudo_printf, settings, user_info, argc, argv,
1229             user_env);
1230         break;
1231     case SUDO_API_MKVERSION(1, 1):
1232         rval = plugin->u.io_1_1->open(plugin->u.io_1_1->version,
1233             sudo_conversation, _sudo_printf, settings, user_info,
1234             command_info, argc, argv, user_env);
1235         break;
1236     default:
1237         rval = plugin->u.io->open(SUDO_API_VERSION, sudo_conversation,
1238             _sudo_printf, settings, user_info, command_info,
1239             argc, argv, user_env, plugin->options);
1240     }
1241     debug_return_bool(rval);
1242 }
1243
1244 static void
1245 iolog_close(struct plugin_container *plugin, int exit_status, int error)
1246 {
1247     debug_decl(iolog_close, SUDO_DEBUG_PCOMM)
1248     if (plugin->u.io->close != NULL)
1249         plugin->u.io->close(exit_status, error);
1250     debug_return;
1251 }
1252
1253 static int
1254 iolog_show_version(struct plugin_container *plugin, int verbose)
1255 {
1256     debug_decl(iolog_show_version, SUDO_DEBUG_PCOMM)
1257     if (plugin->u.io->show_version == NULL)
1258         debug_return_bool(true);
1259     debug_return_bool(plugin->u.io->show_version(verbose));
1260 }
1261
1262 /*
1263  * Remove the specified I/O logging plugin from the io_plugins list.
1264  * Deregisters any hooks before unlinking, then frees the container.
1265  */
1266 static void
1267 iolog_unlink(struct plugin_container *plugin)
1268 {
1269     debug_decl(iolog_unlink, SUDO_DEBUG_PCOMM)
1270
1271     /* Deregister hooks, if any. */
1272     if (plugin->u.io->version >= SUDO_API_MKVERSION(1, 2)) {
1273         if (plugin->u.io->deregister_hooks != NULL)
1274             plugin->u.io->deregister_hooks(SUDO_HOOK_VERSION,
1275                 deregister_hook);
1276     }
1277     /* Remove from io_plugins list and free. */
1278     tq_remove(&io_plugins, plugin);
1279     efree(plugin);
1280
1281     debug_return;
1282 }