8.12 Authenticating with PAM

8.12.1 Problem

You need to perform authentication in your application, but you do not want to tie your application to any specific authentication system. Instead, you want to allow the system administrator to configure an authentication system that is appropriate for the environment in which the application will run.

8.12.2 Solution

Use Pluggable Authentication Modules (PAM), which provides an API that is independent of the underlying authentication system. PAM allows the system administrator to configure the authentication system or systems to use, and it supports a wide variety of existing systems, such as traditional Unix password-based authentication, Kerberos, Radius, and many others.

8.12.3 Discussion

We do not discuss building your own PAM modules in this book, but there is a recipe on that topic on the book's web site.

Most modern Unix systems provide support for PAM and even use it for system-wide authentication (for example, for interactive user login for shell access). Many popular and widely deployed services that use authentication are also capable of using PAM.

Every application that makes use of PAM uses a service name, such as "login" or "ftpd". PAM uses the service name along with a configuration file (often /etc/pam.conf) or files (one for each service, named after the service, and usually located in /etc/pam.d). PAM uses configuration information gleaned from the appropriate configuration file to determine which modules to use, how to treat successes and failures, and other miscellaneous information.

Modules are implemented as shared libraries that are dynamically loaded into your application as required. Each module is expected to export several standard functions in order to interact with the PAM infrastructure. Implementation of PAM modules is outside the scope of this book, but our web site contains more information on this topic.

PAM and its modules handle the drudgery of obtaining passwords from users if required, exchanging keys, or doing whatever must be done to authenticate. All that you need to do in your code is make the proper sequence of calls with the necessary information to PAM, and the details of authentication are handled for you, allowing you to concentrate on the rest of your application.

Unfortunately, the PAM API is somewhat clumsy, and the steps necessary for performing basic authentication with PAM are not necessarily as straightforward as they could be. The functions presented in this recipe, spc_pam_login( ) and spc_pam_logout( ), work together to perform the necessary steps properly.

To use PAM in your own code, you will need to include the header files security/pam_appl.h and security/pam_misc.h in your program, and link against the PAM library, usually by specifying -lpam on the linker command line.

To authenticate a user, call spc_pam_login( ), which has the following signature:

pam_handle_t *spc_pam_login(const char *service, const char *user, int **rc);

This function has the following arguments:

service

Name of the service to use. PAM uses the service name to find the appropriate module configuration information in its configuration file or files. You will typically want to use a service name that does not conflict with anything else, though if you are writing an FTP server, for example, you will want to use "ftpd" as the service.

user

Name of the user to authenticate.

rc

Pointer to an integer that will receive the PAM error code if an error occurs.

If the user is authenticated successfully, spc_pam_login( ) will return a non-NULL pointer to a pam_handle_t context object. Otherwise, it will return NULL, and you should consult the rc argument for the error code.

#include <security/pam_appl.h>
#include <security/pam_misc.h>
   
static struct pam_conv spc_pam_conv = { misc_conv, 0 };
   
pam_handle_t *spc_pam_login(const char *service, const char *user, int *rc) {
  pam_handle_t *hndl;
   
  if (!service || !user || !rc) {
    if (rc) *rc = PAM_ABORT;
    return 0;
  }
  if ((*rc = pam_start(service, user, &spc_pam_conv, &hndl)) != PAM_SUCCESS) {
    pam_end(hndl, *rc);
    return 0;
  }
   
  if ((*rc = pam_authenticate(hndl, PAM_DISALLOW_NULL_AUTHTOK)) != PAM_SUCCESS) {
    pam_end(hndl, *rc);
    return 0;
  }
   
  *rc = pam_acct_mgmt(hndl, 0);
  if (*rc =  = PAM_NEW_AUTHTOK_REQD) {
    pam_chauthtok(hndl, PAM_CHANGE_EXPIRED_AUTHTOK);
    *rc = pam_acct_mgmt(hndl, 0);
  }
  if (*rc != PAM_SUCCESS) {
    pam_end(hndl, *rc);
    return 0;
  }
   
  if ((*rc = pam_setcred(hndl, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
    pam_end(hndl, *rc);
    return 0;
  }
   
  if ((*rc = pam_open_session(hndl, 0)) != PAM_SUCCESS) {
    pam_end(hndl, *rc);
    return 0;
  }
   
  /* no need to set *rc to PAM_SUCCESS; we wouldn't be here if it weren't */
  return hndl;
}

After the authentication is successful, you should maintain the pam_handle_t object returned by spc_pam_login( ) until the user logs out from your application, at which point you should call spc_pam_logout( ) to allow PAM to perform anything it needs to do to log the user out.

void spc_pam_logout(pam_handle_t *hndl) {
  if (!hndl) return;
  pam_close_session(hndl, 0);
  pam_end(hndl, PAM_SUCCESS);
}

8.12.4 See Also

  • "Pluggable Authentication Modules" by A. G. Morgan: http://www.kernel.org/pub/linux/libs/pam/pre/doc/current-draft.txt

  • OpenPAM home page: http://openpam.sourceforge.net

  • Linux PAM home page: http://www.kernel.org/pub/linux/libs/pam/

  • Solaris PAM home page: http://wwws.sun.com/software/solaris/pam/