Sun Java Solaris Communities My SDN Account Join SDN

Article

User Authentication on the Solaris OS Part 3: PAM Conversation Functions

 
By Rich Teer, September 2007  

Learn to use the conversation functions of the PAM (Pluggable Authentication Modules) API and examine a sample program.

Contents

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.

We started the second article in this series 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 a 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);

where:

  • 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 (for example, a prompt asking for a password)
  • resp points to a buffer that holds messages from the user (for example, a typed-in password)
  • 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 that 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 can 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. The type of message is indicated by msg_style, and can be one of the following four values:

  • PAM_PROMPT_ECHO_OFF: Prompt the user, disabling echoing of their response.
  • PAM_PROMPT_ECHO_ON: Prompt the user, echoing their response.
  • PAM_ERROR_MSG: Print an error message.
  • PAM_TEXT_INFO: Print 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 look at one. There are two functions in our source file. The first is a helper function named 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 and then free it. We clear the memory before freeing it because it might contain sensitive data, such as 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 (that is,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 getpassphrase 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. (Note that 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 1 of this series, we changed line 51 to the following before compiling the example program in Part 2:

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
  • 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 (for example, 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 man 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.

Summary

In this article we described what PAM conversation functions are, and what they must do (that is, 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 2 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.

References and Recommended Reading
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. For more information, see Rich's web site.

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.