Initial cut at a hooks implementation. The plugin can register
authorTodd C. Miller <Todd.Miller@courtesan.com>
Wed, 7 Mar 2012 21:35:42 +0000 (16:35 -0500)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Wed, 7 Mar 2012 21:35:42 +0000 (16:35 -0500)
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().

24 files changed:
MANIFEST
aclocal.m4
common/sudo_debug.c
compat/Makefile.in
compat/setenv.c [deleted file]
compat/unsetenv.c [deleted file]
config.h.in
configure
configure.in
include/sudo_debug.h
include/sudo_plugin.h
mkdep.pl
plugins/sudoers/auth/aix_auth.c
plugins/sudoers/env.c
plugins/sudoers/ldap.c
plugins/sudoers/sudoers.c
plugins/sudoers/sudoers.h
src/Makefile.in
src/env_hooks.c [new file with mode: 0644]
src/hooks.c [new file with mode: 0644]
src/load_plugins.c
src/sudo.c
src/sudo.h
src/sudo_plugin_int.h

index ce76320680422756ed3f78841749518a72999974..c932c3feeb73fbb585c324cf8be56b9687e3fe48 100644 (file)
--- 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
index c14b04f07f546dd8bd66e3690723e88a6e3925d4..1a8f2052b795c3b04bc535cd069903a7105314dc 100644 (file)
@@ -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
index de98d0d9028e60d220a8dce6c215dc7e8a2a5047..e105b5d598a0a91265c91cd75ba75f26632e2c86 100644 (file)
@@ -100,6 +100,7 @@ const char *const sudo_debug_subsystems[] = {
     "rbtree",
     "perms",
     "plugin",
+    "hooks",
     NULL
 };
 
index 6b6630e6741a96fe7229771c85f0c7e34dc98bbe..d0361cef021f6a463b1b0b2a201c18c55491facb 100644 (file)
@@ -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 (file)
index cff0c26..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <config.h>
-
-#include <sys/types.h>
-
-#include <stdio.h>
-#ifdef STDC_HEADERS
-# include <stdlib.h>
-# include <stddef.h>
-#else
-# ifdef HAVE_STDLIB_H
-#  include <stdlib.h>
-# endif
-#endif /* STDC_HEADERS */
-#ifdef HAVE_STRING_H
-# include <string.h>
-#endif /* HAVE_STRING_H */
-#ifdef HAVE_STRINGS_H
-# include <strings.h>
-#endif /* HAVE_STRINGS_H */
-#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
-# include <malloc.h>
-#endif /* HAVE_MALLOC_H && !STDC_HEADERS */
-#include <errno.h>
-
-#include "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 (file)
index f7cfaa7..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <config.h>
-
-#include <sys/types.h>
-
-#include <stdio.h>
-#ifdef STDC_HEADERS
-# include <stdlib.h>
-# include <stddef.h>
-#else
-# ifdef HAVE_STDLIB_H
-#  include <stdlib.h>
-# endif
-#endif /* STDC_HEADERS */
-#ifdef HAVE_STRING_H
-# include <string.h>
-#endif /* HAVE_STRING_H */
-#ifdef HAVE_STRINGS_H
-# include <strings.h>
-#endif /* HAVE_STRINGS_H */
-#include <errno.h>
-
-#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
-}
index 7b1991299f3719abd0aee0668f706ce22f8e9c03..4e0a7290c2299a7323aa44c8353a61abe2fb3549 100644 (file)
 /* 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
 
index 18f745fc1457ec7b0e28ae026e430022618ba30f..46dde841d5784983b0faeb14085003c7beff40dd 100755 (executable)
--- 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 :
index 90be2da5c4270f2a7f56cd2cd1e3576c14d9003a..cb77065b04d41e5ff1394f7c1918ad03054ac596 100644 (file)
@@ -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)])
index 81fbfac07da68b994742202bb09100ef72e1d7d5..e2896f7e86d0ece224d59fae738e84f6fee2cd3b 100644 (file)
@@ -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. */
index 1eed2a294ee77b7dc5e431015e0c7a59140b19a1..4269b7158f1ae97c42e235edb9b803136092b4bd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2011 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2009-2012 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -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 */
index 55006ba998b637ce1b902cd873d892166d24b845..3388fd9169e11719b86c0a3632a6ff1e13ca6bd7 100755 (executable)
--- 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;
index be7ba46cc2976be2c3b45065f4b3aca310fb93e2..7aa5e273b7d8134d69bd4a5f569e35263c3327db 100644 (file)
@@ -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);
 }
index 7252120360f380e37e11ef97260623d0fcf5f66e..bb421a441d15f6c43f0d6269306a98334ccff659 100644 (file)
@@ -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;
+}
index a46ba1519032eb858c2652e99cad89015edaa220..126a4858a05d2174a3c2e7610b9eafe834939921 100644 (file)
@@ -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)
index 936298990da7fc504d32e8862533042c5c067725..7efb8df125f5f8f837a073b6e1658900412c784c 100644 (file)
@@ -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
 };
index 9da734d418aa76ced3025f73d4bf1bb1d6f73e8c..07a548128ab1ab0037f9ad6436df82403651d5ff 100644 (file)
@@ -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 *);
index cf89c85621fdf46a5b9dfd710dd7c582d1e07286..67e8bfe5726e930f10620221332282b01cd3dffd 100644 (file)
@@ -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 (file)
index 0000000..c31b5e6
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2010, 2012 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
+# include <malloc.h>
+#endif /* HAVE_MALLOC_H && !STDC_HEADERS */
+#include <errno.h>
+#ifdef HAVE_DLOPEN
+# include <dlfcn.h>
+#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 (file)
index 0000000..e44bfcb
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2012 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#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);
+}
index 7b0f5e50eacf1dfad838f01e92d5fafa5f1fc18d..d85e08910e48a22bb04e174f1c7c8db806fb5f55 100644 (file)
@@ -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:
index 7721f4aa7641b2c06bda18142976d35f163f5e63..a0c94fd058ee9d054c684bd8db94e8b9be044ac0 100644 (file)
@@ -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.
      */
index a5e29bcd816ab766a3c6914c47da83e154939d51..ec981132fd594be0de108f792599911058a784f7 100644 (file)
@@ -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);
 
index a3c77313c7c14b11ad088d3d78f4118045cd2808..62fe31d024100c116cd8dd13b017fd720f27304a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2011 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2010-2012 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above