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