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:
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user