Go to main content

Developer's Guide to Oracle® Solaris 11.4 Security

Exit Print View

Updated: November 2020
 
 

Writing Modules That Provide PAM Services

Applications which call PAM services may 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. See How to Add a PAM Module in Managing Authentication in Oracle Solaris 11.4f or details about how to install both the 32-bit and 64-bit versions of the module so that the PAM framework can load the appropriate version for the application.

Requirements for PAM Service Providers

PAM service modules use pam_get_item(3PAM) and pam_set_item(3PAM) to communicate with applications. To communicate with each other, service modules use pam_get_data(3PAM) and pam_set_data(3PAM). If service modules from the same project need to exchange data, then a unique data name for that project should be established. The service modules can then share this data through the pam_get_data() and pam_set_data() functions.

    Service modules must return one of three classes of PAM return code:

  • PAM_SUCCESS if the module has made a positive decision that is part of the requested policy.

  • PAM_IGNORE if the module does not make a policy decision.

  • PAM_error if the module participates in the decision that results in a failure. The error can be either a generic error code or a code specific to the service module type. The error cannot be an error code for another service module type. See the pam_authtok_get(7) man page for pam_sm_module-type for the error codes.

If a service module performs multiple functions, these functions should be split up into separate modules. This approach gives system administrators finer-grained control for configuring policy.

    Man pages should be provided for any new service modules. Man pages should include the following items:

  • Arguments that the module accepts.

  • All functions that the module implements.

  • The effect of flags on the algorithm.

  • Any required PAM items.

  • Error returns that are specific to this module.

Service modules are required to honor the PAM_SILENT flag for preventing display of messages. The debug argument is recommended for logging debug information to syslog. Use syslog(3C) with LOG_AUTH and LOG_DEBUG for debug logging. Other messages should be sent to syslog() with LOG_AUTH and the appropriate priority. openlog(3C), closelog(3C), and setlogmask(3C) must not be used as these functions interfere with the applications settings.

Sample PAM Provider Service Module

A sample PAM service module follows. This example checks to see if the user is a member of a group that is permitted access to this service. The provider then grants access on success or logs an error message on failure.

    The example goes through the following steps:

  1. Parse the options passed to this module from the PAM configuration. See pam.conf(5).

    This module accepts the nowarn and debug options as well as a specific option group. With the group option, the module can be configured to allow access for a particular group other than the group root that is used by default. See the definition of DEFAULT_GROUP in the source code for the example. To limit access to only users who belong to group staff, an administrator would add the following entry to the account configuration of the PAM service:

    account required pam_members_only.so.1 group=staff
  2. Get the username, service name and hostname.

    The username is obtained by calling pam_get_user(3PAM) which retrieves the current user name from the PAM handle. If the user name has not been set, access is denied. The service name and the host name are obtained by calling pam_get_item(3PAM).

  3. Validate the information to be worked on.

    If the user name is not set, deny access. If the group to be worked on is not defined, deny access.

  4. Verify that the current user is a member of the special group that allows access to this host and grant access.

    In the event that the special group is defined but contains no members at all, PAM_IGNORE is returned to indicate that this module does not participate in any account validation process. The decision is left to other modules on the stack.

  5. If the user is not a member of the special group, display a message to inform the user that access is denied.

    Log a message to record this event.

The following example shows the source code for the sample PAM provider.

Example 7  Sample PAM Service Module
/*
 * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
 */


#include <stdio.h>
#include <stdlib.h>
#include <grp.h>
#include <string.h>
#include <syslog.h>
#include <libintl.h>
#include <security/pam_appl.h>

/*
 * by default, only users who are a member of group "root" are allowed access
 */
#define	DEFAULT_GROUP "root"

static char *NOMSG =
        "Sorry, you are not on the access list for this host - access denied.";

int
pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char **argv) {
    char *user = NULL;
    char *host = NULL;
    char *service = NULL;
    const char *allowed_grp = DEFAULT_GROUP;
    char grp_buf[4096];
    struct group grp;
    struct pam_conv *conversation;
    struct pam_message message;
    struct pam_message *pmessage = &message;
    struct pam_response *res = NULL;
    int i;
    int nowarn = 0;
    int debug = 0;

    /* Set flags to display warnings if in debug mode. */
    for (i = 0; i < argc; i++) {
        if (strcasecmp(argv[i], "nowarn") == 0)
            nowarn = 1;
        else if (strcasecmp(argv[i], "debug") == 0)
            debug = 1;
        else if (strncmp(argv[i], "group=", 6) == 0)
            allowed_grp = &argv[i][6];
    }
    if (flags & PAM_SILENT)
        nowarn = 1;

    /* Get user name,service name, and host name. */
    (void) pam_get_user(pamh, &user, NULL);
    (void) pam_get_item(pamh, PAM_SERVICE, (void **) &service);
    (void) pam_get_item(pamh, PAM_RHOST, (void **) &host);

    /* Deny access if user is NULL. */
    if (user == NULL) {
        syslog(LOG_AUTH | LOG_DEBUG,
                "%s: members_only: user not set", service);
        return (PAM_USER_UNKNOWN);
    }

    if (host == NULL)
        host = "unknown";

    /*
     * Deny access if vuser group is required and user is not in vuser
     * group
     */
    if (getgrnam_r(allowed_grp, &grp, grp_buf, sizeof (grp_buf)) == NULL) {
        syslog(LOG_NOTICE | LOG_AUTH,
                "%s: members_only: group \"%s\" not defined",
                service, allowed_grp);
        return (PAM_SYSTEM_ERR);
    }

    /* Ignore this module if group contains no members. */
    if (grp.gr_mem[0] == 0) {
        if (debug)
            syslog(LOG_AUTH | LOG_DEBUG,
                "%s: members_only: group %s empty: "
                "all users allowed.", service, grp.gr_name);
        return (PAM_IGNORE);
    }

    /* Check to see if user is in group. If so, return SUCCESS. */
    for (; grp.gr_mem[0]; grp.gr_mem++) {
        if (strcmp(grp.gr_mem[0], user) == 0) {
            if (debug)
                syslog(LOG_AUTH | LOG_DEBUG,
                    "%s: user %s is member of group %s. "
                    "Access allowed.",
                    service, user, grp.gr_name);
            return (PAM_SUCCESS);
        }
    }

    /*
     * User is not a member of the group.
     * Set message style to error and specify denial message.
     */
    message.msg_style = PAM_ERROR_MSG;
    message.msg = gettext(NOMSG);

    /* Use conversation function to display denial message to user. */
    (void) pam_get_item(pamh, PAM_CONV, (void **) &conversation);
    if (nowarn == 0 && conversation != NULL) {
        int err;
        err = conversation->conv(1, &pmessage, &res,
                conversation->appdata_ptr);
        if (debug && err != PAM_SUCCESS)
            syslog(LOG_AUTH | LOG_DEBUG,
                "%s: members_only: conversation returned "
                "error %d (%s).", service, err,
                pam_strerror(pamh, err));

        /* free response (if any) */
        if (res != NULL) {
            if (res->resp)
                free(res->resp);
            free(res);
        }
    }

    /* Report denial to system log and return error to caller. */
    syslog(LOG_NOTICE | LOG_AUTH, "%s: members_only: "
            "Connection for %s not allowed from %s", service, user, host);

    return (PAM_PERM_DENIED);
}