root/usr/src/lib/pam_modules/ldap/ldap_acct_mgmt.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
 */

#include "ldap_headers.h"

/*ARGSUSED*/
static void
ldap_cleanup(
        pam_handle_t *pamh,
        void *data,
        int pam_status)
{
        free((ldap_authtok_data *)data);
}

/*
 * warn_user_passwd_will_expire - warn the user when the password will
 *                                        expire.
 */

static void
warn_user_passwd_will_expire(
        pam_handle_t *pamh,
        int sec_until_expired)
{
        char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
        int     days = 0, hours = 0;
        int     seconds_d = 0, seconds_h = 0;

        days = sec_until_expired / 86400;
        seconds_d = sec_until_expired % 86400;
        hours = (days * 24) + seconds_d / 3600;
        seconds_h = seconds_d % 3600;

        if (sec_until_expired <= (86400 * 2)) {
                if (seconds_d <= 3600 && days == 0)
                        (void) snprintf(messages[0], sizeof (messages[0]),
                            dgettext(TEXT_DOMAIN,
                            "Your password will expire within one hour."));
                else
                        (void) snprintf(messages[0], sizeof (messages[0]),
                            dgettext(TEXT_DOMAIN,
                                "Your password will expire in %d hours."),
                                (seconds_h == 0) ? hours : hours + 1);
        } else {
                        (void) snprintf(messages[0], sizeof (messages[0]),
                            dgettext(TEXT_DOMAIN,
                                "Your password will expire in %d days."),
                                (seconds_d == 0) ? days : days + 1);
        }

        (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL);
}

/*
 * display_acct_unlock_time - Display the time left for the account to
 * get auto unlocked after the maximum login failures has reached.
 */
static void
display_acct_unlock_time(pam_handle_t *pamh, int sec_b4_unlock)
{
        char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
        int     days = 0, hours = 0;
        int     seconds_d = 0, seconds_h = 0;

        /* Account is locked forever */
        if (sec_b4_unlock == -1) {
                (void) snprintf(messages[0], sizeof (messages[0]),
                dgettext(TEXT_DOMAIN,
                "Your account is locked, please contact administrator."));
                (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
                        messages, NULL);
                return;
        }

        days = sec_b4_unlock / 86400;
        seconds_d = sec_b4_unlock % 86400;
        hours = (days * 24) + seconds_d / 3600;
        seconds_h = seconds_d % 3600;

        if (sec_b4_unlock <= (86400 * 2)) {
                if (seconds_d <= 3600 && days == 0)
                        (void) snprintf(messages[0], sizeof (messages[0]),
                                dgettext(TEXT_DOMAIN,
                                "Your account is locked and will be unlocked"
                                " within one hour."));
                else
                        (void) snprintf(messages[0], sizeof (messages[0]),
                                dgettext(TEXT_DOMAIN,
                                "Your account is locked and will be unlocked"
                                " in %d hours."),
                                (seconds_h == 0) ? hours : hours + 1);
        } else {
                (void) snprintf(messages[0], sizeof (messages[0]),
                        dgettext(TEXT_DOMAIN,
                        "Your account is locked and will be unlocked"
                        " in %d days."),
                        (seconds_d == 0) ? days : days + 1);
        }

        (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL);
}

/*
 * warn_user_passwd_expired - warn the user that the password has expired
 */
static void
warn_user_passwd_expired(pam_handle_t *pamh, int grace)
{
        char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];

        if (grace)
                (void) snprintf(messages[0], sizeof (messages[0]),
                        dgettext(TEXT_DOMAIN,
                        "Your password has expired. "
                        "Number of grace logins allowed are %d."),
                        grace);
        else
                (void) snprintf(messages[0], sizeof (messages[0]),
                        dgettext(TEXT_DOMAIN,
                        "Your password has expired."));

        (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL);
}

/*
 * display_passwd_reset_msg - tell user that password has been reset by
 * administrator
 */
static void
display_passwd_reset_msg(pam_handle_t *pamh)
{
        char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];

        (void) snprintf(messages[0], sizeof (messages[0]),
                        dgettext(TEXT_DOMAIN,
                        "Your password has been reset by administrator."));

        (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL);
}

/*
 * Retreives account management related attributes for the user using
 * default binding and does local account checks .
 *
 * Return Value: PAM_SUCCESS - If account is valid, seconds param will have
 *                              seconds left for password to expire
 *               PAM_ACCT_EXPIRED - If account is inactive
 *               PAM_NEW_AUTHTOK_REQD - Password is reset by admin
 *               PAM_AUTHTOK_EXPIRED - User password has expired, grace
 *                              param will have no. of grace logins allowed
 *               PAM_MAXTRIES - If maximum failure of wrong password has reached
 *                              seconds param will have no. of seconds for the
 *                              account to get unlocked
 *               PAM_AUTH_ERR - Failure return code
 */
static int
get_account_mgmt(const char *user, int *seconds, int *grace)
{
        int rc  = PAM_AUTH_ERR;
        AcctUsableResponse_t    acctResp;

        (void *)memset((void*)&acctResp, 0, sizeof (acctResp));
        /* get the values for local account checking */
        if ((rc = __ns_ldap_getAcctMgmt(user, &acctResp))
                != NS_LDAP_SUCCESS) {
                syslog(LOG_DEBUG,
                        "__ns_ldap_getAcctMgmt() failed for %s with error %d",
                                user, rc);
                return (PAM_AUTH_ERR);
        }

        if (acctResp.choice == 0) {
                /* should be able to login */
                *seconds =
                        acctResp.AcctUsableResp.seconds_before_expiry;
                return (PAM_SUCCESS);
        } else if (acctResp.choice == 1) {
                /* cannot login */
                if (acctResp.AcctUsableResp.more_info.inactive)
                        /* entry inactive */
                        return (PAM_ACCT_EXPIRED);
                if (acctResp.AcctUsableResp.more_info.reset)
                        /* password reset by administrator */
                        return (PAM_NEW_AUTHTOK_REQD);
                if (acctResp.AcctUsableResp.more_info.expired) {
                        /*
                         * password expired, check for grace logins.
                         */
                        *grace =
                                acctResp.AcctUsableResp.more_info.rem_grace;
                        return (PAM_AUTHTOK_EXPIRED);
                }
                if (acctResp.AcctUsableResp.more_info.sec_b4_unlock) {
                        /* max failures reached, seconds before unlock */
                        *seconds =
                                acctResp.AcctUsableResp.more_info.sec_b4_unlock;
                        return (PAM_MAXTRIES);
                }
        }
        return (PAM_AUTH_ERR);
}

/*
 * pam_sm_acct_mgmt     main account managment routine.
 *                      This routine relies on the LDAP
 *                      directory server to provide the
 *                      password aging and account lockout
 *                      information. This is done by first
 *                      trying to authenticate the user and
 *                      then checking the password status
 *                      returned.
 *
 *                      Returns: module error or specific
 *                      error on failure.
 */

int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
{

        const char *user = NULL;
        int result = PAM_AUTH_ERR;
        int debug = 0;
        int i;
        const char *password = NULL;
        ns_cred_t *credp = NULL;
        int nowarn = 0;
        int seconds = 0, grace = 0;
        ldap_authtok_data *status;

        for (i = 0; i < argc; i++) {
                if (strcmp(argv[i], "debug") == 0)
                        debug = 1;
                else if (strcasecmp(argv[i], "nowarn") == 0) {
                        nowarn = 1;
                        flags = flags | PAM_SILENT;
                }
                else
                        syslog(LOG_DEBUG,
                                "pam_ldap pam_sm_acct_mgmt: "
                                "illegal option %s",
                                argv[i]);
        }

        if ((result = pam_get_item(pamh, PAM_USER, (const void **)&user)) !=
            PAM_SUCCESS) {
                goto out;
        }

        if (debug)
                syslog(LOG_DEBUG,
                        "ldap pam_sm_acct_mgmt(%s), flags = %x %s",
                        (user)?user:"no-user", flags,
                        (nowarn)? ", nowarn": "");

        if (user == NULL) {
                result = PAM_USER_UNKNOWN;
                goto out;
        }

        /* retrieve the password from the PAM handle */
        result = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&password);
        if (password == NULL) {
                if (debug)
                        syslog(LOG_DEBUG, "ldap pam_sm_acct_mgmt: "
                            "no password for user %s", user);
                /* Do local account checking */
                result = get_account_mgmt(user, &seconds, &grace);
        } else {
                /* Try to authenticate to get password management info */
                result = authenticate(&credp, user,
                                password, &seconds);
        }

        /*
         * process the password management info.
         * If user needs to change the password immediately,
         * just return the rc.
         * Otherwise, reset rc to the appropriate PAM error or
         * warn the user about password expiration.
         */
        if (result == PAM_MAXTRIES) {
                /* exceed retry limit, denied access to account */
                if (!(flags & PAM_SILENT))
                        display_acct_unlock_time(pamh, seconds);
                result = PAM_PERM_DENIED;
        } else if (result == PAM_ACCT_EXPIRED)
                /* account is inactivated */
                result = PAM_ACCT_EXPIRED;
        else if (result == PAM_AUTHTOK_EXPIRED) {
                if (!(flags & PAM_SILENT))
                        warn_user_passwd_expired(pamh, grace);
                /* password expired, check for grace logins */
                if (grace > 0)
                        result = PAM_SUCCESS;
                else
                        result = PAM_AUTHTOK_EXPIRED;
        } else if (result == PAM_NEW_AUTHTOK_REQD) {
                /* password has been reset by administrator */
                if (!(flags & PAM_SILENT))
                        display_passwd_reset_msg(pamh);
                result = PAM_NEW_AUTHTOK_REQD;
        } else if (result == PAM_SUCCESS) {
                /*
                 * warn the user if the password
                 * is about to expire.
                 */
                if (!(flags & PAM_SILENT) &&
                        seconds > 0)
                        warn_user_passwd_will_expire(pamh,
                                seconds);

        }

out:
        if (credp != NULL)
                (void) __ns_ldap_freeCred(&credp);

        /* store the password aging status in the pam handle */
        if (result != PAM_SUCCESS) {
                int pam_res;
                ldap_authtok_data *authtok_data;

                pam_res = pam_get_data(
                        pamh, LDAP_AUTHTOK_DATA, (const void **)&authtok_data);

                if ((status = (ldap_authtok_data *)calloc
                        (1, sizeof (ldap_authtok_data))) == NULL) {
                        return (PAM_BUF_ERR);
                }

                if (pam_res == PAM_SUCCESS)
                        (void) memcpy(status, authtok_data,
                                sizeof (ldap_authtok_data));

                status->age_status = result;
                if (pam_set_data(pamh, LDAP_AUTHTOK_DATA, status, ldap_cleanup)
                                                        != PAM_SUCCESS) {
                        free(status);
                        return (PAM_SERVICE_ERR);
                }
        }

        return (result);
}