|
Learn to write PAM (Pluggable Authentication Modules) service modules for authentication and security services, and see an example module.
Contents
In the first three articles in this series (Part 1, Part 2, and Part 3) we covered the basics of password-based user authentication, concentrating on the use of PAM (Pluggable Authentication Modules). We described the PAM API that applications (called PAM consumers) use for authentication, and showed how to write PAM conversation functions.
In this fourth, and final, article we'll describe PAM service modules and show an example of how to write them.
A service module is a shared library that provides one or more of the following authentication and security services:
- Authentication. These service modules are used to authenticate access to an account or service, and to set up user credentials.
- Account management. These service modules determine the validity of the account being accessed. For example, an account management module could check password or account expiration, or enforce access time or account lockout restrictions.
- Session management. These service modules are responsible for setting up and tearing down PAM sessions for previously authenticated users or services.
- Password management. These service modules enforce password strength and previous-use rules, and perform authentication token updates.
Ideally, PAM services should be implemented in service modules that have simple well-defined tasks, so that configuration flexibility is increased. The service modules can then be used as required by appropriate definitions in the PAM configuration file, /etc/pam.conf. We described PAM configuration files in Part 2 of this series.
Service Module Requirements
We mentioned in Part 2 of this series that PAM consumers call one or more of the following functions to perform user authentication and related functions:
pam_authenticate
pam_acct_mgmt
pam_setcred
pam_open_session
pam_close_session
pam_chauthtok
Each of these functions is implemented in service modules by functions having the same name, except that the pam_ prefix is replaced with pam_sm_. So, pam_authenticate is implemented by pam_sm_authenticate, pam_sm_acct_mgmt implements pam_acct_mgmt, and so on. Service modules that we write must provide one or more of these functions.
To communicate with PAM consumer applications, service modules use the pam_get_item and pam_set_item functions, as shown in the following code example. (We should also point out that PAM consumers can use these functions to communicate with service modules.)
#include <security/pam_appl.h>
int pam_get_item(const pam_handle_t *pamh, int item_type,
void **item);
int pam_set_item(pam_handle_t *pamh, int item_type,
const void *item);
|
The pam_set_item function enables service modules to update information for the PAM transaction specified by the handle, pamh. The information type, specified by item_type, can be one of about a dozen specified in the pam_set_item man page. Examples of item types include PAM_AUTHTOK, PAM_CONV, PAM_USER, and PAM_USER_PROMPT. The value we want to set the PAM information to is specified by item.
Similarly, the information for a PAM transaction can be accessed by calling pam_get_item. In this case, a pointer to the PAM information of the specified type is placed into item.
Service modules can access and update module-specific information using the pam_get_data and pam_set_data functions. We won't discuss these functions further because we're focusing on communications between PAM service modules and their consumers. Interested readers are referred to these functions' man pages for more details.
PAM service modules must provide a PAM return code to their consumer. This return code must be one of three types:
PAM_SUCCESS. The module makes a positive decision that is part of the requested policy.
PAM_IGNORE. The module does not make a decision that is part of the requested policy.
PAM_<error>. The module makes a negative decision that is part of the request policy. The error code can either be one that is generic to the PAM infrastructure (for example, PAM_USER_UNKNOWN is an error indicating that the user specified by the PAM handle is unknown, PAM_PERM_DENIED means that the authentication request is denied by this module, and so on), or a module-specific one. In the latter case, error codes must be unique to each module (that is, not be used by any other module), and should therefore be documented in the module's man page.
To prevent the display of unwanted messages, all service modules must honour the PAM_SILENT flag. We recommend the use of the debug flag to enable the logging of diagnostic debugging information via the syslog facility. Debugging messages logged using syslog should use the LOG_AUTH facility and LOG_DEBUG severity level. Any other messages logged using syslog should use the LOG_AUTH facility with an appropriate priority level.
Important: The syslog-related functions openlog, closelog, and setlogmask must not be used in service modules because they interfere with the application's settings.
An Example Service Module
Now that we've described service modules and what they must do, let's have a look at one. The service module we write provides a mechanism by which named users in a certain group are denied access. An example of where this could be useful would be a web hosting company: customers are allowed to connect via ftp and sftp, but login shells are forbidden. This access policy can be enforced by using this module and naming the customers in the forbidden group.
This type of account access policy is applied to users who have successfully authenticated, so it could be characterized as account management. PAM-aware applications call pam_acct_mgmt to perform this task, so our example module implements pam_sm_acct_mgmt, which has the following prototype:
#include <security/pam_appl.h>
#include <security/pam_modules.h>
int pam_sm_acct_mgmt (pam_handle_t *pamh, int flags, int argc,
const char **argv);
|
The PAM handle returned by pam_start is referenced by pamh, flags contains any flags passed to the module by the application, and argc and argv contain the number of module options specified in pam.conf and the list of options respectively.
Here's the source code to our example module.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <grp.h>
4 #include <string.h>
5 #include <syslog.h>
6 #include <security/pam_appl.h>
7 int pam_sm_acct_mgmt (pam_handle_t *ph, int flags, int argc, char **argv)
8 {
9 char *user = NULL;
10 char *host = NULL;
11 char *service = NULL;
12 char *denied_group = "";
13 char group_buf[8192];
14 struct group grp;
15 struct pam_conv *conversation;
16 struct pam_message msg;
17 struct pam_message *msgp = &msg;
18 struct pam_response *resp = NULL;
19 int i;
20 int err;
21 int no_warn = 0;
22 int debug = 0;
23 int ret_val;
24 for (i = 0; i < argc; i++) {
25 if (strcasecmp (argv[i], "nowarn") == 0)
26 no_warn = 1;
27 else if (strcasecmp (argv[i], "debug") == 0)
28 debug = 1;
29 else if (strncmp (argv[i], "group=", 6) == 0)
30 denied_group = &argv[i][6];
31 }
32 if (flags & PAM_SILENT)
33 no_warn = 1;
34 pam_get_user (ph, &user, NULL);
35 pam_get_item (ph, PAM_SERVICE, (void **)&service);
36 pam_get_item (ph, PAM_RHOST, (void **)&host);
37 if (user == NULL) {
38 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: user not set", service);
39 ret_val = PAM_USER_UNKNOWN;
40 goto out;
41 }
42 if (host == NULL)
43 host = "unknown";
44 if (getgrnam_r (denied_group, &grp, group_buf, sizeof (group_buf)) == NULL) {
45 syslog (LOG_AUTH | LOG_NOTICE, "%s: denied_group: group \"%s\" not defined",
46 service, denied_group);
47 ret_val = PAM_SYSTEM_ERR;
48 goto out;
49 }
50 if (grp.gr_mem[0] == '\0') {
51 if (debug)
52 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: group \"%s\" is empty: "
53 "all users allowed", service, grp.gr_name);
54 ret_val = PAM_IGNORE;
55 goto out;
56 }
57 for (; grp.gr_mem[0]; grp.gr_mem++) {
58 if (strcmp (grp.gr_mem[0], user) == 0) {
59 msg.msg_style = PAM_ERROR_MSG;
60 msg.msg = "Access denied: you are not on the access list for this host.";
61 pam_get_item (ph, PAM_CONV, (void **)&conversation);
62 if ((no_warn == 0) && (conversation != NULL)) {
63 err = conversation->conv (1, &msgp, &resp, conversation->appdata_ptr);
64 if (debug && err != PAM_SUCCESS) {
65 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: conversation returned %s",
66 service, pam_strerror (ph, err));
67 }
68 if (resp != NULL) {
69 if (resp->resp)
70 free (resp->resp);
71 free (resp);
72 }
73 }
74 syslog (LOG_AUTH | LOG_NOTICE, "%s: denied_group: Connection for %s "
75 "not allowed from %s", service, user, host);
76 ret_val = PAM_PERM_DENIED;
77 goto out;
78 }
79 }
80 if (debug)
81 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: user %s is not a member of "
82 "group %s. Access granted.", service, user, grp.gr_name);
83
84 ret_val = PAM_SUCCESS;
85 out:
86 return (ret_val);
87 }
|
Let's take a closer look at this 80-line function. Note that for the sake of this example, we've arbitrarily limited the buffer group_buf (defined on line 13) to 8K characters. In a real program we'd probably dynamically size this buffer depending on the maximum system value, as determined by calling sysconf.
1-6:Include the required header files.
24-33:Interpret the module options, setting the debug and no warnings flags as appropriate.
34-36:Get the user, service, and remote host names.
37-41:Deny access if the user isn't specified.
44-49:Deny access if the specified group isn't defined.
50-56:Allow access for all users if the specified group has no named members.
57-79:Check to see if the user is a member of the group. If so, deny access, and (if warnings aren't disabled) call the conversation function to pass the appropriate error message back to the user. Note that the denial is always reported to syslog. Note that for brevity we've used strcmp to compare the user names. In a real application we'd probably use strncmp to avoid buffer overflows. Notice also our use of pam_strerror on line 66. This function returns the error message associated with its second argument, in much the same manner as strerror does for regular error messages.
80-84:If we get here, the user is not a member of the specified group so access is granted.
85-87:Return to the caller.
Service modules are shared objects, so we use the following command to build our example with Sun's Studio compiler.
rich@ultra20# cc -c -Kpic -o pam_service_module.so pam_service_module.c
|
(Users of gcc should replace -Kpic with -fpic.)
The PAM infrastructure performs various security checks, so our shared object must be owned by root, as shown in the following example.
rich@ultra20# su -
root@ultra20# chown root:root /home/rich/pam_service_module.so
|
When we've finished testing our service module and are ready to deploy it, we would normally place the shared object in /usr/lib/security/$ISA, where $ISA represents the instructions set of the target machine. Another place we might install our own modules (if we provide them in package format) is /opt/lib/security/$ISA.
Finally, we need to add an entry for our new module to pam.conf, as shown here.
other account required /home/rich/pam_service_module.so group=staff debug
|
All being well, named members of the group staff will be denied access. With the user rich being named as a member of the group staff, trying to log in using ssh fails, as shown in the following example. (Using telnet will also fail, but security-conscious people shouldn't use telnet.)
rich@sunblade1000# ssh ultra20
Connection closed by 192.168.0.2
|
Changing the denied group (to root, for example) allows the user rich to log in, as shown here.
rich@sunblade1000# ssh ultra20
Last login: Sun Dec 2 16:43:05 2007 from sunblade1000
Sun Microsystems Inc. SunOS 5.11 snv_70 October 2007
rich@ultra20#
|
Removing the user rich from the member list of the group named staff has the same effect.
In this article we described what PAM service modules are, and what they must do (that is, what is expected of a service module). We stated that service modules must implement one or more of the following functions, depending on what the service module is to do: pam_sm_authenticate, pam_sm_acct_mgmt, pam_sm_setcred, pam_sm_open_session, pam_sm_close_session, and pam_sm_chauthtok. We also briefly described the two functions that service modules and PAM consumers use to communicate with each other.
We then described the three types of values that service modules must return to their caller, which indicate that the request was successful, ignored, or resulted in an error (including failure).
After discussing the types of logging a service module is expected to do, and when not to log certain messages, we showed an example service module that implements a policy of denying access to named members of the specified group.
Finally, we showed how to build and install service modules.
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. His web site can be
found at www.rite-group.com/rich.
|
|