User Authentication on the Solaris OS

Part 3: PAM Conversation Functions

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 Solaris 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.

We started the second article with a brief overview of PAM (Pluggable Authentication Modules), 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, and finally wrote a PAM-aware version of the password comparison program we wrote in the first instalment of this series.

In this article, we describe conversation functions, and take a brief look at some other functions provided by the PAM API.

Conversation Functions

As their name suggests, conversation functions handle conversations with the user. That is, they display messages to, and collect input from, a user, service, or device. Conversations can take many forms, for example, the familiar “Login: ” prompt on text terminals, the GUI login manager that is most common these days, or even a fingerprint reader.

Applications using PAM for authentication (called PAM consumers) register the conversation function when they initiate an PAM session by calling the pam_start function we described in the previous article. Conversation functions, which are invoked by PAM service modules, have the following prototype:

    int conv_func (int num_msg, struct pam_message **msg,
        struct pam_response **resp, void *app_data);

When called, the num_msg argument contains the number of messages being passed to the function (which must be between 0 and PAM_MAX_NUM_MSG inclusive), msg is a pointer to the buffer that holds messages to the user (e.g., a prompt asking for a password), resp points to a buffer that holds messages from the user (e.g., a typed-in password), and app_data points to a buffer containing application-specific data. PAM service modules are responsible for allocating and freeing the memory used by msg, whereas the memory for resp is allocated by applications and freed by service modules.

Conversation functions we write should make no assumptions about how PAM communicates with users. Instead, conversation functions should exchange messages with the user until the operation is complete. Also, any messages from PAM modules should be displayed without modification (service modules are responsible for their own message localisation). Individual messages are free-format, and may contain several lines, blanks, or control characters.

Messages are stored in pam_message structures, which have the following members:

    struct pam_message {
        int msg_style;
        char *msg;
    };

The msg member points to the actual message. msg_style indicates the type of message, and may be one of the following four values:

    PAM_PROMPT_ECHO_OFFPrompt the user, disabling echoing of their response.
    PAM_PROMPT_ECHO_ONPrompt the user, echoing their response.
    PAM_ERROR_MSGPrint an error message.
    PAM_TEXT_INFOPrint a general information message.

Similarly, responses from authentication modules are stored in pam_response structures, which have the following members:

    struct pam_response {
        char *resp;
        int resp_retcode;
    };

The resp member contains the actual response, and resp_retcode contains the return code. The latter of these is not currently used, and should be set to 0. If the conversation function is returning an error, the response pointer must be set to NULL.

Conversation functions have one other responsibility: they must strip any terminating newline characters for PAM_PROMPT_ECHO_OFF and PAM_PROMPT_ECHO_ON messages, and add newline characters to PAM_ERROR_MSG and PAM_TEXT_INFO messages as appropriate.

An Example Conversation Function

Now that we’ve described conversation functions and what they must do, let’s have a look at one. There are two functions in our source file. The first is a helper function called free_resp, which frees responses in the event of an error, and the second is the conversation function itself.

The first few lines of the source file include the various header files we need:

 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 <strings.h>
 8 #include <security/pam_appl.h>

Here’s the source code for free_resp:

 9 static void free_resp (int num_msg, struct pam_response *resp)
10 {
11     int i;
12     struct pam_response *r;

13     if (resp == NULL)
14         return;

15     r = resp;

16     for (i = 0; i < num_msg; i++, r++) {
17         if (r->resp) {
18             bzero (r->resp, strlen (r->resp));
19             free (r->resp);
20             r->resp = NULL;
21         }
22     }

23     free (resp);
24 }

Let’s look at this 16-line function.

13-14 If the resp pointer is NULL, there’s nothing to do so return.
16-22 Loop through each message. If the message isn’t NULL, zero its memory then free it. We clear the memory before freeing it because it might contain sensitive data, like a password.
23    Finally, free the first response.

Here’s the source code for our conversation function. It is an improved version of the function we showed in the previous part of this series.

25 int check_conv (int num_msg, struct pam_message **msg,
26     struct pam_response **resp, void *app_data)
27 {
28     int i;
29     struct pam_message *m;
30     struct pam_response *r;
31     char *ct_passwd;

32     m = *msg;

33     if ((num_msg <= 0) || (num_msg >= PAM_MAX_NUM_MSG)) {
34         fprintf (stderr, "Invalid number of messages\n");
35         *resp = NULL;
36         return (PAM_CONV_ERR);
37     }

38     if ((*resp = r = calloc (num_msg, sizeof (struct pam_response))) == NULL)
39         return (PAM_BUF_ERR);

40     for (i = 0; i < num_msg; i++, m++, r++) {
41         if (m->msg == NULL) {
42             fprintf (stderr, "Message %d: %d/NULL\n", i, m->msg_style);
43             goto err;
44         }

45         if (m->msg[strlen (m->msg)] == '\n')
46             m->msg[strlen (m->msg)] = '\0';

47         r->resp = NULL;
48         r->resp_retcode = 0;

49         switch (m->msg_style) {
50             case PAM_PROMPT_ECHO_OFF:
51                 ct_passwd = getpassphrase (m->msg);
52                 r->resp = strdup (ct_passwd);
53                 break;

54             case PAM_PROMPT_ECHO_ON:
55                 printf ("%s", m->msg);
56                 break;

57             case PAM_ERROR_MSG:
58                 fprintf (stderr, "%s\n", m->msg);
59                 break;

60             case PAM_TEXT_INFO:
61                 printf ("%s\n", m->msg);
62                 break;
63         }
64     }

65     return (PAM_SUCCESS);

66 err:
67     free_resp (i, r);
68     *resp = NULL;

69     return (PAM_CONV_ERR);
70 }

Let’s take a closer look at this 46-line function.

33-37 Verify that a valid number of messages (i.e., between 0 and PAM_MAX_NUM_MSG inclusive) have been supplied.
38-39 Allocate a buffer for the response (if any).
41-44 For every message we’ve been passed, flag an error if the message pointer is NULL.
45-46 Fix up the final newline (if any): it is removed on the assumption that the text is a prompt. If the text is a message, a newline is added when the text is displayed.
47-48 Initialise the response structure.
49-63 If the message style is PAM_PROMPT_ECHO_OFF, call get passphrase to display the prompt stored in m->msg and collect a password from the user without echoing it. If the message style is PAM_PROMPT_ECHO_ON, we just print the prompt stored in m->msg (a real conversation function would read the user’s response after doing this, but we ignore this for our trivial example). If the message style is PAM_ERROR_MSG or PAM_TEXT_INFO, we print the message stored in m->msg followed by a newline. In the former case, the message is output to stderr, whereas in the latter case the message is output to stdout.
65    We’ve successfully dealt with all messages, so return success.
66-69 An error occurred, so clean up and return failure.

To make the output identical to the version we showed in part one of this series, we changed line 51 to the following before compiling the example program in part two:

    ct_passwd = getpassphrase ("Enter password ");

Other PAM API Functions

Now that we’ve covered conversation functions, let’s take a very brief look at some of the other functions provided by the PAM API: pam_acct_mgmt, pam_open_session, pam_close_session, pam_setcred, pam_set_item, and pam_get_item.

The pam_acct_mgmt Function

The pam_acct_mgmt function performs account validation procedures, and has the following prototype:

    #include <security/pam_appl.h>

    int pam_acct_mgmt (pam_handle_t *pamh, int flags);

This function performs account validity checks (e.g., that the password and account haven’t expired, and access time restrictions) and is typically called after the user has been authenticated (by calling pam_authenticate).

The pam_open_session and pam_close_session Functions

PAM consumers that start or terminate sessions should call one of these functions.

    #include <security/pam_appl.h>

    int pam_open_session (pam_handle_t *pamh, int flags);

    int pam_close_session (pam_handle_t *pamh, int flags);

After a user has been successfully authenticated using pam_authenticate and pam_acct_mgmt, pam_open_session should be called if the program is creating a new session. This causes the session modules to be notified about the new session. Conversely, when the session is terminated, pam_close_session should be called so that the session modules are notified.

The pam_setcred Function

User credentials for an authentication service are modified by the pam_setcred function.

    #include <security/pam_appl.h>

    int pam_setcred (pam_handle_t *pamh, int flags);

After a user has been authenticated and a session has been opened, the pam_setcred function is used to establish, modify, or delete their credentials.

The pam_set_item and pam_get_item Functions

PAM consumers and service modules can manipulate PAM information using pam_set_item and pam_get_item.

    #include <security/pam_appl.h>

    int pam_set_item (pam_handle_t *pamh, int item_type, const void *item);

    int pam_get_item (const pam_handle_t *pamh, int item_type, void **item);

Applications and service modules can update PAM information using pam_set_item. The type of information to be updated is denoted by the item_type argument, and can be one of several types. Type examples include the PAM service name, the user name, the tty name, and the user authentication token. A complete list of item types can be found in the pam_set_item manual page, pam_set_item(3PAM).

The values for each of these information types can be accessed using pam_get_item.

Space constraints prevent us from showing examples using these functions, but interested readers can see how they’re used by searching for the appropriate function name in the OpenSolaris source code, which can be found at src.opensolaris.org/source.

Summary

In this article we described what PAM conversation functions are, and what they must do (i.e., what is expected of a conversation function). We stated that conversation functions work with messages, and described the different message types.

We then showed a fairly generic example conversation function, which is a more refined version of the function we showed in part two of this series.

Finally, we briefly described some of the other functions provided by the PAM API: pam_acct_mgmt, pam_open_session, pam_close_session, pam_setcred, pam_set_item, and pam_get_item.

In the next (and final) article in this series, we’ll see how to write PAM service modules.

Acknowledgements

Many thanks to Glenn Brunette for reviewing this article.

Recommended Reading

The author’s book, Solaris Systems Programming [Teer 2005], is essential for readers developing Solaris applications. Many books in the Solaris 10 Software Developer Collection at docs.sun.com/app/docs/coll/45.20 are also worth a read—or at least, being aware of.

PAM is the subject of two Sun BluePrints books [Sun 2002a and Sun 2002b]. Chapter 3 of [Sun 2006] also contains much useful information.

Bibliography

Sun Microsystems. 2002a. Extending Authentication in the Solaris 9 Operating Environment Using Pluggable Authentication Modules (PAM): Part 1, www.sun.com/blueprints/0902/816-7669-10.pdf.

Sun Microsystems. 2002b. Extending Authentication in the Solaris 9 Operating Environment Using Pluggable Authentication Modules (PAM): Part 2, www.sun.com/blueprints/1002/816-7670-10.pdf.

Sun Microsystems. 2006. Solaris Security for Developers Guide, docs.sun.com/app/docs/doc/816-4863.

Teer, Rich. 2005. Solaris Systems Programming, ISBN 0-201-75039-2. See richteer.ca/ssp for more details.

Author’s Bio

Rich Teer is an independent Solaris 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 several Solaris-related articles. He was a member of the OpenSolaris pilot program, and currently serves on the OpenSolaris Community Advisory Board (CAB). Rich lives in Kelowna, British Columbia, with his wife, Jenny, and their canine child, Judge. His web site can be found at richteer.ca.