OxideBSD getty, login and PAM: design specification
Status: accepted design, partly implemented (not yet: the serial-terminal tests of §9.2, which need tty01; init’s boot and shutdown records, §8.2; who). Target release: v0.3.0.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in
RFC 2119. Interfaces are documented in the manual pages getty(8), gettytab(5), login(1),
login.conf(5), pam.conf(5), pam_unix(8), pam_nologin(8), pam_securetty(8) and
utmpx(3); this document records the design. Where FreeBSD, NetBSD and OpenBSD agree it follows them; where they
differ, the majority. It depends on TTY.md (terminal devices) and
INIT.md (who starts getty).
1. Scope
The path from a terminal to a user’s shell: init starts getty on each terminal in /etc/ttys;
getty prepares the line and reads a user name; login authenticates through PAM, applies the
user’s login class, records the session, and starts the user’s shell.
2. Components
| Path | Role | Source |
|---|---|---|
/usr/libexec/getty |
Terminal setup and login prompt | Rust, libexec/getty |
/etc/gettytab |
getty’s terminal descriptions (gettytab(5)) |
etc/gettytab |
/usr/bin/login |
Authentication and session start | Rust, usr.bin/login |
/etc/login.conf |
Login classes (login.conf(5)) |
etc/login.conf |
/etc/pam.d/ |
PAM policies: system, login, other |
etc/pam.d/ |
/usr/lib/libpam.a |
OpenPAM, static, with OxideBSD’s modules | external/bsd/openpam + lib/libpam/modules |
/etc/motd |
Message of the day | etc/motd |
/etc/master.passwd, /etc/passwd, /usr/sbin/pwd_mkdb |
Accounts (passwd(5), pwd_mkdb(8)) |
etc/master.passwd, Rust usr.sbin/pwd_mkdb |
/var/run/utmpx, /var/log/wtmpx, /var/log/lastlogx |
Session records (utmpx(3)) |
musl |
/etc/nologin |
Refuses logins while it exists | written by shutdown(8) |
getty and login MUST NOT reuse BusyBox’s; BusyBox getty and login stop being installed.
3. Capability databases
3.1. gettytab(5) and login.conf(5) use the same termcap-style format as the BSDs’ getcap(3):
records of name|alias:cap:cap:..., continued with \, capabilities name (boolean),
name#number, name=string (with \ and ^ escapes), name@ (cancels), and tc=name
(inherits another record).
3.2. One Rust parser implements this format for both files (lib/libgetcap). Unlike the BSDs,
OxideBSD reads the text files directly; there is no cap_mkdb database.
Rationale. A second, compiled copy of each file is a source of stale configuration; the files are small.
4. getty
4.1. init runs getty <type> <tty>, <type> naming a gettytab record (default default) and
<tty> a device under /dev.
4.2. getty MUST open the device, become a session leader, make the terminal its controlling
terminal, and use it as standard input, output and error. It MUST then configure the line from the
record: speed (sp, and the nx chain for speed cycling on a serial line), parity (ep, op,
np, 8b), control characters (er, kl, in, qu, ec, eo…), and output processing.
4.3. getty prints the if file or im banner, expanding the BSDs’ % escapes (%h host name,
%t terminal, %s system name, %m machine, %r release, %v version, %d date), then the
lm prompt (default login:), and reads a name. A name typed in upper case only sets the
terminal to upper-case mode, as in the BSDs. With to, getty exits after that many idle seconds.
4.4. getty then executes lo (default /usr/bin/login) as login -p <name>; with al
(autologin) it runs login -f <user> without prompting.
4.5. Capabilities getty does not implement MUST be parsed and ignored, not rejected.
5. login
5.1. login [-fp] [-h host] [user]: -f skips authentication (only when run by root, as by
getty’s autologin); -p preserves the environment getty passed; -h names the remote host.
5.2. Authentication MUST go through PAM, service name login: pam_start,
pam_authenticate, pam_acct_mgmt (which covers nologin and secure terminals, §6), then
pam_setcred and pam_open_session. A failed attempt MUST NOT say whether the user exists.
5.3. Retries. After a failure login prompts again. The number of attempts and the delay
between them come from the login class: login-retries (default 10) and login-backoff
(default 3, after which each further attempt waits five seconds more), as in FreeBSD and NetBSD. OxideBSD
adds login-timeout (default 300 seconds), after which login exits.
5.4. Session setup. login MUST, in order: change the terminal’s owner to the user, its group
to tty, and its mode to 0620; record the session (§8); apply the user’s login class (§7); set
the group list (initgroups), group ID and user ID, dropping root; set HOME, SHELL, USER,
LOGNAME, PATH, TERM and MAIL; change to the home directory (to / if it is missing,
unless the class sets requirehome); print /etc/motd unless ~/.hushlogin exists (the last-login line comes from pam_lastlog, as
in FreeBSD and NetBSD); and execute
the user’s shell as a login shell (argv[0] starting with -).
5.5. Logout. login stays as the shell’s parent: when the shell exits it closes the PAM session, records the logout, and returns the terminal to root with mode 0600.
Rationale. The BSDs’ login also waits for the shell, so that the session can be closed and recorded; PAM session modules require it.
6. PAM
6.1. OpenPAM (release 2025-05-31) is vendored as a plain source tree under
external/bsd/openpam and built by build.rs into a static libpam.a with musl; no autotools.
6.2. OxideBSD has no dlopen. Modules are linked into the programs that use PAM. OpenPAM’s
static-module lookup (openpam_static.c) is changed to walk a NULL-terminated table,
openpam_static_modules[], that the modules library defines, in place of the GNU linker sets
upstream uses. The change is local to the vendored tree and SHOULD be offered upstream.
6.3. The modules are written in Rust (lib/libpam/modules), exporting OpenPAM’s struct
pam_module for each:
| Module | Functions | Behavior |
|---|---|---|
pam_unix |
auth, account, password | Checks the password against /etc/master.passwd with crypt(3); account checks the expire and change fields and locked (!, *) entries; password changes it |
pam_nologin |
account | If /etc/nologin exists, prints it and refuses everyone but root |
pam_securetty |
account | Refuses root on a terminal not marked secure in /etc/ttys |
pam_lastlog |
session | Prints the user’s last login, and records this one, in lastlogx |
pam_permit, pam_deny |
all | Always succeed / always fail |
6.4. Policies follow FreeBSD’s and NetBSD’s layout: /etc/pam.d/system holds the common stack, and
/etc/pam.d/login includes it and adds pam_nologin and pam_securetty to its account stack.
/etc/pam.d/other denies everything.
6.5. A C program that uses PAM links libpam.a and the modules’ static library; a Rust program
depends on the modules crate directly (linking both would duplicate Rust’s runtime).
7. Login classes
7.1. /etc/login.conf follows login.conf(5), which all three BSDs share. A user’s class is the
fifth field of /etc/master.passwd (§7.4); an empty class means default, and root’s is root.
7.2. login MUST apply: path, setenv, umask, priority, lang, charset, timezone,
shell, welcome (motd file), hushlogin, nologin, requirehome, login-retries,
login-backoff, login-timeout, and the resource limits (cputime, filesize, datasize,
stacksize, coredumpsize, memoryuse, memorylocked, maxproc, openfiles, vmemoryuse),
each with -cur and -max forms.
7.3. Resource limits are set with setrlimit, whether or not the kernel enforces each one yet.
7.4. Accounts move to the layout all three BSDs use: /etc/master.passwd (mode 0600) holds
every field, name:password:uid:gid:class:change:expire:gecos:home:shell; pwd_mkdb(8) generates
the public /etc/passwd from it, with the password replaced by * and the class, change and
expire fields removed. /etc/shadow is removed. musl’s getspnam(3) is changed to read
/etc/master.passwd, mapping the password, change and expire fields into struct spwd, so
existing callers keep working. Unlike the BSDs, pwd_mkdb writes no .db files; libc reads the
text files.
8. Session records
8.1. musl’s utmpx(3) functions, stubs today, are implemented in OxideBSD’s musl fork using
NetBSD’s files, whose names OpenBSD’s utmp files share: /var/run/utmpx (current sessions),
/var/log/wtmpx (history) and /var/log/lastlogx (each user’s last login). Records are musl’s
struct utmpx, written whole.
8.2. login writes a USER_PROCESS record at login and a DEAD_PROCESS record at logout;
/sbin/init writes BOOT_TIME and SHUTDOWN_TIME (INIT.md).
8.3. utmpx does not survive a reboot: rc.d/cleanvar empties /var/run, so no session
appears active after a crash.
9. Verification
9.1. Host unit tests for the capability-database parser, gettytab % expansion, and login.conf
value parsing.
9.2. On-target tests: getty on the serial terminal (TTY.md) driven through a pty on the host:
the banner and prompt appear; a name reaches login; user/user logs in and gets a shell with
the right uid, environment, home and umask; a wrong password is refused and delayed; root is
refused on an insecure terminal; /etc/nologin refuses user; who shows the session and
logout removes it.
10. Open questions
- Whether
login-timeoutshould be proposed upstream, or stay OxideBSD’s. - Account tools:
vipw,chpass,passwdandpw/adduserreplacing BusyBox’s, which edit/etc/shadow.
Source: LOGIN.md