Subject: Re: bug#13738: Add --all option to 'users' command

From: Bob Proulx <[email-masked]>
Date: Sun, 10 Feb 2019 13:23:16 -0700

anatoly techtonik wrote:
> Bob Proulx wrote:
> > > Human users have UIDs starting at 1000,
> >
> > That assumption is incorrect.  Many systems start users off at 100.
> > Many others start users at 500.  There isn't any univerial standard.
> > It is a local system configuration option.
> 
> How to figure out at which number users UIDs start at a given system?

That is a system dependent problem.

On my Debian Stretch 9 system the /etc/login.defs file contains:

  # Min/max values for automatic uid selection in useradd
  #
  UID_MIN                  1000
  UID_MAX                 60000
  # System accounts
  #SYS_UID_MIN              100
  #SYS_UID_MAX              999

Other systems will be different.  It is a policy implemented by the OS.

> > > so you can use that fact to filter out the non-humans:
> > >
> > > cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1
> >
> > This assumes that /etc/passwd is the user database.  While true on a
> > typical standalone system it is incorrect when NIS/yp or LDAP or other
> > account system is in use.  That is why I used 'getent passwd' even
> > though it is not available on all systems.  When available it obeys
> > the local system configuration and returns the correct information.
> 
> If NIS/yp or LDAP are installed, they provide getent, right?

'getent' is actually AFAIK a glibc utility.  AFAIK any OS using glibc
will provide it.  However traditional systems not based on glibc may
or may not.  I only have limited access to other systems at this time
and have no easy way to check *BSD or HP-UX or others for example.

> So if there is no getent, then /etc/passwd is de-facto database and
> can be reliably used as a fallback. Is that correct?

The /etc/nsswitch.conf file determines this.  Certainly the lowest
level default is /etc/passwd.  But the nsswitch.conf file is where
modifications are configured for this.

> Is there other way to distinguish user accounts other than matching
> "things that only seem to be true", like UID numbers?

There is no actual difference between user accounts and system
accounts.  The only real difference is that user accounts have a human
user associated with them but system accounts do not.  Other than that
they are the same.  Certainly to the OS they are simply a uid to hold
a running process.

> > Actually even that isn't sufficient.  The value for nobody 65534 is a
> > traditional value.  But uids may be larger on most modern systems.  It
> > isn't unusual to have the nobody id in the middle of the range with
> > real users having uid numbers both less than and greater than that
> > value.  Therefore in order to be completely correct additional filter
> > methods would be needed such as sets of ranges or block lists or
> > something similar.
> 
> Yes. I believe LXD has UID mapping for containers about 100000,
> and those are not human users in general case.

That is a good example.  And one of which I was not aware.  And I am
sure there are other cases too.

> I am getting the feeling that the approach of solving problems be using
> the tool for specific case is misleading in the case that it battles with
> effects and not the causes. The cause of the mess if UID mapping in
> Linux kernel, which is not about users at all. There is a concept of user
> space, but correct me if I wrong - daemons that run with different UIDs
> are run in their own userspace as well. The user concept is not defined
> by kernel, but rather by some concept of having home and being able to
> login into it either from console or remotely.

All processes have a uid.  Some uids are associated with a human.
Some are not.  The kernel doesn't know the difference.  The kernel is
applying permissions based upon nothing more than the integer number
of the process.  For example the uid can send a signal to another
process with the same uid.  Or the superuser process can send a signal
to any other process regardless of uid.  But a non-superuser process
cannot send a signal to another random process of a different uid.
None of which has any relation to whether a human can log into the
account or not.

> If this behavior of humans vs daemons was explicitly documented
> somewhere, it could lead to better understanding if solving this problem
> in general is real.

I don't think this is possible because there really is no difference
between system uids and non-system uids.  Whether something is a
system uid or a non-system uid is a color we paint on it by human
judgement and two different people might judge the same thing
differently and both would be right.

It is also a difference which makes no difference.

> > It would help if you could say a few words about the case in
> > which this would be helpful?
> 
> Sorry that I've missed the reply. The case is to see if there are any
> alive users on the systems I manage or help with. An easy way to
> check servers for accounts left from former developers.

You are looking for a list of stale accounts that might be removed?

If I were doing such a thing I would look at users with a $HOME
directory in the known /home directory path.  (Noting that even that
is not universial.  It's a new convention in the grand scheme of
things.  /users was previously the ad-hoc standard before /home.  Also
I know many systems where the admin has configured $HOME locations to
exist in /u or elsewhere.)  I would create a script program that
looked at all accounts with a home in the user home directory.  That
would be my working list.  Then from that I would look at the
timestamp of the home directory and look for very old accounts.
Because logging into the system has so many effects such as updating
the shell .*history files and other things the home directory is
almost always updated.  This is a very ad-hoc approach but one that I
think would be very effective.  Especially if it were simply
generating a list of user candidates for review for removal.  Any
extra non-users with accounts in /home could simply be ignored.  For
example something like this.

#!/bin/sh
# List users on a system.  Assumes /home is used.

PATH=/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin
umask 02

for d in /home/*; do
  test "$d" != "lost+found" || continue # skip lost+found
  u="$(basename "$d")"          # user name associated with home directory
  getent passwd "$u" >/dev/null || continue # skip if not an active user
  homedir=$(getent passwd "$u" | awk -F: '{print$6}')
  : homedir=$homedir  # for sh -x debug tracing
  case $homedir in
    /home/*) : okay ;;
    *) continue ;;              # skip home dirs elsewhere
  esac
  printf "%s\n" "$u"
done

exit 0

This can get more complicated when one starts looking at locked
accounts, disabled accounts, accounts with expired passwords, and so
forth.  But it also might be enough just by itself.

Bob