Your Ad Here
                  - 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:

pamstart( const char *servicename, const char username, const struct pam_conv *conv, pam_handle_t *pamh_p );

pamend( pamhandlet *pamh, int exitstatus );

pamauthenticate( pamhandle_t *pamh, int flags );

The pamstart() and pamend() functions begin and end a PAM session. The arguments to pam_start() are as follows:

The pam_conv structure looks like this:

struct pamconv { int (*conv)(int nummsg, const struct pammessage **msg, struct pamresponse **resp, void *appdataptr); void *appdataptr; }

*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 pamend() function's arguments consist of the same pamhandlet* that was filled in by pamstart(), and an exit status. The exit status is normally PAMSUCCESS, but can be different in the event of an unsuccessful PAM session. pamend() will deallocate the memory associated with the pamhandlet*, and any attempt to re-use the handle will likely result in a seg fault.

The pamauthenticate() function again consists of the pamhandlet* filled in by pamstart(), 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/pamunix.so.1 tryfirst_pass

The first field is the service name. This is the service referred to in the first argument to pamstart(). If the service requested by pamstart() 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 PAMSUCCESS for that action. For example, if a module is not designed to support account management, the pamsmacctmgmt() function should simply return PAM_SUCCESS.

The declaration for pamsmauthenticate() is as follows:

extern int pamsmauthenticate( pamhandlet 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 pamsmauthenticate() for the pam_unix module might look like this:

include

include <...>

extern int pamsmauthenticate( pamhandlet 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 pamsmauthenticate(). 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 pamget*() 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 pamstart() and pamend() 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 suconv(int, const struct pammessage *, struct pam_response *, void *);

static struct pamconv pamconv = { su_conv, NULL };

int main( int argc, char **argv ) { pamhandlet *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 suconv(int nummsg, const struct pammessage **msg, struct pamresponse **resp, void *appdata) { struct pammessage *m = *msg; struct pammessage *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 suconv() function is the conversation function - it allows the module to "converse" with the user. Each pammessage struct has a message style, which indicates what type of data the module wants. The PAMPROMPTECHOON and PAMPROMPTECHOOFF cases indicate that the module needs more information from the user. The prompt used will be supplied by the module. In the case of PAMPROMPTECHOOFF, 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 getguipasswd() (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 pamsmauthenticate( pamhandlet 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 getsecondroot_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|-------------------------------------------------------------------------|