Files
sudo/src/env_hooks.c
Todd C. Miller 3f022419ae Be consistent with the naming of the variable used to store the
function return value.  Previously, some code used "rval", some
used "ret".  This standardizes on "ret" and uses "rc" for temporary
return codes.
2016-09-08 16:38:08 -06:00

293 lines
6.4 KiB
C

/*
* Copyright (c) 2010, 2012-2016 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
* 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.
*/
#include <config.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_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;
}
__dso_public 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;
size_t len;
bool found = false;
/* Look for existing entry. */
len = (strchr(string, '=') - 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);
}
__dso_public 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++)
;
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);
}
__dso_public 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
__dso_public void
#else
__dso_public int
#endif
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
}