From: Todd C. Miller Date: Wed, 7 Mar 2012 21:35:42 +0000 (-0500) Subject: Initial cut at a hooks implementation. The plugin can register X-Git-Tag: SUDO_1_8_5~1^2~174 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=37770ecf1ee15fcc6baf4e75c56d0b75feaa5772;p=sudo 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(). --- 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,15 +16768,44 @@ $as_echo "#define UNSETENV_VOID 1" >>confdefs.h fi +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 - case " $LIBOBJS " in - *" unsetenv.$ac_objext "* ) ;; - *) LIBOBJS="$LIBOBJS unsetenv.$ac_objext" - ;; -esac + 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 -done +{ $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 @@ -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