Linux DevCenter    
 Published on Linux DevCenter (http://www.linuxdevcenter.com/)
 See this if you're having trouble printing code examples


Writing PAM-Capable Applications, Part One

by Jennifer Vesperman
04/04/2002

PAM stands for Pluggable Authentication Modules, a system for separating authentication mechanisms from an application.

If an application is PAM-enabled, the system administrator is responsible for determining the authentication methods used, and PAM is responsible for performing the authentication. This lets the application developer concentrate on writing the main application, and it ensures that the application isn't made out-of-date solely because of an outdated authentication schema.

See the articles Introduction to PAM and PAM Modules for a longer introduction to PAM and how to use it.

This is the first part of a two-part series on writing PAM-capable applications. This part provides the background knowledge and some of the supporting functions necessary for a developer to effectively use the PAM library. The second part will introduce the PAM library functions.

Applications and modules

PAM is an interface, rather than an actual service. It provides the glue to hook applications with modules, and it provides a mechanism for configuration. It does not do any of the authentication itself, nor (from the module developer's point of view) does it do any of the application's work.

Because of this, an application developer should consider calling every module type, and think carefully about what aspects of PAM she wishes to leave out. Similarly, a module developer must provide an interface for everything an application may request, even if the functions she provides consist of nothing but return PAM_SUCCESS.

Module types

PAM is structured into four module types, each handling one aspect of authentication and account management. An application is not required to call all four module types, but I recommend that you do.

The four module types are:

auth
User authentication, limited to "Is the user who she says she is?"

account
User account management, handling issues such as whether the account is valid, whether the user is prevented from logging in after hours, whether the machine is set to no-login, and whether the user's password has expired.

session
Handles opening and closing an authenticated session. Some forms of authentication need to be called when a session opens and closes.

password
Changes the authentication token, whatever that may be.

Packages and files

These articles describe a C++ application, because most large-scale application development is in that language, and its differences from the C language, while distinct, are not extreme. You can develop a Linux-PAM-capable application in any language, provided you can call the necessary C functions.

To start with, you'll need the development files: in a Debian system, apt-get install libpam0g-dev. This installs the relevant source files in the right places for g++ to find them.

You will need to #include <security/pam_appl.h>, and if you use the misc functions, #include <security/pam_misc.h>.

When compiling, you'll need to link against the libpam files. My makefile includes g++ -oapp_name -lpam -lpam_misc sourcefile.

The conversation structure

The authentication module often needs to communicate with the user. However, the author of the authentication module has no way of telling whether your application is a GUI, an SMTP session, or a command line console.

The application calls a PAM function for each type of service it requires. This function checks the PAM configuration for the application and then calls each module in turn. If the module needs to communicate with the user, to get a password or some other information, the module calls an application-provided function called the conversation function.

The application must provide a structure, which consists of the pointer to the conversation function and a pointer to any data the application wants to use within the conversation function. The module adds that data pointer as a parameter to the function when it calls the function.

I recommend that you use misc_conv(), a conversation module provided in pam_misc.h for text-based programs. To do this, #include <security/pam_misc> and provide:

static struct pam_conv conv = {
  misc_conv,
  NULL
};

Writing your own conversation function

The conversation structure itself is actually:
struct pam_conv {
    int (*conv)(int num_msg,
        const struct pam_message **msg,
        struct pam_response **resp,
        void *appdata_ptr);
    void *appdata_ptr;
};

When a module calls this function, the appdata_ptr is passed as the parameter of the same name.

The msg parameter is an array of pointers to message structures (pam_message). The message structures are provided by the module and contain the messages the module expects the application to present to the user. The num_msg parameter contains the length of the array.

The module provides a pointer in the resp parameter, which it expects the function to fill with an array of pam_response structures. The application should provide the response to each message (if any) in the corresponding element of the response array. This array will be freed with the free() function and should be allocated with one of the malloc() family of functions.

In C++ this looks like:

reply = 
  static_cast<pam_response*>(std::calloc(num_msg, sizeof(pam_response)));
 if(!reply)
	 return PAM_SYSTEM_ERROR;
struct pam_message {
    int msg_style;
    const char *msg;
};

struct pam_response {
    char *resp;
    int resp_retcode;
};


/*
 * DEMONSTRATION CODE ONLY
 * This WILL NOT work as-is.
 */

// The 'extern C' tells the compiler to generate the function 
// so it can be called by C code.
extern "C" int Conversation(int num_msg, const pam_message **msg,
           pam_response **resp, void *appdata_ptr)
{
#if WE_USE_APPDATA
  myApplicationData *data = 
    static_cast<myApplicationData*>(appdata_ptr);

  if (!data)
	  return PAM_SYSTEM_ERR;
#endif

  if (num_msg <= 0)
	  return PAM_CONV_ERR;

  pam_response* reply = 
    static_cast<pam_response*>(std::calloc(num_msg, sizeof(pam_response)));
  if (!reply)
	  return PAM_SYSTEM_ERR;

  for (int replies = 0; replies < num_msg; replies++) {

    switch (msg[replies]->msg_style) {
      case PAM_PROMPT_ECHO_OFF: { // Don't echo sensitive data
				// Turn echoing off (code not included)

				// output the message from the PAM module
        if(msg[replies]->msg)
					std::cout << msg[replies]->msg;

				// Get some sort of input from the user
				std::string data=get_input();
        reply[replies].resp_retcode = PAM_SUCCESS;
        reply[replies].resp = std::strdup(data.c_str());
        break; }

      case PAM_PROMPT_ECHO_ON: { // Okay to echo
				// Make sure we're echoing output
                                // (code not included)
				// Do the same as for ECHO_OFF
				}

      default: // Say, what?
        std::free(reply);	// Don't leak
        return PAM_CONV_ERR;
    }
  }

  *resp = reply;

  return PAM_SUCCESS;
}

The memory for the response structure must be dynamically allocated and is freed within the module. A note for C++ users: Since PAM is a C-based library, malloc() must be used for the allocation, rather than new.

There is currently only one response code in the response structure: 0, and it doesn't yet mean anything. The response code is in the structure for future expansion of the Linux-PAM system. There are, however, four possible message styles:

Note that PAM_MAX_MSG_SIZE is NOT currently enforced by Linux-PAM and should be enforced by applications (for security).

Handling the environment

Linux-PAM comes with a separate environment associated with the current PAM handle. The environment starts out empty.

Related Reading

Linux in a Nutshell
By Ellen Siever, Stephen Spainhour, Jessica P. Hekman, Stephen Figgins

extern int pam_putenv(pam_handle_t *pamh, const char *name_value);
Attempts to set, reset, or delete the named environment variable. The name_value argument is a NULL terminated (C style) string. Valid formats are:
name=value
Sets "name" to "value"

name=
Sets "name" to the empty string

name
Deletes "name"



extern const char *pam_getenv(pam_handle_t *pamh, const char *name);
Returns the value of the named Linux-PAM environment variable, or NULL if there is a failure.

extern const char * const *pam_getenvlist(pam_handle_t *pamh);
Returns a pointer to a read-only list of the current Linux-PAM environment. If you want a writable copy of the list, use pam_misc_copy_env().

The remaining three functions are found in pam_misc.h:

extern int pam_misc_paste_env(pam_handle_t *pamh, const char * const * user_env);
Copies the parameter (a list of environment pointers) to the Linux-PAM environment.

extern char **pam_misc_copy_env(pam_handle_t *pamh);
Returns a pointer to a list of environment variables that are a copy of the Linux-PAM environment.

extern char **pam_misc_drop_env(char **env);
Liberates the memory used by pam_misc_copy_env().

Setting PAM items

PAM stores eight items, available to be set or retrieved by both application and module.

About the application:

PAM_SERVICE
The PAM name of the application, not necessarily the name the user sees. For security, hard-code this into the application or set it in a sysadmin-only configuration file. This is used in pam_start().

PAM_CONV
The conversation structure.

PAM_FAIL_DELAY
Used only if the default fail delay function won't work for your application. Leave it alone in most cases.

About the user:

PAM_USER
The username to be authenticated against.

PAM_USER_PROMPT
The prompt the module should use if asking for a username.

PAM_RUSER
The user requesting authentication, usually the username of the user calling the application.

About the machine:

PAM_RHOST
The hostname of the machine requesting authentication.

PAM_TTY
The terminal name (console-based apps) or $DISPLAY (GUI-based apps). You can retrieve the terminal name with ttyname().

Set these with pam_set_item() and retrieve them with pam_get_item(). Use the PAM handle you received from pam_start(). item_type is one of the codes in this section. item is a pointer to a string. The functions return PAM_SUCCESS if they succeed and other PAM codes if they fail.

In C++, you may need to call them with code like retval = pam_get_item(pamh, PAM_SERVICE, &static_cast<const void*> (item));.

extern int pam_set_item(pam_handle_t *pamh, int item_type,
                        const void *item);

extern int pam_get_item(const pam_handle_t *pamh, int item_type,
                        const void **item);

pam_get_item() returns a pointer to the actual data and this data should NOT be freed or overwritten. Use pam_set_item() if you want to change an item's contents.

The username

The module calls the function pam_get_user() to get the username. If you know who you want the user to authenticate as, you can set it in pam_start() or using pam_set_item(). If you don't set it, pam_get_user() will use the conversation function and the PAM_USER_PROMPT to request the username.

pam_fail_delay

If you want to limit how frequently people can try to authenticate, set a delay (in microseconds) using this function. This can hinder brute force or timed attacks.

If the fail delay is set, failed authentication in pam_authenticate will cause a delay in returning control to the application. The exact display is randomly chosen, based on the longest value passed to pam_fail_delay.

Fail delay is not guaranteed to be available, and a call to it should be bracketed with #ifdefs.

#ifdef PAM_FAIL_DELAY
	extern int pam_fail_delay(pam_handle_t *pamh, unsigned int micro_sec);
#endif

In some circumstances, the default function is not appropriate. The information to write a fail delay function is in the PAM Application Developer's Guide.

Final words

The next part of this article will describe the PAM functions that perform the actual authentication, account management, session management, and password changing.

Further reading

Jennifer Vesperman is the author of Essential CVS. She writes for the O'Reilly Network, the Linux Documentation Project, and occasionally Linux.Com.


Return to the Linux DevCenter.

Copyright © 2009 O'Reilly Media, Inc.