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

