PAM stands for "Pluggable Authentication Modules", and is the system that does most authentication and authorization work when logging in to a Linux system.

What PAM Supports

There are four authentication types ("management groups"): auth, account, session, and password.

  • auth is the most common/interesting and decides whether a provided password will let you log in to a certain account.
  • account does some checking to make sure the login is valid -- whether an account is marked "expired" is the archetypal example. It's not quite the standard authentication/authorization dichotomy, because auth does a lot of authorization too, but it's related.
  • session does things to set up a login session, such as accounting for logins and logouts, setting resource limits, etc.
  • password is used for password changes, so that the standard password-changing interfaces can work correctly regardless of where passwords are actually stored.

Each service that supports PAM-based logins has a profile in /etc/pam.d, e.g., /etc/pam.d/sshd. This lists a number of modules and their type and arguments, in order that they should be called. Each module can return one of several values, including success, perm_denied, user_unknown, etc. and the stack can be configured to respond in different ways.

What PAM Does Not Support

PAM works around an interactive dialogue style of authentication, which basically means that it supports passwords. In particular, Kerberized logins using tickets do not go through PAM's auth stack (although they go through the session stack, once you're authenticated and you begin a session). pam_krb5 is for checking a typed password against Kerberos and generally getting new tickets, not for checking existing tickets.

PAM also does not have the ability to magically create different accounts with the same name (or UID) based on authentication strategy, so you can't have a local account with the same name as an Athena account. Account naming is mostly handled by NSS, but the fact that names and UIDs uniquely identify an account (at least within a machine) is a more fundamental UNIX design principle.

Common PAM modules

pam_unix does your regular, local machine, traditional UNIX authentication. It checks passwords in /etc/passwd, or /etc/shadow if that exists. The most common option is nullok_secure, which means that empty passwords are only accepted on "secure" (generally local) terminals as listed in /etc/securetty, but not over the network.

pam_krb5 checks provided passwords against Kerberos. The standard behavior (for auth, account, and session)is for it to take the provided password, take the username of the account you're logging in to, and attempt to kinit with that username and password pair. If it works, it will give you those tickets for the duration of your login session and destroy them when you log out, provided you're using the session type. It also supports changing your Kerberos password, so for accounts that authenticate over Kerberos, passwd works just as well as kpasswd.

However, it supports a fair number of configuration options. You can tell it to search the .k5login file in a user's home directory and attempt to authenticate to any principal listed there. You can also set a minimum UID for when pam_krb5 will allow Kerberos to be used for authentication, thereby both providing security for system accounts and not causing logins to root to hang if you mistype the password and your network is down. You can also set various ticket options, such as lifetime and forwardability.

Both pam_unix and pam_krb5 (and most other "normal" authentication modules in this line) support the use_first_pass option, which means to try the password provided to the previous module's request. This allows you to provide a password, have it fail the UNIX authentication (presumably because the account doesn't exist), and continue on to attempting to use it as a Kerberos password without retyping it, or providing a whole lot of PAM cleverness to guess in advance which type of authentication you want to use.

pam_succeed_if is a special module that checks whether certain parameters of the account being authenticated to are true. It's generally used in combination with PAM's limited logic for moving around in the stack (see below), so that you can skip a module under certain circumstances or use it under certain circumstances. For instance, Linerva uses it to only attempt UNIX passwords for root and only attempt krb5 for other users.

pam_echo outputs a file to the screen. Not all clients handle this usefully; console logins and OpenSSH do, but GDM may cut off the text or in newer versions not display it at all, and other SSH clients (e.g., SecureCRT or ConnectBot?) don't display this message. This module returns ignore unconditionally since it doesn't actually affect authentication.

pam_permit and pam_deny immediately return success or permission denied, respectively.

pam_afs_session gets you AFS tokens if Kerberos tickets are available; it's generally just used with the session management group. Options include specifying to aklog to particular cells, or to the cell containing the user's home directory.

pam_env populates the environment with the contents of /etc/environment.

pam_nologin denies logins if /etc/nologin exists, and prints the file's contents instead. It's kind of like a pam_echo that actually affects the login process.

pam_access applies the authorization controls in /etc/security/access.conf.

pam_limits applies the resource limits in /etc/security/limits.conf.

Stupid Tricks with PAM only ever uses password-based authentication for root at the physical console, never for SSH (only Kerberized SSH is allowed, even for root). Therefore their /etc/pam.d/sshd has the following:

# If their user exists (success),
auth    [success=ignore ignore=ignore default=1] uid >= 0
# print the "You don't have tickets" error:
auth    [success=die ignore=reset default=die] file=/etc/
# else print the "your account doesn't exist" error:
auth    [success=die ignore=reset default=die] file=/etc/

In order: pam_succeed_if will only succeed if the user account actually exists, even if the test looks like it should always succeed. In this case, if the account exists at all, that line returns success, else returns failure. The part between square brackets affects what happens on certain conditions: in this case, a success or ignore return from pam_succeed_if is ignored, which means that it carries on to the next one (as opposed to succeeding and permitting authentication). If, however, pam_succeed_if returns anything else, it uses the default=1 case, which means to skip over 1 module and go to the next one.

The next two rules are both pam_echo, which display either a message that your account exists, but you (presumably) didn't log in with Kerberized SSH with valid tickets for that account, or that your account doesn't exist and you can see this website for instructions on how to sign up. In either case, pam_echo succeeds and we fail authentication ("die"), unless something happened to prevent printing the message, in which case we try again (until the user cancels trying to log in).

scripts used to print a huge, unconditional SSH banner with instructions on how to sign up if necessary and log in if necessary. This setup allows them to print only the necessary information. While it's an unusual use of PAM, in that it never interacts with passwords, it's a good example of this sort of flow control.

Integrating with the system PAM configuration

Most applications will include one or more of /etc/pam.d/common-auth, /etc/pam.d/common-session, etc., so you can just make changes there.

While that's effective, it makes it hard for multiple packages to interact with PAM. For instance, Kerberos should install pam_krb5 and configure it to be used, Samba pam_winbind, etc. Ubuntu decided to be clever about this system and created  pam-auth-update, which reads per-module files in /usr/share/pam-configs/ and generates the common-* files with a lot of the square-bracket jumping-around rules to satisfy the requests in each module's PAM config file.