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