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