Contained WithinFind More DocumentationFeatured Support Resources | Download this book in PDF (1731 KB)
Chapter 3 Writing PAM Applications and ServicesPluggable authentication modules (PAM) provide system entry applications with authentication and related security services. This chapter is intended for developers of system entry applications who wish to provide authentication, account management, session management, and password management through PAM modules. There is also information for designers of PAM service modules. The following topics are discussed: PAM was originally developed at Sun. The PAM specification has since been submitted to X/Open, which is now the Open Group. The PAM specification is available in X/Open Single Sign-On Service (XSSO) - Pluggable Authentication, Open Group, UK ISBN 1-85912-144-6 June 1997. The Solaris implementation of PAM is described in the pam(3PAM), libpam(3LIB), and pam_sm(3PAM) man pages. Introduction to the PAM FrameworkThe PAM framework consists of four parts:
The framework provides a uniform way for authentication-related activities to take place. This approach enables application developers to use PAM services without having to know the semantics of the policy. Algorithms are centrally supplied. The algorithms can be modified independently of the individual applications. With PAM, administrators can tailor the authentication process to the needs of a particular system without having to change any applications. Adjustments are made through pam.conf, the PAM configuration file. The following figure illustrates the PAM architecture. Applications communicate with the PAM library through the PAM application programming interface (API). PAM modules communicate with the PAM library through the PAM service provider interface (SPI). Thus, the PAM library enables applications and modules to communicate with each other. Figure 3–1 PAM Architecture
PAM Service ModulesA PAM service module is a shared library that provides authentication and other security services to system entry applications such as login, rlogin, and telnet. The four types of PAM services are:
A PAM module can implement one or more of these services. The use of simple modules with well-defined tasks increases configuration flexibility. PAM services should thus be implemented in separate modules. The services can then be used as needed as defined in the pam.conf(4) file. For example, the Solaris OS provides the pam_authtok_check(5) module for system administrators to configure the site's password policy. The pam_authtok_check(5) module checks proposed passwords for various strength criteria. For a complete list of Solaris PAM modules, see man pages section 5: Standards, Environments, and Macros. The PAM modules have the prefix pam_. PAM LibraryThe PAM library, libpam(3LIB), is the central element in the PAM architecture:
PAM Authentication Process
As an example of how consumers use the PAM library for user authentication, consider how login authenticates a user: In this way, the PAM library connects PAM applications with the PAM modules that have been configured by the system administrator. Requirements for PAM ConsumersPAM consumers must be linked with the PAM library libpam. Before an application can use any service that is provided by the modules, the application must initialize its instance of the PAM library by calling pam_start(3PAM). The call to pam_start() initializes a handle that must be passed to all subsequent PAM calls. When an application is finished with the PAM services, pam_end() is called to clean up any data that was used by the PAM library. Communication between the PAM application and the PAM modules takes place through items. For example, the following items are useful for initialization:
For a complete list of available items, see pam_set_item(3PAM). Items can be set by the application through pam_set_item(3PAM). Values that have been set by the modules can be retrieved by the application through pam_get_item(3PAM). However, PAM_AUTHTOK and PAM_OLDAUTHTOK cannot be retrieved by the application. The PAM_SERVICE item cannot be set. PAM ConfigurationThe PAM configuration file, pam.conf(4), is used to configure PAM service modules for system services, such as login, rlogin, su, and cron. The system administrator manages this file. An incorrect order of entries in pam.conf can cause unforeseen side effects. For example, a badly configured pam.conf can lock out users so that single-user mode becomes necessary for repair. For information on PAM configuration, see PAM Configuration (Reference) in System Administration Guide: Security Services. Writing Applications That Use PAM ServicesThis section provides a sample application that uses several PAM functions. A Simple PAM Consumer ExampleThe following PAM consumer application is provided as an example. The example is a basic terminal-lock application that validates a user trying to access a terminal.
The example goes through the following steps: The following example shows the source code for the sample PAM consumer application. Note – The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5. Example 3–1 Sample PAM Consumer Application/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <signal.h>
#include <pwd.h>
#include <errno.h>
#include <security/pam_appl.h>
extern int pam_tty_conv(int num_msg, struct pam_message **msg,
struct pam_response **response, void *appdata_ptr);
/* Disable keyboard interrupts (Ctrl-C, Ctrl-Z, Ctrl-\) */
static void
disable_kbd_signals(void)
{
(void) signal(SIGINT, SIG_IGN);
(void) signal(SIGTSTP, SIG_IGN);
(void) signal(SIGQUIT, SIG_IGN);
}
/* Terminate current user session, i.e., logout */
static void
logout()
{
pid_t pgroup = getpgrp();
(void) signal(SIGTERM, SIG_IGN);
(void) fprintf(stderr, "Sorry, your session can't be restored.\n");
(void) fprintf(stderr, "Press return to terminate this session.\n");
(void) getchar();
(void) kill(-pgroup, SIGTERM);
(void) sleep(2);
(void) kill(-pgroup, SIGKILL);
exit(-1);
}
int
/*ARGSUSED*/
main(int argc, char *argv)
{
struct pam_conv conv = { pam_tty_conv, NULL };
pam_handle_t *pamh;
struct passwd *pw;
int err;
disable_kbd_signals();
if ((pw = getpwuid(getuid())) == NULL) {
(void) fprintf(stderr, "plock: Can't get username: %s\n",
strerror(errno));
exit(1);
}
/* Initialize PAM framework */
err = pam_start("plock", pw->pw_name, &conv, &pamh);
if (err != PAM_SUCCESS) {
(void) fprintf(stderr, "plock: pam_start failed: %s\n",
pam_strerror(pamh, err));
exit(1);
}
/* Authenticate user in order to unlock screen */
do {
(void) fprintf(stderr, "Terminal locked for %s. ", pw->pw_name);
err = pam_authenticate(pamh, 0);
if (err == PAM_USER_UNKNOWN) {
logout();
} else if (err != PAM_SUCCESS) {
(void) fprintf(stderr, "Invalid password.\n");
}
} while (err != PAM_SUCCESS);
/* Make sure account and password are still valid */
switch (err = pam_acct_mgmt(pamh, 0)) {
case PAM_SUCCESS:
break;
case PAM_USER_UNKNOWN:
case PAM_ACCT_EXPIRED:
/* User not allowed in anymore */
logout();
break;
case PAM_NEW_AUTHTOK_REQD:
/* The user's password has expired. Get a new one */
do {
err = pam_chauthtok(pamh, 0);
} while (err == PAM_AUTHTOK_ERR);
if (err != PAM_SUCCESS)
logout();
break;
default:
logout();
}
if (pam_setcred(pamh, PAM_REFRESH_CRED) != PAM_SUCCESS){
logout();
}
(void) pam_end(pamh, 0);
return(0);
/*NOTREACHED*/
}
Other Useful PAM FunctionsThe previous example, Example 3–1, is a simple application that demonstrates only a few of the major PAM functions. This section describes some other PAM functions that can be useful. The pam_open_session(3PAM) function is called to open a new session after a user has been successfully authenticated. The pam_getenvlist(3PAM) function is called to establish a new environment. pam_getenvlist() returns a new environment to be merged with the existing environment. Writing Conversation FunctionsA PAM module or application can communicate with a user in a number of ways: command line, dialog box, and so on. As a result, the designer of a PAM consumer that communicates with users needs to write a conversation function. A conversation function passes messages between the user and module independently of the means of communication. A conversation function derives the message type from the msg_style parameter in the conversation function callback pam_message parameter. See pam_start(3PAM). Developers should make no assumptions about how PAM is to communicate with users. Rather, the application should exchange messages with the user until the operation is complete. Applications should display the message strings for the conversation function without interpretation or modification. An individual message can contain multiple lines, control characters, or extra blank spaces. Note that service modules are responsible for localizing any strings sent to the conversation function. A sample conversation function, pam_tty_conv(), is provided below. The pam_tty_conv() takes the following arguments:
The sample function gets user input from stdin. The routine needs to allocate memory for the response buffer. A maximum, PAM_MAX_NUM_MSG, can be set to limit the number of messages. If the conversation function returns an error, the conversation function is responsible for clearing and freeing any memory that has been allocated for responses. In addition, the conversation function must set the response pointer to NULL. Note that clearing memory should be accomplished using a zero fill approach. The caller of the conversation function is responsible for freeing any responses that have been returned to the caller. To conduct the conversation, the function loops through the messages from the user application. Valid messages are written to stdout, and any errors are written to stderr. Note – The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5. Example 3–2 PAM Conversation Function/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "@(#)pam_tty_conv.c 1.4 05/02/12 SMI"
#define __EXTENSIONS__ /* to expose flockfile and friends in stdio.h */
#include <errno.h>
#include <libgen.h>
#include <malloc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <stropts.h>
#include <unistd.h>
#include <termio.h>
#include <security/pam_appl.h>
static int ctl_c; /* was the conversation interrupted? */
/* ARGSUSED 1 */
static void
interrupt(int x)
{
ctl_c = 1;
}
/* getinput -- read user input from stdin abort on ^C
* Entry noecho == TRUE, don't echo input.
* Exit User's input.
* If interrupted, send SIGINT to caller for processing.
*/
static char *
getinput(int noecho)
{
struct termio tty;
unsigned short tty_flags;
char input[PAM_MAX_RESP_SIZE];
int c;
int i = 0;
void (*sig)(int);
ctl_c = 0;
sig = signal(SIGINT, interrupt);
if (noecho) {
(void) ioctl(fileno(stdin), TCGETA, &tty);
tty_flags = tty.c_lflag;
tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
(void) ioctl(fileno(stdin), TCSETAF, &tty);
}
/* go to end, but don't overflow PAM_MAX_RESP_SIZE */
flockfile(stdin);
while (ctl_c == 0 &&
(c = getchar_unlocked()) != '\n' &&
c != '\r' &&
c != EOF) {
if (i < PAM_MAX_RESP_SIZE) {
input[i++] = (char)c;
}
}
funlockfile(stdin);
input[i] = '\0';
if (noecho) {
tty.c_lflag = tty_flags;
(void) ioctl(fileno(stdin), TCSETAW, &tty);
(void) fputc('\n', stdout);
}
(void) signal(SIGINT, sig);
if (ctl_c == 1)
(void) kill(getpid(), SIGINT);
return (strdup(input));
}
/* Service modules do not clean up responses if an error is returned.
* Free responses here.
*/
static void
free_resp(int num_msg, struct pam_response *pr)
{
int i;
struct pam_response *r = pr;
if (pr == NULL)
return;
for (i = 0; i < num_msg; i++, r++) {
if (r->resp) {
/* clear before freeing -- may be a password */
bzero(r->resp, strlen(r->resp));
free(r->resp);
r->resp = NULL;
}
}
free(pr);
}
/* ARGSUSED */
int
pam_tty_conv(int num_msg, struct pam_message **mess,
struct pam_response **resp, void *my_data)
{
struct pam_message *m = *mess;
struct pam_response *r;
int i;
if (num_msg <= 0 || num_msg >= PAM_MAX_NUM_MSG) {
(void) fprintf(stderr, "bad number of messages %d "
"<= 0 || >= %d\n",
num_msg, PAM_MAX_NUM_MSG);
*resp = NULL;
return (PAM_CONV_ERR);
}
if ((*resp = r = calloc(num_msg,
sizeof (struct pam_response))) == NULL)
return (PAM_BUF_ERR);
/* Loop through messages */
for (i = 0; i < num_msg; i++) {
int echo_off;
/* bad message from service module */
if (m->msg == NULL) {
(void) fprintf(stderr, "message[%d]: %d/NULL\n",
i, m->msg_style);
goto err;
}
/*
* fix up final newline:
* removed for prompts
* added back for messages
*/
if (m->msg[strlen(m->msg)] == '\n')
m->msg[strlen(m->msg)] = '\0';
r->resp = NULL;
r->resp_retcode = 0;
echo_off = 0;
switch (m->msg_style) {
case PAM_PROMPT_ECHO_OFF:
echo_off = 1;
/*FALLTHROUGH*/
case PAM_PROMPT_ECHO_ON:
(void) fputs(m->msg, stdout);
r->resp = getinput(echo_off);
break;
case PAM_ERROR_MSG:
(void) fputs(m->msg, stderr);
(void) fputc('\n', stderr);
break;
case PAM_TEXT_INFO:
(void) fputs(m->msg, stdout);
(void) fputc('\n', stdout);
break;
default:
(void) fprintf(stderr, "message[%d]: unknown type "
"%d/val=\"%s\"\n",
i, m->msg_style, m->msg);
/* error, service module won't clean up */
goto err;
}
if (errno == EINTR)
goto err;
/* next message/response */
m++;
r++;
}
return (PAM_SUCCESS);
err:
free_resp(i, r);
*resp = NULL;
return (PAM_CONV_ERR);
}
Writing Modules That Provide PAM ServicesThis section describes how to write PAM service modules. Requirements for PAM Service ProvidersPAM service modules use pam_get_item(3PAM) and pam_set_item(3PAM) to communicate with applications. To communicate with each other, service modules use pam_get_data(3PAM) and pam_set_data(3PAM). If service modules from the same project need to exchange data, then a unique data name for that project should be established. The service modules can then share this data through the pam_get_data() and pam_set_data() functions. Service modules must return one of three classes of PAM return code:
If a service module performs multiple functions, these functions should be split up into separate modules. This approach gives system administrators finer-grained control for configuring policy. Man pages should be provided for any new service modules. Man pages should include the following items:
Service modules are required to honor the PAM_SILENT flag for preventing display of messages. The debug argument is recommended for logging debug information to syslog. Use syslog(3C) with LOG_AUTH and LOG_DEBUG for debug logging. Other messages should be sent to syslog() with LOG_AUTH and the appropriate priority. openlog(3C), closelog(3C), and setlogmask(3C) must not be used as these functions interfere with the applications settings. Sample PAM Provider Service ModuleA sample PAM service module follows. This example checks to see if the user is a member of a group that is permitted access to this service. The provider then grants access on success or logs an error message on failure.
The example goes through the following steps: The following example shows the source code for the sample PAM provider. Note – The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5. Example 3–3 Sample PAM Service Module/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <stdlib.h>
#include <grp.h>
#include <string.h>
#include <syslog.h>
#include <libintl.h>
#include <security/pam_appl.h>
/*
* by default, only users who are a member of group "root" are allowed access
*/
#define DEFAULT_GROUP "root"
static char *NOMSG =
"Sorry, you are not on the access list for this host - access denied.";
int
pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char **argv)
{
char *user = NULL;
char *host = NULL;
char *service = NULL;
const char *allowed_grp = DEFAULT_GROUP;
char grp_buf[4096];
struct group grp;
struct pam_conv *conversation;
struct pam_message message;
struct pam_message *pmessage = &message;
struct pam_response *res = NULL;
int i;
int nowarn = 0;
int debug = 0;
/* Set flags to display warnings if in debug mode. */
for (i = 0; i < argc; i++) {
if (strcasecmp(argv[i], "nowarn") == 0)
nowarn = 1;
else if (strcasecmp(argv[i], "debug") == 0)
debug = 1;
else if (strncmp(argv[i], "group=", 6) == 0)
allowed_grp = &argv[i][6];
}
if (flags & PAM_SILENT)
nowarn = 1;
/* Get user name,service name, and host name. */
(void) pam_get_user(pamh, &user, NULL);
(void) pam_get_item(pamh, PAM_SERVICE, (void **) &service);
(void) pam_get_item(pamh, PAM_RHOST, (void **) &host);
/* Deny access if user is NULL. */
if (user == NULL) {
syslog(LOG_AUTH|LOG_DEBUG,
"%s: members_only: user not set", service);
return (PAM_USER_UNKNOWN);
}
if (host == NULL)
host = "unknown";
/*
* Deny access if vuser group is required and user is not in vuser
* group
*/
if (getgrnam_r(allowed_grp, &grp, grp_buf, sizeof (grp_buf)) == NULL) {
syslog(LOG_NOTICE|LOG_AUTH,
"%s: members_only: group \"%s\" not defined",
service, allowed_grp);
return (PAM_SYSTEM_ERR);
}
/* Ignore this module if group contains no members. */
if (grp.gr_mem[0] == 0) {
if (debug)
syslog(LOG_AUTH|LOG_DEBUG,
"%s: members_only: group %s empty: "
"all users allowed.", service, grp.gr_name);
return (PAM_IGNORE);
}
/* Check to see if user is in group. If so, return SUCCESS. */
for (; grp.gr_mem[0]; grp.gr_mem++) {
if (strcmp(grp.gr_mem[0], user) == 0) {
if (debug)
syslog(LOG_AUTH|LOG_DEBUG,
"%s: user %s is member of group %s. "
"Access allowed.",
service, user, grp.gr_name);
return (PAM_SUCCESS);
}
}
/*
* User is not a member of the group.
* Set message style to error and specify denial message.
*/
message.msg_style = PAM_ERROR_MSG;
message.msg = gettext(NOMSG);
/* Use conversation function to display denial message to user. */
(void) pam_get_item(pamh, PAM_CONV, (void **) &conversation);
if (nowarn == 0 && conversation != NULL) {
int err;
err = conversation->conv(1, &pmessage, &res,
conversation->appdata_ptr);
if (debug && err != PAM_SUCCESS)
syslog(LOG_AUTH|LOG_DEBUG,
"%s: members_only: conversation returned "
"error %d (%s).", service, err,
pam_strerror(pamh, err));
/* free response (if any) */
if (res != NULL) {
if (res->resp)
free(res->resp);
free(res);
}
}
/* Report denial to system log and return error to caller. */
syslog(LOG_NOTICE | LOG_AUTH, "%s: members_only: "
"Connection for %s not allowed from %s", service, user, host);
return (PAM_PERM_DENIED);
}
|