/*
- * Copyright (c) 2009-2011 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2009-2013 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
#include <config.h>
#include <sys/types.h>
-#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
-#ifdef HAVE_SYS_SELECT_H
-# include <sys/select.h>
-#endif /* HAVE_SYS_SELECT_H */
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#if TIME_WITH_SYS_TIME
# include <time.h>
#endif
-#ifdef HAVE_SETLOCALE
-# include <locale.h>
-#endif
#ifdef HAVE_LOGIN_CAP_H
# include <login_cap.h>
+# ifndef LOGIN_SETENV
+# define LOGIN_SETENV 0
+# endif
#endif
#ifdef HAVE_PROJECT_H
# include <project.h>
# endif /* __hpux */
# include <prot.h>
#endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */
-#ifdef HAVE_PRIV_SET
-# include <priv.h>
-#endif
#include "sudo.h"
#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
-#include <sudo_usage.h>
+#include "sudo_usage.h"
/*
* Local variables
struct plugin_container_list io_plugins;
struct user_details user_details;
const char *list_user, *runas_user, *runas_group; /* extern for parse_args.c */
-int debug_level;
+static struct command_details command_details;
+static int sudo_mode;
/*
* Local functions
*/
static void fix_fds(void);
static void disable_coredumps(void);
+static void sudo_check_suid(const char *path);
static char **get_user_info(struct user_details *);
static void command_info_to_details(char * const info[],
struct command_details *details);
-static int policy_open(struct plugin_container *plugin, char * const settings[],
- char * const user_info[], char * const user_env[]);
-static void policy_close(struct plugin_container *plugin, int exit_status,
- int error);
-static int iolog_open(struct plugin_container *plugin, char * const settings[],
- char * const user_info[], char * const command_details[],
- int argc, char * const argv[], char * const user_env[]);
-static void iolog_close(struct plugin_container *plugin, int exit_status,
- int error);
-static char *escape_cmnd(const char *src);
/* Policy plugin convenience functions. */
static int policy_open(struct plugin_container *plugin, char * const settings[],
char * const argv[], int verbose, const char *list_user);
static int policy_validate(struct plugin_container *plugin);
static void policy_invalidate(struct plugin_container *plugin, int remove);
-static int policy_init_session(struct plugin_container *plugin,
- struct passwd *pwd);
/* I/O log plugin convenience functions. */
static int iolog_open(struct plugin_container *plugin, char * const settings[],
static void iolog_close(struct plugin_container *plugin, int exit_status,
int error);
static int iolog_show_version(struct plugin_container *plugin, int verbose);
+static void iolog_unlink(struct plugin_container *plugin);
-#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
+#ifdef RLIMIT_CORE
static struct rlimit corelimit;
-#endif /* RLIMIT_CORE && !SUDO_DEVEL */
+#endif /* RLIMIT_CORE */
#if defined(__linux__)
static struct rlimit nproclimit;
#endif
+__dso_public int main(int argc, char *argv[], char *envp[]);
+
int
main(int argc, char *argv[], char *envp[])
{
- int nargc, sudo_mode, exitcode = 0;
+ int nargc, ok, exitcode = 0;
char **nargv, **settings, **env_add;
char **user_info, **command_info, **argv_out, **user_env_out;
struct plugin_container *plugin, *next;
- struct command_details command_details;
sigset_t mask;
- int ok;
-#if defined(SUDO_DEVEL) && defined(__OpenBSD__)
- extern char *malloc_options;
- malloc_options = "AFGJPR";
-#endif
+ debug_decl(main, SUDO_DEBUG_MAIN)
+
+ os_init(argc, argv, envp);
-#ifdef HAVE_SETLOCALE
setlocale(LC_ALL, "");
-#endif
+ bindtextdomain(PACKAGE_NAME, LOCALEDIR);
+ textdomain(PACKAGE_NAME);
-#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME)
- if (argc > 0)
- setprogname(argv[0]);
-#endif
+#ifdef HAVE_TZSET
+ (void) tzset();
+#endif /* HAVE_TZSET */
/* Must be done before we do any password lookups */
#if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS)
# endif
#endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */
- if (geteuid() != 0)
- errorx(1, "must be setuid root");
+ /* Make sure we are setuid root. */
+ sudo_check_suid(argv[0]);
- /* Reset signal mask, disable core dumps and make sure fds 0-2 are open. */
+ /* Reset signal mask, save signal state and make sure fds 0-2 are open. */
(void) sigemptyset(&mask);
(void) sigprocmask(SIG_SETMASK, &mask, NULL);
- disable_coredumps();
+ save_signals();
fix_fds();
+ /* Read sudo.conf. */
+ sudo_conf_read(NULL);
+
/* Fill in user_info with user name, uid, cwd, etc. */
memset(&user_details, 0, sizeof(user_details));
user_info = get_user_info(&user_details);
+ /* Disable core dumps if not enabled in sudo.conf. */
+ disable_coredumps();
+
/* Parse command line arguments. */
sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add);
- sudo_debug(9, "sudo_mode %d", sudo_mode);
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "sudo_mode %d", sudo_mode);
/* Print sudo version early, in case of plugin init failure. */
if (ISSET(sudo_mode, MODE_VERSION)) {
- printf("Sudo version %s\n", PACKAGE_VERSION);
+ printf(_("Sudo version %s\n"), PACKAGE_VERSION);
if (user_details.uid == ROOT_UID)
- (void) printf("Configure args: %s\n", CONFIGURE_ARGS);
+ (void) printf(_("Configure options: %s\n"), CONFIGURE_ARGS);
}
- /* Read sudo.conf and load plugins. */
- if (!sudo_load_plugins(_PATH_SUDO_CONF, &policy_plugin, &io_plugins))
- errorx(1, "fatal error, unable to load plugins");
+ /* Load plugins. */
+ if (!sudo_load_plugins(&policy_plugin, &io_plugins))
+ fatalx(_("fatal error, unable to load plugins"));
/* Open policy plugin. */
ok = policy_open(&policy_plugin, settings, user_info, envp);
- if (ok != TRUE) {
+ if (ok != 1) {
if (ok == -2)
usage(1);
else
- errorx(1, "unable to initialize policy plugin");
+ fatalx(_("unable to initialize policy plugin"));
}
+ init_signals();
+
switch (sudo_mode & MODE_MASK) {
case MODE_VERSION:
policy_show_version(&policy_plugin, !user_details.uid);
tq_foreach_fwd(&io_plugins, plugin) {
ok = iolog_open(plugin, settings, user_info, NULL,
nargc, nargv, envp);
- if (ok == TRUE)
+ if (ok != -1)
iolog_show_version(plugin, !user_details.uid);
}
break;
case MODE_VALIDATE:
case MODE_VALIDATE|MODE_INVALIDATE:
ok = policy_validate(&policy_plugin);
- exit(ok != TRUE);
+ exit(ok != 1);
case MODE_KILL:
case MODE_INVALIDATE:
policy_invalidate(&policy_plugin, sudo_mode == MODE_KILL);
case MODE_LIST|MODE_INVALIDATE:
ok = policy_list(&policy_plugin, nargc, nargv,
ISSET(sudo_mode, MODE_LONG_LIST), list_user);
- exit(ok != TRUE);
+ exit(ok != 1);
case MODE_EDIT:
case MODE_RUN:
ok = policy_check(&policy_plugin, nargc, nargv, env_add,
&command_info, &argv_out, &user_env_out);
- sudo_debug(8, "policy plugin returns %d", ok);
- if (ok != TRUE) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "policy plugin returns %d", ok);
+ if (ok != 1) {
if (ok == -2)
usage(1);
exit(1); /* plugin printed error message */
ok = iolog_open(plugin, settings, user_info,
command_info, nargc, nargv, envp);
switch (ok) {
- case TRUE:
+ case 1:
break;
- case FALSE:
- /* I/O plugin asked to be disabled, remove from list. */
- tq_remove(&io_plugins, plugin);
+ case 0:
+ /* I/O plugin asked to be disabled, remove and free. */
+ iolog_unlink(plugin);
break;
case -2:
usage(1);
break;
default:
- errorx(1, "error initializing I/O plugin %s", plugin->name);
+ fatalx(_("error initializing I/O plugin %s"),
+ plugin->name);
}
}
+ /* Setup command details and run command/edit. */
command_info_to_details(command_info, &command_details);
command_details.argv = argv_out;
command_details.envp = user_env_out;
if (ISSET(sudo_mode, MODE_BACKGROUND))
SET(command_details.flags, CD_BACKGROUND);
+ /* Become full root (not just setuid) so user cannot kill us. */
+ if (setuid(ROOT_UID) == -1)
+ warning("setuid(%d)", ROOT_UID);
/* Restore coredumpsize resource limit before running. */
-#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
- (void) setrlimit(RLIMIT_CORE, &corelimit);
-#endif /* RLIMIT_CORE && !SUDO_DEVEL */
+#ifdef RLIMIT_CORE
+ if (sudo_conf_disable_coredump())
+ (void) setrlimit(RLIMIT_CORE, &corelimit);
+#endif /* RLIMIT_CORE */
if (ISSET(command_details.flags, CD_SUDOEDIT)) {
exitcode = sudo_edit(&command_details);
} else {
- if (ISSET(sudo_mode, MODE_SHELL)) {
- /* Escape meta chars if running a shell with args. */
- if (argv_out[1] != NULL && strcmp(argv_out[1], "-c") == 0 &&
- argv_out[2] != NULL && argv_out[3] == NULL)
- argv_out[2] = escape_cmnd(argv_out[2]);
- }
exitcode = run_command(&command_details);
}
/* The close method was called by sudo_edit/run_command. */
break;
default:
- errorx(1, "unexpected sudo mode 0x%x", sudo_mode);
+ fatalx(_("unexpected sudo mode 0x%x"), sudo_mode);
}
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode);
exit(exitcode);
}
+int
+os_init_common(int argc, char *argv[], char *envp[])
+{
+#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME)
+ if (argc > 0)
+ setprogname(argv[0]);
+#endif
+ return 0;
+}
+
/*
* Ensure that stdin, stdout and stderr are open; set to /dev/null if not.
* Some operating systems do this automatically in the kernel or libc.
fix_fds(void)
{
int miss[3], devnull = -1;
+ debug_decl(fix_fds, SUDO_DEBUG_UTIL)
/*
* stdin, stdout and stderr must be open; set them to /dev/null
miss[STDERR_FILENO] = fcntl(STDERR_FILENO, F_GETFL, 0) == -1;
if (miss[STDIN_FILENO] || miss[STDOUT_FILENO] || miss[STDERR_FILENO]) {
if ((devnull = open(_PATH_DEVNULL, O_RDWR, 0644)) == -1)
- error(1, "unable to open %s", _PATH_DEVNULL);
+ fatal(_("unable to open %s"), _PATH_DEVNULL);
if (miss[STDIN_FILENO] && dup2(devnull, STDIN_FILENO) == -1)
- error(1, "dup2");
+ fatal("dup2");
if (miss[STDOUT_FILENO] && dup2(devnull, STDOUT_FILENO) == -1)
- error(1, "dup2");
+ fatal("dup2");
if (miss[STDERR_FILENO] && dup2(devnull, STDERR_FILENO) == -1)
- error(1, "dup2");
+ fatal("dup2");
if (devnull > STDERR_FILENO)
close(devnull);
}
+ debug_return;
+}
+
+/*
+ * Allocate space for groups and fill in using getgrouplist()
+ * for when we cannot (or don't want to) use getgroups().
+ */
+static int
+fill_group_list(struct user_details *ud, int system_maxgroups)
+{
+ int tries, rval = -1;
+ debug_decl(fill_group_list, SUDO_DEBUG_UTIL)
+
+ /*
+ * If user specified a max number of groups, use it, otherwise keep
+ * trying getgrouplist() until we have enough room in the array.
+ */
+ ud->ngroups = sudo_conf_max_groups();
+ if (ud->ngroups != -1) {
+ ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
+ /* No error on insufficient space if user specified max_groups. */
+ (void)getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups);
+ rval = 0;
+ } else {
+ /*
+ * It is possible to belong to more groups in the group database
+ * than NGROUPS_MAX. We start off with NGROUPS_MAX * 4 entries
+ * and double this as needed.
+ */
+ ud->groups = NULL;
+ ud->ngroups = system_maxgroups << 1;
+ for (tries = 0; tries < 10 && rval == -1; tries++) {
+ ud->ngroups <<= 1;
+ efree(ud->groups);
+ ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
+ rval = getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups);
+ }
+ }
+ debug_return_int(rval);
}
static char *
get_user_groups(struct user_details *ud)
{
- char *gid_list = NULL;
-#ifdef HAVE_GETGROUPS
+ char *cp, *gid_list = NULL;
size_t glsize;
- char *cp;
- int i, len;
+ int i, len, maxgroups, group_source;
+ debug_decl(get_user_groups, SUDO_DEBUG_UTIL)
- if ((ud->ngroups = getgroups(0, NULL)) <= 0)
- return NULL;
+#if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX)
+ maxgroups = (int)sysconf(_SC_NGROUPS_MAX);
+ if (maxgroups < 0)
+#endif
+ maxgroups = NGROUPS_MAX;
+
+ ud->groups = NULL;
+ group_source = sudo_conf_group_source();
+ if (group_source != GROUP_SOURCE_DYNAMIC) {
+ if ((ud->ngroups = getgroups(0, NULL)) > 0) {
+ /* Use groups from kernel if not too many or source is static. */
+ if (ud->ngroups < maxgroups || group_source == GROUP_SOURCE_STATIC) {
+ ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
+ if (getgroups(ud->ngroups, ud->groups) < 0) {
+ efree(ud->groups);
+ ud->groups = NULL;
+ }
+ }
+ }
+ }
+ if (ud->groups == NULL) {
+ /*
+ * Query group database if kernel list is too small or disabled.
+ * Typically, this is because NFS can only support up to 16 groups.
+ */
+ if (fill_group_list(ud, maxgroups) == -1)
+ fatal(_("unable to get group vector"));
+ }
- ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
- if (getgroups(ud->ngroups, ud->groups) < 0)
- error(1, "can't get group vector");
+ /*
+ * Format group list as a comma-separated string of gids.
+ */
glsize = sizeof("groups=") - 1 + (ud->ngroups * (MAX_UID_T_LEN + 1));
gid_list = emalloc(glsize);
memcpy(gid_list, "groups=", sizeof("groups=") - 1);
i ? "," : "", (unsigned int)ud->groups[i]);
cp += len;
}
-#endif
- return gid_list;
+ debug_return_str(gid_list);
}
/*
static char **
get_user_info(struct user_details *ud)
{
- char cwd[PATH_MAX];
- char host[MAXHOSTNAMELEN];
- char **user_info, *cp;
+ char *cp, **user_info, cwd[PATH_MAX], host[HOST_NAME_MAX + 1];
struct passwd *pw;
- int i = 0;
+ int fd, i = 0;
+ debug_decl(get_user_info, SUDO_DEBUG_UTIL)
/* XXX - bound check number of entries */
user_info = emalloc2(32, sizeof(char *));
+ ud->pid = getpid();
+ ud->ppid = getppid();
+ ud->pgid = getpgid(0);
+ ud->tcpgid = (pid_t)-1;
+ fd = open(_PATH_TTY, O_RDWR|O_NOCTTY|O_NONBLOCK, 0);
+ if (fd != -1) {
+ ud->tcpgid = tcgetpgrp(fd);
+ close(fd);
+ }
+ ud->sid = getsid(0);
+
ud->uid = getuid();
ud->euid = geteuid();
ud->gid = getgid();
pw = getpwuid(ud->uid);
if (pw == NULL)
- errorx(1, "unknown uid %u: who are you?", (unsigned int)ud->uid);
+ fatalx(_("unknown uid %u: who are you?"), (unsigned int)ud->uid);
user_info[i] = fmt_string("user", pw->pw_name);
if (user_info[i] == NULL)
- errorx(1, "unable to allocate memory");
+ fatalx(NULL);
ud->username = user_info[i] + sizeof("user=") - 1;
/* Stash user's shell for use with the -s flag; don't pass to plugin. */
}
ud->shell = estrdup(ud->shell);
+ easprintf(&user_info[++i], "pid=%d", (int)ud->pid);
+ easprintf(&user_info[++i], "ppid=%d", (int)ud->ppid);
+ easprintf(&user_info[++i], "pgid=%d", (int)ud->pgid);
+ easprintf(&user_info[++i], "tcpgid=%d", (int)ud->tcpgid);
+ easprintf(&user_info[++i], "sid=%d", (int)ud->sid);
+
easprintf(&user_info[++i], "uid=%u", (unsigned int)ud->uid);
easprintf(&user_info[++i], "euid=%u", (unsigned int)ud->euid);
easprintf(&user_info[++i], "gid=%u", (unsigned int)ud->gid);
if (getcwd(cwd, sizeof(cwd)) != NULL) {
user_info[++i] = fmt_string("cwd", cwd);
if (user_info[i] == NULL)
- errorx(1, "unable to allocate memory");
+ fatalx(NULL);
ud->cwd = user_info[i] + sizeof("cwd=") - 1;
}
- if ((cp = ttyname(STDIN_FILENO)) || (cp = ttyname(STDOUT_FILENO)) ||
- (cp = ttyname(STDERR_FILENO))) {
+ if ((cp = get_process_ttyname()) != NULL) {
user_info[++i] = fmt_string("tty", cp);
if (user_info[i] == NULL)
- errorx(1, "unable to allocate memory");
+ fatalx(NULL);
ud->tty = user_info[i] + sizeof("tty=") - 1;
+ efree(cp);
}
if (gethostname(host, sizeof(host)) == 0)
strlcpy(host, "localhost", sizeof(host));
user_info[++i] = fmt_string("host", host);
if (user_info[i] == NULL)
- errorx(1, "unable to allocate memory");
+ fatalx(NULL);
ud->host = user_info[i] + sizeof("host=") - 1;
get_ttysize(&ud->ts_lines, &ud->ts_cols);
user_info[++i] = NULL;
- return user_info;
+ debug_return_ptr(user_info);
}
/*
long lval;
unsigned long ulval;
char *cp, *ep;
+ debug_decl(command_info_to_details, SUDO_DEBUG_PCOMM)
memset(details, 0, sizeof(*details));
details->closefrom = -1;
break; \
}
+ sudo_debug_printf(SUDO_DEBUG_INFO, "command info from plugin:");
for (i = 0; info[i] != NULL; i++) {
- sudo_debug(9, "command info: %s", info[i]);
+ sudo_debug_printf(SUDO_DEBUG_INFO, " %d: %s", i, info[i]);
switch (info[i][0]) {
case 'c':
SET_STRING("chroot=", chroot)
break;
}
break;
+ case 'e':
+ if (strncmp("exec_background=", info[i], sizeof("exec_background=") - 1) == 0) {
+ if (atobool(info[i] + sizeof("exec_background=") - 1) == true)
+ SET(details->flags, CD_EXEC_BG);
+ break;
+ }
+ break;
case 'l':
SET_STRING("login_class=", login_class)
break;
break;
}
if (strncmp("noexec=", info[i], sizeof("noexec=") - 1) == 0) {
- if (atobool(info[i] + sizeof("noexec=") - 1) == TRUE)
+ if (atobool(info[i] + sizeof("noexec=") - 1) == true)
SET(details->flags, CD_NOEXEC);
break;
}
- /* XXX - deprecated */
- if (strncmp("noexec_file=", info[i], sizeof("noexec_file=") - 1) == 0) {
- noexec_path = info[i] + sizeof("noexec_file=") - 1;
- break;
- }
break;
case 'p':
if (strncmp("preserve_groups=", info[i], sizeof("preserve_groups=") - 1) == 0) {
- if (atobool(info[i] + sizeof("preserve_groups=") - 1) == TRUE)
+ if (atobool(info[i] + sizeof("preserve_groups=") - 1) == true)
SET(details->flags, CD_PRESERVE_GROUPS);
break;
}
}
break;
}
+#ifdef HAVE_PRIV_SET
+ if (strncmp("runas_privs=", info[i], sizeof("runas_privs=") - 1) == 0) {
+ const char *endp;
+ cp = info[i] + sizeof("runas_privs=") - 1;
+ if (*cp == '\0')
+ break;
+ errno = 0;
+ details->privs = priv_str_to_set(cp, ",", &endp);
+ if (details->privs == NULL)
+ warning("invalid runas_privs %s", endp);
+ }
+ if (strncmp("runas_limitprivs=", info[i], sizeof("runas_limitprivs=") - 1) == 0) {
+ const char *endp;
+ cp = info[i] + sizeof("runas_limitprivs=") - 1;
+ if (*cp == '\0')
+ break;
+ errno = 0;
+ details->limitprivs = priv_str_to_set(cp, ",", &endp);
+ if (details->limitprivs == NULL)
+ warning("invalid runas_limitprivs %s", endp);
+ }
+#endif /* HAVE_PRIV_SET */
break;
case 's':
SET_STRING("selinux_role=", selinux_role)
SET_STRING("selinux_type=", selinux_type)
if (strncmp("set_utmp=", info[i], sizeof("set_utmp=") - 1) == 0) {
- if (atobool(info[i] + sizeof("set_utmp=") - 1) == TRUE)
+ if (atobool(info[i] + sizeof("set_utmp=") - 1) == true)
SET(details->flags, CD_SET_UTMP);
break;
}
if (strncmp("sudoedit=", info[i], sizeof("sudoedit=") - 1) == 0) {
- if (atobool(info[i] + sizeof("sudoedit=") - 1) == TRUE)
+ if (atobool(info[i] + sizeof("sudoedit=") - 1) == true)
SET(details->flags, CD_SUDOEDIT);
break;
}
break;
}
if (strncmp("use_pty=", info[i], sizeof("use_pty=") - 1) == 0) {
- if (atobool(info[i] + sizeof("use_pty=") - 1) == TRUE)
+ if (atobool(info[i] + sizeof("use_pty=") - 1) == true)
SET(details->flags, CD_USE_PTY);
break;
}
if (!ISSET(details->flags, CD_SET_EUID))
details->euid = details->uid;
+#ifdef HAVE_SETAUTHDB
+ aix_setauthdb(IDtouser(details->euid));
+#endif
+ details->pw = getpwuid(details->euid);
+ if (details->pw != NULL && (details->pw = pw_dup(details->pw)) == NULL)
+ fatalx(NULL);
+#ifdef HAVE_SETAUTHDB
+ aix_restoreauthdb();
+#endif
+
#ifdef HAVE_SELINUX
if (details->selinux_role != NULL && is_selinux_enabled() > 0)
SET(details->flags, CD_RBAC_ENABLED);
#endif
+ debug_return;
+}
+
+static void
+sudo_check_suid(const char *path)
+{
+ struct stat sb;
+ debug_decl(sudo_check_suid, SUDO_DEBUG_PCOMM)
+
+ if (geteuid() != 0) {
+ if (strchr(path, '/') != NULL && stat(path, &sb) == 0) {
+ /* Try to determine why sudo was not running as root. */
+ if (sb.st_uid != ROOT_UID || !ISSET(sb.st_mode, S_ISUID)) {
+ fatalx(
+ _("%s must be owned by uid %d and have the setuid bit set"),
+ path, ROOT_UID);
+ } else {
+ fatalx(_("effective uid is not %d, is %s on a file system "
+ "with the 'nosuid' option set or an NFS file system without"
+ " root privileges?"), ROOT_UID, path);
+ }
+ } else {
+ fatalx(
+ _("effective uid is not %d, is sudo installed setuid root?"),
+ ROOT_UID);
+ }
+ }
+ debug_return;
}
/*
static void
disable_coredumps(void)
{
-#if defined(__linux__) || (defined(RLIMIT_CORE) && !defined(SUDO_DEVEL))
+#if defined(__linux__) || defined(RLIMIT_CORE)
struct rlimit rl;
#endif
+ debug_decl(disable_coredumps, SUDO_DEBUG_UTIL)
#if defined(__linux__)
/*
(void)setrlimit(RLIMIT_NPROC, &rl);
}
#endif /* __linux__ */
-#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
- /*
- * Turn off core dumps.
- */
- (void) getrlimit(RLIMIT_CORE, &corelimit);
- memcpy(&rl, &corelimit, sizeof(struct rlimit));
- rl.rlim_cur = 0;
- (void) setrlimit(RLIMIT_CORE, &rl);
-#endif /* RLIMIT_CORE && !SUDO_DEVEL */
-}
-
-#ifdef HAVE_PROJECT_H
-static void
-set_project(struct passwd *pw)
-{
- struct project proj;
- char buf[PROJECT_BUFSZ];
- int errval;
-
+#ifdef RLIMIT_CORE
/*
- * Collect the default project for the user and settaskid
+ * Turn off core dumps?
*/
- setprojent();
- if (getdefaultproj(pw->pw_name, &proj, buf, sizeof(buf)) != NULL) {
- errval = setproject(proj.pj_name, pw->pw_name, TASK_NORMAL);
- switch(errval) {
- case 0:
- break;
- case SETPROJ_ERR_TASK:
- switch (errno) {
- case EAGAIN:
- warningx("resource control limit has been reached");
- break;
- case ESRCH:
- warningx("user \"%s\" is not a member of project \"%s\"",
- pw->pw_name, proj.pj_name);
- break;
- case EACCES:
- warningx("the invoking task is final");
- break;
- default:
- warningx("could not join project \"%s\"", proj.pj_name);
- }
- case SETPROJ_ERR_POOL:
- switch (errno) {
- case EACCES:
- warningx("no resource pool accepting default bindings "
- "exists for project \"%s\"", proj.pj_name);
- break;
- case ESRCH:
- warningx("specified resource pool does not exist for "
- "project \"%s\"", proj.pj_name);
- break;
- default:
- warningx("could not bind to default resource pool for "
- "project \"%s\"", proj.pj_name);
- }
- break;
- default:
- if (errval <= 0) {
- warningx("setproject failed for project \"%s\"", proj.pj_name);
- } else {
- warningx("warning, resource control assignment failed for "
- "project \"%s\"", proj.pj_name);
- }
- }
- } else {
- warning("getdefaultproj");
+ if (sudo_conf_disable_coredump()) {
+ (void) getrlimit(RLIMIT_CORE, &corelimit);
+ memcpy(&rl, &corelimit, sizeof(struct rlimit));
+ rl.rlim_cur = 0;
+ (void) setrlimit(RLIMIT_CORE, &rl);
}
- endprojent();
-}
-#endif /* HAVE_PROJECT_H */
-
-/*
- * Disable execution of child processes in the command we are about
- * to run. On systems with privilege sets, we can remove the exec
- * privilege. On other systems we use LD_PRELOAD and the like.
- */
-static void
-disable_execute(struct command_details *details)
-{
- char *cp, **ev, **nenvp;
- int env_len = 0, env_size = 128;
-
-#ifdef HAVE_PRIV_SET
- /* Solaris privileges, remove PRIV_PROC_EXEC post-execve. */
- if (priv_set(PRIV_OFF, PRIV_LIMIT, "PRIV_PROC_EXEC", NULL) == 0)
- return;
- warning("unable to remove PRIV_PROC_EXEC from PRIV_LIMIT");
-#endif /* HAVE_PRIV_SET */
-
- nenvp = emalloc2(env_size, sizeof(char *));
- for (ev = details->envp; *ev != NULL; ev++) {
- if (env_len + 2 > env_size) {
- env_size += 128;
- nenvp = erealloc3(nenvp, env_size, sizeof(char *));
- }
- /*
- * Prune out existing preloaded libraries.
- * XXX - should save and append instead of replacing.
- */
-#if defined(__darwin__) || defined(__APPLE__)
- if (strncmp(*ev, "DYLD_INSERT_LIBRARIES=", sizeof("DYLD_INSERT_LIBRARIES=") - 1) == 0)
- continue;
- if (strncmp(*ev, "DYLD_FORCE_FLAT_NAMESPACE=", sizeof("DYLD_INSERT_LIBRARIES=") - 1) == 0)
- continue;
-#elif defined(__osf__) || defined(__sgi)
- if (strncmp(*ev, "_RLD_LIST=", sizeof("_RLD_LIST=") - 1) == 0)
- continue;
-#elif defined(_AIX)
- if (strncmp(*ev, "LDR_PRELOAD=", sizeof("LDR_PRELOAD=") - 1) == 0)
- continue;
-#else
- if (strncmp(*ev, "LD_PRELOAD=", sizeof("LD_PRELOAD=") - 1) == 0)
- continue;
-#endif
- nenvp[env_len++] = *ev;
- }
-
- /*
- * Preload a noexec file? For a list of LD_PRELOAD-alikes, see
- * http://www.fortran-2000.com/ArnaudRecipes/sharedlib.html
- * XXX - need to support 32-bit and 64-bit variants
- */
-#if defined(__darwin__) || defined(__APPLE__)
- nenvp[env_len++] = "DYLD_FORCE_FLAT_NAMESPACE=";
- cp = fmt_string("DYLD_INSERT_LIBRARIES", noexec_path);
-#elif defined(__osf__) || defined(__sgi)
- easprintf(&cp, "_RLD_LIST=%s:DEFAULT", noexec_path);
-#elif defined(_AIX)
- cp = fmt_string("LDR_PRELOAD", noexec_path);
-#else
- cp = fmt_string("LD_PRELOAD", noexec_path);
-#endif
- if (cp == NULL)
- error(1, NULL);
- nenvp[env_len++] = cp;
- nenvp[env_len] = NULL;
-
- details->envp = nenvp;
+#endif /* RLIMIT_CORE */
+ debug_return;
}
/*
* Setup the execution environment immediately prior to the call to execve()
- * Returns TRUE on success and FALSE on failure.
+ * Returns true on success and false on failure.
*/
-int
+bool
exec_setup(struct command_details *details, const char *ptyname, int ptyfd)
{
- int rval = FALSE;
- struct passwd *pw;
-
-#ifdef HAVE_SETAUTHDB
- aix_setauthdb(IDtouser(details->euid));
-#endif
- pw = getpwuid(details->euid);
-#ifdef HAVE_SETAUTHDB
- aix_restoreauthdb();
-#endif
-
- /*
- * Call policy plugin's session init before other setup occurs.
- * The session init code is expected to print an error as needed.
- */
- if (policy_init_session(&policy_plugin, pw) != TRUE)
- goto done;
+ bool rval = false;
+ debug_decl(exec_setup, SUDO_DEBUG_EXEC)
#ifdef HAVE_SELINUX
if (ISSET(details->flags, CD_RBAC_ENABLED)) {
}
#endif
- if (pw != NULL) {
+ if (details->pw != NULL) {
#ifdef HAVE_PROJECT_H
- set_project(pw);
+ set_project(details->pw);
#endif
+#ifdef HAVE_PRIV_SET
+ if (details->privs != NULL) {
+ if (setppriv(PRIV_SET, PRIV_INHERITABLE, details->privs) != 0) {
+ warning("unable to set privileges");
+ goto done;
+ }
+ }
+ if (details->limitprivs != NULL) {
+ if (setppriv(PRIV_SET, PRIV_LIMIT, details->limitprivs) != 0) {
+ warning("unable to set limit privileges");
+ goto done;
+ }
+ } else if (details->privs != NULL) {
+ if (setppriv(PRIV_SET, PRIV_LIMIT, details->privs) != 0) {
+ warning("unable to set limit privileges");
+ goto done;
+ }
+ }
+#endif /* HAVE_PRIV_SET */
+
#ifdef HAVE_GETUSERATTR
- aix_prep_user(pw->pw_name, ptyname ? ptyname : user_details.tty);
+ aix_prep_user(details->pw->pw_name, ptyname ? ptyname : user_details.tty);
#endif
#ifdef HAVE_LOGIN_CAP_H
if (details->login_class) {
login_cap_t *lc;
/*
- * We only use setusercontext() to set the nice value and rlimits.
+ * We only use setusercontext() to set the nice value and rlimits
+ * unless this is a login shell (sudo -i).
*/
lc = login_getclass((char *)details->login_class);
if (!lc) {
- warningx("unknown login class %s", details->login_class);
+ warningx(_("unknown login class %s"), details->login_class);
errno = ENOENT;
goto done;
}
- flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
- if (setusercontext(lc, pw, pw->pw_uid, flags)) {
- if (pw->pw_uid != ROOT_UID) {
- warning("unable to set user context");
+ if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) {
+ /* Set everything except user, group and login name. */
+ flags = LOGIN_SETALL;
+ CLR(flags, LOGIN_SETGROUP|LOGIN_SETLOGIN|LOGIN_SETUSER|LOGIN_SETENV|LOGIN_SETPATH);
+ CLR(details->flags, CD_SET_UMASK); /* LOGIN_UMASK instead */
+ } else {
+ flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
+ }
+ if (setusercontext(lc, details->pw, details->pw->pw_uid, flags)) {
+ if (details->pw->pw_uid != ROOT_UID) {
+ warning(_("unable to set user context"));
goto done;
} else
- warning("unable to set user context");
+ warning(_("unable to set user context"));
}
}
#endif /* HAVE_LOGIN_CAP_H */
/*
* Set groups, including supplementary group vector.
*/
+ if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) {
+ if (details->ngroups >= 0) {
+ if (sudo_setgroups(details->ngroups, details->groups) < 0) {
+ warning(_("unable to set supplementary group IDs"));
+ goto done;
+ }
+ }
+ }
#ifdef HAVE_SETEUID
if (ISSET(details->flags, CD_SET_EGID) && setegid(details->egid)) {
- warning("unable to set egid to runas gid %u", details->egid);
+ warning(_("unable to set effective gid to runas gid %u"),
+ (unsigned int)details->egid);
goto done;
}
#endif
if (ISSET(details->flags, CD_SET_GID) && setgid(details->gid)) {
- warning("unable to set gid to runas gid %u", details->gid);
+ warning(_("unable to set gid to runas gid %u"),
+ (unsigned int)details->gid);
goto done;
}
- if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) {
-#ifdef HAVE_GETGROUPS
- if (details->ngroups >= 0) {
- if (setgroups(details->ngroups, details->groups) < 0) {
- warning("unable to set supplementary group IDs");
- goto done;
- }
- }
-#else
- if (pw && initgroups(pw->pw_name, pw->pw_gid) < 0) {
- warning("unable to set supplementary group IDs");
- goto done;
- }
-#endif
- }
-
if (ISSET(details->flags, CD_SET_PRIORITY)) {
if (setpriority(PRIO_PROCESS, 0, details->priority) != 0) {
- warning("unable to set process priority");
+ warning(_("unable to set process priority"));
goto done;
}
}
(void) umask(details->umask);
if (details->chroot) {
if (chroot(details->chroot) != 0 || chdir("/") != 0) {
- warning("unable to change root to %s", details->chroot);
+ warning(_("unable to change root to %s"), details->chroot);
goto done;
}
}
- if (ISSET(details->flags, CD_NOEXEC))
- disable_execute(details);
-
#ifdef HAVE_SETRESUID
if (setresuid(details->uid, details->euid, details->euid) != 0) {
- warning("unable to change to runas uid (%u, %u)", details->uid,
+ warning(_("unable to change to runas uid (%u, %u)"), details->uid,
details->euid);
goto done;
}
#elif HAVE_SETREUID
if (setreuid(details->uid, details->euid) != 0) {
- warning("unable to change to runas uid (%u, %u)", details->uid,
- details->euid);
+ warning(_("unable to change to runas uid (%u, %u)"),
+ (unsigned int)details->uid, (unsigned int)details->euid);
goto done;
}
#else
if (seteuid(details->euid) != 0 || setuid(details->euid) != 0) {
- warning("unable to change to runas uid (%u, %u)", details->uid,
+ warning(_("unable to change to runas uid (%u, %u)"), details->uid,
details->euid);
goto done;
}
if (details->chroot || strcmp(details->cwd, user_details.cwd) != 0) {
/* Note: cwd is relative to the new root, if any. */
if (chdir(details->cwd) != 0) {
- warning("unable to change directory to %s", details->cwd);
+ warning(_("unable to change directory to %s"), details->cwd);
goto done;
}
}
}
/*
- * Restore nproc resource limit if pam_limits didn't do it for us.
+ * SuSE Enterprise Linux uses RLIMIT_NPROC and _SC_CHILD_MAX
+ * interchangably. This causes problems when setting RLIMIT_NPROC
+ * to RLIM_INFINITY due to a bug in bash where bash tries to honor
+ * the value of _SC_CHILD_MAX but treats a value of -1 as an error,
+ * and uses a default value of 32 instead.
+ *
+ * To work around this problem, we restore the nproc resource limit
+ * if sysconf(_SC_CHILD_MAX) is negative. In most cases, pam_limits
+ * will set RLIMIT_NPROC for us.
+ *
* We must do this *after* the uid change to avoid potential EAGAIN
* from setuid().
*/
-#if defined(__linux__)
+#if defined(__linux__) && defined(_SC_CHILD_MAX)
{
struct rlimit rl;
- if (getrlimit(RLIMIT_NPROC, &rl) == 0) {
+ long l;
+ errno = 0;
+ l = sysconf(_SC_CHILD_MAX);
+ if (l == -1 && errno == 0 && getrlimit(RLIMIT_NPROC, &rl) == 0) {
if (rl.rlim_cur == RLIM_INFINITY && rl.rlim_max == RLIM_INFINITY)
(void) setrlimit(RLIMIT_NPROC, &nproclimit);
}
}
#endif
- rval = TRUE;
+ rval = true;
done:
- return rval;
-}
-
-/*
- * Escape any non-alpha numeric or blank characters to make sure
- * they are not interpreted specially by the shell.
- */
-static char *
-escape_cmnd(const char *src)
-{
- char *cmnd, *dst;
-
- /* Worst case scenario, we have to escape everything. */
- cmnd = dst = emalloc((2 * strlen(src)) + 1);
- while (*src != '\0') {
- if (!isalnum((unsigned char)*src) && !isspace((unsigned char)*src) &&
- *src != '_' && *src != '-') {
- /* quote potential meta character */
- *dst++ = '\\';
- }
- *dst++ = *src++;
- }
- *dst++ = '\0';
-
- return cmnd;
+ debug_return_bool(rval);
}
/*
struct plugin_container *plugin;
struct command_status cstat;
int exitcode = 1;
+ debug_decl(run_command, SUDO_DEBUG_EXEC)
cstat.type = CMD_INVALID;
cstat.val = 0;
- sudo_execve(details, &cstat);
+ sudo_execute(details, &cstat);
switch (cstat.type) {
case CMD_ERRNO:
/* exec_setup() or execve() returned an error. */
- sudo_debug(9, "calling policy close with errno");
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "calling policy close with errno %d", cstat.val);
policy_close(&policy_plugin, 0, cstat.val);
tq_foreach_fwd(&io_plugins, plugin) {
- sudo_debug(9, "calling I/O close with errno");
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "calling I/O close with errno %d", cstat.val);
iolog_close(plugin, 0, cstat.val);
}
exitcode = 1;
break;
case CMD_WSTATUS:
/* Command ran, exited or was killed. */
- sudo_debug(9, "calling policy close with wait status");
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "calling policy close with wait status %d", cstat.val);
policy_close(&policy_plugin, cstat.val, 0);
tq_foreach_fwd(&io_plugins, plugin) {
- sudo_debug(9, "calling I/O close with wait status");
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "calling I/O close with wait status %d", cstat.val);
iolog_close(plugin, cstat.val, 0);
}
if (WIFEXITED(cstat.val))
exitcode = WTERMSIG(cstat.val) | 128;
break;
default:
- warningx("unexpected child termination condition: %d", cstat.type);
+ warningx(_("unexpected child termination condition: %d"), cstat.type);
break;
}
- return exitcode;
+ debug_return_int(exitcode);
}
static int
policy_open(struct plugin_container *plugin, char * const settings[],
char * const user_info[], char * const user_env[])
{
- return plugin->u.policy->open(SUDO_API_VERSION, sudo_conversation,
- _sudo_printf, settings, user_info, user_env);
+ int rval;
+ debug_decl(policy_open, SUDO_DEBUG_PCOMM)
+
+ /*
+ * Backwards compatibility for older API versions
+ */
+ switch (plugin->u.generic->version) {
+ case SUDO_API_MKVERSION(1, 0):
+ case SUDO_API_MKVERSION(1, 1):
+ rval = plugin->u.policy_1_0->open(plugin->u.io_1_0->version,
+ sudo_conversation, _sudo_printf, settings, user_info, user_env);
+ break;
+ default:
+ rval = plugin->u.policy->open(SUDO_API_VERSION, sudo_conversation,
+ _sudo_printf, settings, user_info, user_env, plugin->options);
+ }
+
+ debug_return_bool(rval);
}
static void
policy_close(struct plugin_container *plugin, int exit_status, int error)
{
- plugin->u.policy->close(exit_status, error);
+ debug_decl(policy_close, SUDO_DEBUG_PCOMM)
+ if (plugin->u.policy->close != NULL)
+ plugin->u.policy->close(exit_status, error);
+ else
+ warning(_("unable to execute %s"), command_details.command);
+ debug_return;
}
static int
policy_show_version(struct plugin_container *plugin, int verbose)
{
- return plugin->u.policy->show_version(verbose);
+ debug_decl(policy_show_version, SUDO_DEBUG_PCOMM)
+ if (plugin->u.policy->show_version == NULL)
+ debug_return_bool(true);
+ debug_return_bool(plugin->u.policy->show_version(verbose));
}
static int
char *env_add[], char **command_info[], char **argv_out[],
char **user_env_out[])
{
- return plugin->u.policy->check_policy(argc, argv, env_add, command_info,
- argv_out, user_env_out);
+ debug_decl(policy_check, SUDO_DEBUG_PCOMM)
+ if (plugin->u.policy->check_policy == NULL) {
+ fatalx(_("policy plugin %s is missing the `check_policy' method"),
+ plugin->name);
+ }
+ debug_return_bool(plugin->u.policy->check_policy(argc, argv, env_add,
+ command_info, argv_out, user_env_out));
}
static int
policy_list(struct plugin_container *plugin, int argc, char * const argv[],
int verbose, const char *list_user)
{
+ debug_decl(policy_list, SUDO_DEBUG_PCOMM)
if (plugin->u.policy->list == NULL) {
- warningx("policy plugin %s does not support listing privileges",
+ warningx(_("policy plugin %s does not support listing privileges"),
plugin->name);
- return FALSE;
+ debug_return_bool(false);
}
- return plugin->u.policy->list(argc, argv, verbose, list_user);
+ debug_return_bool(plugin->u.policy->list(argc, argv, verbose, list_user));
}
static int
policy_validate(struct plugin_container *plugin)
{
+ debug_decl(policy_validate, SUDO_DEBUG_PCOMM)
if (plugin->u.policy->validate == NULL) {
- warningx("policy plugin %s does not support the -v flag",
+ warningx(_("policy plugin %s does not support the -v option"),
plugin->name);
- return FALSE;
+ debug_return_bool(false);
}
- return plugin->u.policy->validate();
+ debug_return_bool(plugin->u.policy->validate());
}
static void
policy_invalidate(struct plugin_container *plugin, int remove)
{
+ debug_decl(policy_invalidate, SUDO_DEBUG_PCOMM)
if (plugin->u.policy->invalidate == NULL) {
- errorx(1, "policy plugin %s does not support the -k/-K flags",
+ fatalx(_("policy plugin %s does not support the -k/-K options"),
plugin->name);
}
plugin->u.policy->invalidate(remove);
+ debug_return;
}
-static int
-policy_init_session(struct plugin_container *plugin, struct passwd *pwd)
+int
+policy_init_session(struct command_details *details)
{
- if (plugin->u.policy->init_session)
- return plugin->u.policy->init_session(pwd);
- return TRUE;
+ int rval = true;
+ debug_decl(policy_init_session, SUDO_DEBUG_PCOMM)
+
+ if (policy_plugin.u.policy->init_session) {
+ /*
+ * Backwards compatibility for older API versions
+ */
+ switch (policy_plugin.u.generic->version) {
+ case SUDO_API_MKVERSION(1, 0):
+ case SUDO_API_MKVERSION(1, 1):
+ rval = policy_plugin.u.policy_1_0->init_session(details->pw);
+ break;
+ default:
+ rval = policy_plugin.u.policy->init_session(details->pw,
+ &details->envp);
+ }
+ }
+ debug_return_bool(rval);
}
static int
int argc, char * const argv[], char * const user_env[])
{
int rval;
+ debug_decl(iolog_open, SUDO_DEBUG_PCOMM)
/*
- * Backwards compatibility for API major 1, minor 0
+ * Backwards compatibility for older API versions
*/
switch (plugin->u.generic->version) {
case SUDO_API_MKVERSION(1, 0):
sudo_conversation, _sudo_printf, settings, user_info, argc, argv,
user_env);
break;
+ case SUDO_API_MKVERSION(1, 1):
+ rval = plugin->u.io_1_1->open(plugin->u.io_1_1->version,
+ sudo_conversation, _sudo_printf, settings, user_info,
+ command_info, argc, argv, user_env);
+ break;
default:
rval = plugin->u.io->open(SUDO_API_VERSION, sudo_conversation,
- _sudo_printf, settings, user_info, command_info, argc, argv,
- user_env);
+ _sudo_printf, settings, user_info, command_info,
+ argc, argv, user_env, plugin->options);
}
- return rval;
+ debug_return_bool(rval);
}
static void
iolog_close(struct plugin_container *plugin, int exit_status, int error)
{
- plugin->u.io->close(exit_status, error);
+ debug_decl(iolog_close, SUDO_DEBUG_PCOMM)
+ if (plugin->u.io->close != NULL)
+ plugin->u.io->close(exit_status, error);
+ debug_return;
}
static int
iolog_show_version(struct plugin_container *plugin, int verbose)
{
- return plugin->u.io->show_version(verbose);
+ debug_decl(iolog_show_version, SUDO_DEBUG_PCOMM)
+ if (plugin->u.io->show_version == NULL)
+ debug_return_bool(true);
+ debug_return_bool(plugin->u.io->show_version(verbose));
}
/*
- * Simple debugging/logging.
+ * Remove the specified I/O logging plugin from the io_plugins list.
+ * Deregisters any hooks before unlinking, then frees the container.
*/
-void
-sudo_debug(int level, const char *fmt, ...)
+static void
+iolog_unlink(struct plugin_container *plugin)
{
- va_list ap;
- char *fmt2;
-
- if (level > debug_level)
- return;
-
- /* Backet fmt with program name and a newline to make it a single write */
- easprintf(&fmt2, "%s: %s\n", getprogname(), fmt);
- va_start(ap, fmt);
- vfprintf(stderr, fmt2, ap);
- va_end(ap);
- efree(fmt2);
+ debug_decl(iolog_unlink, SUDO_DEBUG_PCOMM)
+
+ /* Deregister hooks, if any. */
+ if (plugin->u.io->version >= SUDO_API_MKVERSION(1, 2)) {
+ if (plugin->u.io->deregister_hooks != NULL)
+ plugin->u.io->deregister_hooks(SUDO_HOOK_VERSION,
+ deregister_hook);
+ }
+ /* Remove from io_plugins list and free. */
+ tq_remove(&io_plugins, plugin);
+ efree(plugin);
+
+ debug_return;
}