#632868 base-files: derive PATH in /etc/profile from /etc/login.defs

Package:
base-files
Source:
base-files
Description:
Debian base system miscellaneous files
Submitter:
"Georgios M. Zarkadas"
Date:
2024-04-15 14:39:03 UTC
Severity:
normal
Tags:
#632868#5
Date:
2011-07-06 16:19:03 UTC
From:
To:

This is a solution to bug #571086 (now closed) which keeps /etc/login.defs as
the only place to set PATH, by computing the set there value on the fly using
only grep and coreutils (both essential packages and thus guaranteed to be
always present).

The benefit of using it is that there is no need to sync the two PATH values;
any change to /etc/login.defs will be immediately applied to subsequent logins.

regards
George Zarkadas

- -- System Information:
Debian Release: 6.0.2
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'proposed-updates'), (500, 'stable'), (450, 'testing-proposed-updates'), (450, 'testing'), (400, 'unstable')
Architecture: amd64 (x86_64)

Kernel: Linux 2.6.32-5-amd64 (SMP w/4 CPU cores)
Locale: LANG=el_GR.utf8, LC_CTYPE=el_GR.utf8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/bash

Versions of packages base-files depends on:
ii  gawk [awk]                1:3.1.7.dfsg-5 GNU awk, a pattern scanning and pr
ii  mawk [awk]                1.3.3-15       a pattern scanning and text proces

base-files recommends no packages.

base-files suggests no packages.

- -- no debconf information
iQEcBAEBAgAGBQJOFIriAAoJEJWXIVmJ5BwWy4UH/RX8WAriNj/tVBXXdlr9rfOh
zvAkDynCJygZZuyKa44cA8taNzpiSnphQOgtoWufbxt6TNN637G037HJQmBGKkjy
ELzT3tcO3SpEIxX+m7+QgWhbc04Or/p96Khmy5xbyAqw1bUl9XK37EAlrj3j3IyN
8aAjbvnYaT3rASqX1oxlYSdCJBcaL0RATcBP9PMvmG8VJxWDDAbUV9PfGy5PG1HA
fSJtDYlcOEy71PqF5Ojr/Tbh9PUR5eVgn9sD/yqETgXgdW0O0y3VGF0j6u57eRfF
PF05H9wneOxt9hkfWEdykcUdtSDo9+/9f/lZ2yzntM482EGY4vBWSEOjL5Iy4so=
=/I6N
-----END PGP SIGNATURE-----

#632868#10
Date:
2015-01-24 18:15:42 UTC
From:
To:
	pathkey=ENV_PATH
	if [ "$(id -u)" -eq 0 ]
	then
		pathkey=ENV_SUPATH
	fi

	ifs=${IFS+_$IFS}
	unset IFS
	while read -r key val
	do
		case "$key" in
		"$pathkey")
			# the "PATH=" prefix is optional
			PATH=${val#PATH=}
			export PATH
			;;
		UMASK)  # may want to handle this too
			umask "$val"
			;;
		esac
	done < /etc/login.defs
	[ -z "$ifs" ] || IFS=${ifs#?}
	unset ifs pathkey key val

The uid check could also be changed, to avoid a subshell and to help
with the case where 'id' isn't found in $PATH:
	if [ -f /proc/1/environ ]
	then
		if [ -r /proc/1/environ ]
		then
			pathkey=ENV_SUPATH
		fi
	elif [ "$(id -u)" -eq 0 ]
	then
		pathkey=ENV_SUPATH
	fi
or (with non-bash still using a subshell):
	if [ "${EUID:-$(id -u)}" -eq 0 ]

#632868#15
Date:
2015-03-28 04:59:09 UTC
From:
To:
Hey.

Georgios, your suggestion has some problems:
- it doesn't take care of (accidental) multiple definitions of these
  settings (in which case obviously the last one wins).
- apparently variables set in login.defs may start with whitespace.
- according to login.defs(5), PATH= may be omitted, in which case your
  patch breaks.
- it overwrites the PATH when already set, which IMHO it shouldn't



Michael,
- your 2nd version uses /proc, thus it wouldn't work on non-Linuxes and
  therefore I don't even comment to it further.
- I'm afraid I don't understand what you do with the IFS and it seems
  overly complex:
  - What's "ifs=${IFS+_$IFS}" some bashism? And what if the user's
    calling environment would have "ifs" and/or _$IFS already used
    somehow?
    Same for pathkey key val ... these don't seem like names that noone
    would ever use.
  - since you probably just set it for the read, why not:
    IFS="" read -r ...
    ?
  - you have the same problems as Georgios above, at least the thing
    with the whitespace



I'd propose the following solution, which addresses the above problem
with only one "disadvantage":
- I call out 4 processes, but only in the case that PATH isn't already
  set, which should basically never happen, as login (or maybe even PAM
  on some systems) already does that for us.



PATH="${PATH:-"$( { /bin/sed -n "s/^[[:space:]]*ENV_$(if [ "$(/usr/bin/id -u)" = 0 ]; then /usr/bin/printf SU; fi)PATH[[:space:]][[:space:]]*\(\|PATH=\)\(.*\)$/\2/p" /etc/login.defs | /usr/bin/tail -n 1; } 2> /dev/null )"}"



- POSIX sh compatible
  AFAICS, I also don't use any non POSIX options to the POSIX tools.
- The tools I call: sed, id, printf, tail
  are all from essential+required packages, and even busybox contains
  them.
- PATH isn't overwritten if already set (but it is if unset or "")
- leading whitespace accepted
- with the tail I use the last definition of the setting
- full paths to the tools are required, since the current PATH may be
  bogus, unset or ""
- any errors are sent to /dev/null (but it's unlikely that this would
  ever fail, probably only when the tools are not found or no memory is
  left.
  And even then, the worst likely thing is that an unset/"" PATH is
  overwritten with a "" PATH

If Santiago likes one could even add another layer of ${:-foo} around,
which kicks in if no PATH info is found from login.defs, and then again
sets the hardcoded PATH as now... or even look at other sources first
(are there any?)

The only problem then is that one likely needs to invoke id more often.
Using a local e.g. in a function
determine_path()
{
   local UID="$(/usr/bin/id -u)"
   #plus more voodoo
}
wouldn't solve the problem, a) "local" is strictly speaking not POSIX,
IIRC, b) one would overwrite a function the user might have possible set
outside.
A real solution would be to don't set it, but do everything from within
a (); subshell, e.g.
PATH="$( ( \
       UID="$(/usr/bin/id -u)"
       #plus more voodoo
       prtinf "some path in the end"
     ) )"
Don't forget the spaces between the braces,... otherwise it would try
arithmetic expansion.


Cheers,
Chris.

#632868#18
Date:
2015-03-28 04:59:09 UTC
From:
To:
Hey.

Georgios, your suggestion has some problems:
- it doesn't take care of (accidental) multiple definitions of these
  settings (in which case obviously the last one wins).
- apparently variables set in login.defs may start with whitespace.
- according to login.defs(5), PATH= may be omitted, in which case your
  patch breaks.
- it overwrites the PATH when already set, which IMHO it shouldn't



Michael,
- your 2nd version uses /proc, thus it wouldn't work on non-Linuxes and
  therefore I don't even comment to it further.
- I'm afraid I don't understand what you do with the IFS and it seems
  overly complex:
  - What's "ifs=${IFS+_$IFS}" some bashism? And what if the user's
    calling environment would have "ifs" and/or _$IFS already used
    somehow?
    Same for pathkey key val ... these don't seem like names that noone
    would ever use.
  - since you probably just set it for the read, why not:
    IFS="" read -r ...
    ?
  - you have the same problems as Georgios above, at least the thing
    with the whitespace



I'd propose the following solution, which addresses the above problem
with only one "disadvantage":
- I call out 4 processes, but only in the case that PATH isn't already
  set, which should basically never happen, as login (or maybe even PAM
  on some systems) already does that for us.



PATH="${PATH:-"$( { /bin/sed -n "s/^[[:space:]]*ENV_$(if [ "$(/usr/bin/id -u)" = 0 ]; then /usr/bin/printf SU; fi)PATH[[:space:]][[:space:]]*\(\|PATH=\)\(.*\)$/\2/p" /etc/login.defs | /usr/bin/tail -n 1; } 2> /dev/null )"}"



- POSIX sh compatible
  AFAICS, I also don't use any non POSIX options to the POSIX tools.
- The tools I call: sed, id, printf, tail
  are all from essential+required packages, and even busybox contains
  them.
- PATH isn't overwritten if already set (but it is if unset or "")
- leading whitespace accepted
- with the tail I use the last definition of the setting
- full paths to the tools are required, since the current PATH may be
  bogus, unset or ""
- any errors are sent to /dev/null (but it's unlikely that this would
  ever fail, probably only when the tools are not found or no memory is
  left.
  And even then, the worst likely thing is that an unset/"" PATH is
  overwritten with a "" PATH

If Santiago likes one could even add another layer of ${:-foo} around,
which kicks in if no PATH info is found from login.defs, and then again
sets the hardcoded PATH as now... or even look at other sources first
(are there any?)

The only problem then is that one likely needs to invoke id more often.
Using a local e.g. in a function
determine_path()
{
   local UID="$(/usr/bin/id -u)"
   #plus more voodoo
}
wouldn't solve the problem, a) "local" is strictly speaking not POSIX,
IIRC, b) one would overwrite a function the user might have possible set
outside.
A real solution would be to don't set it, but do everything from within
a (); subshell, e.g.
PATH="$( ( \
       UID="$(/usr/bin/id -u)"
       #plus more voodoo
       prtinf "some path in the end"
     ) )"
Don't forget the spaces between the braces,... otherwise it would try
arithmetic expansion.


Cheers,
Chris.

#632868#23
Date:
2015-03-28 05:04:09 UTC
From:
To:
Oh and I hope this isn't considered rude, but I think this really is a
bug and not just a whislist, since it breaks the well defined behaviour
of login(1) setting the PATH on most Linuxes
:-)

Cheers,
Chris.

#632868#30
Date:
2015-03-28 05:14:26 UTC
From:
To:
And sorry for the noise, but it came just something to my mind:
PATH="${PATH:-"$( { /bin/sed -n "s/^[[:space:]]*ENV_$(if [ "$(/usr/bin/id -u)" = 0 ]; then /usr/bin/printf SU; fi)PATH[[:space:]][[:space:]]*\(\|PATH=\)\(.*\)$/\2/p" /etc/login.defs | /usr/bin/tail -n 1; } 2> /dev/null )"}"
can be improved even more to:
PATH="${PATH:-"$( { PATH=/bin sed -n "s/^[[:space:]]*ENV_$(if [ "$(PATH=/usr/bin id -u)" = 0 ]; then PATH=/usr/bin printf SU; fi)PATH[[:space:]][[:space:]]*\(\|PATH=\)\(.*\)$/\2/p" /etc/login.defs | PATH=/usr/bin tail -n 1; } 2> /dev/null )"}"

The advantage is over using the full path to the programs is:
In many cases, these are implemented as built-ins, so we'd actually save
the fork and use the in-shell version.
This applies at least to busybox and bash (where printf is builtin).

Oh and yes, I use printf over echo.
I don't think printf is slower than echo, and echo is stricly speaking
deprecated by POSIX. ;)



Cheers,
Chris.

#632868#33
Date:
2015-03-28 05:14:26 UTC
From:
To:
And sorry for the noise, but it came just something to my mind:
PATH="${PATH:-"$( { /bin/sed -n "s/^[[:space:]]*ENV_$(if [ "$(/usr/bin/id -u)" = 0 ]; then /usr/bin/printf SU; fi)PATH[[:space:]][[:space:]]*\(\|PATH=\)\(.*\)$/\2/p" /etc/login.defs | /usr/bin/tail -n 1; } 2> /dev/null )"}"
can be improved even more to:
PATH="${PATH:-"$( { PATH=/bin sed -n "s/^[[:space:]]*ENV_$(if [ "$(PATH=/usr/bin id -u)" = 0 ]; then PATH=/usr/bin printf SU; fi)PATH[[:space:]][[:space:]]*\(\|PATH=\)\(.*\)$/\2/p" /etc/login.defs | PATH=/usr/bin tail -n 1; } 2> /dev/null )"}"

The advantage is over using the full path to the programs is:
In many cases, these are implemented as built-ins, so we'd actually save
the fork and use the in-shell version.
This applies at least to busybox and bash (where printf is builtin).

Oh and yes, I use printf over echo.
I don't think printf is slower than echo, and echo is stricly speaking
deprecated by POSIX. ;)



Cheers,
Chris.

#632868#38
Date:
2015-03-28 19:52:11 UTC
From:
To:
Absolutely no way, this is completely insane. The file /etc/profile
*must* be human readable, not a collection of clever hacks designed to
avoid touching /etc/profile as if it were read only.

Moreover, people do not change the PATH so often, so having to change /etc/profile is not a big deal.

If you insist that this is a bug, then it will remain "unfixed".

(But I might better eventually close it so that everybody stop suggesting obfuscating /etc/profile)

Thanks.

#632868#41
Date:
2015-03-28 19:52:11 UTC
From:
To:
Absolutely no way, this is completely insane. The file /etc/profile
*must* be human readable, not a collection of clever hacks designed to
avoid touching /etc/profile as if it were read only.

Moreover, people do not change the PATH so often, so having to change /etc/profile is not a big deal.

If you insist that this is a bug, then it will remain "unfixed".

(But I might better eventually close it so that everybody stop suggesting obfuscating /etc/profile)

Thanks.

#632868#46
Date:
2015-03-28 20:29:37 UTC
From:
To:
uhm... thank you? .... o.O
I didn't see the above to be byte-code,.. and human readable is always
relatively...

But of course I agree that my proposal is a bit more complex than the
average shell code.
OTOH, adding a comment like
should be simply enough for like 98% of all users.

And I wouldn't say it's particularly more readable to add a large set of
shell functions.
Nevertheless, it screws up in many places where the PATH is already set
from higher levels, ranging from login(1) over e.g. when one would sent
his PATH via e.g. ssh.
In the end I do not care how we call it... call it "fixing it" or
"improving it",... what's the difference?
No one calls this a "bug" just in order to blame you personally :P

But as you can see from the above examples it actually influences things
in an annoying way.

If you don't like doing it in the right way that works in all
situations, fine... but then do at least something like:
if [ -z "$PATH" ]; then
  if [ "`/usr/bin/id -u`" -eq 0 ]; then
    PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  else
    PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
  fi
fi

While IMHO this is still ugly, as it introduces yet another location
where a default value for a base variable is defined in Debian (and see
all the issues we've had with /etc/environment and how long it takes us
to really fully get rid of it), it at least doesn't break things if the
PATH is already set :)
Well I don't think that closing a bug, which actually causes breakages
and for which there are simple fixes (like e.g. not setting the PATH at
all in /etc/profile, as this is not really its job) is an appropriate
way to deal with things.


Best wishes,
Chris.

#632868#51
Date:
2024-04-15 11:08:30 UTC
From:
To:
Hi. Let's see what we should do to remove PATH from /etc/profile.

Ubuntu did that a lot of time ago and this is their changelog:

base-files (3.1.9ubuntu3) dapper; urgency=low

   * Implement OneTruePathSpec:
   * share/profile: Remove PATH setting.
   * debian/control: Add dependency libpam-modules (>= 0.79-3ubuntu3) so that
     the user does not end up without any $PATH at all.

So, before I go ahead, I'd like to be sure that things will not break horribly
for somebody. Do we need any kind of dependency like the one on libpam-modules
above? (It's no longer in their current debian/control file).

Can we also drop PATH from /etc/profile in non-Linux systems like hurd-i386?
(I'm Cc:ing Samuel for that).

Thanks.

#632868#56
Date:
2024-04-15 12:34:00 UTC
From:
To:
Hey.

I mostly forgot the details of this issue ^^

What I can say at least (which is of course not a definite answer) is,
that I personally run my systems since quite some years now without
touching PATH in any of .profile, .bashrc, /etc/profile,
/etc/bash.bashrc.


Some people want to add e.g. ~/bin to their PATH, but I think the
default profile didn't do that out of the box, or did it?

I personally found that (adding ~/bin/ anyway) rather unclean, as it
will get inherited by all programs started by my session, but what most
of the time I actually want is some additional commands or overrides
for just my interactive sessions.
So instead of ever adding ~/bin to the PATH, I do set up any executable
in there as an alias (which won't get exported to other shells).


Cheers,
Chris.

#632868#61
Date:
2024-04-15 14:34:04 UTC
From:
To:
Hello,

Santiago Vila, le lun. 15 avril 2024 13:08:30 +0200, a ecrit:

I tried to comment out the PATH definition from /etc/profile on the
exodar porterbox, and it seems to be going fine?

Samuel