ldap_attr.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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 <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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"
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));
}