xref: /illumos-gate/usr/src/lib/libnisdb/ldap_parse.c (revision 3db86aab)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2001-2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/param.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <strings.h>
35 #include <ctype.h>
36 #include <malloc.h>
37 #include <stdlib.h>
38 #include <deflt.h>
39 #include <limits.h>
40 
41 #include "ldap_parse.h"
42 #include "ldap_glob.h"
43 #include "ldap_attr.h"
44 #include "ldap_util.h"
45 #include "ldap_map.h"
46 #include "ldap_ruleval.h"
47 #include "nis_parse_ldap_conf.h"
48 
49 int yp2ldap = 0;
50 /*
51  * List of mapping structures in original (i.e., as in config file) order.
52  * Lined on the 'seqNext' field.
53  */
54 __nis_table_mapping_t	*ldapMappingSeq = 0;
55 
56 /*
57  * Call the parser for the config file 'ldapConfFile', and command line
58  * attribute settings per 'ldapCLA'.
59  *
60  * Returns
61  *	0	Success
62  *	-1	Config file stat/open or parse error
63  *	1	No mapping should be used.
64  */
65 int
66 parseConfig(char **ldapCLA, char *ldapConfFile) {
67 	int		ret;
68 
69 	/*
70 	 * Establish defaults for ldapDBTableMapping, so that we have
71 	 * valid values even if there's no mapping config to parse.
72 	 */
73 	ldapDBTableMapping.initTtlLo = (3600-1800);
74 	ldapDBTableMapping.initTtlHi = (3600+1800);
75 	ldapDBTableMapping.ttl = 3600;
76 	ldapDBTableMapping.enumExpire = 0;
77 	ldapDBTableMapping.fromLDAP = FALSE;
78 	ldapDBTableMapping.toLDAP = FALSE;
79 	ldapDBTableMapping.expire = 0;
80 
81 	ret = parse_ldap_migration((const char **)ldapCLA, ldapConfFile);
82 
83 	return (ret);
84 }
85 
86 /*
87  * Convert the linked list of __nis_table_mapping_t's (produced by the
88  * attribute parser) to the 'ldapMappingList', keyed on the objPath.
89  *
90  * Once this function has returned, the 'tlist' is invalid, and must
91  * not be used in any way.
92  */
93 int
94 linked2hash(__nis_table_mapping_t *tlist) {
95 	__nis_hash_table_mt	dbids;
96 	__nis_table_mapping_t	*t, *told, *x, **seqNext;
97 	__nis_object_dn_t	*o, *to;
98 	char			*myself = "linked2hash";
99 #ifdef	NISDB_LDAP_DEBUG
100 	char			*selectDbid = getenv("NISLDAPSELECTDBID");
101 	char			**sdi, *s;
102 	int			i, nsdi;
103 #endif	/* NISDB_LDAP_DEBUG */
104 
105 
106 	if (tlist == 0)
107 		return (0);
108 
109 	/* proxyInfo.default_nis_domain must end in a dot */
110 	{
111 		int	len = slen(proxyInfo.default_nis_domain);
112 
113 		if (len > 0 && proxyInfo.default_nis_domain[len-1] != '.') {
114 			char	*domain = am(myself, len+2);
115 
116 			(void) memcpy(domain, proxyInfo.default_nis_domain,
117 					len);
118 			domain[len] = '.';
119 			domain[len+1] = '\0';
120 			sfree(proxyInfo.default_nis_domain);
121 			proxyInfo.default_nis_domain = domain;
122 		}
123 	}
124 
125 #ifdef	NISDB_LDAP_DEBUG
126 	for (nsdi = 0, s = selectDbid; s != 0 && *s != '\0'; s++) {
127 		if (*s != ' ') {
128 			nsdi++;
129 			while (*s != ' ' && *s != '\0')
130 				s++;
131 			if (*s == '\0')
132 				break;
133 		}
134 	}
135 	if (nsdi > 0) {
136 		sdi = am(myself, nsdi * sizeof (sdi[0]));
137 		if (sdi == 0)
138 			logmsg(MSG_NOTIMECHECK, LOG_WARNING,
139 				"%s: Memory alloc failure for dbId selection",
140 				myself);
141 		else {
142 			for (i = 0, s = selectDbid; *s != '\0'; s++) {
143 				if (*s != ' ') {
144 					sdi[i++] = selectDbid;
145 					while (*s != ' ' && *s != '\0')
146 						s++;
147 					if (*s != '\0') {
148 						*s = '\0';
149 						s++;
150 					} else
151 						break;
152 					selectDbid = s;
153 				}
154 			}
155 		}
156 	}
157 #endif	/* NISDB_LDAP_DEBUG */
158 
159 	__nis_init_hash_table(&dbids, 0);
160 
161 	seqNext = &ldapMappingSeq;
162 	for (t = tlist; t != 0; t = told) {
163 		int	len;
164 
165 #ifdef	NISDB_LDAP_DEBUG
166 		/*
167 		 * If the dbId doesn't match 'selectDbid', skip this
168 		 * mapping. Re-insert on 'tlist', in order to keep memory
169 		 * leak checking happy. Note that 'tlist' may end up pointing
170 		 * into the real mapping list, so it shouldn't be used once
171 		 * this routine has been called.
172 		 */
173 		if (nsdi > 0) {
174 			for (i = 0; i < nsdi; i++) {
175 				if (strcmp(sdi[i], t->dbId) == 0)
176 					break;
177 			}
178 			if (i >= nsdi) {
179 				told = t->next;
180 				if (tlist != t)
181 					t->next = tlist;
182 				else
183 					t->next = 0;
184 				tlist = t;
185 				continue;
186 			}
187 		}
188 #endif	/* NISDB_LDAP_DEBUG */
189 
190 		told = t->next;
191 		t->next = 0;
192 
193 		/* Make sure t->item.name is set correctly */
194 		if (t->item.name == 0)
195 			t->item.name = t->dbId;
196 
197 		/* Remove leading dot in object name, if any */
198 		len = slen(t->objName);
199 		while (len > 0 && t->objName[0] == '.') {
200 			(void) memmove(t->objName, &t->objName[1], len);
201 			len -= 1;
202 		}
203 
204 		/*
205 		 * Initialize the object path, which is what we'll
206 		 * rehash on.
207 		 */
208 		if (yp2ldap) {
209 			t->objPath = internal_table_name(t->objName,
210 				t->objPath);
211 			if (!t->objPath) {
212 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
213 			"%s: Failed to obtain internal table name for \"%s\"",
214 					myself, t->objName);
215 				return (-1);
216 			}
217 		} else {
218 			t->objPath = am(myself, len + MAXPATHLEN + 1);
219 			if (t->objPath == 0)
220 				return (-1);
221 			if (internal_table_name(t->objName,
222 				t->objPath) == 0) {
223 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
224 			"%s: Failed to obtain internal table name for \"%s\"",
225 					myself, t->objName);
226 				return (-1);
227 			}
228 		}
229 
230 		/*
231 		 * Initialize the column name array.
232 		 */
233 		if (!yp2ldap) {
234 			if (setColumnsDuringConfig && setColumnNames(t)) {
235 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
236 				"%s: Unable to find column names for \"%s\"",
237 					myself, NIL(t->objName));
238 				return (-1);
239 			}
240 		}
241 
242 		/*
243 		 * If there are multiple mapping target containers, make
244 		 * each one into it's own mapping structure. They can all
245 		 * be minimal copies (i.e., share pointers to sub-structures
246 		 * other than the objectDN).
247 		 *
248 		 * If objectDN is NULL, we will never use this structure.
249 		 * In order to allow the rest of the mapping code to assume
250 		 * objectDN != NULL, skip the mapping (even if x == t).
251 		 */
252 		for (o = to = t->objectDN; o != 0; o = o->next) {
253 			__nis_table_mapping_t	*p;
254 
255 			if (o == to) {
256 				x = t;
257 				/*
258 				 * Only insert the first mapping for an
259 				 * object on the sequential list.
260 				 */
261 				*seqNext = t;
262 				t->seqNext = 0;
263 				seqNext = (__nis_table_mapping_t **)&t->seqNext;
264 			} else {
265 				x = am(myself, sizeof (*x));
266 				if (x == 0) {
267 					/*
268 					 * This happens during rpc.nisd
269 					 * initialization, and it's an
270 					 * unrecoverable disaster, so don't
271 					 * bother cleaning up.
272 					 */
273 					return (-1);
274 				}
275 				memcpy(x, t, sizeof (*x));
276 				x->objectDN = o;
277 				x->next = 0;
278 			}
279 
280 			/*
281 			 * If x->objectDN->write.base is NULL, clone it from
282 			 * x->objectDN->read.base.
283 			 */
284 			if (x->objectDN->write.scope != LDAP_SCOPE_UNKNOWN) {
285 				if (x->objectDN->write.base == 0 &&
286 						x->objectDN->read.base != 0) {
287 					x->objectDN->write.base =
288 						sdup(myself, T,
289 						x->objectDN->read.base);
290 					if (x->objectDN->write.base == 0)
291 						return (-1);
292 				}
293 				if (x->objectDN->write.attrs == 0 &&
294 						x->objectDN->read.attrs != 0) {
295 					x->objectDN->write.attrs =
296 						sdup(myself, T,
297 						x->objectDN->read.attrs);
298 					if (x->objectDN->write.attrs == 0)
299 						return (-1);
300 				}
301 			}
302 
303 			if (o != to) {
304 				/* Insert last on the 't->next' list */
305 				for (p = t; p->next != 0; p = p->next);
306 				p->next = x;
307 			}
308 		}
309 
310 		/* Insert on dbid hash list */
311 		if (t->objectDN != 0 && !__nis_insert_item_mt(t, &dbids, 0)) {
312 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
313 			"%s: Error inserting mapping for \"%s\" on hash list",
314 				myself, NIL(t->objName));
315 #ifdef	NISDB_LDAP_DEBUG
316 			abort();
317 #endif	/* NISDB_LDAP_DEBUG */
318 			return (-1);
319 		}
320 	}
321 
322 	/*
323 	 * dbids2objs() will remove the entries on 'dbids', so no need
324 	 * to clean up that list from this function.
325 	 */
326 	return (dbids2objs(&dbids, &ldapMappingList));
327 }
328 
329 int
330 dbids2objs(__nis_hash_table_mt *dbids, __nis_hash_table_mt *objs) {
331 	__nis_table_mapping_t	*t, *o;
332 	char			*myself = "dbids2objs";
333 
334 
335 	while ((t = __nis_pop_item_mt(dbids)) != 0) {
336 		/* Previous entry for this object ? */
337 		o = __nis_find_item_mt(t->objPath, objs, -1, 0);
338 		if (o != 0) {
339 			__nis_table_mapping_t	*p = o;
340 			/*
341 			 * Mapping already exists, so this is an alternate.
342 			 * Find the end of the list of any previous alt's,
343 			 * and insert there.
344 			 */
345 			while (p->next != 0) {
346 				p = p->next;
347 			}
348 			p->next = t;
349 			if (!__nis_release_item(o, objs, -1)) {
350 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
351 					"%s: __nis_release_item error",
352 					myself);
353 				return (-1);
354 			}
355 		} else {
356 			t->item.name = t->objPath;
357 			if (!__nis_insert_item_mt(t, objs, 0)) {
358 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
359 					"%s: __nis_insert_item error",
360 					myself);
361 				return (-1);
362 			}
363 		}
364 	}
365 
366 	return (0);
367 }
368 
369 /*
370  * internal_table_name()
371  *
372  * Removes the local domain part from a fully qualified name
373  * to create the internal table name for an object. These tables are
374  * stored in /var/nis/<hostname>
375  *
376  * Imported from rpc.nisd/nisdb.c.
377  */
378 char *
379 internal_table_name(nis_name name, char *res)
380 {
381 	char		*s, *t;
382 	int		i, j;
383 
384 	if (yp2ldap) {
385 		if (name == NULL)
386 			return (NULL);
387 		res = s_strndup(name, strlen(name));
388 		if (res == NULL)
389 			return (NULL);
390 		return (res);
391 	}
392 
393 	if (res == NULL)
394 		return (NULL);
395 	/* pointer at the first character of the table name */
396 	s = relative_name(name);
397 
398 	/*
399 	 * If s == NULL then either this is a request for a lookup
400 	 * in our parents namespace (ILLEGAL), or we're the root
401 	 * server and this is a lookup in our namespace.
402 	 */
403 	if (s) {
404 		strcpy(res, nis_data(s));
405 		free((void *)s);
406 	} else if (nis_dir_cmp(name, __nis_rpc_domain())  == SAME_NAME) {
407 		strcpy(res, nis_data("ROOT_DIR"));
408 	} else {
409 		return (NULL);
410 	}
411 
412 	t = strrchr(res, '/');
413 	if (t)
414 		t++; /* Point past the slash */
415 	/* Strip off the quotes if they were used here. */
416 	if (t[0] == '"') {
417 		/* Check for simply a quoted quote. */
418 		if (t[1] != '"') {
419 			j = strlen(t);
420 			/* shift string left by one */
421 			for (i = 0; i < j; i++)
422 				t[i] = t[i+1];
423 			t[j-2] = '\0'; /* Trounce trailing dquote */
424 		}
425 	}
426 	/*
427 	 * OK so now we have the unique name for the table.
428 	 * At this point we can fix it up to match local
429 	 * file system conventions if we so desire. Since it
430 	 * is only used in this form by _this_ server we can
431 	 * mangle it any way we want, as long as we are consistent
432 	 * about it. :-)
433 	 */
434 	__make_legal(res);
435 	return (res);
436 }
437 
438 /*
439  * SYSTEM DEPENDENT
440  *
441  * This function makes the table name "legal" for the underlying file system.
442  *
443  * Imported from rpc.nisd/nisdb.c.
444  */
445 void
446 __make_legal(char *s)
447 {
448 	while (*s) {
449 		if (isupper(*s))
450 			*s = tolower(*s);
451 		s++;
452 	}
453 }
454 
455 /*
456  * relative_name()
457  * This internal function will remove from the NIS name, the domain
458  * name of the current server, this will leave the unique part in
459  * the name this becomes the "internal" version of the name. If this
460  * function returns NULL then the name we were given to resolve is
461  * bad somehow.
462  *
463  * A dynamically-allocated string is returned.
464  *
465  * Imported from rpc.nisd/nis_log_common.c
466  */
467 
468 nis_name
469 relative_name(s)
470 	char	*s;	/* string with the name in it. */
471 {
472 	char			*d;
473 	char			*buf;
474 	int			dl, sl;
475 	name_pos		p;
476 
477 	if (s == NULL)
478 		return (NULL);
479 
480 	d = __nis_rpc_domain();
481 	if (d == NULL)
482 		return (NULL);
483 	dl = strlen(d); 	/* _always dot terminated_   */
484 
485 	buf = strdup(s);
486 	if (buf == NULL)
487 		return (NULL);
488 	strcpy(buf, s);		/* Make a private copy of 's'   */
489 	sl = strlen(buf);
490 
491 	if (dl == 1) {			/* We're the '.' directory   */
492 		buf[sl-1] = '\0';	/* Lose the 'dot'	  */
493 		return (buf);
494 	}
495 
496 	p = nis_dir_cmp(buf, d);
497 
498 	/* 's' is above 'd' in the tree */
499 	if ((p == HIGHER_NAME) || (p == NOT_SEQUENTIAL) || (p == SAME_NAME)) {
500 		free(buf);
501 		return (NULL);
502 	}
503 
504 	/* Insert a NUL where the domain name starts in the string */
505 	buf[(sl - dl) - 1] = '\0';
506 
507 	/* Don't return a zero length name */
508 	if (buf[0] == '\0') {
509 		free((void *)buf);
510 		return (NULL);
511 	}
512 
513 	return (buf);
514 }
515 
516 /*
517  * Wrapper for internal_table_name() that allocates a large enough
518  * buffer for the internal name. Return value must be freed by caller.
519  * If the input 'name' is NULL, the name of the root directory table
520  * is returned.
521  */
522 char *
523 internalTableName(char *name) {
524 	char	*buf, *res;
525 	char	*myself = "internalTableName";
526 
527 	buf = (char *)am(myself, MAXPATHLEN + NIS_MAXNAMELEN + 1);
528 	if (buf == 0)
529 		return (0);
530 
531 	if (name == 0) {
532 		(void) memcpy(buf, ROOTDIRFILE, slen(ROOTDIRFILE));
533 		return (buf);
534 	}
535 
536 	res = internal_table_name(name, buf);
537 	if (res != buf) {
538 		sfree(buf);
539 		buf = 0;
540 	}
541 
542 	return (buf);
543 }
544 
545 /*
546  * Return the object mapping for the object indicated either by the
547  * internal DB name ('intNameArg'; preferred), or the FQ object name
548  * 'name'. If 'asObj' is non-zero, the caller is interested in the
549  * object mapping proper, not a mapping of table entries. Optionally,
550  * also indicate if the object is mapped from (read) or to (write) LDAP.
551  *
552  * Note that there may be more than one mapping of the appropriate type.
553  * Use the selectTableMapping() function in ldap_map.c to get all
554  * alternatives. However, the function below works as a short-cut if:
555  *
556  *	You only want an indication that _a_ mapping of the desired
557  *	type exists, or
558  *
559  *	You want the non-objectDN information for an object-mapping
560  *	proper (i.e., _not_ the mapping for entries in a table).
561  */
562 __nis_table_mapping_t *
563 getObjMapping(char *name, char *intNameArg, int asObj,
564 		int *doRead, int *doWrite) {
565 	__nis_table_mapping_t	*t, *x;
566 	char			*intName;
567 	int			freeIntName = 0, rd, wr;
568 
569 	if (doRead != 0)
570 		*doRead = 0;
571 	if (doWrite != 0)
572 		*doWrite = 0;
573 
574 	if (intNameArg == 0) {
575 		if (name == 0)
576 			return (0);
577 		intName = internalTableName(name);
578 		if (intName == 0)
579 			return (0);
580 		freeIntName = 1;
581 	} else {
582 		intName = intNameArg;
583 	}
584 
585 	t = __nis_find_item_mt(intName, &ldapMappingList, 0, 0);
586 	if (t == 0) {
587 		if (freeIntName)
588 			sfree(intName);
589 		return (0);
590 	}
591 
592 	rd = wr = 0;
593 	for (x = t; x != 0; x = x->next) {
594 		/*
595 		 * If we're looking for an object mapping, and this
596 		 * one's for entries in a table, skip it.
597 		 */
598 		if (asObj && x->objType == NIS_TABLE_OBJ &&
599 				x->numColumns > 0)
600 			continue;
601 		/* Check if we should read/write */
602 		if (x->objectDN->read.scope != LDAP_SCOPE_UNKNOWN)
603 			rd++;
604 		if (x->objectDN->write.scope != LDAP_SCOPE_UNKNOWN)
605 			wr++;
606 	}
607 
608 	if (doRead != 0)
609 		*doRead = (rd > 0) ? 1 : 0;
610 	if (doWrite != 0)
611 		*doWrite = (wr > 0) ? 1 : 0;
612 
613 	if (freeIntName)
614 		sfree(intName);
615 
616 	return (x);
617 }
618