Home | History | Annotate | Download | only in libnisdb
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License, Version 1.0 only
      6  * (the "License").  You may not use this file except in compliance
      7  * with the License.
      8  *
      9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10  * or http://www.opensolaris.org/os/licensing.
     11  * See the License for the specific language governing permissions
     12  * and limitations under the License.
     13  *
     14  * When distributing Covered Code, include this CDDL HEADER in each
     15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16  * If applicable, add the following below this CDDL HEADER, with the
     17  * fields enclosed by brackets "[]" replaced with your own identifying
     18  * information: Portions Copyright [yyyy] [name of copyright owner]
     19  *
     20  * CDDL HEADER END
     21  */
     22 /*
     23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     28 
     29 #include <strings.h>
     30 #include <sys/types.h>
     31 #include <sys/stat.h>
     32 #include <errno.h>
     33 #include <stdio.h>
     34 #include <rpcsvc/nis.h>
     35 #include <rpc/xdr.h>
     36 
     37 #include "ldap_util.h"
     38 #include "ldap_attr.h"
     39 #include "ldap_ruleval.h"
     40 #include "ldap_op.h"
     41 #include "ldap_map.h"
     42 #include "ldap_nisplus.h"
     43 #include "ldap_glob.h"
     44 #include "ldap_xdr.h"
     45 #include "ldap_val.h"
     46 
     47 /* From yptol/dit_access_utils.h */
     48 #define	N2LKEY		"rf_key"
     49 #define	N2LIPKEY	"rf_ipkey"
     50 
     51 __nis_hash_table_mt	ldapMappingList = NIS_HASH_TABLE_MT_INIT;
     52 extern	int yp2ldap;
     53 
     54 
     55 int
     56 setColumnNames(__nis_table_mapping_t *t) {
     57 	int	i, j, nic, noc, stat;
     58 	char	**col;
     59 	zotypes	type;
     60 	char	*myself = "setColumnNames";
     61 
     62 	if (t == 0)
     63 		return (0);
     64 
     65 	type = t->objType;
     66 	col = t->column;
     67 	nic = (col != 0) ? t->numColumns : -1;
     68 
     69 	t->objType = NIS_BOGUS_OBJ;
     70 	t->obj = 0;
     71 
     72 	stat = initializeColumnNames(t->objName, &t->column, &t->numColumns,
     73 					&t->objType, &t->obj);
     74 	if (stat == LDAP_OBJECT_CLASS_VIOLATION) {
     75 		/* Not a table object; that's OK */
     76 		sfree(col);
     77 		return (0);
     78 	} else if (justTesting && stat != LDAP_SUCCESS) {
     79 		/*
     80 		 * Restore the parser initialization. This will only work
     81 		 * correctly if the config file is consistent in the ordering
     82 		 * of column names, and either no NIS+ lookups are needed,
     83 		 * or the ordering is the same as in NIS+.
     84 		 */
     85 		t->column = col;
     86 		t->numColumns = nic;
     87 		/* Make a guess at the object type, based on the name */
     88 		if (strstr(t->objName, ".org_dir") != 0)
     89 			t->objType = NIS_TABLE_OBJ;
     90 		else if (strncmp(t->objName, "admin.groups_dir",
     91 				sizeof ("admin.groups_dir")) != 0)
     92 			t->objType = NIS_GROUP_OBJ;
     93 		else
     94 			t->objType = NIS_DIRECTORY_OBJ;
     95 		return (0);
     96 	}
     97 
     98 	/*
     99 	 * If it's a table object, but there are no translation rules,
    100 	 * this mapping is for the table object itself. In that case,
    101 	 * we throw away the column names (if any).
    102 	 */
    103 	if (t->objType == NIS_TABLE_OBJ && t->numRulesFromLDAP == 0 &&
    104 			t->numRulesToLDAP == 0) {
    105 		for (i = 0; i < t->numColumns; i++)
    106 			sfree(t->column[i]);
    107 		sfree(t->column);
    108 		t->column = 0;
    109 		t->numColumns = 0;
    110 		noc = 0;
    111 	}
    112 
    113 	/*
    114 	 * Verify that all column names found by the parser
    115 	 * are present in the actual column list.
    116 	 */
    117 	if (verbose) {
    118 		for (i = 0, noc = 0; i < nic; i++) {
    119 			int	found = 0;
    120 
    121 			if (col[i] == 0)
    122 				continue;
    123 			/* Skip the 'zo_*' special column names */
    124 			if (isObjAttrString(col[i]))
    125 				continue;
    126 			for (j = 0; j < t->numColumns; j++) {
    127 				if (strcmp(col[i], t->column[j]) == 0) {
    128 					noc++;
    129 					found = 1;
    130 					break;
    131 				}
    132 			}
    133 			if (!found) {
    134 				logmsg(MSG_NOTIMECHECK, LOG_WARNING,
    135 					"%s: No column \"%s\" in \"%s\"",
    136 					myself, NIL(col[i]), NIL(t->objName));
    137 			}
    138 		}
    139 	}
    140 
    141 	/* Remove any setup by the parser */
    142 	for (i = 0; i < nic; i++) {
    143 		sfree(col[i]);
    144 	}
    145 	sfree(col);
    146 
    147 	return (0);
    148 }
    149 
    150 void
    151 freeSingleObjAttr(__nis_obj_attr_t *attr) {
    152 	if (attr == 0)
    153 		return;
    154 
    155 	sfree(attr->zo_owner);
    156 	sfree(attr->zo_group);
    157 	sfree(attr->zo_domain);
    158 	sfree(attr);
    159 }
    160 
    161 void
    162 freeObjAttr(__nis_obj_attr_t **attr, int numAttr) {
    163 	int	i;
    164 
    165 	if (attr == 0)
    166 		return;
    167 
    168 	for (i = 0; i < numAttr; i++) {
    169 		freeSingleObjAttr(attr[i]);
    170 	}
    171 
    172 	sfree(attr);
    173 }
    174 
    175 __nis_obj_attr_t *
    176 cloneObjAttr(__nis_obj_attr_t *old) {
    177 	__nis_obj_attr_t	*new;
    178 	char			*myself = "cloneObjAttr";
    179 
    180 	if (old == 0)
    181 		return (0);
    182 
    183 	new = am(myself, sizeof (*new));
    184 	if (new == 0)
    185 		return (0);
    186 
    187 	new->zo_owner = sdup(myself, T, old->zo_owner);
    188 	if (new->zo_owner == 0 && old->zo_owner != 0)
    189 		goto cleanup;
    190 
    191 	new->zo_group = sdup(myself, T, old->zo_group);
    192 	if (new->zo_group == 0 && old->zo_group != 0)
    193 		goto cleanup;
    194 
    195 	new->zo_domain = sdup(myself, T, old->zo_domain);
    196 	if (new->zo_domain == 0 && old->zo_domain != 0)
    197 		goto cleanup;
    198 
    199 	new->zo_access = old->zo_access;
    200 	new->zo_ttl = old->zo_ttl;
    201 
    202 	return (new);
    203 
    204 cleanup:
    205 	freeSingleObjAttr(new);
    206 
    207 	return (0);
    208 }
    209 
    210 
    211 /*
    212  * Obtain NIS+ entries (in the form of db_query's) from the supplied table
    213  * mapping and db_query.
    214  *
    215  * If 'qin' is NULL, enumeration is desired.
    216  *
    217  * On exit, '*numQueries' contains the number of (db_query *)'s in the
    218  * return array, '*ldapStat' the LDAP operation status, and '*objAttr'
    219  * a pointer to an array (of '*numQueries elements) of object attributes
    220  * (zo_owner, etc.). If no object attributes were retrieved, '*objAttr'
    221  * is NULL; any and all of the (*objAttr)[i]'s may be NULL.
    222  */
    223 db_query **
    224 mapFromLDAP(__nis_table_mapping_t *t, db_query *qin, int *numQueries,
    225 		char *dbId, int *ldapStat, __nis_obj_attr_t ***objAttr) {
    226 	__nis_table_mapping_t	**tp;
    227 	db_query		**q;
    228 	__nis_rule_value_t	*rv;
    229 	__nis_ldap_search_t	*ls;
    230 	int			n, numVals, numMatches = 0;
    231 	int			stat;
    232 	__nis_obj_attr_t	**attr;
    233 	char			*myself = "mapFromLDAP";
    234 
    235 	if (ldapStat == 0)
    236 		ldapStat = &stat;
    237 
    238 	if (t == 0 || numQueries == 0) {
    239 		*ldapStat = LDAP_PARAM_ERROR;
    240 		return (0);
    241 	}
    242 
    243 	/* Select the correct table mapping(s) */
    244 	tp = selectTableMapping(t, qin, 0, 0, dbId, &numMatches);
    245 	if (tp == 0 || numMatches <= 0) {
    246 		/*
    247 		 * Not really an error; just no matching mapping
    248 		 * for the query.
    249 		 */
    250 		*ldapStat = LDAP_SUCCESS;
    251 		return (0);
    252 	}
    253 
    254 	q = 0;
    255 	attr = 0;
    256 
    257 	/* For each mapping */
    258 	for (numVals = 0, n = 0; n < numMatches; n++) {
    259 		db_query		**qt;
    260 		int			i, nqt = 0, filterOnQin, res = 0;
    261 
    262 		t = tp[n];
    263 
    264 		if (qin != 0) {
    265 			rv = buildNisPlusRuleValue(t, qin, 0);
    266 			if (rv != 0) {
    267 				/*
    268 				 * Depending on the value of res, we shall
    269 				 * proceed to next table mapping.
    270 				 */
    271 				ls = createLdapRequest(t, rv, 0, 1, &res, NULL);
    272 			}
    273 			else
    274 				ls = 0;
    275 		} else {
    276 			/* Build enumeration request */
    277 			rv = 0;
    278 			ls = createLdapRequest(t, 0, 0, 1, NULL, NULL);
    279 		}
    280 
    281 		freeRuleValue(rv, 1);
    282 
    283 		if (ls == 0) {
    284 			/*
    285 			 * if the res is NP_LDAP_RULES_NO_VALUE, that means we
    286 			 * have enough NIS+ columns for the rules to produce
    287 			 * values, but none of them did, so continue to the
    288 			 * next table mapping. Otherwise do cleanup and return
    289 			 * error.
    290 			 */
    291 			if (res == NP_LDAP_RULES_NO_VALUE)
    292 				continue;
    293 			for (i = 0; i < numVals; i++)
    294 				freeQuery(q[i]);
    295 			sfree(q);
    296 			free(tp);
    297 			*ldapStat = LDAP_OPERATIONS_ERROR;
    298 			return (0);
    299 		}
    300 
    301 		/* Query LDAP */
    302 		nqt = (ls->isDN || qin != 0) ? 0 : -1;
    303 		rv = ldapSearch(ls, &nqt, 0, ldapStat);
    304 
    305 		/*
    306 		 * If qin != 0, then we need to make sure that the
    307 		 * LDAP search is filtered so that only entries that
    308 		 * are compatible with 'qin' are retained. This will
    309 		 * happen automatically if we do a DN search (in which
    310 		 * case, no need to filter on 'qin').
    311 		 */
    312 		if (ls->isDN || qin == 0)
    313 			filterOnQin = 0;
    314 		else
    315 			filterOnQin = 1;
    316 
    317 		freeLdapSearch(ls);
    318 
    319 		/* Convert rule-values to db_query's */
    320 		if (rv != 0 && nqt > 0) {
    321 			int			nrv = nqt;
    322 			__nis_obj_attr_t	**at = 0;
    323 
    324 			qt = ruleValue2Query(t, rv,
    325 				(filterOnQin) ? qin : 0, &at, &nqt);
    326 			freeRuleValue(rv, nrv);
    327 
    328 			if (qt != 0 && q == 0) {
    329 				q = qt;
    330 				attr = at;
    331 				numVals = nqt;
    332 			} else if (qt != 0) {
    333 				db_query		**tmp;
    334 				__nis_obj_attr_t	**atmp;
    335 
    336 				/* Extend the 'q' array */
    337 				tmp = realloc(q,
    338 					(numVals+nqt) * sizeof (q[0]));
    339 				/* ... and the 'attr' array */
    340 				atmp = realloc(attr,
    341 					(numVals+nqt) * sizeof (attr[0]));
    342 				if (tmp == 0 || atmp == 0) {
    343 					logmsg(MSG_NOMEM, LOG_ERR,
    344 						"%s: realloc(%d) => NULL",
    345 						myself,
    346 						(numVals+nqt) * sizeof (q[0]));
    347 					for (i = 0; i < numVals; i++)
    348 						freeQuery(q[i]);
    349 					for (i = 0; i < nqt; i++)
    350 						freeQuery(qt[i]);
    351 					sfree(tmp);
    352 					sfree(atmp);
    353 					sfree(q);
    354 					sfree(qt);
    355 					sfree(tp);
    356 					freeObjAttr(at, nqt);
    357 					freeObjAttr(attr, numVals);
    358 					*ldapStat = LDAP_NO_MEMORY;
    359 					return (0);
    360 				}
    361 				q = tmp;
    362 				attr = atmp;
    363 				/* Add the results for this 't' */
    364 				(void) memcpy(&q[numVals], qt,
    365 						nqt * sizeof (qt[0]));
    366 				(void) memcpy(&attr[numVals], at,
    367 						nqt * sizeof (at[0]));
    368 				numVals += nqt;
    369 
    370 				sfree(qt);
    371 				sfree(at);
    372 			}
    373 		}
    374 	}
    375 
    376 	*numQueries = numVals;
    377 	if (objAttr != 0)
    378 		*objAttr = attr;
    379 	else
    380 		freeObjAttr(attr, numVals);
    381 	sfree(tp);
    382 
    383 	return (q);
    384 }
    385 
    386 /*
    387  * Add the object attributes (zo_owner, etc.) to the rule-value 'rv'.
    388  * Returns a pointer to the (possibly newly allocated) rule-value,
    389  * or NULL in case of failure. If not returning 'rvIn', the latter
    390  * will have been freed.
    391  */
    392 __nis_rule_value_t *
    393 addObjAttr2RuleValue(nis_object *obj, __nis_rule_value_t *rvIn) {
    394 	__nis_rule_value_t	*rv;
    395 	char			abuf[2 * sizeof (obj->zo_access) + 1];
    396 	char			tbuf[2 * sizeof (obj->zo_ttl) + 1];
    397 
    398 	if (obj == 0)
    399 		return (0);
    400 
    401 	if (rvIn != 0) {
    402 		rv = rvIn;
    403 	} else {
    404 		rv = initRuleValue(1, 0);
    405 		if (rv == 0)
    406 			return (0);
    407 	}
    408 
    409 	if (obj->zo_owner != 0) {
    410 		if (addSCol2RuleValue("zo_owner", obj->zo_owner, rv) != 0) {
    411 			freeRuleValue(rv, 1);
    412 			return (0);
    413 		}
    414 	}
    415 
    416 	if (obj->zo_group != 0) {
    417 		if (addSCol2RuleValue("zo_group", obj->zo_group, rv) != 0) {
    418 			freeRuleValue(rv, 1);
    419 			return (0);
    420 		}
    421 	}
    422 
    423 	if (obj->zo_domain != 0) {
    424 		if (addSCol2RuleValue("zo_domain", obj->zo_domain, rv) != 0) {
    425 			freeRuleValue(rv, 1);
    426 			return (0);
    427 		}
    428 	}
    429 
    430 	(void) memset(abuf, 0, sizeof (abuf));
    431 	(void) memset(tbuf, 0, sizeof (tbuf));
    432 
    433 	sprintf(abuf, "%x", obj->zo_access);
    434 	sprintf(tbuf, "%x", obj->zo_ttl);
    435 
    436 	if (addSCol2RuleValue("zo_access", abuf, rv) != 0) {
    437 		freeRuleValue(rv, 1);
    438 		return (0);
    439 	}
    440 	if (addSCol2RuleValue("zo_ttl", tbuf, rv) != 0) {
    441 		freeRuleValue(rv, 1);
    442 		return (0);
    443 	}
    444 
    445 	return (rv);
    446 }
    447 
    448 /*
    449  * Returns a pointer to (NOT a copy of) the value for the specified
    450  * column 'col' in the rule-value 'rv'.
    451  */
    452 __nis_value_t *
    453 findColValue(char *col, __nis_rule_value_t *rv) {
    454 	int		i;
    455 
    456 	if (col == 0 || rv == 0 || rv->numColumns <= 0)
    457 		return (0);
    458 
    459 	for (i = 0; i < rv->numColumns; i++) {
    460 		if (strcmp(col, rv->colName[i]) == 0)
    461 			return (&rv->colVal[i]);
    462 	}
    463 
    464 	return (0);
    465 }
    466 
    467 /*
    468  * Return the NIS+ object attributes (if any) in the rule-value 'rv'.
    469  */
    470 __nis_obj_attr_t *
    471 ruleValue2ObjAttr(__nis_rule_value_t *rv) {
    472 	__nis_obj_attr_t	*attr;
    473 	__nis_value_t		*val;
    474 	char			*myself = "ruleValue2ObjAttr";
    475 
    476 	if (rv == 0 || rv->numColumns <= 0)
    477 		return (0);
    478 
    479 	attr = am(myself, sizeof (*attr));
    480 
    481 	if ((val = findColValue("zo_owner", rv)) != 0 &&
    482 			val->type == vt_string && val->numVals == 1 &&
    483 			val->val[0].value != 0) {
    484 		attr->zo_owner = sdup(myself, T, val->val[0].value);
    485 		if (attr->zo_owner == 0) {
    486 			freeSingleObjAttr(attr);
    487 			return (0);
    488 		}
    489 	}
    490 
    491 	if ((val = findColValue("zo_group", rv)) != 0 &&
    492 			val->type == vt_string && val->numVals == 1 &&
    493 			val->val[0].value != 0) {
    494 		attr->zo_group = sdup(myself, T, val->val[0].value);
    495 		if (attr->zo_group == 0) {
    496 			freeSingleObjAttr(attr);
    497 			return (0);
    498 		}
    499 	}
    500 
    501 	if ((val = findColValue("zo_domain", rv)) != 0 &&
    502 			val->type == vt_string && val->numVals == 1 &&
    503 			val->val[0].value != 0) {
    504 		attr->zo_domain = sdup(myself, T, val->val[0].value);
    505 		if (attr->zo_domain == 0) {
    506 			freeSingleObjAttr(attr);
    507 			return (0);
    508 		}
    509 	}
    510 
    511 	if ((val = findColValue("zo_access", rv)) != 0 &&
    512 			val->type == vt_string && val->numVals == 1 &&
    513 			val->val[0].value != 0) {
    514 		if (sscanf(val->val[0].value, "%x", &attr->zo_access) != 1) {
    515 			freeSingleObjAttr(attr);
    516 			return (0);
    517 		}
    518 	}
    519 
    520 	if ((val = findColValue("zo_ttl", rv)) != 0 &&
    521 			val->type == vt_string && val->numVals == 1 &&
    522 			val->val[0].value != 0) {
    523 		if (sscanf(val->val[0].value, "%x", &attr->zo_ttl) != 1) {
    524 			freeSingleObjAttr(attr);
    525 			return (0);
    526 		}
    527 	}
    528 
    529 	return (attr);
    530 }
    531 
    532 /*
    533  * If the supplied string is one of the object attributes, return one.
    534  * Otherwise, return zero.
    535  */
    536 int
    537 isObjAttrString(char *str) {
    538 	if (str == 0)
    539 		return (0);
    540 
    541 	if (strcmp("zo_owner", str) == 0 ||
    542 		strcmp("zo_group", str) == 0 ||
    543 		strcmp("zo_domain", str) == 0 ||
    544 		strcmp("zo_access", str) == 0 ||
    545 		strcmp("zo_ttl", str) == 0)
    546 		return (1);
    547 	else
    548 		return (0);
    549 }
    550 
    551 
    552 /*
    553  * If the supplied value is one of the object attribute strings, return
    554  * a pointer to the string. Otherwise, return NULL.
    555  */
    556 char *
    557 isObjAttr(__nis_single_value_t *val) {
    558 	if (val == 0 || val->length <= 0 || val->value == 0)
    559 		return (0);
    560 
    561 	if (isObjAttrString(val->value))
    562 		return (val->value);
    563 	else
    564 		return (0);
    565 }
    566 
    567 int
    568 setObjAttrField(char *attrName, __nis_single_value_t *val,
    569 		__nis_obj_attr_t **objAttr) {
    570 	__nis_obj_attr_t	*attr;
    571 	char			*myself = "setObjAttrField";
    572 
    573 	if (attrName == 0 || val == 0 || objAttr == 0 ||
    574 			val->value == 0 || val->length <= 0)
    575 		return (-1);
    576 
    577 	if (*objAttr != 0) {
    578 		attr = *objAttr;
    579 	} else {
    580 		attr = am(myself, sizeof (*attr));
    581 		if (attr == 0)
    582 			return (-2);
    583 		*objAttr = attr;
    584 	}
    585 
    586 	if (strcmp("zo_owner", attrName) == 0) {
    587 		if (attr->zo_owner == 0) {
    588 			attr->zo_owner = sdup(myself, T, val->value);
    589 			if (attr->zo_owner == 0)
    590 				return (-11);
    591 		}
    592 	} else if (strcmp("zo_group", attrName) == 0) {
    593 		if (attr->zo_group == 0) {
    594 			attr->zo_group = sdup(myself, T, val->value);
    595 			if (attr->zo_group == 0)
    596 				return (-12);
    597 		}
    598 	} else if (strcmp("zo_domain", attrName) == 0) {
    599 		if (attr->zo_domain == 0) {
    600 			attr->zo_domain = sdup(myself, T, val->value);
    601 			if (attr->zo_domain == 0)
    602 				return (-13);
    603 		}
    604 	} else if (strcmp("zo_access", attrName) == 0) {
    605 		if (attr->zo_access == 0) {
    606 			if (sscanf(val->value, "%x", &attr->zo_access) != 1)
    607 				return (-14);
    608 		}
    609 	} else if (strcmp("zo_ttl", attrName) == 0) {
    610 		if (attr->zo_ttl == 0) {
    611 			if (sscanf(val->value, "%x", &attr->zo_ttl) != 1)
    612 				return (-15);
    613 		}
    614 	}
    615 
    616 	return (0);
    617 }
    618 
    619 /*
    620  * Return a DN and rule-value for the supplied mapping, db_query's, and
    621  * input rule-value. This function only works on a single mapping. See
    622  * mapToLDAP() below for a description of the action depending on the
    623  * values of 'old' and 'new'.
    624  *
    625  * If both 'old' and 'new' are supplied, and the modify would result
    626  * in a change to the DN, '*oldDN' will contain the old DN. Otherwise
    627  * (and normally), '*oldDN' will be NULL.
    628  */
    629 char *
    630 map1qToLDAP(__nis_table_mapping_t *t, db_query *old, db_query *new,
    631 		__nis_rule_value_t *rvIn, __nis_rule_value_t **rvOutP,
    632 		char **oldDnP) {
    633 
    634 	__nis_rule_value_t	*rv, *rvt;
    635 	__nis_ldap_search_t	*ls;
    636 	char			*dn = 0, *oldDn = 0;
    637 	__nis_table_mapping_t	del;
    638 	char			*myself = "map1qToLDAP";
    639 
    640 	if (t == 0 || (old == 0 && new == 0) || rvOutP == 0)
    641 		return (0);
    642 
    643 	/*
    644 	 * If entry should be deleted, we look at the delete
    645 	 * policy in the table mapping. Should it specify a
    646 	 * rule set, we use that rule set to build a rule-
    647 	 * value, and the delete actually becomes a modify
    648 	 * operation.
    649 	 */
    650 	if (old != 0 && new == 0) {
    651 		if (t->objectDN->delDisp == dd_perDbId) {
    652 			/*
    653 			 * The functions that build a rule-value from a
    654 			 * rule set expect a __nis_table_mapping_t, but the
    655 			 * rule set in the __nis_object_dn_t isn't of that
    656 			 * form. So, build a pseudo-__nis_table_mapping_t that
    657 			 * borrows heavily from 't'.
    658 			 */
    659 			del = *t;
    660 
    661 			del.numRulesToLDAP = del.objectDN->numDbIds;
    662 			del.ruleToLDAP = del.objectDN->dbId;
    663 
    664 			/*
    665 			 * Do a modify with the pseudo-table
    666 			 * mapping, and the 'old' db_query
    667 			 * supplying input to the delete rule
    668 			 * set.
    669 			 */
    670 			t = &del;
    671 			new = old;
    672 		} else if (t->objectDN->delDisp == dd_always) {
    673 
    674 			/* Nothing to do here; all handled below */
    675 
    676 		} else if (t->objectDN->delDisp == dd_never) {
    677 
    678 			return (0);
    679 
    680 		} else {
    681 
    682 			logmsg(MSG_INVALIDDELDISP, LOG_WARNING,
    683 				"%s: Invalid delete disposition %d for \"%s\"",
    684 				myself, t->objectDN->delDisp,
    685 				NIL(t->dbId));
    686 			return (0);
    687 
    688 		}
    689 	}
    690 
    691 	/* Make a copy of the input rule-value */
    692 	if (rvIn != 0) {
    693 		rv = initRuleValue(1, rvIn);
    694 		if (rv == 0)
    695 			return (0);
    696 	} else {
    697 		rv = 0;
    698 	}
    699 
    700 	/* First get a rule-value from the supplied NIS+ entry. */
    701 	rvt = rv;
    702 	rv = buildNisPlusRuleValue(t, ((old != 0) ? old : new), rvt);
    703 	freeRuleValue(rvt, 1);
    704 	if (rv == 0) {
    705 		logmsg(MSG_NORULEVALUE, LOG_WARNING,
    706 			"%s: No in-query rule-value derived for \"%s\"",
    707 			myself, NIL(t->dbId));
    708 		return (0);
    709 	}
    710 
    711 	/*
    712 	 * Create a request (really only care about the DN) from the
    713 	 * supplied NIS+ entry data.
    714 	 */
    715 	ls = createLdapRequest(t, rv, &dn, 0, NULL, NULL);
    716 	if (ls == 0 || dn == 0) {
    717 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
    718 			"%s: Unable to create LDAP request for %s: %s",
    719 			myself, NIL(t->dbId),
    720 			(dn != 0) ? dn : rvId(rv, mit_nisplus));
    721 		sfree(dn);
    722 		freeLdapSearch(ls);
    723 		freeRuleValue(rv, 1);
    724 		return (0);
    725 	}
    726 
    727 	freeLdapSearch(ls);
    728 
    729 	if (new != 0) {
    730 		/*
    731 		 * Create a rule-value from the new NIS+ entry.
    732 		 * Don't want to mix in the rule-value derived
    733 		 * from 'old', so delete it. However, we still
    734 		 * want the owner, group, etc., from 'rvIn'.
    735 		 */
    736 		if (old != 0) {
    737 			freeRuleValue(rv, 1);
    738 			if (rvIn != 0) {
    739 				rv = initRuleValue(1, rvIn);
    740 				if (rv == 0) {
    741 					sfree(dn);
    742 					return (0);
    743 				}
    744 			} else {
    745 				rv = 0;
    746 			}
    747 		}
    748 		rvt = rv;
    749 		rv = buildNisPlusRuleValue(t, new, rvt);
    750 		freeRuleValue(rvt, 1);
    751 		if (rv == 0) {
    752 			logmsg(MSG_NORULEVALUE, LOG_WARNING,
    753 				"%s: No new rule-value derived for \"%s: %s\"",
    754 				myself, NIL(t->dbId), dn);
    755 			sfree(dn);
    756 			return (0);
    757 		}
    758 		/*
    759 		 * Check if the proposed modification would result in a
    760 		 * a change to the DN.
    761 		 */
    762 		if (old != 0) {
    763 			oldDn = dn;
    764 			dn = 0;
    765 			ls = createLdapRequest(t, rv, &dn, 0, NULL, NULL);
    766 			if (ls == 0 || dn == 0) {
    767 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
    768 				"%s: Unable to create new DN for \"%s: %s\"",
    769 					myself, NIL(t->dbId), oldDn);
    770 				sfree(oldDn);
    771 				freeLdapSearch(ls);
    772 				freeRuleValue(rv, 1);
    773 				return (0);
    774 			}
    775 			freeLdapSearch(ls);
    776 			if (strcasecmp(oldDn, dn) == 0) {
    777 				sfree(oldDn);
    778 				oldDn = 0;
    779 			}
    780 		}
    781 	}
    782 
    783 
    784 	*rvOutP = rv;
    785 	if (oldDnP != 0)
    786 		*oldDnP = oldDn;
    787 
    788 	return (dn);
    789 }
    790 
    791 /*
    792  * Since the DN hash list is an automatic variable, there's no need for
    793  * locking, and we remove the locking overhead by using the libnsl
    794  * hash functions.
    795  */
    796 #undef	NIS_HASH_ITEM
    797 #undef	NIS_HASH_TABLE
    798 #undef	nis_insert_item
    799 #undef	nis_find_item
    800 #undef	nis_pop_item
    801 #undef	nis_remove_item
    802 
    803 typedef struct {
    804 	NIS_HASH_ITEM	item;
    805 	int		index;
    806 	char		*oldDn;
    807 } __dn_item_t;
    808 
    809 /*
    810  * Update LDAP per the supplied table mapping and db_query's.
    811  *
    812  * 'nq' is the number of elements in the 'old', 'new', and 'rvIn'
    813  * arrays. mapToLDAP() generally performs one update for each
    814  * element; however, if one or more of the individual queries
    815  * produce the same DN, they're merged into a single update.
    816  *
    817  * There are four cases, depending on the values of 'old[iq]' and
    818  * 'new[iq]':
    819  *
    820  * (1)	old[iq] == 0 && new[iq] == 0
    821  *	No action; skip to next query
    822  *
    823  * (2)	old[iq] == 0 && new[iq] != 0
    824  *	Attempt to use the 'new' db_query to get a DN, and try to create
    825  *	the corresponding LDAP entry.
    826  *
    827  * (3)	old[iq] != 0 && new[iq] == 0
    828  *	Use the 'old' db_query to get a DN, and try to delete the LDAP
    829  *	entry per the table mapping.
    830  *
    831  * (4)	old[iq] != 0 && new[iq] != 0
    832  *	Use the 'old' db_query to get a DN, and update (possibly create)
    833  *	the corresponding LDAP entry per the 'new' db_query.
    834  *
    835  * If 'rvIn' is non-NULL, it is expected to contain the object attributes
    836  * (zo_owner, etc.) to be written to LDAP. 'rvIn' is an array with 'nq'
    837  * elements.
    838  *
    839  * If 'firstOnly' is set, only the first old[iq]/new[iq] pair is used
    840  * to perform the actual update. Any additional queries specified will
    841  * have their values folded in, but are not used to derive update targets.
    842  * This mode is inteded to support the case where multiple NIS+ entries
    843  * map to one and the same LDAP entry. Note that 'rvIn' must still be
    844  * an array of 'nq' elements, though if 'firstOnly' is set, it should be
    845  * OK to leave all but 'rvIn[0]' empty.
    846  *
    847  * 'dbId' is used to further narow down the selection of mapping candidates
    848  * to those matching the 'dbId' value.
    849  */
    850 int
    851 mapToLDAP(__nis_table_mapping_t *tm, int nq, db_query **old, db_query **new,
    852 		__nis_rule_value_t *rvIn, int firstOnly, char *dbId) {
    853 	__nis_table_mapping_t	**tp, **tpa;
    854 	int			i, n, rnq, iq, r, ret = LDAP_SUCCESS;
    855 	int			maxMatches, numMatches = 0;
    856 	__nis_ldap_search_t	*ls;
    857 	char			**dn = 0, **odn = 0;
    858 	__nis_rule_value_t	**rv;
    859 	NIS_HASH_TABLE		dntab;
    860 	__dn_item_t		*dni;
    861 	char			*myself = "mapToLDAP";
    862 
    863 
    864 	if (tm == 0 || (old == 0 && new == 0) || nq <= 0)
    865 		return (LDAP_PARAM_ERROR);
    866 
    867 	/* Determine maximum number of table mapping matches */
    868 	if (nq == 1) {
    869 		tp = selectTableMapping(tm,
    870 			(old != 0 && old[0] != 0) ? old[0] : new[0], 1, 0,
    871 				dbId, &maxMatches);
    872 		numMatches = maxMatches;
    873 	} else {
    874 		tp = selectTableMapping(tm, 0, 1, 0, dbId, &maxMatches);
    875 	}
    876 
    877 	/*
    878 	 * If no matching mapping, we're not mapping to LDAP in this
    879 	 * particular case.
    880 	 */
    881 	if (tp == 0 || maxMatches == 0) {
    882 		sfree(tp);
    883 		return (LDAP_SUCCESS);
    884 	}
    885 
    886 	/*
    887 	 * Allocate the 'rv', 'dn', and 'tpa' arrays. Worst case is that
    888 	 * we need nq * maxMatches elements in each array. However, if
    889 	 * 'firstOnly' is set, we only need one element per matching
    890 	 * mapping in each.
    891 	 */
    892 	dn = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (dn[0]));
    893 	odn = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (odn[0]));
    894 	rv = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (rv[0]));
    895 	tpa = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (tpa[0]));
    896 	if (dn == 0 || odn == 0 || rv == 0 || tpa == 0) {
    897 		sfree(tp);
    898 		sfree(dn);
    899 		sfree(odn);
    900 		sfree(rv);
    901 		sfree(tpa);
    902 		return (LDAP_NO_MEMORY);
    903 	}
    904 
    905 	/* Unless nq == 1, we don't need the 'tp' value */
    906 	if (nq != 1)
    907 		sfree(tp);
    908 
    909 	logmsg(MSG_NOTIMECHECK,
    910 #ifdef	NISDB_LDAP_DEBUG
    911 		LOG_WARNING,
    912 #else
    913 		LOG_INFO,
    914 #endif	/* NISDB_LDAP_DEBUG */
    915 		"%s: %s: %d * %d potential updates",
    916 		myself, NIL(tm->objName), nq, maxMatches);
    917 
    918 	(void) memset(&dntab, 0, sizeof (dntab));
    919 
    920 	/*
    921 	 * Create DNs, column and attribute values, and merge duplicate DNs.
    922 	 */
    923 	for (iq = 0, rnq = 0; iq < nq; iq++) {
    924 		int	idx;
    925 
    926 		if ((old == 0 || old[iq] == 0) &&
    927 				(new == 0 || new[iq] == 0))
    928 			continue;
    929 
    930 		/*
    931 		 * Select matching table mappings; if nq == 1, we've already
    932 		 * got the 'tp' array from above. We expect this to be the
    933 		 * most common case, so it's worth special treatment.
    934 		 */
    935 		if (nq != 1)
    936 			tp = selectTableMapping(tm,
    937 			(old != 0 && old[iq] != 0) ? old[iq] : new[iq], 1, 0,
    938 					dbId, &numMatches);
    939 		if (tp == 0)
    940 			continue;
    941 		else if (numMatches <= 0) {
    942 			sfree(tp);
    943 			continue;
    944 		}
    945 
    946 		idx = iq * maxMatches;
    947 
    948 		if (idx == 0 || !firstOnly)
    949 			(void) memcpy(&tpa[idx], tp,
    950 					numMatches * sizeof (tpa[idx]));
    951 
    952 		for (n = 0; n < numMatches; n++) {
    953 			char			*dnt, *odnt;
    954 			__nis_rule_value_t	*rvt = 0;
    955 
    956 			if (tp[n] == 0)
    957 				continue;
    958 
    959 			dnt = map1qToLDAP(tp[n],
    960 					(old != 0) ? old[iq] : 0,
    961 					(new != 0) ? new[iq] : 0,
    962 					(rvIn != 0) ? &rvIn[iq] : 0,
    963 					&rvt, &odnt);
    964 
    965 			if (dnt == 0)
    966 				continue;
    967 			if (rvt == 0) {
    968 #ifdef  NISDB_LDAP_DEBUG
    969 				abort();
    970 #else
    971 				sfree(dnt);
    972 				sfree(odnt);
    973 				continue;
    974 #endif	/* NISDB_LDAP_DEBUG */
    975 			}
    976 
    977 			/*
    978 			 * Create a request to get a rule-value with
    979 			 * NIS+ data translated to LDAP equivalents.
    980 			 */
    981 			ls = createLdapRequest(tp[n], rvt, 0, 0, NULL, NULL);
    982 			if (ls == 0) {
    983 				if (ret == LDAP_SUCCESS)
    984 					ret = LDAP_OPERATIONS_ERROR;
    985 				logmsg(MSG_NOTIMECHECK, LOG_WARNING,
    986 				"%s: Unable to map to LDAP attrs for %s:dn=%s",
    987 				myself, NIL(tp[n]->dbId), dnt);
    988 				sfree(dnt);
    989 				freeRuleValue(rvt, 1);
    990 				continue;
    991 			}
    992 			freeLdapSearch(ls);
    993 
    994 			/*
    995 			 * If the DN is the same as one we already know
    996 			 * about, merge the rule-values.
    997 			 */
    998 
    999 			dni = (__dn_item_t *)nis_find_item(dnt, &dntab);
   1000 			if (dni != 0) {
   1001 				i = dni->index;
   1002 
   1003 				if (i >= (firstOnly ? ((idx < maxMatches) ?
   1004 						idx : maxMatches) : idx)) {
   1005 					goto update_cleanup;
   1006 				}
   1007 
   1008 				if (odnt != 0 && (dni->oldDn == 0 ||
   1009 						strcasecmp(odnt, dni->oldDn) !=
   1010 							0)) {
   1011 					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
   1012 			"%s: DN mismatch while merging updates: %s: %s != %s",
   1013 						myself, NIL(tpa[i]->dbId),
   1014 						NIL(odnt), NIL(dni->oldDn));
   1015 					goto update_cleanup;
   1016 				}
   1017 
   1018 				if (mergeRuleValue(rv[i], rvt)) {
   1019 					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
   1020 				"%s: Error merging updates for %s:dn=%s",
   1021 						myself, NIL(tpa[i]->dbId),
   1022 						dn[i]);
   1023 					if ((dni = (__dn_item_t *)
   1024 						nis_remove_item(dnt, &dntab)) !=
   1025 							0) {
   1026 						i = dni->index;
   1027 						sfree(dn[i]);
   1028 						dn[i] = 0;
   1029 						tpa[i] = 0;
   1030 						freeRuleValue(rv[i], 1);
   1031 						rv[i] = 0;
   1032 						sfree(dni);
   1033 					}
   1034 					goto update_cleanup;
   1035 				}
   1036 update_cleanup:
   1037 				sfree(dnt);
   1038 				dnt = 0;
   1039 				sfree(odnt);
   1040 				odnt = 0;
   1041 				freeRuleValue(rvt, 1);
   1042 				rvt = 0;
   1043 			} else if ((iq == 0 || !firstOnly) && dnt != 0) {
   1044 				dni = am(myself, sizeof (*dni));
   1045 				if (dni != 0) {
   1046 					dni->item.name = dnt;
   1047 					dni->index = idx + n;
   1048 					dni->oldDn = odnt;
   1049 				} else {
   1050 					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
   1051 					"%s: Skipping update for dn=\"%s\"",
   1052 						myself, dnt);
   1053 					sfree(dnt);
   1054 					dnt = 0;
   1055 				}
   1056 				if (dni != 0 &&
   1057 					nis_insert_item((NIS_HASH_ITEM *)dni,
   1058 							&dntab) != 1) {
   1059 					logmsg(MSG_NOTIMECHECK, LOG_ERR,
   1060 					"%s: Unable to memorize dn=\"%s\"",
   1061 						myself, dnt);
   1062 					sfree(dnt);
   1063 					dnt = 0;
   1064 					sfree(odnt);
   1065 					odnt = 0;
   1066 				}
   1067 				if (dnt != 0) {
   1068 					dn[idx+n] = dnt;
   1069 					odn[idx+n] = odnt;
   1070 					rv[idx+n] = rvt;
   1071 					rnq++;
   1072 				} else {
   1073 					freeRuleValue(rvt, 1);
   1074 					rvt = 0;
   1075 				}
   1076 			} else if (dnt != 0) {
   1077 				sfree(dnt);
   1078 				sfree(odnt);
   1079 				freeRuleValue(rvt, 1);
   1080 			}
   1081 		}
   1082 		sfree(tp);
   1083 	}
   1084 
   1085 	/* Done with the dntab */
   1086 	while ((dni = (__dn_item_t *)nis_pop_item(&dntab)) != 0) {
   1087 		sfree(dni);
   1088 	}
   1089 
   1090 	logmsg(MSG_NOTIMECHECK,
   1091 #ifdef	NISDB_LDAP_DEBUG
   1092 		LOG_WARNING,
   1093 #else
   1094 		LOG_INFO,
   1095 #endif	/* NISDB_LDAP_DEBUG */
   1096 		"%s: %s: %d update%s requested",
   1097 		myself, NIL(tm->objName), rnq, rnq != 1 ? "s" : "");
   1098 
   1099 	/* Perform the updates */
   1100 	for (i = rnq = 0; i < (firstOnly ? maxMatches : nq*maxMatches); i++) {
   1101 		int	delPerDbId;
   1102 
   1103 		if (dn[i] == 0)
   1104 			continue;
   1105 
   1106 #ifdef	NISDB_LDAP_DEBUG
   1107 		logmsg(MSG_NOTIMECHECK, LOG_INFO,
   1108 			"%s: %s %s:dn=%s",
   1109 			myself,
   1110 			(new != 0 && new[i/maxMatches] != 0) ?
   1111 				"modify" : "delete",
   1112 			NIL(tpa[i]->dbId), dn[i]);
   1113 #endif	/* NISDB_LDAP_DEBUG */
   1114 
   1115 		delPerDbId = (tpa[i]->objectDN->delDisp == dd_perDbId);
   1116 		if ((new != 0 && new[i/maxMatches] != 0) || delPerDbId) {
   1117 			/*
   1118 			 * Try to modify/create the specified DN. First,
   1119 			 * however, if the update changes the DN, make
   1120 			 * that change.
   1121 			 */
   1122 			if (odn[i] == 0 || (r = ldapChangeDN(odn[i], dn[i])) ==
   1123 					LDAP_SUCCESS) {
   1124 				int	addFirst;
   1125 
   1126