cannot useradd/adduser when /etc/{passwd,shadow,group} are symlink (debian squeeze)

i'm having trouble with useradd when im moving /etc/passwd /etc/shadow /etc/group from /etc to /home and create a symlink in order to have /etc/{passwd,shadow,group} respecively pointing to /home/{passwd,shadow,group}

i cannot create any user and have useradd outputing:

root@client:/home# useradd testuser
Adding user `testuser' ...
Adding new group `testuser' (1000) ...
groupadd: cannot open /etc/group

btw useradd output is

root@client:/home# adduser testuser
useradd: cannot open /etc/passwd

Why does useradd refuse to open a symlinked /etc/passwd?

To answer the question we need to take a look at the source code of useradd (I did this on Ubuntu 12.04, on Debian it may differ slightly):

  1. Find out which package owns /usr/sbin/useradd:

    $ dpkg-query -S /usr/sbin/useradd
    passwd: /usr/sbin/useradd
    
  2. Install the source:

    $ apt-get source passwd
    Reading package lists... Done
    Building dependency tree       
    Reading state information... Done
    Picking 'shadow' as source package instead of 'passwd'
    (...)
    dpkg-source: info: extracting shadow in shadow-4.1.4.2+svn3283
    dpkg-source: info: unpacking shadow_4.1.4.2+svn3283.orig.tar.gz
    dpkg-source: info: applying shadow_4.1.4.2+svn3283-3ubuntu5.1.diff.gz
    (...)
    
  3. cd to the source directory:

    $ cd shadow-4.1.4.2+svn3283/
    
  4. Search the directory for useradd's source file, which ideally should be called useradd.c:

    $ find . -name useradd.c
    ./src/useradd.c
    

    Bingo!

  5. Look for error message cannot open /etc/passwd (in fact I only search for cannot open, since the whole string doesn't return any results):

    $ grep -B 1 'cannot open' src/useradd.c
    (...)
      if (pw_open (O_RDWR) == 0) {
          fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
    (...)
    

    -B 1 means print 1 line of leading context before the matching line.

    This is where the error message you see is being generated. Function pw_open controls whether /etc/passwd can be opened or an error should be thrown.

    pw_open is not a Linux syscall (apropos pw_open doesn't return any results), so it is probably implemented within this package. Let's search for it.

  6. Tracing pw_open leads to:

    $ grep -R pw_open * 
    (...)
    lib/pwio.c:int pw_open (int mode)
    (...)
    

    pw_open implementation is:

    $ grep -A 3 'int pw_open (int mode)' lib/pwio.c 
    int pw_open (int mode)
    {
        return commonio_open (&passwd_db, mode);
    }
    

    Getting closer, but we're not there yet. commonio_open is our new objective.

  7. Search for commonio_open:

    $ grep -R commonio_open *
    (...)
    lib/commonio.c:int commonio_open (struct commonio_db *db, int mode)
    
  8. Open lib/commonio.c and scroll to function commonio_open:

    int commonio_open (struct commonio_db *db, int mode)
    {
    (...)
    
        fd = open (db->filename,
                     (db->readonly ? O_RDONLY : O_RDWR)
                   | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW);
    

    Do you see O_NOFOLLOW? This is the culprit (from man 2 open):

    O_NOFOLLOW 
          If pathname is a symbolic link, then the open fails.
    

Summarizing, useradd.c uses pw_open, which in turn uses commonio_open, which opens /etc/passwd using syscall open with option O_NOFOLLOW, that rejects symbolic links.

Although a symlink can be used as a replacement of a file in many (I'd say most) situations, useradd is quite picky and rejects it, probably because a symlinked /etc/passwd strongly suggests that /etc has been tampered with.

Why should I leave passwd in /etc?

There are several files in /etc needed to boot and log in, for example (but not limited to): fstab, inittab, passwd, shadow and the init scripts in init.d/. Any sysadmin expects those files to be there, not symlinked to /home or wherever.

So even if you could, you should leave passwd in /etc.

Furthermore, the filesystem structure in Linux is well defined, take a look at it here: http://www.pathname.com/fhs/pub/fhs-2.3.html. There is also a chapter for /etc. Moving things around is not recommended.


Doctor, it hurts when I do this.

Well, don't do it then!

Seriously, don't stick files as critical as that in an unexpected location. Whatever you're trying to test: find a better way. If you're trying to do central authentication: use ldap. Or nis if you must.