Keep environment variables with sudo command.

We have a complicated shell script, and need root permission to finish some operations. But this script may be executed by everyone and we don't want to leak root password, we still don't want everyone to do anything that root can do.
So we can add this script into /etc/sudoers with NOPASSWD.
Ref:
How do I run specific sudo commands without a password?

But the script misses some environment variables when using sudo to execute. We can use -E in command line to keep environment variables in current session, or configure Defaults !env_reset option in /etc/sudoers.
But the PYTHONPATH environment variables is still missing. We can use Defaults env_keep+=PYTHONPATH to keep PYTHONPATH explicitly.
Ref:
Avoid using env_reset in sudoers file

Only commenting Defaults env_reset is useless, because reset environment is default action.

We still want to known why sudo can keep many custom environment variables except PYTHONPATH.
After reading codes, it's because PYTHONPATH is a bad variable. Although we explictly set keep env, these bad variables still will be removed. Except explictly use Defaults env_keep+=PYTHONPATH to keep.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* Merge another environment with our private copy.
* Only overwrite an existing variable if it is not
* being preserved from the user's environment.
* Returns true on success or false on failure.
*/
bool
env_merge(char * const envp[])
{
...

for (ep = envp; *ep != NULL; ep++) {
/* XXX - avoid checking value here, should only check name */
bool overwrite = def_env_reset ? !env_should_keep(*ep) : env_should_delete(*ep);

...
}

In env_should_keep() it will check if ep in bad variables list.

Following list are these bad variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
* Default table of "bad" variables to remove from the environment.
* XXX - how to omit TERMCAP if it starts with '/'?
*/
static const char *initial_badenv_table[] = {
"IFS",
"CDPATH",
"LOCALDOMAIN",
"RES_OPTIONS",
"HOSTALIASES",
"NLSPATH",
"PATH_LOCALE",
"LD_*",
"_RLD*",
#ifdef __hpux
"SHLIB_PATH",
#endif /* __hpux */
#ifdef _AIX
"LDR_*",
"LIBPATH",
"AUTHSTATE",
#endif
#ifdef __APPLE__
"DYLD_*",
#endif
#ifdef HAVE_KERB5
"KRB5_CONFIG*",
"KRB5_KTNAME",
#endif /* HAVE_KERB5 */
#ifdef HAVE_SECURID
"VAR_ACE",
"USR_ACE",
"DLC_ACE",
#endif /* HAVE_SECURID */
"TERMINFO", /* terminfo, exclusive path to terminfo files */
"TERMINFO_DIRS", /* terminfo, path(s) to terminfo files */
"TERMPATH", /* termcap, path(s) to termcap files */
"TERMCAP", /* XXX - only if it starts with '/' */
"ENV", /* ksh, file to source before script runs */
"BASH_ENV", /* bash, file to source before script runs */
"PS4", /* bash, prefix for lines in xtrace mode */
"GLOBIGNORE", /* bash, globbing patterns to ignore */
"BASHOPTS", /* bash, initial "shopt -s" options */
"SHELLOPTS", /* bash, initial "set -o" options */
"JAVA_TOOL_OPTIONS", /* java, extra command line options */
"PERLIO_DEBUG ", /* perl, debugging output file */
"PERLLIB", /* perl, search path for modules/includes */
"PERL5LIB", /* perl 5, search path for modules/includes */
"PERL5OPT", /* perl 5, extra command line options */
"PERL5DB", /* perl 5, command used to load debugger */
"FPATH", /* ksh, search path for functions */
"NULLCMD", /* zsh, command for null file redirection */
"READNULLCMD", /* zsh, command for null file redirection */
"ZDOTDIR", /* zsh, search path for dot files */
"TMPPREFIX", /* zsh, prefix for temporary files */
"PYTHONHOME", /* python, module search path */
"PYTHONPATH", /* python, search path */
"PYTHONINSPECT", /* python, allow inspection */
"PYTHONUSERBASE", /* python, per user site-packages directory */
"RUBYLIB", /* ruby, library load path */
"RUBYOPT", /* ruby, extra command line options */
"*=()*", /* bash functions */
NULL
};