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