diff options
Diffstat (limited to 'src/env_hooks.c')
-rw-r--r-- | src/env_hooks.c | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/src/env_hooks.c b/src/env_hooks.c new file mode 100644 index 0000000..eaacaa7 --- /dev/null +++ b/src/env_hooks.c @@ -0,0 +1,315 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2010, 2012-2016 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "sudo.h" +#include "sudo_plugin.h" +#include "sudo_dso.h" + +extern char **environ; /* global environment pointer */ +static char **priv_environ; /* private environment pointer */ + +/* + * NOTE: we don't use dlsym() to find the libc getenv() + * since this may allocate memory on some systems (glibc) + * which leads to a hang if malloc() calls getenv (jemalloc). + */ +char * +getenv_unhooked(const char *name) +{ + char **ep, *val = NULL; + size_t namelen = 0; + + /* For BSD compatibility, treat '=' in name like end of string. */ + while (name[namelen] != '\0' && name[namelen] != '=') + namelen++; + for (ep = environ; *ep != NULL; ep++) { + if (strncmp(*ep, name, namelen) == 0 && (*ep)[namelen] == '=') { + val = *ep + namelen + 1; + break; + } + } + return val; +} + +sudo_dso_public char *getenv(const char *name); + +char * +getenv(const char *name) +{ + char *val = NULL; + + switch (process_hooks_getenv(name, &val)) { + case SUDO_HOOK_RET_STOP: + return val; + case SUDO_HOOK_RET_ERROR: + return NULL; + default: + return getenv_unhooked(name); + } +} + +static int +rpl_putenv(PUTENV_CONST char *string) +{ + char **ep; + const char *equal; + size_t len; + bool found = false; + + /* Some putenv(3) implementations check for NULL. */ + if (string == NULL) { + errno = EINVAL; + return -1; + } + + /* The string must contain a '=' char but not start with one. */ + equal = strchr(string, '='); + if (equal == NULL || equal == string) { + errno = EINVAL; + return -1; + } + + /* Look for existing entry. */ + len = (equal - string) + 1; + for (ep = environ; *ep != NULL; ep++) { + if (strncmp(string, *ep, len) == 0) { + *ep = (char *)string; + found = true; + break; + } + } + /* Prune out duplicate variables. */ + if (found) { + while (*ep != NULL) { + if (strncmp(string, *ep, len) == 0) { + char **cur = ep; + while ((*cur = *(cur + 1)) != NULL) + cur++; + } else { + ep++; + } + } + } + + /* Append at the end if not already found. */ + if (!found) { + size_t env_len = (size_t)(ep - environ); + char **envp = reallocarray(priv_environ, env_len + 2, sizeof(char *)); + if (envp == NULL) + return -1; + if (environ != priv_environ) + memcpy(envp, environ, env_len * sizeof(char *)); + envp[env_len++] = (char *)string; + envp[env_len] = NULL; + priv_environ = environ = envp; + } + return 0; +} + +typedef int (*sudo_fn_putenv_t)(PUTENV_CONST char *); + +static int +putenv_unhooked(PUTENV_CONST char *string) +{ + sudo_fn_putenv_t fn; + + fn = (sudo_fn_putenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "putenv"); + if (fn != NULL) + return fn(string); + return rpl_putenv(string); +} + +sudo_dso_public int putenv(PUTENV_CONST char *string); + +int +putenv(PUTENV_CONST char *string) +{ + switch (process_hooks_putenv((char *)string)) { + case SUDO_HOOK_RET_STOP: + return 0; + case SUDO_HOOK_RET_ERROR: + return -1; + default: + return putenv_unhooked(string); + } +} + +static int +rpl_setenv(const char *var, const char *val, int overwrite) +{ + char *envstr, *dst; + const char *src; + size_t esize; + + if (!var || *var == '\0') { + errno = EINVAL; + return -1; + } + + /* + * POSIX says a var name with '=' is an error but BSD + * just ignores the '=' and anything after it. + */ + for (src = var; *src != '\0' && *src != '='; src++) + continue; + esize = (size_t)(src - var) + 2; + if (val) { + esize += strlen(val); /* glibc treats a NULL val as "" */ + } + + /* Allocate and fill in envstr. */ + if ((envstr = malloc(esize)) == NULL) + return -1; + for (src = var, dst = envstr; *src != '\0' && *src != '=';) + *dst++ = *src++; + *dst++ = '='; + if (val) { + for (src = val; *src != '\0';) + *dst++ = *src++; + } + *dst = '\0'; + + if (!overwrite && getenv(var) != NULL) { + free(envstr); + return 0; + } + if (rpl_putenv(envstr) == -1) { + free(envstr); + return -1; + } + return 0; +} + +typedef int (*sudo_fn_setenv_t)(const char *, const char *, int); + +static int +setenv_unhooked(const char *var, const char *val, int overwrite) +{ + sudo_fn_setenv_t fn; + + fn = (sudo_fn_setenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "setenv"); + if (fn != NULL) + return fn(var, val, overwrite); + return rpl_setenv(var, val, overwrite); +} + +sudo_dso_public int setenv(const char *var, const char *val, int overwrite); + +int +setenv(const char *var, const char *val, int overwrite) +{ + switch (process_hooks_setenv(var, val, overwrite)) { + case SUDO_HOOK_RET_STOP: + return 0; + case SUDO_HOOK_RET_ERROR: + return -1; + default: + return setenv_unhooked(var, val, overwrite); + } +} + +static int +rpl_unsetenv(const char *var) +{ + char **ep = environ; + size_t len; + + if (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; +} + +#ifdef UNSETENV_VOID +typedef void (*sudo_fn_unsetenv_t)(const char *); +#else +typedef int (*sudo_fn_unsetenv_t)(const char *); +#endif + +static int +unsetenv_unhooked(const char *var) +{ + int ret = 0; + sudo_fn_unsetenv_t fn; + + fn = (sudo_fn_unsetenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "unsetenv"); + if (fn != NULL) { +# ifdef UNSETENV_VOID + fn(var); +# else + ret = fn(var); +# endif + } else { + ret = rpl_unsetenv(var); + } + return ret; +} + +#ifdef UNSETENV_VOID +# define UNSETENV_RTYPE void +#else +# define UNSETENV_RTYPE int +#endif + +sudo_dso_public UNSETENV_RTYPE unsetenv(const char *var); + +UNSETENV_RTYPE +unsetenv(const char *var) +{ + int ret; + + switch (process_hooks_unsetenv(var)) { + case SUDO_HOOK_RET_STOP: + ret = 0; + break; + case SUDO_HOOK_RET_ERROR: + ret = -1; + break; + default: + ret = unsetenv_unhooked(var); + break; + } +#ifndef UNSETENV_VOID + return ret; +#endif +} |