/* * 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)); }