xref: /illumos-gate/usr/src/lib/libnisdb/ldap_map.c (revision d8109ce4330e1b8ad6c29f9fccacec969066bb9d)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2015 Gary Mills
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <strings.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <errno.h>
31 #include <stdio.h>
32 #include <rpcsvc/nis.h>
33 #include <rpc/xdr.h>
34 
35 #include "ldap_util.h"
36 #include "ldap_attr.h"
37 #include "ldap_ruleval.h"
38 #include "ldap_op.h"
39 #include "ldap_map.h"
40 #include "ldap_glob.h"
41 #include "ldap_xdr.h"
42 #include "ldap_val.h"
43 
44 /* From yptol/dit_access_utils.h */
45 #define	N2LKEY		"rf_key"
46 #define	N2LIPKEY	"rf_ipkey"
47 
48 __nis_hash_table_mt	ldapMappingList = NIS_HASH_TABLE_MT_INIT;
49 extern	int yp2ldap;
50 
51 
52 int
53 setColumnNames(__nis_table_mapping_t *t) {
54 	int	i, j, nic, noc;
55 	char	**col;
56 	char	*myself = "setColumnNames";
57 
58 	if (t == 0)
59 		return (0);
60 
61 	col = t->column;
62 	nic = (col != 0) ? t->numColumns : -1;
63 
64 	t->objType = NIS_BOGUS_OBJ;
65 	t->obj = 0;
66 
67 	/*
68 	 * If it's a table object, but there are no translation rules,
69 	 * this mapping is for the table object itself. In that case,
70 	 * we throw away the column names (if any).
71 	 */
72 	if (t->objType == NIS_TABLE_OBJ && t->numRulesFromLDAP == 0 &&
73 			t->numRulesToLDAP == 0) {
74 		for (i = 0; i < t->numColumns; i++)
75 			sfree(t->column[i]);
76 		sfree(t->column);
77 		t->column = 0;
78 		t->numColumns = 0;
79 		noc = 0;
80 	}
81 
82 	/*
83 	 * Verify that all column names found by the parser
84 	 * are present in the actual column list.
85 	 */
86 	if (verbose) {
87 		for (i = 0, noc = 0; i < nic; i++) {
88 			int	found = 0;
89 
90 			if (col[i] == 0)
91 				continue;
92 			/* Skip the 'zo_*' special column names */
93 			if (isObjAttrString(col[i]))
94 				continue;
95 			for (j = 0; j < t->numColumns; j++) {
96 				if (strcmp(col[i], t->column[j]) == 0) {
97 					noc++;
98 					found = 1;
99 					break;
100 				}
101 			}
102 			if (!found) {
103 				logmsg(MSG_NOTIMECHECK, LOG_WARNING,
104 					"%s: No column \"%s\" in \"%s\"",
105 					myself, NIL(col[i]), NIL(t->objName));
106 			}
107 		}
108 	}
109 
110 	/* Remove any setup by the parser */
111 	for (i = 0; i < nic; i++) {
112 		sfree(col[i]);
113 	}
114 	sfree(col);
115 
116 	return (0);
117 }
118 
119 void
120 freeSingleObjAttr(__nis_obj_attr_t *attr) {
121 	if (attr == 0)
122 		return;
123 
124 	sfree(attr->zo_owner);
125 	sfree(attr->zo_group);
126 	sfree(attr->zo_domain);
127 	sfree(attr);
128 }
129 
130 void
131 freeObjAttr(__nis_obj_attr_t **attr, int numAttr) {
132 	int	i;
133 
134 	if (attr == 0)
135 		return;
136 
137 	for (i = 0; i < numAttr; i++) {
138 		freeSingleObjAttr(attr[i]);
139 	}
140 
141 	sfree(attr);
142 }
143 
144 __nis_obj_attr_t *
145 cloneObjAttr(__nis_obj_attr_t *old) {
146 	__nis_obj_attr_t	*new;
147 	char			*myself = "cloneObjAttr";
148 
149 	if (old == 0)
150 		return (0);
151 
152 	new = am(myself, sizeof (*new));
153 	if (new == 0)
154 		return (0);
155 
156 	new->zo_owner = sdup(myself, T, old->zo_owner);
157 	if (new->zo_owner == 0 && old->zo_owner != 0)
158 		goto cleanup;
159 
160 	new->zo_group = sdup(myself, T, old->zo_group);
161 	if (new->zo_group == 0 && old->zo_group != 0)
162 		goto cleanup;
163 
164 	new->zo_domain = sdup(myself, T, old->zo_domain);
165 	if (new->zo_domain == 0 && old->zo_domain != 0)
166 		goto cleanup;
167 
168 	new->zo_access = old->zo_access;
169 	new->zo_ttl = old->zo_ttl;
170 
171 	return (new);
172 
173 cleanup:
174 	freeSingleObjAttr(new);
175 
176 	return (0);
177 }
178 
179 
180 /*
181  * Obtain NIS+ entries (in the form of db_query's) from the supplied table
182  * mapping and db_query.
183  *
184  * If 'qin' is NULL, enumeration is desired.
185  *
186  * On exit, '*numQueries' contains the number of (db_query *)'s in the
187  * return array, '*ldapStat' the LDAP operation status, and '*objAttr'
188  * a pointer to an array (of '*numQueries elements) of object attributes
189  * (zo_owner, etc.). If no object attributes were retrieved, '*objAttr'
190  * is NULL; any and all of the (*objAttr)[i]'s may be NULL.
191  */
192 db_query **
193 mapFromLDAP(__nis_table_mapping_t *t, db_query *qin, int *numQueries,
194 		char *dbId, int *ldapStat, __nis_obj_attr_t ***objAttr) {
195 	__nis_table_mapping_t	**tp;
196 	db_query		**q;
197 	__nis_rule_value_t	*rv;
198 	__nis_ldap_search_t	*ls;
199 	int			n, numVals, numMatches = 0;
200 	int			stat;
201 	__nis_obj_attr_t	**attr;
202 	char			*myself = "mapFromLDAP";
203 
204 	if (ldapStat == 0)
205 		ldapStat = &stat;
206 
207 	if (t == 0 || numQueries == 0) {
208 		*ldapStat = LDAP_PARAM_ERROR;
209 		return (0);
210 	}
211 
212 	/* Select the correct table mapping(s) */
213 	tp = selectTableMapping(t, qin, 0, 0, dbId, &numMatches);
214 	if (tp == 0 || numMatches <= 0) {
215 		/*
216 		 * Not really an error; just no matching mapping
217 		 * for the query.
218 		 */
219 		*ldapStat = LDAP_SUCCESS;
220 		return (0);
221 	}
222 
223 	q = 0;
224 	attr = 0;
225 
226 	/* For each mapping */
227 	for (numVals = 0, n = 0; n < numMatches; n++) {
228 		db_query		**qt;
229 		int			i, nqt = 0, filterOnQin, res = 0;
230 
231 		t = tp[n];
232 
233 		if (qin != 0) {
234 			rv = buildNisPlusRuleValue(t, qin, 0);
235 			if (rv != 0) {
236 				/*
237 				 * Depending on the value of res, we shall
238 				 * proceed to next table mapping.
239 				 */
240 				ls = createLdapRequest(t, rv, 0, 1, &res, NULL);
241 			}
242 			else
243 				ls = 0;
244 		} else {
245 			/* Build enumeration request */
246 			rv = 0;
247 			ls = createLdapRequest(t, 0, 0, 1, NULL, NULL);
248 		}
249 
250 		freeRuleValue(rv, 1);
251 
252 		if (ls == 0) {
253 			/*
254 			 * if the res is NP_LDAP_RULES_NO_VALUE, that means we
255 			 * have enough NIS+ columns for the rules to produce
256 			 * values, but none of them did, so continue to the
257 			 * next table mapping. Otherwise do cleanup and return
258 			 * error.
259 			 */
260 			if (res == NP_LDAP_RULES_NO_VALUE)
261 				continue;
262 			for (i = 0; i < numVals; i++)
263 				freeQuery(q[i]);
264 			sfree(q);
265 			free(tp);
266 			*ldapStat = LDAP_OPERATIONS_ERROR;
267 			return (0);
268 		}
269 
270 		/* Query LDAP */
271 		nqt = (ls->isDN || qin != 0) ? 0 : -1;
272 		rv = ldapSearch(ls, &nqt, 0, ldapStat);
273 
274 		/*
275 		 * If qin != 0, then we need to make sure that the
276 		 * LDAP search is filtered so that only entries that
277 		 * are compatible with 'qin' are retained. This will
278 		 * happen automatically if we do a DN search (in which
279 		 * case, no need to filter on 'qin').
280 		 */
281 		if (ls->isDN || qin == 0)
282 			filterOnQin = 0;
283 		else
284 			filterOnQin = 1;
285 
286 		freeLdapSearch(ls);
287 
288 		/* Convert rule-values to db_query's */
289 		if (rv != 0 && nqt > 0) {
290 			int			nrv = nqt;
291 			__nis_obj_attr_t	**at = 0;
292 
293 			qt = ruleValue2Query(t, rv,
294 				(filterOnQin) ? qin : 0, &at, &nqt);
295 			freeRuleValue(rv, nrv);
296 
297 			if (qt != 0 && q == 0) {
298 				q = qt;
299 				attr = at;
300 				numVals = nqt;
301 			} else if (qt != 0) {
302 				db_query		**tmp;
303 				__nis_obj_attr_t	**atmp;
304 
305 				/* Extend the 'q' array */
306 				tmp = realloc(q,
307 					(numVals+nqt) * sizeof (q[0]));
308 				/* ... and the 'attr' array */
309 				atmp = realloc(attr,
310 					(numVals+nqt) * sizeof (attr[0]));
311 				if (tmp == 0 || atmp == 0) {
312 					logmsg(MSG_NOMEM, LOG_ERR,
313 						"%s: realloc(%d) => NULL",
314 						myself,
315 						(numVals+nqt) * sizeof (q[0]));
316 					for (i = 0; i < numVals; i++)
317 						freeQuery(q[i]);
318 					for (i = 0; i < nqt; i++)
319 						freeQuery(qt[i]);
320 					sfree(tmp);
321 					sfree(atmp);
322 					sfree(q);
323 					sfree(qt);
324 					sfree(tp);
325 					freeObjAttr(at, nqt);
326 					freeObjAttr(attr, numVals);
327 					*ldapStat = LDAP_NO_MEMORY;
328 					return (0);
329 				}
330 				q = tmp;
331 				attr = atmp;
332 				/* Add the results for this 't' */
333 				(void) memcpy(&q[numVals], qt,
334 						nqt * sizeof (qt[0]));
335 				(void) memcpy(&attr[numVals], at,
336 						nqt * sizeof (at[0]));
337 				numVals += nqt;
338 
339 				sfree(qt);
340 				sfree(at);
341 			}
342 		}
343 	}
344 
345 	*numQueries = numVals;
346 	if (objAttr != 0)
347 		*objAttr = attr;
348 	else
349 		freeObjAttr(attr, numVals);
350 	sfree(tp);
351 
352 	return (q);
353 }
354 
355 /*
356  * Add the object attributes (zo_owner, etc.) to the rule-value 'rv'.
357  * Returns a pointer to the (possibly newly allocated) rule-value,
358  * or NULL in case of failure. If not returning 'rvIn', the latter
359  * will have been freed.
360  */
361 __nis_rule_value_t *
362 addObjAttr2RuleValue(nis_object *obj, __nis_rule_value_t *rvIn) {
363 	__nis_rule_value_t	*rv;
364 	char			abuf[2 * sizeof (obj->zo_access) + 1];
365 	char			tbuf[2 * sizeof (obj->zo_ttl) + 1];
366 
367 	if (obj == 0)
368 		return (0);
369 
370 	if (rvIn != 0) {
371 		rv = rvIn;
372 	} else {
373 		rv = initRuleValue(1, 0);
374 		if (rv == 0)
375 			return (0);
376 	}
377 
378 	if (obj->zo_owner != 0) {
379 		if (addSCol2RuleValue("zo_owner", obj->zo_owner, rv) != 0) {
380 			freeRuleValue(rv, 1);
381 			return (0);
382 		}
383 	}
384 
385 	if (obj->zo_group != 0) {
386 		if (addSCol2RuleValue("zo_group", obj->zo_group, rv) != 0) {
387 			freeRuleValue(rv, 1);
388 			return (0);
389 		}
390 	}
391 
392 	if (obj->zo_domain != 0) {
393 		if (addSCol2RuleValue("zo_domain", obj->zo_domain, rv) != 0) {
394 			freeRuleValue(rv, 1);
395 			return (0);
396 		}
397 	}
398 
399 	(void) memset(abuf, 0, sizeof (abuf));
400 	(void) memset(tbuf, 0, sizeof (tbuf));
401 
402 	sprintf(abuf, "%x", obj->zo_access);
403 	sprintf(tbuf, "%x", obj->zo_ttl);
404 
405 	if (addSCol2RuleValue("zo_access", abuf, rv) != 0) {
406 		freeRuleValue(rv, 1);
407 		return (0);
408 	}
409 	if (addSCol2RuleValue("zo_ttl", tbuf, rv) != 0) {
410 		freeRuleValue(rv, 1);
411 		return (0);
412 	}
413 
414 	return (rv);
415 }
416 
417 /*
418  * Returns a pointer to (NOT a copy of) the value for the specified
419  * column 'col' in the rule-value 'rv'.
420  */
421 __nis_value_t *
422 findColValue(char *col, __nis_rule_value_t *rv) {
423 	int		i;
424 
425 	if (col == 0 || rv == 0 || rv->numColumns <= 0)
426 		return (0);
427 
428 	for (i = 0; i < rv->numColumns; i++) {
429 		if (strcmp(col, rv->colName[i]) == 0)
430 			return (&rv->colVal[i]);
431 	}
432 
433 	return (0);
434 }
435 
436 /*
437  * Return the NIS+ object attributes (if any) in the rule-value 'rv'.
438  */
439 __nis_obj_attr_t *
440 ruleValue2ObjAttr(__nis_rule_value_t *rv) {
441 	__nis_obj_attr_t	*attr;
442 	__nis_value_t		*val;
443 	char			*myself = "ruleValue2ObjAttr";
444 
445 	if (rv == 0 || rv->numColumns <= 0)
446 		return (0);
447 
448 	attr = am(myself, sizeof (*attr));
449 
450 	if ((val = findColValue("zo_owner", rv)) != 0 &&
451 			val->type == vt_string && val->numVals == 1 &&
452 			val->val[0].value != 0) {
453 		attr->zo_owner = sdup(myself, T, val->val[0].value);
454 		if (attr->zo_owner == 0) {
455 			freeSingleObjAttr(attr);
456 			return (0);
457 		}
458 	}
459 
460 	if ((val = findColValue("zo_group", rv)) != 0 &&
461 			val->type == vt_string && val->numVals == 1 &&
462 			val->val[0].value != 0) {
463 		attr->zo_group = sdup(myself, T, val->val[0].value);
464 		if (attr->zo_group == 0) {
465 			freeSingleObjAttr(attr);
466 			return (0);
467 		}
468 	}
469 
470 	if ((val = findColValue("zo_domain", rv)) != 0 &&
471 			val->type == vt_string && val->numVals == 1 &&
472 			val->val[0].value != 0) {
473 		attr->zo_domain = sdup(myself, T, val->val[0].value);
474 		if (attr->zo_domain == 0) {
475 			freeSingleObjAttr(attr);
476 			return (0);
477 		}
478 	}
479 
480 	if ((val = findColValue("zo_access", rv)) != 0 &&
481 			val->type == vt_string && val->numVals == 1 &&
482 			val->val[0].value != 0) {
483 		if (sscanf(val->val[0].value, "%x", &attr->zo_access) != 1) {
484 			freeSingleObjAttr(attr);
485 			return (0);
486 		}
487 	}
488 
489 	if ((val = findColValue("zo_ttl", rv)) != 0 &&
490 			val->type == vt_string && val->numVals == 1 &&
491 			val->val[0].value != 0) {
492 		if (sscanf(val->val[0].value, "%x", &attr->zo_ttl) != 1) {
493 			freeSingleObjAttr(attr);
494 			return (0);
495 		}
496 	}
497 
498 	return (attr);
499 }
500 
501 /*
502  * If the supplied string is one of the object attributes, return one.
503  * Otherwise, return zero.
504  */
505 int
506 isObjAttrString(char *str) {
507 	if (str == 0)
508 		return (0);
509 
510 	if (strcmp("zo_owner", str) == 0 ||
511 		strcmp("zo_group", str) == 0 ||
512 		strcmp("zo_domain", str) == 0 ||
513 		strcmp("zo_access", str) == 0 ||
514 		strcmp("zo_ttl", str) == 0)
515 		return (1);
516 	else
517 		return (0);
518 }
519 
520 
521 /*
522  * If the supplied value is one of the object attribute strings, return
523  * a pointer to the string. Otherwise, return NULL.
524  */
525 char *
526 isObjAttr(__nis_single_value_t *val) {
527 	if (val == 0 || val->length <= 0 || val->value == 0)
528 		return (0);
529 
530 	if (isObjAttrString(val->value))
531 		return (val->value);
532 	else
533 		return (0);
534 }
535 
536 int
537 setObjAttrField(char *attrName, __nis_single_value_t *val,
538 		__nis_obj_attr_t **objAttr) {
539 	__nis_obj_attr_t	*attr;
540 	char			*myself = "setObjAttrField";
541 
542 	if (attrName == 0 || val == 0 || objAttr == 0 ||
543 			val->value == 0 || val->length <= 0)
544 		return (-1);
545 
546 	if (*objAttr != 0) {
547 		attr = *objAttr;
548 	} else {
549 		attr = am(myself, sizeof (*attr));
550 		if (attr == 0)
551 			return (-2);
552 		*objAttr = attr;
553 	}
554 
555 	if (strcmp("zo_owner", attrName) == 0) {
556 		if (attr->zo_owner == 0) {
557 			attr->zo_owner = sdup(myself, T, val->value);
558 			if (attr->zo_owner == 0)
559 				return (-11);
560 		}
561 	} else if (strcmp("zo_group", attrName) == 0) {
562 		if (attr->zo_group == 0) {
563 			attr->zo_group = sdup(myself, T, val->value);
564 			if (attr->zo_group == 0)
565 				return (-12);
566 		}
567 	} else if (strcmp("zo_domain", attrName) == 0) {
568 		if (attr->zo_domain == 0) {
569 			attr->zo_domain = sdup(myself, T, val->value);
570 			if (attr->zo_domain == 0)
571 				return (-13);
572 		}
573 	} else if (strcmp("zo_access", attrName) == 0) {
574 		if (attr->zo_access == 0) {
575 			if (sscanf(val->value, "%x", &attr->zo_access) != 1)
576 				return (-14);
577 		}
578 	} else if (strcmp("zo_ttl", attrName) == 0) {
579 		if (attr->zo_ttl == 0) {
580 			if (sscanf(val->value, "%x", &attr->zo_ttl) != 1)
581 				return (-15);
582 		}
583 	}
584 
585 	return (0);
586 }
587 
588 /*
589  * Return a DN and rule-value for the supplied mapping, db_query's, and
590  * input rule-value. This function only works on a single mapping. See
591  * mapToLDAP() below for a description of the action depending on the
592  * values of 'old' and 'new'.
593  *
594  * If both 'old' and 'new' are supplied, and the modify would result
595  * in a change to the DN, '*oldDN' will contain the old DN. Otherwise
596  * (and normally), '*oldDN' will be NULL.
597  */
598 char *
599 map1qToLDAP(__nis_table_mapping_t *t, db_query *old, db_query *new,
600 		__nis_rule_value_t *rvIn, __nis_rule_value_t **rvOutP,
601 		char **oldDnP) {
602 
603 	__nis_rule_value_t	*rv, *rvt;
604 	__nis_ldap_search_t	*ls;
605 	char			*dn = 0, *oldDn = 0;
606 	__nis_table_mapping_t	del;
607 	char			*myself = "map1qToLDAP";
608 
609 	if (t == 0 || (old == 0 && new == 0) || rvOutP == 0)
610 		return (0);
611 
612 	/*
613 	 * If entry should be deleted, we look at the delete
614 	 * policy in the table mapping. Should it specify a
615 	 * rule set, we use that rule set to build a rule-
616 	 * value, and the delete actually becomes a modify
617 	 * operation.
618 	 */
619 	if (old != 0 && new == 0) {
620 		if (t->objectDN->delDisp == dd_perDbId) {
621 			/*
622 			 * The functions that build a rule-value from a
623 			 * rule set expect a __nis_table_mapping_t, but the
624 			 * rule set in the __nis_object_dn_t isn't of that
625 			 * form. So, build a pseudo-__nis_table_mapping_t that
626 			 * borrows heavily from 't'.
627 			 */
628 			del = *t;
629 
630 			del.numRulesToLDAP = del.objectDN->numDbIds;
631 			del.ruleToLDAP = del.objectDN->dbId;
632 
633 			/*
634 			 * Do a modify with the pseudo-table
635 			 * mapping, and the 'old' db_query
636 			 * supplying input to the delete rule
637 			 * set.
638 			 */
639 			t = &del;
640 			new = old;
641 		} else if (t->objectDN->delDisp == dd_always) {
642 
643 			/* Nothing to do here; all handled below */
644 
645 		} else if (t->objectDN->delDisp == dd_never) {
646 
647 			return (0);
648 
649 		} else {
650 
651 			logmsg(MSG_INVALIDDELDISP, LOG_WARNING,
652 				"%s: Invalid delete disposition %d for \"%s\"",
653 				myself, t->objectDN->delDisp,
654 				NIL(t->dbId));
655 			return (0);
656 
657 		}
658 	}
659 
660 	/* Make a copy of the input rule-value */
661 	if (rvIn != 0) {
662 		rv = initRuleValue(1, rvIn);
663 		if (rv == 0)
664 			return (0);
665 	} else {
666 		rv = 0;
667 	}
668 
669 	/* First get a rule-value from the supplied NIS+ entry. */
670 	rvt = rv;
671 	rv = buildNisPlusRuleValue(t, ((old != 0) ? old : new), rvt);
672 	freeRuleValue(rvt, 1);
673 	if (rv == 0) {
674 		logmsg(MSG_NORULEVALUE, LOG_WARNING,
675 			"%s: No in-query rule-value derived for \"%s\"",
676 			myself, NIL(t->dbId));
677 		return (0);
678 	}
679 
680 	/*
681 	 * Create a request (really only care about the DN) from the
682 	 * supplied NIS+ entry data.
683 	 */
684 	ls = createLdapRequest(t, rv, &dn, 0, NULL, NULL);
685 	if (ls == 0 || dn == 0) {
686 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
687 			"%s: Unable to create LDAP request for %s: %s",
688 			myself, NIL(t->dbId),
689 			(dn != 0) ? dn : rvId(rv, mit_nisplus));
690 		sfree(dn);
691 		freeLdapSearch(ls);
692 		freeRuleValue(rv, 1);
693 		return (0);
694 	}
695 
696 	freeLdapSearch(ls);
697 
698 	if (new != 0) {
699 		/*
700 		 * Create a rule-value from the new NIS+ entry.
701 		 * Don't want to mix in the rule-value derived
702 		 * from 'old', so delete it. However, we still
703 		 * want the owner, group, etc., from 'rvIn'.
704 		 */
705 		if (old != 0) {
706 			freeRuleValue(rv, 1);
707 			if (rvIn != 0) {
708 				rv = initRuleValue(1, rvIn);
709 				if (rv == 0) {
710 					sfree(dn);
711 					return (0);
712 				}
713 			} else {
714 				rv = 0;
715 			}
716 		}
717 		rvt = rv;
718 		rv = buildNisPlusRuleValue(t, new, rvt);
719 		freeRuleValue(rvt, 1);
720 		if (rv == 0) {
721 			logmsg(MSG_NORULEVALUE, LOG_WARNING,
722 				"%s: No new rule-value derived for \"%s: %s\"",
723 				myself, NIL(t->dbId), dn);
724 			sfree(dn);
725 			return (0);
726 		}
727 		/*
728 		 * Check if the proposed modification would result in a
729 		 * a change to the DN.
730 		 */
731 		if (old != 0) {
732 			oldDn = dn;
733 			dn = 0;
734 			ls = createLdapRequest(t, rv, &dn, 0, NULL, NULL);
735 			if (ls == 0 || dn == 0) {
736 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
737 				"%s: Unable to create new DN for \"%s: %s\"",
738 					myself, NIL(t->dbId), oldDn);
739 				sfree(oldDn);
740 				freeLdapSearch(ls);
741 				freeRuleValue(rv, 1);
742 				return (0);
743 			}
744 			freeLdapSearch(ls);
745 			if (strcasecmp(oldDn, dn) == 0) {
746 				sfree(oldDn);
747 				oldDn = 0;
748 			}
749 		}
750 	}
751 
752 
753 	*rvOutP = rv;
754 	if (oldDnP != 0)
755 		*oldDnP = oldDn;
756 
757 	return (dn);
758 }
759 
760 /*
761  * Since the DN hash list is an automatic variable, there's no need for
762  * locking, and we remove the locking overhead by using the libnsl
763  * hash functions.
764  */
765 #undef  NIS_HASH_ITEM
766 #undef  NIS_HASH_TABLE
767 
768 typedef struct {
769 	NIS_HASH_ITEM	item;
770 	int		index;
771 	char		*oldDn;
772 } __dn_item_t;
773 
774 /*
775  * Update LDAP per the supplied table mapping and db_query's.
776  *
777  * 'nq' is the number of elements in the 'old', 'new', and 'rvIn'
778  * arrays. mapToLDAP() generally performs one update for each
779  * element; however, if one or more of the individual queries
780  * produce the same DN, they're merged into a single update.
781  *
782  * There are four cases, depending on the values of 'old[iq]' and
783  * 'new[iq]':
784  *
785  * (1)	old[iq] == 0 && new[iq] == 0
786  *	No action; skip to next query
787  *
788  * (2)	old[iq] == 0 && new[iq] != 0
789  *	Attempt to use the 'new' db_query to get a DN, and try to create
790  *	the corresponding LDAP entry.
791  *
792  * (3)	old[iq] != 0 && new[iq] == 0
793  *	Use the 'old' db_query to get a DN, and try to delete the LDAP
794  *	entry per the table mapping.
795  *
796  * (4)	old[iq] != 0 && new[iq] != 0
797  *	Use the 'old' db_query to get a DN, and update (possibly create)
798  *	the corresponding LDAP entry per the 'new' db_query.
799  *
800  * If 'rvIn' is non-NULL, it is expected to contain the object attributes
801  * (zo_owner, etc.) to be written to LDAP. 'rvIn' is an array with 'nq'
802  * elements.
803  *
804  * If 'firstOnly' is set, only the first old[iq]/new[iq] pair is used
805  * to perform the actual update. Any additional queries specified will
806  * have their values folded in, but are not used to derive update targets.
807  * This mode is inteded to support the case where multiple NIS+ entries
808  * map to one and the same LDAP entry. Note that 'rvIn' must still be
809  * an array of 'nq' elements, though if 'firstOnly' is set, it should be
810  * OK to leave all but 'rvIn[0]' empty.
811  *
812  * 'dbId' is used to further narow down the selection of mapping candidates
813  * to those matching the 'dbId' value.
814  */
815 int
816 mapToLDAP(__nis_table_mapping_t *tm, int nq, db_query **old, db_query **new,
817 		__nis_rule_value_t *rvIn, int firstOnly, char *dbId) {
818 	__nis_table_mapping_t	**tp, **tpa;
819 	int			i, n, rnq, iq, r, ret = LDAP_SUCCESS;
820 	int			maxMatches, numMatches = 0;
821 	__nis_ldap_search_t	*ls;
822 	char			**dn = 0, **odn = 0;
823 	__nis_rule_value_t	**rv;
824 	__dn_item_t		*dni;
825 	char			*myself = "mapToLDAP";
826 
827 
828 	if (tm == 0 || (old == 0 && new == 0) || nq <= 0)
829 		return (LDAP_PARAM_ERROR);
830 
831 	/* Determine maximum number of table mapping matches */
832 	if (nq == 1) {
833 		tp = selectTableMapping(tm,
834 			(old != 0 && old[0] != 0) ? old[0] : new[0], 1, 0,
835 				dbId, &maxMatches);
836 		numMatches = maxMatches;
837 	} else {
838 		tp = selectTableMapping(tm, 0, 1, 0, dbId, &maxMatches);
839 	}
840 
841 	/*
842 	 * If no matching mapping, we're not mapping to LDAP in this
843 	 * particular case.
844 	 */
845 	if (tp == 0 || maxMatches == 0) {
846 		sfree(tp);
847 		return (LDAP_SUCCESS);
848 	}
849 
850 	/*
851 	 * Allocate the 'rv', 'dn', and 'tpa' arrays. Worst case is that
852 	 * we need nq * maxMatches elements in each array. However, if
853 	 * 'firstOnly' is set, we only need one element per matching
854 	 * mapping in each.
855 	 */
856 	dn = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (dn[0]));
857 	odn = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (odn[0]));
858 	rv = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (rv[0]));
859 	tpa = am(myself, (firstOnly ? 1 : nq) * maxMatches * sizeof (tpa[0]));
860 	if (dn == 0 || odn == 0 || rv == 0 || tpa == 0) {
861 		sfree(tp);
862 		sfree(dn);
863 		sfree(odn);
864 		sfree(rv);
865 		sfree(tpa);
866 		return (LDAP_NO_MEMORY);
867 	}
868 
869 	/* Unless nq == 1, we don't need the 'tp' value */
870 	if (nq != 1)
871 		sfree(tp);
872 
873 	logmsg(MSG_NOTIMECHECK,
874 #ifdef	NISDB_LDAP_DEBUG
875 		LOG_WARNING,
876 #else
877 		LOG_INFO,
878 #endif	/* NISDB_LDAP_DEBUG */
879 		"%s: %s: %d * %d potential updates",
880 		myself, NIL(tm->objName), nq, maxMatches);
881 
882 	/*
883 	 * Create DNs, column and attribute values, and merge duplicate DNs.
884 	 */
885 	for (iq = 0, rnq = 0; iq < nq; iq++) {
886 		int	idx;
887 
888 		if ((old == 0 || old[iq] == 0) &&
889 				(new == 0 || new[iq] == 0))
890 			continue;
891 
892 		/*
893 		 * Select matching table mappings; if nq == 1, we've already
894 		 * got the 'tp' array from above. We expect this to be the
895 		 * most common case, so it's worth special treatment.
896 		 */
897 		if (nq != 1)
898 			tp = selectTableMapping(tm,
899 			(old != 0 && old[iq] != 0) ? old[iq] : new[iq], 1, 0,
900 					dbId, &numMatches);
901 		if (tp == 0)
902 			continue;
903 		else if (numMatches <= 0) {
904 			sfree(tp);
905 			continue;
906 		}
907 
908 		idx = iq * maxMatches;
909 
910 		if (idx == 0 || !firstOnly)
911 			(void) memcpy(&tpa[idx], tp,
912 					numMatches * sizeof (tpa[idx]));
913 
914 		for (n = 0; n < numMatches; n++) {
915 			char			*dnt, *odnt;
916 			__nis_rule_value_t	*rvt = 0;
917 
918 			if (tp[n] == 0)
919 				continue;
920 
921 			dnt = map1qToLDAP(tp[n],
922 					(old != 0) ? old[iq] : 0,
923 					(new != 0) ? new[iq] : 0,
924 					(rvIn != 0) ? &rvIn[iq] : 0,
925 					&rvt, &odnt);
926 
927 			if (dnt == 0)
928 				continue;
929 			if (rvt == 0) {
930 #ifdef  NISDB_LDAP_DEBUG
931 				abort();
932 #else
933 				sfree(dnt);
934 				sfree(odnt);
935 				continue;
936 #endif	/* NISDB_LDAP_DEBUG */
937 			}
938 
939 			/*
940 			 * Create a request to get a rule-value with
941 			 * NIS+ data translated to LDAP equivalents.
942 			 */
943 			ls = createLdapRequest(tp[n], rvt, 0, 0, NULL, NULL);
944 			if (ls == 0) {
945 				if (ret == LDAP_SUCCESS)
946 					ret = LDAP_OPERATIONS_ERROR;
947 				logmsg(MSG_NOTIMECHECK, LOG_WARNING,
948 				"%s: Unable to map to LDAP attrs for %s:dn=%s",
949 				myself, NIL(tp[n]->dbId), dnt);
950 				sfree(dnt);
951 				freeRuleValue(rvt, 1);
952 				continue;
953 			}
954 			freeLdapSearch(ls);
955 
956 			/*
957 			 * If the DN is the same as one we already know
958 			 * about, merge the rule-values.
959 			 */
960 
961 			if ((iq == 0 || !firstOnly) && dnt != 0) {
962 				dni = am(myself, sizeof (*dni));
963 				if (dni != 0) {
964 					dni->item.name = dnt;
965 					dni->index = idx + n;
966 					dni->oldDn = odnt;
967 				} else {
968 					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
969 					"%s: Skipping update for dn=\"%s\"",
970 						myself, dnt);
971 					sfree(dnt);
972 					dnt = 0;
973 				}
974 				if (dnt != 0) {
975 					dn[idx+n] = dnt;
976 					odn[idx+n] = odnt;
977 					rv[idx+n] = rvt;
978 					rnq++;
979 				} else {
980 					freeRuleValue(rvt, 1);
981 					rvt = 0;
982 				}
983 			} else if (dnt != 0) {
984 				sfree(dnt);
985 				sfree(odnt);
986 				freeRuleValue(rvt, 1);
987 			}
988 		}
989 		sfree(tp);
990 	}
991 
992 	logmsg(MSG_NOTIMECHECK,
993 #ifdef	NISDB_LDAP_DEBUG
994 		LOG_WARNING,
995 #else
996 		LOG_INFO,
997 #endif	/* NISDB_LDAP_DEBUG */
998 		"%s: %s: %d update%s requested",
999 		myself, NIL(tm->objName), rnq, rnq != 1 ? "s" : "");
1000 
1001 	/* Perform the updates */
1002 	for (i = rnq = 0; i < (firstOnly ? maxMatches : nq*maxMatches); i++) {
1003 		int	delPerDbId;
1004 
1005 		if (dn[i] == 0)
1006 			continue;
1007 
1008 #ifdef	NISDB_LDAP_DEBUG
1009 		logmsg(MSG_NOTIMECHECK, LOG_INFO,
1010 			"%s: %s %s:dn=%s",
1011 			myself,
1012 			(new != 0 && new[i/maxMatches] != 0) ?
1013 				"modify" : "delete",
1014 			NIL(tpa[i]->dbId), dn[i]);
1015 #endif	/* NISDB_LDAP_DEBUG */
1016 
1017 		delPerDbId = (tpa[i]->objectDN->delDisp == dd_perDbId);
1018 		if ((new != 0 && new[i/maxMatches] != 0) || delPerDbId) {
1019 			/*
1020 			 * Try to modify/create the specified DN. First,
1021 			 * however, if the update changes the DN, make
1022 			 * that change.
1023 			 */
1024 			if (odn[i] == 0 || (r = ldapChangeDN(odn[i], dn[i])) ==
1025 					LDAP_SUCCESS) {
1026 				int	addFirst;
1027 
1028 				addFirst = (new != 0 &&
1029 						new[i/maxMatches] != 0 &&
1030 						!delPerDbId);
1031 				r = ldapModify(dn[i], rv[i],
1032 					tpa[i]->objectDN->write.attrs,
1033 						addFirst);
1034 			}
1035 		} else {
1036 			/* Try to delete the specified DN */
1037 			r = ldapModify(dn[i], 0,
1038 					tpa[i]->objectDN->write.attrs, 0);
1039 		}
1040 
1041 		if (r == LDAP_SUCCESS) {
1042 			rnq++;
1043 		} else {
1044 			if (ret == LDAP_SUCCESS)
1045 				ret = r;
1046 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
1047 				"%s: LDAP %s request error %d for %s:dn=%s",
1048 				myself,
1049 				(new != 0 && new[i/maxMatches] != 0) ?
1050 					"modify" : "delete",
1051 				r, NIL(tpa[i]->dbId), dn[i]);
1052 		}
1053 
1054 		sfree(dn[i]);
1055 		dn[i] = 0;
1056 		freeRuleValue(rv[i], 1);
1057 		rv[i] = 0;
1058 	}
1059 
1060 	sfree(dn);
1061 	sfree(odn);
1062 	sfree(rv);
1063 	sfree(tpa);
1064 
1065 	logmsg(MSG_NOTIMECHECK,
1066 #ifdef	NISDB_LDAP_DEBUG
1067 		LOG_WARNING,
1068 #else
1069 		LOG_INFO,
1070 #endif	/* NISDB_LDAP_DEBUG */
1071 		"%s: %s: %d update%s performed",
1072 		myself, NIL(tm->objName), rnq, rnq != 1 ? "s" : "");
1073 
1074 	return (ret);
1075 }
1076 
1077 /*
1078  * In nis+2ldap, check if the query 'q' matches the selector index 'x->index'.
1079  *
1080  * In nis2ldap, if 'name' is provided then check if its value in 'val'
1081  * matches the selector index. If 'name' is NULL, then check if rule-value 'rv'
1082  * matches the index.
1083  * To match the selector index, all fieldspecs in the indexlist should match
1084  * (AND). In nis2ldap, an exception is, if there are multiple fieldspecs with
1085  * the same fieldname then only one of them needs to match (OR).
1086  * Example:
1087  *	Indexlist = [host="H*", host="I*", user="U*", domain="D*"]
1088  * Then,
1089  *	host = "H1", user="U1", domain="D1" ==> pass
1090  *	host = "I1", user="U1", domain="D1" ==> pass
1091  *	host = "X1", user="U1", domain="D1" ==> fail
1092  *	host = "H1", user="X1", domain="D1" ==> fail
1093  *	host = "H1", user="U1" ==> fail
1094  *
1095  * Return 1 in case of a match, 0 otherwise.
1096  */
1097 int
1098 verifyIndexMatch(__nis_table_mapping_t *x, db_query *q,
1099 		__nis_rule_value_t *rv, char *name, char *val) {
1100 	int	i, j, k, match = 1;
1101 	char	*myself = "verifyIndexMatch";
1102 
1103 	/*
1104 	 * The pass and fail arrays are used by N2L to keep track of
1105 	 * index matches. This saves us from having matches in a
1106 	 * nested loop to decide OR or AND.
1107 	 */
1108 	int	ppos, fpos;
1109 	char	**pass, **fail;
1110 
1111 	if (x == 0)
1112 		return (0);
1113 
1114 	/* Trivial match */
1115 	if (x->index.numIndexes <= 0 || (!yp2ldap && q == 0))
1116 		return (1);
1117 
1118 	if (yp2ldap) {
1119 		if (!(pass = am(myself, x->index.numIndexes * sizeof (char *))))
1120 			return (0);
1121 		if (!(fail = am(myself,
1122 				x->index.numIndexes * sizeof (char *)))) {
1123 			sfree(pass);
1124 			return (0);
1125 		}
1126 		ppos = fpos = 0;
1127 	}
1128 
1129 	/* Check each index */
1130 	for (i = 0; i < x->index.numIndexes; i++) {
1131 		char	*value = 0;
1132 
1133 		/* Skip NULL index names */
1134 		if (x->index.name[i] == 0)
1135 			continue;
1136 
1137 		/* Check N2L values */
1138 		if (yp2ldap) {
1139 			if (name) {
1140 				if (strcasecmp(x->index.name[i], name) == 0)
1141 					value = val;
1142 				else
1143 					continue;
1144 			} else if (rv) {
1145 				if (strcasecmp(x->index.name[i], N2LKEY) == 0 ||
1146 					strcasecmp(x->index.name[i], N2LIPKEY)
1147 							== 0)
1148 					continue;
1149 				value = findVal(x->index.name[i], rv,
1150 							mit_nisplus);
1151 			}
1152 
1153 			if (value && verifyMappingMatch(x->index.value[i],
1154 									value))
1155 				pass[ppos++] = x->index.name[i];
1156 			else
1157 				fail[fpos++] = x->index.name[i];
1158 			continue;
1159 		}
1160 
1161 		/* If here, means nis+2ldap */
1162 
1163 		/* Is the index name a known column ? */
1164 		for (j = 0; j < x->numColumns; j++) {
1165 			if (strcmp(x->index.name[i], x->column[j]) == 0) {
1166 				/*
1167 				 * Do we have a value for the column ?
1168 				 */
1169 				for (k = 0; k < q->components.components_len;
1170 						k++) {
1171 					if (q->components.components_val[k].
1172 							which_index == j) {
1173 						value = q->components.
1174 							components_val[k].
1175 							index_value->
1176 							itemvalue.
1177 							itemvalue_val;
1178 						break;
1179 					}
1180 				}
1181 				if (value != 0)
1182 					break;
1183 			}
1184 		}
1185 
1186 		/*
1187 		 * If we found a value, check if it matches the
1188 		 * format. If no value found or no match, this
1189 		 * mapping is _not_ an alternative. Otherwise,
1190 		 * we continue checking any other indexes.
1191 		 */
1192 		if (value == 0 ||
1193 			!verifyMappingMatch(x->index.value[i],
1194 				value)) {
1195 			match = 0;
1196 			break;
1197 		}
1198 	}
1199 
1200 	if (yp2ldap) {
1201 		for (--fpos; fpos >= 0; fpos--) {
1202 			for (i = 0; i < ppos; i++) {
1203 				if (strcmp(pass[i], fail[fpos]) == 0)
1204 					break;
1205 			}
1206 			if (i == ppos) {
1207 				match = 0;
1208 				break;
1209 			}
1210 		}
1211 		sfree(pass);
1212 		sfree(fail);
1213 	}
1214 
1215 	return (match);
1216 }
1217 
1218 /*
1219  * Return all table mappings that match the column values in 'q'.
1220  * If there's no match, return those alternative mappings that don't
1221  * have an index; if no such mapping exists, return NULL.
1222  *
1223  * If 'wantWrite' is set, we want mappings for writing (i.e., data
1224  * to LDAP); otherwise, we want mappings for reading.
1225  *
1226  * If 'wantObj' is set, we want object mappings only (i.e., _not_
1227  * those used to map entries in tables).
1228  *
1229  * If 'dbId' is non-NULL, we select mappings with a matching dbId field.
1230  */
1231 __nis_table_mapping_t **
1232 selectTableMapping(__nis_table_mapping_t *t, db_query *q,
1233 			int wantWrite, int wantObj, char *dbId,
1234 			int *numMatches) {
1235 	__nis_table_mapping_t	*x, **tp;
1236 	int			i, nm, numap;
1237 	char			*myself = "selectTableMapping";
1238 
1239 	if (numMatches == 0)
1240 		numMatches = &nm;
1241 
1242 	/*
1243 	 * Count the number of possible mappings, so that we can
1244 	 * allocate the 'tp' array up front.
1245 	 */
1246 	for (numap = 0, x = t; x != 0; numap++, x = x->next);
1247 
1248 	if (numap == 0) {
1249 		*numMatches = 0;
1250 		return (0);
1251 	}
1252 
1253 	tp = am(myself, numap * sizeof (tp[0]));
1254 	if (tp == 0) {
1255 		*numMatches = -1;
1256 		return (0);
1257 	}
1258 
1259 	/*
1260 	 * Special cases:
1261 	 *
1262 	 *	q == 0 trivially matches any 't' of the correct object type
1263 	 *
1264 	 *	wantObj != 0 means we ignore 'q'
1265 	 */
1266 	if (q == 0 || wantObj) {
1267 		for (i = 0, x = t, nm = 0; i < numap; i++, x = x->next) {
1268 			if (x->objectDN == 0)
1269 				continue;
1270 			if (wantWrite) {
1271 				if (x->objectDN->write.scope ==
1272 						LDAP_SCOPE_UNKNOWN)
1273 					continue;
1274 			} else {
1275 				if (x->objectDN->read.scope ==
1276 						LDAP_SCOPE_UNKNOWN)
1277 					continue;
1278 			}
1279 			if (wantObj) {
1280 				if (x->numColumns > 0)
1281 					continue;
1282 			} else {
1283 				if (x->numColumns <= 0)
1284 					continue;
1285 			}
1286 			if (dbId != 0 && x->dbId != 0 &&
1287 					strcmp(dbId, x->dbId) != 0)
1288 				continue;
1289 			tp[nm] = x;
1290 			nm++;
1291 		}
1292 		*numMatches = nm;
1293 		if (nm == 0) {
1294 			sfree(tp);
1295 			tp = 0;
1296 		}
1297 		return (tp);
1298 	}
1299 
1300 	/* Scan all mappings, and collect candidates */
1301 	for (nm = 0, x = t; x != 0; x = x->next) {
1302 		if (x->objectDN == 0)
1303 			continue;
1304 		if (wantWrite) {
1305 			if (x->objectDN->write.scope == LDAP_SCOPE_UNKNOWN)
1306 				continue;
1307 		} else {
1308 			if (x->objectDN->read.scope == LDAP_SCOPE_UNKNOWN)
1309 				continue;
1310 		}
1311 		/* Only want table/entry mappings */
1312 		if (x->numColumns <= 0)
1313 			continue;
1314 		if (dbId != 0 && x->dbId != 0 &&
1315 				strcmp(dbId, x->dbId) != 0)
1316 			continue;
1317 		/*
1318 		 * It's a match if: there are no indexes, or we actually
1319 		 * match the query with the indexes.
1320 		 */
1321 		if (x->index.numIndexes <= 0 ||
1322 					verifyIndexMatch(x, q, 0, 0, 0)) {
1323 			tp[nm] = x;
1324 			nm++;
1325 		}
1326 	}
1327 
1328 	if (nm == 0) {
1329 		free(tp);
1330 		tp = 0;
1331 	}
1332 
1333 	*numMatches = nm;
1334 
1335 	return (tp);
1336 }
1337 
1338 /*
1339  * Return 1 if there's an indexed mapping, 0 otherwise.
1340  */
1341 int
1342 haveIndexedMapping(__nis_table_mapping_t *t) {
1343 	__nis_table_mapping_t	*x;
1344 
1345 	for (x = t; x != 0; x = x->next) {
1346 		if (x->index.numIndexes > 0)
1347 			return (1);
1348 	}
1349 
1350 	return (0);
1351 }
1352 
1353 /*
1354  * Given an input string 'attrs' of the form "attr1=val1,attr2=val2,...",
1355  * or a filter, return the value associated with the attribute 'attrName'.
1356  * If no instance of 'attrName' is found, return 'default'. In all cases,
1357  * the return value is a copy, and must be freed by the caller.
1358  *
1359  * Of course, return NULL in case of failure.
1360  */
1361 static char *
1362 attrVal(char *msg, char *attrName, char *def, char *attrs) {
1363 	char	*val, *filter, **fc = 0;
1364 	int	i, nfc;
1365 	char	*myself = "attrVal";
1366 
1367 	if (attrName == 0 || attrs == 0)
1368 		return (0);
1369 
1370 	if (msg == 0)
1371 		msg = myself;
1372 
1373 	val = def;
1374 
1375 	filter = makeFilter(attrs);
1376 	if (filter != 0 && (fc = makeFilterComp(filter, &nfc)) != 0 &&
1377 			nfc > 0) {
1378 		for (i = 0; i < nfc; i++) {
1379 			char	*name, *value;
1380 
1381 			name = fc[i];
1382 			/* Skip if not of attr=value form */
1383 			if ((value = strchr(name, '=')) == 0)
1384 				continue;
1385 
1386 			*value = '\0';
1387 			value++;
1388 
1389 			if (strcasecmp(attrName, name) == 0) {
1390 				val = value;
1391 				break;
1392 			}
1393 		}
1394 	}
1395 
1396 	if (val != 0)
1397 		val = sdup(msg, T, val);
1398 
1399 	sfree(filter);
1400 	freeFilterComp(fc, nfc);
1401 
1402 	return (val);
1403 }
1404 
1405 extern bool_t	xdr_nis_object(register XDR *xdrs, nis_object *objp);
1406 
1407 /*
1408  * Copy an XDR:ed version of the NIS+ object 'o' (or the one indicated
1409  * by 't->objName' if 'o' is NULL) to the place indicated by
1410  * 't->objectDN->write'. Return an appropriate LDAP status code.
1411  */
1412 int
1413 objToLDAP(__nis_table_mapping_t *t, nis_object *o, entry_obj **ea, int numEa) {
1414 	__nis_table_mapping_t	**tp;
1415 	int			stat, osize, n, numMatches = 0;
1416 	void			*buf;
1417 	__nis_rule_value_t	*rv;
1418 	__nis_value_t		*val;
1419 	__nis_single_value_t	*sv;
1420 	char			**attrName, *dn;
1421 	char			*myself = "objToLDAP";
1422 
1423 	if (t == 0)
1424 		return (LDAP_PARAM_ERROR);
1425 
1426 	logmsg(MSG_NOTIMECHECK,
1427 #ifdef	NISDB_LDAP_DEBUG
1428 		LOG_WARNING,
1429 #else
1430 		LOG_INFO,
1431 #endif	/* NISDB_LDAP_DEBUG */
1432 		"%s: %s", myself, NIL(t->objName));
1433 
1434 	tp = selectTableMapping(t, 0, 1, 1, 0, &numMatches);
1435 	if (tp == 0 || numMatches <= 0) {
1436 		sfree(tp);
1437 		logmsg(MSG_NOTIMECHECK,
1438 #ifdef	NISDB_LDAP_DEBUG
1439 			LOG_WARNING,
1440 #else
1441 			LOG_INFO,
1442 #endif	/* NISDB_LDAP_DEBUG */
1443 			"%s: %s (no mapping)", myself, NIL(t->objName));
1444 		return (LDAP_SUCCESS);
1445 	}
1446 
1447 	for (n = 0; n < numMatches; n++) {
1448 
1449 		t = tp[n];
1450 
1451 		if (o == 0) {
1452 			sfree(tp);
1453 			return (LDAP_OPERATIONS_ERROR);
1454 		}
1455 
1456 		buf = (char *)xdrNisObject(o, ea, numEa, &osize);
1457 		if (buf == 0) {
1458 			sfree(tp);
1459 			return (LDAP_OPERATIONS_ERROR);
1460 		}
1461 
1462 		/*
1463 		 * Prepare to build a rule-value containing the XDR:ed
1464 		 * object
1465 		 */
1466 		rv = am(myself, sizeof (*rv));
1467 		sv = am(myself, sizeof (*sv));
1468 		val = am(myself, sizeof (*val));
1469 		attrName = am(myself, sizeof (attrName[0]));
1470 		if (attrName != 0)
1471 			attrName[0] = attrVal(myself, "nisplusObject",
1472 						"nisplusObject",
1473 						t->objectDN->write.attrs);
1474 		if (rv == 0 || sv == 0 || val == 0 || attrName == 0 ||
1475 				attrName[0] == 0) {
1476 			sfree(tp);
1477 			sfree(buf);
1478 			sfree(rv);
1479 			sfree(sv);
1480 			sfree(val);
1481 			sfree(attrName);
1482 			return (LDAP_NO_MEMORY);
1483 		}
1484 
1485 		sv->length = osize;
1486 		sv->value = buf;
1487 
1488 		/* 'vt_ber' just means "not a NUL-terminated string" */
1489 		val->type = vt_ber;
1490 		val->repeat = 0;
1491 		val->numVals = 1;
1492 		val->val = sv;
1493 
1494 		rv->numAttrs = 1;
1495 		rv->attrName = attrName;
1496 		rv->attrVal = val;
1497 
1498 		/*
1499 		 * The 'write.base' is the actual DN of the entry (and the
1500 		 * scope had better be 'base', but we don't check that).
1501 		 */
1502 		dn = t->objectDN->write.base;
1503 
1504 		stat = ldapModify(dn, rv, t->objectDN->write.attrs, 1);
1505 
1506 		freeRuleValue(rv, 1);
1507 
1508 	logmsg(MSG_NOTIMECHECK,
1509 #ifdef	NISDB_LDAP_DEBUG
1510 		LOG_WARNING,
1511 #else
1512 		LOG_INFO,
1513 #endif	/* NISDB_LDAP_DEBUG */
1514 		"%s: %s (%s)", myself, NIL(t->objName), ldap_err2string(stat));
1515 
1516 		if (stat != LDAP_SUCCESS)
1517 			break;
1518 
1519 	}
1520 
1521 	sfree(tp);
1522 
1523 	return (stat);
1524 }
1525 
1526 /*
1527  * Retrieve a copy of the 't->objName' object from LDAP, where it's
1528  * stored in XDR:ed form in the place indicated by 't->objectDN->read'.
1529  * Un-XDR the object, and return a pointer to it in '*obj'; it's the
1530  * responsibility of the caller to free the object when it's no
1531  * longer needed.
1532  *
1533  * Returns an appropriate LDAP status.
1534  */
1535 int
1536 objFromLDAP(__nis_table_mapping_t *t, nis_object **obj,
1537 		entry_obj ***eaP, int *numEaP) {
1538 	__nis_table_mapping_t	**tp;
1539 	nis_object		*o;
1540 	__nis_rule_value_t	*rv;
1541 	__nis_ldap_search_t	*ls;
1542 	char			*attrs[2], *filter, **fc = 0;
1543 	void			*buf;
1544 	int			i, j, nfc, nrv, blen, stat = LDAP_SUCCESS;
1545 	int			n, numMatches;
1546 	char			*myself = "objFromLDAP";
1547 
1548 	if (t == 0)
1549 		return (LDAP_PARAM_ERROR);
1550 
1551 	/*
1552 	 * If there's nowhere to store the result, we might as
1553 	 * well pretend all went well, and return right away.
1554 	 */
1555 	if (obj == 0)
1556 		return (LDAP_SUCCESS);
1557 
1558 	/* Prepare for the worst */
1559 	*obj = 0;
1560 
1561 	logmsg(MSG_NOTIMECHECK,
1562 #ifdef	NISDB_LDAP_DEBUG
1563 		LOG_WARNING,
1564 #else
1565 		LOG_INFO,
1566 #endif	/* NISDB_LDAP_DEBUG */
1567 		"%s: %s", myself, NIL(t->objName));
1568 
1569 	tp = selectTableMapping(t, 0, 0, 1, 0, &numMatches);
1570 	if (tp == 0 || numMatches <= 0) {
1571 		sfree(tp);
1572 		logmsg(MSG_NOTIMECHECK,
1573 #ifdef	NISDB_LDAP_DEBUG
1574 			LOG_WARNING,
1575 #else
1576 			LOG_INFO,
1577 #endif	/* NISDB_LDAP_DEBUG */
1578 			"%s: %s (no mapping)", myself, NIL(t->objName));
1579 		return (LDAP_SUCCESS);
1580 	}
1581 
1582 	for (n = 0; n < numMatches; n++) {
1583 
1584 		t = tp[n];
1585 
1586 		filter = makeFilter(t->objectDN->read.attrs);
1587 		if (filter == 0 || (fc = makeFilterComp(filter, &nfc)) == 0 ||
1588 				nfc <= 0) {
1589 			sfree(tp);
1590 			sfree(filter);
1591 			freeFilterComp(fc, nfc);
1592 			return ((t->objectDN->read.attrs != 0) ?
1593 				LDAP_NO_MEMORY : LDAP_PARAM_ERROR);
1594 		}
1595 		/* Don't need the filter, just the components */
1596 		sfree(filter);
1597 
1598 		/*
1599 		 * Look for a "nisplusObject" attribute, and (if found) copy
1600 		 * the value to attrs[0]. Also remove the "nisplusObject"
1601 		 * attribute and value from the filter components.
1602 		 */
1603 		attrs[0] = sdup(myself, T, "nisplusObject");
1604 		if (attrs[0] == 0) {
1605 			sfree(tp);
1606 			freeFilterComp(fc, nfc);
1607 			return (LDAP_NO_MEMORY);
1608 		}
1609 		attrs[1] = 0;
1610 		for (i = 0; i < nfc; i++) {
1611 			char	*name, *value;
1612 			int	compare;
1613 
1614 			name = fc[i];
1615 			/* Skip if not of attr=value form */
1616 			if ((value = strchr(name, '=')) == 0)
1617 				continue;
1618 
1619 			/* Temporarily overWrite the '=' with a '\0' */
1620 			*value = '\0';
1621 
1622 			/* Compare with our target attribute name */
1623 			compare = strcasecmp("nisplusObject", name);
1624 
1625 			/* Put back the '=' */
1626 			*value = '=';
1627 
1628 			/* Is it the name we're looking for ? */
1629 			if (compare == 0) {
1630 				sfree(attrs[0]);
1631 				attrs[0] = sdup(myself, T, value+1);
1632 				if (attrs[0] == 0) {
1633 					sfree(tp);
1634 					freeFilterComp(fc, nfc);
1635 					return (LDAP_NO_MEMORY);
1636 				}
1637 				sfree(fc[i]);
1638 				if (i < nfc-1)
1639 					(void) memmove(&fc[i], &fc[i+1],
1640 						(nfc-1-i) * sizeof (fc[i]));
1641 				nfc--;
1642 				break;
1643 			}
1644 		}
1645 
1646 		ls = buildLdapSearch(t->objectDN->read.base,
1647 					t->objectDN->read.scope,
1648 					nfc, fc, 0, attrs, 0, 1);
1649 		sfree(attrs[0]);
1650 		freeFilterComp(fc, nfc);
1651 		if (ls == 0) {
1652 			sfree(tp);
1653 			return (LDAP_OPERATIONS_ERROR);
1654 		}
1655 
1656 		nrv = 0;
1657 		rv = ldapSearch(ls, &nrv, 0, &stat);
1658 		if (rv == 0) {
1659 			sfree(tp);
1660 			freeLdapSearch(ls);
1661 			return (stat);
1662 		}
1663 
1664 		for (i = 0, buf = 0; i < nrv && buf == 0; i++) {
1665 			for (j = 0; j < rv[i].numAttrs; j++) {
1666 				if (strcasecmp(ls->attrs[0],
1667 					rv[i].attrName[j]) == 0) {
1668 					if (rv[i].attrVal[j].numVals <= 0)
1669 						continue;
1670 					buf = rv[i].attrVal[j].val[0].value;
1671 					blen = rv[i].attrVal[j].val[0].length;
1672 					break;
1673 				}
1674 			}
1675 		}
1676 
1677 		if (buf != 0) {
1678 			o = unXdrNisObject(buf, blen, eaP, numEaP);
1679 			if (o == 0) {
1680 				sfree(tp);
1681 				freeLdapSearch(ls);
1682 				freeRuleValue(rv, nrv);
1683 				return (LDAP_OPERATIONS_ERROR);
1684 			}
1685 			stat = LDAP_SUCCESS;
1686 			*obj = o;
1687 		} else {
1688 			stat = LDAP_NO_SUCH_OBJECT;
1689 		}
1690 
1691 		freeLdapSearch(ls);
1692 		freeRuleValue(rv, nrv);
1693 
1694 	logmsg(MSG_NOTIMECHECK,
1695 #ifdef	NISDB_LDAP_DEBUG
1696 		LOG_WARNING,
1697 #else
1698 		LOG_INFO,
1699 #endif	/* NISDB_LDAP_DEBUG */
1700 		"%s: %s (%s)", myself, NIL(t->objName), ldap_err2string(stat));
1701 
1702 		if (stat != LDAP_SUCCESS)
1703 			break;
1704 
1705 	}
1706 
1707 	sfree(tp);
1708 
1709 	return (stat);
1710 }
1711 
1712 int
1713 deleteLDAPobj(__nis_table_mapping_t *t) {
1714 	__nis_table_mapping_t	**tp;
1715 	int		n, stat, numMatches = 0;
1716 	char		*myself = "deleteLDAPobj";
1717 
1718 	if (t == 0)
1719 		return (LDAP_PARAM_ERROR);
1720 
1721 	logmsg(MSG_NOTIMECHECK,
1722 #ifdef	NISDB_LDAP_DEBUG
1723 		LOG_WARNING,
1724 #else
1725 		LOG_INFO,
1726 #endif	/* NISDB_LDAP_DEBUG */
1727 		"%s: %s", myself, NIL(t->objName));
1728 
1729 	tp = selectTableMapping(t, 0, 1, 1, 0, &numMatches);
1730 	if (tp == 0 || numMatches <= 0) {
1731 		sfree(tp);
1732 		logmsg(MSG_NOTIMECHECK,
1733 #ifdef	NISDB_LDAP_DEBUG
1734 			LOG_WARNING,
1735 #else
1736 			LOG_INFO,
1737 #endif	/* NISDB_LDAP_DEBUG */
1738 			"%s: %s (no mapping)", myself, NIL(t->objName));
1739 		return (LDAP_SUCCESS);
1740 	}
1741 
1742 	for (n = 0; n < numMatches; n++) {
1743 
1744 		t = tp[n];
1745 
1746 		if (t->objectDN->delDisp == dd_always) {
1747 			/* Delete entire entry */
1748 			stat = ldapModify(t->objectDN->write.base, 0,
1749 					t->objectDN->write.attrs, 1);
1750 		} else if (t->objectDN->delDisp == dd_perDbId) {
1751 			/*
1752 			 * Delete the attribute holding the object.
1753 			 * First, determine what that attribute is called.
1754 			 */
1755 			char			*attrName =
1756 						attrVal(myself,
1757 							"nisplusObject",
1758 							"nisplusObject",
1759 						t->objectDN->write.attrs);
1760 			__nis_rule_value_t	rv;
1761 			__nis_value_t		val;
1762 
1763 			if (attrName == 0) {
1764 				sfree(tp);
1765 				return (LDAP_NO_MEMORY);
1766 			}
1767 
1768 			/*
1769 			 * Build a __nis_value_t with 'numVals' < 0 to
1770 			 * indicate deletion.
1771 			 */
1772 			val.type = vt_ber;
1773 			val.numVals = -1;
1774 			val.val = 0;
1775 
1776 			/*
1777 			 * Build a rule-value with the name we determined
1778 			 * above, and the deletion value.
1779 			 */
1780 			(void) memset(&rv, 0, sizeof (rv));
1781 			rv.numAttrs = 1;
1782 			rv.attrName = &attrName;
1783 			rv.attrVal = &val;
1784 
1785 			stat = ldapModify(t->objectDN->write.base, &rv,
1786 						t->objectDN->write.attrs, 0);
1787 
1788 			sfree(attrName);
1789 		} else if (t->objectDN->delDisp == dd_never) {
1790 			/* Nothing to do, so we're trivially successful */
1791 			stat = LDAP_SUCCESS;
1792 		} else {
1793 			stat = LDAP_PARAM_ERROR;
1794 		}
1795 
1796 	logmsg(MSG_NOTIMECHECK,
1797 #ifdef	NISDB_LDAP_DEBUG
1798 		LOG_WARNING,
1799 #else
1800 		LOG_INFO,
1801 #endif	/* NISDB_LDAP_DEBUG */
1802 		"%s: %s (%s)", myself, NIL(t->objName), ldap_err2string(stat));
1803 
1804 		/* If there were no such object, we've trivially succeeded */
1805 		if (stat == LDAP_NO_SUCH_OBJECT)
1806 			stat = LDAP_SUCCESS;
1807 
1808 		if (stat != LDAP_SUCCESS)
1809 			break;
1810 
1811 	}
1812 
1813 	sfree(tp);
1814 
1815 	return (stat);
1816 }
1817