/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include "ns_sldap.h" #include #include #include #include #include #include "passwdutil.h" #include "utils.h" 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 **attrs; } 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" /* * int ldap_user_to_authenticate(user, rep, auth_user, privileged) * * 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(); if (uid == pw->pw_uid) { /* changing out own, not privileged */ *privileged = 0; if ((*auth_user = strdup(user)) == NULL) res = PWU_NOMEM; } 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) { if ((*auth_user = strdup(pwr.pw_name)) == NULL) res = PWU_NOMEM; } else { /* hmm. can't find name of current user...??? */ #define MAX_UID_LEN 11 /* UID's larger than 2^32 won't fit... */ if ((*auth_user = malloc(MAX_UID_LEN)) == NULL) { res = PWU_NOMEM; } else { (void) snprintf(*auth_user, MAX_UID_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) { int res; struct passwd *pw = NULL; struct spwd *spw = NULL; attrlist *w; int need_shadow = 0; /* Need shadow info from LDAP server */ int need_normal = 0; /* Need non-shadow info from LDAP server */ /* We need the "shadow" map for the password only */ for (w = items; w != NULL; w = w->next) { if (w->type == ATTR_PASSWD || w->type == ATTR_PASSWD_SERVER_POLICY) need_shadow = 1; else need_normal = 1; } if (need_normal) { res = dup_pw(&pw, getpwnam_from(name, rep, REP_LDAP)); if (res != PWU_SUCCESS) goto out; } if (need_shadow) { res = dup_spw(&spw, getspnam_from(name, rep, REP_LDAP)); if (res != PWU_SUCCESS) { goto out; } } for (w = items; res == PWU_SUCCESS && w != NULL; w = w->next) { switch (w->type) { case ATTR_NAME: if ((w->data.val_s = strdup(pw->pw_name)) == NULL) res = PWU_NOMEM; break; case ATTR_COMMENT: if ((w->data.val_s = strdup(pw->pw_comment)) == NULL) res = PWU_NOMEM; break; case ATTR_GECOS: if ((w->data.val_s = strdup(pw->pw_gecos)) == NULL) res = PWU_NOMEM; break; case ATTR_HOMEDIR: if ((w->data.val_s = strdup(pw->pw_dir)) == NULL) res = PWU_NOMEM; break; case ATTR_SHELL: if ((w->data.val_s = strdup(pw->pw_shell)) == NULL) res = PWU_NOMEM; break; case ATTR_PASSWD: case ATTR_PASSWD_SERVER_POLICY: if ((w->data.val_s = strdup(spw->sp_pwdp)) == NULL) res = PWU_NOMEM; break; case ATTR_AGE: if ((w->data.val_s = strdup(pw->pw_age)) == NULL) res = PWU_NOMEM; break; case ATTR_REP_NAME: if ((w->data.val_s = strdup("ldap")) == NULL) res = PWU_NOMEM; 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: w->data.val_i = -1; break; case ATTR_MIN: w->data.val_i = -1; break; case ATTR_MAX: w->data.val_i = -1; break; case ATTR_WARN: w->data.val_i = -1; break; case ATTR_INACT: w->data.val_i = -1; break; case ATTR_EXPIRE: w->data.val_i = -1; break; case ATTR_FLAG: break; default: break; } } out: if (pw) free_pwd(pw); if (spw) free_spwd(spw); 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) { attrlist *p; int nr_items; int need_pwd = 0; ldapbuf_t *ldapbuf; int res; for (nr_items = 0, p = items; p != NULL; p = p->next) { nr_items++; if (p->type == ATTR_PASSWD || p->type == ATTR_PASSWD_SERVER_POLICY) need_pwd = 1; } ldapbuf = calloc(1, sizeof (ldapbuf_t)); if (ldapbuf == NULL) return (PWU_NOMEM); ldapbuf->attrs = calloc(nr_items, sizeof (ns_ldap_attr_t *)); if (ldapbuf->attrs == NULL) return (PWU_NOMEM); if (need_pwd) { struct spwd *spw; res = dup_pw(&ldapbuf->pwd, getpwnam_from(name, rep, REP_LDAP)); if (res != PWU_SUCCESS) return (res); spw = getspnam_from(name, rep, REP_LDAP); if (spw) { ldapbuf->passwd = strdup(spw->sp_pwdp); if (ldapbuf->passwd == NULL) return (PWU_NOMEM); } } *buf = ldapbuf; return (0); } /* * 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); } /* * 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; int idx = 0; ldapbuf_t *ldapbuf = (ldapbuf_t *)buf; ns_ldap_attr_t **attrs = ldapbuf->attrs; char *pwd, *val; char *salt; size_t cryptlen; for (p = items; p != NULL; p = p->next) { switch (p->type) { case ATTR_PASSWD: salt = crypt_gensalt(ldapbuf->passwd, ldapbuf->pwd); if (salt == NULL) { if (errno == ENOMEM) return (PWU_NOMEM); else { /* 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); attrs[idx] = new_attr(_PWD_USERPASSWORD, val); break; /* * 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 */ case ATTR_PASSWD_SERVER_POLICY: val = strdup(p->data.val_s); if (val == NULL) return (PWU_NOMEM); attrs[idx] = new_attr(_PWD_USERPASSWORD, val); break; case ATTR_COMMENT: /* XX correct? */ attrs[idx] = new_attr(_PWD_DESCRIPTION, p->data.val_s); break; case ATTR_GECOS: attrs[idx] = new_attr(_PWD_GECOS, p->data.val_s); break; case ATTR_HOMEDIR: attrs[idx] = new_attr(_PWD_HOMEDIRECTORY, p->data.val_s); break; case ATTR_SHELL: attrs[idx] = new_attr(_PWD_LOGINSHELL, p->data.val_s); break; /* Unsupported items are below this line */ case ATTR_NAME: case ATTR_UID: case ATTR_GID: case ATTR_AGE: case ATTR_LSTCHG: case ATTR_MIN: case ATTR_MAX: case ATTR_WARN: case ATTR_INACT: case ATTR_EXPIRE: case ATTR_FLAG: break; default: break; } if (attrs[idx] == NULL) return (PWU_NOMEM); idx++; } attrs[idx] = NULL; return (PWU_SUCCESS); } /* * 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 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 (PWU_NOMEM); /* 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, 0, &errorp); if (ldaprc == NS_LDAP_SUCCESS) { result = NS_LDAP_SUCCESS; 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 = 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 **attrs = ldapbuf->attrs; struct passwd *pw; int pwd_status; uid_t uid; if (strcmp(name, "root") == 0) return (PWU_NOT_FOUND); /* * 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. */ /* * 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; /* * 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; res = ldap_replaceattr(dn, attrs, binddn, oldpw, &pwd_status); out: while (*attrs) { free((*attrs)->attrvalue[0]); free(*attrs); attrs++; } if (ldapbuf->passwd) { (void) memset(ldapbuf->passwd, 0, strlen(ldapbuf->passwd)); free(ldapbuf->passwd); } if (ldapbuf->pwd) free_pwd(ldapbuf->pwd); free(dn); return (ldap_to_pwu_code(res, pwd_status)); }