2
0
mirror of https://github.com/sudo-project/sudo.git synced 2025-08-31 06:15:37 +00:00

Initial cut at a hooks implementation. The plugin can register

hooks for getenv, putenv, setenv and unsetenv.  This makes it
possible for the plugin to trap changes to the environment made by
authentication methods such as PAM or BSD auth so that such changes
are reflected in the environment passed back to sudo for execve().
This commit is contained in:
Todd C. Miller
2012-03-07 16:35:42 -05:00
parent 1504256134
commit 37770ecf1e
24 changed files with 1086 additions and 282 deletions

View File

@@ -95,12 +95,6 @@ struct environment {
size_t env_len; /* number of slots used, not counting NULL */
};
/*
* Prototypes
*/
static void sudo_setenv(const char *, const char *, int);
static void sudo_putenv(char *, int, int);
/*
* Copy of the sudo-managed environment.
*/
@@ -213,73 +207,63 @@ env_init(char * const envp[])
size_t len;
debug_decl(env_init, SUDO_DEBUG_ENV)
for (ep = envp; *ep != NULL; ep++)
continue;
len = (size_t)(ep - envp);
if (envp == NULL) {
/* Reset to initial state. */
memset(&env, 0, sizeof(env));
} else {
/* Make private copy of envp. */
for (ep = envp; *ep != NULL; ep++)
continue;
len = (size_t)(ep - envp);
env.env_len = len;
env.env_size = len + 1 + 128;
env.envp = emalloc2(env.env_size, sizeof(char *));
env.env_len = len;
env.env_size = len + 1 + 128;
env.envp = emalloc2(env.env_size, sizeof(char *));
#ifdef ENV_DEBUG
memset(env.envp, 0, env.env_size * sizeof(char *));
memset(env.envp, 0, env.env_size * sizeof(char *));
#endif
memcpy(env.envp, envp, len * sizeof(char *));
env.envp[len] = '\0';
memcpy(env.envp, envp, len * sizeof(char *));
env.envp[len] = '\0';
}
debug_return;
}
/*
* Getter for private copy of the environment.
*/
char **
env_get(void)
{
return env.envp;
}
/*
* Similar to setenv(3) but operates on sudo's private copy of the environment
* (not environ) and it always overwrites. The dupcheck param determines
* whether we need to verify that the variable is not already set.
*/
static void
sudo_setenv(const char *var, const char *val, int dupcheck)
{
char *estring;
size_t esize;
debug_decl(sudo_setenv, SUDO_DEBUG_ENV)
esize = strlen(var) + 1 + strlen(val) + 1;
estring = emalloc(esize);
/* Build environment string and insert it. */
if (strlcpy(estring, var, esize) >= esize ||
strlcat(estring, "=", esize) >= esize ||
strlcat(estring, val, esize) >= esize) {
errorx(1, _("internal error, sudo_setenv() overflow"));
}
sudo_putenv(estring, dupcheck, true);
debug_return;
}
/*
* Similar to putenv(3) but operates on sudo's private copy of the
* environment (not environ) and it always overwrites. The dupcheck param
* determines whether we need to verify that the variable is not already set.
* Will only overwrite an existing variable if overwrite is set.
* Does not include warnings or debugging to avoid recursive calls.
*/
static void
sudo_putenv(char *str, int dupcheck, int overwrite)
static int
sudo_putenv_nodebug(char *str, bool dupcheck, bool overwrite)
{
char **ep;
size_t len;
bool found = false;
debug_decl(sudo_putenv, SUDO_DEBUG_ENV)
/* Make sure there is room for the new entry plus a NULL. */
if (env.env_len + 2 > env.env_size) {
env.env_size += 128;
env.envp = erealloc3(env.envp, env.env_size, sizeof(char *));
char **nenvp;
size_t nsize = env.env_size + 128;
nenvp = env.envp ? realloc(env.envp, nsize * sizeof(char *)) :
malloc(nsize * sizeof(char *));
if (nenvp == NULL) {
errno = ENOMEM;
return -1;
}
env.envp = nenvp;
env.env_size = nsize;
#ifdef ENV_DEBUG
memset(env.envp + env.env_len, 0,
(env.env_size - env.env_len) * sizeof(char *));
@@ -287,8 +271,10 @@ sudo_putenv(char *str, int dupcheck, int overwrite)
}
#ifdef ENV_DEBUG
if (env.envp[env.env_len] != NULL)
errorx(1, _("sudo_putenv: corrupted envp, length mismatch"));
if (env.envp[env.env_len] != NULL) {
errno = EINVAL;
return -1;
}
#endif
if (dupcheck) {
@@ -321,7 +307,182 @@ sudo_putenv(char *str, int dupcheck, int overwrite)
*ep++ = str;
*ep = NULL;
}
debug_return;
return 0;
}
/*
* Similar to putenv(3) but operates on sudo's private copy of the
* environment (not environ) and it always overwrites. The dupcheck param
* determines whether we need to verify that the variable is not already set.
* Will only overwrite an existing variable if overwrite is set.
*/
static int
sudo_putenv(char *str, bool dupcheck, bool overwrite)
{
int rval;
debug_decl(sudo_putenv, SUDO_DEBUG_ENV)
rval = sudo_putenv_nodebug(str, dupcheck, overwrite);
if (rval == -1) {
#ifdef ENV_DEBUG
if (env.envp[env.env_len] != NULL)
errorx(1, _("sudo_putenv: corrupted envp, length mismatch"));
#endif
errorx(1, _("unable to allocate memory"));
}
debug_return_int(rval);
}
/*
* Similar to setenv(3) but operates on a private copy of the environment.
* The dupcheck param determines whether we need to verify that the variable
* is not already set.
*/
static int
sudo_setenv2(const char *var, const char *val, bool dupcheck, bool overwrite)
{
char *estring;
size_t esize;
debug_decl(sudo_setenv2, SUDO_DEBUG_ENV)
esize = strlen(var) + 1 + strlen(val) + 1;
estring = emalloc(esize);
/* Build environment string and insert it. */
if (strlcpy(estring, var, esize) >= esize ||
strlcat(estring, "=", esize) >= esize ||
strlcat(estring, val, esize) >= esize) {
errorx(1, _("internal error, sudo_setenv2() overflow"));
}
debug_return_int(sudo_putenv(estring, dupcheck, overwrite));
}
/*
* Similar to setenv(3) but operates on a private copy of the environment.
* Does not include warnings or debugging to avoid recursive calls.
*/
static int
sudo_setenv_nodebug(const char *var, const char *val, int overwrite)
{
char *estring;
size_t esize;
esize = strlen(var) + 1 + strlen(val) + 1;
if ((estring = malloc(esize)) == NULL) {
errno = ENOMEM;
return -1;
}
/* Build environment string and insert it. */
if (strlcpy(estring, var, esize) >= esize ||
strlcat(estring, "=", esize) >= esize ||
strlcat(estring, val, esize) >= esize) {
errno = EINVAL;
return -1;
}
return sudo_putenv_nodebug(estring, true, overwrite);
}
/*
* Similar to setenv(3) but operates on a private copy of the environment.
*/
int
sudo_setenv(const char *var, const char *val, int overwrite)
{
int rval;
debug_decl(sudo_setenv, SUDO_DEBUG_ENV)
rval = sudo_setenv_nodebug(var, val, overwrite);
if (rval == -1) {
if (errno == EINVAL)
errorx(1, _("internal error, sudo_setenv() overflow"));
errorx(1, _("unable to allocate memory"));
}
debug_return_int(rval);
}
/*
* Similar to unsetenv(3) but operates on a private copy of the environment.
* Does not include warnings or debugging to avoid recursive calls.
*/
static int
sudo_unsetenv_nodebug(const char *var)
{
char **ep = env.envp;
size_t len;
if (ep == NULL || var == NULL || *var == '\0' || strchr(var, '=') != NULL) {
errno = EINVAL;
return -1;
}
len = strlen(var);
while (*ep != NULL) {
if (strncmp(var, *ep, len) == 0 && (*ep)[len] == '=') {
/* Found it; shift remainder + NULL over by one. */
char **cur = ep;
while ((*cur = *(cur + 1)) != NULL)
cur++;
/* Keep going, could be multiple instances of the var. */
} else {
ep++;
}
}
return 0;
}
/*
* Similar to unsetenv(3) but operates on a private copy of the environment.
*/
int
sudo_unsetenv(const char *name)
{
int rval;
debug_decl(sudo_unsetenv, SUDO_DEBUG_ENV)
rval = sudo_unsetenv_nodebug(name);
debug_return_int(rval);
}
/*
* Similar to getenv(3) but operates on a private copy of the environment.
* Does not include warnings or debugging to avoid recursive calls.
*/
static char *
sudo_getenv_nodebug(const char *name)
{
char **ep, *val = NULL;
size_t namelen = 0;
if (env.env_len != 0) {
/* For BSD compatibility, treat '=' in name like end of string. */
while (name[namelen] != '\0' && name[namelen] != '=')
namelen++;
for (ep = env.envp; *ep != NULL; ep++) {
if (strncmp(*ep, name, namelen) == 0 && (*ep)[namelen] == '=') {
val = *ep + namelen + 1;
break;
}
}
}
return val;
}
/*
* Similar to getenv(3) but operates on a private copy of the environment.
*/
char *
sudo_getenv(const char *name)
{
char *val;
debug_decl(sudo_getenv, SUDO_DEBUG_ENV)
val = sudo_getenv_nodebug(name);
debug_return_str(val);
}
/*
@@ -521,21 +682,23 @@ rebuild_env(void)
* on sudoers options).
*/
if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) {
sudo_setenv("SHELL", runas_pw->pw_shell, ISSET(didvar, DID_SHELL));
sudo_setenv("LOGNAME", runas_pw->pw_name,
ISSET(didvar, DID_LOGNAME));
sudo_setenv("USER", runas_pw->pw_name, ISSET(didvar, DID_USER));
sudo_setenv("USERNAME", runas_pw->pw_name,
ISSET(didvar, DID_USERNAME));
sudo_setenv2("SHELL", runas_pw->pw_shell,
ISSET(didvar, DID_SHELL), true);
sudo_setenv2("LOGNAME", runas_pw->pw_name,
ISSET(didvar, DID_LOGNAME), true);
sudo_setenv2("USER", runas_pw->pw_name,
ISSET(didvar, DID_USER), true);
sudo_setenv2("USERNAME", runas_pw->pw_name,
ISSET(didvar, DID_USERNAME), true);
} else {
if (!ISSET(didvar, DID_SHELL))
sudo_setenv("SHELL", sudo_user.pw->pw_shell, false);
sudo_setenv2("SHELL", sudo_user.pw->pw_shell, false, true);
if (!ISSET(didvar, DID_LOGNAME))
sudo_setenv("LOGNAME", user_name, false);
sudo_setenv2("LOGNAME", user_name, false, true);
if (!ISSET(didvar, DID_USER))
sudo_setenv("USER", user_name, false);
sudo_setenv2("USER", user_name, false, true);
if (!ISSET(didvar, DID_USERNAME))
sudo_setenv("USERNAME", user_name, false);
sudo_setenv2("USERNAME", user_name, false, true);
}
/* If we didn't keep HOME, reset it based on target user. */
@@ -589,7 +752,7 @@ rebuild_env(void)
}
/* Replace the PATH envariable with a secure one? */
if (def_secure_path && !user_is_exempt()) {
sudo_setenv("PATH", def_secure_path, true);
sudo_setenv2("PATH", def_secure_path, true, true);
SET(didvar, DID_PATH);
}
@@ -601,22 +764,22 @@ rebuild_env(void)
*/
if (def_set_logname && !ISSET(sudo_mode, MODE_LOGIN_SHELL|MODE_EDIT)) {
if (!ISSET(didvar, KEPT_LOGNAME))
sudo_setenv("LOGNAME", runas_pw->pw_name, true);
sudo_setenv2("LOGNAME", runas_pw->pw_name, true, true);
if (!ISSET(didvar, KEPT_USER))
sudo_setenv("USER", runas_pw->pw_name, true);
sudo_setenv2("USER", runas_pw->pw_name, true, true);
if (!ISSET(didvar, KEPT_USERNAME))
sudo_setenv("USERNAME", runas_pw->pw_name, true);
sudo_setenv2("USERNAME", runas_pw->pw_name, true, true);
}
/* Set $HOME to target user if not preserving user's value. */
if (reset_home)
sudo_setenv("HOME", runas_pw->pw_dir, true);
sudo_setenv2("HOME", runas_pw->pw_dir, true, true);
/* Provide default values for $TERM and $PATH if they are not set. */
if (!ISSET(didvar, DID_TERM))
sudo_putenv("TERM=unknown", false, false);
if (!ISSET(didvar, DID_PATH))
sudo_setenv("PATH", _PATH_STDPATH, false);
sudo_setenv2("PATH", _PATH_STDPATH, false, true);
/* Set PS1 if SUDO_PS1 is set. */
if (ps1 != NULL)
@@ -625,18 +788,18 @@ rebuild_env(void)
/* Add the SUDO_COMMAND envariable (cmnd + args). */
if (user_args) {
easprintf(&cp, "%s %s", user_cmnd, user_args);
sudo_setenv("SUDO_COMMAND", cp, true);
sudo_setenv2("SUDO_COMMAND", cp, true, true);
efree(cp);
} else {
sudo_setenv("SUDO_COMMAND", user_cmnd, true);
sudo_setenv2("SUDO_COMMAND", user_cmnd, true, true);
}
/* Add the SUDO_USER, SUDO_UID, SUDO_GID environment variables. */
sudo_setenv("SUDO_USER", user_name, true);
sudo_setenv2("SUDO_USER", user_name, true, true);
snprintf(idbuf, sizeof(idbuf), "%u", (unsigned int) user_uid);
sudo_setenv("SUDO_UID", idbuf, true);
sudo_setenv2("SUDO_UID", idbuf, true, true);
snprintf(idbuf, sizeof(idbuf), "%u", (unsigned int) user_gid);
sudo_setenv("SUDO_GID", idbuf, true);
sudo_setenv2("SUDO_GID", idbuf, true, true);
/* Free old environment. */
efree(old_envp);
@@ -800,3 +963,59 @@ init_envtables(void)
def_env_keep = cur;
}
}
int
sudoers_hook_getenv(const char *name, char **value, void *closure)
{
static bool in_progress = false; /* avoid recursion */
if (in_progress || env.envp == NULL)
return SUDO_HOOK_RET_NEXT;
in_progress = true;
*value = sudo_getenv_nodebug(name);
in_progress = false;
return SUDO_HOOK_RET_STOP;
}
int
sudoers_hook_putenv(char *string, void *closure)
{
static bool in_progress = false; /* avoid recursion */
if (in_progress || env.envp == NULL)
return SUDO_HOOK_RET_NEXT;
in_progress = true;
sudo_putenv_nodebug(string, true, true);
in_progress = false;
return SUDO_HOOK_RET_STOP;
}
int
sudoers_hook_setenv(const char *name, const char *value, int overwrite, void *closure)
{
static bool in_progress = false; /* avoid recursion */
if (in_progress || env.envp == NULL)
return SUDO_HOOK_RET_NEXT;
in_progress = true;
sudo_setenv_nodebug(name, value, overwrite);
in_progress = false;
return SUDO_HOOK_RET_STOP;
}
int
sudoers_hook_unsetenv(const char *name, void *closure)
{
static bool in_progress = false; /* avoid recursion */
if (in_progress || env.envp == NULL)
return SUDO_HOOK_RET_NEXT;
in_progress = true;
sudo_unsetenv_nodebug(name);
in_progress = false;
return SUDO_HOOK_RET_STOP;
}