Home / Alt manpages / pam_exec(8)

  • pam_exec(8)
  • Admin command
  • linux

Run a Controlled Hook from PAM with pam_exec

pam_exec runs one fixed, executable helper from inside a PAM stack, which beats improvising a shell trick to log or notify on login events. This guide sets up a narrow, logged hook without ever handing a password to a script, then shows how to undo it. Allow about twenty minutes, plus a maintenance window if you touch a real login, SSH or password stack.

Examples match pam_exec from Ubuntu's libpam-modules package, version 1.5.3-5ubuntu5.7. You need root to install the helper and edit a file under /etc/pam.d. PAM changes are security-sensitive and can lock you out, so keep an existing root shell or console session open while testing.

1. Check the module and pick a narrow event

These are ordinary, read-only checks:

$ dpkg-query -W -f='${Package} ${Version}\n' libpam-modules:amd64
libpam-modules 1.5.3-5ubuntu5.7
$ man 8 pam_exec
$ man 5 pam.d

A PAM service file lists a management type, a control flag, a module and its arguments. /etc/pam.d/passwd is used by the passwd service, so a line beginning password belongs to password changes. The available module types are auth, account, password and session.

  • Pick the one event your helper actually needs. A notification after a password change belongs in a password stack; a check before access belongs in an authentication or account stack.
  • Do not add the line to several stacks just in case. That creates repeated side effects and makes failures harder to trace.

Checkpoint

Write down the exact service file and event, such as /etc/pam.d/passwd and password. Do not start with a shared file such as common-auth unless you deliberately want the hook running for every service that includes it.

2. Build a fixed helper with a clear exit status

The helper must be an absolute path. pam_exec does not turn a command string into a safe policy boundary, and the child's environment can contain values a user controls. Avoid sh -c, PATH lookups and unquoted data; make the helper accept only what it needs and return zero on success:

#!/bin/sh
set -eu

log_file=/var/log/pam-account-hook.log

printf 'service=%s type=%s user=%s\n' \
    "${PAM_SERVICE-}" "${PAM_TYPE-}" "${PAM_USER-}" >> "$log_file"
exit 0

Install the real version as root, with an owner and mode that stop ordinary users replacing it:

# install -o root -g root -m 0755 /tmp/pam-account-hook /usr/local/sbin/pam-account-hook
# install -o root -g root -m 0600 /dev/null /var/log/pam-account-hook.log
# sh -n /usr/local/sbin/pam-account-hook
# ls -l /usr/local/sbin/pam-account-hook /var/log/pam-account-hook.log

Use a log destination with sane permissions, or the system journal through a purpose-built helper. Never log PAM_AUTHTOK, passwords, session tokens or the whole environment: the helper's own output is normally discarded by pam_exec, but its exit status still matters to the control flag.

Checkpoint

The helper is root-owned, executable and passes a shell syntax check. The install commands above change state and need root; the temporary source file is only an example, so replace it with the reviewed helper you actually intend to deploy.

3. Add the smallest possible PAM rule

Back up the service file before editing it. This is reversible, but a malformed or badly placed rule can disrupt authentication:

# cp -p /etc/pam.d/passwd /etc/pam.d/passwd.pam-exec-backup

Then add a single line at a deliberate point in the existing password stack:

password optional pam_exec.so type=password /usr/local/sbin/pam-account-hook
  • optional means the hook's result is not, by itself, the deciding result when other modules are present: a reasonable starting point for logging or notification. Use required only when a failed helper genuinely must fail the PAM operation, and accept that a helper outage can then block password changes or access.
  • type=password restricts execution to the password module type.
  • Other arguments the module supports: debug, quiet, quiet_log, stdout, log=file, seteuid and expose_authtok. Leave expose_authtok out: it sends the authentication password to the command's standard input, which this logging example does not need.

By default the command runs with the calling process's real user ID. seteuid switches that to the effective user ID, which can materially change file access, so add it only after checking the helper's actual privilege needs. log= appends command output to a file; stdout sends output to the calling application instead and makes log= ineffective. Neither is a substitute for structured, access-controlled logging.

4. Test without closing your safety net

There is no standalone pam_exec command: only a PAM-aware application invokes the module. For the example above, use passwd only when you are prepared to complete a real password change, and keep a separate root session open. If the service is SSH, validate the file and test a new connection before ending the existing one.

After a successful password change, check only the expected record:

$ sudo tail -n 1 /var/log/pam-account-hook.log
service=passwd type=password user=EXAMPLE_USER

Username and service are host-specific. The module also exports PAM_RHOST, PAM_RUSER, PAM_SERVICE, PAM_TTY, PAM_USER and PAM_TYPE in the child environment, alongside the current PAM environment list. Treat every one of them as untrusted input: a helper should use an allow-list and careful quoting, never build a shell command straight from them.

If the helper fails, the PAM module returns a system error. Without quiet, pam_exec can echo a failed command's exit status; without quiet_log, it can log that failure too. These are diagnostics, not proof the surrounding stack will deny the operation: the control flag decides how the stack actually treats the module's result.

Common traps

Warning

Do not experiment by locking out the only administrator. If a test breaks the service, use the still-open root console and restore the backup:

# cp -p /etc/pam.d/passwd.pam-exec-backup /etc/pam.d/passwd
# rm -f /etc/pam.d/passwd.pam-exec-backup

Removing the rule is the normal undo. Remove the helper and its log only once you have confirmed no other PAM service uses them:

# rg -n 'pam-account-hook|pam_exec' /etc/pam.d
# rm -f /usr/local/sbin/pam-account-hook /var/log/pam-account-hook.log

Do not reach for expose_authtok as a quick fix for missing context. If a helper genuinely needs authentication data, review its process permissions, input handling, memory lifetime and logging before you ever change the PAM rule: a short password on standard input is still a secret.

Done means

  • Version and target recorded. The installed libpam-modules version and target PAM service are written down.
  • Helper solid. Absolute path, root-owned, executable, and passes syntax checks.
  • Rule narrow and deliberate. Narrowest event, intentional control flag.
  • Inputs treated as untrusted. The helper never logs passwords and treats every PAM environment value as untrusted.
  • Real test passed. A live PAM-aware test produced the expected service, type and user record.
  • Recovery ready. A backup or console recovery path exists before the change goes live.