Cogs and Levers A blog full of technical stuff

Linux PAM

Introduction

PAM (Pluggable Authentication Modules) is a flexible mechanism for authenticating users, which has become a fundamental part of system security on Linux and other Unix-like operating systems. PAM abstracts a range of authentication tasks into modular plugins that can be tailored to suit the needs of system administrators, providing a way to develop programs that are independent of authentication scheme specifics. This modularity not only enhances security but also simplifies the management of user authentication.

In today’s guide, we will delve into the process of creating a program that interacts with the Linux passwd system using PAM. By the end of this article, you’ll have a clear understanding of how to harness PAM’s capabilities to authenticate users in your own applications, ensuring secure and efficient access control.

Architecture

PAM (Pluggable Authentication Modules) employ a unique architecture that separates the specific implementation of authentication methods from the application programming interface (API).

At the heart of this design is the concept of “conversations,” a mechanism that facilitates communication between the application and the authentication modules.

This conversation-based model allows PAM to present a uniform interface to the application, regardless of the underlying authentication process.

As a result, developers can integrate various authentication technologies into their applications without having to tailor their code to each method.

Instead, they rely on PAM to handle the specifics through configurable modules, each responsible for a different aspect of the authentication process.

This abstraction not only simplifies development but also enhances the flexibility and scalability of security systems, accommodating a wide range of authentication schemes with minimal changes to core application code.

Conversation

The conversation function is the callback that PAM uses for interactivity. In this code snippet, we’re using the password of "password" to stuff into the response to use. You could use the conversation opportunity to ask for input from the user.

int converse(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) {
    *resp = NULL;
    
    if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) {
        return PAM_CONV_ERR;
    }
    
    struct pam_response *response =
        malloc(num_msg * sizeof(struct pam_response));

    if (response == NULL) {
        return PAM_BUF_ERR;
    }
    
    for (int i = 0; i < num_msg; ++i) {
        if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
            // supply the password directly here
            const char *password = "password";
            response[i].resp = strdup(password);
        } else {
            // Handle other message styles if needed
            response[i].resp = NULL;
        }
        response[i].resp_retcode = 0;
    }
    
    *resp = response;
    return PAM_SUCCESS;
}

You can see that there’s a preference asked for here with the message style of PAM_PROMPT_ECHO_OFF. This would be an indicator back to the user interface to not echo the user’s keystrokes to the interface as they type.

Test Harness

We can now use this conversation function with the pam library:

int main() {
    const char *username = "username";
    const char *service = "login";

    pam_handle_t *pamh = NULL;
    struct pam_conv conv = { converse, NULL };

    int retval = pam_start(service, username, &conv, &pamh);

    if (retval != PAM_SUCCESS) {
        fprintf(stderr, "pam_start failed: %s\n", pam_strerror(pamh, retval));
        return 1;
    }

    retval = pam_authenticate(pamh, 0);

    if (retval != PAM_SUCCESS) {
        fprintf(stderr, "Authentication failed: %s\n", pam_strerror(pamh, retval));
        return 1;
    }

    printf("Authentication successful!\n");

    pam_end(pamh, retval);
    return 0;
}

The pam_start function begins the authentication conversation. The first parameter supplied is the service, and in this case it’s set to "login". PAM uses this value and looks for a configuration file named /etc/pam.d/login. This is pretty standard on any system. This particular service is for the “Shadow ‘login’ service.

The username variable should contain the name of a registered user.

Our conversation function converse is supplied to pam_start via the conv variable.

We then use pam_authenticate to preform the conversation, and pam_end will do any clean up for us.

A full example

#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <stdio.h>

int converse(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) {
    *resp = NULL;
    
    if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) {
        return PAM_CONV_ERR;
    }
    
    struct pam_response *response =
        malloc(num_msg * sizeof(struct pam_response));

    if (response == NULL) {
        return PAM_BUF_ERR;
    }
    
    for (int i = 0; i < num_msg; ++i) {
        if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
            // supply the password directly here
            const char *password = "password";
            response[i].resp = strdup(password);
        } else {
            // Handle other message styles if needed
            response[i].resp = NULL;
        }
        response[i].resp_retcode = 0;
    }
    
    *resp = response;
    return PAM_SUCCESS;
}

int main() {
    const char *username = "username";
    const char *service = "login";

    pam_handle_t *pamh = NULL;
    struct pam_conv conv = { converse, NULL };

    int retval = pam_start(service, username, &conv, &pamh);

    if (retval != PAM_SUCCESS) {
        fprintf(stderr, "pam_start failed: %s\n", pam_strerror(pamh, retval));
        return 1;
    }

    retval = pam_authenticate(pamh, 0);

    if (retval != PAM_SUCCESS) {
        fprintf(stderr, "Authentication failed: %s\n", pam_strerror(pamh, retval));
        return 1;
    }

    printf("Authentication successful!\n");

    pam_end(pamh, retval);
    return 0;
}

Building

In order to build this test program you need to link with pam and pam_misc.

gcc auth.c -o auth -lpam -lpam_misc