Sun Java Solaris Communities My SDN Account Join SDN
 
Article

User Authentication on the Solaris OS Part 2: Introduction to PAM

 
By Rich Teer, August 2007  

Learn to use PAM (Pluggable Authentication Modules) for user authentication, which decouples the authentication mechanism from the application.

Introduction

In the first article in this series we covered the basics of password-based user authentication. We defined the terms authentication and authorisation, discussed local file-based password storage and encryption algorithms, and described the API that the Solaris OS provides for reading and encrypting passwords. Finally, we showed an example program that asks the invoker for a password and compares it to their login password.

The Solaris OS provides an extensible mechanism for user authentication, called the PAM (Pluggable Authentication Modules) framework. In this article we provide an overview of the PAM facility and develop a PAM-based version of the program that we wrote in the first article in this series. Although an application that uses PAM for user authentication is more complex than one that doesn't, using PAM has the advantage of decoupling the authentication mechanism from the application. This means that new authentication mechanisms can be added (e.g., biometric scans) or existing ones modified without changing the application.

PAM Overview

The PAM facility is made up of two parts: the PAM library, which provides the API and SPI (Service Provider Interface), and various stackable service modules. A configuration file named /etc/pam.conf is used to configure the service modules for various system services, such as login or cron. Applications communicate with the PAM library via the API, whereas service modules use the SPI.

PAM Service Modules

A service module is a shared library that provides one or more of the following authentication and security services:

  • Authentication. These service modules are used to authenticate access to an account or service, and to set up user credentials.
  • Account management. These service modules determine the validity of the current user's account. For example, an account management module could check password or account expiration, or enforce access time restrictions.
  • Session management. These service modules are responsible for setting up and tearing down login sessions.
  • Password management. These service modules enforce password strength and previous-use rules, and perform authentication token updates.

Ideally, PAM services should be implemented in service modules that have simple well-defined tasks, so that configuration flexibility is increased. The service modules can then be used as required by appropriate definitions in /etc/pam.conf.

PAM Configuration File

We stated previously that the PAM facility is configured with its configuration file, /etc/pam.conf, which is managed by the system administrator. Each system service can have one or more entries in pam.conf. The order of the entries in pam.conf is very important because a misconfigured pam.conf file can result in a system that locks out all users in multiuser mode. System services that don't have their own set of entries use those for the "other" service.

Each entry in the configuration file consists of several space-separated fields, the first four of which are mandatory:

  • Service name. This is the name of the service this entry pertains to. Examples include cron, login, and passwd. An application can use one or more service names, as determined by its needs. The service name other is reserved as a wildcard: if the service name specified by the application isn't found in the configuration file, the entries for other are used. An example of an application that uses more than one service name is the SSH daemon, sshd, which uses per-authentication method keywords. (See the SECURITY section of the sshd manual page for more details.)
  • Module type. This is the type of service module, which is account, auth, password, or session. The account service modules verify the validity of the account (for example, password or account expiration, and access hours restrictions), auth service modules authenticate account access and set up user credentials, password service modules are used for managing users' password changes, and session service modules are used to set up and tear down login sessions.
  • Control flag. This determines the role of the module in determining the success or failure of the service. Valid values are binding, optional, required, requisite, and sufficient. We describe these in more detail in the next section.
  • Module path. This is the file system path to the shared object that implements the service module. Relative path names are assumed to be relative to /usr/lib/security/$ISA, where $ISA is a macro that causes the PAM library to look in the directory for the application's specific architecture.
  • Module options. This lists any options that are to be passed to the service module, and should be documented in the module's manual page.
Stacking PAM Modules

We stated previously that applications can use multiple service modules, and that an entry for each module appears in /etc/pam.conf. When an application calls one of the following files, the PAM library reads pam.conf to determine which modules to use:

pam_authenticate
pam_acct_mgmt
pam_setcred
pam_open_session
pam_close_session
pam_chauthtok

If the service has only one entry in pam.conf, the result of that module determines the outcome of an operation that uses that service. However, if the service has multiple entries in pam.conf, the modules are stacked, and the outcome of operations is determined by an integration process that takes into account the results from all modules in the service's PAM stack (unless prematurely terminated).

For a given service, each module is executed in the order in which it appears in pam.conf. The success or failure of each module is integrated into the overall operation result, as determined by the value of the module's control flag. This control flag must be one of the following five values:

  • binding. If no previous modules with a required control flag set have failed, successfully meeting this module's requirements immediately returns a successful result to the application, bypassing any other modules in the stack. A failure causes a "required failure" to be recorded and subsequent modules in the stack are executed.
  • optional. If this module fails, an "optional failure" is recorded and subsequent modules in the stack are executed. Otherwise subsequent modules in the stack are executed.
  • required. In order to use the service, this module's requirements must be met. If they are not met, a "required failure" is recorded and subsequent modules in the stack are executed. Only if all required or binding modules have no errors is success returned to the application.
  • requisite. In order to use the service, this module's requirements must be met. If they are not met, a "required failure" is immediately returned to the application and subsequent modules in the stack are not executed. Otherwise, subsequent modules in the stack are executed. Only if all requisite modules have no errors is success returned to the application.
  • sufficient. If this module fails, an "optional failure" is recorded. Otherwise, if no previous errors have been recorded, success is immediately returned to the application and subsequent modules in the stack are not executed.

The preceding description is fairly complicated, so let's take a look at a real example from pam.conf, in this case for the rlogin service. Here are the rlogin entries in pam.conf:

rlogin  auth sufficient         pam_rhosts_auth.so.1	
rlogin  auth requisite          pam_authtok_get.so.1
rlogin  auth required           pam_dhkeys.so.1
rlogin  auth required           pam_unix_cred.so.1
rlogin  auth required           pam_unix_auth.so.1

The first observation we can make is that all of the modules for rlogin are for authentication, and the first one (pam_rhosts_auth) is flagged as sufficient. This means that when the rlogin service requests authentication, a positive result from pam_rhosts_auth halts processing and immediately returns success to the application.

If pam_rhosts_auth fails, the next module (pam_authtok_get) is executed. This is flagged as requisite, which means that if pam_authtok_get fails, processing is halted and failure is immediately returned to the application. If pam_authtok_get is successful, the remaining three modules (pam_dhkeys, pam_unix_cred, and pam_unix_auth) are executed in sequence. Each of these modules is flagged as required, which means that all of them must return successfully in order for success to be returned to the application. However, if one of them fails, the failure is noted and is returned to the application only after all three modules have been executed.

When pam_unix_auth is executed, there are no more modules to execute, so the indication of success or failure is returned to the application. If failure is indicated, the user is denied access to the system via rlogin.

Some API Functions

Now that we've described the PAM framework and how it's configured, let's take a look at some of the PAM API functions. For a very simple PAM-aware application, there are three functions we need to consider: pam_start, pam_end, and pam_authenticate.

The pam_start Function

The pam_start function is used to initiate a PAM authentication transaction.

#include <security/pam_appl.h>
int pam_start (const char service, const char user,
	const struct pam_conv pam_conv, pam_handle_t pamh);

This function initiates an authentication transaction for the service and user specified by service and user respectively. The pam_conv argument specifies the conversation function to be used. On success, pamh points to a handle which must be used with subsequent PAM functions, in much the same way that file descriptors are used for files. We discuss conversation functions in the next part of this series, but for now we can state that as the name implies, conversation functions handle conversations with the user (for example, prompting for their password).

The pam_end Function

The pam_end function is used to terminate a PAM authentication transaction.

#include <security/pam_appl.h>

int pam_end (pam_handle_t *pamh, int status);

The PAM authentication transaction specified by pamh is terminated, and the status argument is passed to the cleanup function (if any) that is stored within the PAM handle.

The pam_authenticate Function

The pam_authenticate function is used to authenticate the current user.

#include <security/pam_appl.h>

int pam_authenticate (pam_handle_t *pamh, int flags);

This function is used to authenticate the current user, which is the user specified at the time the handle, pamh, was created by calling pam_start. Exactly how the user is authenticated is module-dependant, but authentication usually involves asking the user for their password, prompting the user for their one-time password or token, or some other method of validating the user's identity. Other authentication mechanisms include biometric scans and smart cards. The flags argument can be used to set various flags that are used by the authentication service.

An Example PAM Application

We've covered enough material now to take a look at an example application that uses PAM for authentication. As in the previous article, our example repeatedly asks for the password of the current user (as determined by the real user ID of the process), until the correct one is entered, which will cause the program to end. Here's the source code for our example program.

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <pwd.h>
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 #include <string.h>
 7 #include <security/pam_appl.h>
 8 extern int check_conv (int num_msg, struct pam_message **msg,
 9 struct pam_response *resp, void *app_data);
10 int main (void)
11 {
12     struct passwd *pwd_info;
13     struct pam_conv conv = {check_conv, NULL};
14     pam_handle_t *ph;
15     int error;
16     if ((pwd_info = getpwuid (getuid ())) == NULL) {
17             fprintf (stderr, "Call to getpwuid failed\n");
18             exit (1);
19     }
20     if ((error = pam_start ("check_pass", pwd_info -> pw_name,
21         &conv, &ph)) != PAM_SUCCESS) {
22         fprintf (stderr, "Call to pam_start failed: %s\n",
23             pam_strerror (ph, error));
24         exit (1);
25     }
26     for (;;) {
27         error = pam_authenticate (ph, 0);
28         if (error == PAM_SUCCESS) {
29             printf ("Passwords match.\n");
30             break;
31         } else {
32             printf ("Passwords don't match.\n");
33         }
34     }
35     pam_end (ph, 0);
36     return (0);
37 }

Let's take a closer look at this 37-line program.

1-9: Include header files and declare the conversation function. Although we do not describe conversation functions until the next article in this series, we show the function that we use for this example below for readers who want to experiment now.

16-19: Call getpwuid to obtain the password file information for the current user, as identified by the real user ID of the process. We could use getlogin or cuserid instead, but security-conscious programs should avoid these functions because they rely on, and trust, the contents of /var/adm/utmpx. On some UNIX platforms, although not on Solaris, this file is world-writable, so a malicious user could change their entry to that of an authorised user and our program would not be aware of it. We do this because we must pass the user's user name to pam_start in the next step.

20-25: Call pam_start to initiate the PAM session, using "check_pass" as the service name, and the user name we ascertained previously. Note that because the service name we're using doesn't exist in the default /etc/pam.conf, the authentication entries for the other service will be used. If an error occurs, inform the user and exit.

26-34: In an infinite loop, call pam_authenticate to authenticate the user, using the conversation function we established when we called pam_start. Very briefly, it is this conversation function that prompts for and reads a password from the user. If pam_authenticate returns PAM_SUCCESS, print a message stating that the passwords match and break out of the loop. Otherwise, print a failure message and try again.

35: Call pam_end to terminate the PAM transaction.

Here's the source code for our conversation function. (We'll take a detailed look at it in the next article in the series.)

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <pwd.h>
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 #include <string.h>
 7 #include <security/pam_appl.h>
 8 int check_conv (int num_msg, struct pam_message **msg,
 9     struct pam_response **resp, void *app_data)
10 {
11     struct pam_message *m = *msg;
12     struct pam_response *r;
13     int i;
14     char *ct_passwd;
15     if ((num_msg <= 0) || (num_msg >= PAM_MAX_NUM_MSG)) {
16         fprintf (stderr, "Invalid number of messages\n");
17         *resp = NULL;
18         return (PAM_CONV_ERR);
19     }
20     if ((*resp = r = calloc (num_msg, sizeof (struct pam_response))) == NULL)
21         return (PAM_BUF_ERR);
22     for (i = 0; i < num_msg; i++) {
23         switch (m->msg_style) {
24             case PAM_PROMPT_ECHO_OFF:
25                 ct_passwd = getpassphrase ("Enter password: ");
26                 r->resp = strdup (ct_passwd);
27                 m++;
28                 r++;
29                 break
30             case PAM_PROMPT_ECHO_ON:
31                 if (m->msg)
32                     fputs (m->msg, stdout);
33                 r->resp = NULL;
34                 m++;
35                 r++;
36                 break;
37             case PAM_ERROR_MSG:
38                 if (m->msg)
39                     fprintf (stderr, "%s\n", m->msg);
40                 m++;
41                 r++;
42                 break;
43             case PAM_TEXT_INFO:
44                 if (m->msg)
45                     printf ("%s\n", m->msg);
46                 m++;
47                 r++;
48                 break;
49         }
50         return (PAM_SUCCESS);
51     }
52 }

After compiling this program, lets's run it and see what happens.

rich@marrakesh4112# make pam_check_pass
cc -o pam_check_pass -lpam pam_check_pass.c pam_check_pass_conv.c
pam_check_pass.c:
pam_check_pass_conv.c:
rich@marrakesh4113# ./pam_check_pass
Enter password: 
Passwords don't match.
Enter password: 
Passwords don't match.

Notice that no matter what password we type, we are told that they don't match. This is because the user "rich" is not privileged. Behind the scenes we need to read the shadow file to compare the typed password to our login one, because "rich" is a local user and the shadow file (/etc/shadow) is readable only by root. Let's become root and try again.

rich@marrakesh4114# su
Password: 
# ./pam_check_pass
Enter password: 
Passwords don't match.
Enter password: 
Passwords match.

This time, after first entering a wrong password, we succeed. Note that using least privilege isn't an option when using PAM. This is because all zone privileges are required.

As before, with a little bit of work, this example could be modified to become a simple terminal locking program, and again this is an exercise which is left to the reader.

Summary

We started this article with a brief overview of PAM and then described various parts of the PAM framework. We talked about PAM service modules, the PAM configuration file, /etc/pam.conf, and described how service modules can be stacked.

We then described a few important functions in the PAM API: pam_start, pam_end, and pam_authenticate. Finally, we wrote an example program that tied all these things together: a PAM-aware version of the password comparison program we wrote in the first article in this series.

In the next article in this series, we'll see how to write PAM conversation functions and take a look at some of the other API functions.

Acknowledgements

Many thanks to Glenn Brunette for reviewing this article.

References

About the Author

Rich Teer is CEO of My Online Home Inventory and an independent Solaris OS consultant who has been an active member of the Solaris community for more than ten years. He is the author of the best-selling Sun Microsystems Press book, Solaris Systems Programming, and numerous related articles. He was a member of the OpenSolaris pilot program, and currently serves on the OpenSolaris Governing Board (OGB). Rich lives in Kelowna, British Columbia, with his wife, Jenny. His web site can be found at www.rite-group.com/rich.

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.