root/usr/src/lib/passwdutil/ldap_attr.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 * Copyright 2024 OmniOS Community Edition (OmniOSce) Association.
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <macros.h>
#include <priv.h>

#include "ns_sldap.h"

#include <nss_dbdefs.h>
#include <nsswitch.h>

#include <pwd.h>
#include <shadow.h>
#include <syslog.h>

#include "passwdutil.h"

#include "utils.h"

#define MAX_INT_LEN 11  /* 10+1 %d buflen for words/ints [not longs] */

#define STRDUP_OR_RET(to, from) \
        if ((to = strdup(from)) == NULL) \
                return (PWU_NOMEM);

#define STRDUP_OR_ERR(to, from, err) \
        if (((to) = strdup(from)) == NULL) \
                (err) = PWU_NOMEM;

#define NUM_TO_STR(to, from) \
        { \
                char nb[MAX_INT_LEN]; \
                if (snprintf(nb, MAX_INT_LEN, "%d", (from)) >= MAX_INT_LEN) \
                        return (PWU_NOMEM); \
                STRDUP_OR_RET(to, nb); \
        }

#define NEW_ATTR(p, i, attr, val) \
        { \
                p[i] = new_attr(attr, (val)); \
                if (p[i] == NULL) \
                        return (PWU_NOMEM); \
                i++; \
        }

int ldap_getattr(const char *name, attrlist *item, pwu_repository_t *rep);
int ldap_getpwnam(const char *name, attrlist *items, pwu_repository_t *rep,
    void **buf);
int ldap_update(attrlist *items, pwu_repository_t *rep, void *buf);
int ldap_putpwnam(const char *name, const char *oldpw, pwu_repository_t *rep,
    void *buf);
int ldap_user_to_authenticate(const char *name, pwu_repository_t *rep,
        char **auth_user, int *privileged);

/*
 * ldap function pointer table, used by passwdutil_init to initialize
 * the global Repository-OPerations table "rops"
 */
struct repops ldap_repops = {
        NULL,   /* checkhistory */
        ldap_getattr,
        ldap_getpwnam,
        ldap_update,
        ldap_putpwnam,
        ldap_user_to_authenticate,
        NULL,   /* lock */
        NULL    /* unlock */
};

/*
 * structure used to keep state between get/update/put calls
 */
typedef struct {
        char *passwd;                   /* encrypted password */
        struct passwd *pwd;
        ns_ldap_attr_t **pattrs;        /* passwd attrs */
        int npattrs;                    /* max attrs */
        struct spwd *spwd;
        ns_ldap_attr_t **sattrs;        /* passwd attrs */
        int nsattrs;                    /* max attrs */
        boolean_t shadow_update_enabled;        /* shadow update configured */
} ldapbuf_t;

/*
 * The following define's are taken from
 *      usr/src/lib/nsswitch/ldap/common/getpwnam.c
 */

/* passwd attributes filters */
#define _PWD_CN                 "cn"
#define _PWD_UID                "uid"
#define _PWD_USERPASSWORD       "userpassword"
#define _PWD_UIDNUMBER          "uidnumber"
#define _PWD_GIDNUMBER          "gidnumber"
#define _PWD_GECOS              "gecos"
#define _PWD_DESCRIPTION        "description"
#define _PWD_HOMEDIRECTORY      "homedirectory"
#define _PWD_LOGINSHELL         "loginshell"

#define _PWD_MAX_ATTR           10      /* 9+NULL */

/* shadow attributes filters */
#define _S_LASTCHANGE           "shadowlastchange"
#define _S_MIN                  "shadowmin"
#define _S_MAX                  "shadowmax"
#define _S_WARNING              "shadowwarning"
#define _S_INACTIVE             "shadowinactive"
#define _S_EXPIRE               "shadowexpire"
#define _S_FLAG                 "shadowflag"

#define _S_MAX_ATTR             8       /* 7+NULL */

/*
 * Frees up an ldapbuf_t
 */

static void
free_ldapbuf(ldapbuf_t *p)
{
        int i;

        if (p == NULL)
                return;
        if (p->passwd) {
                (void) memset(p->passwd, 0, strlen(p->passwd));
                free(p->passwd);
        }
        if (p->pwd)
                free_pwd(p->pwd);
        if (p->spwd)
                free_spwd(p->spwd);
        if (p->pattrs) {
                for (i = 0; i < p->npattrs; i++) {
                        if (p->pattrs[i] != NULL) {
                                free(p->pattrs[i]->attrvalue[0]);
                                free(p->pattrs[i]);
                        }
                }
                free(p->pattrs);
        }
        if (p->sattrs) {
                for (i = 0; i < p->nsattrs; i++) {
                        if (p->sattrs[i] != NULL) {
                                free(p->sattrs[i]->attrvalue[0]);
                                free(p->sattrs[i]);
                        }
                }
                free(p->sattrs);
        }
}

/*
 * int ldap_user_to_authenticate(user, rep, auth_user, privileged)
 *
 * If the Shadow Update functionality is enabled, then we check to
 * see if the caller has 0 as the euid or has all zone privs. If so,
 * the caller would be able to modify shadow(5) data stored on the
 * LDAP server. Otherwise, when LDAP Shadow Update is not enabled,
 * we can't determine whether the user is "privileged" in the LDAP
 * sense. The operation should be attempted and will succeed if the
 * user had privileges. For our purposes, we say that the user is
 * privileged if they are attempting to change another user's
 * password attributes.
 */
int
ldap_user_to_authenticate(const char *user, pwu_repository_t *rep,
        char **auth_user, int *privileged)
{
        struct passwd *pw;
        uid_t uid;
        uid_t priviledged_uid;
        int res = PWU_SUCCESS;

        if (strcmp(user, "root") == 0)
                return (PWU_NOT_FOUND);

        if ((pw = getpwnam_from(user, rep, REP_LDAP)) == NULL)
                return (PWU_NOT_FOUND);

        uid = getuid();

        /*
         * need equivalent of write access to /etc/shadow
         * the privilege escalation model is euid == 0 || all zone privs
         */
        if (__ns_ldap_is_shadow_update_enabled()) {
                boolean_t priv;

                priv = (geteuid() == 0);
                if (!priv) {
                        priv_set_t *ps = priv_allocset();       /* caller */
                        priv_set_t *zs;                         /* zone */

                        (void) getppriv(PRIV_EFFECTIVE, ps);
                        zs = priv_str_to_set("zone", ",", NULL);
                        priv = priv_isequalset(ps, zs);
                        priv_freeset(ps);
                        priv_freeset(zs);
                }
                /*
                 * priv can change anyone's password,
                 * only root isn't prompted.
                 */
                *privileged = 0;        /* for proper prompting */
                if (priv) {
                        if (uid == 0) {
                                *privileged = 1;
                                *auth_user = NULL;
                                return (res);
                        } else if (uid == pw->pw_uid) {
                                STRDUP_OR_ERR(*auth_user, user, res);
                                return (res);
                        }
                }

                return (PWU_DENIED);
        }

        if (uid == pw->pw_uid) {
                /* changing our own, not privileged */
                *privileged = 0;
                STRDUP_OR_RET(*auth_user, user);
        } else {
                char pwd_buf[1024];
                struct passwd pwr;

                *privileged = 1;
                /*
                 * specific case for root
                 * we want 'user' to be authenticated.
                 */
                if (uid == 0)  {
                        priviledged_uid = pw->pw_uid;
                } else {
                        priviledged_uid = uid;
                }
                if (getpwuid_r(priviledged_uid, &pwr, pwd_buf,
                    sizeof (pwd_buf)) != NULL) {
                        STRDUP_OR_ERR(*auth_user, pwr.pw_name, res);
                } else {
                        /* hmm. can't find name of current user...??? */

                        if ((*auth_user = malloc(MAX_INT_LEN)) == NULL) {
                                res = PWU_NOMEM;
                        } else {
                                (void) snprintf(*auth_user, MAX_INT_LEN, "%d",
                                    (int)uid);
                        }
                }
        }

        return (res);
}

/*
 * int ldap_getattr(name, item, rep)
 *
 * retrieve attributes specified in "item" for user "name".
 */
/*ARGSUSED*/
int
ldap_getattr(const char *name, attrlist *items, pwu_repository_t *rep)
{
        attrlist *w;
        int res;
        ldapbuf_t *ldapbuf;
        struct passwd *pw = NULL;
        struct spwd *spw = NULL;

        res = ldap_getpwnam(name, items, rep, (void **)&ldapbuf);
        if (res != PWU_SUCCESS)
                return (res);

        pw = ldapbuf->pwd;
        spw = ldapbuf->spwd;

        for (w = items; res == PWU_SUCCESS && w != NULL; w = w->next) {
                switch (w->type) {
                case ATTR_NAME:
                        STRDUP_OR_ERR(w->data.val_s, pw->pw_name, res);
                        break;
                case ATTR_COMMENT:
                        STRDUP_OR_ERR(w->data.val_s, pw->pw_comment, res);
                        break;
                case ATTR_GECOS:
                        STRDUP_OR_ERR(w->data.val_s, pw->pw_gecos, res);
                        break;
                case ATTR_HOMEDIR:
                        STRDUP_OR_ERR(w->data.val_s, pw->pw_dir, res);
                        break;
                case ATTR_SHELL:
                        STRDUP_OR_ERR(w->data.val_s, pw->pw_shell, res);
                        break;
                case ATTR_PASSWD:
                case ATTR_PASSWD_SERVER_POLICY:
                        STRDUP_OR_ERR(w->data.val_s, spw->sp_pwdp, res);
                        break;
                case ATTR_AGE:
                        STRDUP_OR_ERR(w->data.val_s, pw->pw_age, res);
                        break;
                case ATTR_REP_NAME:
                        STRDUP_OR_ERR(w->data.val_s, "ldap", res);
                        break;

                /* integer values */
                case ATTR_UID:
                        w->data.val_i = pw->pw_uid;
                        break;
                case ATTR_GID:
                        w->data.val_i = pw->pw_gid;
                        break;
                case ATTR_LSTCHG:
                        if (ldapbuf->shadow_update_enabled)
                                w->data.val_i = spw->sp_lstchg;
                        else
                                w->data.val_i = -1;
                        break;
                case ATTR_MIN:
                        if (ldapbuf->shadow_update_enabled)
                                w->data.val_i = spw->sp_min;
                        else
                                w->data.val_i = -1;
                        break;
                case ATTR_MAX:
                        if (ldapbuf->shadow_update_enabled)
                                w->data.val_i = spw->sp_max;
                        else
                                w->data.val_i = -1;
                        break;
                case ATTR_WARN:
                        if (ldapbuf->shadow_update_enabled)
                                w->data.val_i = spw->sp_warn;
                        else
                                w->data.val_i = -1;
                        break;
                case ATTR_INACT:
                        if (ldapbuf->shadow_update_enabled)
                                w->data.val_i = spw->sp_inact;
                        else
                                w->data.val_i = -1;
                        break;
                case ATTR_EXPIRE:
                        if (ldapbuf->shadow_update_enabled)
                                w->data.val_i = spw->sp_expire;
                        else
                                w->data.val_i = -1;
                        break;
                case ATTR_FLAG:
                        if (ldapbuf->shadow_update_enabled)
                                w->data.val_i = spw->sp_flag;
                        break;
                case ATTR_FAILED_LOGINS:
                        w->data.val_i = spw->sp_flag & FAILCOUNT_MASK;
                        break;
                default:
                        break;
                }
        }

        free_ldapbuf(ldapbuf);
        free(ldapbuf);
        return (res);
}

/*
 * int ldap_getpwnam(name, items, rep, buf)
 *
 * There is no need to get the old values from the ldap
 * server, as the update will update each item individually.
 * Therefore, we only allocate a buffer that will be used by
 * _update and _putpwnam to hold the attributes to update.
 *
 * Only when we're about to update a password, we need to retrieve
 * the old password since it contains salt-information.
 */
/*ARGSUSED*/
int
ldap_getpwnam(const char *name, attrlist *items, pwu_repository_t *rep,
    void **buf)
{
        ldapbuf_t *ldapbuf;
        int res = PWU_NOMEM;

        /*
         * [sp]attrs is treated as NULL terminated
         */

        ldapbuf = calloc(1, sizeof (ldapbuf_t));
        if (ldapbuf == NULL)
                return (PWU_NOMEM);

        ldapbuf->pattrs = calloc(_PWD_MAX_ATTR, sizeof (ns_ldap_attr_t *));
        if (ldapbuf->pattrs == NULL)
                goto out;
        ldapbuf->npattrs = _PWD_MAX_ATTR;

        ldapbuf->sattrs = calloc(_S_MAX_ATTR, sizeof (ns_ldap_attr_t *));
        if (ldapbuf->sattrs == NULL)
                goto out;
        ldapbuf->nsattrs = _S_MAX_ATTR;

        res = dup_pw(&ldapbuf->pwd, getpwnam_from(name, rep, REP_LDAP));
        if (res != PWU_SUCCESS)
                goto out;

        res = dup_spw(&ldapbuf->spwd, getspnam_from(name, rep, REP_LDAP));
        if (res != PWU_SUCCESS)
                goto out;
        else {
                char *spw = ldapbuf->spwd->sp_pwdp;
                if (spw != NULL && *spw != '\0') {
                        ldapbuf->passwd = strdup(spw);
                        if (ldapbuf->passwd == NULL)
                                goto out;
                } else
                        ldapbuf->passwd = NULL;
        }

        /* remember if shadow update is enabled */
        ldapbuf->shadow_update_enabled = __ns_ldap_is_shadow_update_enabled();

        *buf = (void *)ldapbuf;
        return (PWU_SUCCESS);

out:
        free_ldapbuf(ldapbuf);
        free(ldapbuf);
        return (res);
}

/*
 * new_attr(name, value)
 *
 * create a new LDAP attribute to be sent to the server
 */
ns_ldap_attr_t *
new_attr(char *name, char *value)
{
        ns_ldap_attr_t *tmp;

        tmp = malloc(sizeof (*tmp));
        if (tmp != NULL) {
                tmp->attrname = name;
                tmp->attrvalue = (char **)calloc(2, sizeof (char *));
                if (tmp->attrvalue == NULL) {
                        free(tmp);
                        return (NULL);
                }
                tmp->attrvalue[0] = value;
                tmp->value_count = 1;
        }

        return (tmp);
}

/*
 * max_present(list)
 *
 * returns '1' if a ATTR_MAX with value != -1 is present. (in other words:
 * if password aging is to be turned on).
 */
static int
max_present(attrlist *list)
{
        while (list != NULL)
                if (list->type == ATTR_MAX && list->data.val_i != -1)
                        return (1);
                else
                        list = list->next;
        return (0);
}

/*
 * attr_addmod(attrs, idx, item, val)
 *
 * Adds or updates attribute 'item' in ldap_attrs list to value
 * update idx if item is added
 * return:  -1 - PWU_NOMEM/error, 0 - success
 */
static int
attr_addmod(ns_ldap_attr_t **attrs, int *idx, char *item, int value)
{
        char numbuf[MAX_INT_LEN], *strp;
        int i;

        /* stringize the value or abort */
        if (snprintf(numbuf, MAX_INT_LEN, "%d", value) >= MAX_INT_LEN)
                return (-1);

        /* check for existence and modify existing */
        for (i = 0; i < *idx; i++) {
                if (attrs[i] != NULL &&
                    strcmp(item, attrs[i]->attrname) == 0) {
                        strp = strdup(numbuf);
                        if (strp == NULL)
                                return (-1);
                        free(attrs[i]->attrvalue[0]);
                        attrs[i]->attrvalue[0] = strp;
                        return (0);
                }
        }
        /* else add */
        strp = strdup(numbuf);
        if (strp == NULL)
                return (-1);
        attrs[*idx] = new_attr(item, strp);
        if (attrs[*idx] == NULL)
                return (-1);
        (*idx)++;
        return (0);
}

/*
 * ldap_update(items, rep, buf)
 *
 * create LDAP attributes in 'buf' for each attribute in 'items'.
 */
/*ARGSUSED*/
int
ldap_update(attrlist *items, pwu_repository_t *rep, void *buf)
{
        attrlist *p;
        ldapbuf_t *ldapbuf = (ldapbuf_t *)buf;
        struct spwd *spw;
        ns_ldap_attr_t **pattrs = ldapbuf->pattrs;
        int pidx = 0;
        ns_ldap_attr_t **sattrs = ldapbuf->sattrs;
        int sidx = 0;
        char *pwd, *val;
        char *salt;
        size_t cryptlen;
        int len;
        int count;
        int rc = PWU_SUCCESS;
        int aging_needed = 0;
        int aging_set = 0;
        int disable_aging;

        spw = ldapbuf->spwd;

        /*
         * if sp_max==0 and shadow update is enabled:
         * disable passwd aging after updating the password
         */
        disable_aging = (spw != NULL && spw->sp_max == 0 &&
            ldapbuf->shadow_update_enabled);

        for (p = items; p != NULL; p = p->next) {
                switch (p->type) {
                case ATTR_PASSWD:
                        /*
                         * There is a special case for ldap: if the
                         * password is to be deleted (-d to passwd),
                         * p->data.val_s will be NULL.
                         */
                        if (p->data.val_s == NULL) {
                                if (!ldapbuf->shadow_update_enabled)
                                        return (PWU_CHANGE_NOT_ALLOWED);
                                cryptlen =
                                    sizeof ("{crypt}" NS_LDAP_NO_UNIX_PASSWORD);
                                val = malloc(cryptlen);
                                if (val == NULL)
                                        return (PWU_NOMEM);
                                (void) snprintf(val, cryptlen,
                                "{crypt}" NS_LDAP_NO_UNIX_PASSWORD);
                        } else { /* not deleting password */
                                salt = crypt_gensalt(ldapbuf->passwd,
                                    ldapbuf->pwd);

                                if (salt == NULL) {
                                        if (errno == ENOMEM)
                                                return (PWU_NOMEM);

                                        /* algorithm problem? */
                                        syslog(LOG_AUTH | LOG_ALERT,
                                            "passwdutil: crypt_gensalt "
                                            "%m");
                                        return (PWU_UPDATE_FAILED);
                                }

                                pwd = crypt(p->data.val_s, salt);
                                free(salt);
                                cryptlen = strlen(pwd) + sizeof ("{crypt}");
                                val = malloc(cryptlen);
                                if (val == NULL)
                                        return (PWU_NOMEM);
                                (void) snprintf(val, cryptlen,
                                    "{crypt}%s", pwd);
                        }

                        /*
                         * If not managing passwordAccount,
                         * insert the new password in the
                         * passwd attr array and break.
                         */
                        if (!ldapbuf->shadow_update_enabled) {
                                NEW_ATTR(pattrs, pidx,
                                    _PWD_USERPASSWORD, val);
                                break;
                        }

                        /*
                         * Managing passwordAccount, insert the
                         * new password, along with lastChange and
                         * shadowFlag, in the shadow attr array.
                         */
                        NEW_ATTR(sattrs, sidx, _PWD_USERPASSWORD, val);

                        if (attr_addmod(sattrs, &sidx, _S_LASTCHANGE,
                            DAY_NOW_32) < 0)
                                return (PWU_NOMEM);
                        spw->sp_lstchg = DAY_NOW_32;

                        if (attr_addmod(sattrs, &sidx, _S_FLAG,
                            spw->sp_flag & ~FAILCOUNT_MASK) < 0)
                                return (PWU_NOMEM);
                        spw->sp_flag &= ~FAILCOUNT_MASK; /* reset count */
                        aging_needed = 1;
                        break;
                case ATTR_PASSWD_SERVER_POLICY:
                        /*
                         * For server policy, don't crypt the password,
                         * send the password as is to the server and
                         * let the LDAP server do its own password
                         * encryption
                         */
                        STRDUP_OR_RET(val, p->data.val_s);

                        NEW_ATTR(pattrs, pidx, _PWD_USERPASSWORD, val);
                        break;
                case ATTR_COMMENT:
                        /* XX correct? */
                        NEW_ATTR(pattrs, pidx, _PWD_DESCRIPTION, p->data.val_s);
                        break;
                case ATTR_GECOS:
                        if (!ldapbuf->shadow_update_enabled) {
                                NEW_ATTR(pattrs, pidx, _PWD_GECOS,
                                    p->data.val_s);
                        } else {
                                NEW_ATTR(sattrs, sidx, _PWD_GECOS,
                                    p->data.val_s);
                        }
                        break;
                case ATTR_HOMEDIR:
                        if (!ldapbuf->shadow_update_enabled) {
                                NEW_ATTR(pattrs, pidx, _PWD_HOMEDIRECTORY,
                                    p->data.val_s);
                        } else {
                                NEW_ATTR(sattrs, sidx, _PWD_HOMEDIRECTORY,
                                    p->data.val_s);
                        }
                        break;
                case ATTR_SHELL:
                        if (!ldapbuf->shadow_update_enabled) {
                                NEW_ATTR(pattrs, pidx, _PWD_LOGINSHELL,
                                    p->data.val_s);
                        } else {
                                NEW_ATTR(sattrs, sidx, _PWD_LOGINSHELL,
                                    p->data.val_s);
                        }
                        break;
                /* We don't update NAME, UID, GID */
                case ATTR_NAME:
                case ATTR_UID:
                case ATTR_GID:
                /* Unsupported item */
                case ATTR_AGE:
                        break;
                case ATTR_LOCK_ACCOUNT:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        if (spw->sp_pwdp == NULL) {
                                spw->sp_pwdp = LOCKSTRING;
                        } else if ((strncmp(spw->sp_pwdp, LOCKSTRING,
                            sizeof (LOCKSTRING)-1) != 0) &&
                            (strcmp(spw->sp_pwdp, NOLOGINSTRING) != 0)) {
                                len = sizeof (LOCKSTRING)-1 +
                                    strlen(spw->sp_pwdp) + 1 +
                                    sizeof ("{crypt}");
                                pwd = malloc(len);
                                if (pwd == NULL) {
                                        return (PWU_NOMEM);
                                }
                                (void) strlcpy(pwd, "{crypt}", len);
                                (void) strlcat(pwd, LOCKSTRING, len);
                                (void) strlcat(pwd, spw->sp_pwdp, len);
                                free(spw->sp_pwdp);
                                spw->sp_pwdp = pwd;
                                NEW_ATTR(sattrs, sidx, _PWD_USERPASSWORD,
                                    spw->sp_pwdp);
                        }
                        if (attr_addmod(sattrs, &sidx, _S_LASTCHANGE,
                            DAY_NOW_32) < 0)
                                return (PWU_NOMEM);
                        spw->sp_lstchg = DAY_NOW_32;
                        break;

                case ATTR_UNLOCK_ACCOUNT:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        if (spw->sp_pwdp &&
                            strncmp(spw->sp_pwdp, LOCKSTRING,
                            sizeof (LOCKSTRING)-1) == 0) {
                                len = (sizeof ("{crypt}") -
                                    sizeof (LOCKSTRING)) +
                                    strlen(spw->sp_pwdp) + 1;
                                pwd = malloc(len);
                                if (pwd == NULL) {
                                        return (PWU_NOMEM);
                                }
                                (void) strlcpy(pwd, "{crypt}", len);
                                (void) strlcat(pwd, spw->sp_pwdp +
                                    sizeof (LOCKSTRING)-1, len);
                                free(spw->sp_pwdp);
                                spw->sp_pwdp = pwd;

                                NEW_ATTR(sattrs, sidx, _PWD_USERPASSWORD,
                                    spw->sp_pwdp);
                                if (attr_addmod(sattrs, &sidx, _S_LASTCHANGE,
                                    DAY_NOW_32) < 0)
                                        return (PWU_NOMEM);
                                spw->sp_lstchg = DAY_NOW_32;
                        }
                        break;

                case ATTR_NOLOGIN_ACCOUNT:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        free(spw->sp_pwdp);
                        STRDUP_OR_RET(spw->sp_pwdp, "{crypt}" NOLOGINSTRING);
                        NEW_ATTR(sattrs, sidx, _PWD_USERPASSWORD, spw->sp_pwdp);
                        if (attr_addmod(sattrs, &sidx, _S_LASTCHANGE,
                            DAY_NOW_32) < 0)
                                return (PWU_NOMEM);
                        spw->sp_lstchg = DAY_NOW_32;
                        break;

                case ATTR_EXPIRE_PASSWORD:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        NUM_TO_STR(val, 0);
                        NEW_ATTR(sattrs, sidx, _S_LASTCHANGE, val);
                        break;

                case ATTR_LSTCHG:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        NUM_TO_STR(val, p->data.val_i);
                        NEW_ATTR(sattrs, sidx, _S_LASTCHANGE, val);
                        break;

                case ATTR_MIN:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        if (spw->sp_max == -1 && p->data.val_i != -1 &&
                            max_present(p->next) == 0)
                                return (PWU_AGING_DISABLED);
                        NUM_TO_STR(val, p->data.val_i);
                        NEW_ATTR(sattrs, sidx, _S_MIN, val);
                        aging_set = 1;
                        break;

                case ATTR_MAX:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        if (p->data.val_i == -1) {
                                /* Turn off aging. Reset min and warn too */
                                spw->sp_max = spw->sp_min = spw->sp_warn = -1;
                                NUM_TO_STR(val, -1);
                                NEW_ATTR(sattrs, sidx, _S_MIN, val);
                                NUM_TO_STR(val, -1);
                                NEW_ATTR(sattrs, sidx, _S_WARNING, val);
                        } else {
                                /* Turn account aging on */
                                if (spw->sp_min == -1) {
                                        /*
                                         * minage was not set with command-
                                         * line option: set to zero
                                         */
                                        spw->sp_min = 0;
                                        NUM_TO_STR(val, 0);
                                        NEW_ATTR(sattrs, sidx, _S_MIN,
                                            val);
                                }
                                /*
                                 * If aging was turned off, we update lstchg.
                                 * We take care not to update lstchg if the
                                 * user has no password, otherwise the user
                                 * might not be required to provide a password
                                 * the next time they log in.
                                 *
                                 * Also, if lstchg != -1 (i.e., not set)
                                 * we keep the old value.
                                 */
                                if (spw->sp_max == -1 &&
                                    spw->sp_pwdp != NULL && *spw->sp_pwdp &&
                                    spw->sp_lstchg == -1) {
                                        if (attr_addmod(sattrs, &sidx,
                                            _S_LASTCHANGE,
                                            DAY_NOW_32) < 0)
                                                return (PWU_NOMEM);
                                        spw->sp_lstchg = DAY_NOW_32;
                                }
                        }
                        NUM_TO_STR(val, p->data.val_i);
                        NEW_ATTR(sattrs, sidx, _S_MAX, val);
                        aging_set = 1;
                        break;

                case ATTR_WARN:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        if (spw->sp_max == -1 &&
                            p->data.val_i != -1 && max_present(p->next) == 0)
                                return (PWU_AGING_DISABLED);
                        NUM_TO_STR(val, p->data.val_i);
                        NEW_ATTR(sattrs, sidx, _S_WARNING, val);
                        break;

                case ATTR_INACT:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        NUM_TO_STR(val, p->data.val_i);
                        NEW_ATTR(sattrs, sidx, _S_INACTIVE, val);
                        break;

                case ATTR_EXPIRE:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        NUM_TO_STR(val, p->data.val_i);
                        NEW_ATTR(sattrs, sidx, _S_EXPIRE, val);
                        break;

                case ATTR_FLAG:
                        if (!ldapbuf->shadow_update_enabled)
                                break;  /* not managing passwordAccount */
                        NUM_TO_STR(val, p->data.val_i);
                        NEW_ATTR(sattrs, sidx, _S_FLAG, val);
                        break;
                case ATTR_INCR_FAILED_LOGINS:
                        if (!ldapbuf->shadow_update_enabled) {
                                rc = PWU_CHANGE_NOT_ALLOWED;
                                break;  /* not managing passwordAccount */
                        }
                        count = (spw->sp_flag & FAILCOUNT_MASK) + 1;
                        spw->sp_flag &= ~FAILCOUNT_MASK;
                        spw->sp_flag |= min(FAILCOUNT_MASK, count);
                        p->data.val_i = count;
                        NUM_TO_STR(val, spw->sp_flag);
                        NEW_ATTR(sattrs, sidx, _S_FLAG, val);
                        break;
                case ATTR_RST_FAILED_LOGINS:
                        if (!ldapbuf->shadow_update_enabled) {
                                rc = PWU_CHANGE_NOT_ALLOWED;
                                break;  /* not managing passwordAccount */
                        }
                        p->data.val_i = spw->sp_flag & FAILCOUNT_MASK;
                        spw->sp_flag &= ~FAILCOUNT_MASK;
                        NUM_TO_STR(val, spw->sp_flag);
                        NEW_ATTR(sattrs, sidx, _S_FLAG, val);
                        break;
                default:
                        break;
                }
        }

        /*
         * If the ldap client is configured with shadow update enabled,
         * then what should the new aging values look like?
         *
         * There are a number of different conditions
         *
         *  a) aging is already configured: don't touch it
         *
         *  b) disable_aging is set: disable aging
         *
         *  c) aging is not configured: turn on default aging;
         *
         *  b) and c) of course only if aging_needed and !aging_set.
         *  (i.e., password changed, and aging values not changed)
         */

        if (ldapbuf->shadow_update_enabled && spw != NULL && spw->sp_max <= 0) {
                /* a) aging not yet configured */
                if (aging_needed && !aging_set) {
                        if (disable_aging) {
                                /* b) turn off aging */
                                spw->sp_min = spw->sp_max = spw->sp_warn = -1;
                                if (attr_addmod(sattrs, &sidx, _S_MIN, -1) < 0)
                                        return (PWU_NOMEM);
                                if (attr_addmod(sattrs, &sidx, _S_MAX, -1) < 0)
                                        return (PWU_NOMEM);
                                if (attr_addmod(sattrs, &sidx, _S_WARNING,
                                    -1) < 0)
                                        return (PWU_NOMEM);
                        } else {
                                /* c) */
                                turn_on_default_aging(spw);

                                if (attr_addmod(sattrs, &sidx, _S_MIN,
                                    spw->sp_min) < 0)
                                        return (PWU_NOMEM);
                                if (attr_addmod(sattrs, &sidx, _S_MAX,
                                    spw->sp_max) < 0)
                                        return (PWU_NOMEM);
                                if (attr_addmod(sattrs, &sidx,
                                    _S_WARNING, spw->sp_warn) < 0)
                                        return (PWU_NOMEM);
                        }
                }
        }

        pattrs[pidx] = NULL;
        sattrs[sidx] = NULL;

        return (rc);
}

/*
 * ldap_to_pwu_code(error, pwd_status)
 *
 * translation from LDAP return values and PWU return values
 */
int
ldap_to_pwu_code(int error, int pwd_status)
{
        switch (error) {
        case NS_LDAP_SUCCESS:   return (PWU_SUCCESS);
        case NS_LDAP_OP_FAILED: return (PWU_DENIED);
        case NS_LDAP_NOTFOUND:  return (PWU_NOT_FOUND);
        case NS_LDAP_MEMORY:    return (PWU_NOMEM);
        case NS_LDAP_CONFIG:    return (PWU_NOT_FOUND);
        case NS_LDAP_INTERNAL:
                switch (pwd_status) {
                case NS_PASSWD_EXPIRED:
                        return (PWU_DENIED);
                case NS_PASSWD_CHANGE_NOT_ALLOWED:
                        return (PWU_CHANGE_NOT_ALLOWED);
                case NS_PASSWD_TOO_SHORT:
                        return (PWU_PWD_TOO_SHORT);
                case NS_PASSWD_INVALID_SYNTAX:
                        return (PWU_PWD_INVALID);
                case NS_PASSWD_IN_HISTORY:
                        return (PWU_PWD_IN_HISTORY);
                case NS_PASSWD_WITHIN_MIN_AGE:
                        return (PWU_WITHIN_MIN_AGE);
                default:
                        return (PWU_SYSTEM_ERROR);
                }
        default:                return (PWU_SYSTEM_ERROR);
        }
}

int
ldap_replaceattr(const char *dn, ns_ldap_attr_t **attrs, const char *binddn,
        const char *pwd, int *pwd_status, int flags)
{
        int             result = NS_LDAP_OP_FAILED;
        int             ldaprc;
        int             authstried = 0;
        char            **certpath = NULL;
        ns_auth_t       **app;
        ns_auth_t       **authpp = NULL;
        ns_auth_t       *authp = NULL;
        ns_cred_t       *credp;
        ns_ldap_error_t *errorp = NULL;

        debug("%s: replace_ldapattr()", __FILE__);

        if ((credp = (ns_cred_t *)calloc(1, sizeof (ns_cred_t))) == NULL)
                return (NS_LDAP_MEMORY); /* map to PWU_NOMEM */

        /* for admin shadow update, dn and pwd will be set later in libsldap */
        if ((flags & NS_LDAP_UPDATE_SHADOW) == 0) {
                /* Fill in the user name and password */
                if (dn == NULL || pwd == NULL)
                        goto out;
                credp->cred.unix_cred.userID = strdup(binddn);
                credp->cred.unix_cred.passwd = strdup(pwd);
        }

        /* get host certificate path, if one is configured */
        ldaprc = __ns_ldap_getParam(NS_LDAP_HOST_CERTPATH_P,
            (void ***)&certpath, &errorp);
        if (ldaprc != NS_LDAP_SUCCESS)
                goto out;

        if (certpath && *certpath)
                credp->hostcertpath = *certpath;

        /* Load the service specific authentication method */
        ldaprc = __ns_ldap_getServiceAuthMethods("passwd-cmd", &authpp,
            &errorp);

        if (ldaprc != NS_LDAP_SUCCESS)
                goto out;

        /*
         * if authpp is null, there is no serviceAuthenticationMethod
         * try default authenticationMethod
         */
        if (authpp == NULL) {
                ldaprc = __ns_ldap_getParam(NS_LDAP_AUTH_P, (void ***)&authpp,
                    &errorp);
                if (ldaprc != NS_LDAP_SUCCESS)
                        goto out;
        }

        /*
         * if authpp is still null, then can not authenticate, syslog
         * error message and return error
         */
        if (authpp == NULL) {
                syslog(LOG_ERR,
                "passwdutil: no legal LDAP authentication method configured");
                result = NS_LDAP_OP_FAILED;
                goto out;
        }

        /*
         * Walk the array and try all authentication methods in order except
         * for "none".
         */
        for (app = authpp; *app; app++) {
                authp = *app;
                /* what about disabling other mechanisms? "tls:sasl/EXTERNAL" */
                if (authp->type == NS_LDAP_AUTH_NONE)
                        continue;
                authstried++;
                credp->auth.type = authp->type;
                credp->auth.tlstype = authp->tlstype;
                credp->auth.saslmech = authp->saslmech;
                credp->auth.saslopt = authp->saslopt;

                ldaprc = __ns_ldap_repAttr("shadow", dn,
                    (const ns_ldap_attr_t * const *)attrs,
                    credp, flags, &errorp);
                if (ldaprc == NS_LDAP_SUCCESS) {
                        result = NS_LDAP_SUCCESS;
                        goto out;
                }

                /*
                 * if change not allowed due to configuration, indicate so
                 * to the caller
                 */
                if (ldaprc == NS_LDAP_CONFIG &&
                    errorp->status == NS_CONFIG_NOTALLOW) {
                        result = NS_LDAP_CONFIG;
                        *pwd_status = NS_PASSWD_CHANGE_NOT_ALLOWED;
                        goto out;
                }

                /*
                 * other errors might need to be added to this list, for
                 * the current supported mechanisms this is sufficient
                 */
                if ((ldaprc == NS_LDAP_INTERNAL) &&
                    (errorp->pwd_mgmt.status == NS_PASSWD_GOOD) &&
                    ((errorp->status == LDAP_INAPPROPRIATE_AUTH) ||
                    (errorp->status == LDAP_INVALID_CREDENTIALS))) {
                        result = ldaprc;
                        goto out;
                }

                /*
                 * If there is error related to password policy,
                 * return it to caller
                 */
                if ((ldaprc == NS_LDAP_INTERNAL) &&
                    errorp->pwd_mgmt.status != NS_PASSWD_GOOD) {
                        *pwd_status = errorp->pwd_mgmt.status;
                        result = ldaprc;
                        goto out;
                } else
                        *pwd_status = NS_PASSWD_GOOD;

                /* we don't really care about the error, just clean it up */
                if (errorp)
                        (void) __ns_ldap_freeError(&errorp);
        }
        if (authstried == 0) {
                syslog(LOG_ERR,
                "passwdutil: no legal LDAP authentication method configured");
                result = NS_LDAP_CONFIG;
                goto out;
        }
        result = NS_LDAP_OP_FAILED; /* map to PWU_DENIED */

out:
        if (credp)
                (void) __ns_ldap_freeCred(&credp);

        if (authpp)
                (void) __ns_ldap_freeParam((void ***)&authpp);

        if (errorp)
                (void) __ns_ldap_freeError(&errorp);

        return (result);
}


/*
 * ldap_putpwnam(name, oldpw, rep, buf)
 *
 * update the LDAP server with the attributes contained in 'buf'.
 */
/*ARGSUSED*/
int
ldap_putpwnam(const char *name, const char *oldpw, pwu_repository_t *rep,
    void *buf)
{
        int res;
        char *dn;       /* dn of user whose attributes we are changing */
        char *binddn;   /* dn of user who is performing the change */
        ns_ldap_error_t *errorp;
        ldapbuf_t *ldapbuf = (ldapbuf_t *)buf;
        ns_ldap_attr_t **pattrs = ldapbuf->pattrs;
        ns_ldap_attr_t **sattrs = ldapbuf->sattrs;
        struct passwd *pw;
        int pwd_status;
        uid_t uid;

        if (strcmp(name, "root") == 0)
                return (PWU_NOT_FOUND);

        /*
         * convert name of user whose attributes we are changing
         * to a distinguished name
         */
        res = __ns_ldap_uid2dn(name, &dn, NULL, &errorp);
        if (res != NS_LDAP_SUCCESS)
                goto out;

        /* update shadow via ldap_cachemgr if it is enabled */
        if (ldapbuf->shadow_update_enabled &&
            sattrs != NULL && sattrs[0] != NULL) {
                /*
                 * flag NS_LDAP_UPDATE_SHADOW indicates the shadow update
                 * should be done via ldap_cachemgr
                 */
                res = ldap_replaceattr(dn, sattrs, NULL, NULL, &pwd_status,
                    NS_LDAP_UPDATE_SHADOW);
                goto out;
        }

        /*
         * The LDAP server checks whether we are permitted to perform
         * the requested change. We need to send the name of the user
         * who is executing this piece of code, together with his
         * current password to the server.
         * If this is executed by a normal user changing his/her own
         * password, this will simply be the OLD password that is to
         * be changed.
         * Specific case if the user who is executing this piece
         * of code is root. We will then issue the LDAP request
         * with the DN of the user we want to change the passwd of.
         */

        /*
         * create a dn for the user who is executing this code
         */
        uid = getuid();
        if (uid == 0) {
                if ((pw = getpwnam_from(name, rep, REP_LDAP)) == NULL) {
                        res = NS_LDAP_OP_FAILED;
                        goto out;
                }
        } else if ((pw = getpwuid_from(uid, rep, REP_LDAP)) == NULL) {
                /*
                 * User executing this code is not known to the LDAP
                 * server. This operation is to be denied
                 */
                res = NS_LDAP_OP_FAILED;
                goto out;
        }

        res = __ns_ldap_uid2dn(pw->pw_name, &binddn, NULL, &errorp);
        if (res != NS_LDAP_SUCCESS)
                goto out;

        if (pattrs && pattrs[0] != NULL) {
                res = ldap_replaceattr(dn, pattrs, binddn, oldpw,
                    &pwd_status, 0);
        } else
                res = NS_LDAP_OP_FAILED;

out:
        free_ldapbuf(ldapbuf);
        free(dn);

        return (ldap_to_pwu_code(res, pwd_status));
}