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


Writing PAM Modules, Part Two

by Jennifer Vesperman
05/23/2002

PAM stands for Pluggable Authentication Modules, and is a way of providing application independence for authentication. A PAM-enabled application calls a stack of PAM modules to run authentication, open and close sessions, and check account validity.

This is Part Two of a three-part article on writing PAM modules. Part One discussed the background information needed to write modules, and Part Three discusses the critical functions the module must supply. This part discusses the support code a module author will need to use.

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 module 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_modules.h>.

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

In case the module is linked statically, the four module types need to be #defined before security/pam_modules.h is #included.

#define PAM_SM_ACCOUNT
#define PAM_SM_AUTH
#define PAM_SM_PASSWORD
#define PAM_SM_SESSION

Setting and Getting PAM Items

Most of the information a module needs can be found or passed through PAM items. These items are related to a specific PAM session, which is identified with a PAM handle. The PAM handle is passed to each of the module's major functions as a parameter.

Use the pam_set_item() and pam_get_item() functions to store or retrieve PAM items.

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

The available PAM item types can be arranged into groups.

About the application:

PAM_SERVICE
The PAM name of the application, not necessarily the name the user sees.

PAM_CONV
The conversation structure. (See the next section.)

PAM_FAIL_DELAY
The pointer to the PAM_FAIL_DELAY function. Some applications replace the default PAM version.

About the user:

PAM_USER
The username to be authenticated against. It is usually safer to use pam_get_user() rather than asking for this directly.

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().

About authentication:

PAM_AUTHTOK
The authentication token. This should be ignored by anything other than authentication and password changing modules, and only used in the functions pam_sm_authenticate() and pam_sm_chauthtok().

PAM_OLDAUTHTOK
The previous authentication token. This should only be used by pam_sm_chauthtok() in the password module type.

Other than PAM_SUCCESS, the PAM item functions can return PAM_SYSTEM_ERR, PAM_PERM_DENIED, PAM_BUF_ERR or PAM_BAD_ITEM.

The Conversation Function

An application may display a message in any number of formats. A GUI displays messages very differently from a command line interface, and both are different from a secure HTTP session or an FTP session.

PAM insulates the module developer from these issues, by requiring the application to provide a conversation function. This function takes messages from the module, presents them to the user, and provides the module with responses.

A call to pam_get_item(pamhandle, PAM_CONV, &item) produces a pointer to a conversation structure. The conversation structure contains the conversation function and a pointer that the application can use to transfer data.

struct pam_conv {
    int (*conv)(int num_msg,
        const struct pam_message **msg,
        struct pam_response **resp,
        void *appdata_ptr);
    void *appdata_ptr;
};

The module uses this function to pass messages to the user. The module may pass messages one at a time or as a cluster of several messages at once. The application will determine whether the messages are presented to the user as a group, or individually.

The module must create an array of pointers to pam_message structures to contain the message strings it needs the application to display. This array is the msg parameter, and each structure contains both the message and the message format. The num_msg parameter is the length of the message array.

struct pam_message {
    int msg_style;
    const char *msg;
};

Available message formats are:

PAM_ERROR_MSG
Display an error message.

PAM_PROMPT_ECHO_OFF
Receive a string but do not echo input.

PAM_PROMPT_ECHO_ON
Receive a string and echo input.
PAM_TEXT_INFO
Display a message.

The application will provide an array of pam_response structures on return. The module must provide a pointer, which the application can fill with a pointer to the array. Yes, this is a complicated piece of indirection. The module provides its pointer as the resp parameter.

Learning the Korn Shell

Related Reading

Learning the Korn Shell
By Bill Rosenblatt, Arnold Robbins

The module should fill this pointer with a NULL or other testable value before calling the conversation function, so it can verify that the application has changed it.

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

The resp_retcode in the response structure is currently meaningless, and is usually filled with a 0.

Note that the appdata_ptr is included as the last parameter of the function -- this is important. The application uses the appdata_ptr to transfer data it may need within the function, so be sure to send it that pointer.

Aside from PAM_SUCCESS, the conversation function might return PAM_CONV_ERR.

Getting the Username

Getting a username can be involved -- first checking whether it's a PAM item, then calling the conversation function, and identifying the appropriate prompt to ask for the username. Fortunately PAM is willing to do the work for you, in the pam_get_user() function.

Use pam_get_user rather than doing the work yourself. It's easier, cleaner to read, and if the process changes, you don't have to do a thing!

extern int pam_get_user(pam_handle_t *pamh, const char **user, const char *prompt);

Do not free the user data memory. A second call to pam_get_user() or a call to pam_end() will do that for you.

The meaning of the return value PAM_SUCCESS is obvious. Other return values are PAM_CONV_ERR which means no username could be obtained, and PAM_CONV_AGAIN.

If you receive PAM_CONV_AGAIN, return control to the application with PAM_INCOMPLETE, and, when next invoked with that PAM handle, return to the failed pam_get_user() and pick up where you left off.

Setting and Getting Data

The module author cannot predict how the module will be loaded, so you should avoid static variables. The functions to set and get data provide a way to associate data with a PAM handle. The data items set using this function are available to other modules, but not to the application.

The cleanup parameter is a function you need to provide to do a proper cleanup of the data item. If no special cleanup is required by this item, it may be set to NULL.

The error_status parameter may be logically ORed with PAM_DATA_REPLACE to signal a second call to set the same item, or PAM_DATA_SILENT to cause the cleanup function to avoid logging or sending messages to the user.

If an entry is present but has the value NULL, pam_get_data() will return PAM_NO_MODULE_DATA.

extern int pam_set_data(pam_handle_t *pamh, const char *module_data_name, void *data, void (*cleanup)(pam_handle_t *pamh, void *data, int error_status) );

extern int pam_get_data(const pam_handle_t *pamh, const char *module_data_name, const void **data);

Managing the Environment

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

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.

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 NUL terminated (C style) string. Valid formats are:
name=value
Sets 'name' to 'value'
name=
Sets 'name' to the empty string
name
Deletes 'name'

Memory Security

Any memory that your module does not personally free must be allocated with the std::malloc() family of functions from the Standard C library. new and delete can be used, but only for memory you will be releasing yourself.

Final Words

This is Part Two of a three-part article on writing PAM modules. Part One discusses the underlying theory of PAM, and what behaviors are required of a module. Part Three discusses the functions a module is required to provide, security issues, and response codes.

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.