The basic task of a daemon providing logins is to execute one or more commands in the correct context for a user, for that system. This is harder than it seems, given historical requirements derived from terminal logins, and differences in process attributes and credentials between platforms. Different steps must be precisely ordered to ensure correct set-up.
The github project netlogind is a simple test application that demonstrates the operation of such a daemon. It illustrates the steps listed below.
Basic steps to create a process running as a given user
struct passwd pw; //< the user
(Error checking should be done.) In addition, for highly security-critical calls such as setuid, call
geteuid afterwards to assert that the correct credentials were set. Continuing to execute code under the wrong uid is the worst disaster of all.
setuid resets the saved-set-userid on all platforms where this is supported.
PAM is an API allowing system administrators to configure how applications perform authentication and launch user processes. PAM is widely deployed.
PAM is used to set up session environment through the
pam_open_session functions. There are many issues with calling these functions portably, and constraints on the order.
- They must be called from the same thread of execution as
pam_authenticate if that was used to perform authentication. Some modules work by collecting credentials during the authentication conversation, and performing an action with them during the session phase (eg
pam_mount). In particular, PAM modules that use
pam_set_data internally will not work if
pam_setcred/open_session is called from a different process to
pam_authenticate (for example, some versions of
pam_afs or `pam_krb5).
- They must be called as root.
- They must be called after
initgroups, as they may be used to set up extra group memberships.
- There is debate over which order
pam_open_session should be called in. It seems preferable to invoke
pam_open_session on most modern platforms, as some modules reasonably require this.[*] However, there are reasons for wanting to order it the other way.[*] Regardless, the strictest constraint is that Solaris and HP-UX PAM will fail with certain modules unless
pam_setcred comes second, so there is no much choice on those platforms (that is, you actually have to follow the order documented on those platforms). LinuxPAM's documentation says that `pam_setcred should come first, the opposite to OpenPAM's documentation.
- Ordering of
pam_open_session/setcred relative to forking: Do not fork between calling the two PAM functions and
pam_limits applies resource limits to the calling process, and root could very well be running more processes than the target user is allowed to run, in which case forking will always fail.
- PAM bugs to be aware of: some vendor-supplied modules, eg on HP-UX, do not pass the appdata parameter to the conversation function. For portability, use a static variable instead to avoid relying on the appdata parameter. Other notable real-world compatibility issues: RedHat #126985, RedHat #127054, the
PAM_TTY issues on Sun (eg OpenSSH #687), ruid restrictions on
pam_chauthtok (AIX requires ruid of 0, Solaris requires ruid non-zero).
Setting up the execution environment for a user process
Close all fds before exec'ing the user's command. Whether this should be done is debated, because it kills many implementations of
posix_trace (for example). Although sometimes listed as one of the steps for daemonizing a process, it's a very paranoid thing to do. It's more justifiable to do though when creating a user session.
Platforms: native on Solaris, FreeBSD. Otherwise, emulate using fds listed in
proc if available. On no account naively try to close up to
getrlimit(RLIMIT_NOFILE) or similar, as this can be far too large a number to loop up to.
Call at: any time
See also: Austin Group Defect Tracker, "Add fdwalk system interface"
setlogin(pw.pw_name) to ensure that the session has the correct name associated with it.
Call: Right after a
setsid; absolutely not from the same session the daemon is running in. Call as root.
Platforms: FreeBSD, Mac OSX. Because one uid may have several entries in the password database with different names,
getpwuid(getuid()) mightn't tell you the username that was used to log on, so another function,
getlogin, has to be provided to do this. The implementation may be done in terms of utmp (unreliable), or
$LOGNAME (insecure). BSD-derived systems solve the problem in the ideal way by storing a username in the per-session kernel data structure. AIX solves this using
On AIX, call
usrinfo(SETUINFO, "LOGIN=<name>\0LOGNAME=<name>\0NAME=name\0", ...). This is similar in function to
setlogin on BSD-derived systems. Call as root.
setpcred(pw.pw_name, NULL) to set up process limits correctly from the credentials in the user database.
$LOGIN (legacy, AIX)
Defaults may be in
More detail on env vars needed! Edit me!
Setting the execution context of the child process is best done through PAM on Linux systems. It could however be set manually, to guarantee that any privileges of the daemon are not applied to the user process, irrespective of PAM configuration. In this case, it would set before calling
On some Linux kernels, processes maintain an auid, an additional userid which is preserved when the user switches userid using
su(1), for example. This permits actions taken to be logged and traced to the user who performed it.
The auid is typically set using
pam_loginid. To guarantee it is set even when PAM is not configured correctly, write the user's uid to
See further: "The Linux Audit System, or Who Changed That File?", Rainer Wichmann
Create a new contract for processes launched from a daemon.
See example: "Creating subprocesses in new contracts on Solaris 10", Floris Bruynooghe
Many of these tasks are factored out of
login(1) into libutil on BSD systems. See