Development with Pluggable Authentication Modules (PAM)

A comprehensive and practical guide to PAM for Linux: how modules work and how to implement them


PAM is a very powerful and flexible framework. Applications that require authentication must be aware of PAM. But most basic applications and utilities in the UNIX and Linux world have been migrated. If you are an application programmer and your application requires authentication, you might wish to dig into the possibilities of PAM.

You can find modules for almost any situation, or maybe a combination of modules can solve your problem. But still you might end up in the situation where you cannot find a suitable module. In this article, you will learn how to develop your own modules.

PAM-aware Applications

The PAM runtime library has a well-defined API (Application Programming Interface). The PAM API is to a large extent the same on every UNIX and Linux operating system. Only a small number of differences exist, but any programmer can make a portable work-around. The differences are primarily related to the conversation function discussed later in this article. Linux-PAM provides one as a library function while other PAM implementations require the programmer to develop a conversation function. The pamtester utility provides a conversation function, which might be applicable to other applications.

The example application presented here is very simple. It can store and retrieve data (strings) in a simple (GNU DBM) database. In order to gain access, authentication through the PAM system is required. The idea is that the system administrator can control the access as he or she wishes by configuring PAM in a suitable fashion. This application is called vault.

The figure below outlines how a typical application uses PAM for authentication. Most of the usage is straightforward; the application calls a set of well-defined functions, which creates, operates on, and destroys data structures related to PAM. But PAM applies a little trick: the modules can call back to the application in order to retrieve user-related data.

Development with Pluggable Authentication Modules (PAM)

In order to call PAM functions in your applications you must include the pam_appl.h header file. The sample application includes two:

#include <security/pam_appl.h>
#include <security/pam_misc.h>

The second header file is special to Linux-PAM and it is related to a text-based conversation function and a few other utility functions.

Opening and Closing a PAM Session

Any PAM session begins with creating and initializing a data structure. The data structure (C-type) is called pam_handle_t. During the application run time, it is required to hold one variable of this data structure. It contains all relevant data about the PAM session.

The creation of the data structure is equivalent to opening a PAM session. The data structure is initialized by the function pam_start. Four parameters must be supplied when calling the pam_start function. In the sample application the call to pam_start is:

retval = pam_start(“vault”, user, &conv, &pamh);

The first parameter is the service name. It is a simple text string, and if the application programmer permits it, the service name can be set by the user instead of hard-coding the service name in the application. In the sample application the service name is set to vault precisely at the call, and at this point the PAM runtime will try to find the configuration file associated with the service (/etc/pam.d/vault in this example) or the appropriate lines in the /etc/pam.conf file. The second parameter is the user name. The standard C runtime library provides the getlogin function, which returns the user’s login name as a text string.

The third parameter is a pointer to the conversation function, which is, the function that takes care of the callbacks from the modules. We will return to the conversation function shortly. The fourth and last parameter is a pointer to the PAM handling data structure (actually, a pointer to a pointer). The call to pam_start returns an integer. If the return value is PAM_SUCCESS, the initializing of the PAM handler was as it should have been. Linux-PAM provides—as defined in the pam_appl.h header file—a conversation function, while other PAM implementations require the application programmer to develop conversation functions.

When the application does not need the PAM handling data structure, it can destroy it by calling the pam_end function. This is typically just before the application is to stop executing. In the sample application, the call to pam_end at the end of the main program is as follows:

pam_end(pamh, retval);

The argument retval is carried along from the last call to the PAM runtime, and depending on the return value of the previous call, PAM might have to shut down a PAM session differently.

Authenticating the User

When an application has initialized the PAM handling data structure, the next step is to authenticate the user. Since the service name and the user name are set by the call to pam_start, the authentication can be done by a simple call to the function pam_authenticate. The call is typically as simple as:

retval = pam_authenticate(pamh, 0);

The first parameter is the PAM handling data structure while the second parameter is optional flags. 0 (Zero) means silence authentication but others flags might be valid depending on the PAM implementation. The return value (stored in the variable retval above), is set to PAM_SUCCESS if the user is authenticated. If the user is it not known to PAM, the return value is PAM_UNKNOWN_USER, while a general authentication failure will lead to PAM_AUTH_ERR. In the case of Linux-PAM, the only flag is PAM_DISALLOW_NULL_AUTHTOK which will lead the return value PAM_AUTH_ERR if the user is not known to PAM. In order to authenticate a user for a particular service, the auth management group cannot be empty, that is the auth stack must have at least one module. If there are no modules the return value will be PAM_AUTHINFO_UNAVAIL.


Account Health Check

It is one thing to authenticate the user, but it is another thing to say whether the user is allowed to use the account. A number of issues influence the health of an account. For example, an account can be expired or the user may not currently be allowed to log in. The PAM function pam_acct_mgmt is used to check the health of the requested account. The simple call to the function is as follows:

retval = pam_acct_mgmt(pamh, 0);

The second parameter can be set to PAM_SILENT, which suppresses any messages from the PAM runtime, or to PAM_DISALLOW_NULL_AUTHTOK in order to require an authentication token. The flag has the same effect as for the pam_authenticate function.

Manipulating the PAM Handling Data Structure

In the sample application, the user name is set at the time of the call to the pam_start function, but this might not be always possible, so you need a function to set any piece of data. PAM data should not be accessed directly, so PAM provides methods for storing and retrieving the data items. The function's name is pam_set_item.

Many types of items are used by PAM; the table overleaf summarizes the most important types. A complete list can be found in the Linux-PAM documentation and the Open Group's single-sign on service (see for details).

PAM item



User name


Service name


Text asking for user name

Conversation Functions

The callback feature is also called the PAM conversation as outlined in the figure at the beginning of this article. The PAM conversation trick requires the application programmer to implement a function that can handle the call-backs. The conversation function is used by the modules to get the application to prompt the user for authentication-relevant information, for example, the user's password.

The conversation function is implemented by the application and must follow certain calling conventions, for example, which parameters the conversation function must have. The conversation function receives a number of messages from a PAM module, and when the function returns execution to the module, a set of data structures must be set.

But fortunately Linux PAM does implement a generic conversation. The function conv in the header file pam_misc can be used in most applications. The call to pam_start in the sample application as discussed in the section Opening and Closing a PAM Session is as follows:

retval = pam_start(“vault”, user, &conv, &pamh);

The third parameter is a pointer to the conversation function as implemented by the Linux-PAM library. In order to use this function in your applications you must include the pam_misc header file, by having the include statement at the beginning of the application:

#include <security/pam_misc.h>

If you are not using Linux-PAM, you might find the conversation function in the pamtester utility ( a place to learn how such a function is programmed—see the compat.c file for details.

Working with Error Messages

The PAM library functions return an integer, which indicates how the request went. Mostly, the PAM functions return PAM_SUCCESS but if an error occurs, the pam_strerror can be used to generate a text string. From the sample application we have the following call to pam_strerror (wrapped in a call to the standard error output printing function).

fprintf(stderr, “%sn”, pam_strerror(pamh, retval));

Both the return value from the previous call to a PAM function and the PAM handler are used in order to generate the text string. Even the PAM_SUCCESS return code can be used as an error code, but this will lead to text that does to indicate an error (the typical text is Success).

If your application should react to errors more intelligently than just printing out an error message before failing; for example, you could give the user a second chance to correct a wrongly typed password; you must observe the possible error codes for each PAM function—see the section Return Codes for a list of the most common ones.

Developing your Own PAM Modules

The dominant programming language of UNIX is C, and it is in many ways easier to develop new modules in C than any other language. It might sound like a huge assignment to develop a PAM module, but many modules are small—ranging from 100 to 1000 lines of code in the C language. Of course, the pam_unix module is typically a very large one. The implementation of the module in Linux-PAM is about 4500 lines of code—a large portion is used to check new passwords.

The PAM run-time environment expects a few things from the modules. In particular the API for a set of functions related to the management groups must be followed. The example module presented in this article is a very simple one—about 70 lines of C code. It only operates in the session management group, and it sets up a number of Secure Shell A module can support one or more management groups. Each supported management group is implemented by one or more functions in the module. The general declaration of these functions is:

PAM_EXTERN int pam_sm_FUNC(pam_handle_t *pamh, int flags, int argc, const char **argv)

The FUNC is explained in the table below.


Management Group




Authentication of the user



Setting credentials



Validating account health



Manipulating passwords



Open a new session



Clean up when closing a session

The function operates on a PAM handle (pamh), which is created by the pam_start function. The PAM handle contain all the data about the current PAM session. The argc and argv represent the arguments for the particular function.

A macro (a #define construct in the C programming language) must be defined for each management group that the module supports. The macros follow the pattern PAM_SM_<group>. In the sample module, only the session management group is supported, and this leads to the macro at the top of the source code:


Return Codes

On behalf of the applications, the PAM runtime calls functions for authentication, opening a session, and so on in modules defined by the stack in the configuration of a particular service. The return codes of functions are indicators of what the function was able to deduce about the user. It is vital for the PAM runtime that the results from the modules in the stack are returned. Otherwise PAM cannot make decisions on whether the user should be allowed to log in or not.

The most obvious return code from any function in a module is PAM_SUCCESS. This return code should be used in the case where everything goes fine. The table below lists a subset of the return codes—a complete list can be gathered from the Linux-PAM documentation.

Return code

Management group




Everything went well


Auth, Account, Password

The authentication token (user name) is not known



Any error related to opening or closing sessions


Auth, Account

Authentication failed



Account has expired


Supporting Functions

The PAM data structures might hold information gathered from one module that can be useful for the following modules. Two important supporting functions are: pam_get_user and pam_strerror.

The pam_get_user function is used to obtain the user name or authentication token. The user name is supplied to PAM by the person logging in. When the function is called, the user name might have been already obtained by PAM in a prior module. Typically the module in the auth stack will request the user to provide a user name.

If the user name is not known to PAM when the pam_get_user function is called, PAM will automatically use the conversation function to get it. Whether the conversation function is called or not is decided by PAM and not the module developer.

The sample module calls the function as:

if (pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) {
syslog(LOG_ERR, “cannot determine user name”);

The function returns PAM_SUCCESS if a user name can be obtained. Moreover, the user name is stored in a string pointed to by the variable username.

The pam_strerror function has already been mentioned. Still, the function might be useful for module developers in order to give unified error messages. Instead of using syslog in the code, a similar code snippet is:

if ((pam_error=pam_get_user(pamh, &username, NULL)) != PAM_SUCCESS) {
fprintf(stderr, “%sn”, pam_strerror(pamh, pam_error));
return pam_error;

Of course, module developers can use any function that can be called from within a C program. The sample module uses a number of standard C functions for file and string operations. Moreover, the syslog facility is also used.


PAM modules are shared objects (so files). A shared object can be loaded on demand, and the PAM subsystem does not require a complete recompile if a module is added, removed, or modified.

Using GNU development tools, it is not difficult to compile modules for Linux-PAM. The sample module is compiled and installed by the following commands:

$ gcc -fPIC -c pam_tunnels.c
$ ld -x --shared -o pam_tunnels.o
$ sudo cp pam_tunnels.o /lib/security

It is possible to compile the PAM subsystem as one big static system. It might be feasible in embedded systems where flexibility is sacrificed for smaller systems. In such a case, you must supply a structure in the source code so PAM knows which function implements what. In the sample module, the structure is set to:

struct pam_module _pam_deny_modstruct = {

This is basically a listing of the functions that can be implemented by a module.

When the new module is compiling without errors, it must be tested. A simple testing method is to set up a test service and use the pamtester utility.


Developing with PAM might be a new world for you. But if you are used to UNIX programming in C, it is not a completely strange world.

Applications can easily become PAM-aware, and that might give your applications a great deal of flexibility when it comes to authentication. Moreover, in situations where you cannot find a PAM module that satisfies your demands, it is possible to write your own module.


Books to Consider

Pluggable Authentication Modules: The Definitive Guide to PAM for Linux SysAdmins and C Developers
$ 6.00
Learning Joomla! 1.5 Extension Development: Creating Modules, Components, and Plugins with PHP
$ 13.20
comments powered by Disqus

An Introduction to 3D Printing

Explore the future of manufacturing and design  - read our guide to 3d printing for free