/*
* 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, 2011, Oracle and/or its affiliates. All rights reserved.
*/
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <rpcsvc/nis.h>
#include <rpc/xdr.h>
#include "ldap_util.h"
#include "ldap_attr.h"
#include "ldap_ruleval.h"
#include "ldap_op.h"
#include "ldap_map.h"
#include "ldap_glob.h"
#include "ldap_xdr.h"
#include "ldap_val.h"
/* From yptol/dit_access_utils.h */
#define N2LKEY "rf_key"
#define N2LIPKEY "rf_ipkey"
__nis_hash_table_mt ldapMappingList = NIS_HASH_TABLE_MT_INIT;
extern int yp2ldap;
int
setColumnNames(__nis_table_mapping_t *t) {
int i, j, nic, noc;
char **col;
zotypes type;
const char *myself = "setColumnNames";
if (t == 0)
return (0);
type = t->objType;
col = t->column;
nic = (col != 0) ? t->numColumns : -1;
t->objType = NIS_BOGUS_OBJ;
t->obj = 0;
/*
* If it's a table object, but there are no translation rules,
* this mapping is for the table object itself. In that case,
* we throw away the column names (if any).
*/
if (t->objType == NIS_TABLE_OBJ && t->numRulesFromLDAP == 0 &&
t->numRulesToLDAP == 0) {
for (i = 0; i < t->numColumns; i++)
sfree(t->column[i]);
sfree(t->column);
t->column = 0;
t->numColumns = 0;
noc = 0;
}
/*
* Verify that all column names found by the parser
* are present in the actual column list.
*/
if (verbose) {
for (i = 0, noc = 0; i < nic; i++) {
int found = 0;
if (col[i] == 0)
continue;
/* Skip the 'zo_*' special column names */
if (isObjAttrString(col[i]))
continue;
for (j = 0; j < t->numColumns; j++) {
if (strcmp(col[i], t->column[j]) == 0) {
noc++;
found = 1;
break;
}
}
if (!found) {
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: No column \"%s\" in \"%s\"",
myself, NIL(col[i]), NIL(t->objName));
}
}
}
/* Remove any setup by the parser */
for (i = 0; i < nic; i++) {
sfree(col[i]);
}
sfree(col);
return (0);
}
void
freeSingleObjAttr(__nis_obj_attr_t *attr) {
if (attr == 0)
return;
sfree(attr->zo_owner);
sfree(attr->zo_group);
sfree(attr->zo_domain);
sfree(attr);
}
void
freeObjAttr(__nis_obj_attr_t **attr, int numAttr) {
int i;
if (attr == 0)
return;
for (i = 0; i < numAttr; i++) {
freeSingleObjAttr(attr[i]);
}
sfree(attr);
}
__nis_obj_attr_t *
cloneObjAttr(__nis_obj_attr_t *old) {
__nis_obj_attr_t *new;
const char *myself = "cloneObjAttr";
if (old == 0)
return (0);
new = am(myself, sizeof (*new));
if (new == 0)
return (0);
new->zo_owner = sdup(myself, T, old->zo_owner);
if (new->zo_owner == 0 && old->zo_owner != 0)
goto cleanup;
new->zo_group = sdup(myself, T, old->zo_group);
if (new->zo_group == 0 && old->zo_group != 0)
goto cleanup;
new->zo_domain = sdup(myself, T, old->zo_domain);
if (new->zo_domain == 0 && old->zo_domain != 0)
goto cleanup;
new->zo_access = old->zo_access;
new->zo_ttl = old->zo_ttl;
return (new);
cleanup:
freeSingleObjAttr(new);
return (0);
}
/*
* Obtain NIS+ entries (in the form of db_query's) from the supplied table
* mapping and db_query.
*
* If 'qin' is NULL, enumeration is desired.
*
* On exit, '*numQueries' contains the number of (db_query *)'s in the
* return array, '*ldapStat' the LDAP operation status, and '*objAttr'
* a pointer to an array (of '*numQueries elements) of object attributes
* (zo_owner, etc.). If no object attributes were retrieved, '*objAttr'
* is NULL; any and all of the (*objAttr)[i]'s may be NULL.
*/
db_query **
mapFromLDAP(__nis_table_mapping_t *t, db_query *qin, int *numQueries,
char *dbId, int *ldapStat, __nis_obj_attr_t ***objAttr) {
__nis_table_mapping_t **tp;
db_query **q;
__nis_rule_value_t *rv;
__nis_ldap_search_t *ls;
int n, numVals, numMatches = 0;
int stat;
__nis_obj_attr_t **attr;
const char *myself = "mapFromLDAP";
if (ldapStat == 0)
ldapStat = &stat;
if (t == 0 || numQueries == 0) {
*ldapStat = LDAP_PARAM_ERROR;
return (0);
}
/* Select the correct table mapping(s) */
tp = selectTableMapping(t, qin, 0, 0, dbId, &numMatches);
if (tp == 0 || numMatches <= 0) {
/*
* Not really an error; just no matching mapping
* for the query.
*/
*ldapStat = LDAP_SUCCESS;
return (0);
}
q = 0;
attr = 0;
/* For each mapping */
for (numVals = 0, n = 0; n < numMatches; n++) {
db_query **qt;
int i, nqt = 0, filterOnQin, res = 0;
t = tp[n];
if (qin != 0) {
rv = buildNisPlusRuleValue(t, qin, 0);
if (rv != 0) {
/*
* Depending on the value of res, we shall
* proceed to next table mapping.
*/
ls = createLdapRequest(t, rv, 0, 1, &res, NULL);
}
else
ls = 0;
} else {
/* Build enumeration request */
rv = 0;
ls = createLdapRequest(t, 0, 0, 1, NULL, NULL);
}
freeRuleValue(rv, 1);
if (ls == 0) {
/*
* if the res is NP_LDAP_RULES_NO_VALUE, that means we
* have enough NIS+ columns for the rules to produce
* values, but none of them did, so continue to the
* next table mapping. Otherwise do cleanup and return
* error.
*/
if (res == NP_LDAP_RULES_NO_VALUE)
continue;
for (i = 0; i < numVals; i++)
freeQuery(q[i]);
sfree(q);
free(tp);
*ldapStat = LDAP_OPERATIONS_ERROR;
return (0);
}
/* Query LDAP */
nqt = (ls->isDN || qin != 0) ? 0 : -1;
rv = ldapSearch(ls, &nqt, 0, ldapStat);
/*
* If qin != 0, then we need to make sure that the
* LDAP search is filtered so that only entries that
* are compatible with 'qin' are retained. This will
* happen automatically if we do a DN search (in which
* case, no need to filter on 'qin').
*/
if (ls->isDN || qin == 0)
filterOnQin = 0;
else
filterOnQin = 1;
freeLdapSearch(ls);
/* Convert rule-values to db_query's */
if (rv != 0 && nqt > 0) {
int nrv = nqt;
__nis_obj_attr_t **at = 0;
qt = ruleValue2Query(t, rv,
(filterOnQin) ? qin : 0, &at, &nqt);
freeRuleValue(rv, nrv);
if (qt != 0 && q == 0) {
q = qt;
attr = at;
numVals = nqt;
} else if (qt != 0) {
db_query **tmp;
__nis_obj_attr_t **atmp;
/* Extend the 'q' array */
tmp = realloc(q,
(numVals+nqt) * sizeof (q[0]));
/* ... and the 'attr' array */
atmp = realloc(attr,
(numVals+nqt) * sizeof (attr[0]));
if (tmp == 0 || atmp == 0) {
logmsg(MSG_NOMEM, LOG_ERR,
"%s: realloc(%d) => NULL",
myself,
(numVals+nqt) * sizeof (q[0]));
for (i = 0; i < numVals; i++)
freeQuery(q[i]);
for (i = 0; i < nqt; i++)
freeQuery(qt[i]);
sfree(tmp);
sfree(atmp);
sfree(q);
sfree(qt);
sfree(tp);
freeObjAttr(at, nqt);
freeObjAttr(attr, numVals);
*ldapStat = LDAP_NO_MEMORY;
return (0);
}
q = tmp;
attr = atmp;
/* Add the results for this 't' */
(void) memcpy(&q[numVals], qt,
nqt * sizeof (qt[0]));
(void) memcpy(&attr[numVals], at,
nqt * sizeof (at[0]));
numVals += nqt;
sfree(qt);
sfree(at);
}
}
}
*numQueries = numVals;
if (objAttr != 0)
*objAttr = attr;
else
freeObjAttr(attr, numVals);
sfree(tp);
return (q);
}
/*
* Add the object attributes (zo_owner, etc.) to the rule-value 'rv'.
* Returns a pointer to the (possibly newly allocated) rule-value,
* or NULL in case of failure. If not returning 'rvIn', the latter
* will have been freed.
*/
__nis_rule_value_t *
addObjAttr2RuleValue(nis_object *obj, __nis_rule_value_t *rvIn) {
__nis_rule_value_t *rv;
char abuf[2 * sizeof (obj->zo_access) + 1];
char tbuf[2 * sizeof (obj->zo_ttl) + 1];
if (obj == 0)
return (0);
if (rvIn != 0) {
rv = rvIn;
} else {
rv = initRuleValue(1, 0);
if (rv == 0)
return (0);
}
if (obj->zo_owner != 0) {
if (addSCol2RuleValue("zo_owner", obj->zo_owner, rv) != 0) {
freeRuleValue(rv, 1);
return (0);
}
}
if (obj->zo_group != 0) {
if (addSCol2RuleValue("zo_group", obj->zo_group, rv) != 0) {
freeRuleValue(rv, 1);
return (0);
}
}
if (obj->zo_domain != 0) {
if (addSCol2RuleValue("zo_domain", obj->zo_domain, rv) != 0) {
freeRuleValue(rv, 1);
return (0);
}
}
(void) memset(abuf, 0, sizeof (abuf));
(void) memset(tbuf, 0, sizeof (tbuf));
sprintf(abuf, "%x", obj->zo_access);
sprintf(tbuf, "%x", obj->zo_ttl);
if (addSCol2RuleValue("zo_access", abuf, rv) != 0) {
freeRuleValue(rv, 1);
return (0);
}
if (addSCol2RuleValue("zo_ttl", tbuf, rv) != 0) {
freeRuleValue(rv, 1);
return (0);
}
return (rv);
}
/*
* Returns a pointer to (NOT a copy of) the value for the specified
* column 'col' in the rule-value 'rv'.
*/
__nis_value_t *
findColValue(char *col, __nis_rule_value_t *rv) {
int i;
if (col == 0 || rv == 0 || rv->numColumns <= 0)
return (0);
for (i = 0; i < rv->numColumns; i++) {
if (strcmp(col, rv->colName[i]) == 0)
return (&rv->colVal[i]);
}
return (0);
}
/*
* Return the NIS+ object attributes (if any) in the rule-value 'rv'.
*/
__nis_obj_attr_t *
ruleValue2ObjAttr(__nis_rule_value_t *rv) {
__nis_obj_attr_t *attr;
__nis_value_t *val;
const char *myself = "ruleValue2ObjAttr";
if (rv == 0 || rv->numColumns <= 0)
return (0);
attr = am(myself, sizeof (*attr));
if ((val = findColValue("zo_owner", rv)) != 0 &&
val->type == vt_string && val->numVals == 1 &&
val->val[0].value != 0) {
attr->zo_owner = sdup(myself, T, val->val[0].value);
if (attr->zo_owner == 0) {
freeSingleObjAttr(attr);
return (0);
}
}
if ((val = findColValue("zo_group", rv)) != 0 &&
val->type == vt_string && val->numVals == 1 &&
val->val[0].value != 0) {
attr->zo_group = sdup(myself, T, val->val[0].value);
if (attr->zo_group == 0) {
freeSingleObjAttr(attr);
return (0);
}
}
if ((val = findColValue("zo_domain", rv)) != 0 &&
val->type == vt_string && val->numVals == 1 &&
val->val[0].value != 0) {
attr->zo_domain = sdup(myself, T, val->val[0].value);
if (attr->zo_domain == 0) {
freeSingleObjAttr(attr);
return (0);
}
}
if ((val = findColValue("zo_access", rv)) != 0 &&
val->type == vt_string && val->numVals == 1 &&
val->val[0].value != 0) {
if (sscanf(val->val[0].value, "%x", &attr->zo_access) != 1) {
freeSingleObjAttr(attr);
return (0);
}
}
if ((val = findColValue("zo_ttl", rv)) != 0 &&
val->type == vt_string && val->numVals == 1 &&
val->val[0].value != 0) {
if (sscanf(val->val[0].value, "%x", &attr->zo_ttl) != 1) {
freeSingleObjAttr(attr);
return (0);
}
}
return (attr);
}
/*
* If the supplied string is one of the object attributes, return one.
* Otherwise, return zero.
*/
int
isObjAttrString(char *str) {
if (str == 0)
return (0);
if (strcmp("zo_owner", str) == 0 ||
strcmp("zo_group", str) == 0 ||
strcmp("zo_domain", str) == 0 ||
strcmp("zo_access", str) == 0 ||
strcmp("zo_ttl", str) == 0)
return (1);
else
return (0);
}
/*
* If the supplied value is one of the object attribute strings, return
* a pointer to the string. Otherwise, return NULL.
*/
char *
isObjAttr(__nis_single_value_t *val) {
if (val == 0 || val->length <= 0 || val->value == 0)
return (0);
if (isObjAttrString(val->value))
return (val->value);
else
return (0);
}
int
setObjAttrField(char *attrName, __nis_single_value_t *val,
__nis_obj_attr_t **objAttr) {
__nis_obj_attr_t *attr;
const char *myself = "setObjAttrField";
if (attrName == 0 || val == 0 || objAttr == 0 ||
val->value == 0 || val->length <= 0)
return (-1);
if (*objAttr != 0) {
attr = *objAttr;
} else {
attr = am(myself, sizeof (*attr));
if (attr == 0)
return (-2);
*objAttr = attr;
}
if (strcmp("zo_owner", attrName) == 0) {
if (attr->zo_owner == 0) {
attr->zo_owner = sdup(myself, T, val->value);
if (attr->zo_owner == 0)
return (-11);
}
} else if (strcmp("zo_group", attrName) == 0) {
if (attr->zo_group == 0) {
attr->zo_group = sdup(myself, T, val->value);
if (attr->zo_group == 0)
return (-12);
}
} else if (strcmp("zo_domain", attrName) == 0) {
if (attr->zo_domain == 0) {
attr->zo_domain = sdup(myself, T, val->value);
if (attr->zo_domain == 0)
return (-13);
}
} else if (strcmp("zo_access", attrName) == 0) {
if (attr->zo_access == 0) {
if (sscanf(val->value, "%x", &attr->zo_access) != 1)
return (-14);
}
} else if (strcmp("zo_ttl", attrName) == 0) {
if (attr->zo_ttl == 0) {
if (sscanf(val->value, "%x", &attr->zo_ttl) != 1)
return (-15);
}
}
return (0);
}
/*
* Return a DN and rule-value for the supplied mapping, db_query's, and
* input rule-value. This function only works on a single mapping. See
* mapToLDAP() below for a description of the action depending on the
* values of 'old' and 'new'.
*
* If both 'old' and 'new' are supplied, and the modify would result
* in a change to the DN, '*oldDN' will contain the old DN. Otherwise
* (and normally), '*oldDN' will be NULL.
*/
char *
map1qToLDAP(__nis_table_mapping_t *t, db_query *old, db_query *new,
__nis_rule_value_t *rvIn, __nis_rule_value_t **rvOutP,
char **oldDnP) {
__nis_rule_value_t *rv, *rvt;
__nis_ldap_search_t *ls;
char *dn = 0, *oldDn = 0;
__nis_table_mapping_t del;
const char *myself = "map1qToLDAP";
if (t == 0 || (old == 0 && new == 0) || rvOutP == 0)
return (0);
/*
* If entry should be deleted, we look at the delete
* policy in the table mapping. Should it specify a
* rule set, we use that rule set to build a rule-
* value, and the delete actually becomes a modify
* operation.
*/
if (old != 0 && new == 0) {
if (t->objectDN->delDisp == dd_perDbId) {
/*
* The functions that build a rule-value from a
* rule set expect a __nis_table_mapping_t, but the
* rule set in the __nis_object_dn_t isn't of that
* form. So, build a pseudo-__nis_table_mapping_t that
* borrows heavily from 't'.
*/
del = *t;
del.numRulesToLDAP = del.objectDN->numDbIds;
del.ruleToLDAP = del.objectDN->dbId;
/*
* Do a modify with the pseudo-table
* mapping, and the 'old' db_query
* supplying input to the delete rule
* set.
*/
t = &del;
new = old;
} else if (t->objectDN->delDisp == dd_always) {
/* Nothing to do here; all handled below */
} else if (t->objectDN->delDisp == dd_never) {
return (0);
} else {
logmsg(MSG_INVALIDDELDISP, LOG_WARNING,
"%s: Invalid delete disposition %d for \"%s\"",
myself, t->objectDN->delDisp,
NIL(t->dbId));
return (0);
}
}
/* Make a copy of the input rule-value */
if (rvIn != 0) {
rv = initRuleValue(1, rvIn);
if (rv == 0)
return (0);
} else {
rv = 0;
}
/* First get a rule-value from the supplied NIS+ entry. */
rvt = rv;
rv = buildNisPlusRuleValue(t, ((old != 0) ? old : new), rvt);
freeRuleValue(rvt, 1);
if (rv == 0) {
logmsg(MSG_NORULEVALUE, LOG_WARNING,
"%s: No in-query rule-value derived for \"%s\"",
myself, NIL(t->dbId));
return (0);
}
/*
* Create a request (really only care about the DN) from the
* supplied NIS+ entry data.
*/
ls = createLdapRequest(t, rv, &dn, 0, NULL, NULL);
if (ls == 0 || dn == 0) {
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: Unable to create LDAP request for %s: %s",
myself, NIL(t->dbId),
(dn != 0) ? dn : rvId(rv, mit_nis));
sfree(dn);
freeLdapSearch(ls);
freeRuleValue(rv, 1);
return (0);
}
freeLdapSearch(ls);
if (new != 0) {
/*
* Create a rule-value from the new NIS+ entry.
* Don't want to mix in the rule-value derived
* from 'old', so delete it. However, we still
* want the owner, group, etc., from 'rvIn'.
*/
if (old != 0) {
freeRuleValue(rv, 1);
if (rvIn != 0) {
rv = initRuleValue(1, rvIn);
if (rv == 0) {
sfree(dn);
return (0);
}
} else {
rv = 0;
}
}
rvt = rv;
rv = buildNisPlusRuleValue(t, new, rvt);
freeRuleValue(rvt, 1);
if (rv == 0) {
logmsg(MSG_NORULEVALUE, LOG_WARNING,
"%s: No new rule-value derived for \"%s: %s\"",
myself, NIL(t->dbId), dn);
sfree(dn);
return (0);
}
/*
* Check if the proposed modification would result in a
* a change to the DN.
*/
if (old != 0) {
oldDn = dn;
dn = 0;
ls = createLdapRequest(t, rv, &dn, 0, NULL, NULL);
if (ls == 0 || dn == 0) {
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: Unable to create new DN for \"%s: %s\"",
myself, NIL(t->dbId), oldDn);
sfree(oldDn);
freeLdapSearch(ls);
freeRuleValue(rv, 1);
return (0);
}
freeLdapSearch(ls);
if (strcasecmp(oldDn, dn) == 0) {
sfree(oldDn);
oldDn = 0;
}
}
}
*rvOutP = rv;
if (oldDnP != 0)
*oldDnP = oldDn;
return (dn);
}
/*
* Since the DN hash list is an automatic variable, there's no need for
* locking, and we remove the locking overhead by using the libnsl
* hash functions.
*/
#undef NIS_HASH_ITEM
#undef NIS_HASH_TABLE
typedef struct {
NIS_HASH_ITEM item;
int index;
char *oldDn;
} __dn_item_t;
/*
* Update LDAP per the supplied table mapping and db_query's.
*
* 'nq' is the number of elements in the 'old', 'new', and 'rvIn'
* arrays. mapToLDAP() generally performs one update for each
* element; however, if one or more of the individual queries
* produce the same DN, they're merged into a single update.
*
* There are four cases, depending on the values of 'old[iq]' and
* 'new[iq]':
*
* (1) old[iq] == 0 && new[iq] == 0
* No action; skip to next query
*
* (2) old[iq] == 0 && new[iq] != 0
* Attempt to use the 'new' db_query to get a DN, and try to create
* the corresponding LDAP entry.
*
* (3) old[iq] != 0 && new[iq] == 0
* Use the 'old' db_query to get a DN, and try to delete the LDAP
* entry per the table mapping.
*
* (4) old[iq] != 0 && new[iq] != 0
* Use the 'old' db_query to get a DN, and update (possibly create)
* the corresponding LDAP entry per the 'new' db_query.
*
* If 'rvIn' is non-NULL, it is expected to contain the object attributes
* (zo_owner, etc.) to be written to LDAP. 'rvIn' is an array with 'nq'
* elements.
*
* If 'firstOnly' is set, only the first old[iq]/new[iq] pair is used
* to perform the actual update. Any additional queries specified will
* have their values folded in, but are not used to derive update targets.
* This mode is inteded to support the case where multiple NIS+ entries
* map to one and the same LDAP entry. Note that 'rvIn' must still be
* an array of 'nq' elements, though if 'firstOnly' is set, it should be
* OK to leave all but 'rvIn[0]' empty.
*
* 'dbId' is used to further narow down the selection of mapping candidates
* to those matching the 'dbId' value.
*/
int
mapToLDAP(__nis_table_mapping_t *tm, int nq, db_query **old, db_query **new,
__nis_rule_value_t *rvIn, int firstOnly, char *dbId) {
__nis_table_mapping_t **tp, **tpa;
int i, n, rnq, iq, r, ret = LDAP_SUCCESS;
int maxMatches, numMatches = 0;
__nis_ldap_search_t *ls;
char **dn = 0, **odn = 0;
__nis_rule_value_t **rv;
__dn_item_t *dni;
const char *myself = "mapToLDAP";
if (tm == 0 || (old == 0 && new == 0) || nq <= 0)
return (LDAP_PARAM_ERROR);
/* Determine maximum number of table mapping matches */
if (nq == 1) {
tp = selectTableMapping(tm,
(old != 0 && old[0] != 0) ? old[0] : new[0], 1, 0,
dbId, &maxMatches);
numMatches = maxMatches;
} else {
tp = selectTableMapping(tm, 0, 1, 0, dbId, &maxMatches);
}
/*
* If no matching mapping, we're not mapping to LDAP in this
* particular case.
*/
if (tp == 0 || maxMatches == 0) {
sfree(tp);
return (LDAP_SUCCESS);
}
/*
* Allocate the 'rv', 'dn', and 'tpa' arrays. Worst case is that
* we need nq * maxMatches elements in each array. However, if
* 'firstOnly' is set, we only need one element per matching
* mapping in each.
*/
dn = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (dn[0]));
odn = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (odn[0]));
rv = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (rv[0]));
tpa = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (tpa[0]));
if (dn == 0 || odn == 0 || rv == 0 || tpa == 0) {
sfree(tp);
sfree(dn);
sfree(odn);
sfree(rv);
sfree(tpa);
return (LDAP_NO_MEMORY);
}
/* Unless nq == 1, we don't need the 'tp' value */
if (nq != 1)
sfree(tp);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s: %d * %d potential updates",
myself, NIL(tm->objName), nq, maxMatches);
/*
* Create DNs, column and attribute values, and merge duplicate DNs.
*/
for (iq = 0, rnq = 0; iq < nq; iq++) {
int idx;
if ((old == 0 || old[iq] == 0) &&
(new == 0 || new[iq] == 0))
continue;
/*
* Select matching table mappings; if nq == 1, we've already
* got the 'tp' array from above. We expect this to be the
* most common case, so it's worth special treatment.
*/
if (nq != 1)
tp = selectTableMapping(tm,
(old != 0 && old[iq] != 0) ? old[iq] : new[iq], 1, 0,
dbId, &numMatches);
if (tp == 0)
continue;
else if (numMatches <= 0) {
sfree(tp);
continue;
}
idx = iq * maxMatches;
if (idx == 0 || !firstOnly)
(void) memcpy(&tpa[idx], tp,
numMatches * sizeof (tpa[idx]));
for (n = 0; n < numMatches; n++) {
char *dnt, *odnt;
__nis_rule_value_t *rvt = 0;
if (tp[n] == 0)
continue;
dnt = map1qToLDAP(tp[n],
(old != 0) ? old[iq] : 0,
(new != 0) ? new[iq] : 0,
(rvIn != 0) ? &rvIn[iq] : 0,
&rvt, &odnt);
if (dnt == 0)
continue;
if (rvt == 0) {
#ifdef NISDB_LDAP_DEBUG
abort();
#else
sfree(dnt);
sfree(odnt);
continue;
#endif /* NISDB_LDAP_DEBUG */
}
/*
* Create a request to get a rule-value with
* NIS+ data translated to LDAP equivalents.
*/
ls = createLdapRequest(tp[n], rvt, 0, 0, NULL, NULL);
if (ls == 0) {
if (ret == LDAP_SUCCESS)
ret = LDAP_OPERATIONS_ERROR;
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Unable to map to LDAP attrs for %s:dn=%s",
myself, NIL(tp[n]->dbId), dnt);
sfree(dnt);
freeRuleValue(rvt, 1);
continue;
}
freeLdapSearch(ls);
/*
* If the DN is the same as one we already know
* about, merge the rule-values.
*/
if ((iq == 0 || !firstOnly) && dnt != 0) {
dni = am(myself, sizeof (*dni));
if (dni != 0) {
dni->item.name = dnt;
dni->index = idx + n;
dni->oldDn = odnt;
} else {
logmsg(MSG_NOTIMECHECK, LOG_WARNING,
"%s: Skipping update for dn=\"%s\"",
myself, dnt);
sfree(dnt);
dnt = 0;
}
if (dnt != 0) {
dn[idx+n] = dnt;
odn[idx+n] = odnt;
rv[idx+n] = rvt;
rnq++;
} else {
freeRuleValue(rvt, 1);
rvt = 0;
}
} else if (dnt != 0) {
sfree(dnt);
sfree(odnt);
freeRuleValue(rvt, 1);
}
}
sfree(tp);
}
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s: %d update%s requested",
myself, NIL(tm->objName), rnq, rnq != 1 ? "s" : "");
/* Perform the updates */
for (i = rnq = 0; i < (firstOnly ? maxMatches : nq*maxMatches); i++) {
int delPerDbId;
if (dn[i] == 0)
continue;
#ifdef NISDB_LDAP_DEBUG
logmsg(MSG_NOTIMECHECK, LOG_INFO,
"%s: %s %s:dn=%s",
myself,
(new != 0 && new[i/maxMatches] != 0) ?
"modify" : "delete",
NIL(tpa[i]->dbId), dn[i]);
#endif /* NISDB_LDAP_DEBUG */
delPerDbId = (tpa[i]->objectDN->delDisp == dd_perDbId);
if ((new != 0 && new[i/maxMatches] != 0) || delPerDbId) {
/*
* Try to modify/create the specified DN. First,
* however, if the update changes the DN, make
* that change.
*/
if (odn[i] == 0 || (r = ldapChangeDN(odn[i], dn[i])) ==
LDAP_SUCCESS) {
int addFirst;
addFirst = (new != 0 &&
new[i/maxMatches] != 0 &&
!delPerDbId);
r = ldapModify(dn[i], rv[i],
tpa[i]->objectDN->write.attrs,
addFirst);
}
} else {
/* Try to delete the specified DN */
r = ldapModify(dn[i], 0,
tpa[i]->objectDN->write.attrs, 0);
}
if (r == LDAP_SUCCESS) {
rnq++;
} else {
if (ret == LDAP_SUCCESS)
ret = r;
logmsg(MSG_NOTIMECHECK, LOG_ERR,
"%s: LDAP %s request error %d for %s:dn=%s",
myself,
(new != 0 && new[i/maxMatches] != 0) ?
"modify" : "delete",
r, NIL(tpa[i]->dbId), dn[i]);
}
sfree(dn[i]);
dn[i] = 0;
freeRuleValue(rv[i], 1);
rv[i] = 0;
}
sfree(dn);
sfree(odn);
sfree(rv);
sfree(tpa);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s: %d update%s performed",
myself, NIL(tm->objName), rnq, rnq != 1 ? "s" : "");
return (ret);
}
/*
* In nis2ldap, if 'name' is provided then check if its value in 'val'
* matches the selector index. If 'name' is NULL, then check if rule-value 'rv'
* matches the index.
* To match the selector index, all fieldspecs in the indexlist should match
* (AND). In nis2ldap, an exception is, if there are multiple fieldspecs with
* the same fieldname then only one of them needs to match (OR).
* Example:
* Indexlist = [host="H*", host="I*", user="U*", domain="D*"]
* Then,
* host = "H1", user="U1", domain="D1" ==> pass
* host = "I1", user="U1", domain="D1" ==> pass
* host = "X1", user="U1", domain="D1" ==> fail
* host = "H1", user="X1", domain="D1" ==> fail
* host = "H1", user="U1" ==> fail
*
* Return 1 in case of a match, 0 otherwise.
*/
int
verifyIndexMatch(__nis_table_mapping_t *x, db_query *q,
__nis_rule_value_t *rv, char *name, char *val) {
int i, j, k, match = 1;
const char *myself = "verifyIndexMatch";
/*
* The pass and fail arrays are used by N2L to keep track of
* index matches. This saves us from having matches in a
* nested loop to decide OR or AND.
*/
int ppos, fpos;
char **pass, **fail;
if (x == 0)
return (0);
/* Trivial match */
if (x->index.numIndexes <= 0 || (!yp2ldap && q == 0))
return (1);
if (yp2ldap) {
if (!(pass = am(myself, x->index.numIndexes * sizeof (char *))))
return (0);
if (!(fail = am(myself,
x->index.numIndexes * sizeof (char *)))) {
sfree(pass);
return (0);
}
ppos = fpos = 0;
}
/* Check each index */
for (i = 0; i < x->index.numIndexes; i++) {
int len = 0;
char *value = 0;
/* Skip NULL index names */
if (x->index.name[i] == 0)
continue;
/* Check N2L values */
if (yp2ldap) {
if (name) {
if (strcasecmp(x->index.name[i], name) == 0)
value = val;
else
continue;
} else if (rv) {
if (strcasecmp(x->index.name[i], N2LKEY) == 0 ||
strcasecmp(x->index.name[i], N2LIPKEY)
== 0)
continue;
value = findVal(x->index.name[i], rv,
mit_nis);
}
if (value && verifyMappingMatch(x->index.value[i],
value))
pass[ppos++] = x->index.name[i];
else
fail[fpos++] = x->index.name[i];
continue;
}
/* Is the index name a known column ? */
for (j = 0; j < x->numColumns; j++) {
if (strcmp(x->index.name[i], x->column[j]) == 0) {
/*
* Do we have a value for the column ?
*/
for (k = 0; k < q->components.components_len;
k++) {
if (q->components.components_val[k].
which_index == j) {
value = q->components.
components_val[k].
index_value->
itemvalue.
itemvalue_val;
len = q->components.
components_val[k].
index_value->
itemvalue.
itemvalue_len;
break;
}
}
if (value != 0)
break;
}
}
/*
* If we found a value, check if it matches the
* format. If no value found or no match, this
* mapping is _not_ an alternative. Otherwise,
* we continue checking any other indexes.
*/
if (value == 0 ||
!verifyMappingMatch(x->index.value[i],
value)) {
match = 0;
break;
}
}
if (yp2ldap) {
for (--fpos; fpos >= 0; fpos--) {
for (i = 0; i < ppos; i++) {
if (strcmp(pass[i], fail[fpos]) == 0)
break;
}
if (i == ppos) {
match = 0;
break;
}
}
sfree(pass);
sfree(fail);
}
return (match);
}
/*
* Return all table mappings that match the column values in 'q'.
* If there's no match, return those alternative mappings that don't
* have an index; if no such mapping exists, return NULL.
*
* If 'wantWrite' is set, we want mappings for writing (i.e., data
* to LDAP); otherwise, we want mappings for reading.
*
* If 'wantObj' is set, we want object mappings only (i.e., _not_
* those used to map entries in tables).
*
* If 'dbId' is non-NULL, we select mappings with a matching dbId field.
*/
__nis_table_mapping_t **
selectTableMapping(__nis_table_mapping_t *t, db_query *q,
int wantWrite, int wantObj, char *dbId,
int *numMatches) {
__nis_table_mapping_t *r, *x, **tp;
int i, j, k, nm, numap;
const char *myself = "selectTableMapping";
if (numMatches == 0)
numMatches = &nm;
/*
* Count the number of possible mappings, so that we can
* allocate the 'tp' array up front.
*/
for (numap = 0, x = t; x != 0; numap++, x = x->next);
if (numap == 0) {
*numMatches = 0;
return (0);
}
tp = am(myself, numap * sizeof (tp[0]));
if (tp == 0) {
*numMatches = -1;
return (0);
}
/*
* Special cases:
*
* q == 0 trivially matches any 't' of the correct object type
*
* wantObj != 0 means we ignore 'q'
*/
if (q == 0 || wantObj) {
for (i = 0, x = t, nm = 0; i < numap; i++, x = x->next) {
if (x->objectDN == 0)
continue;
if (wantWrite) {
if (x->objectDN->write.scope ==
LDAP_SCOPE_UNKNOWN)
continue;
} else {
if (x->objectDN->read.scope ==
LDAP_SCOPE_UNKNOWN)
continue;
}
if (wantObj) {
if (x->numColumns > 0)
continue;
} else {
if (x->numColumns <= 0)
continue;
}
if (dbId != 0 && x->dbId != 0 &&
strcmp(dbId, x->dbId) != 0)
continue;
tp[nm] = x;
nm++;
}
*numMatches = nm;
if (nm == 0) {
sfree(tp);
tp = 0;
}
return (tp);
}
/* Scan all mappings, and collect candidates */
for (nm = 0, r = 0, x = t; x != 0; x = x->next) {
if (x->objectDN == 0)
continue;
if (wantWrite) {
if (x->objectDN->write.scope == LDAP_SCOPE_UNKNOWN)
continue;
} else {
if (x->objectDN->read.scope == LDAP_SCOPE_UNKNOWN)
continue;
}
/* Only want table/entry mappings */
if (x->numColumns <= 0)
continue;
if (dbId != 0 && x->dbId != 0 &&
strcmp(dbId, x->dbId) != 0)
continue;
/*
* It's a match if: there are no indexes, or we actually
* match the query with the indexes.
*/
if (x->index.numIndexes <= 0 ||
verifyIndexMatch(x, q, 0, 0, 0)) {
tp[nm] = x;
nm++;
}
}
if (nm == 0) {
free(tp);
tp = 0;
}
*numMatches = nm;
return (tp);
}
/*
* Return 1 if there's an indexed mapping, 0 otherwise.
*/
int
haveIndexedMapping(__nis_table_mapping_t *t) {
__nis_table_mapping_t *x;
for (x = t; x != 0; x = x->next) {
if (x->index.numIndexes > 0)
return (1);
}
return (0);
}
/*
* Given an input string 'attrs' of the form "attr1=val1,attr2=val2,...",
* or a filter, return the value associated with the attribute 'attrName'.
* If no instance of 'attrName' is found, return 'default'. In all cases,
* the return value is a copy, and must be freed by the caller.
*
* Of course, return NULL in case of failure.
*/
static char *
attrVal(const char *msg, char *attrName, char *def, char *attrs) {
char *val, *filter, **fc = 0;
int i, nfc;
const char *myself = "attrVal";
if (attrName == 0 || attrs == 0)
return (0);
if (msg == 0)
msg = myself;
val = def;
filter = makeFilter(attrs);
if (filter != 0 && (fc = makeFilterComp(filter, &nfc)) != 0 &&
nfc > 0) {
for (i = 0; i < nfc; i++) {
char *name, *value;
name = fc[i];
/* Skip if not of attr=value form */
if ((value = strchr(name, '=')) == 0)
continue;
*value = '\0';
value++;
if (strcasecmp(attrName, name) == 0) {
val = value;
break;
}
}
}
if (val != 0)
val = sdup(msg, T, val);
sfree(filter);
freeFilterComp(fc, nfc);
return (val);
}
extern bool_t xdr_nis_object(register XDR *xdrs, nis_object *objp);
/*
* Copy an XDR:ed version of the NIS+ object 'o' (or the one indicated
* by 't->objName' if 'o' is NULL) to the place indicated by
* 't->objectDN->write'. Return an appropriate LDAP status code.
*/
int
objToLDAP(__nis_table_mapping_t *t, nis_object *o, entry_obj **ea, int numEa) {
__nis_table_mapping_t **tp;
XDR xdr;
char *objName;
int stat, osize, n, numMatches = 0;
void *buf;
__nis_rule_value_t *rv;
__nis_value_t *val;
__nis_single_value_t *sv;
char **attrName, *dn;
const char *myself = "objToLDAP";
if (t == 0)
return (LDAP_PARAM_ERROR);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s", myself, NIL(t->objName));
tp = selectTableMapping(t, 0, 1, 1, 0, &numMatches);
if (tp == 0 || numMatches <= 0) {
sfree(tp);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s (no mapping)", myself, NIL(t->objName));
return (LDAP_SUCCESS);
}
for (n = 0; n < numMatches; n++) {
t = tp[n];
if (o == 0) {
sfree(tp);
return (LDAP_OPERATIONS_ERROR);
}
buf = (char *)xdrNisObject(o, ea, numEa, &osize);
if (buf == 0) {
sfree(tp);
return (LDAP_OPERATIONS_ERROR);
}
/*
* Prepare to build a rule-value containing the XDR:ed
* object
*/
rv = am(myself, sizeof (*rv));
sv = am(myself, sizeof (*sv));
val = am(myself, sizeof (*val));
attrName = am(myself, sizeof (attrName[0]));
if (attrName != 0)
attrName[0] = attrVal(myself, "nisObject",
"nisObject",
t->objectDN->write.attrs);
if (rv == 0 || sv == 0 || val == 0 || attrName == 0 ||
attrName[0] == 0) {
sfree(tp);
sfree(buf);
sfree(rv);
sfree(sv);
sfree(val);
sfree(attrName);
return (LDAP_NO_MEMORY);
}
sv->length = osize;
sv->value = buf;
/* 'vt_ber' just means "not a NUL-terminated string" */
val->type = vt_ber;
val->repeat = 0;
val->numVals = 1;
val->val = sv;
rv->numAttrs = 1;
rv->attrName = attrName;
rv->attrVal = val;
/*
* The 'write.base' is the actual DN of the entry (and the
* scope had better be 'base', but we don't check that).
*/
dn = t->objectDN->write.base;
stat = ldapModify(dn, rv, t->objectDN->write.attrs, 1);
freeRuleValue(rv, 1);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s (%s)", myself, NIL(t->objName), ldap_err2string(stat));
if (stat != LDAP_SUCCESS)
break;
}
sfree(tp);
return (stat);
}
/*
* Retrieve a copy of the 't->objName' object from LDAP, where it's
* stored in XDR:ed form in the place indicated by 't->objectDN->read'.
* Un-XDR the object, and return a pointer to it in '*obj'; it's the
* responsibility of the caller to free the object when it's no
* longer needed.
*
* Returns an appropriate LDAP status.
*/
int
objFromLDAP(__nis_table_mapping_t *t, nis_object **obj,
entry_obj ***eaP, int *numEaP) {
__nis_table_mapping_t **tp;
XDR xdr;
nis_object *o;
__nis_rule_value_t *rv;
__nis_ldap_search_t *ls;
char *attrs[2], *filter, **fc = 0;
void *buf;
int i, j, nfc, nrv, blen, stat = LDAP_SUCCESS;
int n, numMatches;
const char *myself = "objFromLDAP";
if (t == 0)
return (LDAP_PARAM_ERROR);
/*
* If there's nowhere to store the result, we might as
* well pretend all went well, and return right away.
*/
if (obj == 0)
return (LDAP_SUCCESS);
/* Prepare for the worst */
*obj = 0;
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s", myself, NIL(t->objName));
tp = selectTableMapping(t, 0, 0, 1, 0, &numMatches);
if (tp == 0 || numMatches <= 0) {
sfree(tp);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s (no mapping)", myself, NIL(t->objName));
return (LDAP_SUCCESS);
}
for (n = 0; n < numMatches; n++) {
t = tp[n];
filter = makeFilter(t->objectDN->read.attrs);
if (filter == 0 || (fc = makeFilterComp(filter, &nfc)) == 0 ||
nfc <= 0) {
sfree(tp);
sfree(filter);
freeFilterComp(fc, nfc);
return ((t->objectDN->read.attrs != 0) ?
LDAP_NO_MEMORY : LDAP_PARAM_ERROR);
}
/* Don't need the filter, just the components */
sfree(filter);
/*
* Look for a "nisObject" attribute, and (if found) copy
* the value to attrs[0]. Also remove the "nisObject"
* attribute and value from the filter components.
*/
attrs[0] = sdup(myself, T, "nisObject");
if (attrs[0] == 0) {
sfree(tp);
freeFilterComp(fc, nfc);
return (LDAP_NO_MEMORY);
}
attrs[1] = 0;
for (i = 0; i < nfc; i++) {
char *name, *value;
int compare;
name = fc[i];
/* Skip if not of attr=value form */
if ((value = strchr(name, '=')) == 0)
continue;
/* Temporarily overWrite the '=' with a '\0' */
*value = '\0';
/* Compare with our target attribute name */
compare = strcasecmp("nisObject", name);
/* Put back the '=' */
*value = '=';
/* Is it the name we're looking for ? */
if (compare == 0) {
sfree(attrs[0]);
attrs[0] = sdup(myself, T, value+1);
if (attrs[0] == 0) {
sfree(tp);
freeFilterComp(fc, nfc);
return (LDAP_NO_MEMORY);
}
sfree(fc[i]);
if (i < nfc-1)
(void) memmove(&fc[i], &fc[i+1],
(nfc-1-i) * sizeof (fc[i]));
nfc--;
break;
}
}
ls = buildLdapSearch(t->objectDN->read.base,
t->objectDN->read.scope,
nfc, fc, 0, attrs, 0, 1);
sfree(attrs[0]);
freeFilterComp(fc, nfc);
if (ls == 0) {
sfree(tp);
return (LDAP_OPERATIONS_ERROR);
}
nrv = 0;
rv = ldapSearch(ls, &nrv, 0, &stat);
if (rv == 0) {
sfree(tp);
freeLdapSearch(ls);
return (stat);
}
for (i = 0, buf = 0; i < nrv && buf == 0; i++) {
for (j = 0; j < rv[i].numAttrs; j++) {
if (strcasecmp(ls->attrs[0],
rv[i].attrName[j]) == 0) {
if (rv[i].attrVal[j].numVals <= 0)
continue;
buf = rv[i].attrVal[j].val[0].value;
blen = rv[i].attrVal[j].val[0].length;
break;
}
}
}
if (buf != 0) {
o = unXdrNisObject(buf, blen, eaP, numEaP);
if (o == 0) {
sfree(tp);
freeLdapSearch(ls);
freeRuleValue(rv, nrv);
return (LDAP_OPERATIONS_ERROR);
}
stat = LDAP_SUCCESS;
*obj = o;
} else {
stat = LDAP_NO_SUCH_OBJECT;
}
freeLdapSearch(ls);
freeRuleValue(rv, nrv);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s (%s)", myself, NIL(t->objName), ldap_err2string(stat));
if (stat != LDAP_SUCCESS)
break;
}
sfree(tp);
return (stat);
}
int
deleteLDAPobj(__nis_table_mapping_t *t) {
__nis_table_mapping_t **tp;
int n, stat, numMatches = 0;
const char *myself = "deleteLDAPobj";
if (t == 0)
return (LDAP_PARAM_ERROR);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s", myself, NIL(t->objName));
tp = selectTableMapping(t, 0, 1, 1, 0, &numMatches);
if (tp == 0 || numMatches <= 0) {
sfree(tp);
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s (no mapping)", myself, NIL(t->objName));
return (LDAP_SUCCESS);
}
for (n = 0; n < numMatches; n++) {
t = tp[n];
if (t->objectDN->delDisp == dd_always) {
/* Delete entire entry */
stat = ldapModify(t->objectDN->write.base, 0,
t->objectDN->write.attrs, 1);
} else if (t->objectDN->delDisp == dd_perDbId) {
/*
* Delete the attribute holding the object.
* First, determine what that attribute is called.
*/
char *attrName =
attrVal(myself,
"nisObject",
"nisObject",
t->objectDN->write.attrs);
__nis_rule_value_t rv;
__nis_value_t val;
if (attrName == 0) {
sfree(tp);
return (LDAP_NO_MEMORY);
}
/*
* Build a __nis_value_t with 'numVals' < 0 to
* indicate deletion.
*/
val.type = vt_ber;
val.numVals = -1;
val.val = 0;
/*
* Build a rule-value with the name we determined
* above, and the deletion value.
*/
(void) memset(&rv, 0, sizeof (rv));
rv.numAttrs = 1;
rv.attrName = &attrName;
rv.attrVal = &val;
stat = ldapModify(t->objectDN->write.base, &rv,
t->objectDN->write.attrs, 0);
sfree(attrName);
} else if (t->objectDN->delDisp == dd_never) {
/* Nothing to do, so we're trivially successful */
stat = LDAP_SUCCESS;
} else {
stat = LDAP_PARAM_ERROR;
}
logmsg(MSG_NOTIMECHECK,
#ifdef NISDB_LDAP_DEBUG
LOG_WARNING,
#else
LOG_INFO,
#endif /* NISDB_LDAP_DEBUG */
"%s: %s (%s)", myself, NIL(t->objName), ldap_err2string(stat));
/* If there were no such object, we've trivially succeeded */
if (stat == LDAP_NO_SUCH_OBJECT)
stat = LDAP_SUCCESS;
if (stat != LDAP_SUCCESS)
break;
}
sfree(tp);
return (stat);
}