Pluggable Authentication Modules for Linux

This article describes an implementation of a user-authentication API: the Pluggable-Authentication-Modules API (PAM for short).

Some details

In the remainder of this article, I will give a brief
overview of how to write and use a PAM-based application. Potential
authors and interested administrators should see the Linux-PAM URL
(at the end) for more complete details. The intention of this
article is to provide only a taste of what you can do with
Linux-PAM. In particular, I will not address the issue of how to
write an authentication module. For those details you should
consult the full documentation available from the Linux-PAM
URL.

Let us consider a generic login-type application. We will see
which responsibilities are delegated to the PAM-API and which
responsibilities are retained by the application. Finally, we will
cover how a local administrator can configure the application to
suit local taste.

Figure 1 is a graphic
protraying the three components to a working PAM-based application.
On the left is the application which is linked to the libpam.so
shared library. In the middle we have the PAM library which parses
the configuration file(s) and uses the entries listed there to load
the configured selection of authentication modules
(PAMs). Additionally, the application supplies
a conversation function which provides a means
for the modules to talk directly with the user.

In Listing 1 the skeleton of
a login-type application is shown. It can be compared with the
sample application given in the OSF-RFC defining PAM. The
differences reflect enhancements to PAM since the RFC was written.
Note, the listing is not very secure; it pays little attention to
possible errors returned by the framework and is intended only to
orient the reader.

The application initializes the library with a call to
pam_start(), which silently parses
the configuration file and loads those authentication modules that
are appropriate to this application. It then enters a loop that
attempts to authenticate applicant users. This process is repeated
until a user is correctly authenticated, or the loaded
authentication modules indicate that their patience has been
exhausted.

Once a user has been authenticated, the
pam_acct_mgmt() function is used
to establish if the user is permitted to log in at this time.
Modules of account-management type can be used to restrict users
from logging in at certain times of the day/week or for enforcing
password expiration. This latter case is intercepted, and the user
is prevented from gaining access to the system until they have
successfully updated their password with the
pam_chauthtok() function.

The user's login session is surrounded with two sets of
function calls. The outer function calls,
pam_open_session() and
pam_close_session(), mark the
beginning and end of the PAM-authenticated session. Session
initialization and termination typically include tasks such as
making a system resource available (mounting the user's home
directory) and establishing an audit trail. The inner function
calls, pam_setcred(), first
establish and finally release the PAM-configurable identity of the
user. These can include credentials like access-tickets and
supplementary group memberships.

Following logout, the user's PAM-configurable credentials are
deleted, and the session is closed with a call to the
pam_close_session() function.

Finally, with a call to
pam_end(), the login application
breaks its connection to the PAM library. The PAMs are unloaded,
and the dynamically allocated memory is
scrubbed and returned to the system.

This simple application demonstrates most of the
functionality provided by the PAM paradigm. The conversation
mechanism flexibly leaves the mode of direct interaction with the
user entirely at the discretion of the application. In this way, it
is possible for modules to be used simultaneously with
graphically-based programs (xdm
etc.) and their text based equivalents
(login etc.).

Configuring an Application

Having obtained a PAM-based application, it is necessary to
attach authentication modules to it. At the
time of this writing there is an old way and a new way of doing
this. The old way corresponds to the method advocated in the RFC
and is based on a the contents of a single PAM configuration file:
/etc/pam.conf. The new method is to break up
the entries for the separate services into independent
configuration files that are each located in the /etc/pam.d/
directory. The name of the file containing the configuration for a
given application is the service name (in lower-case
letters).

The function of the configuration file(s) is to provide a
mapping from the application's service name to a selection of
modules that provide authentication services to the raw
application. In the case of the source program of Listing 1, the
service name is simply “login”. (This is the first argument of
the pam_start() function call.)

Along with similar entries for each of the PAM-aware services
present on your system, the old configuration file (/etc/pam.conf)
might contain entries of the following form:

The first four fields are: service-name,
module-type, control-flag
and module-filename. The fifth and greater
fields are for optional arguments that are specific to the
individual authentication modules.

The second field in the configuration file is the
module-type, it indicates which of the four
PAM management services the corresponding module will provide to
the application. Our sample configuration file refers to all four
groups:

auth: identifies the PAMs that
are invoked when the application calls pam_authenticate() and
pam_setcred().

account: maps to the
pam_acct_mgmt() function.

session: indicates the mapping
for the pam_open_session() and pam_close_session() calls.

password: group refers to the
pam_chauthtok() function.

Generally, you only need to supply mappings for the functions
that are needed by a specific application. For example, the
standard password changing application, passwd, only requires a
password group entry; any other entries are
ignored.

The third field indicates what action is to be taken based on
the success or failure of the corresponding module. Choices for
tokens to fill this field are:

requisite: Failure instantly
returns control to the application indicating the nature of the
first module failure.

required: All these modules are
required to succeed for libpam to
return success to the application.

sufficient: Given that all
preceding modules have succeeded, the success of this module leads
to an immediate and successful return to the application (failure
of this module is ignored).

optional: The success or failure
of this module is generally not recorded.

The fourth field contains the name of the loadable module,
pam_*.so. For the sake of readability, the full pathname of each
module is not given. Before Linux-PAM-0.56 was released, there was
no support for a default authentication-module directory. If you
have an earlier version of Linux-PAM installed, you will have to
specify the full path for each of the modules. Your distribution
most likely placed these modules exclusively in one of the
following directories: /lib/security/ or /usr/lib/security/.

The equivalent functionality for our login application can be
obtained with the new configuration
arrangement via an independent login configuration file:

The newer configuration file is distinct from the old in that
it is missing a service-name field. This field
is not needed, as the name of the service-specific configuration
file is by definition the service-name of the
application.

It should be noted that the content of an /etc/pam.d/
directory takes precedence over the contents of any /etc/pam.conf
file.

Note that the example contains more than a single module
mapping for the auth, session
and password management groups. This feature is
referred to as stacking and enables a single
application to make use of more than one module at a time. The
order in which the modules are stacked is the same as the order in
which they are invoked.

The two stacked auth modules are used to
pam_authenticate() the user. The first module (pam_securetty.so)
checks to see if the user is root and prevents root from logging in
from an insecure terminal. The value requisite
for control-flag is used to force immediate
authentication failure if the securetty module
fails. If this occurs, no more of the auth
modules are executed. This has the benefit of preventing root from
mistakenly typing a password over an insecure terminal line.
Another popular module that can be used to prevent log in attempts
like this is pam_listfile.so. It can be configured to perform many
types of access control based on a list of tokens in a specified
file.

When a non-superuser, joe for example, is
successfully evaluated by the securetty module,
control is passed to the next module in the stack,
pam_unix_auth.so. This module performs standard Unix
authentication. It prompts for a password and checks it against
that stored in the local system. Providing your libc can handle it,
it works on both shadowed and non-shadowed systems. An enhanced
alternative to pam_unix_...so is the pam_pwdb.so module. This
module makes use of the password database library, libpwdb.so, and
can do things like MD5 passwords and offer RADIUS support. The
important point is that the system administrator is the one
deciding which authentication policy to implement by simply
plugging in the corresponding module.

The auth module also supplies a binding
for the pam_setcred() function. It is linked to the authentication
process because the method by which a user is authenticated is
strongly tied to the user's identity. Kerberos, for example,
requires a network-based authentication and yields a
ticket (the user's credential) with which they
can obtain network services, such as remote login and print
requests.

The account module line in our
configuration file is used to check that the user is permitted to
login. This is different from establishing whether the user is who
they say they are. Account management deals with enforcing the
expiration of passwords and preventing logins during system time.
Our login example uses the standard pam_unix_acct.so module to
enforce shadow password aging. Here, it is used (in conjunction
with the password module type) to force the
renewal of a user's password.

For experiments in this area, the administrator might like to
try pam_time.so. This module can be configured to permit or deny
access to users based on their terminal line, the time they are
logging in and what they intend to do.

Next, we come to the session modules. The first in the stack,
pam_cfs.so does not currently exist. (Because of ITAR export
restrictions, I will not be writing it.) However, I have included
it to illustrate the PAM session concept. At the start (and end) of
the user's session on the system, this module would mount/unmount
the user's cryptographically-secured home-directory, obtaining the
user's home-directory/key-mapping information from the cfs.keys
file. With PAM, someone could make a single module available, and
that module could be used in any PAM aware application. The use of
the value optional for
control-flag ensures that the user can log in
even when no such directory is available.

The second module, pam_unix_sess.so, logs a message with
syslog(3) to announce the user's entry to and exit from the
system.

Finally, we come to the password
management group. Here, the stacked modules are invoked when users
change their authentication token(s).
Traditionally, this change could be for updating their password,
but it has the potential to be extended to refreshing a smart card
or a yearly update of an employee's retinal scan. In the case of
the login example, we simply request a replacement for the user's
Unix password. Because the pam_unix_passwd.so module is marked as
sufficient, a warning is logged by the
pam_warn.so module only in the case that the user fails to
successfully enter a new password.

In addition to configuring specific service names, there is
also a default mapping, given the service name
other. It can be used to ease the integration of
new services by providing a default selection of modules
appropriate to the local security policy. Instead, it can be used
to deny access to any application that does not have a specific
pam.conf entry. This is the recommended usage, for example, we can
make use of the pam_deny.so (always deny access) and pam_warn
(syslog(3) an informative warning) modules as follows:

This configuration always denies user access to an
application. As before, the pam_warn.so is used to send a warning
message to syslog(3) for administrative action. This configuration
can be used to make sure that only specific services are available
on your system. Note, if you write an application that uses PAM and
this configuration file is not sufficient to block service from it,
your application is not using PAM in the correct manner.

I ran across an article you put out in Linux Journal back in Dec/2007 (little over a year ago) concerning the PAM modules for Linux. We run linux on the Mainframe and are using the delivered PAM there (under z/VM).

However, we have a problem with password expirations…..when this happens the end user gets “unknown” or “sorry” for the return message.

I think this is because MF LDAP (ITDS) returns a different set of codes what PAM doesn’t understand. So, is there a way

To capture those codes and give the user MORE details about around the “expired” password. Better yet, prompt for reset of password.

In our case, we want the linux to authenticate users against the MF LDAP (ITDS) ..AND..to detect if the password is expired.