xref: /illumos-gate/usr/src/lib/libnisdb/yptol/dit_access_utils.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
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 (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 /*
27  * DESCRIPTION:	Contains dit_access interface support functions.
28  */
29 #include <sys/systeminfo.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/systeminfo.h>
35 #include <unistd.h>
36 #include <stdlib.h>
37 #include <syslog.h>
38 #include <ndbm.h>
39 #include <strings.h>
40 #include <errno.h>
41 #include <ctype.h>
42 #include "../ldap_util.h"
43 #include "../ldap_map.h"
44 #include "../ldap_parse.h"
45 #include "../ldap_structs.h"
46 #include "../ldap_val.h"
47 #include "../ldap_ruleval.h"
48 #include "../ldap_op.h"
49 #include "../ldap_attr.h"
50 #include "../ldap_nisdbquery.h"
51 #include "../nisdb_mt.h"
52 #include "shim.h"
53 #include "yptol.h"
54 #include "dit_access_utils.h"
55 
56 #define	YPMULTI		"YP_MULTI_"
57 #define	YPMULTISZ	9		/* == strlen(YPMULTI) */
58 
59 /*
60  * Returns 'map,domain.'
61  */
62 char *
63 getFullMapName(char *map, char *domain) {
64 	char	*myself = "getFullMapName";
65 	char	*objPath;
66 	if (map == 0 || domain == 0) {
67 		return (0);
68 	}
69 	objPath =  scat(myself, T, scat(myself, F, map, ","),
70 		scat(myself, F, domain, "."));
71 
72 	return (objPath);
73 }
74 
75 /*
76  * Convert string to __nis_value_t
77  */
78 __nis_value_t *stringToValue(char *dptr, int dsize) {
79 	char		*myself = "stringToValue";
80 	char		*emptystr = "";
81 	__nis_value_t	*val;
82 
83 	if ((val = am(myself, sizeof (*val))) == 0) {
84 		return (0);
85 	}
86 
87 	val->type = vt_string;
88 	val->repeat = 0;
89 	val->numVals = 1;
90 	if ((val->val = am(myself, sizeof (val->val[0]))) == 0) {
91 		sfree(val);
92 		return (0);
93 	}
94 
95 	/*
96 	 * Null strings or strings with length 0 are treated
97 	 * as empty strings with length 1
98 	 */
99 	if (dptr == 0 || dsize <= 0) {
100 		dptr = emptystr;
101 		dsize = 1;
102 	}
103 
104 	val->val->length = dsize;
105 	if (dptr[dsize - 1] != '\0') {
106 		val->val->length = dsize + 1;
107 	}
108 
109 	val->val->value = am(myself, val->val->length);
110 	if (val->val->value == 0) {
111 		freeValue(val, 1);
112 		return (0);
113 	}
114 	(void) memcpy(val->val->value, dptr, dsize);
115 
116 	return (val);
117 }
118 
119 /*
120  * Returns an array of rule-values corresponding to the
121  * splitfields.
122  */
123 __nis_rule_value_t *
124 processSplitField(__nis_table_mapping_t *sf, __nis_value_t *inVal,
125 			int *nv, int *statP) {
126 
127 	char			*sepset;
128 	__nis_rule_value_t	*rvq;
129 	__nis_value_t		**valA, *tempVal;
130 	int			i, j, res, numVals, oldlen, count;
131 	char			*str, *oldstr;
132 
133 	/* sf will be non NULL */
134 
135 	if (inVal == 0 || inVal->type != vt_string) {
136 		*statP =  MAP_PARAM_ERROR;
137 		return (0);
138 	}
139 
140 	/* Get the separator list */
141 	sepset = sf->separatorStr;
142 
143 	/* Initialize rule-value */
144 	rvq = 0;
145 	count = 0;
146 
147 	if ((tempVal = stringToValue(inVal->val->value,
148 			inVal->val->length)) == 0) {
149 		*statP = MAP_NO_MEMORY;
150 		return (0);
151 	}
152 
153 	str = oldstr = tempVal->val->value;
154 	oldlen = tempVal->val->length;
155 
156 	while (str) {
157 		tempVal->val->value = str;
158 		tempVal->val->length = strlen(str) + 1;
159 
160 		/* Loop to check which format matches str */
161 		for (i = 0; i <= sf->numSplits; i++) {
162 			valA = matchMappingItem(sf->e[i].element.match.fmt,
163 				tempVal, &numVals, sepset, &str);
164 			if (valA == 0) {
165 				/* The format didn't match. Try the next one */
166 				continue;
167 			}
168 
169 			/*
170 			 * If we are here means we had a match.
171 			 * Each new set of values obtained from the match is
172 			 * added to a new rule-value. This is to preserve the
173 			 * the distinction between each set.
174 			 */
175 			rvq = growRuleValue(count, count + 1, rvq, 0);
176 			if (rvq == 0) {
177 				*statP = MAP_INTERNAL_ERROR;
178 				for (j = 0; j < numVals; j++)
179 					freeValue(valA[j], 1);
180 				sfree(valA);
181 				tempVal->val->value = oldstr;
182 				tempVal->val->length = oldlen;
183 				freeValue(tempVal, 1);
184 				return (0);
185 			}
186 			count++;
187 
188 			for (j = 0; j < numVals; j++) {
189 				res = addCol2RuleValue(vt_string,
190 					sf->e[i].element.match.item[j].name,
191 					valA[j]->val->value,
192 					valA[j]->val->length,
193 					&rvq[count - 1]);
194 				if (res == -1) {
195 					*statP = MAP_INTERNAL_ERROR;
196 					for (; j < numVals; j++)
197 						freeValue(valA[j], 1);
198 					sfree(valA);
199 					tempVal->val->value = oldstr;
200 					tempVal->val->length = oldlen;
201 					freeValue(tempVal, 1);
202 					freeRuleValue(rvq, count);
203 					return (0);
204 				}
205 				freeValue(valA[j], 1);
206 			}
207 			sfree(valA);
208 
209 			/*
210 			 * Since we had a match, break out of this loop
211 			 * to parse remainder of str
212 			 */
213 			break;
214 		}
215 
216 		/* Didn't find any match, so get out of the loop */
217 		if (i > sf->numSplits) {
218 			str = 0;
219 			break;
220 		}
221 
222 		/* Skip the separators before looping back */
223 		if (str) {
224 			str = str + strspn(str, sepset);
225 			if (*str == '\0')
226 				break;
227 		}
228 	}
229 
230 	tempVal->val->value = oldstr;
231 	tempVal->val->length = oldlen;
232 	freeValue(tempVal, 1);
233 
234 	if (str == 0) {
235 		freeRuleValue(rvq, count);
236 		return (0);
237 	}
238 
239 	if (nv != 0)
240 		*nv = count;
241 
242 	return (rvq);
243 }
244 
245 /*
246  * Convert the datum to an array of RuleValues
247  */
248 __nis_rule_value_t *
249 datumToRuleValue(datum *key, datum *value, __nis_table_mapping_t *t,
250 			int *nv, char *domain, bool_t readonly, int *statP) {
251 
252 	__nis_rule_value_t	*rvq, *subrvq, *newrvq;
253 	__nis_value_t		*val;
254 	__nis_value_t		**valA;
255 	__nis_table_mapping_t	*sf;
256 	int			valueLen, comLen, numVals, nr, count = 1;
257 	int			i, j, k, l;
258 	char			*ipaddr, *ipvalue;
259 
260 	/*  At this point, 't' is always non NULL */
261 
262 	/* Initialize rule-value */
263 	if ((rvq = initRuleValue(1, 0)) == 0) {
264 		*statP = MAP_INTERNAL_ERROR;
265 		return (0);
266 	}
267 
268 	/* Add domainname to rule-value */
269 	if (addCol2RuleValue(vt_string, N2LDOMAIN, domain, strlen(domain),
270 						rvq)) {
271 		freeRuleValue(rvq, 1);
272 		*statP = MAP_INTERNAL_ERROR;
273 		return (0);
274 	}
275 
276 	/* Handle key */
277 	if (key != 0) {
278 		/* Add field=value pair for N2LKEY */
279 		i = addCol2RuleValue(vt_string, N2LKEY, key->dptr, key->dsize,
280 						rvq);
281 
282 		/* For readonly, add field=value pair for N2LSEARCHKEY */
283 		if (readonly == TRUE && i == 0) {
284 			i = addCol2RuleValue(vt_string, N2LSEARCHKEY, key->dptr,
285 						key->dsize, rvq);
286 		}
287 		if (i) {
288 			freeRuleValue(rvq, 1);
289 			*statP = MAP_INTERNAL_ERROR;
290 			return (0);
291 		}
292 
293 		/* Add field=value pairs for IP addresses */
294 		if (checkIPaddress(key->dptr, key->dsize, &ipaddr) > 0) {
295 			/* If key is IPaddress, use preferred format */
296 			ipvalue = ipaddr;
297 			valueLen = strlen(ipaddr);
298 			i = addCol2RuleValue(vt_string, N2LIPKEY, ipvalue,
299 						valueLen, rvq);
300 		} else {
301 			/* If not, use original value for N2LSEARCHIPKEY */
302 			ipaddr = 0;
303 			ipvalue = key->dptr;
304 			valueLen = key->dsize;
305 			i = 0;
306 		}
307 
308 		if (readonly == TRUE && i == 0) {
309 			i = addCol2RuleValue(vt_string, N2LSEARCHIPKEY, ipvalue,
310 								valueLen, rvq);
311 		}
312 		sfree(ipaddr);
313 		if (i) {
314 			freeRuleValue(rvq, 1);
315 			*statP = MAP_INTERNAL_ERROR;
316 			return (0);
317 		}
318 	}
319 
320 	/* Handle datum value */
321 	if (value != 0 && t->e) {
322 		valueLen = value->dsize;
323 		/*
324 		 * Extract the comment, if any, and add it to
325 		 * the rule-value.
326 		 */
327 		if (t->commentChar != '\0') {
328 			/*
329 			 * We loop on value->dsize because value->dptr
330 			 * may not be NULL-terminated.
331 			 */
332 			for (i = 0; i < value->dsize; i++) {
333 				if (value->dptr[i] == t->commentChar) {
334 					valueLen = i;
335 					comLen = value->dsize - i - 1;
336 					if (comLen == 0)
337 						break;
338 					if (addCol2RuleValue(vt_string,
339 						N2LCOMMENT, value->dptr + i + 1,
340 						comLen, rvq)) {
341 						freeRuleValue(rvq, 1);
342 						*statP = MAP_INTERNAL_ERROR;
343 						return (0);
344 					}
345 					break;
346 				}
347 			}
348 		}
349 
350 		/* Skip trailing whitespaces */
351 		for (; valueLen > 0 && (value->dptr[valueLen - 1] == ' ' ||
352 			value->dptr[valueLen - 1] == '\t'); valueLen--);
353 
354 		/*
355 		 * At this point valueLen is the effective length of
356 		 * the data. Convert value into __nis_value_t so that
357 		 * we can use the matchMappingItem function to break it
358 		 * into fields.
359 		 */
360 		if ((val = stringToValue(value->dptr, valueLen)) == 0) {
361 			freeRuleValue(rvq, 1);
362 			*statP = MAP_NO_MEMORY;
363 			return (0);
364 		}
365 
366 		/* Perform namefield match */
367 		valA = matchMappingItem(t->e->element.match.fmt, val,
368 							&numVals, 0, 0);
369 		if (valA == 0) {
370 			freeValue(val, 1);
371 			freeRuleValue(rvq, 1);
372 			*statP = MAP_NAMEFIELD_MATCH_ERROR;
373 			return (0);
374 		}
375 
376 		/* We don't need val anymore, so free it */
377 		freeValue(val, 1);
378 
379 		/*
380 		 * Since matchMappingItem only returns us an array of
381 		 * __nis_value_t's, we need to associate each value
382 		 * in the array with the corresponding item name.
383 		 * This code assumes that numVals will be less than or
384 		 * equal to the number of item names associated with
385 		 * the format.
386 		 * These name=value pairs are added to rvq.
387 		 */
388 		for (i = 0, *statP = SUCCESS; i < numVals; i++) {
389 			for (j = 0; j < count; j++) {
390 				if (addCol2RuleValue(vt_string,
391 					t->e->element.match.item[i].name,
392 					valA[i]->val->value,
393 					valA[i]->val->length, &rvq[j])) {
394 					*statP = MAP_INTERNAL_ERROR;
395 					break;
396 				}
397 			}
398 			if (*statP == MAP_INTERNAL_ERROR)
399 				break;
400 
401 			/*
402 			 * Check if splitField exists for the field.
403 			 * Since splitfields are also stored as mapping
404 			 * structures, we need to get the hash table entry
405 			 * corresponding to the splitfield name
406 			 */
407 			sf = mappingFromMap(t->e->element.match.item[i].name,
408 					domain, statP);
409 			if (*statP == MAP_NO_MEMORY)
410 				break;
411 			*statP = SUCCESS;
412 			if (sf == 0)
413 				continue;
414 
415 			/*
416 			 * Process and add splitFields to rule-value rvq
417 			 */
418 			subrvq = processSplitField(sf, valA[i], &nr, statP);
419 
420 			if (subrvq == 0) {
421 				/* statP would have been set */
422 				break;
423 			}
424 
425 			/*
426 			 * We merge 'count' rule-values in rvq with 'nr'
427 			 * rule-values from subrvq to give us a whopping
428 			 * 'count * nr' rule-values
429 			 */
430 
431 			/* Initialize the new rule-value array */
432 			if ((newrvq = initRuleValue(count * nr, 0)) == 0) {
433 				*statP = MAP_INTERNAL_ERROR;
434 				freeRuleValue(subrvq, nr);
435 				break;
436 			}
437 
438 			for (j = 0, l = 0; j < nr; j++) {
439 				for (k = 0; k < count; k++, l++) {
440 					if ((mergeRuleValue(&newrvq[l],
441 							&rvq[k]) == -1) ||
442 							(mergeRuleValue(
443 							&newrvq[l],
444 							&subrvq[j]) == -1)) {
445 						*statP = MAP_INTERNAL_ERROR;
446 						for (i = 0; i < numVals; i++)
447 							freeValue(valA[i], 1);
448 						sfree(valA);
449 						freeRuleValue(rvq, count);
450 						freeRuleValue(newrvq,
451 								count * nr);
452 						freeRuleValue(subrvq, nr);
453 						return (0);
454 					}
455 				}
456 			}
457 
458 			freeRuleValue(rvq, count);
459 			rvq = newrvq;
460 			count = l;
461 			freeRuleValue(subrvq, nr);
462 
463 		}
464 
465 		/* We don't need valA anymore, so free it */
466 		for (i = 0; i < numVals; i++)
467 			freeValue(valA[i], 1);
468 		sfree(valA);
469 
470 		if (*statP != SUCCESS) {
471 			freeRuleValue(rvq, count);
472 			return (0);
473 		}
474 
475 	} /* if value */
476 
477 	if (nv != 0)
478 		*nv = count;
479 	return (rvq);
480 
481 }
482 
483 /*
484  * Generate name=values pairs for splitfield names
485  *
486  * Consider Example:
487  *	nisLDAPnameFields club:
488  *			("%s %s %s", name, code, members)
489  *	nisLDAPsplitField members:
490  *			("(%s,%s,%s)", host, user, domain),
491  *			("%s", group)
492  * On entry,
493  * - rv is an array of numVals rule-values each containing
494  * name=value pairs for names occuring in nisLDAPsplitField.
495  * (i.e host, user, domain, group)
496  * - trv contains name=value pairs for names occuring in
497  * nisLDAPnameFields. (i.e name, code but not members)
498  *
499  * For every name in nisLDAPnamefields that is a splitfield,
500  * this function applies the data in rv to the corresponding
501  * splitfield formats (accessed thru t), to generate a single
502  * string value for the corresponding splitfield (members).
503  * This new name=value pair is then added to trv.
504  * Besides, any uninitialized namefield names are set to empty strings.
505  */
506 suc_code
507 addSplitFieldValues(__nis_table_mapping_t *t, __nis_rule_value_t *rv,
508 			__nis_rule_value_t *trv, int numVals, char *domain) {
509 	__nis_table_mapping_t	*sf;
510 	__nis_value_t		*val;
511 	int			i, j, k, nitems, res, statP;
512 	char			*str, *tempstr;
513 	char			delim[2] = {0, 0};
514 	char			*emptystr = "";
515 	char			*myself = "addSplitFieldValues";
516 
517 	if (trv == 0)
518 		return (MAP_INTERNAL_ERROR);
519 
520 	if (t->e == 0)
521 		return (SUCCESS);
522 
523 	nitems = t->e->element.match.numItems;
524 
525 	/*
526 	 * Procedure:
527 	 * - Check each name in nisLDAPnamefield
528 	 * - if it's a splifield, construct its value and add it to trv
529 	 * - if not, check if it has a value
530 	 * - if not, add empty string
531 	 */
532 	for (i = 0, sf = 0; i < nitems; i++) {
533 		if (rv) {
534 			/*
535 			 * str will eventually contain the single string
536 			 * value for the corresponding  splitfield.
537 			 * No point initializing str if rv == 0 because
538 			 * splitfield cannot be constructed without rv.
539 			 * So, only initialized here.
540 			 */
541 			str = 0;
542 
543 			/* Check if it's a splitfield name */
544 			sf = mappingFromMap(t->e->element.match.item[i].name,
545 				domain, &statP);
546 
547 			/*
548 			 * Return only incase of memory allocation failure.
549 			 * The other error case (MAP_NO_MAPPING_EXISTS),
550 			 * indicates that the item name is not a splitfieldname
551 			 * i.e it's a namefieldname. This case is handled by
552 			 * the following if (sf == 0)
553 			 */
554 			if (statP == MAP_NO_MEMORY)
555 				return (statP);
556 		}
557 
558 		if (sf == 0) {
559 			/*
560 			 * Not a splitfield name. Verify if it has a value
561 			 */
562 			if (findVal(t->e->element.match.item[i].name,
563 				trv, mit_nisplus) == 0) {
564 				/* if not, use empty string */
565 				res = addCol2RuleValue(vt_string,
566 					t->e->element.match.item[i].name,
567 					emptystr, 0, trv);
568 				if (res == -1) {
569 					return (MAP_INTERNAL_ERROR);
570 				}
571 			}
572 			/*
573 			 * If rv == 0 then sf == 0 so we will continue here
574 			 * i.e. does not matter that str is not yet set up.
575 			 */
576 			continue;
577 		}
578 
579 		/* Code to construct a single value */
580 
581 		/* Use the first separator character as the delimiter */
582 		delim[0] = sf->separatorStr[0];
583 
584 		for (j = 0; j < numVals; j++) {
585 			/* sf->numSplits is zero-based */
586 			for (k = 0; k <= sf->numSplits; k++) {
587 				val = getMappingFormatArray(
588 					sf->e[k].element.match.fmt, &rv[j],
589 					fa_item,
590 					sf->e[k].element.match.numItems,
591 					sf->e[k].element.match.item);
592 				if (val == 0)
593 					continue;
594 				if (val->numVals > 0) {
595 					if (str) {
596 						tempstr = scat(myself,
597 							0, str, delim);
598 						sfree(str);
599 						if (tempstr)
600 							str = tempstr;
601 						else {
602 							freeValue(val, 1);
603 							return (MAP_NO_MEMORY);
604 						}
605 					}
606 					tempstr = scat(myself, 0, str,
607 						val->val->value);
608 					sfree(str);
609 					if (tempstr)
610 						str = tempstr;
611 					else {
612 						freeValue(val, 1);
613 						return (MAP_NO_MEMORY);
614 					}
615 				}
616 				freeValue(val, 1);
617 			}
618 		}
619 		if (str == 0)
620 			str = emptystr;
621 
622 		res = addCol2RuleValue(vt_string,
623 				t->e->element.match.item[i].name,
624 				str, strlen(str), trv);
625 
626 		if (str != emptystr)
627 			sfree(str);
628 
629 		if (res == -1) {
630 			return (MAP_INTERNAL_ERROR);
631 		}
632 	}
633 
634 	return (SUCCESS);
635 }
636 
637 /*
638  * Updates 'rv' with NIS name=value pairs suitable to
639  * construct datum from namefield information.
640  * Some part based on createNisPlusEntry (from ldap_nisdbquery.c)
641  * This code assumes that from a given LDAP entry, applying the
642  * mapping rules, would give us one or more NIS entries, differing
643  * only in key.
644  */
645 suc_code
646 buildNISRuleValue(__nis_table_mapping_t *t, __nis_rule_value_t *rv,
647 					char *domain) {
648 	int			r, i, j, k, l, nrq, res, len;
649 	int			numItems, splitname, count, statP;
650 	__nis_value_t		*rval;
651 	__nis_mapping_item_t	*litem;
652 	__nis_mapping_rule_t	*rl;
653 	__nis_rule_value_t	*rvq;
654 	char			*value, *emptystr = "";
655 
656 	statP = SUCCESS;
657 
658 	/* Initialize default base */
659 	__nisdb_get_tsd()->searchBase = t->objectDN->read.base;
660 
661 	/* Initialize rule-value rvq */
662 	rvq = 0;
663 	count = 0;
664 
665 	/* Add domainname to rule-value */
666 	if (addCol2RuleValue(vt_string, N2LDOMAIN, domain, strlen(domain),
667 						rv)) {
668 		return (MAP_INTERNAL_ERROR);
669 	}
670 
671 	for (r = 0; r < t->numRulesFromLDAP; r++) {
672 		rl = t->ruleFromLDAP[r];
673 
674 		/* Set escapeFlag if RHS is "dn" to remove escape chars */
675 		if (rl->rhs.numElements == 1 &&
676 			rl->rhs.element->type == me_item &&
677 			rl->rhs.element->element.item.type == mit_ldap &&
678 			strcasecmp(rl->rhs.element->element.item.name, "dn")
679 									== 0) {
680 				__nisdb_get_tsd()->escapeFlag = '2';
681 		}
682 
683 		rval = buildRvalue(&rl->rhs, mit_ldap, rv, NULL);
684 
685 		/* Reset escapeFlag */
686 		__nisdb_get_tsd()->escapeFlag = '\0';
687 
688 		if (rval == 0) {
689 			continue;
690 		}
691 
692 		if (rval->numVals <= 0) {
693 			/* Treat as invalid */
694 			freeValue(rval, 1);
695 			continue;
696 		}
697 
698 		litem = buildLvalue(&rl->lhs, &rval, &numItems);
699 		if (litem == 0) {
700 			/* This will take care of numItems == 0 */
701 			freeValue(rval, 1);
702 			continue;
703 		}
704 
705 		if (rval->numVals > 1) {
706 			if (numItems == 1 && litem->repeat)
707 				nrq = rval->numVals;
708 			else if (numItems > 1 && rval->repeat)
709 				nrq = 1 + ((rval->numVals-1)/numItems);
710 			else
711 				nrq = 1;
712 		} else
713 			nrq = 1;
714 
715 		/* Set splitname if splitfield names are specified */
716 		for (i = 0; i < numItems; i++) {
717 			if (strcasecmp(litem[i].name, N2LKEY) == 0 ||
718 				strcasecmp(litem[i].name, N2LIPKEY) == 0 ||
719 				strcasecmp(litem[i].name, N2LCOMMENT) == 0)
720 				continue;
721 			for (j = 0; j < t->numColumns; j++) {
722 				if (strcmp(litem[i].name, t->column[j]) == 0)
723 					break;
724 			}
725 			if (j == t->numColumns)
726 				break;
727 		}
728 
729 		splitname = (i < numItems)?1:0;
730 
731 		for (j = 0; j < nrq; j++) {
732 			if (splitname == 1) {
733 				/*
734 				 * Put every value of splitfieldname in a new
735 				 * rule-value. Helps generating splitfields.
736 				 */
737 				rvq = growRuleValue(count, count + 1, rvq, 0);
738 				if (rvq == 0) {
739 					freeRuleValue(rvq, count);
740 					freeValue(rval, 1);
741 					freeMappingItem(litem, numItems);
742 					return (MAP_INTERNAL_ERROR);
743 				}
744 				count++;
745 			}
746 
747 			for (k = j % nrq, l = 0; l < numItems; k += nrq, l++) {
748 				/* If we run out of values, use empty strings */
749 				if (k >= rval->numVals) {
750 					value = emptystr;
751 					len = 0;
752 				} else {
753 					value = rval->val[k].value;
754 					len = rval->val[k].length;
755 				}
756 				res = (splitname == 1)?addCol2RuleValue(
757 					vt_string, litem[l].name, value,
758 					len, &rvq[count - 1]):0;
759 				if (res != -1)
760 					res = addCol2RuleValue(vt_string,
761 						litem[l].name, value, len, rv);
762 				if (res == -1) {
763 					freeRuleValue(rvq, count);
764 					freeValue(rval, 1);
765 					freeMappingItem(litem, numItems);
766 					return (MAP_INTERNAL_ERROR);
767 				}
768 			}
769 		}
770 		freeValue(rval, 1);
771 		rval = 0;
772 		freeMappingItem(litem, numItems);
773 		litem = 0;
774 		numItems = 0;
775 	} /* for r < t->numRulesFromLDAP */
776 
777 	statP = addSplitFieldValues(t, rvq, rv, count, domain);
778 
779 	if (rvq)
780 		freeRuleValue(rvq, count);
781 
782 	if (verifyIndexMatch(t, 0, rv, 0, 0) == 0)
783 		return (MAP_INDEXLIST_ERROR);
784 	return (statP);
785 
786 } /* end of buildNISRuleValue */
787 
788 /*
789  * Convert rule-value to datum using namefield information
790  */
791 datum *
792 ruleValueToDatum(__nis_table_mapping_t *t, __nis_rule_value_t *rv, int *statP) {
793 	__nis_value_t	*val;
794 	datum		*value;
795 	char		*str, *cstr, commentSep[3] = {' ', 0, 0};
796 	char		*myself = "ruleValueToDatum";
797 
798 	/* No error yet */
799 	*statP = 0;
800 
801 	/* Return empty datum if no namefield information available */
802 	if (t->e == 0) {
803 		if ((value = am(myself, sizeof (*value))) == 0)
804 			*statP = MAP_NO_MEMORY;
805 		return (value);
806 	}
807 
808 	val = getMappingFormatArray(t->e->element.match.fmt, rv,
809 				fa_item, t->e->element.match.numItems,
810 				t->e->element.match.item);
811 
812 	if (val && val->val && val->val->value) {
813 		if ((value = am(myself, sizeof (*value))) == 0) {
814 			*statP = MAP_NO_MEMORY;
815 			freeValue(val, 1);
816 			return (0);
817 		}
818 
819 		/* Strip trailing whitespaces */
820 		cstr = (char *)val->val->value + val->val->length;
821 		for (; cstr >= (char *)val->val->value &&
822 			(*cstr == ' ' || *cstr == '\t'); *cstr-- = '\0');
823 
824 		if (t->commentChar != '\0' &&
825 		    (str = findVal(N2LCOMMENT, rv, mit_nisplus)) != 0 &&
826 		    *str != '\0') {
827 			commentSep[1] = t->commentChar;
828 			cstr = scat(myself, F, commentSep, str);
829 			if (cstr) {
830 				value->dptr = scat(myself, F,
831 						val->val->value, cstr);
832 				sfree(cstr);
833 			}
834 		} else {
835 			value->dptr = sdup(myself, T, val->val->value);
836 		}
837 		freeValue(val, 1);
838 		if (value->dptr) {
839 			value->dsize = strlen(value->dptr);
840 			return (value);
841 		} else {
842 			*statP = MAP_NO_MEMORY;
843 			sfree(value);
844 			return (0);
845 		}
846 	}
847 
848 	*statP = MAP_NAMEFIELD_MATCH_ERROR;
849 	return (0);
850 }
851 
852 datum *
853 getKeyFromRuleValue(__nis_table_mapping_t *t, __nis_rule_value_t *rv, int *nv,
854     int *statP, bool_t xlate_to_lcase)
855 {
856 	int	i, j, k;
857 	datum	*key = 0;
858 	char	*str;
859 	char	*myself = "getKeyFromRuleValue";
860 
861 	/* No error yet */
862 	*statP = 0;
863 
864 	if (rv == 0 || nv == 0)
865 		return (0);
866 
867 	for (i = 0; i < rv->numColumns; i++) {
868 		if (rv->colName[i] == 0)
869 			continue;
870 		if (strcasecmp(N2LKEY, rv->colName[i]) == 0 ||
871 		    strcasecmp(N2LIPKEY, rv->colName[i]) == 0) {
872 			if ((*nv = rv->colVal[i].numVals) == 0)
873 				return (0);
874 			if ((key = am(myself, sizeof (key[0]) * *nv)) == 0) {
875 				*statP = MAP_NO_MEMORY;
876 				return (0);
877 			}
878 			for (j = 0; j < *nv; j++) {
879 				if ((str = rv->colVal[i].val[j].value) == 0) {
880 					key[j].dsize = 0;
881 					key[j].dptr = 0;
882 				} else {
883 					if (verifyIndexMatch(t, 0, 0,
884 					    rv->colName[i], str) == 0) {
885 						key[j].dsize = 0;
886 						key[j].dptr = 0;
887 						continue;
888 					}
889 
890 					key[j].dsize = strlen(str);
891 					key[j].dptr = am(myself,
892 					    key[j].dsize + 1);
893 					if (key[j].dptr == 0) {
894 						*statP = MAP_NO_MEMORY;
895 						for (--j; j >= 0; j--)
896 							sfree(key[j].dptr);
897 						sfree(key);
898 						return (0);
899 					}
900 
901 					/* transliterate key to lowercase */
902 					if (xlate_to_lcase == TRUE) {
903 
904 						/*
905 						 * For multi-homed
906 						 * entries, skip over
907 						 * "YP_MULTI_" prefix.
908 						 */
909 						k = 0;
910 						if (strncmp(YPMULTI, str,
911 						    YPMULTISZ) == 0) {
912 							k = YPMULTISZ;
913 							bcopy(str, key[j].dptr,
914 							    YPMULTISZ);
915 						}
916 						while (k < key[j].dsize) {
917 							key[j].dptr[k] =
918 							    (char)tolower(
919 							    (int)(uchar_t)
920 							    str[k]);
921 							k++;
922 						}
923 					} else {
924 						bcopy(str, key[j].dptr,
925 						    key[j].dsize);
926 					}
927 				}
928 			}
929 			return (key);
930 		}
931 	}
932 	return (0);
933 }
934 
935 /*
936  * Get the mapping structure corresponding to `map,domain.'
937  */
938 __nis_table_mapping_t *
939 mappingFromMap(char *map, char *domain, int *statP) {
940 	char			*mapPath;
941 	__nis_table_mapping_t	*t;
942 
943 	/* No error yet */
944 	*statP = 0;
945 
946 	/* Construct map,domain. */
947 	if ((mapPath = getFullMapName(map, domain)) == 0) {
948 		*statP = MAP_NO_MEMORY;
949 		return (0);
950 	}
951 
952 	/* Get the hash table entry for the mapPath */
953 	if ((t = __nis_find_item_mt(mapPath, &ldapMappingList, 1, 0))
954 			== 0) {
955 		*statP = MAP_NO_MAPPING_EXISTS;
956 	}
957 	sfree(mapPath);
958 	return (t);
959 }
960 
961 /*
962  * Verify at least one key value obtained from DIT matches the search key
963  * RETURNS:	 1	MATCH
964  *		 0	NO MATCH
965  *		-1	NO KEY FOUND
966  */
967 static int
968 verifyKey(char *key, __nis_rule_value_t *rv) {
969 	int	i, j;
970 	char	*sipkey, *str;
971 
972 	for (i = 0; i < rv->numColumns; i++) {
973 		if (rv->colName[i] == 0)
974 			continue;
975 		if (strcasecmp(N2LKEY, rv->colName[i]) == 0) {
976 			if (rv->colVal[i].val == 0)
977 				return (0);
978 			for (j = 0; j < rv->colVal[i].numVals; j++) {
979 				str = (char *)rv->colVal[i].val[j].value;
980 				if (str && strcmp(str, key) == 0)
981 					return (1);
982 			}
983 			return (0);
984 		} else if (strcasecmp(N2LIPKEY, rv->colName[i]) == 0) {
985 			if (checkIPaddress(key, strlen(key), &sipkey) > 0) {
986 				if (rv->colVal[i].val == 0)
987 					return (0);
988 				for (j = 0; j < rv->colVal[i].numVals; j++) {
989 					str = rv->colVal[i].val[j].value;
990 					if (str && strcmp(str, sipkey) == 0) {
991 						sfree(sipkey);
992 						return (1);
993 					}
994 				}
995 				sfree(sipkey);
996 			}
997 			return (0);
998 		}
999 	}
1000 	return (-1);
1001 }
1002 
1003 /*
1004  * Read (i.e get and map) a single NIS entry from the LDAP DIT
1005  */
1006 bool_t
1007 singleReadFromDIT(char *map, char *domain, datum *key, datum *value,
1008 							int *statP) {
1009 	__nis_table_mapping_t	*t;
1010 	__nis_rule_value_t	*rv_request = 0, *rv_result = 0;
1011 	__nis_ldap_search_t	*ls;
1012 	__nis_object_dn_t	*objectDN = NULL;
1013 	int			i, rc, nr = 0;
1014 	datum			*datval = 0;
1015 	char			*skey, *str;
1016 	char			*myself = "singleReadFromDIT";
1017 
1018 	*statP = SUCCESS;
1019 
1020 	if (!map || !domain || !key || !value) {
1021 		*statP = MAP_PARAM_ERROR;
1022 		return (FALSE);
1023 	}
1024 
1025 
1026 	/* Get the mapping information for the map */
1027 	if ((t = mappingFromMap(map, domain, statP)) == 0) {
1028 		/*
1029 		 * No problem. We don't handle this map and domain. Maybe it's
1030 		 * handled by a service other than NIS.
1031 		 */
1032 		return (FALSE);
1033 	}
1034 
1035 	/* NULL-terminated version of datum key for logging */
1036 	if ((skey = am(myself, key->dsize + 1)) == 0) {
1037 		*statP = MAP_NO_MEMORY;
1038 		return (FALSE);
1039 	}
1040 	(void) memcpy(skey, key->dptr, key->dsize);
1041 
1042 	if ((str = getFullMapName(map, domain)) == 0) {
1043 		*statP = MAP_NO_MEMORY;
1044 		return (FALSE);
1045 	}
1046 
1047 	/* For each alternate mapping */
1048 	for (; t != 0; t = t->next) {
1049 		/* Verify objName */
1050 		if (strcmp(str, t->objName) != 0) {
1051 			continue;
1052 		}
1053 
1054 		/* Verify if key matches the index */
1055 		if (verifyIndexMatch(t, 0, 0, N2LKEY, skey) == 0 ||
1056 			verifyIndexMatch(t, 0, 0, N2LIPKEY, skey) == 0)
1057 			continue;
1058 
1059 		/* Check if rulesFromLDAP are provided */
1060 		if (t->numRulesFromLDAP == 0) {
1061 			logmsg(MSG_NOTIMECHECK, LOG_INFO,
1062 				"%s: No rulesFromLDAP information available "
1063 				"for %s (%s)", myself, t->dbId, map);
1064 			continue;
1065 		}
1066 
1067 		/* Convert key into rule-value */
1068 		if ((rv_request = datumToRuleValue(key, 0, t, 0, domain, TRUE,
1069 								statP)) == 0) {
1070 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
1071 				"%s: Conversion error %d (NIS to name=value "
1072 				"pairs) for NIS key (%s) for %s (%s)",
1073 				myself, *statP, skey, t->dbId, map);
1074 			continue;
1075 		}
1076 		/* Convert rule-value into ldap request */
1077 		for (objectDN = t->objectDN; objectDN &&
1078 				objectDN->read.base;
1079 				objectDN = objectDN->next) {
1080 			ls = createLdapRequest(t, rv_request, 0, 1, NULL,
1081 								objectDN);
1082 			if (ls == 0) {
1083 				*statP = MAP_CREATE_LDAP_REQUEST_ERROR;
1084 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
1085 					"%s: Failed to create ldapSearch "
1086 					"request for "
1087 					"NIS key (%s) for %s (%s) "
1088 					"for base %s",
1089 					myself, skey, t->dbId, map,
1090 					objectDN->read.base);
1091 				continue;
1092 			}
1093 			ls->timeout.tv_sec = SINGLE_ACCESS_TIMEOUT_SEC;
1094 			ls->timeout.tv_usec = SINGLE_ACCESS_TIMEOUT_USEC;
1095 			/* Query LDAP */
1096 			nr = (ls->isDN)?0:-1;
1097 			rv_result = ldapSearch(ls, &nr, 0, statP);
1098 			freeLdapSearch(ls);
1099 			if (rv_result == 0) {
1100 				if (*statP == LDAP_NO_SUCH_OBJECT) {
1101 					/* Entry does not exist in */
1102 					/* the ldap server */
1103 				}
1104 				continue;
1105 			}
1106 			freeRuleValue(rv_request, 1);
1107 			rv_request = 0;
1108 
1109 			/* if result > 1, first match will be returned */
1110 			if (nr > 1) {
1111 				logmsg(MSG_NOTIMECHECK, LOG_INFO,
1112 					"%s: %d ldapSearch results "
1113 					"for NIS key (%s) "
1114 					"for %s (%s) for base %s. "
1115 					"First match will be returned ",
1116 					myself, nr, skey, t->dbId, map,
1117 					objectDN->read.base);
1118 			}
1119 
1120 			for (i = 0; i < nr; i++) {
1121 				/* Convert LDAP data to NIS equivalents */
1122 				*statP = buildNISRuleValue(t, &rv_result[i],
1123 								domain);
1124 				if (*statP == MAP_INDEXLIST_ERROR)
1125 					continue;
1126 
1127 				if (*statP != SUCCESS) {
1128 					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1129 					"%s: Conversion error %d (LDAP to "
1130 					"name=value pairs) for NIS key (%s) "
1131 					"for %s (%s) for base %s", myself,
1132 					*statP, skey,
1133 					t->dbId, map, objectDN->read.base);
1134 					continue;
1135 				}
1136 
1137 			/*
1138 			 * Check if 'key' from the ldap result matches the key
1139 			 * provided by our caller
1140 			 */
1141 				if ((rc = verifyKey(skey, &rv_result[i]))
1142 						== -1) {
1143 					logmsg(MSG_NOTIMECHECK, LOG_INFO,
1144 					"%s: Cannot verify key from ldap "
1145 					"result for NIS key (%s) for %s (%s) "
1146 					"for base %s",
1147 					myself, skey, t->dbId, map,
1148 					objectDN->read.base);
1149 					continue;
1150 				}
1151 
1152 				if (rc == 1) {
1153 					datval = ruleValueToDatum(t,
1154 							&rv_result[i], statP);
1155 					if (datval == 0) {
1156 						logmsg(MSG_NOTIMECHECK,
1157 						LOG_WARNING,
1158 						"%s: Conversion error %d "
1159 						"(name=value pairs to NIS) "
1160 						"for NIS key (%s) for %s (%s)"
1161 						" for base %s",
1162 						myself,
1163 						*statP, skey, t->dbId, map,
1164 						objectDN->read.base);
1165 						continue;
1166 					}
1167 					if (value) {
1168 						value->dptr = datval->dptr;
1169 						value->dsize = datval->dsize;
1170 					}
1171 					sfree(datval);
1172 					sfree(skey);
1173 					freeRuleValue(rv_result, nr);
1174 					rv_result = 0;
1175 					*statP = SUCCESS;
1176 
1177 					/* Free full map name */
1178 					sfree(str);
1179 
1180 					return (TRUE);
1181 				}
1182 			}
1183 			freeRuleValue(rv_result, nr);
1184 			rv_result = 0;
1185 		}   /* end of for over objectDN */
1186 
1187 		if (rv_request != 0) {
1188 			freeRuleValue(rv_request, 1);
1189 			rv_request = 0;
1190 		}
1191 		if (rv_result != 0) {
1192 			freeRuleValue(rv_result, nr);
1193 			rv_result = 0;
1194 		}
1195 	}
1196 	sfree(skey);
1197 	*statP = MAP_NO_MATCHING_KEY;
1198 
1199 	/* Free full map name */
1200 	sfree(str);
1201 
1202 	return (FALSE);
1203 }
1204 
1205 
1206 /*
1207  * Maps and writes a single NIS entry to the LDAP DIT
1208  */
1209 int
1210 singleWriteToDIT(char *map, char *domain, datum *key, datum *value,
1211 						bool_t replace) {
1212 	__nis_table_mapping_t	*t;
1213 	__nis_rule_value_t	*rv, *frv;
1214 	__nis_ldap_search_t	*ls;
1215 	int			statP = SUCCESS, flag;
1216 	int			nv, nr, i, rc, collapse;
1217 	char			*dn = 0, *skey, *svalue, *str;
1218 	char			*myself = "singleWriteToDIT";
1219 
1220 	if (!map || !domain || !key || !value) {
1221 		return (MAP_PARAM_ERROR);
1222 	}
1223 
1224 	/* Return SUCCESS for empty or whitespace key */
1225 	for (i = 0; i < key->dsize && (key->dptr[i] == 0 ||
1226 		key->dptr[i] == ' ' || key->dptr[i] == '\t'); i++);
1227 	if (i >= key->dsize)
1228 		return (SUCCESS);
1229 
1230 	/* Get the mapping information for the map */
1231 	if ((t = mappingFromMap(map, domain, &statP)) == 0) {
1232 		/*
1233 		 * No problem. We don't handle this map and domain. Maybe it's
1234 		 * handled by a service other than NIS.
1235 		 */
1236 		return (statP);
1237 	}
1238 
1239 	/* NULL-terminated version of key and value for logging */
1240 	if ((skey = am(myself, key->dsize + 1)) == 0)
1241 		return (MAP_NO_MEMORY);
1242 	(void) memcpy(skey, key->dptr, key->dsize);
1243 
1244 	if ((svalue = am(myself, value->dsize + 1)) == 0) {
1245 		sfree(skey);
1246 		return (MAP_NO_MEMORY);
1247 	}
1248 	(void) memcpy(svalue, value->dptr, value->dsize);
1249 
1250 	if ((str = getFullMapName(map, domain)) == 0) {
1251 		sfree(skey);
1252 		sfree(svalue);
1253 		return (MAP_NO_MEMORY);
1254 	}
1255 
1256 	/* For each alternate mapping */
1257 	for (flag = 0; t != 0; t = t->next) {
1258 		/* Verify objName */
1259 		if (strcmp(str, t->objName) != 0) {
1260 			continue;
1261 		}
1262 
1263 		/* Verify if key matches the index */
1264 		if (verifyIndexMatch(t, 0, 0, N2LKEY, skey) == 0 ||
1265 			verifyIndexMatch(t, 0, 0, N2LIPKEY, skey) == 0)
1266 			continue;
1267 
1268 		/* Check the writespecs */
1269 		if (t->objectDN->write.base == 0) {
1270 			logmsg(MSG_NOTIMECHECK, LOG_INFO,
1271 				"%s: No baseDN in writespec. Write disabled "
1272 				"for %s (%s)", myself, t->dbId, map);
1273 			continue;
1274 		}
1275 
1276 		/* Check if rulesToLDAP are provided */
1277 		if (t->numRulesToLDAP == 0) {
1278 			logmsg(MSG_NOTIMECHECK, LOG_INFO,
1279 				"%s: No rulesToLDAP. Write disabled for "
1280 				"%s (%s)", myself, t->dbId, map);
1281 			continue;
1282 		}
1283 
1284 		/* Set flag to indicate write is enabled */
1285 		flag = 1;
1286 
1287 		/* Convert key  and value into an array of rule-values */
1288 		if ((rv = datumToRuleValue(key, value, t, &nv, domain, FALSE,
1289 								&statP)) == 0) {
1290 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
1291 				"%s: Conversion error %d (NIS to name=value "
1292 				"pairs) for NIS data (key=%s, value=%s) "
1293 				"for %s (%s)",
1294 				myself, statP, skey, svalue, t->dbId, map);
1295 			sfree(skey);
1296 			sfree(svalue);
1297 
1298 			/* Free full map name */
1299 			sfree(str);
1300 
1301 			return (statP);
1302 		}
1303 
1304 		/* Convert NIS data to LDAP equivalents for each rule-value */
1305 		for (i = 0; i < nv; i++) {
1306 			/* Verify indexlist with name=value pairs */
1307 			if (verifyIndexMatch(t, 0, &rv[i], 0, 0) == 0)
1308 				break;
1309 
1310 			/* Create LDAP request and LDAP name=value pairs */
1311 			if ((ls = createLdapRequest(t, &rv[i],
1312 			    0, 0, NULL, NULL)) == 0) {
1313 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
1314 					"%s: Conversion error (name=value pairs"
1315 					" to LDAP) for NIS data "
1316 					"(key=%s, value=%s) for %s (%s)",
1317 					myself, skey, svalue, t->dbId, map);
1318 				freeRuleValue(rv, nv);
1319 				sfree(skey);
1320 				sfree(svalue);
1321 
1322 				/* Free full map name */
1323 				sfree(str);
1324 
1325 				return (MAP_CREATE_LDAP_REQUEST_ERROR);
1326 			}
1327 			freeLdapSearch(ls);
1328 			/* printRuleValue(&rv[i]); */
1329 		}
1330 
1331 		/* If i < nv then this alternate mapping isn't the one */
1332 		if (i < nv)
1333 			continue;
1334 
1335 		/*
1336 		 * Merge rule-values with the same DN so that we have
1337 		 * one ldap write request for each DN
1338 		 */
1339 		nr = nv;
1340 		frv = mergeRuleValueWithSameDN(rv, &nr);
1341 		freeRuleValue(rv, nv);
1342 		if (frv == 0) {
1343 			if (nr == -1) {
1344 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
1345 					"%s: Unable to merge LDAP write "
1346 					"requests to same DN for NIS data "
1347 					"(key=%s, value=%s) for %s (%s)",
1348 					myself, skey, svalue, t->dbId, map);
1349 				statP = MAP_INTERNAL_ERROR;
1350 			} else if (nr == 0) {
1351 				logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1352 					"%s: Cannot generate write DN due to "
1353 					"missing information for NIS data "
1354 					"(key=%s, value=%s) for %s (%s)",
1355 					myself, skey, svalue, t->dbId, map);
1356 				statP = MAP_NO_DN;
1357 			}
1358 			sfree(skey);
1359 			sfree(svalue);
1360 
1361 			/* Free full map name */
1362 			sfree(str);
1363 
1364 			return (statP);
1365 		}
1366 
1367 		/* Write to the LDAP server */
1368 		for (collapse = 0, i = 0; i < nr; i++) {
1369 			if ((dn = findVal("dn", &frv[i], mit_ldap)) != 0) {
1370 				if (replace == FALSE) {
1371 					/* ldap add */
1372 					rc = ldapAdd(dn, &frv[i],
1373 						t->objectDN->write.attrs, 0);
1374 				} else {
1375 					/* ldap modify with addFirst set */
1376 					rc = ldapModify(dn, &frv[i],
1377 						t->objectDN->write.attrs, 1);
1378 				}
1379 
1380 				/* if we get err=20, collapse and try again */
1381 				if (!collapse &&
1382 					(rc == LDAP_TYPE_OR_VALUE_EXISTS) &&
1383 					(collapseRuleValue(&frv[i]) == 1)) {
1384 					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1385 						"%s: Ignoring values differing "
1386 						"in case from NIS data (key=%s,"
1387 						" value=%s) for (dn: %s) for "
1388 						"%s (%s)", myself, skey,
1389 						svalue, dn, t->dbId, map);
1390 					collapse = 1;
1391 					i--;
1392 					continue;
1393 				}
1394 
1395 				collapse = 0;
1396 				if (rc != LDAP_SUCCESS) {
1397 					/* Log error */
1398 					logmsg(MSG_NOTIMECHECK, LOG_ERR,
1399 						"%s: %s error %d (%s) for "
1400 						"(dn: %s) for NIS data "
1401 						"(key=%s, value=%s) "
1402 						"for %s (%s)",
1403 						myself, (replace == TRUE) ?
1404 						"ldapModify" : "ldapAdd", rc,
1405 						ldap_err2string(rc), dn, skey,
1406 						svalue, t->dbId, map);
1407 
1408 					/* Dumping failed call may be useful */
1409 					/* printRuleValue(&frv[i]); */
1410 
1411 					/*
1412 					 * Return the error code and let wrapper
1413 					 * sort out if mapping should continue
1414 					 * or abort.
1415 					 */
1416 					statP = rc;
1417 					sfree(skey);
1418 					sfree(svalue);
1419 					freeRuleValue(frv, nr);
1420 
1421 					/* Free full map name */
1422 					sfree(str);
1423 
1424 					return (statP);
1425 				}
1426 			}
1427 		}
1428 
1429 		freeRuleValue(frv, nr);
1430 	}
1431 
1432 	sfree(skey);
1433 	sfree(svalue);
1434 
1435 	/* Free full map name */
1436 	sfree(str);
1437 
1438 	return ((flag)?SUCCESS:MAP_WRITE_DISABLED);
1439 }
1440 
1441 suc_code
1442 collapseRuleValue(__nis_rule_value_t *rv) {
1443 	int		i, j, k, flag;
1444 
1445 	/* Using 'val' to appease cstyle's 80 chars/line limit */
1446 	__nis_value_t	*val;
1447 
1448 	for (i = 0, flag = 0; i < rv->numAttrs; i++) {
1449 		val = &rv->attrVal[i];
1450 		for (j = 1; j < val->numVals; j++) {
1451 			for (k = 0; k < j; k++) {
1452 				if (val->val[j].length != val->val[k].length)
1453 					continue;
1454 				if (val->val[k].length == 0)
1455 					continue;
1456 				if (strncasecmp(val->val[j].value,
1457 						val->val[k].value,
1458 						val->val[j].length) != 0)
1459 					continue;
1460 				flag = 1;
1461 				sfree(val->val[j].value);
1462 
1463 #ifdef	ORDER_NOT_IMPORTANT
1464 				val->val[j--] = val->val[--val->numVals];
1465 #else
1466 				/* Order needs to be maintained */
1467 				for (k = j + 1; k < val->numVals; k++)
1468 					val->val[k - 1] = val->val[k];
1469 				j--;
1470 				val->numVals--;
1471 #endif
1472 				break;
1473 			}
1474 		}
1475 	}
1476 	return (flag);
1477 }
1478 
1479 /* ObjectClass lookup table */
1480 static struct {
1481 	const char *attrType;
1482 	const char *objectClass;
1483 } oc_lookup[] = {
1484 	{ "o",				"objectclass=organization"},
1485 	{ "organizationname",		"objectclass=organization"},
1486 	{ "2.5.4.10",			"objectclass=organization"},
1487 	{ "ou",				"objectclass=organizationalunit"},
1488 	{ "organizationalunitname",	"objectclass=organizationalunit"},
1489 	{ "2.5.4.11",			"objectclass=organizationalunit"},
1490 	{ "c",				"objectclass=country"},
1491 	{ "countryname",		"objectclass=country"},
1492 	{ "2.5.4.6",			"objectclass=country"},
1493 	{ "dc",				"objectclass=domain"},
1494 	{ "domaincomponent",		"objectclass=domain"},
1495 	{ "0.9.2342.19200300.100.1.25",	"objectclass=domain"},
1496 	{ "nismapname",			"objectclass=nismap"},
1497 	{ "1.3.6.1.1.1.1.26",		"objectclass=nismap"},
1498 	{ "automountmapname",		"objectclass=automountmap"},
1499 	{ "1.3.6.1.1.1.1.31",		"objectclass=automountmap"},
1500 	{ 0,				0}
1501 };
1502 
1503 /*
1504  * Returns the name of the objectclass to which the object
1505  * represented by the given 'rdn' will most likely belong to.
1506  * The return value is in static memory so it should not be
1507  * freed
1508  */
1509 const char *
1510 getObjectClass(char *rdn) {
1511 
1512 	char *attrtype, *p;
1513 	int len, i;
1514 
1515 	/* Skip leading whitespaces */
1516 	for (p = rdn; *p == ' ' || *p == '\t'; p++);
1517 	if (*p == '\0')
1518 		return (0);
1519 	attrtype = p;
1520 
1521 	/* Find '=' */
1522 	if ((p = strchr(attrtype, '=')) == 0 || p == attrtype ||
1523 						*(p - 1) == '\\')
1524 		return (0);
1525 
1526 	/*
1527 	 * Skip trailing whitespaces in attrtype
1528 	 * Don't worry, p won't decrease beyond attrtype
1529 	 */
1530 	for (--p; *p == ' ' || *p == '\t'; p--);
1531 	len = p - attrtype + 1;
1532 
1533 	for (i = 0; oc_lookup[i].attrType; i++)
1534 		if (!strncasecmp(oc_lookup[i].attrType, attrtype, len))
1535 			/* Check length is right */
1536 			if (len == strlen(oc_lookup[i].attrType))
1537 				return (oc_lookup[i].objectClass);
1538 
1539 	return (0);
1540 }
1541 
1542 /*
1543  * Split 'dn' into rdn and parentdn based on the first
1544  * occurrence of unescaped 'comma' or 'semicolon'. rdn
1545  * lies on the LHS while parentdn lies on the RHS of the
1546  * split. If none found, then an empty string ("") is
1547  * assigned to parentdn
1548  */
1549 int
1550 splitDN(char *dn, char **rdn, char **parentdn) {
1551 	char	*value, *name;
1552 	char	*myself = "splitDN";
1553 
1554 	if ((name = sdup(myself, T, dn)) == 0)
1555 		return (-1);
1556 
1557 	for (value = name; *value != '\0'; value++) {
1558 		if (*value == ',' || *value == ';')
1559 			if (value == name || *(value - 1) != '\\')
1560 				break;
1561 	}
1562 
1563 	if (*value != '\0') {
1564 		*value = '\0';
1565 		value++;
1566 	} else
1567 		value = 0;
1568 
1569 	if (parentdn) {
1570 		if ((*parentdn = sdup(myself, T, value)) == 0) {
1571 			sfree(name);
1572 			return (-1);
1573 		}
1574 	}
1575 	if (rdn)
1576 		*rdn = name;
1577 	else
1578 		sfree(name);
1579 
1580 	return (1);
1581 }
1582 
1583 /*
1584  * FUNCTION :	makeNISObject()
1585  *
1586  * DESCRIPTION: Sets up a nis Object in the DIT.
1587  *
1588  * GIVEN :
1589  *		Case 1: Both 'domain' and 'dn' are non-NULL
1590  *			Create nisDomainObject with the given information
1591  *		Case 2: Only 'domain' is  non-NULL
1592  *			Obtain the 'dn' from the nisLDAPdomainContext list
1593  *			Create nisDomainObject with the above information
1594  *		Case 3: Only 'dn' is  non-NULL
1595  *			Create an object with the 'dn'
1596  *			Here we guess the objectclass attribute, based on
1597  *			oc_lookup table
1598  *		Case 4: Both 'domain' and 'dn' are NULL
1599  *			Error
1600  *
1601  * RETURNS :	SUCCESS = It worked
1602  *		FAILURE = There was a problem.
1603  */
1604 suc_code
1605 makeNISObject(char *domain, char *dn) {
1606 	__nis_rule_value_t	*rv;
1607 	__nis_ldap_search_t	*ls;
1608 	int			i, rc, nr, add_rc;
1609 	char			*val;
1610 	char			*myself = "makeNISObject";
1611 
1612 	if (!dn && !domain)
1613 		return (FAILURE);
1614 
1615 	/*
1616 	 * If only 'domain' name is provided, then
1617 	 * try to find dn from the nisLDAPdomainContext
1618 	 * list generated by the parser
1619 	 */
1620 	if (!dn) {
1621 		for (i = 0; i < ypDomains.numDomains; i++) {
1622 			if (ypDomains.domainLabels[i] == 0)
1623 				continue;
1624 			if (strcasecmp(domain, ypDomains.domainLabels[i])
1625 								== 0) {
1626 				dn = ypDomains.domains[i];
1627 				break;
1628 			}
1629 		}
1630 		if (!dn)
1631 			return (FAILURE);
1632 	}
1633 
1634 	/*
1635 	 * If only 'dn' is given, then it means that the
1636 	 * caller simply wants to a create an entry for
1637 	 * that 'dn'.
1638 	 *
1639 	 * If 'domain' is given, then check if the 'dn'
1640 	 * has already been set up as a nis domain object.
1641 	 * If not, see if we can make it become one.
1642 	 */
1643 	if (domain) {
1644 		/*
1645 		 * Check to see if the nis domain object has
1646 		 * already been set up
1647 		 */
1648 		ls = buildLdapSearch(dn, LDAP_SCOPE_BASE, 0, 0,
1649 			"objectclass=*", 0, 0, 0);
1650 		if (ls == 0) {
1651 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
1652 				"%s: Unable to create ldapSearch "
1653 				"request for dn: %s", myself, dn);
1654 			return (FAILURE);
1655 		}
1656 		nr = -1;
1657 		rv = ldapSearch(ls, &nr, 0, &rc);
1658 		freeLdapSearch(ls);
1659 		if (rc == LDAP_SUCCESS) {
1660 			val = findVal("nisDomain", rv, mit_ldap);
1661 			if (val != NULL) {
1662 				/*
1663 				 * Yes, nis domain object found. Check
1664 				 * to see if the domain names match.
1665 				 * If so, we are done. If not, log
1666 				 * a warning message, and return SUCCESS.
1667 				 */
1668 				if (strcasecmp(val, domain) == 0) {
1669 					freeRuleValue(rv, nr);
1670 					return (SUCCESS);
1671 				} else {
1672 					logmsg(MSG_NOTIMECHECK,
1673 						LOG_WARNING,
1674 						"%s: Entry (dn: %s) already "
1675 						"contains a nis domain name "
1676 						"(%s). The domain name (%s) "
1677 						"is not added.",
1678 						myself, dn, val, domain);
1679 					freeRuleValue(rv, nr);
1680 					return (SUCCESS);
1681 				}
1682 			} else {
1683 				freeRuleValue(rv, nr);
1684 				/*
1685 				 * Entry for the 'dn' exists, but it
1686 				 * is not a nis domain object yet.
1687 				 * Add the nisDoamin attribute and
1688 				 * the nisDomainObject objectclass to
1689 				 * the entry.
1690 				 */
1691 				if ((rv = initRuleValue(1, 0)) == 0)
1692 					return (FAILURE);
1693 
1694 				if (addSAttr2RuleValue("nisDomain",
1695 						domain, rv) == -1) {
1696 					freeRuleValue(rv, 1);
1697 					return (FAILURE);
1698 				}
1699 				rc = ldapModify(dn, rv,
1700 					"objectclass=nisDomainObject",
1701 					0);
1702 				freeRuleValue(rv, 1);
1703 				if (rc == LDAP_SUCCESS) {
1704 					logmsg(MSG_NOTIMECHECK,
1705 						LOG_INFO,
1706 						"%s: entry (dn: %s) "
1707 						"modified to be an "
1708 						"nis domain object",
1709 						myself, dn);
1710 					return (SUCCESS);
1711 				} else {
1712 					logmsg(MSG_NOTIMECHECK,
1713 						LOG_ERR,
1714 						"%s: unable to modify "
1715 						"entry (dn: %s) to be "
1716 						"a nis domain object: "
1717 						"ldapModify error %d (%s)",
1718 						myself, dn, rc,
1719 						ldap_err2string(rc));
1720 					return (FAILURE);
1721 				}
1722 			}
1723 		} else { /* search for 'dn' failed */
1724 			freeRuleValue(rv, nr);
1725 
1726 			/*
1727 			 * It is OK if no such object, otherwise
1728 			 * log an error.
1729 			 */
1730 			if (rc != LDAP_NO_SUCH_OBJECT) {
1731 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
1732 					"%s: unable to retrieve "
1733 					"entry (dn: %s): "
1734 					"ldapSearch error %d (%s)",
1735 					myself, dn, rc,
1736 					ldap_err2string(rc));
1737 				return (FAILURE);
1738 			}
1739 		}
1740 
1741 		/*
1742 		 * If the 'dn' is actually the naming context of
1743 		 * the DIT, we should be able to make it a nis domain
1744 		 * object without worrying about missing parent
1745 		 * entries. If unable to add the entry for the 'dn'
1746 		 * due to missing parent entries, fall through
1747 		 * to create them and then add the nis domain object.
1748 		 */
1749 		if (addNISObject(domain, dn, &add_rc) == SUCCESS)
1750 			return (SUCCESS);
1751 		else if (add_rc != LDAP_NO_SUCH_OBJECT)
1752 			return (FAILURE);
1753 	}
1754 
1755 	/* Create parent */
1756 	if (addParent(dn, NULL) == FAILURE)
1757 		return (FAILURE);
1758 
1759 	if (addNISObject(domain, dn, NULL) == FAILURE)
1760 		return (FAILURE);
1761 
1762 	return (SUCCESS);
1763 }
1764 
1765 suc_code
1766 addParent(char *dn, char **attr) {
1767 	__nis_rule_value_t	*rv;
1768 	__nis_ldap_search_t	*ls;
1769 	int			rc, nr;
1770 	char			*parentdn = 0, *rdn = 0;
1771 	char			*myself = "addParent";
1772 
1773 	/* Obtain parentdn */
1774 	if (splitDN(dn, &rdn, &parentdn) == -1)
1775 		return (FAILURE);
1776 	if (!parentdn) {
1777 		sfree(rdn);
1778 		return (FAILURE);
1779 	}
1780 
1781 	/* Check if parentdn exists */
1782 	ls = buildLdapSearch(parentdn, LDAP_SCOPE_BASE, 0, 0,
1783 					"objectclass=*", 0, 0, 0);
1784 	if (ls == 0) {
1785 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
1786 			"%s: Unable to create ldapSearch request for "
1787 			"parent (dn: %s) of (dn: %s)",
1788 			myself, parentdn, dn);
1789 		sfree(parentdn);
1790 		sfree(rdn);
1791 		return (FAILURE);
1792 	}
1793 	nr = -1;
1794 	rv = ldapSearch(ls, &nr, 0, &rc);
1795 	freeLdapSearch(ls);
1796 	freeRuleValue(rv, nr);
1797 
1798 	/* Create parent if it doesn't exists */
1799 	if (rc == LDAP_NO_SUCH_OBJECT) {
1800 		if (makeNISObject(0, parentdn) == FAILURE) {
1801 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
1802 				"%s: Unable to create parent (dn: %s) of "
1803 				"(dn: %s) in the DIT", myself, parentdn, dn);
1804 			sfree(parentdn);
1805 			sfree(rdn);
1806 			return (FAILURE);
1807 		}
1808 	}
1809 	sfree(parentdn);
1810 
1811 	if (attr && rdn)
1812 		*attr = (char *)getObjectClass(rdn);
1813 	sfree(rdn);
1814 
1815 	return (SUCCESS);
1816 }
1817 
1818 
1819 
1820 /*
1821  * FUNCTION :	is_fatal_error()
1822  *
1823  * DESCRIPTION:	Works out if a failed mapping operation should be retried.
1824  *
1825  * INPUTS :	Result code from operation
1826  *
1827  * OUTPUTS :	TRUE = Fatal error, don't retry.
1828  *		FALSE = Temporary error, retry.
1829  */
1830 bool_t
1831 is_fatal_error(int res)
1832 {
1833 
1834 	if (0 > res)
1835 		/* An internal mapping error. Not going to go away. */
1836 		return (TRUE);
1837 
1838 	switch (res) {
1839 		case (LDAP_PROTOCOL_ERROR):
1840 		case (LDAP_TIMELIMIT_EXCEEDED):
1841 		case (LDAP_PARTIAL_RESULTS):
1842 		case (LDAP_BUSY):
1843 		case (LDAP_UNAVAILABLE):
1844 		case (LDAP_UNWILLING_TO_PERFORM):
1845 		case (LDAP_OTHER):
1846 		case (LDAP_SERVER_DOWN):
1847 		case (LDAP_LOCAL_ERROR):
1848 		case (LDAP_TIMEOUT):
1849 		case (LDAP_NO_MEMORY):
1850 			/* Probably worth a retry */
1851 			return (FALSE);
1852 
1853 		default:
1854 			return (TRUE);
1855 	}
1856 }
1857 
1858 /*
1859  * FUNCTION :	addNISObject()
1860  *
1861  * DESCRIPTION: Add a nis Object in the DIT.
1862  *
1863  * GIVEN :
1864  *		Case 1: 'dn' is NULL
1865  *			Error
1866  *		Case 2: 'domain' is non-NULL
1867  *			Create nisDomainObject with the given information
1868  *		Case 3: 'domain' is NULL
1869  *			Create an object with the 'dn'
1870  *			Here we guess the objectclass attribute, based on
1871  *			oc_lookup table
1872  *
1873  * RETURNS :	SUCCESS = It worked
1874  *		FAILURE = There was a problem. If the ldap add
1875  *                        operation failed, ldap_rc will be set
1876  *			  to the ldap error code.
1877  */
1878 suc_code
1879 addNISObject(char *domain, char *dn, int *ldap_rc) {
1880 	__nis_rule_value_t	*rv;
1881 	int			rc;
1882 	char			*objClassAttrs = NULL, *attrs;
1883 	char			*value, *svalue, *rdn = NULL;
1884 	char			*myself = "addNISObject";
1885 
1886 	if (!dn)
1887 		return (FAILURE);
1888 
1889 	if ((rv = initRuleValue(1, 0)) == 0)
1890 		return (FAILURE);
1891 
1892 	if (ldap_rc)
1893 		*ldap_rc = -1;
1894 
1895 	/*
1896 	 * Add name=value pairs from RDN. Although this is not required
1897 	 * for SunOne Directory Server, during openldap interoperabilty
1898 	 * tests, it was found out that openldap server returned object
1899 	 * class violation errors if MUST attributes were not specified
1900 	 * explicitly.
1901 	 */
1902 	if (splitDN(dn, &rdn, 0) == -1)
1903 		return (FAILURE);
1904 	if (rdn != NULL) {
1905 		objClassAttrs = (char *)getObjectClass(rdn);
1906 		if (objClassAttrs == NULL) {
1907 			sfree(rdn);
1908 			return (FAILURE);
1909 		}
1910 
1911 		/*
1912 		 * RDN can be composed of multiple name=value pairs
1913 		 * concatenated by '+'. Hence, we need to determine each
1914 		 * pair and add it to 'rv'
1915 		 */
1916 		for (value = rdn, svalue = NULL; *value != '\0'; value++) {
1917 			if (*value == '+') {
1918 				/* Make sure it's not escaped */
1919 				if (value == rdn || *(value - 1) != '\\') {
1920 					/*
1921 					 * We are at the start of the new
1922 					 * pair. 'svalue' now contains the
1923 					 * value for the previous pair. Add
1924 					 * the previous pair to 'rv'
1925 					 */
1926 					*value = '\0';
1927 					if (svalue &&
1928 					addSAttr2RuleValue(rdn, svalue, rv)
1929 									== -1) {
1930 						sfree(rdn);
1931 						freeRuleValue(rv, 1);
1932 						return (FAILURE);
1933 					}
1934 					svalue = NULL;
1935 					rdn = value + 1;
1936 					continue;
1937 				}
1938 			}
1939 
1940 			if (*value == '=') {
1941 				if (value == rdn || *(value - 1) != '\\') {
1942 					/*
1943 					 * 'rdn' now contains the name.
1944 					 * Whatever follows till the next
1945 					 * unescaped '+' or '\0' is the
1946 					 * value for this pair.
1947 					 */
1948 					*value = '\0';
1949 					svalue = value + 1;
1950 					continue;
1951 				}
1952 			}
1953 		}
1954 
1955 		/*
1956 		 * End of String. Add the previous name=value pair to 'rv'
1957 		 */
1958 		if (svalue && addSAttr2RuleValue(rdn, svalue, rv) == -1) {
1959 			sfree(rdn);
1960 			freeRuleValue(rv, 1);
1961 			return (FAILURE);
1962 		}
1963 		sfree(rdn);
1964 	} else /* rdn  == NULL */
1965 		return (FAILURE);
1966 
1967 	/* Create the entry */
1968 	if (domain) {
1969 		if (addSAttr2RuleValue("nisDomain", domain, rv) == -1) {
1970 			freeRuleValue(rv, 1);
1971 			return (FAILURE);
1972 		}
1973 		attrs = scat(myself, F, "objectclass=nisdomainobject,",
1974 					objClassAttrs);
1975 		if (!attrs) {
1976 			freeRuleValue(rv, 1);
1977 			return (FAILURE);
1978 		}
1979 		rc = ldapAdd(dn, rv, attrs, 0);
1980 		sfree(attrs);
1981 	} else {
1982 		rc = ldapAdd(dn, rv, objClassAttrs, 0);
1983 	}
1984 
1985 	if (rc == LDAP_SUCCESS)
1986 		logmsg(MSG_NOTIMECHECK, LOG_INFO,
1987 				"%s: Entry (dn: %s) added to DIT",
1988 				myself, dn);
1989 	else if (rc == LDAP_ALREADY_EXISTS)
1990 		/* Treat this as success */
1991 		rc = LDAP_SUCCESS;
1992 	else
1993 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
1994 				"%s: ldapAdd error %d (%s) for (dn: %s)",
1995 				myself, rc, ldap_err2string(rc), dn);
1996 
1997 	freeRuleValue(rv, 1);
1998 	if (ldap_rc)
1999 		*ldap_rc = rc;
2000 	return ((rc == LDAP_SUCCESS)?SUCCESS:FAILURE);
2001 }
2002