Introduction
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.
Practical example
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
setgid(pw.pw_gid);
initgroups(pw.pw_name, pw.pw_gid);
setuid(pw.pw_uid);
(Error checking should be done.) In addition, for highly security-critical calls such as setuid, call getuid and 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
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_setcred and 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_setcred and pam_open_session should be called in. It seems preferable to invoke pam_setcred before 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 setuid. 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
closefrom
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
Invoke 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 usrinfo (below)
usrinfo, setpcred
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.
Use setpcred(pw.pw_name, NULL) to set up process limits correctly from the credentials in the user database.
Environment variables
$USER, $HOME, $PATH, $LOGNAME, $LOGIN (legacy, AIX)
Optional: $MAIL, $TZ
Defaults may be in /etc/environment.
More detail on env vars needed! Edit me!
SELinux
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 initgroups.
Audit userid
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 /proc/self/loginuid.
See further: "The Linux Audit System, or Who Changed That File?", Rainer Wichmann
Solaris contract(4)
Create a new contract for processes launched from a daemon.
See example: "Creating subprocesses in new contracts on Solaris 10", Floris Bruynooghe
setusercontext
Many of these tasks are factored out of login(1) into libutil on BSD systems. See setusercontext documentation.