diff --git a/MANIFEST b/MANIFEST index ce7632068..c932c3fee 100644 --- a/MANIFEST +++ b/MANIFEST @@ -48,7 +48,6 @@ compat/regress/fnmatch/fnm_test.in compat/regress/glob/files compat/regress/glob/globtest.c compat/regress/glob/globtest.in -compat/setenv.c compat/siglist.in compat/snprintf.c compat/stdbool.h @@ -56,7 +55,6 @@ compat/strlcat.c compat/strlcpy.c compat/strsignal.c compat/timespec.h -compat/unsetenv.c compat/utime.h compat/utimes.c config.guess @@ -270,11 +268,13 @@ plugins/sudoers/visudo.c pp src/Makefile.in src/conversation.c +src/env_hooks.c src/error.c src/exec.c src/exec_common.c src/exec_pty.c src/get_pty.c +src/hooks.c src/load_plugins.c src/net_ifs.c src/parse_args.c diff --git a/aclocal.m4 b/aclocal.m4 index c14b04f07..1a8f2052b 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -228,6 +228,24 @@ AC_DEFUN([SUDO_FUNC_UNSETENV_VOID], fi ]) +dnl +dnl check putenv() argument for const +dnl +AC_DEFUN([SUDO_FUNC_PUTENV_CONST], +[AC_CACHE_CHECK([whether putenv takes a const argument], +sudo_cv_func_putenv_const, +[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT +int putenv(const char *string) {return 0;}], [])], + [sudo_cv_func_putenv_const=yes], + [sudo_cv_func_putenv_const=no]) + ]) + if test $sudo_cv_func_putenv_const = yes; then + AC_DEFINE(PUTENV_CONST, const, [Define to const if the `putenv' takes a const argument.]) + else + AC_DEFINE(PUTENV_CONST, []) + fi +]) + dnl dnl check for sa_len field in struct sockaddr dnl diff --git a/common/sudo_debug.c b/common/sudo_debug.c index de98d0d90..e105b5d59 100644 --- a/common/sudo_debug.c +++ b/common/sudo_debug.c @@ -100,6 +100,7 @@ const char *const sudo_debug_subsystems[] = { "rbtree", "perms", "plugin", + "hooks", NULL }; diff --git a/compat/Makefile.in b/compat/Makefile.in index 6b6630e67..d0361cef0 100644 --- a/compat/Makefile.in +++ b/compat/Makefile.in @@ -181,8 +181,8 @@ mktemp.lo: $(srcdir)/mktemp.c $(top_builddir)/config.h $(incdir)/missing.h nanosleep.lo: $(srcdir)/nanosleep.c $(top_builddir)/config.h \ $(top_srcdir)/compat/timespec.h $(incdir)/missing.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/nanosleep.c -setenv.lo: $(srcdir)/setenv.c $(top_builddir)/config.h $(incdir)/missing.h - $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/setenv.c +pw_dup.lo: $(srcdir)/pw_dup.c $(top_builddir)/config.h + $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/pw_dup.c siglist.lo: siglist.c $(top_builddir)/config.h $(incdir)/missing.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) siglist.c snprintf.lo: $(srcdir)/snprintf.c $(top_builddir)/config.h $(incdir)/missing.h @@ -194,8 +194,6 @@ strlcpy.lo: $(srcdir)/strlcpy.c $(top_builddir)/config.h $(incdir)/missing.h strsignal.lo: $(srcdir)/strsignal.c $(top_builddir)/config.h \ $(incdir)/missing.h $(incdir)/gettext.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/strsignal.c -unsetenv.lo: $(srcdir)/unsetenv.c $(top_builddir)/config.h $(incdir)/missing.h - $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/unsetenv.c utimes.lo: $(srcdir)/utimes.c $(top_builddir)/config.h \ $(top_srcdir)/compat/utime.h $(incdir)/missing.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/utimes.c diff --git a/compat/setenv.c b/compat/setenv.c deleted file mode 100644 index cff0c2615..000000000 --- a/compat/setenv.c +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2010 Todd C. Miller - * - * 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 - -#include - -#include -#ifdef STDC_HEADERS -# include -# include -#else -# ifdef HAVE_STDLIB_H -# include -# endif -#endif /* STDC_HEADERS */ -#ifdef HAVE_STRING_H -# include -#endif /* HAVE_STRING_H */ -#ifdef HAVE_STRINGS_H -# include -#endif /* HAVE_STRINGS_H */ -#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS) -# include -#endif /* HAVE_MALLOC_H && !STDC_HEADERS */ -#include - -#include "missing.h" - -int -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 putenv(envstr); -} diff --git a/compat/unsetenv.c b/compat/unsetenv.c deleted file mode 100644 index f7cfaa76b..000000000 --- a/compat/unsetenv.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2010 Todd C. Miller - * - * 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 - -#include - -#include -#ifdef STDC_HEADERS -# include -# include -#else -# ifdef HAVE_STDLIB_H -# include -# endif -#endif /* STDC_HEADERS */ -#ifdef HAVE_STRING_H -# include -#endif /* HAVE_STRING_H */ -#ifdef HAVE_STRINGS_H -# include -#endif /* HAVE_STRINGS_H */ -#include - -#include "missing.h" - -extern char **environ; /* global environment */ - -#ifdef UNSETENV_VOID -void -#else -int -#endif -unsetenv(const char *var) -{ - char **ep = environ; - size_t len; - - if (var == NULL || *var == '\0' || strchr(var, '=') != NULL) { - errno = EINVAL; -#ifdef UNSETENV_VOID - return; -#else - return -1; -#endif - } - - 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++; - } - } -#ifndef UNSETENV_VOID - return 0; -#endif -} diff --git a/config.h.in b/config.h.in index 7b1991299..4e0a7290c 100644 --- a/config.h.in +++ b/config.h.in @@ -748,6 +748,9 @@ /* The syslog priority sudo will use for successful attempts. */ #undef PRI_SUCCESS +/* Define to const if the `putenv' takes a const argument. */ +#undef PUTENV_CONST + /* The default value of preloaded objects (if any). */ #undef RTLD_PRELOAD_DEFAULT diff --git a/configure b/configure index 18f745fc1..46dde841d 100755 --- a/configure +++ b/configure @@ -16388,7 +16388,7 @@ $as_echo "#define HAVE_GETGROUPS 1" >>confdefs.h fi LIBS=$ac_save_LIBS -for ac_func in strrchr sysconf tzset strftime \ +for ac_func in strrchr sysconf tzset strftime setenv \ regcomp setlocale nl_langinfo mbr_check_membership \ setrlimit64 do : @@ -16768,16 +16768,45 @@ $as_echo "#define UNSETENV_VOID 1" >>confdefs.h fi -else - case " $LIBOBJS " in - *" unsetenv.$ac_objext "* ) ;; - *) LIBOBJS="$LIBOBJS unsetenv.$ac_objext" - ;; -esac - fi done +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether putenv takes a const argument" >&5 +$as_echo_n "checking whether putenv takes a const argument... " >&6; } +if ${sudo_cv_func_putenv_const+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default +int putenv(const char *string) {return 0;} +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + sudo_cv_func_putenv_const=yes +else + sudo_cv_func_putenv_const=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $sudo_cv_func_putenv_const" >&5 +$as_echo "$sudo_cv_func_putenv_const" >&6; } + if test $sudo_cv_func_putenv_const = yes; then + +$as_echo "#define PUTENV_CONST const" >>confdefs.h + + else + $as_echo "#define PUTENV_CONST /**/" >>confdefs.h + + fi + if test -z "$SKIP_SETRESUID"; then for ac_func in setresuid do : @@ -17142,19 +17171,6 @@ esac fi -ac_fn_c_check_func "$LINENO" "setenv" "ac_cv_func_setenv" -if test "x$ac_cv_func_setenv" = xyes; then : - $as_echo "#define HAVE_SETENV 1" >>confdefs.h - -else - case " $LIBOBJS " in - *" setenv.$ac_objext "* ) ;; - *) LIBOBJS="$LIBOBJS setenv.$ac_objext" - ;; -esac - -fi - for ac_func in nanosleep do : diff --git a/configure.in b/configure.in index 90be2da5c..cb77065b0 100644 --- a/configure.in +++ b/configure.in @@ -2127,7 +2127,7 @@ dnl dnl Function checks dnl AC_FUNC_GETGROUPS -AC_CHECK_FUNCS(strrchr sysconf tzset strftime \ +AC_CHECK_FUNCS(strrchr sysconf tzset strftime setenv \ regcomp setlocale nl_langinfo mbr_check_membership \ setrlimit64) AC_REPLACE_FUNCS(getgrouplist) @@ -2194,7 +2194,8 @@ AC_CHECK_FUNCS(openpty, [AC_CHECK_HEADERS(libutil.h util.h pty.h, [break])], [ ]) ]) ]) -AC_CHECK_FUNCS(unsetenv, [SUDO_FUNC_UNSETENV_VOID], [AC_LIBOBJ(unsetenv)]) +AC_CHECK_FUNCS(unsetenv, [SUDO_FUNC_UNSETENV_VOID], []) +SUDO_FUNC_PUTENV_CONST if test -z "$SKIP_SETRESUID"; then AC_CHECK_FUNCS(setresuid, [ SKIP_SETREUID=yes @@ -2228,7 +2229,7 @@ SUDO_FUNC_FNMATCH([AC_DEFINE(HAVE_FNMATCH)], [AC_LIBOBJ(fnmatch) COMPAT_TEST_PROGS="${COMPAT_TEST_PROGS}${COMPAT_TEST_PROGS+ }fnm_test" ]) SUDO_FUNC_ISBLANK -AC_REPLACE_FUNCS(memrchr pw_dup strlcpy strlcat setenv) +AC_REPLACE_FUNCS(memrchr pw_dup strlcpy strlcat) AC_CHECK_FUNCS(nanosleep, [], [ # On Solaris, nanosleep is in librt AC_CHECK_LIB(rt, nanosleep, [REPLAY_LIBS="${REPLAY_LIBS} -lrt"], [AC_LIBOBJ(nanosleep)]) diff --git a/include/sudo_debug.h b/include/sudo_debug.h index 81fbfac07..e2896f7e8 100644 --- a/include/sudo_debug.h +++ b/include/sudo_debug.h @@ -68,6 +68,7 @@ #define SUDO_DEBUG_RBTREE (22<<4) /* red-black tree functions */ #define SUDO_DEBUG_PERMS (23<<4) /* uid/gid swapping functions */ #define SUDO_DEBUG_PLUGIN (24<<4) /* main plugin functions */ +#define SUDO_DEBUG_HOOKS (25<<4) /* hook functions */ #define SUDO_DEBUG_ALL 0xfff0 /* all subsystems */ /* Extract priority and convert to an index. */ diff --git a/include/sudo_plugin.h b/include/sudo_plugin.h index 1eed2a294..4269b7158 100644 --- a/include/sudo_plugin.h +++ b/include/sudo_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2011 Todd C. Miller + * Copyright (c) 2009-2012 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,7 +29,7 @@ #define SUDO_API_VERSION_SET_MAJOR(vp, n) do { \ *(vp) = (*(vp) & 0x0000ffff) | ((n) << 16); \ } while(0) -#define SUDO_VERSION_SET_MINOR(vp, n) do { \ +#define SUDO_API_VERSION_SET_MINOR(vp, n) do { \ *(vp) = (*(vp) & 0xffff0000) | (n); \ } while(0) @@ -55,6 +55,60 @@ typedef int (*sudo_conv_t)(int num_msgs, const struct sudo_conv_message msgs[], struct sudo_conv_reply replies[]); typedef int (*sudo_printf_t)(int msg_type, const char *fmt, ...); +/* + * Hook structure for the optional plugin hook list. + * This allows the plugin to hook into specific sudo and/or libc functions. + */ +struct sudo_hook { + int hook_version; + int hook_type; + int (*hook_fn)(); + void *closure; +}; + +/* Hook API version major/minor */ +#define SUDO_HOOK_VERSION_MAJOR 1 +#define SUDO_HOOK_VERSION_MINOR 0 +#define SUDO_HOOK_MKVERSION(x, y) ((x << 16) | y) +#define SUDO_HOOK_VERSION SUDO_HOOK_MKVERSION(SUDO_HOOK_VERSION_MAJOR, SUDO_HOOK_VERSION_MINOR) + +/* Getters and setters for hook API version */ +#define SUDO_HOOK_VERSION_GET_MAJOR(v) ((v) >> 16) +#define SUDO_HOOK_VERSION_GET_MINOR(v) ((v) & 0xffff) +#define SUDO_HOOK_VERSION_SET_MAJOR(vp, n) do { \ + *(vp) = (*(vp) & 0x0000ffff) | ((n) << 16); \ +} while(0) +#define SUDO_HOOK_VERSION_SET_MINOR(vp, n) do { \ + *(vp) = (*(vp) & 0xffff0000) | (n); \ +} while(0) + +/* + * Hook function return values. + */ +#define SUDO_HOOK_RET_ERROR -1 /* error */ +#define SUDO_HOOK_RET_NEXT 0 /* go to the next hook in the list */ +#define SUDO_HOOK_RET_STOP 1 /* stop here, skip the rest of tghe list */ + +/* + * Hooks for setenv/unsetenv/putenv/getenv. + * This allows the plugin to be notified when a PAM module modifies + * the environment so it can update the copy of the environment that + * is passed to execve(). + */ +#define SUDO_HOOK_SETENV 1 +#define SUDO_HOOK_UNSETENV 2 +#define SUDO_HOOK_PUTENV 3 +#define SUDO_HOOK_GETENV 4 + +/* + * Hook functions types. + */ +typedef int (*sudo_hook_fn_t)(); +typedef int (*sudo_hook_fn_setenv_t)(const char *name, const char *value, int overwrite, void *closure); +typedef int (*sudo_hook_fn_putenv_t)(char *string, void *closure); +typedef int (*sudo_hook_fn_getenv_t)(const char *name, char **value, void *closure); +typedef int (*sudo_hook_fn_unsetenv_t)(const char *name, void *closure); + /* Policy plugin type and defines */ struct passwd; struct policy_plugin { @@ -75,6 +129,8 @@ struct policy_plugin { int (*validate)(void); void (*invalidate)(int remove); int (*init_session)(struct passwd *pwd); + void (*register_hooks)(int version, int (*register_hook)(struct sudo_hook *hook)); + void (*deregister_hooks)(int version, int (*deregister_hook)(struct sudo_hook *hook)); }; /* I/O plugin type and defines */ @@ -94,6 +150,8 @@ struct io_plugin { int (*log_stdin)(const char *buf, unsigned int len); int (*log_stdout)(const char *buf, unsigned int len); int (*log_stderr)(const char *buf, unsigned int len); + void (*register_hooks)(int version, int (*register_hook)(struct sudo_hook *hook)); + void (*deregister_hooks)(int version, int (*deregister_hook)(struct sudo_hook *hook)); }; /* Sudoers group plugin version major/minor */ diff --git a/mkdep.pl b/mkdep.pl index 55006ba99..3388fd916 100755 --- a/mkdep.pl +++ b/mkdep.pl @@ -55,7 +55,7 @@ sub mkdep { $makefile =~ s:\@SUDOERS_OBJS\@:bsm_audit.lo linux_audit.lo ldap.lo plugin_error.lo:; # XXX - fill in AUTH_OBJS from contents of the auth dir instead $makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo getspwuid.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid5.lo sia.lo:; - $makefile =~ s:\@LTLIBOBJS\@:closefrom.lo dlopen.lo fnmatch.lo getcwd.lo getgrouplist.lo getline.lo getprogname.lo glob.lo isblank.lo memrchr.lo mksiglist.lo mktemp.lo nanosleep.lo setenv.lo siglist.lo snprintf.lo strlcat.lo strlcpy.lo strsignal.lo unsetenv.lo utimes.lo globtest.o fnm_test.o:; + $makefile =~ s:\@LTLIBOBJS\@:closefrom.lo dlopen.lo fnmatch.lo getcwd.lo getgrouplist.lo getline.lo getprogname.lo glob.lo isblank.lo memrchr.lo mksiglist.lo mktemp.lo nanosleep.lo pw_dup.lo siglist.lo snprintf.lo strlcat.lo strlcpy.lo strsignal.lo utimes.lo globtest.o fnm_test.o:; # Parse OBJS lines my %objs; diff --git a/plugins/sudoers/auth/aix_auth.c b/plugins/sudoers/auth/aix_auth.c index be7ba46cc..7aa5e273b 100644 --- a/plugins/sudoers/auth/aix_auth.c +++ b/plugins/sudoers/auth/aix_auth.c @@ -94,7 +94,7 @@ sudo_aix_cleanup(struct passwd *pw, sudo_auth *auth) debug_decl(sudo_aix_cleanup, SUDO_DEBUG_AUTH) /* Unset AUTHSTATE as it may not be correct for the runas user. */ - unsetenv("AUTHSTATE"); + sudo_unsetenv("AUTHSTATE"); debug_return_int(AUTH_SUCCESS); } diff --git a/plugins/sudoers/env.c b/plugins/sudoers/env.c index 725212036..bb421a441 100644 --- a/plugins/sudoers/env.c +++ b/plugins/sudoers/env.c @@ -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; +} diff --git a/plugins/sudoers/ldap.c b/plugins/sudoers/ldap.c index a46ba1519..126a4858a 100644 --- a/plugins/sudoers/ldap.c +++ b/plugins/sudoers/ldap.c @@ -2075,7 +2075,7 @@ sudo_ldap_bind_s(LDAP *ld) DPRINTF(("gss_krb5_ccache_name() failed: %d", status), 1); } # else - setenv("KRB5CCNAME", ldap_conf.krb5_ccname, true); + sudo_setenv("KRB5CCNAME", ldap_conf.krb5_ccname, true); # endif } rc = ldap_sasl_interactive_bind_s(ld, ldap_conf.binddn, "GSSAPI", @@ -2086,9 +2086,9 @@ sudo_ldap_bind_s(LDAP *ld) DPRINTF(("gss_krb5_ccache_name() failed: %d", status), 1); # else if (old_ccname != NULL) - setenv("KRB5CCNAME", old_ccname, true); + sudo_setenv("KRB5CCNAME", old_ccname, true); else - unsetenv("KRB5CCNAME"); + sudo_unsetenv("KRB5CCNAME"); # endif } if (rc != LDAP_SUCCESS) { @@ -2144,9 +2144,9 @@ sudo_ldap_open(struct sudo_nss *nss) debug_return_int(-1); /* Prevent reading of user ldaprc and system defaults. */ - if (getenv("LDAPNOINIT") == NULL) { + if (sudo_getenv("LDAPNOINIT") == NULL) { ldapnoinit = true; - setenv("LDAPNOINIT", "1", true); + sudo_setenv("LDAPNOINIT", "1", true); } /* Connect to LDAP server */ @@ -2165,7 +2165,7 @@ sudo_ldap_open(struct sudo_nss *nss) } if (ldapnoinit) - unsetenv("LDAPNOINIT"); + sudo_unsetenv("LDAPNOINIT"); /* Set LDAP options */ if (sudo_ldap_set_options(ld) < 0) diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 936298990..7efb8df12 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -697,7 +697,10 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], *command_infop = command_info; *argv_out = edit_argv ? edit_argv : NewArgv; - *user_env_out = env_get(); /* our private copy */ + + /* Get private version of the environment and zero out stashed copy. */ + *user_env_out = env_get(); + env_init(NULL); goto done; @@ -1539,6 +1542,31 @@ create_admin_success_flag(void) } #endif /* USE_ADMIN_FLAG */ +static void +sudoers_policy_register_hooks(int version, int (*register_hook)(struct sudo_hook *hook)) +{ + struct sudo_hook hook; + + memset(&hook, 0, sizeof(hook)); + hook.hook_version = SUDO_HOOK_VERSION; + + hook.hook_type = SUDO_HOOK_SETENV; + hook.hook_fn = sudoers_hook_setenv; + register_hook(&hook); + + hook.hook_type = SUDO_HOOK_UNSETENV; + hook.hook_fn = sudoers_hook_unsetenv; + register_hook(&hook); + + hook.hook_type = SUDO_HOOK_GETENV; + hook.hook_fn = sudoers_hook_getenv; + register_hook(&hook); + + hook.hook_type = SUDO_HOOK_PUTENV; + hook.hook_fn = sudoers_hook_putenv; + register_hook(&hook); +} + struct policy_plugin sudoers_policy = { SUDO_POLICY_PLUGIN, SUDO_API_VERSION, @@ -1549,5 +1577,6 @@ struct policy_plugin sudoers_policy = { sudoers_policy_list, sudoers_policy_validate, sudoers_policy_invalidate, - sudoers_policy_init_session + sudoers_policy_init_session, + sudoers_policy_register_hooks }; diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index 9da734d41..07a548128 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -310,6 +310,13 @@ void insert_env_vars(char * const envp[]); void read_env_file(const char *, int); void rebuild_env(void); void validate_env_vars(char * const envp[]); +int sudo_setenv(const char *var, const char *val, int overwrite); +int sudo_unsetenv(const char *var); +char *sudo_getenv(const char *name); +int sudoers_hook_getenv(const char *name, char **value, void *closure); +int sudoers_hook_putenv(char *string, void *closure); +int sudoers_hook_setenv(const char *name, const char *value, int overwrite, void *closure); +int sudoers_hook_unsetenv(const char *name, void *closure); /* fmt_string.c */ char *fmt_string(const char *, const char *); diff --git a/src/Makefile.in b/src/Makefile.in index cf89c8562..67e8bfe57 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -72,9 +72,9 @@ SHELL = @SHELL@ PROGS = @PROGS@ -OBJS = conversation.o error.o exec.o exec_common.o exec_pty.o get_pty.o \ - net_ifs.o load_plugins.o parse_args.o sudo.o sudo_edit.o tgetpass.o \ - ttyname.o ttysize.o utmp.o @SUDO_OBJS@ +OBJS = conversation.o env_hooks.o error.o exec.o exec_common.o exec_pty.o \ + get_pty.o hooks.o net_ifs.o load_plugins.o parse_args.o sudo.o \ + sudo_edit.o tgetpass.o ttyname.o ttysize.o utmp.o @SUDO_OBJS@ LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/ @@ -158,6 +158,14 @@ conversation.o: $(srcdir)/conversation.c $(top_builddir)/config.h \ $(incdir)/sudo_debug.h $(incdir)/gettext.h \ $(incdir)/sudo_plugin.h $(srcdir)/sudo_plugin_int.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/conversation.c +env_hooks.o: $(srcdir)/env_hooks.c $(top_builddir)/config.h \ + $(top_srcdir)/compat/dlfcn.h $(srcdir)/sudo.h \ + $(top_builddir)/pathnames.h $(top_srcdir)/compat/stdbool.h \ + $(incdir)/missing.h $(incdir)/alloc.h $(incdir)/error.h \ + $(incdir)/fileops.h $(incdir)/list.h $(incdir)/sudo_conf.h \ + $(incdir)/list.h $(incdir)/sudo_debug.h $(incdir)/gettext.h \ + $(incdir)/sudo_plugin.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/env_hooks.c error.o: $(srcdir)/error.c $(top_builddir)/config.h $(incdir)/missing.h \ $(incdir)/error.h $(incdir)/gettext.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/error.c @@ -190,6 +198,14 @@ get_pty.o: $(srcdir)/get_pty.c $(top_builddir)/config.h $(srcdir)/sudo.h \ $(incdir)/fileops.h $(incdir)/list.h $(incdir)/sudo_conf.h \ $(incdir)/list.h $(incdir)/sudo_debug.h $(incdir)/gettext.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/get_pty.c +hooks.o: $(srcdir)/hooks.c $(top_builddir)/config.h $(srcdir)/sudo.h \ + $(top_builddir)/pathnames.h $(top_srcdir)/compat/stdbool.h \ + $(incdir)/missing.h $(incdir)/alloc.h $(incdir)/error.h \ + $(incdir)/fileops.h $(incdir)/list.h $(incdir)/sudo_conf.h \ + $(incdir)/list.h $(incdir)/sudo_debug.h $(incdir)/gettext.h \ + $(incdir)/sudo_plugin.h $(srcdir)/sudo_plugin_int.h \ + $(incdir)/sudo_debug.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/hooks.c load_plugins.o: $(srcdir)/load_plugins.c $(top_builddir)/config.h \ $(top_srcdir)/compat/dlfcn.h $(srcdir)/sudo.h \ $(top_builddir)/pathnames.h $(top_srcdir)/compat/stdbool.h \ diff --git a/src/env_hooks.c b/src/env_hooks.c new file mode 100644 index 000000000..c31b5e6c6 --- /dev/null +++ b/src/env_hooks.c @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2010, 2012 Todd C. Miller + * + * 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 + +#include + +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS) +# include +#endif /* HAVE_MALLOC_H && !STDC_HEADERS */ +#include +#ifdef HAVE_DLOPEN +# include +#else +# include "compat/dlfcn.h" +#endif + +#include "sudo.h" +#include "sudo_plugin.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; +} + +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: { +#if defined(HAVE_DLOPEN) + char * (*fn)(const char *); + + fn = dlsym(RTLD_NEXT, "getenv"); + if (fn != NULL) + return fn(name); +#endif /* HAVE_DLOPEN */ + return rpl_getenv(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 = 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++] = string; + envp[env_len] = NULL; + priv_environ = environ = envp; + } + return 0; +} + +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: { +#if defined(HAVE_DLOPEN) + int (*fn)(PUTENV_CONST char *); + + fn = dlsym(RTLD_NEXT, "putenv"); + if (fn != NULL) + return fn(string); +#endif /* HAVE_DLOPEN */ + return rpl_putenv(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); +} + +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: { +#if defined(HAVE_SETENV) && defined(HAVE_DLOPEN) + int (*fn)(const char *, const char *, int); + + fn = dlsym(RTLD_NEXT, "setenv"); + if (fn != NULL) + return fn(var, val, overwrite); +#endif /* HAVE_SETENV && HAVE_DLOPEN */ + return rpl_setenv(var, val, overwrite); + } + } +} + +#ifdef UNSETENV_VOID +static void +#else +int +#endif +rpl_unsetenv(const char *var) +{ + char **ep = environ; + size_t len; + + if (var == NULL || *var == '\0' || strchr(var, '=') != NULL) { + errno = EINVAL; +#ifdef UNSETENV_VOID + return; +#else + return -1; +#endif + } + + 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++; + } + } +#ifndef UNSETENV_VOID + return 0; +#endif +} + +#ifdef UNSETENV_VOID +void +unsetenv(const char *var) +{ + switch (process_hooks_unsetenv(var)) { + case SUDO_HOOK_RET_STOP: + return 0; + case SUDO_HOOK_RET_ERROR: + return -1; + default: { +#if defined(HAVE_UNSETENV) && defined(HAVE_DLOPEN) + void (*fn)(const char *); + + fn = dlsym(RTLD_NEXT, "unsetenv"); + if (fn != NULL) + fn(var); + else +#endif /* HAVE_UNSETENV && HAVE_DLOPEN */ + rpl_unsetenv(var); + } + } +} +#else +int +unsetenv(const char *var) +{ + switch (process_hooks_unsetenv(var)) { + case SUDO_HOOK_RET_STOP: + return 0; + case SUDO_HOOK_RET_ERROR: + return -1; + default: { +#if defined(HAVE_UNSETENV) && defined(HAVE_DLOPEN) + int (*fn)(const char *); + + fn = dlsym(RTLD_NEXT, "unsetenv"); + if (fn != NULL) + return fn(var); +#endif /* HAVE_UNSETENV && HAVE_DLOPEN */ + return rpl_unsetenv(var); + } + } +} +#endif /* UNSETENV_VOID */ diff --git a/src/hooks.c b/src/hooks.c new file mode 100644 index 000000000..e44bfcb23 --- /dev/null +++ b/src/hooks.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2012 Todd C. Miller + * + * 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 + +#include +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ + +#include "sudo.h" +#include "sudo_plugin.h" +#include "sudo_plugin_int.h" +#include "sudo_debug.h" + +/* XXX - autogen from config file? */ +/* XXX - implement deregister_hook */ + +/* HOOK: setenv */ + +static struct sudo_hook_setenv { + struct sudo_hook_setenv *next; + sudo_hook_fn_setenv_t hook_fn; + void *closure; +} *sudo_hook_setenv_list; + +static void +register_hook_setenv(int (*hook_fn)(), void *closure) +{ + struct sudo_hook_setenv *hook; + debug_decl(add_hook_setenv, SUDO_DEBUG_HOOKS) + + hook = emalloc(sizeof(*hook)); + hook->hook_fn = (sudo_hook_fn_setenv_t)hook_fn; + hook->closure = closure; + hook->next = sudo_hook_setenv_list; + sudo_hook_setenv_list = hook; + + debug_return; +} + +int +process_hooks_setenv(const char *name, const char *value, int overwrite) +{ + struct sudo_hook_setenv *hook; + int rc = SUDO_HOOK_RET_NEXT; + debug_decl(process_hooks_setenv, SUDO_DEBUG_HOOKS) + + /* First process the hooks. */ + for (hook = sudo_hook_setenv_list; hook != NULL; hook = hook->next) { + rc = hook->hook_fn(name, value, overwrite, hook->closure); + switch (rc) { + case SUDO_HOOK_RET_NEXT: + break; + case SUDO_HOOK_RET_ERROR: + case SUDO_HOOK_RET_STOP: + goto done; + default: + warningx("invalid setenv hook return value: %d", rc); + break; + } + } +done: + debug_return_int(rc); +} + +/* HOOK: putenv */ + +static struct sudo_hook_putenv { + struct sudo_hook_putenv *next; + sudo_hook_fn_putenv_t hook_fn; + void *closure; +} *sudo_hook_putenv_list; + +static void +register_hook_putenv(int (*hook_fn)(), void *closure) +{ + struct sudo_hook_putenv *hook; + debug_decl(add_hook_putenv, SUDO_DEBUG_HOOKS) + + hook = emalloc(sizeof(*hook)); + hook->hook_fn = (sudo_hook_fn_putenv_t)hook_fn; + hook->closure = closure; + hook->next = sudo_hook_putenv_list; + sudo_hook_putenv_list = hook; + + debug_return; +} + +int +process_hooks_putenv(char *string) +{ + struct sudo_hook_putenv *hook; + int rc = SUDO_HOOK_RET_NEXT; + debug_decl(process_hooks_putenv, SUDO_DEBUG_HOOKS) + + /* First process the hooks. */ + for (hook = sudo_hook_putenv_list; hook != NULL; hook = hook->next) { + rc = hook->hook_fn(string, hook->closure); + switch (rc) { + case SUDO_HOOK_RET_NEXT: + break; + case SUDO_HOOK_RET_ERROR: + case SUDO_HOOK_RET_STOP: + goto done; + default: + warningx("invalid putenv hook return value: %d", rc); + break; + } + } +done: + debug_return_int(rc); +} + +/* HOOK: getenv */ + +static struct sudo_hook_getenv { + struct sudo_hook_getenv *next; + sudo_hook_fn_getenv_t hook_fn; + void *closure; +} *sudo_hook_getenv_list; + +static void +register_hook_getenv(int (*hook_fn)(), void *closure) +{ + struct sudo_hook_getenv *hook; + debug_decl(add_hook_putenv, SUDO_DEBUG_HOOKS) + + hook = emalloc(sizeof(*hook)); + hook->hook_fn = (sudo_hook_fn_getenv_t)hook_fn; + hook->closure = closure; + hook->next = sudo_hook_getenv_list; + sudo_hook_getenv_list = hook; + + debug_return; +} + +int +process_hooks_getenv(const char *name, char **value) +{ + struct sudo_hook_getenv *hook; + char *val = NULL; + int rc = SUDO_HOOK_RET_NEXT; + debug_decl(process_hooks_getenv, SUDO_DEBUG_HOOKS) + + /* First process the hooks. */ + for (hook = sudo_hook_getenv_list; hook != NULL; hook = hook->next) { + rc = hook->hook_fn(name, &val, hook->closure); + switch (rc) { + case SUDO_HOOK_RET_NEXT: + break; + case SUDO_HOOK_RET_ERROR: + case SUDO_HOOK_RET_STOP: + goto done; + default: + warningx("invalid getenv hook return value: %d", rc); + break; + } + } +done: + if (val != NULL) + *value = val; + debug_return_int(rc); +} + +/* HOOK: unsetenv */ + +static struct sudo_hook_unsetenv { + struct sudo_hook_unsetenv *next; + sudo_hook_fn_unsetenv_t hook_fn; + void *closure; +} *sudo_hook_unsetenv_list; + +static void +register_hook_unsetenv(int (*hook_fn)(), void *closure) +{ + struct sudo_hook_unsetenv *hook; + debug_decl(add_hook_unsetenv, SUDO_DEBUG_HOOKS) + + hook = emalloc(sizeof(*hook)); + hook->hook_fn = (sudo_hook_fn_unsetenv_t)hook_fn; + hook->closure = closure; + hook->next = sudo_hook_unsetenv_list; + sudo_hook_unsetenv_list = hook; + + debug_return; +} + +int +process_hooks_unsetenv(const char *name) +{ + struct sudo_hook_unsetenv *hook; + int rc = SUDO_HOOK_RET_NEXT; + debug_decl(process_hooks_unsetenv, SUDO_DEBUG_HOOKS) + + /* First process the hooks. */ + for (hook = sudo_hook_unsetenv_list; hook != NULL; hook = hook->next) { + rc = hook->hook_fn(name, hook->closure); + switch (rc) { + case SUDO_HOOK_RET_NEXT: + break; + case SUDO_HOOK_RET_ERROR: + case SUDO_HOOK_RET_STOP: + goto done; + default: + warningx("invalid unsetenv hook return value: %d", rc); + break; + } + } +done: + debug_return_int(rc); +} + +/* Register the specified hook. */ +int +register_hook(struct sudo_hook *hook) +{ + int rval = 0; + debug_decl(register_hook, SUDO_DEBUG_HOOKS) + + if (SUDO_HOOK_VERSION_GET_MAJOR(hook->hook_version) != SUDO_HOOK_VERSION_MAJOR) { + /* Major versions must match. */ + rval = -1; + } else { + switch (hook->hook_type) { + case SUDO_HOOK_GETENV: + register_hook_getenv(hook->hook_fn, hook->closure); + break; + case SUDO_HOOK_PUTENV: + register_hook_putenv(hook->hook_fn, hook->closure); + break; + case SUDO_HOOK_SETENV: + register_hook_setenv(hook->hook_fn, hook->closure); + break; + case SUDO_HOOK_UNSETENV: + register_hook_unsetenv(hook->hook_fn, hook->closure); + break; + default: + /* XXX - use define for unknown value */ + rval = 1; + break; + } + } + + debug_return_int(rval); +} diff --git a/src/load_plugins.c b/src/load_plugins.c index 7b0f5e50e..d85e08910 100644 --- a/src/load_plugins.c +++ b/src/load_plugins.c @@ -155,6 +155,16 @@ sudo_load_plugins(struct plugin_container *policy_plugin, goto done; } + /* Install hooks (XXX - later). */ + if (policy_plugin->u.policy->version >= SUDO_API_MKVERSION(1, 2)) { + if (policy_plugin->u.policy->register_hooks != NULL) + policy_plugin->u.policy->register_hooks(SUDO_HOOK_VERSION, register_hook); + tq_foreach_fwd(io_plugins, container) { + if (container->u.io->register_hooks != NULL) + container->u.io->register_hooks(SUDO_HOOK_VERSION, register_hook); + } + } + rval = true; done: diff --git a/src/sudo.c b/src/sudo.c index 7721f4aa7..a0c94fd05 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -844,6 +844,13 @@ exec_setup(struct command_details *details, const char *ptyname, int ptyfd) aix_restoreauthdb(); #endif + /* + * Swap in the plugin-supplied environment in case session init + * modifies the environment. Also needed for LOGIN_SETENV. + * This is kind of a hack. + */ + environ = details->envp; + /* * Call policy plugin's session init before other setup occurs. * The session init code is expected to print an error as needed. @@ -886,8 +893,6 @@ exec_setup(struct command_details *details, const char *ptyname, int ptyfd) flags = LOGIN_SETALL; CLR(flags, LOGIN_SETGROUP|LOGIN_SETLOGIN|LOGIN_SETUSER); CLR(details->flags, CD_SET_UMASK); /* LOGIN_UMASK instead */ - /* Swap in the plugin-supplied environment for LOGIN_SETENV */ - environ = details->envp; } else { flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY; } @@ -898,14 +903,13 @@ exec_setup(struct command_details *details, const char *ptyname, int ptyfd) } else warning(_("unable to set user context")); } - if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) { - /* Stash the updated environment pointer in command details */ - details->envp = environ; - } } #endif /* HAVE_LOGIN_CAP_H */ } + /* Update the environment pointer in command details */ + details->envp = environ; + /* * Set groups, including supplementary group vector. */ diff --git a/src/sudo.h b/src/sudo.h index a5e29bcd8..ec981132f 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -222,6 +222,16 @@ void aix_prep_user(char *user, const char *tty); void aix_restoreauthdb(void); void aix_setauthdb(char *user); +/* hooks.c */ +/* XXX - move to sudo_plugin_int.h? */ +struct sudo_hook; +int register_hook(struct sudo_hook *hook); +int deregister_hook(struct sudo_hook *hook); +int process_hooks_getenv(const char *name, char **val); +int process_hooks_setenv(const char *name, const char *value, int overwrite); +int process_hooks_putenv(char *string); +int process_hooks_unsetenv(const char *name); + /* interfaces.c */ int get_net_ifs(char **addrinfo); diff --git a/src/sudo_plugin_int.h b/src/sudo_plugin_int.h index a3c77313c..62fe31d02 100644 --- a/src/sudo_plugin_int.h +++ b/src/sudo_plugin_int.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2011 Todd C. Miller + * Copyright (c) 2010-2012 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above