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):
-
Find out which package owns
/usr/sbin/useradd
:$ dpkg-query -S /usr/sbin/useradd passwd: /usr/sbin/useradd
-
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 (...)
-
cd
to the source directory:$ cd shadow-4.1.4.2+svn3283/
-
Search the directory for
useradd
's source file, which ideally should be calleduseradd.c
:$ find . -name useradd.c ./src/useradd.c
Bingo!
-
Look for error message
cannot open /etc/passwd
(in fact I only search forcannot 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. -
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. -
Search for
commonio_open
:$ grep -R commonio_open * (...) lib/commonio.c:int commonio_open (struct commonio_db *db, int mode)
-
Open
lib/commonio.c
and scroll to functioncommonio_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 (fromman 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.