431 lines
16 KiB
Plaintext
431 lines
16 KiB
Plaintext
- P H R A C K M A G A Z I N E -
|
|
|
|
Volume 0xa Issue 0x38
|
|
05.01.2000
|
|
0x0d[0x10]
|
|
|
|
|---------------------------- INTRODUCTION TO PAM ----------------------------|
|
|
|-----------------------------------------------------------------------------|
|
|
|------------------------------- Bryan Ericson -------------------------------|
|
|
|
|
|
|
----| INTRODUCTION
|
|
|
|
The Pluggable Authentication Module (PAM) system is a means by which programs
|
|
can perform services relating to user authentication and account maintenance.
|
|
The authentication part is usually done through a challenge-response
|
|
interaction. Using PAM, an administrator can customize the methods used
|
|
by authenticating programs without recompilation of those programs.
|
|
|
|
The PAM system is comprised of four parts. The first part, libpam, is the
|
|
library which implements the PAM API. The second part is the PAM
|
|
configuration file, /etc/pam.conf. The third consists of a suite of
|
|
dynamically loadable binary objects, often called the service modules, which
|
|
handle the actual work of authentication. The final part is comprised of
|
|
the system commands which use (or should use) the PAM API, such as login, su,
|
|
ftp, telnet, etc...
|
|
|
|
|
|
----| LIBPAM
|
|
|
|
The authentication routines of the PAM API consist of three primary
|
|
functions:
|
|
|
|
pam_start( const char *service_name, const char *username,
|
|
const struct pam_conv *conv, pam_handle_t **pamh_p );
|
|
|
|
pam_end( pam_handle_t *pamh, int exit_status );
|
|
|
|
pam_authenticate( pam_handle_t *pamh, int flags );
|
|
|
|
The pam_start() and pam_end() functions begin and end a PAM session. The
|
|
arguments to pam_start() are as follows:
|
|
|
|
+ service_name: a string specifying a particular service as defined
|
|
in the pam.conf file (see below)
|
|
|
|
+ username: the login name of the user to be authenticated
|
|
|
|
+ conv: a pointer to a pam_conv structure (more on this in a
|
|
minute)
|
|
|
|
+ pamh_p: a double pointer to a pam_handle_t structure. The PAM
|
|
framework will allocate and deallocate the memory for the
|
|
structure, and an application should never access it directly. It
|
|
is basically used by the PAM framework to deal with multiple
|
|
concurrent PAM sessions.
|
|
|
|
The pam_conv structure looks like this:
|
|
|
|
struct pam_conv {
|
|
int (*conv)(int num_msg, const struct pam_message **msg,
|
|
struct pam_response **resp, void *appdata_ptr);
|
|
void *appdata_ptr;
|
|
}
|
|
|
|
*conv is a pointer to a function in the application known as the PAM
|
|
conversation function. It will be discussed below. The appdata_ptr points to
|
|
application-specific data, and is not often used.
|
|
|
|
The pam_end() function's arguments consist of the same pam_handle_t* that was
|
|
filled in by pam_start(), and an exit status. The exit status is normally
|
|
PAM_SUCCESS, but can be different in the event of an unsuccessful PAM session.
|
|
pam_end() will deallocate the memory associated with the pam_handle_t*, and
|
|
any attempt to re-use the handle will likely result in a seg fault.
|
|
|
|
The pam_authenticate() function again consists of the pam_handle_t* filled
|
|
in by pam_start(), and optional flags that can be passed to the framework.
|
|
|
|
Some other functions in the PAM API available to applications are as follows
|
|
(consult your system's documentation for a complete description of its PAM
|
|
API):
|
|
|
|
+ pam_set_item() - write state information for PAM session
|
|
|
|
+ pam_get_item() - retrieve state information for PAM session
|
|
|
|
+ pam_acct_mgmt() - checks whether the current user's account is
|
|
valid
|
|
|
|
+ pam_open_session() - begin a new session
|
|
|
|
+ pam_close_session() - close current session
|
|
|
|
+ pam_setcred() - manage user credentials
|
|
|
|
+ pam_chauthtok() - change user's authentication token
|
|
|
|
+ pam_strerror() - returns an error string, similar to perror()
|
|
|
|
|
|
----| PAM.CONF
|
|
|
|
The PAM configuration file is usually located in /etc/pam.conf. It is divided
|
|
into four sections: authentication, account management, session management,
|
|
and password management. A typical line looks like this:
|
|
|
|
login auth required /usr/lib/security/pam_unix.so.1 try_first_pass
|
|
|
|
The first field is the service name. This is the service referred to in the
|
|
first argument to pam_start(). If the service requested by pam_start() is not
|
|
listed in pam.conf, the default service "other" will be used. Other service
|
|
names might be "su" and "rlogin". If the service name is specified more
|
|
than once, the modules are said to be "stacked", and the behavior of the
|
|
framework will be determined by the value of the third field, as discussed
|
|
below.
|
|
|
|
The second field denotes what action this particular service will perform.
|
|
The valid values are "auth" for authentication, "account" for account
|
|
management, "session" for session management, and "password" for password
|
|
management. Not all applications will need to access every action. For
|
|
example, su will need only to access the "auth" action, while "passwd" should
|
|
need only the "password" action.
|
|
|
|
The third field is known as the control field, and will require some
|
|
discussion. It indicates the behavior of the PAM framework if the user
|
|
should fail the authentication. Valid values for this field are "requisite",
|
|
"required", "sufficient", and "optional":
|
|
|
|
+ "requisite" means that if the user fails authentication for this
|
|
particular module, the framework will immediately return a
|
|
failure, and no other modules will be invoked.
|
|
|
|
+ "required" denotes that if a user fails authentication, the
|
|
framework will return a failure only after all other modules have
|
|
been invoked. This is done so that the user will not know for
|
|
which module authentication was denied. For a user to
|
|
successfully authenticate, all "required" modules have to return
|
|
success.
|
|
|
|
+ "optional" means that the user will be allowed access even if
|
|
authentication fails. In the event of failure, the next module on
|
|
the stack will be processed.
|
|
|
|
+ "sufficient" means that if a user passes this particular module,
|
|
the framework will immediately return success, even if subsequent
|
|
modules have "requisite" or "required" control values. Like
|
|
"optional", "sufficient" will allow access even if authentication
|
|
fails.
|
|
|
|
Note that if any module returns success, the user will succeed authentication
|
|
with the only exception being if the user previously failed to authenticate
|
|
with a "required" module.
|
|
|
|
The fourth field in pam.conf is the path to the authentication module. The
|
|
path can differ between systems. For example, the PAM modules are located
|
|
in /usr/lib in the Linux-PAM implementation, while Solaris maintains the
|
|
modules in /usr/lib/security.
|
|
|
|
The fifth field is a space-separated list of module-dependent options, which
|
|
are passed to the authentication module whenever it is invoked. Consult
|
|
the specific module's man page for details.
|
|
|
|
|
|
----| MODULES
|
|
|
|
Each PAM module is essentially a library which must export specified functions.
|
|
These functions are called by the PAM framework. The functions exported by
|
|
the library are:
|
|
|
|
+ pam_sm_authenticate()
|
|
|
|
+ pam_sm_setcred()
|
|
|
|
+ pam_sm_acct_mgmt()
|
|
|
|
+ pam_sm_open_session()
|
|
|
|
+ pam_sm_close_session()
|
|
|
|
+ pam_sm_chauthtok()
|
|
|
|
If an implementer decides not to support a particular action within a module,
|
|
the module should return PAM_SUCCESS for that action. For example, if a
|
|
module is not designed to support account management, the pam_sm_acct_mgmt()
|
|
function should simply return PAM_SUCCESS.
|
|
|
|
The declaration for pam_sm_authenticate() is as follows:
|
|
|
|
extern int pam_sm_authenticate( pam_handle_t *pamh, int flags,
|
|
int argc, char **argv);
|
|
|
|
where pamh is a pointer to a PAM handle which has been filled in by the
|
|
framework, flags is the set of flags passed to the framework by the
|
|
application's call to pam_authenticate(), and argc and argv are the number
|
|
and values of the optional arguments for this service in pam.conf.
|
|
|
|
A simple pam_sm_authenticate() for the pam_unix module might look like
|
|
this:
|
|
|
|
#include <security/pam_modules.h>
|
|
#include <...>
|
|
|
|
extern int
|
|
pam_sm_authenticate( pam_handle_t *pamh, int flgs, int c, char **v )
|
|
{
|
|
char *user;
|
|
char *passwd;
|
|
struct passwd *pwd;
|
|
int ret;
|
|
|
|
/* ignore flags and optional arguments */
|
|
|
|
if ( (ret = pam_get_user( ..., &user )) != PAM_SUCCESS )
|
|
return ret;
|
|
if ( (ret = pam_get_pass( ..., &passwd )) != PAM_SUCCESS )
|
|
return ret;
|
|
if ( (pwd = getpwnam(user)) != NULL ) {
|
|
if ( !strcmp(pwd->pw_passwd, crypt(passwd)) )
|
|
return PAM_SUCCESS;
|
|
else
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
Of course, this function is grossly oversimplified, but it demonstrates
|
|
the basic functionality of pam_sm_authenticate(). It retrieves the user's
|
|
login name and password from the framework, then retrieves the user's
|
|
encrypted password, and finally calls crypt() on the user's password and
|
|
compares the result with the encrypted system password. Success or
|
|
failure is determined on this comparison. The functions pam_get_*() are
|
|
calls to the framework, and may not have the same declaration between
|
|
implementations.
|
|
|
|
|
|
----| THE APPLICATION
|
|
|
|
A PAM application is fairly simple to implement. The portions that deal
|
|
with PAM must consist of a pam_start() and pam_end() pair, and a PAM
|
|
conversation function. Fortunately, the user-space PAM API is well-defined
|
|
and stable, and so the conversation function will pretty much be boilerplate
|
|
code (at least for a command-line application). A simple implementation
|
|
of su might look like this:
|
|
|
|
#include <security/pam_appl.h>
|
|
#include <...>
|
|
|
|
int su_conv(int, const struct pam_message **,
|
|
struct pam_response **, void *);
|
|
|
|
static struct pam_conv pam_conv = { su_conv, NULL };
|
|
|
|
int
|
|
main( int argc, char **argv )
|
|
{
|
|
pam_handle_t *pamh;
|
|
int ret;
|
|
struct passwd *pwd;
|
|
|
|
/* assume arguments are correct and argv[1] is the username */
|
|
|
|
ret = pam_start("su", argv[1], &pam_conv, &pamh);
|
|
if ( ret == PAM_SUCCESS )
|
|
ret = pam_authenticate(pamh, 0);
|
|
if ( ret == PAM_SUCCESS )
|
|
ret = pam_acct_mgmt(pamh, 0);
|
|
|
|
if ( ret == PAM_SUCCESS ) {
|
|
if ( (pwd = getpwnam(argv[1])) != NULL )
|
|
setuid(pwd->pw_uid);
|
|
else {
|
|
pam_end(pamh, PAM_AUTH_ERR);
|
|
exit(1);
|
|
}
|
|
}
|
|
pam_end(pamh, PAM_SUCCESS);
|
|
|
|
/* return 0 on success, !0 on failure */
|
|
return ( ret == PAM_SUCCESS ? 0 : 1 );
|
|
}
|
|
|
|
int
|
|
su_conv(int num_msg, const struct pam_message **msg,
|
|
struct pam_response **resp, void *appdata)
|
|
{
|
|
struct pam_message *m = *msg;
|
|
struct pam_message *r = *resp;
|
|
|
|
while ( num_msg-- )
|
|
{
|
|
switch(m->msg_style) {
|
|
|
|
case PAM_PROMPT_ECHO_ON:
|
|
fprintf(stdout, "%s", m->msg);
|
|
r->resp = (char *)malloc(PAM_MAX_RESP_SIZE);
|
|
fgets(r->resp, PAM_MAX_RESP_SIZE-1, stdin);
|
|
m++; r++;
|
|
break;
|
|
|
|
case PAM_PROMPT_ECHO_OFF:
|
|
r->resp = getpass(m->msg);
|
|
m++; r++;
|
|
break;
|
|
|
|
case PAM_ERROR_MSG:
|
|
fprintf(stderr, "%s\n", m->msg);
|
|
m++; r++;
|
|
break;
|
|
|
|
case PAM_TEXT_MSG:
|
|
fprintf(stdout, "%s\n", m->msg);
|
|
m++; r++;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
The su_conv() function is the conversation function - it allows the module
|
|
to "converse" with the user. Each pam_message struct has a message style,
|
|
which indicates what type of data the module wants. The PAM_PROMPT_ECHO_ON
|
|
and PAM_PROMPT_ECHO_OFF cases indicate that the module needs more information
|
|
from the user. The prompt used will be supplied by the module. In the case
|
|
of PAM_PROMPT_ECHO_OFF, the module usually wants a password. It is up to
|
|
the application to disable echoing of the characters. The *_MSG cases are
|
|
used for displaying messages on the user's terminal.
|
|
|
|
The beauty of the PAM conversation is that all of the character-based output
|
|
can be replaced with calls to different display systems without changing
|
|
the authentication module. For example, the getpass() could be replaced
|
|
with get_gui_passwd() (or whatever) if we want to implement a gui-based
|
|
su-like command.
|
|
|
|
Note that a real conversation function should be much more robust. Also,
|
|
the Linux-PAM implementation supplies the misc_conv() conversation
|
|
function for command-line interactions, which should be used if a standard
|
|
conversation function is all that is required. Finally, it is usually the
|
|
application's responsibility to free() the memory allocated for the
|
|
responses.
|
|
|
|
|
|
----| FUN WITH MODULES
|
|
|
|
Now that you have a familiarity with PAM, we can briefly discuss custom
|
|
authentication routines. For example, it is easy to modify our earlier
|
|
module so that, when authenticating the root user, a second password must
|
|
be typed:
|
|
|
|
extern int
|
|
pam_sm_authenticate( pam_handle_t *pamh, int flgs, int c, char **v )
|
|
{
|
|
char *user;
|
|
char *passwd;
|
|
struct passwd *pwd;
|
|
int ret;
|
|
|
|
/* ignore flags and optional arguments */
|
|
|
|
if ( (ret = pam_get_user( ..., &user )) != PAM_SUCCESS )
|
|
return ret;
|
|
if ( (ret = pam_get_pass( ..., &passwd )) != PAM_SUCCESS )
|
|
return ret;
|
|
if ( (pwd = getpwnam(user)) != NULL ) {
|
|
if ( !strcmp(pwd->pw_passwd, crypt(passwd)) )
|
|
ret = PAM_SUCCESS;
|
|
else
|
|
ret = PAM_AUTH_ERR;
|
|
}
|
|
|
|
if ( !strcmp(user, "root") ) {
|
|
pam_display_message("root user must enter secondary password");
|
|
if ( (ret = pam_get_pass( ..., &passwd )) != PAM_SUCCESS )
|
|
return ret;
|
|
if ( !strcmp(get_second_root_pwd(), crypt(passwd)) )
|
|
ret = PAM_SUCCESS;
|
|
else
|
|
ret = PAM_AUTH_ERR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
Here we assume there is a function get_second_root_pwd() which returns some
|
|
secret encrypted password. Of course, this example is a little silly, but
|
|
it demonstrates that we can be as free as we want to be when designing our
|
|
PAM modules. Also, because the modules live in user space, they have
|
|
access to all library functions. If you have some sort of biometric
|
|
scanner hooked up to your machine and a library function that can access
|
|
it, you could write a PAM module that does the following:
|
|
|
|
thumbprint_t *tp;
|
|
tp = scan_thumbprint();
|
|
/* or scan_retina() if you like James Bond */
|
|
if ( match_print_to_user(tp, user) )
|
|
return PAM_SUCCESS;
|
|
|
|
|
|
----| CONCLUSION
|
|
|
|
The point is, the PAM modules are not limited to calling crypt() or some
|
|
similar function on a user's password. You are limited only by what you
|
|
can think of.
|
|
|
|
|
|
----| REFERENCES
|
|
|
|
"Making Login Services Independent of Authentication Technologies".
|
|
Samar, Vipin and Charlie Lai.
|
|
http://www.sun.com/software/solaris/pam/pam.external.pdf
|
|
|
|
"The Linux-PAM System Administrator's Guide". Morgan, Andrew G.
|
|
http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam.html
|
|
|
|
"The Linux-PAM Module Writers' Guide". Morgan, Andrew G.
|
|
|
|
http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_modules.html
|
|
|
|
"The Linux-PAM Application Developers' Guide". Morgan, Andrew G.
|
|
|
|
http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_appl.html
|
|
|
|
Linux-PAM source code from FreeBSD 3.3 source packages.
|
|
http://www.FreeBSD.org/availability.html
|
|
|
|
|EOF|-------------------------------------------------------------------------|
|
|
|