xref: /titanic_52/usr/src/lib/libadutils/common/addisc.c (revision 69112edd987c28fa551d4f8d9362a84a45365f17)
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 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Active Directory Auto-Discovery.
29  *
30  * This [project private] API allows the caller to provide whatever
31  * details it knows a priori (i.e., provided via configuration so as to
32  * override auto-discovery) and in any order.  Then the caller can ask
33  * for any of the auto-discoverable parameters in any order.
34  *
35  * But there is an actual order in which discovery must be done.  Given
36  * the discovery mechanism implemented here, that order is:
37  *
38  *  - the domain name joined must be discovered first
39  *  - then the domain controllers
40  *  - then the forest name and site name
41  *  - then the global catalog servers, and site-specific domain
42  *    controllers and global catalog servers.
43  *
44  * The API does not require it be called in the same order because there
45  * may be other discovery mechanisms in the future, and exposing
46  * ordering requirements of the current mechanism now can create trouble
47  * down the line.  Also, this makes the API easier to use now, which
48  * means less work to do some day when we make this a public API.
49  *
50  * Domain discovery is done by res_nsearch() of the DNS SRV RR name for
51  * domain controllers.  As long as the joined domain appears in the DNS
52  * resolver's search list then we'll find it.
53  *
54  * Domain controller discovery is a matter of formatting the DNS SRV RR
55  * FQDN for domain controllers and doing a lookup for them.  Knowledge
56  * of the domain name is not fundamentally required, but we separate the
57  * two processes, which in practice can lead to one more DNS lookup than
58  * is strictly required.
59  *
60  * Forest and site name discovery require an LDAP search of the AD
61  * "configuration partition" at a domain controller for the joined
62  * domain.  Forest and site name discovery depend on knowing the joined
63  * domain name and domain controllers for that domain.
64  *
65  * Global catalog server discovery requires knowledge of the forest
66  * name in order to format the DNS SRV RR FQDN to lookup.  Site-specific
67  * domain controller discovery depends on knowing the site name (and,
68  * therefore, joined domain, ...).  Site-specific global catalog server
69  * discovery depends on knowledge of the forest and site names, which
70  * depend on...
71  *
72  * All the work of discovering particular items is done by functions
73  * named validate_<item>().  Each such function calls validate_<item>()
74  * for any items that it depends on.
75  *
76  * This API is not thread-safe.
77  */
78 
79 
80 #include <stdio.h>
81 #include <string.h>
82 #include <strings.h>
83 #include <unistd.h>
84 #include <assert.h>
85 #include <stdlib.h>
86 #include <net/if.h>
87 #include <net/if.h>
88 #include <sys/types.h>
89 #include <sys/socket.h>
90 #include <sys/sockio.h>
91 #include <netinet/in.h>
92 #include <netinet/in.h>
93 #include <arpa/inet.h>
94 #include <arpa/nameser.h>
95 #include <resolv.h>
96 #include <netdb.h>
97 #include <ctype.h>
98 #include <errno.h>
99 #include <ldap.h>
100 #include <sasl/sasl.h>
101 #include <sys/u8_textprep.h>
102 #include <syslog.h>
103 #include "adutils_impl.h"
104 #include "addisc.h"
105 
106 
107 enum ad_item_state {
108 		AD_STATE_INVALID = 0,	/* The value is not valid */
109 		AD_STATE_FIXED,		/* The value was fixed by caller */
110 		AD_STATE_AUTO		/* The value is auto discovered */
111 		};
112 
113 enum ad_data_type {
114 		AD_STRING = 123,
115 		AD_DIRECTORY,
116 		AD_DOMAINS_IN_FOREST,
117 		AD_TRUSTED_DOMAINS
118 		};
119 
120 
121 typedef struct ad_subnet {
122 	char subnet[24];
123 } ad_subnet_t;
124 
125 
126 typedef struct ad_item {
127 	enum ad_item_state	state;
128 	enum ad_data_type	type;
129 	void 			*value;
130 	time_t 			ttl;
131 	unsigned int 		version;	/* Version is only changed */
132 						/* if the value changes */
133 #define	PARAM1		0
134 #define	PARAM2		1
135 	int 		param_version[2];
136 					/* These holds the version of */
137 					/* dependents so that a dependent */
138 					/* change can be detected */
139 } ad_item_t;
140 
141 typedef struct ad_disc {
142 	struct __res_state res_state;
143 	int		res_ninitted;
144 	ad_subnet_t	*subnets;
145 	boolean_t	subnets_changed;
146 	time_t		subnets_last_check;
147 	ad_item_t	domain_name;		/* DNS hostname string */
148 	ad_item_t	domain_controller;	/* Directory hostname and */
149 						/* port array */
150 	ad_item_t	site_name;		/* String */
151 	ad_item_t	forest_name;		/* DNS forestname string */
152 	ad_item_t	global_catalog;		/* Directory hostname and */
153 						/* port array */
154 	ad_item_t	domains_in_forest;	/* DNS domainname and SID */
155 						/* array */
156 	ad_item_t	trusted_domains;	/* DNS domainname and trust */
157 						/* direction array */
158 	/* Site specfic versions */
159 	ad_item_t	site_domain_controller;	/* Directory hostname and */
160 						/* port array */
161 	ad_item_t	site_global_catalog;	/* Directory hostname and */
162 						/* port array */
163 } ad_disc;
164 
165 
166 #define	DNS_MAX_NAME	NS_MAXDNAME
167 
168 
169 /* SRV RR names for various queries */
170 #define	LDAP_SRV_HEAD		"_ldap._tcp."
171 #define	SITE_SRV_MIDDLE		"%s._sites."
172 #define	GC_SRV_TAIL		"gc._msdcs"
173 #define	DC_SRV_TAIL		"dc._msdcs"
174 #define	ALL_GC_SRV_TAIL		"_gc._tcp"
175 #define	PDC_SRV			 "_ldap._tcp.pdc._msdcs.%s"
176 
177 /* A RR name for all GCs -- last resort this works */
178 #define	GC_ALL_A_NAME_FSTR "gc._msdcs.%s."
179 
180 
181 /*
182  * We try res_ninit() whenever we don't have one.  res_ninit() fails if
183  * idmapd is running before the network is up!
184  */
185 #define	DO_RES_NINIT(ctx)   if (!(ctx)->res_ninitted) \
186 		(ctx)->res_ninitted = (res_ninit(&ctx->res_state) != -1)
187 
188 #define	is_fixed(item)					\
189 	((item)->state == AD_STATE_FIXED)
190 
191 #define	is_changed(item, num, param) 			\
192 	((item)->param_version[num] != (param)->version)
193 
194 /*LINTLIBRARY*/
195 
196 /*
197  * Function definitions
198  */
199 static ad_item_t *
200 validate_SiteName(ad_disc_t ctx);
201 
202 
203 
204 static void
205 update_version(ad_item_t *item, int  num, ad_item_t *param)
206 {
207 	item->param_version[num] = param->version;
208 }
209 
210 
211 
212 static boolean_t
213 is_valid(ad_item_t *item)
214 {
215 	if (item->value != NULL) {
216 		if (item->state == AD_STATE_FIXED)
217 			return (B_TRUE);
218 		if (item->state == AD_STATE_AUTO &&
219 		    (item->ttl == 0 || item->ttl > time(NULL)))
220 			return (B_TRUE);
221 	}
222 	return (B_FALSE);
223 }
224 
225 
226 static void
227 update_item(ad_item_t *item, void *value, enum ad_item_state state,
228 		uint32_t ttl)
229 {
230 	if (item->value != NULL && value != NULL) {
231 		if ((item->type == AD_STRING &&
232 		    strcmp(item->value, value) != 0) ||
233 		    (item->type == AD_DIRECTORY &&
234 		    ad_disc_compare_ds(item->value, value) != 0)||
235 		    (item->type == AD_DOMAINS_IN_FOREST &&
236 		    ad_disc_compare_domainsinforest(item->value, value) != 0) ||
237 		    (item->type == AD_TRUSTED_DOMAINS &&
238 		    ad_disc_compare_trusteddomains(item->value, value) != 0))
239 			item->version++;
240 	} else if (item->value != value)
241 		item->version++;
242 
243 	if (item->value != NULL)
244 		free(item->value);
245 
246 	item->value = value;
247 	item->state = state;
248 
249 	if (ttl == 0)
250 		item->ttl = 0;
251 	else
252 		item->ttl = time(NULL) + ttl;
253 }
254 
255 
256 /* Compare DS lists */
257 int
258 ad_disc_compare_ds(idmap_ad_disc_ds_t *ds1, idmap_ad_disc_ds_t *ds2)
259 {
260 	int		i, j;
261 	int		num_ds1;
262 	int		num_ds2;
263 	boolean_t	match;
264 
265 	for (i = 0; ds1[i].host[0] != '\0'; i++)
266 		continue;
267 	num_ds1 = i;
268 	for (j = 0; ds2[j].host[0] != '\0'; j++)
269 		continue;
270 	num_ds2 = j;
271 	if (num_ds1 != num_ds2)
272 		return (1);
273 
274 	for (i = 0; i < num_ds1; i++) {
275 		match = B_FALSE;
276 		for (j = 0; j < num_ds2; j++) {
277 			if (strcmp(ds1[i].host, ds2[j].host) == 0 &&
278 			    ds1[i].port == ds2[j].port) {
279 				match = B_TRUE;
280 				break;
281 			}
282 		}
283 		if (!match)
284 			return (1);
285 	}
286 	return (0);
287 }
288 
289 
290 /* Copy a list of DSs */
291 static idmap_ad_disc_ds_t *
292 ds_dup(const idmap_ad_disc_ds_t *srv)
293 {
294 	int	i;
295 	int	size;
296 	idmap_ad_disc_ds_t *new = NULL;
297 
298 	for (i = 0; srv[i].host[0] != '\0'; i++)
299 		continue;
300 
301 	size = (i + 1) * sizeof (idmap_ad_disc_ds_t);
302 	new = malloc(size);
303 	if (new != NULL)
304 		memcpy(new, srv, size);
305 	return (new);
306 }
307 
308 
309 int
310 ad_disc_compare_trusteddomains(ad_disc_trusteddomains_t *td1,
311 			ad_disc_trusteddomains_t *td2)
312 {
313 	int		i, j;
314 	int		num_td1;
315 	int		num_td2;
316 	boolean_t	match;
317 
318 	for (i = 0; td1[i].domain[0] != '\0'; i++)
319 		continue;
320 	num_td1 = i;
321 
322 	for (j = 0; td2[j].domain[0] != '\0'; j++)
323 		continue;
324 	num_td2 = j;
325 
326 	if (num_td1 != num_td2)
327 		return (1);
328 
329 	for (i = 0; i < num_td1; i++) {
330 		match = B_FALSE;
331 		for (j = 0; j < num_td2; j++) {
332 			if (domain_eq(td1[i].domain, td2[j].domain)) {
333 				match = B_TRUE;
334 				break;
335 			}
336 		}
337 		if (!match)
338 			return (1);
339 	}
340 	return (0);
341 }
342 
343 
344 
345 /* Copy a list of Trusted Domains */
346 static ad_disc_trusteddomains_t *
347 td_dup(const ad_disc_trusteddomains_t *td)
348 {
349 	int	i;
350 	int	size;
351 	ad_disc_trusteddomains_t *new = NULL;
352 
353 	for (i = 0; td[i].domain[0] != '\0'; i++)
354 		continue;
355 
356 	size = (i + 1) * sizeof (ad_disc_trusteddomains_t);
357 	new = malloc(size);
358 	if (new != NULL)
359 		memcpy(new, td, size);
360 	return (new);
361 }
362 
363 
364 
365 int
366 ad_disc_compare_domainsinforest(ad_disc_domainsinforest_t *df1,
367 			ad_disc_domainsinforest_t *df2)
368 {
369 	int		i, j;
370 	int		num_df1;
371 	int		num_df2;
372 	boolean_t	match;
373 
374 	for (i = 0; df1[i].domain[0] != '\0'; i++)
375 		continue;
376 	num_df1 = i;
377 
378 	for (j = 0; df2[j].domain[0] != '\0'; j++)
379 		continue;
380 	num_df2 = j;
381 
382 	if (num_df1 != num_df2)
383 		return (1);
384 
385 	for (i = 0; i < num_df1; i++) {
386 		match = B_FALSE;
387 		for (j = 0; j < num_df2; j++) {
388 			if (domain_eq(df1[i].domain, df2[j].domain) &&
389 			    strcmp(df1[i].sid, df2[j].sid) == 0) {
390 				match = B_TRUE;
391 				break;
392 			}
393 		}
394 		if (!match)
395 			return (1);
396 	}
397 	return (0);
398 }
399 
400 
401 
402 /* Copy a list of Trusted Domains */
403 static ad_disc_domainsinforest_t *
404 df_dup(const ad_disc_domainsinforest_t *df)
405 {
406 	int	i;
407 	int	size;
408 	ad_disc_domainsinforest_t *new = NULL;
409 
410 	for (i = 0; df[i].domain[0] != '\0'; i++)
411 		continue;
412 
413 	size = (i + 1) * sizeof (ad_disc_domainsinforest_t);
414 	new = malloc(size);
415 	if (new != NULL)
416 		memcpy(new, df, size);
417 	return (new);
418 }
419 
420 
421 
422 
423 
424 /*
425  * Returns an array of IPv4 address/prefix length
426  * The last subnet is NULL
427  */
428 static ad_subnet_t *
429 find_subnets()
430 {
431 	int		sock, n, i;
432 	struct lifconf	lifc;
433 	struct lifreq	lifr, *lifrp;
434 	struct lifnum	lifn;
435 	uint32_t	prefix_len;
436 	char		*s;
437 	ad_subnet_t	*results;
438 
439 	lifrp = &lifr;
440 
441 	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
442 		logger(LOG_ERR, "Failed to open IPv4 socket for "
443 		    "listing network interfaces (%s)", strerror(errno));
444 		return (NULL);
445 	}
446 
447 	lifn.lifn_family = AF_INET;
448 	lifn.lifn_flags = 0;
449 	if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) {
450 		logger(LOG_ERR,
451 		    "Failed to find the number of network interfaces (%s)",
452 		    strerror(errno));
453 		close(sock);
454 		return (NULL);
455 	}
456 
457 	if (lifn.lifn_count < 1) {
458 		logger(LOG_ERR, "No IPv4 network interfaces found");
459 		close(sock);
460 		return (NULL);
461 	}
462 
463 	lifc.lifc_family = AF_INET;
464 	lifc.lifc_flags = 0;
465 	lifc.lifc_len = lifn.lifn_count * sizeof (struct lifreq);
466 	lifc.lifc_buf = malloc(lifc.lifc_len);
467 
468 	if (lifc.lifc_buf == NULL) {
469 		logger(LOG_ERR, "Out of memory");
470 		close(sock);
471 		return (NULL);
472 	}
473 
474 	if (ioctl(sock, SIOCGLIFCONF, (char *)&lifc) < 0) {
475 		logger(LOG_ERR, "Failed to list network interfaces (%s)",
476 		    strerror(errno));
477 		free(lifc.lifc_buf);
478 		close(sock);
479 		return (NULL);
480 	}
481 
482 	n = lifc.lifc_len / (int)sizeof (struct lifreq);
483 
484 	if ((results = calloc(n + 1, sizeof (ad_subnet_t))) == NULL) {
485 		free(lifc.lifc_buf);
486 		close(sock);
487 		return (NULL);
488 	}
489 
490 	for (i = 0, lifrp = lifc.lifc_req; i < n; i++, lifrp++) {
491 		if (ioctl(sock, SIOCGLIFFLAGS, lifrp) < 0)
492 			continue;
493 
494 		if ((lifrp->lifr_flags & IFF_UP) == 0)
495 			continue;
496 
497 		if (ioctl(sock, SIOCGLIFSUBNET, lifrp) < 0)
498 			continue;
499 
500 		prefix_len = lifrp->lifr_addrlen;
501 
502 		s = inet_ntoa(((struct sockaddr_in *)
503 		    &lifrp->lifr_addr)->sin_addr);
504 
505 		(void) snprintf(results[i].subnet, sizeof (ad_subnet_t),
506 		    "%s/%d", s, prefix_len);
507 	}
508 
509 	free(lifc.lifc_buf);
510 	close(sock);
511 
512 	return (results);
513 }
514 
515 static int
516 cmpsubnets(ad_subnet_t *subnets1, ad_subnet_t *subnets2)
517 {
518 	int num_subnets1;
519 	int num_subnets2;
520 	boolean_t matched;
521 	int i, j;
522 
523 	for (i = 0; subnets1[i].subnet[0] != '\0'; i++)
524 		continue;
525 	num_subnets1 = i;
526 
527 	for (i = 0; subnets2[i].subnet[0] != '\0'; i++)
528 		continue;
529 	num_subnets2 = i;
530 
531 	if (num_subnets1 != num_subnets2)
532 		return (1);
533 
534 	for (i = 0;  i < num_subnets1; i++) {
535 		matched = B_FALSE;
536 		for (j = 0; j < num_subnets2; j++) {
537 			if (strcmp(subnets1[i].subnet,
538 			    subnets2[j].subnet) == 0) {
539 				matched = B_TRUE;
540 				break;
541 			}
542 		}
543 		if (!matched)
544 			return (1);
545 	}
546 	return (0);
547 }
548 
549 
550 
551 
552 /* Convert a DN's DC components into a DNS domainname */
553 char *
554 DN_to_DNS(const char *dn_name)
555 {
556 	char	dns[DNS_MAX_NAME];
557 	char	*dns_name;
558 	int	i, j;
559 	int	num = 0;
560 
561 	j = 0;
562 	i = 0;
563 
564 	if (dn_name == NULL)
565 		return (NULL);
566 	/*
567 	 * Find all DC=<value> and form DNS name of the
568 	 * form <value1>.<value2>...
569 	 */
570 	while (dn_name[i] != '\0') {
571 		if (strncasecmp(&dn_name[i], "DC=", 3) == 0) {
572 			i += 3;
573 			if (dn_name[i] != '\0' && num > 0)
574 				dns[j++] = '.';
575 			while (dn_name[i] != '\0' &&
576 			    dn_name[i] != ',' && dn_name[i] != '+')
577 				dns[j++] = dn_name[i++];
578 			num++;
579 		} else {
580 			/* Skip attr=value as it is not DC= */
581 			while (dn_name[i] != '\0' &&
582 			    dn_name[i] != ',' && dn_name[i] != '+')
583 				i++;
584 		}
585 		/* Skip over separator ','  or '+' */
586 		if (dn_name[i] != '\0') i++;
587 	}
588 	dns[j] = '\0';
589 	dns_name = malloc(j + 1);
590 	if (dns_name != NULL)
591 		(void) strlcpy(dns_name, dns, j + 1);
592 	return (dns_name);
593 }
594 
595 
596 /* Format the DN of an AD LDAP subnet object for some subnet */
597 static char *
598 subnet_to_DN(const char *subnet, const char *baseDN)
599 {
600 	char *result;
601 	int len;
602 
603 	len = snprintf(NULL, 0,
604 	    "CN=%s,CN=Subnets,CN=Sites,%s",
605 	    subnet, baseDN) + 1;
606 
607 	result = malloc(len);
608 	if (result != NULL)
609 		(void) snprintf(result, len,
610 		    "CN=%s,CN=Subnets,CN=Sites,%s",
611 		    subnet, baseDN);
612 	return (result);
613 }
614 
615 
616 /* Make a list of subnet object DNs from a list of subnets */
617 static char **
618 subnets_to_DNs(ad_subnet_t *subnets, const char *base_dn)
619 {
620 	char **results;
621 	int i, j;
622 
623 	for (i = 0; subnets[i].subnet[0] != '\0'; i++)
624 		continue;
625 
626 	results = calloc(i + 1, sizeof (char *));
627 	if (results == NULL)
628 		return (NULL);
629 
630 	for (i = 0; subnets[i].subnet[0] != '\0'; i++) {
631 		if ((results[i] = subnet_to_DN(subnets[i].subnet, base_dn))
632 		    == NULL) {
633 			for (j = 0; j < i; j++)
634 				free(results[j]);
635 			free(results);
636 			return (NULL);
637 		}
638 	}
639 
640 	return (results);
641 }
642 
643 
644 /* Compare SRC RRs; used with qsort() */
645 static int
646 srvcmp(idmap_ad_disc_ds_t *s1, idmap_ad_disc_ds_t *s2)
647 {
648 	if (s1->priority < s2->priority)
649 		return (1);
650 	else if (s1->priority > s2->priority)
651 		return (-1);
652 
653 	if (s1->weight < s2->weight)
654 		return (1);
655 	else if (s1->weight > s2->weight)
656 		return (-1);
657 
658 	return (0);
659 }
660 
661 
662 /*
663  * Query or search the SRV RRs for a given name.
664  *
665  * If name == NULL then search (as in res_nsearch(3RESOLV), honoring any
666  * search list/option), else query (as in res_nquery(3RESOLV)).
667  *
668  * The output TTL will be the one of the SRV RR with the lowest TTL.
669  */
670 idmap_ad_disc_ds_t *
671 srv_query(res_state state, const char *svc_name, const char *dname,
672 		char **rrname, uint32_t *ttl)
673 {
674 	idmap_ad_disc_ds_t *srv;
675 	idmap_ad_disc_ds_t *srv_res = NULL;
676 	union {
677 		HEADER hdr;
678 		uchar_t buf[NS_MAXMSG];
679 	} msg;
680 	int len, cnt, qdcount, ancount;
681 	uchar_t *ptr, *eom;
682 	uchar_t *end;
683 	uint16_t type;
684 	/* LINTED  E_FUNC_SET_NOT_USED */
685 	uint16_t class;
686 	uint32_t rttl;
687 	uint16_t size;
688 	char namebuf[NS_MAXDNAME];
689 
690 	if (state == NULL)
691 		return (NULL);
692 
693 	/* Set negative result TTL */
694 	*ttl = 5 * 60;
695 
696 	/* 1. query necessary resource records */
697 
698 	/* Search, querydomain or query */
699 	if (rrname != NULL) {
700 		*rrname = NULL;
701 		len = res_nsearch(state, svc_name, C_IN, T_SRV,
702 		    msg.buf, sizeof (msg.buf));
703 		logger(LOG_DEBUG, "Searching DNS for SRV RRs named '%s'",
704 		    svc_name);
705 		if (len < 0) {
706 			logger(LOG_DEBUG, "DNS search for '%s' failed (%s)",
707 			    svc_name, hstrerror(state->res_h_errno));
708 			return (NULL);
709 		}
710 	} else if (dname != NULL) {
711 		logger(LOG_DEBUG,
712 		    "Querying DNS for SRV RRs named '%s' for '%s' ",
713 		    svc_name, dname);
714 
715 		len = res_nquerydomain(state, svc_name, dname, C_IN, T_SRV,
716 		    msg.buf, sizeof (msg.buf));
717 
718 		if (len < 0) {
719 			logger(LOG_DEBUG,
720 			    "DNS query for '%s' for '%s' failed (%s)",
721 			    svc_name, dname, hstrerror(state->res_h_errno));
722 			return (NULL);
723 		}
724 	}
725 
726 	if (len > sizeof (msg.buf)) {
727 		logger(LOG_ERR, "DNS query %ib message doesn't fit"
728 		    " into %ib buffer",
729 		    len, sizeof (msg.buf));
730 		return (NULL);
731 	}
732 
733 	/* 2. parse the reply, skip header and question sections */
734 
735 	ptr = msg.buf + sizeof (msg.hdr);
736 	eom = msg.buf + len;
737 	qdcount = ntohs(msg.hdr.qdcount);
738 	ancount = ntohs(msg.hdr.ancount);
739 
740 	for (cnt = qdcount; cnt > 0; --cnt) {
741 		if ((len = dn_skipname(ptr, eom)) < 0) {
742 			logger(LOG_ERR, "DNS query invalid message format");
743 			return (NULL);
744 		}
745 		ptr += len + QFIXEDSZ;
746 	}
747 
748 	/* 3. walk through the answer section */
749 
750 	srv_res = calloc(ancount + 1, sizeof (idmap_ad_disc_ds_t));
751 	if (srv_res == NULL) {
752 		logger(LOG_ERR, "Out of memory");
753 		return (NULL);
754 	}
755 
756 	*ttl = (uint32_t)-1;
757 
758 	for (srv = srv_res, cnt = ancount;
759 	    cnt > 0; --cnt, srv++) {
760 
761 		len = dn_expand(msg.buf, eom, ptr, namebuf,
762 		    sizeof (namebuf));
763 		if (len < 0) {
764 			logger(LOG_ERR, "DNS query invalid message format");
765 			goto err;
766 		}
767 		if (rrname != NULL && *rrname == NULL) {
768 			*rrname = strdup(namebuf);
769 			if (*rrname == NULL) {
770 				logger(LOG_ERR, "Out of memory");
771 				goto err;
772 			}
773 		}
774 		ptr += len;
775 		NS_GET16(type, ptr);
776 		NS_GET16(class, ptr);
777 		NS_GET32(rttl, ptr);
778 		NS_GET16(size, ptr);
779 		if ((end = ptr + size) > eom) {
780 			logger(LOG_ERR, "DNS query invalid message format");
781 			goto err;
782 		}
783 
784 		if (type != T_SRV) {
785 			ptr = end;
786 			continue;
787 		}
788 
789 		NS_GET16(srv->priority, ptr);
790 		NS_GET16(srv->weight, ptr);
791 		NS_GET16(srv->port, ptr);
792 		len = dn_expand(msg.buf, eom, ptr, srv->host,
793 		    sizeof (srv->host));
794 		if (len < 0) {
795 			logger(LOG_ERR, "DNS query invalid SRV record");
796 			goto err;
797 		}
798 
799 		if (rttl < *ttl)
800 			*ttl = rttl;
801 
802 		logger(LOG_DEBUG, "Found %s %d IN SRV [%d][%d] %s:%d",
803 		    namebuf, rttl, srv->priority, srv->weight, srv->host,
804 		    srv->port);
805 
806 		/* 3. move ptr to the end of current record */
807 
808 		ptr = end;
809 	}
810 
811 	if (ancount > 1)
812 		qsort(srv_res, ancount, sizeof (*srv_res),
813 		    (int (*)(const void *, const void *))srvcmp);
814 
815 	return (srv_res);
816 
817 err:
818 	free(srv_res);
819 	if (rrname != NULL) {
820 		free(*rrname);
821 		*rrname = NULL;
822 	}
823 	return (NULL);
824 }
825 
826 
827 /*
828  * A utility function to bind to a Directory server
829  */
830 
831 static LDAP*
832 ldap_lookup_init(idmap_ad_disc_ds_t *ds)
833 {
834 	int 	i;
835 	int	rc, ldversion;
836 	int	zero = 0;
837 	int 	timeoutms = 5 * 1000;
838 	char 	*saslmech = "GSSAPI";
839 	uint32_t saslflags = LDAP_SASL_INTERACTIVE;
840 	LDAP 	*ld = NULL;
841 
842 	for (i = 0; ds[i].host[0] != '\0'; i++) {
843 		ld = ldap_init(ds[i].host, ds[i].port);
844 		if (ld == NULL) {
845 			logger(LOG_DEBUG, "Couldn't connect to "
846 			    "AD DC %s:%d (%s)",
847 			    ds[i].host, ds[i].port,
848 			    strerror(errno));
849 			continue;
850 		}
851 
852 		ldversion = LDAP_VERSION3;
853 		(void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION,
854 		    &ldversion);
855 		(void) ldap_set_option(ld, LDAP_OPT_REFERRALS,
856 		    LDAP_OPT_OFF);
857 		(void) ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &zero);
858 		(void) ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &zero);
859 		/* setup TCP/IP connect timeout */
860 		(void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT,
861 		    &timeoutms);
862 		(void) ldap_set_option(ld, LDAP_OPT_RESTART,
863 		    LDAP_OPT_ON);
864 
865 		rc = adutils_set_thread_functions(ld);
866 		if (rc != LDAP_SUCCESS) {
867 			/* Error has already been logged */
868 			(void) ldap_unbind(ld);
869 			ld = NULL;
870 			continue;
871 		}
872 
873 		rc = ldap_sasl_interactive_bind_s(ld, "" /* binddn */,
874 		    saslmech, NULL, NULL, saslflags, &saslcallback,
875 		    NULL /* defaults */);
876 		if (rc == LDAP_SUCCESS)
877 			break;
878 
879 		logger(LOG_INFO, "LDAP SASL bind to %s:%d failed (%s)",
880 		    ds[i].host, ds[i].port, ldap_err2string(rc));
881 		(void) ldap_unbind(ld);
882 		ld = NULL;
883 	}
884 	return (ld);
885 }
886 
887 
888 
889 /*
890  * A utility function to get the value of some attribute of one of one
891  * or more AD LDAP objects named by the dn_list; first found one wins.
892  */
893 static char *
894 ldap_lookup_entry_attr(LDAP **ld, idmap_ad_disc_ds_t *domainControllers,
895 			char **dn_list, char *attr)
896 {
897 	int 	i;
898 	int	rc;
899 	int	scope = LDAP_SCOPE_BASE;
900 	char	*attrs[2];
901 	LDAPMessage *results = NULL;
902 	LDAPMessage *entry;
903 	char	**values = NULL;
904 	char	*val = NULL;
905 
906 	attrs[0] = attr;
907 	attrs[1] = NULL;
908 
909 	if (*ld == NULL)
910 		*ld = ldap_lookup_init(domainControllers);
911 
912 	if (*ld == NULL)
913 		return (NULL);
914 
915 	for (i = 0; dn_list[i] != NULL; i++) {
916 		rc = ldap_search_s(*ld, dn_list[i], scope,
917 		    "(objectclass=*)", attrs, 0, &results);
918 		if (rc == LDAP_SUCCESS) {
919 			for (entry = ldap_first_entry(*ld, results);
920 			    entry != NULL && values == NULL;
921 			    entry = ldap_next_entry(*ld, entry)) {
922 				values = ldap_get_values(
923 				    *ld, entry, attr);
924 			}
925 
926 			if (values != NULL) {
927 				(void) ldap_msgfree(results);
928 				val = strdup(values[0]);
929 				ldap_value_free(values);
930 				return (val);
931 			}
932 		}
933 		if (results != NULL) {
934 			(void) ldap_msgfree(results);
935 			results = NULL;
936 		}
937 	}
938 
939 	return (NULL);
940 }
941 
942 
943 /*
944  * Lookup the trusted domains in the global catalog.
945  *
946  * Returns:
947  *	array of trusted domains which is terminated by
948  *		an empty trusted domain.
949  *	NULL an error occured
950  */
951 ad_disc_trusteddomains_t *
952 ldap_lookup_trusted_domains(LDAP **ld, idmap_ad_disc_ds_t *globalCatalog,
953 			char *base_dn)
954 {
955 	int		scope = LDAP_SCOPE_SUBTREE;
956 	char		*attrs[3];
957 	int		rc;
958 	LDAPMessage	*results = NULL;
959 	LDAPMessage	*entry;
960 	char		*filter;
961 	char		**partner = NULL;
962 	char		**direction = NULL;
963 	int		num = 0;
964 	ad_disc_trusteddomains_t *trusted_domains = NULL;
965 
966 
967 	if (*ld == NULL)
968 		*ld = ldap_lookup_init(globalCatalog);
969 
970 	if (*ld == NULL)
971 		return (NULL);
972 
973 	attrs[0] = "trustPartner";
974 	attrs[1] = "trustDirection";
975 	attrs[2] = NULL;
976 
977 	/* trustDirection values - inbound = 1 and bidirectional = 3 */
978 	filter = "(&(objectclass=trustedDomain)"
979 	    "(|(trustDirection=3)(trustDirection=1)))";
980 
981 	rc = ldap_search_s(*ld, base_dn, scope, filter, attrs, 0, &results);
982 	if (rc == LDAP_SUCCESS) {
983 		for (entry = ldap_first_entry(*ld, results);
984 		    entry != NULL; entry = ldap_next_entry(*ld, entry)) {
985 			partner = ldap_get_values(*ld, entry, "trustPartner");
986 			direction = ldap_get_values(
987 			    *ld, entry, "trustDirection");
988 
989 			if (partner != NULL && direction != NULL) {
990 				num++;
991 				trusted_domains = realloc(trusted_domains,
992 				    (num + 1) *
993 				    sizeof (ad_disc_trusteddomains_t));
994 				if (trusted_domains == NULL) {
995 					ldap_value_free(partner);
996 					ldap_value_free(direction);
997 					ldap_msgfree(results);
998 					return (NULL);
999 				}
1000 				/* Last element should be zero */
1001 				memset(&trusted_domains[num], 0,
1002 				    sizeof (ad_disc_trusteddomains_t));
1003 				strcpy(trusted_domains[num - 1].domain,
1004 				    partner[0]);
1005 				trusted_domains[num - 1].direction =
1006 				    atoi(direction[0]);
1007 			}
1008 			if (partner != NULL)
1009 				ldap_value_free(partner);
1010 			if (direction != NULL)
1011 				ldap_value_free(direction);
1012 		}
1013 	} else if (rc == LDAP_NO_RESULTS_RETURNED) {
1014 		/* This is not an error - return empty trusted domain */
1015 		trusted_domains = calloc(1, sizeof (ad_disc_trusteddomains_t));
1016 	}
1017 	if (results != NULL)
1018 		ldap_msgfree(results);
1019 
1020 	return (trusted_domains);
1021 }
1022 
1023 
1024 /*
1025  * This functions finds all the domains in a forest.
1026  */
1027 ad_disc_domainsinforest_t *
1028 ldap_lookup_domains_in_forest(LDAP **ld, idmap_ad_disc_ds_t *globalCatalogs)
1029 {
1030 	static char	*attrs[] = {
1031 		"objectSid",
1032 		NULL,
1033 	};
1034 	int		rc;
1035 	LDAPMessage	*result = NULL;
1036 	LDAPMessage	*entry;
1037 	int		ndomains = 0;
1038 	int		nresults;
1039 	ad_disc_domainsinforest_t *domains = NULL;
1040 
1041 	if (*ld == NULL)
1042 		*ld = ldap_lookup_init(globalCatalogs);
1043 
1044 	if (*ld == NULL)
1045 		return (NULL);
1046 
1047 	logger(LOG_DEBUG, "Looking for domains in forest...");
1048 	/* Find domains */
1049 	rc = ldap_search_s(*ld, "", LDAP_SCOPE_SUBTREE,
1050 	    "(objectClass=Domain)", attrs, 0, &result);
1051 	if (rc != LDAP_SUCCESS)
1052 		goto err;
1053 
1054 	nresults = ldap_count_entries(*ld, result);
1055 	domains = calloc(nresults + 1, sizeof (*domains));
1056 	if (domains == NULL)
1057 		goto err;
1058 
1059 	for (entry = ldap_first_entry(*ld, result);
1060 	    entry != NULL;
1061 	    entry = ldap_next_entry(*ld, entry)) {
1062 		struct berval	**sid_ber;
1063 		adutils_sid_t	sid;
1064 		char		*sid_str;
1065 		char 		*name;
1066 		char		*dn;
1067 
1068 		sid_ber = ldap_get_values_len(*ld, entry,
1069 		    "objectSid");
1070 		if (sid_ber == NULL)
1071 			continue;
1072 
1073 		rc = adutils_getsid(sid_ber[0], &sid);
1074 		ldap_value_free_len(sid_ber);
1075 		if (rc < 0)
1076 			goto err;
1077 
1078 		if ((sid_str = adutils_sid2txt(&sid)) == NULL)
1079 			goto err;
1080 
1081 		strcpy(domains[ndomains].sid, sid_str);
1082 		free(sid_str);
1083 
1084 		dn = ldap_get_dn(*ld, entry);
1085 		name = DN_to_DNS(dn);
1086 		free(dn);
1087 		if (name == NULL)
1088 			goto err;
1089 
1090 		strcpy(domains[ndomains].domain, name);
1091 		free(name);
1092 
1093 		logger(LOG_DEBUG, "    found %s", domains[ndomains].domain);
1094 
1095 		ndomains++;
1096 	}
1097 
1098 	if (ndomains == 0)
1099 		goto err;
1100 
1101 	if (ndomains < nresults) {
1102 		ad_disc_domainsinforest_t *tmp;
1103 		tmp = realloc(domains, (ndomains + 1) * sizeof (*domains));
1104 		if (tmp == NULL)
1105 			goto err;
1106 		domains = tmp;
1107 	}
1108 
1109 	if (result != NULL)
1110 		ldap_msgfree(result);
1111 
1112 	return (domains);
1113 
1114 err:
1115 	free(domains);
1116 	if (result != NULL)
1117 		ldap_msgfree(result);
1118 	return (NULL);
1119 }
1120 
1121 
1122 ad_disc_t
1123 ad_disc_init(void)
1124 {
1125 	struct ad_disc *ctx;
1126 	ctx = calloc(1, sizeof (struct ad_disc));
1127 	if (ctx != NULL)
1128 		DO_RES_NINIT(ctx);
1129 
1130 	ctx->domain_name.type = AD_STRING;
1131 	ctx->domain_controller.type = AD_DIRECTORY;
1132 	ctx->site_name.type = AD_STRING;
1133 	ctx->forest_name.type = AD_STRING;
1134 	ctx->global_catalog.type = AD_DIRECTORY;
1135 	ctx->domains_in_forest.type = AD_DOMAINS_IN_FOREST;
1136 	ctx->trusted_domains.type = AD_TRUSTED_DOMAINS;
1137 	/* Site specific versions */
1138 	ctx->site_domain_controller.type = AD_DIRECTORY;
1139 	ctx->site_global_catalog.type = AD_DIRECTORY;
1140 	return (ctx);
1141 }
1142 
1143 
1144 void
1145 ad_disc_fini(ad_disc_t ctx)
1146 {
1147 	if (ctx == NULL)
1148 		return;
1149 
1150 	if (ctx->res_ninitted)
1151 		res_ndestroy(&ctx->res_state);
1152 
1153 	if (ctx->subnets != NULL)
1154 		free(ctx->subnets);
1155 
1156 	if (ctx->domain_name.value != NULL)
1157 		free(ctx->domain_name.value);
1158 
1159 	if (ctx->domain_controller.value != NULL)
1160 		free(ctx->domain_controller.value);
1161 
1162 	if (ctx->site_name.value != NULL)
1163 		free(ctx->site_name.value);
1164 
1165 	if (ctx->forest_name.value != NULL)
1166 		free(ctx->forest_name.value);
1167 
1168 	if (ctx->global_catalog.value != NULL)
1169 		free(ctx->global_catalog.value);
1170 
1171 	if (ctx->domains_in_forest.value != NULL)
1172 		free(ctx->domains_in_forest.value);
1173 
1174 	if (ctx->trusted_domains.value != NULL)
1175 		free(ctx->trusted_domains.value);
1176 
1177 	/* Site specific versions */
1178 	if (ctx->site_domain_controller.value != NULL)
1179 		free(ctx->site_domain_controller.value);
1180 
1181 	if (ctx->site_global_catalog.value != NULL)
1182 		free(ctx->site_global_catalog.value);
1183 
1184 	free(ctx);
1185 }
1186 
1187 void
1188 ad_disc_refresh(ad_disc_t ctx)
1189 {
1190 	if (ctx->res_ninitted)
1191 		res_ndestroy(&ctx->res_state);
1192 	(void) memset(&ctx->res_state, 0, sizeof (ctx->res_state));
1193 	ctx->res_ninitted = res_ninit(&ctx->res_state) != -1;
1194 
1195 	if (ctx->domain_name.state == AD_STATE_AUTO)
1196 		ctx->domain_name.state = AD_STATE_INVALID;
1197 
1198 	if (ctx->domain_controller.state == AD_STATE_AUTO)
1199 		ctx->domain_controller.state  = AD_STATE_INVALID;
1200 
1201 	if (ctx->site_name.state == AD_STATE_AUTO)
1202 		ctx->site_name.state = AD_STATE_INVALID;
1203 
1204 	if (ctx->forest_name.state == AD_STATE_AUTO)
1205 		ctx->forest_name.state = AD_STATE_INVALID;
1206 
1207 	if (ctx->global_catalog.state == AD_STATE_AUTO)
1208 		ctx->global_catalog.state = AD_STATE_INVALID;
1209 
1210 	if (ctx->domains_in_forest.state == AD_STATE_AUTO)
1211 		ctx->domains_in_forest.state  = AD_STATE_INVALID;
1212 
1213 	if (ctx->trusted_domains.state == AD_STATE_AUTO)
1214 		ctx->trusted_domains.state  = AD_STATE_INVALID;
1215 
1216 	if (ctx->site_domain_controller.state == AD_STATE_AUTO)
1217 		ctx->site_domain_controller.state  = AD_STATE_INVALID;
1218 
1219 	if (ctx->site_global_catalog.state == AD_STATE_AUTO)
1220 		ctx->site_global_catalog.state = AD_STATE_INVALID;
1221 }
1222 
1223 
1224 
1225 /* Discover joined Active Directory domainName */
1226 static ad_item_t *
1227 validate_DomainName(ad_disc_t ctx)
1228 {
1229 	idmap_ad_disc_ds_t *domain_controller = NULL;
1230 	char *dname, *srvname;
1231 	uint32_t ttl = 0;
1232 	int len;
1233 
1234 	if (is_valid(&ctx->domain_name))
1235 		return (&ctx->domain_name);
1236 
1237 
1238 	/* Try to find our domain by searching for DCs for it */
1239 	DO_RES_NINIT(ctx);
1240 	domain_controller = srv_query(&ctx->res_state, LDAP_SRV_HEAD
1241 	    DC_SRV_TAIL, ctx->domain_name.value, &srvname, &ttl);
1242 
1243 	/*
1244 	 * If we can't find DCs by via res_nsearch() then there's no
1245 	 * point in trying anything else to discover the AD domain name.
1246 	 */
1247 	if (domain_controller == NULL)
1248 		return (NULL);
1249 
1250 	free(domain_controller);
1251 	/*
1252 	 * We have the FQDN of the SRV RR name, so now we extract the
1253 	 * domainname suffix from it.
1254 	 */
1255 	dname = strdup(srvname + strlen(LDAP_SRV_HEAD DC_SRV_TAIL) +
1256 	    1 /* for the dot between RR name and domainname */);
1257 
1258 	free(srvname);
1259 
1260 	if (dname == NULL) {
1261 		logger(LOG_ERR, "Out of memory");
1262 		return (NULL);
1263 	}
1264 
1265 	/* Eat any trailing dot */
1266 	len = strlen(dname);
1267 	if (len > 0 && dname[len - 1] == '.')
1268 		dname[len - 1] = '\0';
1269 
1270 	update_item(&ctx->domain_name, dname, AD_STATE_AUTO, ttl);
1271 
1272 	return (&ctx->domain_name);
1273 }
1274 
1275 
1276 char *
1277 ad_disc_get_DomainName(ad_disc_t ctx, boolean_t *auto_discovered)
1278 {
1279 	char *domain_name = NULL;
1280 	ad_item_t *domain_name_item;
1281 
1282 	domain_name_item = validate_DomainName(ctx);
1283 
1284 	if (domain_name_item) {
1285 		domain_name = strdup(domain_name_item->value);
1286 		if (auto_discovered != NULL)
1287 			*auto_discovered =
1288 			    (domain_name_item->state == AD_STATE_AUTO);
1289 	} else if (auto_discovered != NULL)
1290 		*auto_discovered = B_FALSE;
1291 
1292 	return (domain_name);
1293 }
1294 
1295 
1296 /* Discover domain controllers */
1297 static ad_item_t *
1298 validate_DomainController(ad_disc_t ctx, enum ad_disc_req req)
1299 {
1300 	uint32_t ttl = 0;
1301 	idmap_ad_disc_ds_t *domain_controller = NULL;
1302 	boolean_t validate_global = B_FALSE;
1303 	boolean_t validate_site = B_FALSE;
1304 	ad_item_t *domain_name_item;
1305 	ad_item_t *site_name_item = NULL;
1306 
1307 	/* If the values is fixed there will not be a site specific version */
1308 	if (is_fixed(&ctx->domain_controller))
1309 		return (&ctx->domain_controller);
1310 
1311 	domain_name_item = validate_DomainName(ctx);
1312 	if (domain_name_item == NULL)
1313 		return (NULL);
1314 
1315 	if (req == AD_DISC_GLOBAL)
1316 		validate_global = B_TRUE;
1317 	else {
1318 		site_name_item = validate_SiteName(ctx);
1319 		if (site_name_item != NULL)
1320 			validate_site = B_TRUE;
1321 		else if (req == AD_DISC_PREFER_SITE)
1322 			validate_global = B_TRUE;
1323 	}
1324 
1325 	if (validate_global) {
1326 		if (!is_valid(&ctx->domain_controller) ||
1327 		    is_changed(&ctx->domain_controller, PARAM1,
1328 		    domain_name_item)) {
1329 			/*
1330 			 * Lookup DNS SRV RR named
1331 			 * _ldap._tcp.dc._msdcs.<DomainName>
1332 			 */
1333 			DO_RES_NINIT(ctx);
1334 			domain_controller = srv_query(&ctx->res_state,
1335 			    LDAP_SRV_HEAD DC_SRV_TAIL,
1336 			    domain_name_item->value, NULL, &ttl);
1337 
1338 			if (domain_controller == NULL)
1339 				return (NULL);
1340 
1341 			update_item(&ctx->domain_controller, domain_controller,
1342 			    AD_STATE_AUTO, ttl);
1343 			update_version(&ctx->domain_controller, PARAM1,
1344 			    domain_name_item);
1345 		}
1346 		return (&ctx->domain_controller);
1347 	}
1348 
1349 	if (validate_site) {
1350 		if (!is_valid(&ctx->site_domain_controller) ||
1351 		    is_changed(&ctx->site_domain_controller, PARAM1,
1352 		    domain_name_item) ||
1353 		    is_changed(&ctx->site_domain_controller, PARAM2,
1354 		    site_name_item)) {
1355 			char rr_name[DNS_MAX_NAME];
1356 			/*
1357 			 * Lookup DNS SRV RR named
1358 			 * _ldap._tcp.<SiteName>._sites.dc._msdcs.<DomainName>
1359 			 */
1360 			(void) snprintf(rr_name, sizeof (rr_name),
1361 			    LDAP_SRV_HEAD SITE_SRV_MIDDLE DC_SRV_TAIL,
1362 			    site_name_item->value);
1363 			DO_RES_NINIT(ctx);
1364 			domain_controller = srv_query(&ctx->res_state, rr_name,
1365 			    domain_name_item->value, NULL, &ttl);
1366 			if (domain_controller == NULL)
1367 				return (NULL);
1368 
1369 			update_item(&ctx->site_domain_controller,
1370 			    domain_controller, AD_STATE_AUTO, ttl);
1371 			update_version(&ctx->site_domain_controller, PARAM1,
1372 			    domain_name_item);
1373 			update_version(&ctx->site_domain_controller, PARAM2,
1374 			    site_name_item);
1375 		}
1376 		return (&ctx->site_domain_controller);
1377 	}
1378 	return (NULL);
1379 }
1380 
1381 idmap_ad_disc_ds_t *
1382 ad_disc_get_DomainController(ad_disc_t ctx, enum ad_disc_req req,
1383 			boolean_t *auto_discovered)
1384 {
1385 	ad_item_t *domain_controller_item;
1386 	idmap_ad_disc_ds_t *domain_controller = NULL;
1387 
1388 	domain_controller_item = validate_DomainController(ctx, req);
1389 
1390 	if (domain_controller_item != NULL) {
1391 		domain_controller = ds_dup(domain_controller_item->value);
1392 		if (auto_discovered != NULL)
1393 			*auto_discovered =
1394 			    (domain_controller_item->state == AD_STATE_AUTO);
1395 	} else if (auto_discovered != NULL)
1396 		*auto_discovered = B_FALSE;
1397 
1398 	return (domain_controller);
1399 }
1400 
1401 
1402 /* Discover site name (for multi-homed systems the first one found wins) */
1403 static ad_item_t *
1404 validate_SiteName(ad_disc_t ctx)
1405 {
1406 	LDAP *ld = NULL;
1407 	ad_subnet_t *subnets = NULL;
1408 	char **dn_subnets = NULL;
1409 	char *dn_root[2];
1410 	char *config_naming_context = NULL;
1411 	char *site_object = NULL;
1412 	char *site_name = NULL;
1413 	char *forest_name;
1414 	int len;
1415 	int i;
1416 	boolean_t update_required = B_FALSE;
1417 	ad_item_t *domain_controller_item;
1418 
1419 	if (is_fixed(&ctx->site_name))
1420 		return (&ctx->site_name);
1421 
1422 	/* Can't rely on site-specific DCs */
1423 	domain_controller_item = validate_DomainController(ctx, AD_DISC_GLOBAL);
1424 	if (domain_controller_item == NULL)
1425 		return (NULL);
1426 
1427 	if (!is_valid(&ctx->site_name) ||
1428 	    is_changed(&ctx->site_name, PARAM1, &ctx->domain_controller) ||
1429 	    ctx->subnets == NULL || ctx->subnets_changed) {
1430 		subnets = find_subnets();
1431 		ctx->subnets_last_check = time(NULL);
1432 		update_required = B_TRUE;
1433 	} else if (ctx->subnets_last_check + 60 < time(NULL)) {
1434 		subnets = find_subnets();
1435 		ctx->subnets_last_check = time(NULL);
1436 		if (cmpsubnets(ctx->subnets, subnets) != 0)
1437 			update_required = B_TRUE;
1438 	}
1439 
1440 	if (!update_required) {
1441 		free(subnets);
1442 		return (&ctx->site_name);
1443 	}
1444 
1445 	if (subnets == NULL)
1446 		return (NULL);
1447 
1448 	dn_root[0] = "";
1449 	dn_root[1] = NULL;
1450 
1451 	config_naming_context = ldap_lookup_entry_attr(
1452 	    &ld, ctx->domain_controller.value,
1453 	    dn_root, "configurationNamingContext");
1454 	if (config_naming_context == NULL)
1455 		goto out;
1456 	/*
1457 	 * configurationNamingContext also provides the Forest
1458 	 * Name.
1459 	 */
1460 	if (!is_fixed(&ctx->forest_name)) {
1461 		/*
1462 		 * The configurationNamingContext should be of
1463 		 * form:
1464 		 * CN=Configuration,<DNforestName>
1465 		 * Remove the first part and convert to DNS form
1466 		 * (replace ",DC=" with ".")
1467 		 */
1468 		char *str = "CN=Configuration,";
1469 		int len = strlen(str);
1470 		if (strncasecmp(config_naming_context, str, len) == 0) {
1471 			forest_name = DN_to_DNS(config_naming_context + len);
1472 			update_item(&ctx->forest_name, forest_name,
1473 			    AD_STATE_AUTO, 0);
1474 		}
1475 	}
1476 
1477 	dn_subnets = subnets_to_DNs(subnets, config_naming_context);
1478 	if (dn_subnets == NULL)
1479 		goto out;
1480 
1481 	site_object = ldap_lookup_entry_attr(
1482 	    &ld, domain_controller_item->value,
1483 	    dn_subnets, "siteobject");
1484 	if (site_object != NULL) {
1485 		/*
1486 		 * The site object should be of the form
1487 		 * CN=<site>,CN=Sites,CN=Configuration,
1488 		 *		<DN Domain>
1489 		 */
1490 		if (strncasecmp(site_object, "CN=", 3) == 0) {
1491 			for (len = 0; site_object[len + 3] != ','; len++)
1492 					;
1493 			site_name = malloc(len + 1);
1494 			(void) strncpy(site_name, &site_object[3], len);
1495 			site_name[len] = '\0';
1496 			update_item(&ctx->site_name, site_name,
1497 			    AD_STATE_AUTO, 0);
1498 		}
1499 	}
1500 
1501 	if (ctx->subnets != NULL) {
1502 		free(ctx->subnets);
1503 		ctx->subnets = NULL;
1504 	}
1505 	ctx->subnets = subnets;
1506 	subnets = NULL;
1507 	ctx->subnets_changed = B_FALSE;
1508 
1509 out:
1510 	if (ld != NULL)
1511 		(void) ldap_unbind(ld);
1512 
1513 	if (dn_subnets != NULL) {
1514 		for (i = 0; dn_subnets[i] != NULL; i++)
1515 			free(dn_subnets[i]);
1516 		free(dn_subnets);
1517 	}
1518 	if (config_naming_context != NULL)
1519 		free(config_naming_context);
1520 	if (site_object != NULL)
1521 		free(site_object);
1522 
1523 	free(subnets);
1524 	if (site_name == NULL)
1525 		return (NULL);
1526 	return (&ctx->site_name);
1527 
1528 }
1529 
1530 
1531 char *
1532 ad_disc_get_SiteName(ad_disc_t ctx, boolean_t *auto_discovered)
1533 {
1534 	ad_item_t *site_name_item;
1535 	char	*site_name = NULL;
1536 
1537 	site_name_item = validate_SiteName(ctx);
1538 	if (site_name_item != NULL) {
1539 		site_name = strdup(site_name_item->value);
1540 		if (auto_discovered != NULL)
1541 			*auto_discovered =
1542 			    (site_name_item->state == AD_STATE_AUTO);
1543 	} else if (auto_discovered != NULL)
1544 		*auto_discovered = B_FALSE;
1545 
1546 	return (site_name);
1547 }
1548 
1549 
1550 
1551 /* Discover forest name */
1552 static ad_item_t *
1553 validate_ForestName(ad_disc_t ctx)
1554 {
1555 	LDAP	*ld = NULL;
1556 	char	*config_naming_context;
1557 	char	*forest_name = NULL;
1558 	char	*dn_list[2];
1559 	ad_item_t *domain_controller_item;
1560 
1561 	if (is_fixed(&ctx->forest_name))
1562 		return (&ctx->forest_name);
1563 	/*
1564 	 * We may not have a site name yet, so we won't rely on
1565 	 * site-specific DCs.  (But maybe we could replace
1566 	 * validate_ForestName() with validate_siteName()?)
1567 	 */
1568 	domain_controller_item = validate_DomainController(ctx, AD_DISC_GLOBAL);
1569 	if (domain_controller_item == NULL)
1570 		return (NULL);
1571 
1572 	if (!is_valid(&ctx->forest_name) ||
1573 	    is_changed(&ctx->forest_name, PARAM1, domain_controller_item)) {
1574 
1575 		dn_list[0] = "";
1576 		dn_list[1] = NULL;
1577 		config_naming_context = ldap_lookup_entry_attr(
1578 		    &ld, ctx->domain_controller.value,
1579 		    dn_list, "configurationNamingContext");
1580 		if (config_naming_context != NULL) {
1581 			/*
1582 			 * The configurationNamingContext should be of
1583 			 * form:
1584 			 * CN=Configuration,<DNforestName>
1585 			 * Remove the first part and convert to DNS form
1586 			 * (replace ",DC=" with ".")
1587 			 */
1588 			char *str = "CN=Configuration,";
1589 			int len = strlen(str);
1590 			if (strncasecmp(config_naming_context, str, len) == 0) {
1591 				forest_name = DN_to_DNS(
1592 				    config_naming_context + len);
1593 			}
1594 			free(config_naming_context);
1595 		}
1596 		if (ld != NULL)
1597 			(void) ldap_unbind(ld);
1598 
1599 		if (forest_name == NULL)
1600 			return (NULL);
1601 
1602 		update_item(&ctx->forest_name, forest_name, AD_STATE_AUTO, 0);
1603 		update_version(&ctx->forest_name, PARAM1,
1604 		    domain_controller_item);
1605 	}
1606 	return (&ctx->forest_name);
1607 }
1608 
1609 
1610 char *
1611 ad_disc_get_ForestName(ad_disc_t ctx, boolean_t *auto_discovered)
1612 {
1613 	ad_item_t *forest_name_item;
1614 	char	*forest_name = NULL;
1615 
1616 	forest_name_item = validate_ForestName(ctx);
1617 
1618 	if (forest_name_item != NULL) {
1619 		forest_name = strdup(forest_name_item->value);
1620 		if (auto_discovered != NULL)
1621 			*auto_discovered =
1622 			    (forest_name_item->state == AD_STATE_AUTO);
1623 	} else if (auto_discovered != NULL)
1624 		*auto_discovered = B_FALSE;
1625 
1626 	return (forest_name);
1627 }
1628 
1629 
1630 /* Discover global catalog servers */
1631 static ad_item_t *
1632 validate_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req)
1633 {
1634 	idmap_ad_disc_ds_t *global_catalog = NULL;
1635 	uint32_t ttl = 0;
1636 	boolean_t validate_global = B_FALSE;
1637 	boolean_t validate_site = B_FALSE;
1638 	ad_item_t *forest_name_item;
1639 	ad_item_t *site_name_item;
1640 
1641 	/* If the values is fixed there will not be a site specific version */
1642 	if (is_fixed(&ctx->global_catalog))
1643 		return (&ctx->global_catalog);
1644 
1645 	forest_name_item = validate_ForestName(ctx);
1646 	if (forest_name_item == NULL)
1647 		return (NULL);
1648 
1649 	if (req == AD_DISC_GLOBAL)
1650 		validate_global = B_TRUE;
1651 	else {
1652 		site_name_item = validate_SiteName(ctx);
1653 		if (site_name_item != NULL)
1654 			validate_site = B_TRUE;
1655 		else if (req == AD_DISC_PREFER_SITE)
1656 			validate_global = B_TRUE;
1657 	}
1658 
1659 	if (validate_global) {
1660 		if (!is_valid(&ctx->global_catalog) ||
1661 		    is_changed(&ctx->global_catalog, PARAM1,
1662 		    forest_name_item)) {
1663 			/*
1664 			 * Lookup DNS SRV RR named
1665 			 * _ldap._tcp.gc._msdcs.<ForestName>
1666 			 */
1667 			DO_RES_NINIT(ctx);
1668 			global_catalog =
1669 			    srv_query(&ctx->res_state,
1670 			    LDAP_SRV_HEAD GC_SRV_TAIL,
1671 			    ctx->forest_name.value, NULL, &ttl);
1672 
1673 			if (global_catalog == NULL)
1674 				return (NULL);
1675 
1676 			update_item(&ctx->global_catalog, global_catalog,
1677 			    AD_STATE_AUTO, ttl);
1678 			update_version(&ctx->global_catalog, PARAM1,
1679 			    forest_name_item);
1680 		}
1681 		return (&ctx->global_catalog);
1682 	}
1683 
1684 	if (validate_site) {
1685 		if (!is_valid(&ctx->site_global_catalog) ||
1686 		    is_changed(&ctx->site_global_catalog, PARAM1,
1687 		    forest_name_item) ||
1688 		    is_changed(&ctx->site_global_catalog, PARAM2,
1689 		    site_name_item)) {
1690 			char 	rr_name[DNS_MAX_NAME];
1691 
1692 			/*
1693 			 * Lookup DNS SRV RR named:
1694 			 * _ldap._tcp.<siteName>._sites.gc.
1695 			 *	_msdcs.<ForestName>
1696 			 */
1697 			(void) snprintf(rr_name,
1698 			    sizeof (rr_name),
1699 			    LDAP_SRV_HEAD SITE_SRV_MIDDLE GC_SRV_TAIL,
1700 			    ctx->site_name.value);
1701 			DO_RES_NINIT(ctx);
1702 			global_catalog = srv_query(&ctx->res_state, rr_name,
1703 			    ctx->forest_name.value, NULL, &ttl);
1704 
1705 			if (global_catalog == NULL)
1706 				return (NULL);
1707 			update_item(&ctx->site_global_catalog, global_catalog,
1708 			    AD_STATE_AUTO, ttl);
1709 			update_version(&ctx->site_global_catalog, PARAM1,
1710 			    forest_name_item);
1711 			update_version(&ctx->site_global_catalog, PARAM2,
1712 			    site_name_item);
1713 		}
1714 		return (&ctx->site_global_catalog);
1715 	}
1716 	return (NULL);
1717 }
1718 
1719 
1720 idmap_ad_disc_ds_t *
1721 ad_disc_get_GlobalCatalog(ad_disc_t ctx, enum ad_disc_req req,
1722 			boolean_t *auto_discovered)
1723 {
1724 	idmap_ad_disc_ds_t *global_catalog = NULL;
1725 	ad_item_t *global_catalog_item;
1726 
1727 	global_catalog_item = validate_GlobalCatalog(ctx, req);
1728 
1729 	if (global_catalog_item != NULL) {
1730 		global_catalog = ds_dup(global_catalog_item->value);
1731 		if (auto_discovered != NULL)
1732 			*auto_discovered =
1733 			    (global_catalog_item->state == AD_STATE_AUTO);
1734 	} else if (auto_discovered != NULL)
1735 		*auto_discovered = B_FALSE;
1736 
1737 	return (global_catalog);
1738 }
1739 
1740 
1741 static ad_item_t *
1742 validate_TrustedDomains(ad_disc_t ctx)
1743 {
1744 	LDAP *ld = NULL;
1745 	ad_item_t *global_catalog_item;
1746 	ad_item_t *forest_name_item;
1747 	ad_disc_trusteddomains_t *trusted_domains;
1748 	char *dn = NULL;
1749 	char *forest_name_dn;
1750 	int len;
1751 	int num_parts;
1752 
1753 	if (is_fixed(&ctx->trusted_domains))
1754 		return (&ctx->trusted_domains);
1755 
1756 	global_catalog_item = validate_GlobalCatalog(ctx, AD_DISC_GLOBAL);
1757 	if (global_catalog_item == NULL)
1758 		return (NULL);
1759 
1760 	forest_name_item = validate_ForestName(ctx);
1761 	if (forest_name_item == NULL)
1762 		return (NULL);
1763 
1764 	if (!is_valid(&ctx->trusted_domains) ||
1765 	    is_changed(&ctx->trusted_domains, PARAM1, global_catalog_item) ||
1766 	    is_changed(&ctx->trusted_domains, PARAM2, forest_name_item)) {
1767 
1768 		forest_name_dn = ldap_dns_to_dn(forest_name_item->value,
1769 		    &num_parts);
1770 		if (forest_name_dn == NULL)
1771 			return (NULL);
1772 
1773 		len = snprintf(NULL, 0, "CN=System,%s", forest_name_dn) + 1;
1774 		dn = malloc(len);
1775 		if (dn == NULL)  {
1776 			free(forest_name_dn);
1777 			return (NULL);
1778 		}
1779 		(void) snprintf(dn, len, "CN=System,%s", forest_name_dn);
1780 		free(forest_name_dn);
1781 
1782 		trusted_domains = ldap_lookup_trusted_domains(
1783 		    &ld, global_catalog_item->value, dn);
1784 
1785 		if (ld != NULL)
1786 			(void) ldap_unbind(ld);
1787 		free(dn);
1788 
1789 		if (trusted_domains == NULL)
1790 			return (NULL);
1791 
1792 		update_item(&ctx->trusted_domains, trusted_domains,
1793 		    AD_STATE_AUTO, 0);
1794 		update_version(&ctx->trusted_domains, PARAM1,
1795 		    global_catalog_item);
1796 		update_version(&ctx->trusted_domains, PARAM2,
1797 		    forest_name_item);
1798 	}
1799 
1800 	return (&ctx->trusted_domains);
1801 }
1802 
1803 
1804 ad_disc_trusteddomains_t *
1805 ad_disc_get_TrustedDomains(ad_disc_t ctx, boolean_t *auto_discovered)
1806 {
1807 	ad_disc_trusteddomains_t *trusted_domains = NULL;
1808 	ad_item_t *trusted_domains_item;
1809 
1810 	trusted_domains_item = validate_TrustedDomains(ctx);
1811 
1812 	if (trusted_domains_item != NULL) {
1813 		trusted_domains = td_dup(trusted_domains_item->value);
1814 		if (auto_discovered != NULL)
1815 			*auto_discovered =
1816 			    (trusted_domains_item->state == AD_STATE_AUTO);
1817 	} else if (auto_discovered != NULL)
1818 		*auto_discovered = B_FALSE;
1819 
1820 	return (trusted_domains);
1821 }
1822 
1823 
1824 static ad_item_t *
1825 validate_DomainsInForest(ad_disc_t ctx)
1826 {
1827 	ad_item_t *global_catalog_item;
1828 	LDAP *ld = NULL;
1829 	ad_disc_domainsinforest_t *domains_in_forest;
1830 
1831 	if (is_fixed(&ctx->domains_in_forest))
1832 		return (&ctx->domains_in_forest);
1833 
1834 	global_catalog_item = validate_GlobalCatalog(ctx, AD_DISC_GLOBAL);
1835 	if (global_catalog_item == NULL)
1836 		return (NULL);
1837 
1838 	if (!is_valid(&ctx->domains_in_forest) ||
1839 	    is_changed(&ctx->domains_in_forest, PARAM1, global_catalog_item)) {
1840 
1841 		domains_in_forest = ldap_lookup_domains_in_forest(
1842 		    &ld, global_catalog_item->value);
1843 
1844 		if (ld != NULL)
1845 			(void) ldap_unbind(ld);
1846 
1847 		if (domains_in_forest == NULL)
1848 			return (NULL);
1849 
1850 		update_item(&ctx->domains_in_forest, domains_in_forest,
1851 		    AD_STATE_AUTO, 0);
1852 		update_version(&ctx->domains_in_forest, PARAM1,
1853 		    global_catalog_item);
1854 	}
1855 	return (&ctx->domains_in_forest);
1856 }
1857 
1858 
1859 ad_disc_domainsinforest_t *
1860 ad_disc_get_DomainsInForest(ad_disc_t ctx, boolean_t *auto_discovered)
1861 {
1862 	ad_disc_domainsinforest_t *domains_in_forest = NULL;
1863 	ad_item_t *domains_in_forest_item;
1864 
1865 	domains_in_forest_item = validate_DomainsInForest(ctx);
1866 
1867 	if (domains_in_forest_item != NULL) {
1868 		domains_in_forest = df_dup(domains_in_forest_item->value);
1869 		if (auto_discovered != NULL)
1870 			*auto_discovered =
1871 			    (domains_in_forest_item->state == AD_STATE_AUTO);
1872 	} else if (auto_discovered != NULL)
1873 		*auto_discovered = B_FALSE;
1874 
1875 	return (domains_in_forest);
1876 }
1877 
1878 
1879 
1880 
1881 int
1882 ad_disc_set_DomainName(ad_disc_t ctx, const char *domainName)
1883 {
1884 	char *domain_name = NULL;
1885 	if (domainName != NULL) {
1886 		domain_name = strdup(domainName);
1887 		if (domain_name == NULL)
1888 			return (-1);
1889 		update_item(&ctx->domain_name, domain_name,
1890 		    AD_STATE_FIXED, 0);
1891 	} else if (ctx->domain_name.state == AD_STATE_FIXED)
1892 		ctx->domain_name.state = AD_STATE_INVALID;
1893 	return (0);
1894 }
1895 
1896 
1897 int
1898 ad_disc_set_DomainController(ad_disc_t ctx,
1899 				const idmap_ad_disc_ds_t *domainController)
1900 {
1901 	idmap_ad_disc_ds_t *domain_controller = NULL;
1902 	if (domainController != NULL) {
1903 		domain_controller = ds_dup(domainController);
1904 		if (domain_controller == NULL)
1905 			return (-1);
1906 		update_item(&ctx->domain_controller, domain_controller,
1907 		    AD_STATE_FIXED, 0);
1908 	} else if (ctx->domain_controller.state == AD_STATE_FIXED)
1909 		ctx->domain_controller.state = AD_STATE_INVALID;
1910 	return (0);
1911 }
1912 
1913 
1914 int
1915 ad_disc_set_SiteName(ad_disc_t ctx, const char *siteName)
1916 {
1917 	char *site_name = NULL;
1918 	if (siteName != NULL) {
1919 		site_name = strdup(siteName);
1920 		if (site_name == NULL)
1921 			return (-1);
1922 		update_item(&ctx->site_name, site_name, AD_STATE_FIXED, 0);
1923 	} else if (ctx->site_name.state == AD_STATE_FIXED)
1924 		ctx->site_name.state = AD_STATE_INVALID;
1925 	return (0);
1926 }
1927 
1928 int
1929 ad_disc_set_ForestName(ad_disc_t ctx, const char *forestName)
1930 {
1931 	char *forest_name = NULL;
1932 	if (forestName != NULL) {
1933 		forest_name = strdup(forestName);
1934 		if (forest_name == NULL)
1935 			return (-1);
1936 		update_item(&ctx->forest_name, forest_name,
1937 		    AD_STATE_FIXED, 0);
1938 	} else if (ctx->forest_name.state == AD_STATE_FIXED)
1939 		ctx->forest_name.state = AD_STATE_INVALID;
1940 	return (0);
1941 }
1942 
1943 int
1944 ad_disc_set_GlobalCatalog(ad_disc_t ctx,
1945     const idmap_ad_disc_ds_t *globalCatalog)
1946 {
1947 	idmap_ad_disc_ds_t *global_catalog = NULL;
1948 	if (globalCatalog != NULL) {
1949 		global_catalog = ds_dup(globalCatalog);
1950 		if (global_catalog == NULL)
1951 			return (-1);
1952 		update_item(&ctx->global_catalog, global_catalog,
1953 		    AD_STATE_FIXED, 0);
1954 	} else if (ctx->global_catalog.state == AD_STATE_FIXED)
1955 		ctx->global_catalog.state = AD_STATE_INVALID;
1956 	return (0);
1957 }
1958 
1959 
1960 int
1961 ad_disc_unset(ad_disc_t ctx)
1962 {
1963 	if (ctx->domain_name.state == AD_STATE_FIXED)
1964 		ctx->domain_name.state =  AD_STATE_INVALID;
1965 
1966 	if (ctx->domain_controller.state == AD_STATE_FIXED)
1967 		ctx->domain_controller.state =  AD_STATE_INVALID;
1968 
1969 	if (ctx->site_name.state == AD_STATE_FIXED)
1970 		ctx->site_name.state =  AD_STATE_INVALID;
1971 
1972 	if (ctx->forest_name.state == AD_STATE_FIXED)
1973 		ctx->forest_name.state =  AD_STATE_INVALID;
1974 
1975 	if (ctx->global_catalog.state == AD_STATE_FIXED)
1976 		ctx->global_catalog.state =  AD_STATE_INVALID;
1977 
1978 	return (0);
1979 }
1980 
1981 /*
1982  * ad_disc_get_TTL
1983  *
1984  * This routines the time to live for AD
1985  * auto discovered items.
1986  *
1987  *	Returns:
1988  *		-1 if there are no TTL items
1989  *		0  if there are expired items
1990  *		else the number of seconds
1991  *
1992  * The MIN_GT_ZERO(x, y) macro return the lesser of x and y, provided it
1993  * is positive -- min() greater than zero.
1994  */
1995 #define	MIN_GT_ZERO(x, y) (((x) <= 0) ? (((y) <= 0) ? \
1996 		(-1) : (y)) : (((y) <= 0) ? (x) : (((x) > (y)) ? (y) : (x))))
1997 int
1998 ad_disc_get_TTL(ad_disc_t ctx)
1999 {
2000 	int ttl;
2001 
2002 	ttl = MIN_GT_ZERO(ctx->domain_controller.ttl, ctx->global_catalog.ttl);
2003 	ttl = MIN_GT_ZERO(ttl, ctx->site_domain_controller.ttl);
2004 	ttl = MIN_GT_ZERO(ttl, ctx->site_global_catalog.ttl);
2005 
2006 	if (ttl == -1)
2007 		return (-1);
2008 	ttl -= time(NULL);
2009 	if (ttl < 0)
2010 		return (0);
2011 	return (ttl);
2012 }
2013 
2014 boolean_t
2015 ad_disc_SubnetChanged(ad_disc_t ctx)
2016 {
2017 	ad_subnet_t *subnets;
2018 
2019 	if (ctx->subnets_changed || ctx->subnets == NULL)
2020 		return (B_TRUE);
2021 
2022 	if ((subnets = find_subnets()) != NULL) {
2023 		if (cmpsubnets(subnets, ctx->subnets) != 0)
2024 			ctx->subnets_changed = B_TRUE;
2025 		free(subnets);
2026 	}
2027 
2028 	return (ctx->subnets_changed);
2029 }
2030