1: # Pluggable Authentication Modules (PAM)
2:
3: ## About
4:
5: This article describes the underlying principles and mechanisms of the
6: *Pluggable Authentication Modules (PAM)* library, and explains how to configure
7: PAM, how to integrate PAM into applications, and how to write PAM modules.
8:
9: See below for the license of this text.
10:
11: ## Introduction
12:
13: The Pluggable Authentication Modules (PAM) library is a generalized API for
14: authentication-related services which allows a system administrator to add new
15: authentication methods simply by installing new PAM modules, and to modify
16: authentication policies by editing configuration files.
17:
18: PAM was defined and developed in 1995 by Vipin Samar and Charlie Lai of Sun
19: Microsystems, and has not changed much since. In 1997, the Open Group published
20: the X/Open Single Sign-on (XSSO) preliminary specification, which standardized
21: the PAM API and added extensions for single (or rather integrated) sign-on. At
22: the time of this writing, this specification has not yet been adopted as a
23: standard.
24:
25: Although this article focuses primarily on FreeBSD 5.x and NetBSD 3.x, which
26: both use OpenPAM, it should be equally applicable to FreeBSD 4.x, which uses
27: Linux-PAM, and other operating systems such as Linux and Solaris.
28:
29: ## Terms and conventions
30:
31: ### Definitions
32:
33: The terminology surrounding PAM is rather confused. Neither Samar and Lai's
34: original paper nor the XSSO specification made any attempt at formally defining
35: terms for the various actors and entities involved in PAM, and the terms that
36: they do use (but do not define) are sometimes misleading and ambiguous. The
37: first attempt at establishing a consistent and unambiguous terminology was a
38: whitepaper written by Andrew G. Morgan (author of Linux-PAM) in 1999. While
39: Morgan's choice of terminology was a huge leap forward, it is in this author's
40: opinion by no means perfect. What follows is an attempt, heavily inspired by
41: Morgan, to define precise and unambiguous terms for all actors and entities
42: involved in PAM.
43:
44: * *account* -- The set of credentials the applicant is requesting from the
45: arbitrator.
46:
47: * *applicant* -- The user or entity requesting authentication.
48:
49: * *arbitrator* -- The user or entity who has the privileges necessary to verify
50: the applicant's credentials and the authority to grant or deny the request.
51:
52: * *chain* -- A sequence of modules that will be invoked in response to a PAM
53: request. The chain includes information about the order in which to invoke
54: the modules, what arguments to pass to them, and how to interpret the
55: results.
56:
57: * *client* -- The application responsible for initiating an authentication
58: request on behalf of the applicant and for obtaining the necessary
59: authentication information from him.
60:
61: * *facility* -- One of the four basic groups of functionality provided by PAM:
62: authentication, account management, session management and authentication
63: token update.
64:
65: * *module* -- A collection of one or more related functions implementing a
66: particular authentication facility, gathered into a single (normally
67: dynamically loadable) binary file and identified by a single name.
68:
69: * *policy* -- The complete set of configuration statements describing how to
70: handle PAM requests for a particular service. A policy normally consists of
71: four chains, one for each facility, though some services do not use all four
72: facilities.
73:
74: * *server* -- The application acting on behalf of the arbitrator to converse
75: with the client, retrieve authentication information, verify the applicant's
76: credentials and grant or deny requests.
77:
78: * *service* -- A class of servers providing similar or related functionality
79: and requiring similar authentication. PAM policies are defined on a
80: per-service basis, so all servers that claim the same service name will be
81: subject to the same policy.
82:
83: * *session* -- The context within which service is rendered to the applicant by
84: the server. One of PAM's four facilities, session management, is concerned
85: exclusively with setting up and tearing down this context.
86:
87: * *token* -- A chunk of information associated with the account, such as a
88: password or passphrase, which the applicant must provide to prove his
89: identity.
90:
91: * *transaction* -- A sequence of requests from the same applicant to the same
92: instance of the same server, beginning with authentication and session set-up
93: and ending with session tear-down.
94:
95: ### Usage examples
96:
97: This section aims to illustrate the meanings of some of the terms defined above by way of a handful of simple examples.
98:
99: #### Client and server are one
100:
101: This simple example shows `alice` [su(1)](http://netbsd.gw.com/cgi-bin/man-cgi?su+1+NetBSD-5.0.1+i386)'ing to `root`.
102:
103: $ whoami
104: alice
105: $ ls -l `which su`
106: -r-sr-xr-x 1 root wheel 10744 Dec 6 19:06 /usr/bin/su
107: $ su -
108: Password: xi3kiune
109: # whoami
110: root
111:
112: * The applicant is `alice`.
113: * The account is `root`.
114: * The [su(1)](http://netbsd.gw.com/cgi-bin/man-cgi?su+1+NetBSD-5.0.1+i386) process is both client and server.
115: * The authentication token is `xi3kiune`.
116: * The arbitrator is `root`, which is why [su(1)](http://netbsd.gw.com/cgi-bin/man-cgi?su+1+NetBSD-5.0.1+i386) is setuid `root`.
117:
118: #### Client and server are separate
119:
120: The example below shows `eve` try to initiate an
121: [ssh(1)](http://netbsd.gw.com/cgi-bin/man-cgi?ssh+1+NetBSD-5.0.1+i386)
122: connection to `login.example.com`, ask to log in as `bob`, and succeed. Bob
123: should have chosen a better password!
124:
125: $ whoami
126: eve
127: $ ssh bob@login.example.com
128: bob@login.example.com's password: god
129: Last login: Thu Oct 11 09:52:57 2001 from 192.168.0.1
130: NetBSD 3.0 (LOGIN) #1: Thu Mar 10 18:22:36 WET 2005
131:
132: Welcome to NetBSD!
133: $
134:
135: * The applicant is `eve`.
136: * The client is Eve's [ssh(1)](http://netbsd.gw.com/cgi-bin/man-cgi?ssh+1+NetBSD-5.0.1+i386) process.
137: * The server is the [sshd(8)](http://netbsd.gw.com/cgi-bin/man-cgi?sshd+8+NetBSD-5.0.1+i386) process on `login.example.com`
138: * The account is `bob`.
139: * The authentication token is `god`.
140: * Although this is not shown in this example, the arbitrator is `root`.
141:
142: #### Sample policy
143:
144: The following is FreeBSD's default policy for `sshd`:
145:
146: sshd auth required pam_nologin.so no_warn
147: sshd auth required pam_unix.so no_warn try_first_pass
148: sshd account required pam_login_access.so
149: sshd account required pam_unix.so
150: sshd session required pam_lastlog.so no_fail
151: sshd password required pam_permit.so
152:
153: * This policy applies to the `sshd` service (which is not necessarily
154: restricted to the
155: [sshd(8)](http://netbsd.gw.com/cgi-bin/man-cgi?sshd+8+NetBSD-5.0.1+i386)
156: server.)
157:
158: * `auth`, `account`, `session` and `password` are facilities.
159:
160: * `pam_nologin.so`, `pam_unix.so`, `pam_login_access.so`, `pam_lastlog.so` and
161: `pam_permit.so` are modules. It is clear from this example that `pam_unix.so`
162: provides at least two facilities (authentication and account management.)
163:
164: There are some differences between FreeBSD and NetBSD PAM policies:
165:
166: * By default, every configuration is done under `/etc/pam.d`.
167:
168: * If configuration is non-existent, you will not have access to the system, in
169: contrast with FreeBSD that has a default policy of allowing authentication.
170:
171: * For authentication, NetBSD forces at least one `required`, `requisite` or
172: `binding` module to be present.
173:
174: ## PAM Essentials
175:
176: ### Facilities and primitives
177:
178: The PAM API offers six different authentication primitives grouped in four
179: facilities, which are described below.
180:
181: * `auth` -- *Authentication.* This facility concerns itself with authenticating
182: the applicant and establishing the account credentials. It provides two
183: primitives:
184:
185: * [pam\_authenticate(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_authenticate+3+NetBSD-5.0.1+i386)
186: authenticates the applicant, usually by requesting an authentication token
187: and comparing it with a value stored in a database or obtained from an
188: authentication server.
189:
190: * [pam\_setcred(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_setcred+3+NetBSD-5.0.1+i386)
191: establishes account credentials such as user ID, group membership and
192: resource limits.
193:
194: * `account` -- *Account management.* This facility handles
195: non-authentication-related issues of account availability, such as access
196: restrictions based on the time of day or the server's work load. It provides
197: a single primitive:
198:
199: * [pam\_acct\_mgmt(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_acct_mgmt+3+NetBSD-5.0.1+i386)
200: verifies that the requested account is available.
201:
202: * `session` -- *Session management.* This facility handles tasks associated
203: with session set-up and tear-down, such as login accounting. It provides two
204: primitives:
205:
206: * [pam\_open\_session(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_open_session+3+NetBSD-5.0.1+i386)
207: performs tasks associated with session set-up: add an entry in the `utmp`
208: and `wtmp` databases, start an SSH agent, etc.
209:
210: * [pam\_close\_session(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_close_session+3+NetBSD-5.0.1+i386)
211: performs tasks associated with session tear-down: add an entry in the
212: `utmp` and `wtmp` databases, stop the SSH agent, etc.
213:
214: * `password` -- *Password management.* This facility is used to change the
215: authentication token associated with an account, either because it has
216: expired or because the user wishes to change it. It provides a single
217: primitive:
218:
219: * [pam\_chauthtok(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_chauthtok+3+NetBSD-5.0.1+i386)
220: changes the authentication token, optionally verifying that it is
221: sufficiently hard to guess, has not been used previously, etc.
222:
223: ### Modules
224:
225: Modules are a very central concept in PAM; after all, they are the *M* in *PAM*.
226: A PAM module is a self-contained piece of program code that implements the
227: primitives in one or more facilities for one particular mechanism; possible
228: mechanisms for the authentication facility, for instance, include the UNIX®
229: password database, NIS, LDAP and Radius.
230:
231: #### Module Naming
232:
233: FreeBSD and NetBSD implement each mechanism in a single module, named
234: `pam_mechanism`.so (for instance, `pam_unix.so` for the UNIX mechanism.) Other
235: implementations sometimes have separate modules for separate facilities, and
236: include the facility name as well as the mechanism name in the module name. To
237: name one example, Solaris has a `pam_dial_auth.so.1` module which is commonly
238: used to authenticate dialup users. Also, almost every module has a man page with
239: the same name, i.e.:
240: [pam\_unix(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_unix+8+NetBSD-5.0.1+i386)
241: explains how the `pam_unix.so` module works.
242:
243: #### Module Versioning
244:
245: FreeBSD's original PAM implementation, based on Linux-PAM, did not use version
246: numbers for PAM modules. This would commonly cause problems with legacy
247: applications, which might be linked against older versions of the system
248: libraries, as there was no way to load a matching version of the required
249: modules.
250:
251: OpenPAM, on the other hand, looks for modules that have the same version number
252: as the PAM library (currently 2 in FreeBSD and 0 in NetBSD), and only falls back
253: to an unversioned module if no versioned module could be loaded. Thus legacy
254: modules can be provided for legacy applications, while allowing new (or newly
255: built) applications to take advantage of the most recent modules.
256:
257: Although Solaris PAM modules commonly have a version number, they're not truly
258: versioned, because the number is a part of the module name and must be included
259: in the configuration.
260:
261: #### Module Path
262:
263: There isn't a common directory for storing PAM modules. Under FreeBSD, they are
264: located at `/usr/lib` and, under NetBSD, you can find them in
265: `/usr/lib/security`.
266:
267: ### Chains and policies
268:
269: When a server initiates a PAM transaction, the PAM library tries to load a
270: policy for the service specified in the
271: [pam\_start(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_start+3+NetBSD-5.0.1+i386)
272: call. The policy specifies how authentication requests should be processed, and
273: is defined in a configuration file. This is the other central concept in PAM:
274: the possibility for the admin to tune the system security policy (in the wider
275: sense of the word) simply by editing a text file.
276:
277: A policy consists of four chains, one for each of the four PAM facilities. Each
278: chain is a sequence of configuration statements, each specifying a module to
279: invoke, some (optional) parameters to pass to the module, and a control flag
280: that describes how to interpret the return code from the module.
281:
282: Understanding the control flags is essential to understanding PAM configuration
283: files. There are a number of different control flags:
284:
285: * `binding` -- If the module succeeds and no earlier module in the chain has
286: failed, the chain is immediately terminated and the request is granted. If
287: the module fails, the rest of the chain is executed, but the request is
288: ultimately denied.
289:
290: This control flag was introduced by Sun in Solaris 9 (SunOS 5.9), and is also supported by OpenPAM.
291:
292: * `required` -- If the module succeeds, the rest of the chain is executed, and
293: the request is granted unless some other module fails. If the module fails,
294: the rest of the chain is also executed, but the request is ultimately denied.
295:
296: * `requisite` -- If the module succeeds, the rest of the chain is executed, and
297: the request is granted unless some other module fails. If the module fails,
298: the chain is immediately terminated and the request is denied.
299:
300: * `sufficient` -- If the module succeeds and no earlier module in the chain has
301: failed, the chain is immediately terminated and the request is granted. If
302: the module fails, the module is ignored and the rest of the chain is
303: executed.
304:
305: As the semantics of this flag may be somewhat confusing, especially when it is used for the last module in a chain, it is recommended that the `binding` control flag be used instead if the implementation supports it.
306:
307: * `optional` -- The module is executed, but its result is ignored. If all
308: modules in a chain are marked `optional`, all requests will always be
309: granted.
310:
311: When a server invokes one of the six PAM primitives, PAM retrieves the chain for
312: the facility the primitive belongs to, and invokes each of the modules listed in
313: the chain, in the order they are listed, until it reaches the end, or determines
314: that no further processing is necessary (either because a `binding` or
315: `sufficient` module succeeded, or because a `requisite` module failed.) The
316: request is granted if and only if at least one module was invoked, and all
317: non-optional modules succeeded.
318:
319: Note that it is possible, though not very common, to have the same module listed
320: several times in the same chain. For instance, a module that looks up user names
321: and passwords in a directory server could be invoked multiple times with
322: different parameters specifying different directory servers to contact. PAM
323: treat different occurrences of the same module in the same chain as different,
324: unrelated modules.
325:
326: ### Transactions
327:
328: The lifecycle of a typical PAM transaction is described below. Note that if any
329: of these steps fails, the server should report a suitable error message to the
330: client and abort the transaction.
331:
332: 1. If necessary, the server obtains arbitrator credentials through a mechanism
333: independent of PAM -- most commonly by virtue of having been started by `root`,
334: or of being setuid `root`.
335:
336: 2. The server calls
337: [pam\_start(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_start+3+NetBSD-5.0.1+i386)
338: to initialize the PAM library and specify its service name and the target
339: account, and register a suitable conversation function.
340:
341: 3. The server obtains various information relating to the transaction (such as
342: the applicant's user name and the name of the host the client runs on) and
343: submits it to PAM using
344: [pam\_set\_item(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_set_item+3+NetBSD-5.0.1+i386).
345:
346: 4. The server calls
347: [pam\_authenticate(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_authenticate+3+NetBSD-5.0.1+i386)
348: to authenticate the applicant.
349:
350: 5. The server calls
351: [pam\_acct\_mgmt(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_acct_mgmt+3+NetBSD-5.0.1+i386)
352: to verify that the requested account is available and valid. If the password is
353: correct but has expired,
354: [pam\_acct\_mgmt(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_acct_mgmt+3+NetBSD-5.0.1+i386)
355: will return `PAM_NEW_AUTHTOK_REQD` instead of `PAM_SUCCESS`.
356:
357: 6. If the previous step returned `PAM_NEW_AUTHTOK_REQD`, the server now calls
358: [pam\_chauthtok(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_chauthtok+3+NetBSD-5.0.1+i386)
359: to force the client to change the authentication token for the requested
360: account.
361:
362: 7. Now that the applicant has been properly authenticated, the server calls
363: [pam\_setcred(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_setcred+3+NetBSD-5.0.1+i386)
364: to establish the credentials of the requested account. It is able to do this
365: because it acts on behalf of the arbitrator, and holds the arbitrator's
366: credentials.
367:
368: 8. Once the correct credentials have been established, the server calls
369: [pam\_open\_session(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_open_session+3+NetBSD-5.0.1+i386)
370: to set up the session.
371:
372: 9. The server now performs whatever service the client requested -- for
373: instance, provide the applicant with a shell.
374:
375: 10. Once the server is done serving the client, it calls
376: [pam\_close\_session(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_close_session+3+NetBSD-5.0.1+i386)
377: to tear down the session.
378:
379: 11. Finally, the server calls
380: [pam\_end(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_end+3+NetBSD-5.0.1+i386)
381: to notify the PAM library that it is done and that it can release whatever
382: resources it has allocated in the course of the transaction.
383:
384: ## PAM Configuration
385:
386: ### PAM policy files
387:
388: #### The `/etc/pam.conf` file
389:
390: The traditional PAM policy file is `/etc/pam.conf`. This file contains all the
391: PAM policies for your system. Each line of the file describes one step in a
392: chain, as shown below:
393:
394: login auth required pam_nologin.so no_warn
395:
396: The fields are, in order: service name, facility name, control flag, module
397: name, and module arguments. Any additional fields are interpreted as additional
398: module arguments.
399:
400: A separate chain is constructed for each service / facility pair, so while the
401: order in which lines for the same service and facility appear is significant,
402: the order in which the individual services and facilities are listed is not. The
403: examples in the original PAM paper grouped configuration lines by facility, and
404: the Solaris stock `pam.conf` still does that, but FreeBSD's stock configuration
405: groups configuration lines by service. Either way is fine; either way makes
406: equal sense.
407:
408: #### The `/etc/pam.d` directory
409:
410: OpenPAM and Linux-PAM support an alternate configuration mechanism, which is the
411: preferred mechanism in FreeBSD and NetBSD. In this scheme, each policy is
412: contained in a separate file bearing the name of the service it applies to.
413: These files are stored in `/etc/pam.d/`.
414:
415: These per-service policy files have only four fields instead of `pam.conf`'s
416: five: the service name field is omitted. Thus, instead of the sample `pam.conf`
417: line from the previous section, one would have the following line in
418: `/etc/pam.d/login`:
419:
420: auth required pam_nologin.so no_warn
421:
422: As a consequence of this simplified syntax, it is possible to use the same
423: policy for multiple services by linking each service name to a same policy file.
424: For instance, to use the same policy for the `su` and `sudo` services, one could
425: do as follows:
426:
427: # cd /etc/pam.d
428: # ln -s su sudo
429:
430: This works because the service name is determined from the file name rather than
431: specified in the policy file, so the same file can be used for multiple
432: differently-named services.
433:
434: Since each service's policy is stored in a separate file, the `pam.d` mechanism
435: also makes it very easy to install additional policies for third-party software
436: packages.
437:
438: #### The policy search order
439:
440: As we have seen above, PAM policies can be found in a number of places. If no
441: configuration file is found for a particular service, the `/etc/pam.d/other` is
442: used instead. If that file does not exist, `/etc/pam.conf` is searched for
443: entries matching he specified service or, failing that, the "other" service.
444:
445: It is essential to understand that PAM's configuration system is centered on
446: chains.
447:
448: ### Breakdown of a configuration line
449:
450: As explained in the [PAM policy files](chap-pam.html#pam-config-file "18.5.1.
451: PAM policy files") section, each line in `/etc/pam.conf` consists of four or
452: more fields: the service name, the facility name, the control flag, the module
453: name, and zero or more module arguments.
454:
455: The service name is generally (though not always) the name of the application
456: the statement applies to. If you are unsure, refer to the individual
457: application's documentation to determine what service name it uses.
458:
459: Note that if you use `/etc/pam.d/` instead of `/etc/pam.conf`, the service name
460: is specified by the name of the policy file, and omitted from the actual
461: configuration lines, which then start with the facility name.
462:
463: The facility is one of the four facility keywords described in the
464: [[Facilities and primitives|guide/pam#facilities-primitives]]] section.
465:
466: Likewise, the control flag is one of the four keywords described in the [[Chains
467: and policies|guide/pam#chains-policies]] section, describing how to interpret
468: the return code from the module. Linux-PAM supports an alternate syntax that
469: lets you specify the action to associate with each possible return code, but
470: this should be avoided as it is non-standard and closely tied in with the way
471: Linux-PAM dispatches service calls (which differs greatly from the way Solaris
472: and OpenPAM do it.) Unsurprisingly, OpenPAM does not support this syntax.
473:
474: ### Policies
475:
476: To configure PAM correctly, it is essential to understand how policies are
477: interpreted.
478:
479: When an application calls
480: [pam\_start(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_start+3+NetBSD-5.0.1+i386),
481: the PAM library loads the policy for the specified service and constructs four
482: module chains (one for each facility.) If one or more of these chains are empty,
483: the corresponding chains from the policy for the `other` service are
484: substituted.
485:
486: When the application later calls one of the six PAM primitives, the PAM library
487: retrieves the chain for the corresponding facility and calls the appropriate
488: service function in each module listed in the chain, in the order in which they
489: were listed in the configuration. After each call to a service function, the
490: module type and the error code returned by the service function are used to
491: determine what happens next. With a few exceptions, which we discuss below, the
492: following table applies:
493:
494: [[!table data="""
495: | `PAM_SUCCESS` | `PAM_IGNORE` | `other`
496: binding | if (!fail) break; | - | fail = true;
497: required | - | - | fail = true;
498: requisite | - | - | fail = true; break;
499: sufficient | if (!fail) break; | - | -
500: optional | - | - | -
501: """]]
502:
503: If `fail` is true at the end of a chain, or when a `break` is reached, the
504: dispatcher returns the error code returned by the first module that failed.
505: Otherwise, it returns `PAM_SUCCESS`.
506:
507: The first exception of note is that the error code `PAM_NEW_AUTHTOK_REQD` is
508: treated like a success, except that if no module failed, and at least one module
509: returned `PAM_NEW_AUTHTOK_REQD`, the dispatcher will return
510: `PAM_NEW_AUTHTOK_REQD`.
511:
512: The second exception is that
513: [pam\_setcred(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_setcred+3+NetBSD-5.0.1+i386)
514: treats `binding` and `sufficient` modules as if they were `required`.
515:
516: The third and final exception is that
517: [pam\_chauthtok(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_chauthtok+3+NetBSD-5.0.1+i386)
518: runs the entire chain twice (once for preliminary checks and once to actually
519: set the password), and in the preliminary phase it treats `binding` and
520: `sufficient` modules as if they were `required`.
521:
522: ## PAM modules
523:
524: ### Common Modules
525:
526: #### pam\_deny(8)
527:
528: The
529: [pam\_deny(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_deny+8+NetBSD-5.0.1+i386)
530: module is one of the simplest modules available; it responds to any request with
531: `PAM_AUTH_ERR`. It is useful for quickly disabling a service (add it to the top
532: of every chain), or for terminating chains of `sufficient` modules.
533:
534: #### pam\_echo(8)
535:
536: The
537: [pam\_echo(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_echo+8+NetBSD-5.0.1+i386)
538: module simply passes its arguments to the conversation function as a
539: `PAM_TEXT_INFO` message. It is mostly useful for debugging, but can also serve
540: to display messages such as `Unauthorized access will be prosecuted` before
541: starting the authentication procedure.
542:
543: #### pam\_exec(8)
544:
545: The
546: [pam\_exec(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_exec+8+NetBSD-5.0.1+i386)
547: module takes its first argument to be the name of a program to execute, and the
548: remaining arguments are passed to that program as command-line arguments. One
549: possible application is to use it to run a program at login time which mounts
550: the user's home directory.
551:
552: #### pam\_ftpusers(8)
553:
554: The
555: [pam\_ftpusers(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_ftpusers+8+NetBSD-5.0.1+i386)
556: module successes if and only if the user is listed in `/etc/ftpusers`.
557: Currently, in NetBSD, this module doesn't understand the extended syntax of
558: [ftpd(8)](http://netbsd.gw.com/cgi-bin/man-cgi?ftpd+8+NetBSD-5.0.1+i386), but
559: this will be fixed in the future.
560:
561: #### pam\_group(8)
562:
563: The
564: [pam\_group(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_group+8+NetBSD-5.0.1+i386)
565: module accepts or rejects applicants on the basis of their membership in a
566: particular file group (normally `wheel` for
567: [su(1)](http://netbsd.gw.com/cgi-bin/man-cgi?su+1+NetBSD-5.0.1+i386)). It is
568: primarily intended for maintaining the traditional behaviour of BSD
569: [su(1)](http://netbsd.gw.com/cgi-bin/man-cgi?su+1+NetBSD-5.0.1+i386), but has
570: many other uses, such as excluding certain groups of users from a particular
571: service.
572:
573: In NetBSD, there is an argument called `authenticate` in which the user is asked
574: to authenticate using his own password.
575:
576: #### pam\_guest(8)
577:
578: The
579: [pam\_guest(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_guest+8+NetBSD-5.0.1+i386)
580: module allows guest logins using fixed login names. Various requirements can be
581: placed on the password, but the default behaviour is to allow any password as
582: long as the login name is that of a guest account. The
583: [pam\_guest(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_guest+8+NetBSD-5.0.1+i386)
584: module can easily be used to implement anonymous FTP logins.
585:
586: #### pam\_krb5(8)
587:
588: The
589: [pam\_krb5(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_krb5+8+NetBSD-5.0.1+i386)
590: module provides functions to verify the identity of a user and to set user
591: specific credentials using Kerberos 5. It prompts the user for a password and
592: obtains a new Kerberos TGT for the principal. The TGT is verified by obtaining a
593: service ticket for the local host. The newly acquired credentials are stored in
594: a credential cache and the environment variable KRB5CCNAME is set appropriately.
595: The credentials cache should be destroyed by the user at logout with
596: [kdestroy(1)](http://netbsd.gw.com/cgi-bin/man-cgi?kdestroy+1+NetBSD-5.0.1+i386).
597:
598: #### pam\_ksu(8)
599:
600: The
601: [pam\_ksu(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_ksu+8+NetBSD-5.0.1+i386)
602: module provides only authentication services for Kerberos 5 to determine whether
603: or not the applicant is authorized to obtain the privileges of the target
604: account.
605:
606: #### pam\_lastlog(8)
607:
608: The
609: [pam\_lastlog(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_lastlog+8+NetBSD-5.0.1+i386)
610: module provides only session management services. It records the session in
611: [utmp(5)](http://netbsd.gw.com/cgi-bin/man-cgi?utmp+5+NetBSD-5.0.1+i386),
612: [utmpx(5)](http://netbsd.gw.com/cgi-bin/man-cgi?utmpx+5+NetBSD-5.0.1+i386),
613: [wtmp(5)](http://netbsd.gw.com/cgi-bin/man-cgi?wtmp+5+NetBSD-5.0.1+i386),
614: [wtmpx(5)](http://netbsd.gw.com/cgi-bin/man-cgi?wtmpx+5+NetBSD-5.0.1+i386),
615: [lastlog(5)](http://netbsd.gw.com/cgi-bin/man-cgi?lastlog+5+NetBSD-5.0.1+i386)
616: and
617: [lastlogx(5)](http://netbsd.gw.com/cgi-bin/man-cgi?lastlogx+5+NetBSD-5.0.1+i386)
618: databases.
619:
620: #### pam\_login\_access(8)
621:
622: The
623: [pam\_login\_access(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_login_access+8+NetBSD-5.0.1+i386)
624: module provides an implementation of the account management primitive which
625: enforces the login restrictions specified in the
626: [login.access(5)](http://netbsd.gw.com/cgi-bin/man-cgi?login.access+5+NetBSD-5.0.1+i386)
627: table.
628:
629: #### pam\_nologin(8)
630:
631: The
632: [pam\_nologin(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_nologin+8+NetBSD-5.0.1+i386)
633: module refuses non-root logins when `/var/run/nologin` exists. This file is
634: normally created by
635: [shutdown(8)](http://netbsd.gw.com/cgi-bin/man-cgi?shutdown+8+NetBSD-5.0.1+i386)
636: when less than five minutes remain until the scheduled shutdown time.
637:
638: #### pam\_permit(8)
639:
640: The
641: [pam\_permit(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_permit+8+NetBSD-5.0.1+i386)
642: module is one of the simplest modules available; it responds to any request with
643: `PAM_SUCCESS`. It is useful as a placeholder for services where one or more
644: chains would otherwise be empty.
645:
646: #### pam\_radius(8)
647:
648: The
649: [pam\_radius(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_radius+8+NetBSD-5.0.1+i386)
650: module provides authentication services based upon the RADIUS (Remote
651: Authentication Dial In User Service) protocol.
652:
653: #### pam\_rhosts(8)
654:
655: The
656: [pam\_rhosts(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_rhosts+8+NetBSD-5.0.1+i386)
657: module provides only authentication services. It reports success if and only if
658: the target user's ID is not 0 and the remote host and user are listed in
659: `/etc/hosts.equiv` or in the target user's `~/.rhosts`.
660:
661: #### pam\_rootok(8)
662:
663: The
664: [pam\_rootok(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_rootok+8+NetBSD-5.0.1+i386)
665: module reports success if and only if the real user id of the process calling it
666: (which is assumed to be run by the applicant) is 0. This is useful for
667: non-networked services such as
668: [su(1)](http://netbsd.gw.com/cgi-bin/man-cgi?su+1+NetBSD-5.0.1+i386) or
669: [passwd(1)](http://netbsd.gw.com/cgi-bin/man-cgi?passwd+1+NetBSD-5.0.1+i386), to
670: which the `root` should have automatic access.
671:
672: #### pam\_securetty(8)
673:
674: The
675: [pam\_securetty(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_securetty+8+NetBSD-5.0.1+i386)
676: module provides only account services. It is used when the applicant is
677: attempting to authenticate as superuser, and the process is attached to an
678: insecure TTY.
679:
680: #### pam\_self(8)
681:
682: The
683: [pam\_self(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_self+8+NetBSD-5.0.1+i386)
684: module reports success if and only if the names of the applicant matches that of
685: the target account. It is most useful for non-networked services such as
686: [su(1)](http://netbsd.gw.com/cgi-bin/man-cgi?su+1+NetBSD-5.0.1+i386), where the
687: identity of the applicant can be easily verified.
688:
689: #### pam\_ssh(8)
690:
691: The
692: [pam\_ssh(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_ssh+8+NetBSD-5.0.1+i386)
693: module provides both authentication and session services. The authentication
694: service allows users who have passphrase-protected SSH secret keys in their
695: `~/.ssh` directory to authenticate themselves by typing their passphrase. The
696: session service starts
697: [ssh-agent(1)](http://netbsd.gw.com/cgi-bin/man-cgi?ssh-agent+1+NetBSD-5.0.1+i386)
698: and preloads it with the keys that were decrypted in the authentication phase.
699: This feature is particularly useful for local logins, whether in X (using
700: [xdm(1)](http://netbsd.gw.com/cgi-bin/man-cgi?xdm+1+NetBSD-5.0.1+i386) or
701: another PAM-aware X login manager) or at the console.
702:
703: This module implements what is fundamentally a password authentication scheme.
704: Care should be taken to only use this module over a secure session (secure TTY,
705: encrypted session, etc.), otherwise the user's SSH passphrase could be
706: compromised.
707:
708: Additional consideration should be given to the use of
709: [pam\_ssh(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_ssh+8+NetBSD-5.0.1+i386).
710: Users often assume that file permissions are sufficient to protect their SSH
711: keys, and thus use weak or no passphrases. Since the system administrator has no
712: effective means of enforcing SSH passphrase quality, this has the potential to
713: expose the system to security risks.
714:
715: #### pam\_unix(8)
716:
717: The
718: [pam\_unix(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_unix+8+NetBSD-5.0.1+i386)
719: module implements traditional UNIX® password authentication, using
720: [getpwnam(3)](http://netbsd.gw.com/cgi-bin/man-cgi?getpwnam+3+NetBSD-5.0.1+i386)
721: under FreeBSD or
722: [getpwnam\_r(3)](http://netbsd.gw.com/cgi-bin/man-cgi?getpwnam_r+3+NetBSD-5.0.1+i386)
723: under NetBSD to obtain the target account's password and compare it with the one
724: provided by the applicant. It also provides account management services
725: (enforcing account and password expiration times) and password-changing
726: services. This is probably the single most useful module, as the great majority
727: of admins will want to maintain historical behaviour for at least some services.
728:
729: ### NetBSD-specific PAM Modules
730:
731: #### pam\_skey(8)
732:
733: The
734: [pam\_skey(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_skey+8+NetBSD-5.0.1+i386)
735: module implements S/Key One Time Password (OTP) authentication methods, using
736: the `/etc/skeykeys` database.
737:
738: ## PAM Application Programming
739:
740: This section has not yet been written.
741:
742: ## PAM Module Programming
743:
744: This section has not yet been written.
745:
746: ## Sample PAM Application
747:
748: The following is a minimal implementation of
749: [su(1)](http://netbsd.gw.com/cgi-bin/man-cgi?su+1+NetBSD-5.0.1+i386) using PAM.
750: Note that it uses the OpenPAM-specific
751: [openpam\_ttyconv(3)](http://netbsd.gw.com/cgi-bin/man-cgi?openpam_ttyconv+3+NetBSD-5.0.1+i386)
752: conversation function, which is prototyped in `security/openpam.h`. If you wish
753: build this application on a system with a different PAM library, you will have
754: to provide your own conversation function. A robust conversation function is
755: surprisingly difficult to implement; the one presented in the [Sample PAM
756: Conversation Function](chap-pam.html#pam-sample-conv "18.11. Sample PAM
757: Conversation Function") sub-chapter is a good starting point, but should not be
758: used in real-world applications.
759:
760: #include <sys/param.h>
761: #include <sys/wait.h>
762:
763: #include <err.h>
764: #include <pwd.h>
765: #include <stdio.h>
766: #include <stdlib.h>
767: #include <string.h>
768: #include <syslog.h>
769: #include <unistd.h>
770:
771: #include <security/pam_appl.h>
772: #include <security/openpam.h> /* for openpam_ttyconv() */
773:
774: extern char **environ;
775:
776: static pam_handle_t *pamh;
777: static struct pam_conv pamc;
778:
779: static void
780: usage(void)
781: {
782:
783: fprintf(stderr, "Usage: su [login [args]]\n");
784: exit(1);
785: }
786:
787: int
788: main(int argc, char *argv[])
789: {
790: char hostname[MAXHOSTNAMELEN];
791: const char *user, *tty;
792: char **args, **pam_envlist, **pam_env;
793: struct passwd *pwd;
794: int o, pam_err, status;
795: pid_t pid;
796:
797: while ((o = getopt(argc, argv, "h")) != -1)
798: switch (o) {
799: case 'h':
800: default:
801: usage();
802: }
803:
804: argc -= optind;
805: argv += optind;
806:
807: if (argc > 0) {
808: user = *argv;
809: --argc;
810: ++argv;
811: } else {
812: user = "root";
813: }
814:
815: /* initialize PAM */
816: pamc.conv = &openpam_ttyconv;
817: pam_start("su", user, &pamc, &pamh);
818:
819: /* set some items */
820: gethostname(hostname, sizeof(hostname));
821: if ((pam_err = pam_set_item(pamh, PAM_RHOST, hostname)) != PAM_SUCCESS)
822: goto pamerr;
823: user = getlogin();
824: if ((pam_err = pam_set_item(pamh, PAM_RUSER, user)) != PAM_SUCCESS)
825: goto pamerr;
826: tty = ttyname(STDERR_FILENO);
827: if ((pam_err = pam_set_item(pamh, PAM_TTY, tty)) != PAM_SUCCESS)
828: goto pamerr;
829:
830: /* authenticate the applicant */
831: if ((pam_err = pam_authenticate(pamh, 0)) != PAM_SUCCESS)
832: goto pamerr;
833: if ((pam_err = pam_acct_mgmt(pamh, 0)) == PAM_NEW_AUTHTOK_REQD)
834: pam_err = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
835: if (pam_err != PAM_SUCCESS)
836: goto pamerr;
837:
838: /* establish the requested credentials */
839: if ((pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS)
840: goto pamerr;
841:
842: /* authentication succeeded; open a session */
843: if ((pam_err = pam_open_session(pamh, 0)) != PAM_SUCCESS)
844: goto pamerr;
845:
846: /* get mapped user name; PAM may have changed it */
847: pam_err = pam_get_item(pamh, PAM_USER, (const void **)&user);
848: if (pam_err != PAM_SUCCESS || (pwd = getpwnam(user)) == NULL)
849: goto pamerr;
850:
851: /* export PAM environment */
852: if ((pam_envlist = pam_getenvlist(pamh)) != NULL) {
853: for (pam_env = pam_envlist; *pam_env != NULL; ++pam_env) {
854: putenv(*pam_env);
855: free(*pam_env);
856: }
857: free(pam_envlist);
858: }
859:
860: /* build argument list */
861: if ((args = calloc(argc + 2, sizeof *args)) == NULL) {
862: warn("calloc()");
863: goto err;
864: }
865: *args = pwd->pw_shell;
866: memcpy(args + 1, argv, argc * sizeof *args);
867:
868: /* fork and exec */
869: switch ((pid = fork())) {
870: case -1:
871: warn("fork()");
872: goto err;
873: case 0:
874: /* child: give up privs and start a shell */
875:
876: /* set uid and groups */
877: if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
878: warn("initgroups()");
879: _exit(1);
880: }
881: if (setgid(pwd->pw_gid) == -1) {
882: warn("setgid()");
883: _exit(1);
884: }
885: if (setuid(pwd->pw_uid) == -1) {
886: warn("setuid()");
887: _exit(1);
888: }
889: execve(*args, args, environ);
890: warn("execve()");
891: _exit(1);
892: default:
893: /* parent: wait for child to exit */
894: waitpid(pid, &status, 0);
895:
896: /* close the session and release PAM resources */
897: pam_err = pam_close_session(pamh, 0);
898: pam_end(pamh, pam_err);
899:
900: exit(WEXITSTATUS(status));
901: }
902:
903: pamerr:
904: fprintf(stderr, "Sorry\n");
905: err:
906: pam_end(pamh, pam_err);
907: exit(1);
908: }
909:
910: ## Sample PAM Module
911:
912: The following is a minimal implementation of
913: [pam\_unix(8)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_unix+8+NetBSD-5.0.1+i386),
914: offering only authentication services. It should build and run with most PAM
915: implementations, but takes advantage of OpenPAM extensions if available: note
916: the use of
917: [pam\_get\_authtok(3)](http://netbsd.gw.com/cgi-bin/man-cgi?pam_get_authtok+3+NetBSD-5.0.1+i386),
918: which enormously simplifies prompting the user for a password.
919:
920: #include <sys/param.h>
921:
922: #include <pwd.h>
923: #include <stdlib.h>
924: #include <stdio.h>
925: #include <string.h>
926: #include <unistd.h>
927:
928: #include <security/pam_modules.h>
929: #include <security/pam_appl.h>
930:
931: #ifndef _OPENPAM
932: static char password_prompt[] = "Password:";
933: #endif
934:
935: #ifndef PAM_EXTERN
936: #define PAM_EXTERN
937: #endif
938:
939: PAM_EXTERN int
940: pam_sm_authenticate(pam_handle_t *pamh, int flags,
941: int argc, const char *argv[])
942: {
943: #ifndef _OPENPAM
944: const void *ptr;
945: const struct pam_conv *conv;
946: struct pam_message msg;
947: const struct pam_message *msgp;
948: struct pam_response *resp;
949: #endif
950: struct passwd *pwd;
951: const char *user;
952: char *crypt_password, *password;
953: int pam_err, retry;
954:
955: /* identify user */
956: if ((pam_err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
957: return (pam_err);
958: if ((pwd = getpwnam(user)) == NULL)
959: return (PAM_USER_UNKNOWN);
960:
961: /* get password */
962: #ifndef _OPENPAM
963: pam_err = pam_get_item(pamh, PAM_CONV, &ptr);
964: if (pam_err != PAM_SUCCESS)
965: return (PAM_SYSTEM_ERR);
966: conv = ptr;
967: msg.msg_style = PAM_PROMPT_ECHO_OFF;
968: msg.msg = password_prompt;
969: msgp = &msg;
970: #endif
971: password = NULL;
972: for (retry = 0; retry < 3; ++retry) {
973: #ifdef _OPENPAM
974: pam_err = pam_get_authtok(pamh, PAM_AUTHTOK,
975: (const char **)&password, NULL);
976: #else
977: resp = NULL;
978: pam_err = (*conv->conv)(1, &msgp, &resp, conv->appdata_ptr);
979: if (resp != NULL) {
980: if (pam_err == PAM_SUCCESS)
981: password = resp->resp;
982: else
983: free(resp->resp);
984: free(resp);
985: }
986: #endif
987: if (pam_err == PAM_SUCCESS)
988: break;
989: }
990: if (pam_err == PAM_CONV_ERR)
991: return (pam_err);
992: if (pam_err != PAM_SUCCESS)
993: return (PAM_AUTH_ERR);
994:
995: /* compare passwords */
996: if ((!pwd->pw_passwd[0] && (flags & PAM_DISALLOW_NULL_AUTHTOK)) ||
997: (crypt_password = crypt(password, pwd->pw_passwd)) == NULL ||
998: strcmp(crypt_password, pwd->pw_passwd) != 0)
999: pam_err = PAM_AUTH_ERR;
1000: else
1001: pam_err = PAM_SUCCESS;
1002: #ifndef _OPENPAM
1003: free(password);
1004: #endif
1005: return (pam_err);
1006: }
1007:
1008: PAM_EXTERN int
1009: pam_sm_setcred(pam_handle_t *pamh, int flags,
1010: int argc, const char *argv[])
1011: {
1012:
1013: return (PAM_SUCCESS);
1014: }
1015:
1016: PAM_EXTERN int
1017: pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
1018: int argc, const char *argv[])
1019: {
1020:
1021: return (PAM_SUCCESS);
1022: }
1023:
1024: PAM_EXTERN int
1025: pam_sm_open_session(pam_handle_t *pamh, int flags,
1026: int argc, const char *argv[])
1027: {
1028:
1029: return (PAM_SUCCESS);
1030: }
1031:
1032: PAM_EXTERN int
1033: pam_sm_close_session(pam_handle_t *pamh, int flags,
1034: int argc, const char *argv[])
1035: {
1036:
1037: return (PAM_SUCCESS);
1038: }
1039:
1040: PAM_EXTERN int
1041: pam_sm_chauthtok(pam_handle_t *pamh, int flags,
1042: int argc, const char *argv[])
1043: {
1044:
1045: return (PAM_SERVICE_ERR);
1046: }
1047:
1048: #ifdef PAM_MODULE_ENTRY
1049: PAM_MODULE_ENTRY("pam_unix");
1050: #endif
1051:
1052: ## Sample PAM Conversation Function
1053:
1054: The conversation function presented below is a greatly simplified version of
1055: OpenPAM's
1056: [openpam\_ttyconv(3)](http://netbsd.gw.com/cgi-bin/man-cgi?openpam_ttyconv+3+NetBSD-5.0.1+i386).
1057: It is fully functional, and should give the reader a good idea of how a
1058: conversation function should behave, but it is far too simple for real-world
1059: use. Even if you're not using OpenPAM, feel free to download the source code and
1060: adapt
1061: [openpam\_ttyconv(3)](http://netbsd.gw.com/cgi-bin/man-cgi?openpam_ttyconv+3+NetBSD-5.0.1+i386)
1062: to your uses; we believe it to be as robust as a tty-oriented conversation
1063: function can reasonably get.
1064:
1065: #include <stdio.h>
1066: #include <stdlib.h>
1067: #include <string.h>
1068: #include <unistd.h>
1069:
1070: #include <security/pam_appl.h>
1071:
1072: int
1073: converse(int n, const struct pam_message **msg,
1074: struct pam_response **resp, void *data)
1075: {
1076: struct pam_response *aresp;
1077: char buf[PAM_MAX_RESP_SIZE];
1078: int i;
1079:
1080: data = data;
1081: if (n <= 0 || n > PAM_MAX_NUM_MSG)
1082: return (PAM_CONV_ERR);
1083: if ((aresp = calloc(n, sizeof *aresp)) == NULL)
1084: return (PAM_BUF_ERR);
1085: for (i = 0; i < n; ++i) {
1086: aresp[i].resp_retcode = 0;
1087: aresp[i].resp = NULL;
1088: switch (msg[i]->msg_style) {
1089: case PAM_PROMPT_ECHO_OFF:
1090: aresp[i].resp = strdup(getpass(msg[i]->msg));
1091: if (aresp[i].resp == NULL)
1092: goto fail;
1093: break;
1094: case PAM_PROMPT_ECHO_ON:
1095: fputs(msg[i]->msg, stderr);
1096: if (fgets(buf, sizeof buf, stdin) == NULL)
1097: goto fail;
1098: aresp[i].resp = strdup(buf);
1099: if (aresp[i].resp == NULL)
1100: goto fail;
1101: break;
1102: case PAM_ERROR_MSG:
1103: fputs(msg[i]->msg, stderr);
1104: if (strlen(msg[i]->msg) > 0 &&
1105: msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
1106: fputc('\n', stderr);
1107: break;
1108: case PAM_TEXT_INFO:
1109: fputs(msg[i]->msg, stdout);
1110: if (strlen(msg[i]->msg) > 0 &&
1111: msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
1112: fputc('\n', stdout);
1113: break;
1114: default:
1115: goto fail;
1116: }
1117: }
1118: *resp = aresp;
1119: return (PAM_SUCCESS);
1120: fail:
1121: for (i = 0; i < n; ++i) {
1122: if (aresp[i].resp != NULL) {
1123: memset(aresp[i].resp, 0, strlen(aresp[i].resp));
1124: free(aresp[i].resp);
1125: }
1126: }
1127: memset(aresp, 0, n * sizeof *aresp);
1128: *resp = NULL;
1129: return (PAM_CONV_ERR);
1130: }
1131:
1132: ## Further Reading
1133:
1134: ### Papers
1135:
1136: * *[sun-pam]: [Making Login Services Independent of Authentication Technologies](http://www.sun.com/software/solaris/pam/pam.external.pdf)*. Vipin Samar and Charlie Lai. Sun Microsystems.
1137: * *[opengroup-singlesignon]: [X/Open Single Sign-on Preliminary Specification](http://www.opengroup.org/pubs/catalog/p702.htm)*. The Open Group. 1-85912-144-6. June 1997.
1138: * *[kernelorg-pamdraft]: [Pluggable Authentication Modules](http://www.kernel.org/pub/linux/libs/pam/pre/doc/current-draft.txt)*. Andrew G. Morgan. October 6, 1999.
1139:
1140: ### User Manuals
1141:
1142: * *[sun-pamadmin]: [PAM Administration](http://www.sun.com/software/solaris/pam/pam.admin.pdf)*. Sun Microsystems.
1143:
1144: ### Related Web pages
1145:
1146: * *[openpam-website]: [OpenPAM homepage](http://openpam.sourceforge.net/)*. Dag-Erling Smørgrav. ThinkSec AS.
1147: * *[linuxpam-website]: [Linux-PAM homepage](http://www.kernel.org/pub/linux/libs/pam/)*. Andrew G. Morgan.
1148: * *[solarispam-website]: [Solaris PAM homepage](http://www.sun.com/software/solaris/pam/)*. Sun Microsystems.
1149:
CVSweb for NetBSD wikisrc <wikimaster@NetBSD.org> software: FreeBSD-CVSweb