/*
* 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 (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved.
*/
#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, 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 authorized user isn't prompted.
*/
*privileged = 0; /* for proper prompting */
if (priv) {
if (repos_authorized()) {
*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 authorized user
* we want 'user' to be authenticated.
*/
if (repos_authorized()) {
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);
if ((strncmp(spw->sp_pwdp, LOCKSTRING,
sizeof (LOCKSTRING)-1) == 0)) {
/* append new password hash */
cryptlen = sizeof (LOCKSTRING)-1 +
strlen(pwd) + sizeof ("{crypt}");
val = malloc(cryptlen);
if (val == NULL) {
return (PWU_NOMEM);
}
(void) snprintf(val, cryptlen,
"{crypt}%s%s",
LOCKSTRING, pwd);
} else {
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);
setchg_time(spw);
if (attr_addmod(sattrs, &sidx, _S_LASTCHANGE,
spw->sp_lstchg) < 0)
return (PWU_NOMEM);
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:
case ATTR_LOCK_FAILED_LOGINS:
if (!ldapbuf->shadow_update_enabled)
break; /* not managing passwordAccount */
if (p->type == ATTR_LOCK_FAILED_LOGINS &&
strcmp(spw->sp_pwdp, NOLOGINSTRING) == 0) {
return (PWU_CHANGE_NOT_ALLOWED);
}
if (spw->sp_pwdp == NULL) {
spw->sp_pwdp = LOCKSTRING;
} else if ((strncmp(spw->sp_pwdp, LOCKSTRING,
sizeof (LOCKSTRING)-1)) != 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);
} else {
return (PWU_NO_CHANGE);
}
setchg_time(spw);
if (attr_addmod(sattrs, &sidx, _S_LASTCHANGE,
spw->sp_lstchg) < 0)
return (PWU_NOMEM);
if (attr_addmod(sattrs, &sidx, _S_FLAG,
spw->sp_flag) < 0)
return (PWU_NOMEM);
break;
case ATTR_UNLOCK_ACCOUNT:
if (!ldapbuf->shadow_update_enabled)
break; /* not managing passwordAccount */
if (spw->sp_pwdp != NULL &&
strncmp(spw->sp_pwdp, LOCKSTRING,
sizeof (LOCKSTRING)-1) == 0) {
len = (sizeof ("{crypt}") -
sizeof (LOCKSTRING)) +
strlen(spw->sp_pwdp) + 1;
if (len == sizeof ("{crypt}")) {
free(spw->sp_pwdp);
return (PWU_NO_CHANGE);
}
pwd = malloc(len);
if (pwd == NULL) {
free(spw->sp_pwdp);
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);
setchg_time(spw);
if (attr_addmod(sattrs, &sidx, _S_FLAG,
spw->sp_flag) < 0) {
free(spw->sp_pwdp);
return (PWU_NOMEM);
}
if (attr_addmod(sattrs, &sidx, _S_LASTCHANGE,
spw->sp_lstchg) < 0) {
free(spw->sp_pwdp);
return (PWU_NOMEM);
}
}
break;
case ATTR_NOLOGIN_ACCOUNT:
if (!ldapbuf->shadow_update_enabled)
break; /* not managing passwordAccount */
if (strcmp(spw->sp_pwdp, NOLOGINSTRING) == 0) {
free(spw->sp_pwdp);
return (PWU_NO_CHANGE);
}
/* Locked accounts stay locked on transition to NP */
if ((strncmp(spw->sp_pwdp, LOCKSTRING,
sizeof (LOCKSTRING) - 1) == 0)) {
STRDUP_OR_RET(spw->sp_pwdp,
"{crypt}" LOCKSTRING NOLOGINSTRING);
} else {
STRDUP_OR_RET(spw->sp_pwdp,
"{crypt}" NOLOGINSTRING);
}
NEW_ATTR(sattrs, sidx, _PWD_USERPASSWORD, spw->sp_pwdp);
setchg_time(spw);
if (attr_addmod(sattrs, &sidx, _S_LASTCHANGE,
spw->sp_lstchg) < 0) {
free(spw->sp_pwdp);
return (PWU_NOMEM);
}
if (attr_addmod(sattrs, &sidx, _S_FLAG,
spw->sp_flag) < 0) {
free(spw->sp_pwdp);
return (PWU_NOMEM);
}
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);
NUM_TO_STR(val, (spw->sp_flag & ~TIME_MASK));
NEW_ATTR(sattrs, sidx, _S_FLAG, 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);
NUM_TO_STR(val, (spw->sp_flag & ~TIME_MASK));
NEW_ATTR(sattrs, sidx, _S_FLAG, 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) {
setchg_time(spw);
if (attr_addmod(sattrs, &sidx,
_S_LASTCHANGE, spw->sp_lstchg) < 0)
return (PWU_NOMEM);
if (attr_addmod(sattrs, &sidx,
_S_FLAG, spw->sp_flag) < 0)
return (PWU_NOMEM);
}
}
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:
/* __set_authtoken_attr() blocks this */
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 */
}
if (strcmp(spw->sp_pwdp, NOLOGINSTRING) == 0) {
return (PWU_NO_CHANGE);
}
count = (spw->sp_flag & FAILCOUNT_MASK) + 1;
spw->sp_flag &= ~FAILCOUNT_MASK;
spw->sp_flag |= min(FAILCOUNT_MASK, count);
p->data.val_i = count;
setchg_time(spw);
NUM_TO_STR(val, spw->sp_flag);
NEW_ATTR(sattrs, sidx, _S_FLAG, val);
NUM_TO_STR(val, spw->sp_lstchg);
NEW_ATTR(sattrs, sidx, _S_LASTCHANGE, 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;
setchg_time(spw);
NUM_TO_STR(val, spw->sp_flag);
NEW_ATTR(sattrs, sidx, _S_FLAG, val);
NUM_TO_STR(val, spw->sp_lstchg);
NEW_ATTR(sattrs, sidx, _S_LASTCHANGE, 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(char *name, 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));
}