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