ldap_op.c revision a87701e9837f8a9ee9e4c4d3186295c0e29f743f
/*
* 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 2015 Gary Mills
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <synch.h>
#include <strings.h>
#include <sys/time.h>
#include <ctype.h>
#include "ldap_op.h"
#include "ldap_util.h"
#include "ldap_structs.h"
#include "ldap_ruleval.h"
#include "ldap_attr.h"
#include "ldap_print.h"
#include "ldap_glob.h"
#include "nis_parse_ldap_conf.h"
#ifndef LDAPS_PORT
#define LDAPS_PORT 636
#endif
static int setupConList(char *serverList, char *who,
char *cred, auth_method_t method);
/*
* Build one of our internal LDAP search structures, containing copies of
* the supplied input. return NULL in case of error.
*
* If 'filter' is NULL, build an AND-filter using the filter components.
*/
__nis_ldap_search_t *
buildLdapSearch(char *base, int scope, int numFilterComps, char **filterComp,
char *filter, char **attrs, int attrsonly, int isDN) {
__nis_ldap_search_t *ls;
char **a;
int i, na, err = 0;
char *myself = "buildLdapSearch";
ls = am(myself, sizeof (*ls));
if (ls == 0)
return (0);
ls->base = sdup(myself, T, base);
if (ls->base == 0 && base != 0)
err++;
ls->scope = scope;
if (filterComp != 0 && numFilterComps > 0) {
ls->filterComp = am(myself, numFilterComps *
sizeof (ls->filterComp[0]));
if (ls->filterComp == 0) {
err++;
numFilterComps = 0;
}
for (i = 0; i < numFilterComps; i++) {
ls->filterComp[i] = sdup(myself, T, filterComp[i]);
if (ls->filterComp[i] == 0 && filterComp[i] != 0)
err++;
}
ls->numFilterComps = numFilterComps;
if (filter == 0) {
ls->filter = concatenateFilterComps(ls->numFilterComps,
ls->filterComp);
if (ls->filter == 0)
err++;
}
} else {
ls->filterComp = 0;
ls->numFilterComps = 0;
ls->filter = sdup(myself, T, filter);
if (ls->filter == 0 && filter != 0)
err++;
}
if (attrs != 0) {
for (na = 0, a = attrs; *a != 0; a++, na++);
ls->attrs = am(myself, (na + 1) * sizeof (ls->attrs[0]));
if (ls->attrs != 0) {
for (i = 0; i < na; i++) {
ls->attrs[i] = sdup(myself, T, attrs[i]);
if (ls->attrs[i] == 0 && attrs[i] != 0)
err++;
}
ls->attrs[na] = 0;
ls->numAttrs = na;
} else {
err++;
}
} else {
ls->attrs = 0;
ls->numAttrs = 0;
}
ls->attrsonly = attrsonly;
ls->isDN = isDN;
if (err > 0) {
freeLdapSearch(ls);
ls = 0;
}
return (ls);
}
void
freeLdapSearch(__nis_ldap_search_t *ls) {
int i;
if (ls == 0)
return;
sfree(ls->base);
if (ls->filterComp != 0) {
for (i = 0; i < ls->numFilterComps; i++) {
sfree(ls->filterComp[i]);
}
sfree(ls->filterComp);
}
sfree(ls->filter);
if (ls->attrs != 0) {
for (i = 0; i < ls->numAttrs; i++) {
sfree(ls->attrs[i]);
}
sfree(ls->attrs);
}
free(ls);
}
/*
* Given a table mapping, and a rule/value pointer,
* return an LDAP search structure with values suitable for use
* by ldap_search() or (if dn != 0) ldap_modify(). The rule/value
* may be modified.
*
* If dn != 0 and *dn == 0, the function attemps to return a pointer
* to the DN. This may necessitate an ldapSearch, if the rule set doesn't
* produce a DN directly.
*
* if dn == 0, and the rule set produces a DN as well as other attribute/
* value pairs, the function returns an LDAP search structure with the
* DN only.
*
* If 'fromLDAP' is set, the caller wants base/scope/filter from
* t->objectDN->read; otherwise, from t->objectDN->write.
*
* If 'rv' is NULL, the caller wants an enumeration of the container.
*
* Note that this function only creates a search structure for 't' itself;
* if there are alternative mappings for the table, those must be handled
* by our caller.
*/
__nis_ldap_search_t *
createLdapRequest(__nis_table_mapping_t *t,
__nis_rule_value_t *rv, char **dn, int fromLDAP,
int *res, __nis_object_dn_t *obj_dn) {
int i, j;
__nis_ldap_search_t *ls = 0;
char **locDN;
int numLocDN, stat = 0, count = 0;
char *myself = "createLdapRequest";
__nis_object_dn_t *objectDN = NULL;
if (t == 0)
return (0);
if (obj_dn == NULL)
objectDN = t->objectDN;
else
objectDN = obj_dn;
if (rv == 0) {
char *base;
char *filter;
if (fromLDAP) {
base = objectDN->read.base;
filter = makeFilter(objectDN->read.attrs);
} else {
base = objectDN->write.base;
filter = makeFilter(objectDN->write.attrs);
}
/* Create request to enumerate container */
ls = buildLdapSearch(base, objectDN->read.scope, 0, 0, filter,
0, 0, 0);
sfree(filter);
return (ls);
}
for (i = 0; i < t->numRulesToLDAP; i++) {
rv = addLdapRuleValue(t, t->ruleToLDAP[i],
mit_ldap, mit_nisplus, rv, !fromLDAP, &stat);
if (rv == 0)
return (0);
if (stat == NP_LDAP_RULES_NO_VALUE)
count++;
stat = 0;
}
/*
* If none of the rules produced a value despite
* having enough NIS+ columns, return error.
*/
if (rv->numAttrs == 0 && count > 0) {
*res = NP_LDAP_RULES_NO_VALUE;
return (0);
}
/*
* 'rv' now contains everything we know about the attributes and
* values. Build an LDAP search structure from it.
*/
/* Look for a single-valued DN */
locDN = findDNs(myself, rv, 1,
fromLDAP ? objectDN->read.base :
objectDN->write.base,
&numLocDN);
if (locDN != 0 && numLocDN == 1) {
if (dn != 0 && *dn == 0) {
*dn = locDN[0];
sfree(locDN);
} else {
char *filter;
if (fromLDAP)
filter = makeFilter(objectDN->read.attrs);
else
filter = makeFilter(objectDN->write.attrs);
ls = buildLdapSearch(locDN[0], LDAP_SCOPE_BASE, 0, 0,
filter, 0, 0, 1);
sfree(filter);
freeDNs(locDN, numLocDN);
}
} else {
freeDNs(locDN, numLocDN);
}
if (ls != 0) {
ls->useCon = 1;
return (ls);
}
/*
* No DN, or caller wanted a search structure with the non-DN
* attributes.
*/
/* Initialize search structure */
{
char *filter = (fromLDAP) ?
makeFilter(objectDN->read.attrs) :
makeFilter(objectDN->write.attrs);
char **ofc;
int nofc = 0;
ofc = makeFilterComp(filter, &nofc);
if (filter != 0 && ofc == 0) {
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: Unable to break filter into components: \"%s\"",
myself, NIL(filter));
sfree(filter);
return (0);
}
if (fromLDAP)
ls = buildLdapSearch(objectDN->read.base,
objectDN->read.scope,
nofc, ofc, 0, 0, 0, 0);
else
ls = buildLdapSearch(objectDN->write.base,
objectDN->write.scope,
nofc, ofc, 0, 0, 0, 0);
sfree(filter);
freeFilterComp(ofc, nofc);
if (ls == 0)
return (0);
}
/* Build and add the filter components */
for (i = 0; i < rv->numAttrs; i++) {
/* Skip DN */
if (strcasecmp("dn", rv->attrName[i]) == 0)
continue;
/* Skip vt_ber values */
if (rv->attrVal[i].type == vt_ber)
continue;
for (j = 0; j < rv->attrVal[i].numVals; j++) {
__nis_buffer_t b = {0, 0};
char **tmpComp;
bp2buf(myself, &b, "%s=%s",
rv->attrName[i], rv->attrVal[i].val[j].value);
tmpComp = addFilterComp(b.buf, ls->filterComp,
&ls->numFilterComps);
if (tmpComp == 0) {
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: Unable to add filter component \"%s\"",
myself, NIL(b.buf));
sfree(b.buf);
freeLdapSearch(ls);
return (0);
}
ls->filterComp = tmpComp;
sfree(b.buf);
}
}
if (ls->numFilterComps > 0) {
sfree(ls->filter);
ls->filter = concatenateFilterComps(ls->numFilterComps,
ls->filterComp);
if (ls->filter == 0) {
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: Unable to concatenate filter components",
myself);
freeLdapSearch(ls);
return (0);
}
}
if (dn != 0 && *dn == 0) {
/*
* The caller wants a DN, but we didn't get one from the
* the rule set. We have an 'ls', so use it to ldapSearch()
* for an entry from which we can extract the DN.
*/
__nis_rule_value_t *rvtmp;
char **locDN;
int nv = 0, numLocDN;
rvtmp = ldapSearch(ls, &nv, 0, 0);
locDN = findDNs(myself, rvtmp, nv, 0, &numLocDN);
if (locDN != 0 && numLocDN == 1) {
*dn = locDN[0];
sfree(locDN);
} else {
freeDNs(locDN, numLocDN);
}
freeRuleValue(rvtmp, nv);
}
ls->useCon = 1;
return (ls);
}
int ldapConnAttemptRetryTimeout = 60; /* seconds */
typedef struct {
LDAP *ld;
mutex_t mutex; /* Mutex for update of structure */
pthread_t owner; /* Thread holding mutex */
mutex_t rcMutex; /* Mutex for refCount */
int refCount; /* Reference count */
int isBound; /* Is connection open and usable ? */
time_t retryTime; /* When should open be retried */
int status; /* Status of last operation */
int doDis; /* To be disconnected if refCount==0 */
int doDel; /* To be deleted if refCount zero */
int onList; /* True if on the 'ldapCon' list */
char *sp; /* server string */
char *who;
char *cred;
auth_method_t method;
int port;
struct timeval bindTimeout;
struct timeval searchTimeout;
struct timeval modifyTimeout;
struct timeval addTimeout;
struct timeval deleteTimeout;
int simplePage; /* Can do simple-page */
int vlv; /* Can do VLV */
uint_t batchFrom; /* # entries read in one operation */
void *next;
} __nis_ldap_conn_t;
/*
* List of connections, 'ldapCon', protected by an RW lock.
*
* The following locking scheme is used:
*
* (1) Find a connection structure to use to talk to LDAP
* Rlock list
* Locate structure
* Acquire 'mutex'
* Acquire 'rcMutex'
* update refCount
* Release 'rcMutex'
* release 'mutex'
* Unlock list
* Use structure
* Release structure when done
* (2) Insert/delete structure(s) on/from list
* Wlock list
* Insert/delete structure; if deleting, must
* acquire 'mutex', and 'rcMutex' (in that order),
* and 'refCount' must be zero.
* Unlock list
* (3) Modify structure
* Find structure
* Acquire 'mutex'
* Modify (except refCount)
* Release 'mutex'
* Release structure
*/
__nis_ldap_conn_t *ldapCon = 0;
__nis_ldap_conn_t *ldapReferralCon = 0;
static rwlock_t ldapConLock = DEFAULTRWLOCK;
static rwlock_t referralConLock = DEFAULTRWLOCK;
void
exclusiveLC(__nis_ldap_conn_t *lc) {
pthread_t me = pthread_self();
int stat;
if (lc == 0)
return;
stat = mutex_trylock(&lc->mutex);
if (stat == EBUSY && lc->owner != me)
mutex_lock(&lc->mutex);
lc->owner = me;
}
/* Return 1 if mutex held by this thread, 0 otherwise */
int
assertExclusive(__nis_ldap_conn_t *lc) {
pthread_t me;
int stat;
if (lc == 0)
return (0);
stat = mutex_trylock(&lc->mutex);
if (stat == 0) {
mutex_unlock(&lc->mutex);
return (0);
}
me = pthread_self();
if (stat != EBUSY || lc->owner != me)
return (0);
return (1);
}
void
releaseLC(__nis_ldap_conn_t *lc) {
pthread_t me = pthread_self();
if (lc == 0 || lc->owner != me)
return;
lc->owner = 0;
(void) mutex_unlock(&lc->mutex);
}
void
incrementRC(__nis_ldap_conn_t *lc) {
if (lc == 0)
return;
(void) mutex_lock(&lc->rcMutex);
lc->refCount++;
(void) mutex_unlock(&lc->rcMutex);
}
void
decrementRC(__nis_ldap_conn_t *lc) {
if (lc == 0)
return;
(void) mutex_lock(&lc->rcMutex);
if (lc->refCount > 0)
lc->refCount--;
(void) mutex_unlock(&lc->rcMutex);
}
/* Accept a server/port indication, and call ldap_init() */
static LDAP *
ldapInit(char *srv, int port, bool_t use_ssl) {
LDAP *ld;
int ldapVersion = LDAP_VERSION3;
int derefOption = LDAP_DEREF_ALWAYS;
int timelimit = proxyInfo.search_time_limit;
int sizelimit = proxyInfo.search_size_limit;
if (srv == 0)
return (0);
if (use_ssl) {
ld = ldapssl_init(srv, port, 1);
} else {
ld = ldap_init(srv, port);
}
if (ld != 0) {
(void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION,
&ldapVersion);
(void) ldap_set_option(ld, LDAP_OPT_DEREF, &derefOption);
(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
(void) ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &timelimit);
(void) ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &sizelimit);
(void) ldap_set_option(ld, LDAP_OPT_REBIND_ARG, 0);
}
return (ld);
}
/*
* Bind the specified LDAP structure per the supplied authentication.
* Note: tested with none, simple, and digest_md5. May or may not
* work with other authentication methods, mostly depending on whether
* or not 'who' and 'cred' contain sufficient information.
*/
static int
ldapBind(LDAP **ldP, char *who, char *cred, auth_method_t method,
struct timeval timeout) {
int ret;
LDAP *ld;
char *myself = "ldapBind";
if (ldP == 0 || (ld = *ldP) == 0)
return (LDAP_PARAM_ERROR);
if (method == none) {
/* No ldap_bind() required (or even possible) */
ret = LDAP_SUCCESS;
} else if (method == simple) {
struct timeval tv;
LDAPMessage *msg = 0;
tv = timeout;
ret = ldap_bind(ld, who, cred, LDAP_AUTH_SIMPLE);
if (ret != -1) {
ret = ldap_result(ld, ret, 0, &tv, &msg);
if (ret == 0) {
ret = LDAP_TIMEOUT;
} else if (ret == -1) {
(void) ldap_get_option(ld,
LDAP_OPT_ERROR_NUMBER,
&ret);
} else {
ret = ldap_result2error(ld, msg, 0);
}
if (msg != 0)
(void) ldap_msgfree(msg);
} else {
(void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER,
&ret);
}
} else if (method == cram_md5) {
/* Note: there is only a synchronous call for cram-md5 */
struct berval ber_cred;
ber_cred.bv_len = strlen(cred);
ber_cred.bv_val = cred;
ret = ldap_sasl_cram_md5_bind_s(ld, who, &ber_cred, NULL, NULL);
} else if (method == digest_md5) {
/* Note: there is only a synchronous call for digest-md5 */
struct berval ber_cred;
ber_cred.bv_len = strlen(cred);
ber_cred.bv_val = cred;
ret = ldap_x_sasl_digest_md5_bind_s(ld, who, &ber_cred, NULL,
NULL);
} else {
ret = LDAP_AUTH_METHOD_NOT_SUPPORTED;
}
if (ret != LDAP_SUCCESS) {
(void) ldap_unbind_s(ld);
*ldP = 0;
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Unable to bind as: %s: %s",
myself, who, ldap_err2string(ret));
}
return (ret);
}
/*
* Free 'lc' and all related memory. Caller must hold the exclusive lock.
* Return LDAP_UNAVAILABLE upon success, in which case the caller mustn't
* try to use the structure pointer in any way.
*/
static int
freeCon(__nis_ldap_conn_t *lc) {
if (!assertExclusive(lc))
return (LDAP_PARAM_ERROR);
incrementRC(lc);
/* Must be unused, unbound, and not on the 'ldapCon' list */
if (lc->onList || lc->refCount != 1 || lc->isBound) {
lc->doDel++;
decrementRC(lc);
return (LDAP_BUSY);
}
sfree(lc->sp);
sfree(lc->who);
sfree(lc->cred);
/* Delete structure with both mutex:es held */
free(lc);
return (LDAP_UNAVAILABLE);
}
/*
* Disconnect the specified LDAP connection. Caller must have acquired 'mutex'.
*
* On return, if the status is LDAP_UNAVAILABLE, the caller must not touch
* the structure in any way.
*/
static int
disconnectCon(__nis_ldap_conn_t *lc) {
int stat;
char *myself = "disconnectCon";
if (lc == 0)
return (LDAP_SUCCESS);
if (!assertExclusive(lc))
return (LDAP_UNAVAILABLE);
if (lc->doDis) {
/* Increment refCount to protect against interference */
incrementRC(lc);
/* refCount must be one (i.e., just us) */
if (lc->refCount != 1) {
/*
* In use; already marked for disconnect,
* so do nothing.
*/
decrementRC(lc);
return (LDAP_BUSY);
}
stat = ldap_unbind_s(lc->ld);
if (stat == LDAP_SUCCESS) {
lc->ld = 0;
lc->isBound = 0;
lc->doDis = 0;
/* Reset simple page and vlv indication */
lc->simplePage = 0;
lc->vlv = 0;
} else if (verbose) {
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: ldap_unbind_s() => %d (%s)",
myself, stat, ldap_err2string(stat));
}
decrementRC(lc);
}
if (lc->doDel) {
if (LDAP_UNAVAILABLE == freeCon(lc))
stat = LDAP_UNAVAILABLE;
}
return (stat);
}
/*
* controlSupported will determine for a given connection whether a set
* of controls is supported or not. The input parameters:
* lc The connection
* ctrl A an array of OID strings, the terminal string should be NULL
* The returned values if LDAP_SUCCESS is returned:
* supported A caller supplied array which will be set to TRUE or
* FALSE depending on whether the corresponding control
* is reported as supported.
* Returns LDAP_SUCCESS if the supportedControl attribute is read.
*/
static int
controlSupported(__nis_ldap_conn_t *lc, char **ctrl, bool_t *supported) {
LDAPMessage *res, *e;
char *attr[2], *a, **val;
int stat, i;
BerElement *ber = 0;
char *myself = "controlSupported";
attr[0] = "supportedControl";
attr[1] = 0;
stat = ldap_search_st(lc->ld, "", LDAP_SCOPE_BASE, "(objectclass=*)",
attr, 0, &lc->searchTimeout, &res);
if (stat != LDAP_SUCCESS) {
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Unable to retrieve supported control information for %s: %s",
myself, NIL(lc->sp), ldap_err2string(stat));
return (stat);
}
e = ldap_first_entry(lc->ld, res);
if (e != 0) {
a = ldap_first_attribute(lc->ld, e, &ber);
if (a != 0) {
val = ldap_get_values(lc->ld, e, a);
if (val == 0) {
ldap_memfree(a);
if (ber != 0)
ber_free(ber, 0);
}
}
}
if (e == 0 || a == 0 || val == 0) {
ldap_msgfree(res);
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: Unable to get root DSE for %s",
myself, NIL(lc->sp));
return (LDAP_OPERATIONS_ERROR);
}
while (*ctrl != NULL) {
*supported = FALSE;
for (i = 0; val[i] != 0; i++) {
if (strstr(val[i], *ctrl) != 0) {
*supported = TRUE;
break;
}
}
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: %s: %s: %s",
myself, NIL(lc->sp), NIL(*ctrl),
*supported ? "enabled" : "disabled");
ctrl++;
supported++;
}
ldap_value_free(val);
ldap_memfree(a);
if (ber != 0)
ber_free(ber, 0);
ldap_msgfree(res);
return (stat);
}
/*
* Connect the LDAP connection 'lc'. Caller must have acquired the 'mutex',
* and the refCount must be zero.
*
* On return, if the status is LDAP_UNAVAILABLE, the caller must not touch
* the structure in any way.
*/
static int
connectCon(__nis_ldap_conn_t *lc, int check_ctrl) {
struct timeval tp;
int stat;
bool_t supported[2] = {FALSE, FALSE};
char *ctrl[3] = {LDAP_CONTROL_SIMPLE_PAGE,
LDAP_CONTROL_VLVREQUEST,
NULL};
if (lc == 0)
return (LDAP_SUCCESS);
if (!assertExclusive(lc))
return (LDAP_PARAM_ERROR);
incrementRC(lc);
if (lc->refCount != 1) {
/*
* Don't want to step on structure when it's used by someone
* else.
*/
decrementRC(lc);
return (LDAP_BUSY);
}
(void) gettimeofday(&tp, 0);
if (lc->ld != 0) {
/* Try to disconnect */
lc->doDis++;
decrementRC(lc);
/* disconnctCon() will do the delete if required */
stat = disconnectCon(lc);
if (stat != LDAP_SUCCESS)
return (stat);
incrementRC(lc);
if (lc->refCount != 1 || lc->ld != 0) {
decrementRC(lc);
return (lc->ld != 0) ? LDAP_SUCCESS :
LDAP_BUSY;
}
} else if (tp.tv_sec < lc->retryTime) {
/* Too early to retry connect */
decrementRC(lc);
return (LDAP_SERVER_DOWN);
}
/* Set new retry time in case we fail below */
lc->retryTime = tp.tv_sec + ldapConnAttemptRetryTimeout;
lc->ld = ldapInit(lc->sp, lc->port, proxyInfo.tls_method != no_tls);
if (lc->ld == 0) {
decrementRC(lc);
return (LDAP_LOCAL_ERROR);
}
stat = lc->status = ldapBind(&lc->ld, lc->who, lc->cred, lc->method,
lc->bindTimeout);
if (lc->status == LDAP_SUCCESS) {
lc->isBound = 1;
lc->retryTime = 0;
if (check_ctrl) {
(void) controlSupported(lc, ctrl, supported);
lc->simplePage = supported[0];
lc->vlv = supported[1];
lc->batchFrom = 50000;
}
}
decrementRC(lc);
return (stat);
}
/*
* Find and return a connection believed to be OK.
*/
static __nis_ldap_conn_t *
findCon(int *stat) {
__nis_ldap_conn_t *lc;
int ldapStat;
char *myself = "findCon";
if (stat == 0)
stat = &ldapStat;
(void) rw_rdlock(&ldapConLock);
if (ldapCon == 0) {
/* Probably first call; try to set up the connection list */
(void) rw_unlock(&ldapConLock);
if ((*stat = setupConList(proxyInfo.default_servers,
proxyInfo.proxy_dn,
proxyInfo.proxy_passwd,
proxyInfo.auth_method)) !=
LDAP_SUCCESS)
return (0);
(void) rw_rdlock(&ldapConLock);
}
for (lc = ldapCon; lc != 0; lc = lc->next) {
exclusiveLC(lc);
if (!lc->isBound) {
*stat = connectCon(lc, 1);
if (*stat != LDAP_SUCCESS) {
if (*stat != LDAP_UNAVAILABLE) {
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Cannot open connection to LDAP server (%s): %s",
myself, NIL(lc->sp),
ldap_err2string(*stat));
releaseLC(lc);
}
continue;
}
} else if (lc->doDis || lc->doDel) {
*stat = disconnectCon(lc);
if (*stat != LDAP_UNAVAILABLE)
releaseLC(lc);
continue;
}
incrementRC(lc);
releaseLC(lc);
break;
}
(void) rw_unlock(&ldapConLock);
return (lc);
}
/* Release connection; decrements ref count for the connection */
static void
releaseCon(__nis_ldap_conn_t *lc, int status) {
int stat;
if (lc == 0)
return;
exclusiveLC(lc);
lc->status = status;
decrementRC(lc);
if (lc->doDis)
stat = disconnectCon(lc);
else
stat = LDAP_SUCCESS;
if (stat != LDAP_UNAVAILABLE)
releaseLC(lc);
}
static __nis_ldap_conn_t *
createCon(char *sp, char *who, char *cred, auth_method_t method, int port) {
__nis_ldap_conn_t *lc;
char *myself = "createCon";
char *r;
if (sp == 0)
return (0);
lc = am(myself, sizeof (*lc));
if (lc == 0)
return (0);
(void) mutex_init(&lc->mutex, 0, 0);
(void) mutex_init(&lc->rcMutex, 0, 0);
/* If we need to delete 'lc', freeCon() wants the mutex held */
exclusiveLC(lc);
lc->sp = sdup(myself, T, sp);
if (lc->sp == 0) {
(void) freeCon(lc);
return (0);
}
if ((r = strchr(lc->sp, ']')) != 0) {
/*
* IPv6 address. Does libldap want this with the
* '[' and ']' left in place ? Assume so for now.
*/
r = strchr(r, ':');
} else {
r = strchr(lc->sp, ':');
}
if (r != NULL) {
*r++ = '\0';
port = atoi(r);
} else if (port == 0)
port = proxyInfo.tls_method == ssl_tls ? LDAPS_PORT : LDAP_PORT;
if (who != 0) {
lc->who = sdup(myself, T, who);
if (lc->who == 0) {
(void) freeCon(lc);
return (0);
}
}
if (cred != 0) {
lc->cred = sdup(myself, T, cred);
if (lc->cred == 0) {
(void) freeCon(lc);
return (0);
}
}
lc->method = method;
lc->port = port;
lc->bindTimeout = proxyInfo.bind_timeout;
lc->searchTimeout = proxyInfo.search_timeout;
lc->modifyTimeout = proxyInfo.modify_timeout;
lc->addTimeout = proxyInfo.add_timeout;
lc->deleteTimeout = proxyInfo.delete_timeout;
/* All other fields OK at zero */
releaseLC(lc);
return (lc);
}
static int
setupConList(char *serverList, char *who, char *cred, auth_method_t method) {
char *sls, *sl, *s, *e;
__nis_ldap_conn_t *lc, *tmp;
char *myself = "setupConList";
if (serverList == 0)
return (LDAP_PARAM_ERROR);
(void) rw_wrlock(&ldapConLock);
if (ldapCon != 0) {
/* Assume we've already been called and done the set-up */
(void) rw_unlock(&ldapConLock);
return (LDAP_SUCCESS);
}
/* Work on a copy of 'serverList' */
sl = sls = sdup(myself, T, serverList);
if (sl == 0) {
(void) rw_unlock(&ldapConLock);
return (LDAP_NO_MEMORY);
}
/* Remove leading white space */
for (; *sl == ' ' || *sl == '\t'; sl++);
/* Create connection for each server on the list */
for (s = sl; *s != '\0'; s = e+1) {
int l;
/* Find end of server/port token */
for (e = s; *e != ' ' && *e != '\t' && *e != '\0'; e++);
if (*e != '\0')
*e = '\0';
else
e--;
l = slen(s);
if (l > 0) {
lc = createCon(s, who, cred, method, 0);
if (lc == 0) {
free(sls);
(void) rw_unlock(&ldapConLock);
return (LDAP_NO_MEMORY);
}
lc->onList = 1;
if (ldapCon == 0) {
ldapCon = lc;
} else {
/* Insert at end of list */
for (tmp = ldapCon; tmp->next != 0;
tmp = tmp->next);
tmp->next = lc;
}
}
}
free(sls);
(void) rw_unlock(&ldapConLock);
return (LDAP_SUCCESS);
}
static bool_t
is_same_connection(__nis_ldap_conn_t *lc, LDAPURLDesc *ludpp)
{
return (strcasecmp(ludpp->lud_host, lc->sp) == 0 &&
ludpp->lud_port == lc->port);
}
static __nis_ldap_conn_t *
find_connection_from_list(__nis_ldap_conn_t *list,
LDAPURLDesc *ludpp, int *stat)
{
int ldapStat;
__nis_ldap_conn_t *lc = NULL;
if (stat == 0)
stat = &ldapStat;
*stat = LDAP_SUCCESS;
for (lc = list; lc != 0; lc = lc->next) {
exclusiveLC(lc);
if (is_same_connection(lc, ludpp)) {
if (!lc->isBound) {
*stat = connectCon(lc, 1);
if (*stat != LDAP_SUCCESS) {
releaseLC(lc);
continue;
}
} else if (lc->doDis || lc->doDel) {
(void) disconnectCon(lc);
releaseLC(lc);
continue;
}
incrementRC(lc);
releaseLC(lc);
break;
}
releaseLC(lc);
}
return (lc);
}
static __nis_ldap_conn_t *
findReferralCon(char **referralsp, int *stat)
{
__nis_ldap_conn_t *lc = NULL;
__nis_ldap_conn_t *tmp;
int ldapStat;
int i;
LDAPURLDesc *ludpp = NULL;
char *myself = "findReferralCon";
if (stat == 0)
stat = &ldapStat;
*stat = LDAP_SUCCESS;
/*
* We have the referral lock - to prevent multiple
* threads from creating a referred connection simultaneously
*
* Note that this code assumes that the ldapCon list is a
* static list - that it has previously been created
* (otherwise we wouldn't have gotten a referral) and that
* it will neither grow or shrink - elements may have new
* connections or unbound. If this assumption is no longer valid,
* the locking needs to be reworked.
*/
(void) rw_rdlock(&referralConLock);
for (i = 0; referralsp[i] != NULL; i++) {
if (ldap_url_parse(referralsp[i], &ludpp) != LDAP_SUCCESS)
continue;
/* Ignore referrals if not at the appropriate tls level */
#ifdef LDAP_URL_OPT_SECURE
if (ludpp->lud_options & LDAP_URL_OPT_SECURE) {
if (proxyInfo.tls_method != ssl_tls) {
ldap_free_urldesc(ludpp);
continue;
}
} else {
if (proxyInfo.tls_method != no_tls) {
ldap_free_urldesc(ludpp);
continue;
}
}
#endif
/* Determine if we already have a connection to the server */
lc = find_connection_from_list(ldapReferralCon, ludpp, stat);
if (lc == NULL)
lc = find_connection_from_list(ldapCon, ludpp, stat);
ldap_free_urldesc(ludpp);
if (lc != NULL) {
(void) rw_unlock(&referralConLock);
return (lc);
}
}
for (i = 0; referralsp[i] != NULL; i++) {
if (ldap_url_parse(referralsp[i], &ludpp) != LDAP_SUCCESS)
continue;
/* Ignore referrals if not at the appropriate tls level */
#ifdef LDAP_URL_OPT_SECURE
if (ludpp->lud_options & LDAP_URL_OPT_SECURE) {
if (proxyInfo.tls_method != ssl_tls) {
ldap_free_urldesc(ludpp);
continue;
}
} else {
if (proxyInfo.tls_method != no_tls) {
ldap_free_urldesc(ludpp);
continue;
}
}
#endif
lc = createCon(ludpp->lud_host, proxyInfo.proxy_dn,
proxyInfo.proxy_passwd,
proxyInfo.auth_method,
ludpp->lud_port);
if (lc == 0) {
ldap_free_urldesc(ludpp);
(void) rw_unlock(&referralConLock);
*stat = LDAP_NO_MEMORY;
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: Could not connect to host: %s",
myself, NIL(ludpp->lud_host));
return (NULL);
}
lc->onList = 1;
if (ldapReferralCon == 0) {
ldapReferralCon = lc;
} else {
/* Insert at end of list */
for (tmp = ldapReferralCon; tmp->next != 0;
tmp = tmp->next) {}
tmp->next = lc;
}
lc = find_connection_from_list(ldapReferralCon, ludpp, stat);
ldap_free_urldesc(ludpp);
if (lc != NULL)
break;
}
(void) rw_unlock(&referralConLock);
if (lc == NULL) {
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: Could not find a connection to %s, ...",
myself, NIL(referralsp[0]));
}
return (lc);
}
/*
* Find and return a connection believed to be OK and ensure children
* will never use parent's connection.
*/
static __nis_ldap_conn_t *
findYPCon(__nis_ldap_search_t *ls, int *stat) {
__nis_ldap_conn_t *lc, *newlc;
int ldapStat, newstat;
char *myself = "findYPCon";
if (stat == 0)
stat = &ldapStat;
(void) rw_rdlock(&ldapConLock);
if (ldapCon == 0) {
/* Probably first call; try to set up the connection list */
(void) rw_unlock(&ldapConLock);
if ((*stat = setupConList(proxyInfo.default_servers,
proxyInfo.proxy_dn,
proxyInfo.proxy_passwd,
proxyInfo.auth_method)) !=
LDAP_SUCCESS)
return (0);
(void) rw_rdlock(&ldapConLock);
}
for (lc = ldapCon; lc != 0; lc = lc->next) {
exclusiveLC(lc);
if (lc->isBound && (lc->doDis || lc->doDel)) {
*stat = disconnectCon(lc);
if (*stat != LDAP_UNAVAILABLE)
releaseLC(lc);
continue;
}
/*
* Use a new connection for all cases except when
* requested by the main thread in the parent ypserv
* process.
*/
if (ls->useCon == 0) {
newlc = createCon(lc->sp, lc->who, lc->cred,
lc->method, lc->port);
if (!newlc) {
releaseLC(lc);
continue;
}
if (lc->ld != 0) {
newlc->simplePage = lc->simplePage;
newlc->vlv = lc->vlv;
newlc->batchFrom = lc->batchFrom;
}
releaseLC(lc);
exclusiveLC(newlc);
newstat = connectCon(newlc, 0);
if (newstat != LDAP_SUCCESS) {
if (newstat != LDAP_UNAVAILABLE) {
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Cannot open connection to LDAP server (%s): %s",
myself, NIL(newlc->sp),
ldap_err2string(*stat));
}
(void) freeCon(newlc);
newlc = 0;
continue;
}
/*
* No need to put newlc on the ldapCon list as this
* connection will be freed after use.
*/
newlc->onList = 0;
lc = newlc;
} else if (!lc->isBound) {
*stat = connectCon(lc, 1);
if (*stat != LDAP_SUCCESS) {
if (*stat != LDAP_UNAVAILABLE) {
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Cannot open connection to LDAP server (%s): %s",
myself, NIL(lc->sp),
ldap_err2string(*stat));
releaseLC(lc);
}
continue;
}
}
incrementRC(lc);
releaseLC(lc);
break;
}
(void) rw_unlock(&ldapConLock);
return (lc);
}
#define SORTKEYLIST "cn uid"
/*
* Perform an LDAP search operation per 'ls', adding the result(s) to
* a copy of the 'rvIn' structure; the copy becomes the return value.
* The caller must deallocate both 'rvIn' and the result, if any.
*
* On entry, '*numValues' contains a hint regarding the expected
* number of entries. Zero is the same as one, and negative values
* imply no information. This is used to decide whether or not to
* try an indexed search.
*
* On successful (non-NULL) return, '*numValues' contains the number
* of __nis_rule_value_t elements in the returned array, and '*stat'
* the LDAP operations status.
*/
__nis_rule_value_t *
ldapSearch(__nis_ldap_search_t *ls, int *numValues, __nis_rule_value_t *rvIn,
int *ldapStat) {
__nis_rule_value_t *rv = 0;
int stat, numEntries, numVals, tnv, done, lprEc;
LDAPMessage *msg = 0, *m;
__nis_ldap_conn_t *lc;
struct timeval tv, start, now;
LDAPsortkey **sortKeyList = 0;
LDAPControl *ctrls[3], *sortCtrl = 0, *vlvCtrl = 0;
LDAPControl **retCtrls = 0;
LDAPVirtualList vList;
struct berval *spCookie = 0;
int doVLV = 0;
int doSP = 0;
long index;
char *myself = "ldapSearch";
bool_t follow_referral =
proxyInfo.follow_referral == follow;
int doIndex = 1;
char **referralsp = NULL;
ctrls[0] = ctrls[1] = ctrls[2] = 0;
if (ldapStat == 0)
ldapStat = &stat;
if (ls == 0) {
*ldapStat = LDAP_PARAM_ERROR;
return (0);
}
if (yp2ldap) {
/* make sure the parent's connection is not used by child */
if ((lc = findYPCon(ls, ldapStat)) == 0) {
*ldapStat = LDAP_SERVER_DOWN;
return (0);
}
} else {
if ((lc = findCon(ldapStat)) == 0) {
*ldapStat = LDAP_SERVER_DOWN;
return (0);
}
}
if (numValues != 0 && (*numValues == 0 || *numValues == 1))
doIndex = 0;
retry_new_conn:
/* Prefer VLV over simple page, and SP over nothing */
if (doIndex && lc->vlv) {
stat = ldap_create_sort_keylist(&sortKeyList, SORTKEYLIST);
if (stat != LDAP_SUCCESS) {
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: Error creating sort keylist: %s",
myself, ldap_err2string(stat));
freeRuleValue(rv, numVals);
*ldapStat = stat;
rv = 0;
goto retry_noVLV;
}
stat = ldap_create_sort_control(lc->ld, sortKeyList, 1,
&sortCtrl);
if (stat == LDAP_SUCCESS) {
vList.ldvlist_before_count = 0;
vList.ldvlist_after_count = lc->batchFrom - 1;
vList.ldvlist_attrvalue = 0;
vList.ldvlist_extradata = 0;
index = 1;
doVLV = 1;
} else {
ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: Error creating VLV sort control: %s",
myself, ldap_err2string(stat));
freeRuleValue(rv, numVals);
*ldapStat = stat;
rv = 0;
}
}
retry_noVLV:
if (doIndex && !doVLV && lc->simplePage) {
spCookie = am(myself, sizeof (*spCookie));
if (spCookie != 0 &&
(spCookie->bv_val = sdup(myself, T, "")) != 0) {
spCookie->bv_len = 0;
doSP = 1;
} else {
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: No memory for simple page cookie; using un-paged LDAP search",
myself);
freeRuleValue(rv, numVals);
*ldapStat = stat;
rv = 0;
goto cleanup;
}
}
if (!doVLV && !doSP)
ctrls[0] = ctrls[1] = 0;
numVals = 0;
done = 0;
if (ls->timeout.tv_sec || ls->timeout.tv_usec) {
tv = ls->timeout;
} else {
tv = lc->searchTimeout;
}
(void) gettimeofday(&start, 0);
do {
/* don't do vlv or simple page for base level searches */
if (doVLV && ls->base != LDAP_SCOPE_BASE) {
vList.ldvlist_index = index;
vList.ldvlist_size = 0;
if (vlvCtrl != 0)
ldap_control_free(vlvCtrl);
stat = ldap_create_virtuallist_control(lc->ld,
&vList, &vlvCtrl);
if (stat != LDAP_SUCCESS) {
ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: Error creating VLV at index %ld: %s",
myself, index, ldap_err2string(stat));
*ldapStat = stat;
freeRuleValue(rv, numVals);
rv = 0;
goto cleanup;
}
ctrls[0] = sortCtrl;
ctrls[1] = vlvCtrl;
ctrls[2] = 0;
stat = ldap_search_ext_s(lc->ld, ls->base,
ls->scope, ls->filter, ls->attrs,
ls->attrsonly, ctrls, 0, &tv,
proxyInfo.search_size_limit, &msg);
/* don't do vlv or simple page for base level searches */
} else if (doSP && ls->base != LDAP_SCOPE_BASE) {
if (ctrls[0] != 0)
ldap_control_free(ctrls[0]);
stat = ldap_create_page_control(lc->ld,
lc->batchFrom, spCookie, 0, &ctrls[0]);
if (stat != LDAP_SUCCESS) {
ber_bvfree(spCookie);
spCookie = 0;
ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: Simple page error: %s",
myself, ldap_err2string(stat));
freeRuleValue(rv, numVals);
*ldapStat = stat;
rv = 0;
goto cleanup;
}
ctrls[1] = 0;
stat = ldap_search_ext_s(lc->ld, ls->base,
ls->scope, ls->filter, ls->attrs,
ls->attrsonly, ctrls, 0, &tv,
proxyInfo.search_size_limit, &msg);
} else {
stat = ldap_search_st(lc->ld, ls->base, ls->scope,
ls->filter, ls->attrs, ls->attrsonly,
&tv, &msg);
}
if (stat == LDAP_SUCCESS)
ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
if (stat == LDAP_SERVER_DOWN) {
lc->doDis++;
releaseCon(lc, stat);
lc = (yp2ldap)?findYPCon(ls, ldapStat):
findCon(ldapStat);
if (lc == 0) {
*ldapStat = LDAP_SERVER_DOWN;
rv = 0;
goto cleanup;
}
goto retry_new_conn;
}
if (stat == LDAP_REFERRAL && follow_referral) {
(void) ldap_parse_result(lc->ld, msg, NULL, NULL, NULL,
&referralsp, NULL, 0);
if (referralsp != NULL) {
/* We support at most one level of referrals */
follow_referral = FALSE;
releaseCon(lc, stat);
lc = findReferralCon(referralsp, &stat);
ldap_value_free(referralsp);
if (lc == NULL) {
freeRuleValue(rv, numVals);
rv = 0;
*ldapStat = stat;
goto cleanup;
}
stat = LDAP_SUCCESS;
goto retry_new_conn;
}
}
*ldapStat = stat;
if (*ldapStat == LDAP_NO_SUCH_OBJECT) {
freeRuleValue(rv, numVals);
rv = 0;
goto cleanup;
} else if (doVLV && *ldapStat == LDAP_INSUFFICIENT_ACCESS) {
/*
* The LDAP server (at least Netscape 4.x) can return
* LDAP_INSUFFICIENT_ACCESS when VLV is supported,
* but not for the bind DN specified. So, just in
* case, we clean up, and try again without VLV.
*/
doVLV = 0;
if (msg != 0) {
(void) ldap_msgfree(msg);
msg = 0;
}
if (ctrls[0] != 0) {
ldap_control_free(ctrls[0]);
ctrls[0] = 0;
}
if (ctrls[1] != 0) {
ldap_control_free(ctrls[1]);
ctrls[1] = 0;
}
logmsg(MSG_VLV_INSUFF_ACC, LOG_WARNING,
"%s: VLV insufficient access from server %s; retrying without VLV",
myself, NIL(lc->sp));
goto retry_noVLV;
} else if (*ldapStat != LDAP_SUCCESS) {
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"ldap_search(0x%x,\n\t\"%s\",\n\t %d,",
lc->ld, NIL(ls->base), ls->scope);
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"\t\"%s\",\n\t0x%x,\n\t%d) => %d (%s)",
NIL(ls->filter), ls->attrs, ls->attrsonly,
*ldapStat, ldap_err2string(stat));
freeRuleValue(rv, numVals);
rv = 0;
goto cleanup;
}
numEntries = ldap_count_entries(lc->ld, msg);
if (numEntries == 0 && *ldapStat == LDAP_SUCCESS) {
/*
* This is a bit weird, but the server (or, at least,
* ldap_search_ext()) can sometimes return
* LDAP_SUCCESS and no entries when it didn't
* find what we were looking for. Seems it ought to
* return LDAP_NO_SUCH_OBJECT or some such.
*/
freeRuleValue(rv, numVals);
rv = 0;
*ldapStat = LDAP_NO_SUCH_OBJECT;
goto cleanup;
}
tnv = numVals + numEntries;
if ((rv = growRuleValue(numVals, tnv, rv, rvIn)) == 0) {
*ldapStat = LDAP_NO_MEMORY;
goto cleanup;
}
for (m = ldap_first_entry(lc->ld, msg); m != 0;
m = ldap_next_entry(lc->ld, m), numVals++) {
char *nm;
BerElement *ber = 0;
if (numVals > tnv) {
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: Inconsistent LDAP entry count > %d",
myself, numEntries);
break;
}
nm = ldap_get_dn(lc->ld, m);
if (nm == 0 || addSAttr2RuleValue("dn", nm,
&rv[numVals])) {
sfree(nm);
*ldapStat = LDAP_NO_MEMORY;
freeRuleValue(rv, tnv);
rv = 0;
goto cleanup;
}
sfree(nm);
for (nm = ldap_first_attribute(lc->ld, m, &ber);
nm != 0;
nm = ldap_next_attribute(lc->ld, m, ber)) {
struct berval **val;
int i, nv;
val = ldap_get_values_len(lc->ld, m, nm);
nv = (val == 0) ? 0 :
ldap_count_values_len(val);
for (i = 0; i < nv; i++) {
/*
* Since we don't know if the value is
* BER-encoded or not, we mark it as a
* string. All is well as long as we
* don't insist on 'vt_ber' when
* interpreting.
*/
if (addAttr2RuleValue(vt_string, nm,
val[i]->bv_val,
val[i]->bv_len,
&rv[numVals])) {
if (ber != 0)
ber_free(ber, 0);
ldap_value_free_len(val);
*ldapStat = LDAP_NO_MEMORY;
freeRuleValue(rv, tnv);
rv = 0;
goto cleanup;
}
}
ldap_memfree(nm);
if (val != 0)
ldap_value_free_len(val);
}
if (ber != 0)
ber_free(ber, 0);
}
if (numVals != tnv) {
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Inconsistent LDAP entry count, found = %d, expected %d",
myself, numVals, tnv);
}
if (doVLV) {
stat = ldap_parse_result(lc->ld, msg, &lprEc, 0, 0, 0,
&retCtrls, 0);
if (stat != LDAP_SUCCESS) {
ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: VLV parse result error: %s",
myself, ldap_err2string(stat));
*ldapStat = stat;
freeRuleValue(rv, tnv);
rv = 0;
goto cleanup;
}
if (retCtrls != 0) {
unsigned long targetPosP = 0;
unsigned long listSize = 0;
stat = ldap_parse_virtuallist_control(lc->ld,
retCtrls, &targetPosP, &listSize,
&lprEc);
if (stat == LDAP_SUCCESS) {
index = targetPosP + lc->batchFrom;
if (index >= listSize)
done = 1;
}
ldap_controls_free(retCtrls);
retCtrls = 0;
} else {
done = 1;
}
} else if (doSP) {
stat = ldap_parse_result(lc->ld, msg, &lprEc, 0, 0, 0,
&retCtrls, 0);
if (stat != LDAP_SUCCESS) {
ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: Simple page parse result error: %s",
myself, ldap_err2string(stat));
*ldapStat = stat;
freeRuleValue(rv, tnv);
rv = 0;
goto cleanup;
}
if (retCtrls != 0) {
unsigned int count;
if (spCookie != 0) {
ber_bvfree(spCookie);
spCookie = 0;
}
stat = ldap_parse_page_control(lc->ld,
retCtrls, &count, &spCookie);
if (stat == LDAP_SUCCESS) {
if (spCookie == 0 ||
spCookie->bv_val == 0 ||
spCookie->bv_len == 0)
done = 1;
}
ldap_controls_free(retCtrls);
retCtrls = 0;
} else {
done = 1;
}
} else {
done = 1;
}
(void) ldap_msgfree(msg);
msg = 0;
/*
* If we're using VLV or SP, the timeout should apply
* to all calls as an aggregate, so we need to reduce
* 'tv' with the time spent on this chunk of data.
*/
if (!done) {
struct timeval tmp;
(void) gettimeofday(&now, 0);
tmp = now;
now.tv_sec -= start.tv_sec;
now.tv_usec -= start.tv_usec;
if (now.tv_usec < 0) {
now.tv_usec += 1000000;
now.tv_sec -= 1;
}
tv.tv_sec -= now.tv_sec;
tv.tv_usec -= now.tv_usec;
if (tv.tv_usec < 0) {
tv.tv_usec += 1000000;
tv.tv_sec -= 1;
}
if (tv.tv_sec < 0) {
*ldapStat = LDAP_TIMEOUT;
freeRuleValue(rv, tnv);
rv = 0;
goto cleanup;
}
start = tmp;
}
} while (!done);
if (numValues != 0)
*numValues = numVals;
cleanup:
if (NULL != lc) {
if (yp2ldap && ls->useCon == 0) {
/* Disconnect and free the connection */
lc->doDis++;
lc->doDel++;
releaseCon(lc, stat);
releaseLC(lc);
} else {
releaseCon(lc, stat);
}
}
if (msg != 0)
(void) ldap_msgfree(msg);
if (ctrls[0] != 0)
ldap_control_free(ctrls[0]);
if (ctrls[1] != 0)
ldap_control_free(ctrls[1]);
if (spCookie != 0)
ber_bvfree(spCookie);
if (sortKeyList != 0)
ldap_free_sort_keylist(sortKeyList);
return (rv);
}
static void
freeLdapModEntry(LDAPMod *m) {
if (m == 0)
return;
sfree(m->mod_type);
if ((m->mod_op & LDAP_MOD_BVALUES) == 0) {
char **v = m->mod_values;
if (v != 0) {
while (*v != 0) {
sfree(*v);
v++;
}
free(m->mod_values);
}
} else {
struct berval **b = m->mod_bvalues;
if (b != 0) {
while (*b != 0) {
sfree((*b)->bv_val);
free(*b);
b++;
}
free(m->mod_bvalues);
}
}
free(m);
}
static void
freeLdapMod(LDAPMod **mods) {
LDAPMod *m, **org = mods;
if (mods == 0)
return;
while ((m = *mods) != 0) {
freeLdapModEntry(m);
mods++;
}
free(org);
}
/*
* Convert a rule-value structure to the corresponding LDAPMod.
* If 'add' is set, attributes/values are added; object classes
* are also added. If 'add' is cleared, attributes/values are modified,
* and 'oc' controls whether or not object classes are added.
*/
LDAPMod **
search2LdapMod(__nis_rule_value_t *rv, int add, int oc) {
LDAPMod **mods;
int i, j, nm;
char *myself = "search2LdapMod";
if (rv == 0 || rv->numAttrs <= 0)
return (0);
mods = am(myself, (rv->numAttrs + 1) * sizeof (mods[0]));
if (mods == 0)
return (0);
for (i = 0, nm = 0; i < rv->numAttrs; i++) {
int isOc;
/*
* If we're creating an LDAPMod array for an add operation,
* just skip attributes that should be deleted.
*/
if (add && rv->attrVal[i].numVals < 0)
continue;
/*
* Skip DN; it's specified separately to ldap_modify()
* and ldap_add(), and mustn't appear among the
* attributes to be modified/added.
*/
if (strcasecmp("dn", rv->attrName[i]) == 0)
continue;
/*
* If modifying, and 'oc' is off, skip object class
* attributes.
*/
isOc = (strcasecmp("objectclass", rv->attrName[i]) == 0);
if (!add && !oc && isOc)
continue;
mods[nm] = am(myself, sizeof (*mods[nm]));
if (mods[nm] == 0) {
freeLdapMod(mods);
return (0);
}
/* 'mod_type' is the attribute name */
mods[nm]->mod_type = sdup(myself, T, rv->attrName[i]);
if (mods[nm]->mod_type == 0) {
freeLdapMod(mods);
return (0);
}
/*
* numVals < 0 means attribute and all values should
* be deleted.
*/
if (rv->attrVal[i].numVals < 0) {
mods[nm]->mod_op = LDAP_MOD_DELETE;
mods[nm]->mod_values = 0;
nm++;
continue;
}
/* objectClass attributes always added */
mods[nm]->mod_op = (add) ? 0 : ((isOc) ? 0 : LDAP_MOD_REPLACE);
if (rv->attrVal[i].type == vt_string) {
/*
* mods[]->mod_values is a NULL-terminated array
* of (char *)'s.
*/
mods[nm]->mod_values = am(myself,
(rv->attrVal[i].numVals + 1) *
sizeof (mods[nm]->mod_values[0]));
if (mods[nm]->mod_values == 0) {
freeLdapMod(mods);
return (0);
}
for (j = 0; j < rv->attrVal[i].numVals; j++) {
/*
* Just in case the string isn't NUL
* terminated, add one byte to the
* allocated length; am() will initialize
* the buffer to zero.
*/
mods[nm]->mod_values[j] = am(myself,
rv->attrVal[i].val[j].length + 1);
if (mods[nm]->mod_values[j] == 0) {
freeLdapMod(mods);
return (0);
}
memcpy(mods[nm]->mod_values[j],
rv->attrVal[i].val[j].value,
rv->attrVal[i].val[j].length);
}
} else {
mods[nm]->mod_op |= LDAP_MOD_BVALUES;
mods[nm]->mod_bvalues = am(myself,
(rv->attrVal[i].numVals+1) *
sizeof (mods[nm]->mod_bvalues[0]));
if (mods[nm]->mod_bvalues == 0) {
freeLdapMod(mods);
return (0);
}
for (j = 0; j < rv->attrVal[i].numVals; j++) {
mods[nm]->mod_bvalues[j] = am(myself,
sizeof (*mods[nm]->mod_bvalues[j]));
if (mods[nm]->mod_bvalues[j] == 0) {
freeLdapMod(mods);
return (0);
}
mods[nm]->mod_bvalues[j]->bv_val = am(myself,
rv->attrVal[i].val[j].length);
if (mods[nm]->mod_bvalues[j]->bv_val == 0) {
freeLdapMod(mods);
return (0);
}
mods[nm]->mod_bvalues[j]->bv_len =
rv->attrVal[i].val[j].length;
memcpy(mods[nm]->mod_bvalues[j]->bv_val,
rv->attrVal[i].val[j].value,
mods[nm]->mod_bvalues[j]->bv_len);
}
}
nm++;
}
return (mods);
}
/*
* Remove 'value' from 'val'. If value==0, remove the entire
* __nis_single_value_t array from 'val'.
*/
static void
removeSingleValue(__nis_value_t *val, void *value, int length) {
int i;
if (val == 0)
return;
if (value == 0) {
for (i = 0; i < val->numVals; i++) {
sfree(val->val[i].value);
}
sfree(val->val);
val->val = 0;
val->numVals = 0;
return;
}
for (i = 0; i < val->numVals; i++) {
if (val->val[i].value == 0 || (val->val[i].length != length))
continue;
if (memcmp(val->val[i].value, value, length) != 0)
continue;
sfree(val->val[i].value);
if (i != (val->numVals - 1)) {
(void) memmove(&val->val[i], &val->val[i+1],
(val->numVals - 1 - i) * sizeof (val->val[0]));
}
val->numVals -= 1;
break;
}
}
/*
* Helper function for LdapModify
* When a modify operation fails with an object class violation,
* the most probable reason is that the attributes we're modifying are new,
* and the needed object class are not present. So, try the modify again,
* but add the object classes this time.
*/
static int
ldapModifyObjectClass(__nis_ldap_conn_t **lc, char *dn,
__nis_rule_value_t *rvIn, char *objClassAttrs)
{
LDAPMod **mods = 0;
int msgid;
int lderr;
struct timeval tv;
int stat;
LDAPMessage *msg = 0;
char **referralsp = NULL;
__nis_rule_value_t *rv, *rvldap;
__nis_ldap_search_t *ls;
int i, ocrv, ocrvldap, nv;
char *oc[2] = { "objectClass", 0};
char *myself = "ldapModifyObjectClass";
rv = initRuleValue(1, rvIn);
if (rv == 0)
return (LDAP_NO_MEMORY);
delAttrFromRuleValue(rv, "objectClass");
rv = addObjectClasses(rv, objClassAttrs);
if (rv == 0) {
stat = LDAP_OPERATIONS_ERROR;
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: addObjectClasses failed for %s",
myself, NIL(dn));
goto cleanup;
}
/*
* Before adding the object classes whole-sale, try retrieving
* the entry specified by the 'dn'. If it exists, we filter out
* those object classes that already are present in LDAP from our
* update.
*/
ls = buildLdapSearch(dn, LDAP_SCOPE_BASE, 0, 0, "objectClass=*",
oc, 0, 1);
if (ls == 0) {
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: Unable to build DN search for \"%s\"",
myself, NIL(dn));
/* Fall through to try just adding the object classes */
goto addObjectClasses;
}
nv = 0;
rvldap = ldapSearch(ls, &nv, 0, &lderr);
freeLdapSearch(ls);
if (rvldap == 0) {
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: No data for DN search (\"%s\"); LDAP status %d",
myself, NIL(dn), lderr);
/* Fall through to try just adding the object classes */
goto addObjectClasses;
}
/*
* Find the indices of the 'objectClass' attribute
* in 'rvldap' and 'rv'.
*/
for (i = 0, ocrvldap = -1; i < rvldap->numAttrs; i++) {
if (rvldap->attrName[i] != 0 &&
strcasecmp("objectClass", rvldap->attrName[i]) == 0) {
ocrvldap = i;
break;
}
}
for (i = 0, ocrv = -1; i < rv->numAttrs; i++) {
if (rv->attrName[i] != 0 &&
strcasecmp("objectClass", rv->attrName[i]) == 0) {
ocrv = i;
break;
}
}
/*
* Remove those object classes that already exist
* in LDAP (i.e., in 'rvldap') from 'rv'.
*/
if (ocrv >= 0 && ocrvldap >= 0) {
for (i = 0; i < rvldap->attrVal[ocrvldap].numVals; i++) {
removeSingleValue(&rv->attrVal[ocrv],
rvldap->attrVal[ocrvldap].val[i].value,
rvldap->attrVal[ocrvldap].val[i].length);
}
/*
* If no 'objectClass' values left in 'rv', delete
* 'objectClass' from 'rv'.
*/
if (rv->attrVal[ocrv].numVals == 0)
delAttrFromRuleValue(rv, "objectClass");
}
/*
* 'rv' now contains the update we want to make, with just the
* object class(es) that need to be added. Fall through to the
* actual LDAP modify operation.
*/
freeRuleValue(rvldap, 1);
addObjectClasses:
mods = search2LdapMod(rv, 0, 1);
if (mods == 0) {
stat = LDAP_OPERATIONS_ERROR;
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Unable to create LDAP modify changes with object classes for %s",
myself, NIL(dn));
goto cleanup;
}
msgid = ldap_modify((*lc)->ld, dn, mods);
if (msgid != -1) {
tv = (*lc)->modifyTimeout;
stat = ldap_result((*lc)->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option((*lc)->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
} else {
stat = ldap_parse_result((*lc)->ld, msg, &lderr, NULL,
NULL, &referralsp, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
stat = ldap_result2error((*lc)->ld, msg, 0);
}
} else {
(void) ldap_get_option((*lc)->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
}
if (proxyInfo.follow_referral == follow &&
stat == LDAP_REFERRAL && referralsp != NULL) {
releaseCon(*lc, stat);
if (msg != NULL)
(void) ldap_msgfree(msg);
msg = NULL;
*lc = findReferralCon(referralsp, &stat);
ldap_value_free(referralsp);
referralsp = NULL;
if (*lc == NULL)
goto cleanup;
msgid = ldap_modify((*lc)->ld, dn, mods);
if (msgid == -1) {
(void) ldap_get_option((*lc)->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
goto cleanup;
}
stat = ldap_result((*lc)->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option((*lc)->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
} else {
stat = ldap_parse_result((*lc)->ld, msg, &lderr,
NULL, NULL, NULL, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
}
}
cleanup:
if (mods != 0)
freeLdapMod(mods);
freeRuleValue(rv, 1);
return (stat);
}
/*
* Modify the specified 'dn' per the attribute names/values in 'rv'.
* If 'rv' is NULL, we attempt to delete the entire entry.
*
* The 'objClassAttrs' parameter is needed if the entry must be added
* (i.e., created), or a modify fails with an object class violation.
*
* If 'addFirst' is set, we try an add before a modify; modify before
* add otherwise (ignored if we're deleting).
*/
int
ldapModify(char *dn, __nis_rule_value_t *rv, char *objClassAttrs,
int addFirst) {
int stat, add = 0;
LDAPMod **mods = 0;
__nis_ldap_conn_t *lc;
struct timeval tv;
LDAPMessage *msg = 0;
int msgid;
int lderr;
char **referralsp = NULL;
bool_t delete = FALSE;
if (dn == 0)
return (LDAP_PARAM_ERROR);
if ((lc = findCon(&stat)) == 0)
return (stat);
if (rv == 0) {
delete = TRUE;
/* Simple case: if rv == 0, try to delete the entire entry */
msgid = ldap_delete(lc->ld, dn);
if (msgid == -1) {
(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
goto cleanup;
}
tv = lc->deleteTimeout;
stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
} else {
stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
NULL, &referralsp, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
}
if (proxyInfo.follow_referral == follow &&
stat == LDAP_REFERRAL && referralsp != NULL) {
releaseCon(lc, stat);
if (msg != NULL)
(void) ldap_msgfree(msg);
msg = NULL;
lc = findReferralCon(referralsp, &stat);
ldap_value_free(referralsp);
if (lc == NULL)
goto cleanup;
msgid = ldap_delete(lc->ld, dn);
if (msgid == -1) {
(void) ldap_get_option(lc->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
goto cleanup;
}
stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option(lc->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
} else {
stat = ldap_parse_result(lc->ld, msg, &lderr,
NULL, NULL, NULL, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
}
}
/* No such object means someone else has done our job */
if (stat == LDAP_NO_SUCH_OBJECT)
stat = LDAP_SUCCESS;
} else {
if (addFirst) {
stat = ldapAdd(dn, rv, objClassAttrs, lc);
lc = NULL;
if (stat != LDAP_ALREADY_EXISTS)
goto cleanup;
if ((lc = findCon(&stat)) == 0)
return (stat);
}
/*
* First try the modify without specifying object classes
* (i.e., assume they're already present).
*/
mods = search2LdapMod(rv, 0, 0);
if (mods == 0) {
stat = LDAP_PARAM_ERROR;
goto cleanup;
}
msgid = ldap_modify(lc->ld, dn, mods);
if (msgid == -1) {
(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
goto cleanup;
}
tv = lc->modifyTimeout;
stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
} else {
stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
NULL, &referralsp, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
}
if (proxyInfo.follow_referral == follow &&
stat == LDAP_REFERRAL && referralsp != NULL) {
releaseCon(lc, stat);
if (msg != NULL)
(void) ldap_msgfree(msg);
msg = NULL;
lc = findReferralCon(referralsp, &stat);
ldap_value_free(referralsp);
referralsp = NULL;
if (lc == NULL)
goto cleanup;
msgid = ldap_modify(lc->ld, dn, mods);
if (msgid == -1) {
(void) ldap_get_option(lc->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
goto cleanup;
}
stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option(lc->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
} else {
stat = ldap_parse_result(lc->ld, msg, &lderr,
NULL, NULL, NULL, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
}
}
/*
* If the modify failed with an object class violation,
* the most probable reason is that at least on of the
* attributes we're modifying didn't exist before, and
* neither did its object class. So, try the modify again,
* but add the object classes this time.
*/
if (stat == LDAP_OBJECT_CLASS_VIOLATION &&
objClassAttrs != 0) {
freeLdapMod(mods);
mods = 0;
stat = ldapModifyObjectClass(&lc, dn, rv,
objClassAttrs);
}
if (stat == LDAP_NO_SUCH_ATTRIBUTE) {
/*
* If there was at least one attribute delete, then
* the cause of this error could be that said attribute
* didn't exist in LDAP. So, do things the slow way,
* and try to delete one attribute at a time.
*/
int d, numDelete, st;
__nis_rule_value_t *rvt;
for (d = 0, numDelete = 0; d < rv->numAttrs; d++) {
if (rv->attrVal[d].numVals < 0)
numDelete++;
}
/* If there's just one, we've already tried */
if (numDelete <= 1)
goto cleanup;
/* Make a copy of the rule value */
rvt = initRuleValue(1, rv);
if (rvt == 0)
goto cleanup;
/*
* Remove all delete attributes from the tmp
* rule value.
*/
for (d = 0; d < rv->numAttrs; d++) {
if (rv->attrVal[d].numVals < 0) {
delAttrFromRuleValue(rvt,
rv->attrName[d]);
}
}
/*
* Now put the attributes back in one by one, and
* invoke ourselves.
*/
for (d = 0; d < rv->numAttrs; d++) {
if (rv->attrVal[d].numVals >= 0)
continue;
st = addAttr2RuleValue(rv->attrVal[d].type,
rv->attrName[d], 0, 0, rvt);
if (st != 0) {
logmsg(MSG_NOMEM, LOG_ERR,
"%s: Error deleting \"%s\" for \"%s\"",
NIL(rv->attrName[d]), NIL(dn));
stat = LDAP_NO_MEMORY;
freeRuleValue(rvt, 1);
goto cleanup;
}
stat = ldapModify(dn, rvt, objClassAttrs, 0);
if (stat != LDAP_SUCCESS &&
stat != LDAP_NO_SUCH_ATTRIBUTE) {
freeRuleValue(rvt, 1);
goto cleanup;
}
delAttrFromRuleValue(rvt, rv->attrName[d]);
}
/*
* If we got here, then all attributes that should
* be deleted either have been, or didn't exist. For
* our purposes, the latter is as good as the former.
*/
stat = LDAP_SUCCESS;
freeRuleValue(rvt, 1);
}
if (stat == LDAP_NO_SUCH_OBJECT && !addFirst) {
/*
* Entry doesn't exist, so try an ldap_add(). If the
* ldap_add() also fails, that could be because someone
* else added it between our modify and add operations.
* If so, we consider that foreign add to be
* authoritative (meaning we don't retry our modify).
*
* Also, if all modify operations specified by 'mods'
* are deletes, LDAP_NO_SUCH_OBJECT is a kind of
* success; we certainly don't want to create the
* entry.
*/
int allDelete;
LDAPMod **m;
for (m = mods, allDelete = 1; *m != 0 && allDelete;
m++) {
if (((*m)->mod_op & LDAP_MOD_DELETE) == 0)
allDelete = 0;
}
add = 1;
if (allDelete) {
stat = LDAP_SUCCESS;
} else if (objClassAttrs == 0) {
/* Now we need it, so this is fatal */
stat = LDAP_PARAM_ERROR;
} else {
stat = ldapAdd(dn, rv, objClassAttrs, lc);
lc = NULL;
}
}
}
cleanup:
if (stat != LDAP_SUCCESS) {
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s(0x%x (%s), \"%s\") => %d (%s)\n",
!delete ? (add ? "ldap_add" : "ldap_modify") :
"ldap_delete",
lc != NULL ? lc->ld : 0,
lc != NULL ? NIL(lc->sp) : "nil",
dn, stat, ldap_err2string(stat));
}
releaseCon(lc, stat);
freeLdapMod(mods);
if (msg != 0)
(void) ldap_msgfree(msg);
return (stat);
}
/*
* Create the entry specified by 'dn' to have the values per 'rv'.
* The 'objClassAttrs' are the extra object classes we need when
* creating an entry.
*
* If 'lc' is non-NULL, we use that connection; otherwise, we find
* our own. CAUTION: This connection will be released on return. Regardless
* of return value, this connection should not subsequently used by the
* caller.
*
* Returns an LDAP status.
*/
int
ldapAdd(char *dn, __nis_rule_value_t *rv, char *objClassAttrs, void *lcv) {
int stat;
LDAPMod **mods = 0;
struct timeval tv;
LDAPMessage *msg = 0;
__nis_ldap_conn_t *lc = lcv;
int msgid;
int lderr;
char **referralsp = NULL;
if (dn == 0 || rv == 0 || objClassAttrs == 0) {
releaseCon(lc, LDAP_SUCCESS);
return (LDAP_PARAM_ERROR);
}
if (lc == 0) {
if ((lc = findCon(&stat)) == 0)
return (stat);
}
rv = addObjectClasses(rv, objClassAttrs);
if (rv == 0) {
stat = LDAP_OPERATIONS_ERROR;
goto cleanup;
}
mods = search2LdapMod(rv, 1, 0);
if (mods == 0) {
stat = LDAP_OPERATIONS_ERROR;
goto cleanup;
}
msgid = ldap_add(lc->ld, dn, mods);
if (msgid == -1) {
(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
goto cleanup;
}
tv = lc->addTimeout;
stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER, &stat);
} else {
stat = ldap_parse_result(lc->ld, msg, &lderr, NULL, NULL,
&referralsp, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
}
if (proxyInfo.follow_referral == follow && stat == LDAP_REFERRAL &&
referralsp != NULL) {
releaseCon(lc, stat);
if (msg != NULL)
(void) ldap_msgfree(msg);
msg = NULL;
lc = findReferralCon(referralsp, &stat);
ldap_value_free(referralsp);
if (lc == NULL)
goto cleanup;
msgid = ldap_add(lc->ld, dn, mods);
if (msgid == -1) {
(void) ldap_get_option(lc->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
goto cleanup;
}
stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
} else {
stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
NULL, NULL, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
}
}
cleanup:
if (stat != LDAP_SUCCESS) {
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"ldap_add(0x%x (%s), \"%s\") => %d (%s)\n",
lc != NULL ? lc->ld : 0,
lc != NULL ? NIL(lc->sp) : "nil",
dn, stat, ldap_err2string(stat));
}
releaseCon(lc, stat);
freeLdapMod(mods);
if (msg != 0)
(void) ldap_msgfree(msg);
return (stat);
}
/*
* Change the entry at 'oldDn' to have the new DN (not RDN) 'dn'.
* Returns an LDAP error status.
*/
int
ldapChangeDN(char *oldDn, char *dn) {
int stat;
__nis_ldap_conn_t *lc;
int i, j, lo, ln;
char *rdn;
int msgid;
int lderr;
struct timeval tv;
LDAPMessage *msg = 0;
char **referralsp = NULL;
char *myself = "ldapChangeDN";
if ((lo = slen(oldDn)) <= 0 || (ln = slen(dn)) <= 0)
return (LDAP_PARAM_ERROR);
if (strcasecmp(oldDn, dn) == 0)
return (LDAP_SUCCESS);
if ((lc = findCon(&stat)) == 0)
return (stat);
rdn = sdup(myself, T, dn);
if (rdn == 0) {
releaseCon(lc, LDAP_SUCCESS);
return (LDAP_NO_MEMORY);
}
/* Compare old and new DN from the end */
for (i = lo-1, j = ln-1; i >= 0 && j >= 0; i--, j--) {
if (tolower(oldDn[i]) != tolower(rdn[j])) {
/*
* Terminate 'rdn' after this character in order
* to snip off the portion of the new DN that is
* the same as the old DN. What remains in 'rdn'
* is the relative DN.
*/
rdn[j+1] = '\0';
break;
}
}
stat = ldap_rename(lc->ld, oldDn, rdn, NULL, 1, NULL, NULL, &msgid);
if (msgid != -1) {
tv = lc->modifyTimeout;
stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option(lc->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
} else {
stat = ldap_parse_result(lc->ld, msg, &lderr, NULL,
NULL, &referralsp, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
stat = ldap_result2error(lc->ld, msg, 0);
}
} else {
(void) ldap_get_option(lc->ld, LDAP_OPT_ERROR_NUMBER,
&stat);
}
if (proxyInfo.follow_referral == follow &&
stat == LDAP_REFERRAL && referralsp != NULL) {
releaseCon(lc, stat);
if (msg != NULL)
(void) ldap_msgfree(msg);
msg = NULL;
lc = findReferralCon(referralsp, &stat);
ldap_value_free(referralsp);
referralsp = NULL;
if (lc == NULL)
goto cleanup;
msgid = ldap_rename(lc->ld, oldDn, rdn, NULL, 1, NULL, NULL,
&msgid);
if (msgid == -1) {
(void) ldap_get_option(lc->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
goto cleanup;
}
stat = ldap_result(lc->ld, msgid, 0, &tv, &msg);
if (stat == 0) {
stat = LDAP_TIMEOUT;
} else if (stat == -1) {
(void) ldap_get_option(lc->ld,
LDAP_OPT_ERROR_NUMBER, &stat);
} else {
stat = ldap_parse_result(lc->ld, msg, &lderr,
NULL, NULL, NULL, NULL, 0);
if (stat == LDAP_SUCCESS)
stat = lderr;
}
}
cleanup:
if (msg != NULL)
(void) ldap_msgfree(msg);
#if 1
fprintf(stderr, "%s: ldap_modrdn_s(0x%x, %s, %s, 1) => %s\n",
myself, lc == NULL ? 0: lc->ld, NIL(oldDn), NIL(rdn),
ldap_err2string(stat));
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: ldap_modrdn_s(0x%x, %s, %s, 1) => %s",
myself, lc == NULL ? 0: lc->ld, NIL(oldDn), NIL(rdn),
ldap_err2string(stat));
#endif
if (stat == LDAP_NO_SUCH_OBJECT) {
/*
* Fine from our point of view, since all we want to do
* is to make sure that an update to the new DN doesn't
* leave the old entry around.
*/
stat = LDAP_SUCCESS;
}
releaseCon(lc, stat);
sfree(rdn);
return (stat);
}