text: /*
   * Copyright (c) 2010, 2012 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>
  #ifdef STDC_HEADERS
  # include <stdlib.h>
  # include <stddef.h>
  #else
  # ifdef HAVE_STDLIB_H
  #  include <stdlib.h>
  # endif
  #endif /* STDC_HEADERS */
  #ifdef HAVE_STRING_H
  # include <string.h>
  #endif /* HAVE_STRING_H */
  #ifdef HAVE_STRINGS_H
  # include <strings.h>
  #endif /* HAVE_STRINGS_H */
  #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
  # include <malloc.h>
  #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
  #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 */
  
  static char *
  rpl_getenv(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;
  }
  
  typedef char * (*sudo_fn_getenv_t)(const char *);
  
  char *
  getenv_unhooked(const char *name)
  {
      sudo_fn_getenv_t fn;
  
      fn = (sudo_fn_getenv_t)sudo_dso_findsym(SUDO_DSO_NEXT, "getenv");
      if (fn != NULL)
  	return fn(name);
      return rpl_getenv(name);
  }
  
  __dso_public char *getenv(const char *);
  
  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 = erealloc3(priv_environ, env_len + 2, sizeof(char *));
  	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);
  }
  
  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;
      }
      return rpl_putenv(envstr);
  }
  
  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);
  }
  
  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 rval = 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
  	rval = fn(var);
  # endif
      } else {
  	rval = rpl_unsetenv(var);
      }
      return rval;
  }
  
  #ifdef UNSETENV_VOID
  void
  #else
  int
  #endif
  unsetenv(const char *var)
  {
      int rval;
  
      switch (process_hooks_unsetenv(var)) {
  	case SUDO_HOOK_RET_STOP:
  	    rval = 0;
  	    break;
  	case SUDO_HOOK_RET_ERROR:
  	    rval = -1;
  	    break;
  	default:
  	    rval = unsetenv_unhooked(var);
  	    break;
      }
  #ifndef UNSETENV_VOID
      return rval;
  #endif
  }
  