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