/*
 * 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.
 */

#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(char *name, attrlist *item, pwu_repository_t *rep);
int ldap_getpwnam(char *name, attrlist *items, pwu_repository_t *rep,
    void **buf);
int ldap_update(attrlist *items, pwu_repository_t *rep, void *buf);
int ldap_putpwnam(char *name, char *oldpw, char *dummy,
	pwu_repository_t *rep, void *buf);
int ldap_user_to_authenticate(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(4) 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 he/she is attempting to change another user's
 * password attributes.
 */
int
ldap_user_to_authenticate(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(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;
		}
	}

out:
	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(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 [s]he logs 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, dummy, rep, buf)
 *
 * update the LDAP server with the attributes contained in 'buf'.
 * The dummy parameter is a placeholder for NIS+ where the old
 * RPC password is passwd.
 */
/*ARGSUSED*/
int
ldap_putpwnam(char *name, char *oldpw, char *dummy,
	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));
}