Go to main content

Developer's Guide to Oracle® Solaris 11.4 Security

Exit Print View

Updated: November 2020
 
 

Writing Applications That Use PAM Services

Depending on the application PAM services can be compiled as either 32-bit or 64-bit binaries. Since PAM modules are loaded as shared objects via dlopen, they must be provided in both 32-bit and 64-bit versions in order to support use by either form of application. For more information, see the dlopen(3C) man page.

For more information about how to install both the 32-bit and 64-bit versions of the module so the PAM framework can load the appropriate version of the application, see How to Add a PAM Module in Managing Authentication in Oracle Solaris 11.4.

Simple PAM Consumer Example

The following PAM consumer application is provided as an example. The example is a basic terminal-lock application that validates a user trying to access a terminal.

    The example goes through the following steps:

  1. Initialize the PAM session.

    PAM sessions are initiated by calling the pam_start(3PAM) function. A PAM consumer application must first establish a PAM session before calling any of the other PAM functions.

      The pam_start (3PAM) function takes the following arguments:

    • plock – Service name, that is, the name of the application. The service name is used by the PAM framework to determine which rules in the configuration file, /etc/pam.conf or, the /etc/pam.d, are applicable. The service name is generally used for logging and error-reporting.

    • pw->pw_name – The username is the name of the user that the PAM framework acts on.

    • &conv – The conversation function, conv, which provides a generic means for PAM to communicate with a user or application. Conversation functions are necessary because the PAM modules have no way of knowing how communication is to be conducted. Communication can be by means of GUIs, the command line, a smart card reader, or other devices. For more information, see Writing Conversation Functions.

    • &pamh – The PAM handle, pamh, which is an opaque handle that is used by the PAM framework to store information about the current operation. This handle is returned by a successful call to pam_start().


    Note -  An application that calls PAM interfaces must be sufficiently privileged to perform any needed operations such as authentication, password change, process credential manipulation, or audit state initialization. In this example, the application must be able to read /etc/shadow to verify the passwords for local users.
  2. Authenticate the user.

    The application calls pam_authenticate(3PAM) to authenticate the current user. Generally, the user is required to enter a password or other authentication token depending on the type of authentication service.

    The PAM framework invokes the modules configured for the service name plock which corresponds to the service module type of authentication, auth, in /etc/pam.d/plock. If there are no auth entries for the plock service in either /etc/pam.conf or /etc/pam.d/plock, then auth entries for the other service are searched in /etc/pam.conf and finally in the /etc/pam.d/other file.

  3. Check account validity.

    The example uses the pam_acct_mgmt(3PAM) function to check the validity of the authenticated user's account. In this example, pam_acct_mgmt() checks for expiration of the password.

    The pam_acct_mgmt() function also uses the PAM_DISALLOW_NULL_AUTHTOK flag. If pam_acct_mgmt() returns PAM_NEW_AUTHTOK_REQD, then pam_chauthtok(3PAM) should be called to allow the authenticated user to change the password.

  4. Force the user to change passwords if the system discovers that the password has expired.

    The example uses a loop to call pam_chauthtok() until success is returned. The pam_chauthtok() function returns success if the user successfully changes his or her authentication information, which is usually the password. In this example, the loop continues until success is returned. More commonly, an application would set a maximum number of tries before terminating.

  5. Call pam_setcred(3PAM).

    The pam_setcred(3PAM) function is used to establish, modify, or delete user credentials. pam_setcred() is typically called when a user has been authenticated. The call is made after the account has been validated, but before a session has been opened. The pam_setcred() function is used with the PAM_ESTABLISH_CRED flag to establish a new user session. If the session is the renewal of an existing session, such as for lockscreen, pam_setcred() with the PAM_REFRESH_CRED flag should be called. If the session is changing the credentials, such as using su or assuming a role, then pam_setcred() with the PAM_REINITIALIZE_CRED flag should be called.

  6. Close the PAM session.

    The PAM session is closed by calling the pam_end(3PAM) function. pam_end() frees all PAM resources as well.

The following example shows the source code for the sample PAM consumer application.

Example 5  Sample PAM Consumer Application
/*
 * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <signal.h>
#include <pwd.h>
#include <errno.h>
#include <security/pam_appl.h>

extern int pam_tty_conv(int num_msg, struct pam_message **msg,
        struct pam_response **response, void *appdata_ptr);

/* Disable keyboard interrupts (Ctrl-C, Ctrl-Z, Ctrl-\) */
static void
disable_kbd_signals(void) {
    (void) signal(SIGINT, SIG_IGN);
    (void) signal(SIGTSTP, SIG_IGN);
    (void) signal(SIGQUIT, SIG_IGN);
}

/* Terminate current user session, i.e., logout */
static void
logout() {
    pid_t pgroup = getpgrp();

    (void) signal(SIGTERM, SIG_IGN);
    (void) fprintf(stderr, "Sorry, your session can't be restored.\n");
    (void) fprintf(stderr, "Press return to terminate this session.\n");
    (void) getchar();
    (void) kill(-pgroup, SIGTERM);
    (void) sleep(2);
    (void) kill(-pgroup, SIGKILL);
    exit(-1);
}

int
/*ARGSUSED*/
main(int argc, char *argv) {
    struct pam_conv conv = {pam_tty_conv, NULL};
    pam_handle_t *pamh;
    struct passwd *pw;
    int err;

    disable_kbd_signals();
    if ((pw = getpwuid(getuid())) == NULL) {
        (void) fprintf(stderr, "plock: Can't get username: %s\n",
                strerror(errno));
        exit(1);
    }

    /* Initialize PAM framework */
    err = pam_start("plock", pw->pw_name, &conv, &pamh);
    if (err != PAM_SUCCESS) {
        (void) fprintf(stderr, "plock: pam_start failed: %s\n",
                pam_strerror(pamh, err));
        exit(1);
    }

    /* Authenticate user in order to unlock screen */
    do {
        (void) fprintf(stderr, "Terminal locked for %s. ", pw->pw_name);
        err = pam_authenticate(pamh, 0);
        if (err == PAM_USER_UNKNOWN) {
            logout();
        } else if (err != PAM_SUCCESS) {
            (void) fprintf(stderr, "Invalid password.\n");
        }
    } while (err != PAM_SUCCESS);

    /* Make sure account and password are still valid */
    switch (err = pam_acct_mgmt(pamh, 0)) {
        case PAM_SUCCESS:
            break;
        case PAM_USER_UNKNOWN:
        case PAM_ACCT_EXPIRED:
            /* User not allowed in anymore */
            logout();
            break;
        case PAM_NEW_AUTHTOK_REQD:
            /* The user's password has expired. Get a new one */
            do {
                err = pam_chauthtok(pamh, 0);
            } while (err == PAM_AUTHTOK_ERR);
            if (err != PAM_SUCCESS)
                logout();
            break;
        default:
            logout();
    }
    /* Establish the requested credentials */
    if ((err = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS)
        logout();



    /* Open a session */
    if ((err = pam_open_session(pamh, 0)) != PAM_SUCCESS)
        logout();
   


    /* Close a session */
    if ((err = pam_close_session(pamh, 0)) != PAM_SUCCESS)
        logout();


   /* Delete the requested credentials */
    if ((err = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
        logout();

    if (pam_setcred(pamh, PAM_REFRESH_CRED) != PAM_SUCCESS) {
        logout();
    }

    (void) pam_end(pamh, 0);
    return (0);
    /*NOTREACHED*/
}

Useful PAM Functions

The preceding example, Example 5, Sample PAM Consumer Application, is a simple application that demonstrates only a few of the major PAM functions. This section describes some other PAM functions that can be useful.

  • The pam_open_session(3PAM) function is called to open a new session after a user has been successfully authenticated.

  • The pam_getenvlist(3PAM) function is called to establish a new environment. pam_getenvlist() returns a new environment to be merged with the existing environment.

  • The pam_eval(3PAM) function loads and evaluates a PAM configuration stored in a file specified by the caller. This function is called by the pam_user_policy(5) PAM module.