xref: /illumos-gate/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c (revision fb2a9bae0030340ad72b9c26ba1ffee2ee3cafec)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/param.h>
27 #include <ldap.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <sys/time.h>
34 #include <netdb.h>
35 #include <pthread.h>
36 #include <unistd.h>
37 #include <arpa/nameser.h>
38 #include <resolv.h>
39 #include <sys/synch.h>
40 #include <string.h>
41 #include <strings.h>
42 #include <fcntl.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <assert.h>
46 #include <sasl/sasl.h>
47 #include <note.h>
48 #include <errno.h>
49 #include <cryptoutil.h>
50 
51 #include <smbsrv/libsmbns.h>
52 #include <smbns_dyndns.h>
53 #include <smbns_krb.h>
54 
55 #define	SMB_ADS_AF_UNKNOWN(x)	(((x)->ipaddr.a_family != AF_INET) && \
56 	((x)->ipaddr.a_family != AF_INET6))
57 
58 #define	SMB_ADS_MAXBUFLEN 100
59 #define	SMB_ADS_DN_MAX	300
60 #define	SMB_ADS_MAXMSGLEN 512
61 #define	SMB_ADS_COMPUTERS_CN "Computers"
62 #define	SMB_ADS_COMPUTER_NUM_ATTR 8
63 #define	SMB_ADS_SHARE_NUM_ATTR 3
64 #define	SMB_ADS_SITE_MAX MAXHOSTNAMELEN
65 
66 /*
67  * [MS-DISO] A machine password is an ASCII string of randomly chosen
68  * characters. Each character's ASCII code is between 32 and 122 inclusive.
69  */
70 #define	SMB_ADS_PWD_CHAR_NUM	91
71 #define	SMB_ADS_PWD_CHAR_START	32
72 
73 #define	SMB_ADS_MSDCS_SRV_DC_RR		"_ldap._tcp.dc._msdcs"
74 #define	SMB_ADS_MSDCS_SRV_SITE_RR	"_ldap._tcp.%s._sites.dc._msdcs"
75 
76 /*
77  * domainControllerFunctionality
78  *
79  * This rootDSE attribute indicates the functional level of the DC.
80  */
81 #define	SMB_ADS_ATTR_DCLEVEL	"domainControllerFunctionality"
82 #define	SMB_ADS_DCLEVEL_W2K	0
83 #define	SMB_ADS_DCLEVEL_W2K3	2
84 #define	SMB_ADS_DCLEVEL_W2K8	3
85 #define	SMB_ADS_DCLEVEL_W2K8_R2 4
86 
87 /*
88  * msDs-supportedEncryptionTypes (Windows Server 2008 only)
89  *
90  * This attribute defines the encryption types supported by the system.
91  * Encryption Types:
92  *  - DES cbc mode with CRC-32
93  *  - DES cbc mode with RSA-MD5
94  *  - ArcFour with HMAC/md5
95  *  - AES-128
96  *  - AES-256
97  */
98 #define	SMB_ADS_ATTR_ENCTYPES	"msDs-supportedEncryptionTypes"
99 #define	SMB_ADS_ENC_DES_CRC	1
100 #define	SMB_ADS_ENC_DES_MD5	2
101 #define	SMB_ADS_ENC_RC4		4
102 #define	SMB_ADS_ENC_AES128	8
103 #define	SMB_ADS_ENC_AES256	16
104 
105 #define	SMB_ADS_ATTR_SAMACCT	"sAMAccountName"
106 #define	SMB_ADS_ATTR_UPN	"userPrincipalName"
107 #define	SMB_ADS_ATTR_SPN	"servicePrincipalName"
108 #define	SMB_ADS_ATTR_CTL	"userAccountControl"
109 #define	SMB_ADS_ATTR_DNSHOST	"dNSHostName"
110 #define	SMB_ADS_ATTR_KVNO	"msDS-KeyVersionNumber"
111 #define	SMB_ADS_ATTR_DN		"distinguishedName"
112 
113 /*
114  * UserAccountControl flags: manipulate user account properties.
115  *
116  * The hexadecimal value of the following property flags are based on MSDN
117  * article # 305144.
118  */
119 #define	SMB_ADS_USER_ACCT_CTL_SCRIPT				0x00000001
120 #define	SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE			0x00000002
121 #define	SMB_ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED			0x00000008
122 #define	SMB_ADS_USER_ACCT_CTL_LOCKOUT				0x00000010
123 #define	SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD			0x00000020
124 #define	SMB_ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE		0x00000040
125 #define	SMB_ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED	0x00000080
126 #define	SMB_ADS_USER_ACCT_CTL_TMP_DUP_ACCT			0x00000100
127 #define	SMB_ADS_USER_ACCT_CTL_NORMAL_ACCT			0x00000200
128 #define	SMB_ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT		0x00000800
129 #define	SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT		0x00001000
130 #define	SMB_ADS_USER_ACCT_CTL_SRV_TRUST_ACCT			0x00002000
131 #define	SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD		0x00010000
132 #define	SMB_ADS_USER_ACCT_CTL_MNS_LOGON_ACCT			0x00020000
133 #define	SMB_ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED		0x00040000
134 #define	SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION		0x00080000
135 #define	SMB_ADS_USER_ACCT_CTL_NOT_DELEGATED			0x00100000
136 #define	SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY			0x00200000
137 #define	SMB_ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH			0x00400000
138 #define	SMB_ADS_USER_ACCT_CTL_PASSWD_EXPIRED			0x00800000
139 #define	SMB_ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION	0x01000000
140 
141 /*
142  * Length of "dc=" prefix.
143  */
144 #define	SMB_ADS_DN_PREFIX_LEN	3
145 
146 static char *smb_ads_computer_objcls[] = {
147 	"top", "person", "organizationalPerson",
148 	"user", "computer", NULL
149 };
150 
151 static char *smb_ads_share_objcls[] = {
152 	"top", "leaf", "connectionPoint", "volume", NULL
153 };
154 
155 /* Cached ADS server to communicate with */
156 static smb_ads_host_info_t *smb_ads_cached_host_info = NULL;
157 static mutex_t smb_ads_cached_host_mtx;
158 
159 /*
160  * SMB ADS config cache is maintained to facilitate the detection of
161  * changes in configuration that is relevant to AD selection.
162  */
163 typedef struct smb_ads_config {
164 	char c_site[SMB_ADS_SITE_MAX];
165 	smb_inaddr_t c_pdc;
166 	mutex_t c_mtx;
167 } smb_ads_config_t;
168 
169 static smb_ads_config_t smb_ads_cfg;
170 
171 
172 /* attribute/value pair */
173 typedef struct smb_ads_avpair {
174 	char *avp_attr;
175 	char *avp_val;
176 } smb_ads_avpair_t;
177 
178 /* query status */
179 typedef enum smb_ads_qstat {
180 	SMB_ADS_STAT_ERR = -2,
181 	SMB_ADS_STAT_DUP,
182 	SMB_ADS_STAT_NOT_FOUND,
183 	SMB_ADS_STAT_FOUND
184 } smb_ads_qstat_t;
185 
186 typedef struct smb_ads_host_list {
187 	int ah_cnt;
188 	smb_ads_host_info_t *ah_list;
189 } smb_ads_host_list_t;
190 
191 static smb_ads_handle_t *smb_ads_open_main(char *, char *, char *);
192 static int smb_ads_add_computer(smb_ads_handle_t *, int, char *);
193 static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *);
194 static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *);
195 static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *,
196     smb_ads_avpair_t *, int, char *);
197 static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *);
198 static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *);
199 static int smb_ads_gen_machine_passwd(char *, size_t);
200 static void smb_ads_free_cached_host(void);
201 static int smb_ads_get_spnset(char *, char **);
202 static void smb_ads_free_spnset(char **);
203 static int smb_ads_alloc_attr(LDAPMod **, int);
204 static void smb_ads_free_attr(LDAPMod **);
205 static int smb_ads_get_dc_level(smb_ads_handle_t *);
206 static smb_ads_host_info_t *smb_ads_select_dc(smb_ads_host_list_t *);
207 static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *, char *);
208 static smb_ads_qstat_t smb_ads_getattr(LDAP *, LDAPMessage *,
209     smb_ads_avpair_t *);
210 static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *, LDAPMessage *,
211     smb_ads_avpair_t *);
212 static boolean_t smb_ads_match_pdc(smb_ads_host_info_t *);
213 static boolean_t smb_ads_is_sought_host(smb_ads_host_info_t *, char *);
214 static boolean_t smb_ads_is_same_domain(char *, char *);
215 static boolean_t smb_ads_is_pdc_configured(void);
216 static smb_ads_host_info_t *smb_ads_dup_host_info(smb_ads_host_info_t *);
217 static char *smb_ads_get_sharedn(const char *, const char *, const char *);
218 
219 /*
220  * smb_ads_init
221  *
222  * Initializes the ADS config cache.
223  */
224 void
225 smb_ads_init(void)
226 {
227 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
228 	(void) smb_config_getstr(SMB_CI_ADS_SITE,
229 	    smb_ads_cfg.c_site, SMB_ADS_SITE_MAX);
230 	(void) smb_config_getip(SMB_CI_DOMAIN_SRV, &smb_ads_cfg.c_pdc);
231 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
232 }
233 
234 void
235 smb_ads_fini(void)
236 {
237 	smb_ads_free_cached_host();
238 }
239 
240 /*
241  * smb_ads_refresh
242  *
243  * This function will be called when smb/server SMF service is refreshed.
244  * Clearing the smb_ads_cached_host_info would allow the next DC
245  * discovery process to pick up an AD based on the new AD configuration.
246  */
247 void
248 smb_ads_refresh(void)
249 {
250 	char new_site[SMB_ADS_SITE_MAX];
251 	smb_inaddr_t new_pdc;
252 	boolean_t purge = B_FALSE;
253 
254 	(void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX);
255 	(void) smb_config_getip(SMB_CI_DOMAIN_SRV, &new_pdc);
256 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
257 	if (smb_strcasecmp(smb_ads_cfg.c_site, new_site, 0)) {
258 		(void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX);
259 		purge = B_TRUE;
260 	}
261 
262 	smb_ads_cfg.c_pdc = new_pdc;
263 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
264 
265 	(void) mutex_lock(&smb_ads_cached_host_mtx);
266 	if (smb_ads_cached_host_info &&
267 	    smb_ads_is_pdc_configured() &&
268 	    !smb_ads_match_pdc(smb_ads_cached_host_info))
269 		purge = B_TRUE;
270 	(void) mutex_unlock(&smb_ads_cached_host_mtx);
271 
272 	if (purge)
273 		smb_ads_free_cached_host();
274 }
275 
276 
277 
278 static boolean_t
279 smb_ads_is_pdc_configured(void)
280 {
281 	boolean_t configured;
282 
283 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
284 	configured = !smb_inet_iszero(&smb_ads_cfg.c_pdc);
285 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
286 
287 	return (configured);
288 }
289 
290 /*
291  * smb_ads_build_unc_name
292  *
293  * Construct the UNC name of the share object in the format of
294  * \\hostname.domain\shareUNC
295  *
296  * Returns 0 on success, -1 on error.
297  */
298 int
299 smb_ads_build_unc_name(char *unc_name, int maxlen,
300     const char *hostname, const char *shareUNC)
301 {
302 	char my_domain[MAXHOSTNAMELEN];
303 
304 	if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0)
305 		return (-1);
306 
307 	(void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s",
308 	    hostname, my_domain, shareUNC);
309 	return (0);
310 }
311 
312 /*
313  * smb_ads_ldap_ping
314  *
315  * This is used to bind to an ADS server to see
316  * if it is still alive.
317  *
318  * Returns:
319  *   -1: error
320  *    0: successful
321  */
322 /*ARGSUSED*/
323 static int
324 smb_ads_ldap_ping(smb_ads_host_info_t *ads_host)
325 {
326 	int ldversion = LDAP_VERSION3, status, timeoutms = 5 * 1000;
327 	LDAP *ld = NULL;
328 
329 	ld = ldap_init(ads_host->name, ads_host->port);
330 	if (ld == NULL)
331 		return (-1);
332 
333 	ldversion = LDAP_VERSION3;
334 	(void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion);
335 	/* setup TCP/IP connect timeout */
336 	(void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms);
337 
338 	status = ldap_bind_s(ld, "", NULL, LDAP_AUTH_SIMPLE);
339 
340 	if (status != LDAP_SUCCESS) {
341 		(void) ldap_unbind(ld);
342 		return (-1);
343 	}
344 
345 	(void) ldap_unbind(ld);
346 
347 	return (0);
348 }
349 
350 /*
351  * The cached ADS host is no longer valid if one of the following criteria
352  * is satisfied:
353  *
354  * 1) not in the specified domain
355  * 2) not the sought host (if specified)
356  * 3) not reachable
357  *
358  * The caller is responsible for acquiring the smb_ads_cached_host_mtx lock
359  * prior to calling this function.
360  *
361  * Return B_TRUE if the cache host is still valid. Otherwise, return B_FALSE.
362  */
363 static boolean_t
364 smb_ads_validate_cache_host(char *domain, char *srv)
365 {
366 	if (!smb_ads_cached_host_info)
367 		return (B_FALSE);
368 
369 	if (!smb_ads_is_same_domain(smb_ads_cached_host_info->name, domain))
370 		return (B_FALSE);
371 
372 	if (smb_ads_ldap_ping(smb_ads_cached_host_info) == 0) {
373 		if (!srv)
374 			return (B_TRUE);
375 
376 		if (smb_ads_is_sought_host(smb_ads_cached_host_info, srv))
377 			return (B_TRUE);
378 	}
379 
380 	return (B_FALSE);
381 }
382 
383 /*
384  * smb_ads_is_sought_host
385  *
386  * Returns true, if the sought host name matches the input host (host) name.
387  * The sought host is expected to be in Fully Qualified Domain Name (FQDN)
388  * format.
389  */
390 static boolean_t
391 smb_ads_is_sought_host(smb_ads_host_info_t *host, char *sought_host_name)
392 {
393 	if ((host == NULL) || (sought_host_name == NULL))
394 		return (B_FALSE);
395 
396 	if (smb_strcasecmp(host->name, sought_host_name, 0))
397 		return (B_FALSE);
398 
399 	return (B_TRUE);
400 }
401 
402 /*
403  * smb_ads_match_hosts_same_domain
404  *
405  * Returns true, if the cached ADS host is in the same domain as the
406  * current (given) domain.
407  */
408 static boolean_t
409 smb_ads_is_same_domain(char *cached_host_name, char *current_domain)
410 {
411 	char *cached_host_domain;
412 
413 	if ((cached_host_name == NULL) || (current_domain == NULL))
414 		return (B_FALSE);
415 
416 	cached_host_domain = strchr(cached_host_name, '.');
417 	if (cached_host_domain == NULL)
418 		return (B_FALSE);
419 
420 	++cached_host_domain;
421 	if (smb_strcasecmp(cached_host_domain, current_domain, 0))
422 		return (B_FALSE);
423 
424 	return (B_TRUE);
425 }
426 
427 /*
428  * smb_ads_skip_ques_sec
429  * Skips the question section.
430  */
431 static int
432 smb_ads_skip_ques_sec(int qcnt, uchar_t **ptr, uchar_t *eom)
433 {
434 	int i, len;
435 
436 	for (i = 0; i < qcnt; i++) {
437 		if ((len = dn_skipname(*ptr, eom)) < 0)
438 			return (-1);
439 
440 		*ptr += len + QFIXEDSZ;
441 	}
442 
443 	return (0);
444 }
445 
446 /*
447  * smb_ads_decode_host_ans_sec
448  * Decodes ADS hosts, priority, weight and port number from the answer
449  * section based on the current buffer pointer.
450  */
451 static int
452 smb_ads_decode_host_ans_sec(int ans_cnt, uchar_t **ptr, uchar_t *eom,
453     uchar_t *buf, smb_ads_host_info_t *ads_host_list)
454 {
455 	int i, len;
456 	smb_ads_host_info_t *ads_host;
457 
458 	for (i = 0; i < ans_cnt; i++) {
459 		ads_host = &ads_host_list[i];
460 
461 		if ((len = dn_skipname(*ptr, eom)) < 0)
462 			return (-1);
463 
464 
465 		*ptr += len;
466 
467 		/* skip type, class, ttl */
468 		*ptr += 8;
469 		/* data size */
470 		*ptr += 2;
471 
472 		/* Get priority, weight */
473 		/* LINTED: E_CONSTANT_CONDITION */
474 		NS_GET16(ads_host->priority, *ptr);
475 		/* LINTED: E_CONSTANT_CONDITION */
476 		NS_GET16(ads_host->weight, *ptr);
477 
478 		/* port */
479 		/* LINTED: E_CONSTANT_CONDITION */
480 		NS_GET16(ads_host->port, *ptr);
481 		/* domain name */
482 		len = dn_expand(buf, eom, *ptr, ads_host->name, MAXHOSTNAMELEN);
483 		if (len < 0)
484 			return (-1);
485 
486 		*ptr += len;
487 	}
488 
489 	return (0);
490 }
491 
492 /*
493  * smb_ads_skip_auth_sec
494  * Skips the authority section.
495  */
496 static int
497 smb_ads_skip_auth_sec(int ns_cnt, uchar_t **ptr, uchar_t *eom)
498 {
499 	int i, len;
500 	uint16_t size;
501 
502 	for (i = 0; i < ns_cnt; i++) {
503 		if ((len = dn_skipname(*ptr, eom)) < 0)
504 			return (-1);
505 
506 		*ptr += len;
507 		/* skip type, class, ttl */
508 		*ptr += 8;
509 		/* get len of data */
510 		/* LINTED: E_CONSTANT_CONDITION */
511 		NS_GET16(size, *ptr);
512 		if ((*ptr + size) > eom)
513 			return (-1);
514 
515 		*ptr += size;
516 	}
517 
518 	return (0);
519 }
520 
521 /*
522  * smb_ads_decode_host_ip
523  *
524  * Decodes ADS hosts and IP Addresses from the additional section based
525  * on the current buffer pointer.
526  */
527 static int
528 smb_ads_decode_host_ip(int addit_cnt, int ans_cnt, uchar_t **ptr,
529     uchar_t *eom, uchar_t *buf, smb_ads_host_info_t *ads_host_list)
530 {
531 	int i, j, len;
532 	smb_inaddr_t ipaddr;
533 	char hostname[MAXHOSTNAMELEN];
534 	char *name;
535 	uint16_t size = 0;
536 
537 	for (i = 0; i < addit_cnt; i++) {
538 
539 		/* domain name */
540 		len = dn_expand(buf, eom, *ptr, hostname, MAXHOSTNAMELEN);
541 		if (len < 0)
542 			return (-1);
543 
544 		*ptr += len;
545 
546 		/* skip type, class, TTL, data len */
547 		*ptr += 8;
548 		/* LINTED: E_CONSTANT_CONDITION */
549 		NS_GET16(size, *ptr);
550 
551 		if (size == INADDRSZ) {
552 			/* LINTED: E_CONSTANT_CONDITION */
553 			NS_GET32(ipaddr.a_ipv4, *ptr);
554 			ipaddr.a_ipv4 = htonl(ipaddr.a_ipv4);
555 			ipaddr.a_family = AF_INET;
556 		} else if (size == IN6ADDRSZ) {
557 #ifdef BIG_ENDIAN
558 			bcopy(*ptr, &ipaddr.a_ipv6, IN6ADDRSZ);
559 #else
560 			for (i = 0; i < IN6ADDRSZ; i++)
561 				(uint8_t *)(ipaddr.a_ipv6)
562 				    [IN6ADDRSZ-1-i] = *(*ptr+i);
563 #endif
564 			ipaddr.a_family = AF_INET6;
565 		}
566 
567 		/*
568 		 * find the host in the list of DC records from
569 		 * the answer section, that matches the host in the
570 		 * additional section, and set its IP address.
571 		 */
572 		for (j = 0; j < ans_cnt; j++) {
573 			if ((name = ads_host_list[j].name) == NULL)
574 				continue;
575 			if (smb_strcasecmp(name, hostname, 0) == 0) {
576 				ads_host_list[j].ipaddr = ipaddr;
577 			}
578 		}
579 	}
580 	return (0);
581 }
582 
583 /*
584  * smb_ads_dup_host_info
585  *
586  * Duplicates the passed smb_ads_host_info_t structure.
587  * Caller must free memory allocated by this method.
588  *
589  * Returns a reference to the duplicated smb_ads_host_info_t structure.
590  * Returns NULL on error.
591  */
592 static smb_ads_host_info_t *
593 smb_ads_dup_host_info(smb_ads_host_info_t *ads_host)
594 {
595 	smb_ads_host_info_t *dup_host;
596 
597 	if (ads_host == NULL)
598 		return (NULL);
599 
600 	dup_host = malloc(sizeof (smb_ads_host_info_t));
601 
602 	if (dup_host != NULL)
603 		bcopy(ads_host, dup_host, sizeof (smb_ads_host_info_t));
604 
605 	return (dup_host);
606 }
607 
608 /*
609  * smb_ads_hlist_alloc
610  */
611 static smb_ads_host_list_t *
612 smb_ads_hlist_alloc(int count)
613 {
614 	int size;
615 	smb_ads_host_list_t *hlist;
616 
617 	if (count == 0)
618 		return (NULL);
619 
620 	size = sizeof (smb_ads_host_info_t) * count;
621 	hlist = (smb_ads_host_list_t *)malloc(sizeof (smb_ads_host_list_t));
622 	if (hlist == NULL)
623 		return (NULL);
624 
625 	hlist->ah_cnt = count;
626 	hlist->ah_list = (smb_ads_host_info_t *)malloc(size);
627 	if (hlist->ah_list == NULL) {
628 		free(hlist);
629 		return (NULL);
630 	}
631 
632 	bzero(hlist->ah_list, size);
633 	return (hlist);
634 }
635 
636 /*
637  * smb_ads_hlist_free
638  */
639 static void
640 smb_ads_hlist_free(smb_ads_host_list_t *host_list)
641 {
642 	if (host_list == NULL)
643 		return;
644 
645 	free(host_list->ah_list);
646 	free(host_list);
647 }
648 
649 /*
650  * smb_ads_query_dns_server
651  *
652  * This routine sends a DNS service location (SRV) query message to the
653  * DNS server via TCP to query it for a list of ADS server(s). Once a reply
654  * is received, the reply message is parsed to get the hostname. If there are IP
655  * addresses populated in the additional section then the additional section
656  * is parsed to obtain the IP addresses.
657  *
658  * The service location of _ldap._tcp.dc.msdcs.<ADS domain> is used to
659  * guarantee that Microsoft domain controllers are returned.  Microsoft domain
660  * controllers are also ADS servers.
661  *
662  * The ADS hostnames are stored in the answer section of the DNS reply message.
663  * The IP addresses are stored in the additional section.
664  *
665  * The DNS reply message may be in compress formed.  The compression is done
666  * on repeating domain name label in the message.  i.e hostname.
667  *
668  * Upon successful completion, host list of ADS server(s) is returned.
669  */
670 static smb_ads_host_list_t *
671 smb_ads_query_dns_server(char *domain, char *msdcs_svc_name)
672 {
673 	smb_ads_host_list_t *hlist = NULL;
674 	int len, qcnt, ans_cnt, ns_cnt, addit_cnt;
675 	uchar_t *ptr, *eom;
676 	struct __res_state res_state;
677 	union {
678 		HEADER hdr;
679 		uchar_t buf[NS_MAXMSG];
680 	} msg;
681 
682 	bzero(&res_state, sizeof (struct __res_state));
683 	if (res_ninit(&res_state) < 0)
684 		return (NULL);
685 
686 	/* use TCP */
687 	res_state.options |= RES_USEVC;
688 
689 	len = res_nquerydomain(&res_state, msdcs_svc_name, domain,
690 	    C_IN, T_SRV, msg.buf, sizeof (msg.buf));
691 
692 	if (len < 0) {
693 		syslog(LOG_NOTICE, "DNS query for %s failed: %s",
694 		    msdcs_svc_name, hstrerror(res_state.res_h_errno));
695 		res_ndestroy(&res_state);
696 		return (NULL);
697 	}
698 
699 	if (len > sizeof (msg.buf)) {
700 		syslog(LOG_NOTICE,
701 		    "DNS query for %s failed: too big", msdcs_svc_name);
702 		res_ndestroy(&res_state);
703 		return (NULL);
704 	}
705 
706 	/* parse the reply, skip header and question sections */
707 	ptr = msg.buf + sizeof (msg.hdr);
708 	eom = msg.buf + len;
709 
710 	/* check truncated message bit */
711 	if (msg.hdr.tc)
712 		syslog(LOG_NOTICE,
713 		    "DNS query for %s failed: truncated", msdcs_svc_name);
714 
715 	qcnt = ntohs(msg.hdr.qdcount);
716 	ans_cnt = ntohs(msg.hdr.ancount);
717 	ns_cnt = ntohs(msg.hdr.nscount);
718 	addit_cnt = ntohs(msg.hdr.arcount);
719 
720 	if (smb_ads_skip_ques_sec(qcnt, &ptr, eom) != 0) {
721 		res_ndestroy(&res_state);
722 		return (NULL);
723 	}
724 
725 	hlist = smb_ads_hlist_alloc(ans_cnt);
726 	if (hlist == NULL) {
727 		res_ndestroy(&res_state);
728 		return (NULL);
729 	}
730 
731 	/* walk through the answer section */
732 	if (smb_ads_decode_host_ans_sec(ans_cnt, &ptr, eom, msg.buf,
733 	    hlist->ah_list) != 0) {
734 		smb_ads_hlist_free(hlist);
735 		res_ndestroy(&res_state);
736 		return (NULL);
737 	}
738 
739 	/* check authority section */
740 	if (ns_cnt > 0) {
741 		if (smb_ads_skip_auth_sec(ns_cnt, &ptr, eom) != 0) {
742 			smb_ads_hlist_free(hlist);
743 			res_ndestroy(&res_state);
744 			return (NULL);
745 		}
746 	}
747 
748 	/*
749 	 * Check additional section to get IP address of ADS host.
750 	 */
751 	if (addit_cnt > 0) {
752 		if (smb_ads_decode_host_ip(addit_cnt, ans_cnt,
753 		    &ptr, eom, msg.buf, hlist->ah_list) != 0) {
754 			smb_ads_hlist_free(hlist);
755 			res_ndestroy(&res_state);
756 			return (NULL);
757 		}
758 	}
759 
760 	res_ndestroy(&res_state);
761 	return (hlist);
762 }
763 
764 /*
765  * smb_ads_get_site_service
766  *
767  * Gets the msdcs SRV RR for the specified site.
768  */
769 static void
770 smb_ads_get_site_service(char *site_service, size_t len)
771 {
772 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
773 	if (*smb_ads_cfg.c_site == '\0')
774 		*site_service = '\0';
775 	else
776 		(void) snprintf(site_service, len,
777 		    SMB_ADS_MSDCS_SRV_SITE_RR, smb_ads_cfg.c_site);
778 
779 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
780 }
781 
782 /*
783  * smb_ads_getipnodebyname
784  *
785  * This method gets the IP address by doing a host name lookup.
786  */
787 static int
788 smb_ads_getipnodebyname(smb_ads_host_info_t *hentry)
789 {
790 	struct hostent *h;
791 	int error;
792 
793 	switch (hentry->ipaddr.a_family) {
794 	case AF_INET6:
795 		h = getipnodebyname(hentry->name, hentry->ipaddr.a_family,
796 		    AI_DEFAULT, &error);
797 		if (h == NULL || h->h_length != IPV6_ADDR_LEN)
798 			return (-1);
799 		break;
800 
801 	case AF_INET:
802 		h = getipnodebyname(hentry->name, hentry->ipaddr.a_family,
803 		    0, &error);
804 		if (h == NULL || h->h_length != INADDRSZ)
805 			return (-1);
806 		break;
807 
808 	default:
809 		return (-1);
810 	}
811 	bcopy(*(h->h_addr_list), &hentry->ipaddr.a_ip, h->h_length);
812 	freehostent(h);
813 	return (0);
814 }
815 
816 /*
817  *  Checks the IP address to see if it is zero.  If so, then do a host
818  *  lookup by hostname to get the IP address based on the IP family.
819  *
820  *  If the family is unknown then do a lookup by hostame based on the
821  *  setting of the SMB_CI_IPV6_ENABLE property.
822  */
823 static int
824 smb_ads_set_ipaddr(smb_ads_host_info_t *hentry)
825 {
826 	if (smb_inet_iszero(&hentry->ipaddr)) {
827 		if (smb_ads_getipnodebyname(hentry) < 0)
828 			return (-1);
829 	} else if (SMB_ADS_AF_UNKNOWN(hentry)) {
830 		hentry->ipaddr.a_family =
831 		    smb_config_getbool(SMB_CI_IPV6_ENABLE) ? AF_INET6 : AF_INET;
832 
833 		if (smb_ads_getipnodebyname(hentry) < 0) {
834 			hentry->ipaddr.a_family = 0;
835 			return (-1);
836 		}
837 	}
838 
839 	return (0);
840 }
841 
842 /*
843  * smb_ads_find_host
844  *
845  * Finds an ADS host in a given domain.
846  *
847  * If the cached host is valid, it will be used. Otherwise, a DC will
848  * be selected based on the following criteria:
849  *
850  * 1) pdc (aka preferred DC) configuration
851  * 2) AD site configuration - the scope of the DNS lookup will be
852  * restricted to the specified site.
853  * 3) DC on the same subnet
854  * 4) DC with the lowest priority/highest weight
855  *
856  * The above items are listed in decreasing preference order. The selected
857  * DC must be online.
858  *
859  * If this function is called during domain join, the specified kpasswd server
860  * takes precedence over preferred DC, AD site, and so on.
861  *
862  * Parameters:
863  *   domain: fully-qualified domain name.
864  *   kpasswd_srv: fully-quailifed hostname of the kpasswd server.
865  *
866  * Returns:
867  *   A copy of the cached host info is returned. The caller is responsible
868  *   for deallocating the memory returned by this function.
869  */
870 /*ARGSUSED*/
871 smb_ads_host_info_t *
872 smb_ads_find_host(char *domain, char *kpasswd_srv)
873 {
874 	int i;
875 	char site_service[MAXHOSTNAMELEN];
876 	smb_ads_host_list_t *hlist, *hlist2;
877 	smb_ads_host_info_t *hlistp = NULL, *host = NULL;
878 	smb_ads_host_info_t *found_kpasswd_srv = NULL;
879 	smb_ads_host_info_t *found_pdc = NULL;
880 
881 	if ((kpasswd_srv) && (*kpasswd_srv == '\0'))
882 		kpasswd_srv = NULL;
883 
884 	(void) mutex_lock(&smb_ads_cached_host_mtx);
885 	if (smb_ads_validate_cache_host(domain, kpasswd_srv)) {
886 		host = smb_ads_dup_host_info(smb_ads_cached_host_info);
887 		(void) mutex_unlock(&smb_ads_cached_host_mtx);
888 		return (host);
889 	}
890 
891 	(void) mutex_unlock(&smb_ads_cached_host_mtx);
892 	smb_ads_free_cached_host();
893 
894 	/*
895 	 * First look for ADS hosts in ADS site if configured.  Then try
896 	 * without ADS site info.
897 	 */
898 	hlist = NULL;
899 	smb_ads_get_site_service(site_service, MAXHOSTNAMELEN);
900 
901 	/*
902 	 * If we're given an AD, the DNS SRV RR lookup should not be restricted
903 	 * to the specified site since there is no guarantee that the specified
904 	 * AD is in the specified site.
905 	 */
906 	if (*site_service != '\0' && !kpasswd_srv &&
907 	    !smb_ads_is_pdc_configured())
908 		hlist = smb_ads_query_dns_server(domain, site_service);
909 
910 	if (!hlist)
911 		hlist = smb_ads_query_dns_server(domain,
912 		    SMB_ADS_MSDCS_SRV_DC_RR);
913 
914 	if ((hlist == NULL) || (hlist->ah_list == NULL) || (hlist->ah_cnt == 0))
915 		return (NULL);
916 
917 	for (i = 0, hlistp = hlist->ah_list; i < hlist->ah_cnt; i++) {
918 		if (smb_ads_set_ipaddr(&hlistp[i]) < 0)
919 			continue;
920 
921 		if (smb_ads_is_sought_host(&hlistp[i], kpasswd_srv))
922 			found_kpasswd_srv = &hlistp[i];
923 
924 		if (smb_ads_match_pdc(&hlistp[i]))
925 			found_pdc = &hlistp[i];
926 	}
927 
928 	if (found_kpasswd_srv && smb_ads_ldap_ping(found_kpasswd_srv) == 0) {
929 		host = found_kpasswd_srv;
930 		goto update_cache;
931 	}
932 
933 	if (found_pdc && smb_ads_ldap_ping(found_pdc) == 0) {
934 		host = found_pdc;
935 		goto update_cache;
936 	}
937 
938 	/*
939 	 * If the specified DC (kpasswd_srv or pdc) is not found, fallback
940 	 * to find a DC in the specified AD site.
941 	 */
942 	if (*site_service != '\0' &&
943 	    (kpasswd_srv || smb_ads_is_pdc_configured())) {
944 		hlist2 = smb_ads_query_dns_server(domain, site_service);
945 		if (hlist2 && hlist2->ah_list && hlist2->ah_cnt != 0) {
946 			smb_ads_hlist_free(hlist);
947 			hlist = hlist2;
948 			hlistp = hlist->ah_list;
949 
950 			for (i = 0; i < hlist->ah_cnt; i++)
951 				(void) smb_ads_set_ipaddr(&hlistp[i]);
952 		}
953 	}
954 
955 	/* Select DC from DC list */
956 	host = smb_ads_select_dc(hlist);
957 
958 update_cache:
959 	if (host) {
960 		(void) mutex_lock(&smb_ads_cached_host_mtx);
961 		if (!smb_ads_cached_host_info)
962 			smb_ads_cached_host_info = smb_ads_dup_host_info(host);
963 		host = smb_ads_dup_host_info(smb_ads_cached_host_info);
964 		(void) mutex_unlock(&smb_ads_cached_host_mtx);
965 	}
966 
967 	smb_ads_hlist_free(hlist);
968 	return (host);
969 }
970 
971 /*
972  * Return the number of dots in a string.
973  */
974 static int
975 smb_ads_count_dots(const char *s)
976 {
977 	int ndots = 0;
978 
979 	while (*s) {
980 		if (*s++ == '.')
981 			ndots++;
982 	}
983 
984 	return (ndots);
985 }
986 
987 /*
988  * Convert a domain name in dot notation to distinguished name format,
989  * for example: sun.com -> dc=sun,dc=com.
990  *
991  * Returns a pointer to an allocated buffer containing the distinguished
992  * name.
993  */
994 static char *
995 smb_ads_convert_domain(const char *domain_name)
996 {
997 	const char *s;
998 	char *dn_name;
999 	char buf[2];
1000 	int ndots;
1001 	int len;
1002 
1003 	if (domain_name == NULL || *domain_name == 0)
1004 		return (NULL);
1005 
1006 	ndots = smb_ads_count_dots(domain_name);
1007 	++ndots;
1008 	len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1;
1009 
1010 	if ((dn_name = malloc(len)) == NULL)
1011 		return (NULL);
1012 
1013 	bzero(dn_name, len);
1014 	(void) strlcpy(dn_name, "dc=", len);
1015 
1016 	buf[1] = '\0';
1017 	s = domain_name;
1018 
1019 	while (*s) {
1020 		if (*s == '.') {
1021 			(void) strlcat(dn_name, ",dc=", len);
1022 		} else {
1023 			buf[0] = *s;
1024 			(void) strlcat(dn_name, buf, len);
1025 		}
1026 		++s;
1027 	}
1028 
1029 	return (dn_name);
1030 }
1031 
1032 /*
1033  * smb_ads_free_cached_host
1034  *
1035  * Free the memory use by the global smb_ads_cached_host_info & set it to NULL.
1036  */
1037 static void
1038 smb_ads_free_cached_host(void)
1039 {
1040 	(void) mutex_lock(&smb_ads_cached_host_mtx);
1041 	if (smb_ads_cached_host_info) {
1042 		free(smb_ads_cached_host_info);
1043 		smb_ads_cached_host_info = NULL;
1044 	}
1045 	(void) mutex_unlock(&smb_ads_cached_host_mtx);
1046 }
1047 
1048 /*
1049  * smb_ads_open
1050  * Open a LDAP connection to an ADS server if the system is in domain mode.
1051  * Acquire both Kerberos TGT and LDAP service tickets for the host principal.
1052  *
1053  * This function should only be called after the system is successfully joined
1054  * to a domain.
1055  */
1056 smb_ads_handle_t *
1057 smb_ads_open(void)
1058 {
1059 	char domain[MAXHOSTNAMELEN];
1060 
1061 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
1062 		return (NULL);
1063 
1064 	if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
1065 		return (NULL);
1066 
1067 	return (smb_ads_open_main(domain, NULL, NULL));
1068 }
1069 
1070 static int
1071 smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts)
1072 {
1073 	NOTE(ARGUNUSED(ld, defaults));
1074 	sasl_interact_t *interact;
1075 
1076 	if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
1077 		return (LDAP_PARAM_ERROR);
1078 
1079 	/* There should be no extra arguemnts for SASL/GSSAPI authentication */
1080 	for (interact = prompts; interact->id != SASL_CB_LIST_END;
1081 	    interact++) {
1082 		interact->result = NULL;
1083 		interact->len = 0;
1084 	}
1085 	return (LDAP_SUCCESS);
1086 }
1087 
1088 /*
1089  * smb_ads_open_main
1090  * Open a LDAP connection to an ADS server.
1091  * If ADS is enabled and the administrative username, password, and
1092  * ADS domain are defined then query DNS to find an ADS server if this is the
1093  * very first call to this routine.  After an ADS server is found then this
1094  * server will be used everytime this routine is called until the system is
1095  * rebooted or the ADS server becomes unavailable then an ADS server will
1096  * be queried again.  After the connection is made then an ADS handle
1097  * is created to be returned.
1098  *
1099  * After the LDAP connection, the LDAP version will be set to 3 using
1100  * ldap_set_option().
1101  *
1102  * The LDAP connection is bound before the ADS handle is returned.
1103  * Parameters:
1104  *   domain - fully-qualified domain name
1105  *   user   - the user account for whom the Kerberos TGT ticket and ADS
1106  *            service tickets are acquired.
1107  *   password - password of the specified user
1108  *
1109  * Returns:
1110  *   NULL              : can't connect to ADS server or other errors
1111  *   smb_ads_handle_t* : handle to ADS server
1112  */
1113 static smb_ads_handle_t *
1114 smb_ads_open_main(char *domain, char *user, char *password)
1115 {
1116 	smb_ads_handle_t *ah;
1117 	LDAP *ld;
1118 	int version = 3;
1119 	smb_ads_host_info_t *ads_host = NULL;
1120 	int rc;
1121 
1122 	if (user != NULL) {
1123 		if (smb_kinit(user, password) == 0)
1124 			return (NULL);
1125 		user = NULL;
1126 		password = NULL;
1127 	}
1128 
1129 	ads_host = smb_ads_find_host(domain, NULL);
1130 	if (ads_host == NULL)
1131 		return (NULL);
1132 
1133 	ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
1134 	if (ah == NULL) {
1135 		free(ads_host);
1136 		return (NULL);
1137 	}
1138 
1139 	(void) memset(ah, 0, sizeof (smb_ads_handle_t));
1140 
1141 	if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) {
1142 		smb_ads_free_cached_host();
1143 		free(ah);
1144 		free(ads_host);
1145 		return (NULL);
1146 	}
1147 
1148 	if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version)
1149 	    != LDAP_SUCCESS) {
1150 		smb_ads_free_cached_host();
1151 		free(ah);
1152 		free(ads_host);
1153 		(void) ldap_unbind(ld);
1154 		return (NULL);
1155 	}
1156 
1157 	(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
1158 	ah->ld = ld;
1159 	ah->domain = strdup(domain);
1160 
1161 	if (ah->domain == NULL) {
1162 		smb_ads_close(ah);
1163 		free(ads_host);
1164 		return (NULL);
1165 	}
1166 
1167 	/*
1168 	 * ah->domain is often used for generating service principal name.
1169 	 * Convert it to lower case for RFC 4120 section 6.2.1 conformance.
1170 	 */
1171 	(void) smb_strlwr(ah->domain);
1172 	ah->domain_dn = smb_ads_convert_domain(domain);
1173 	if (ah->domain_dn == NULL) {
1174 		smb_ads_close(ah);
1175 		free(ads_host);
1176 		return (NULL);
1177 	}
1178 
1179 	ah->hostname = strdup(ads_host->name);
1180 	if (ah->hostname == NULL) {
1181 		smb_ads_close(ah);
1182 		free(ads_host);
1183 		return (NULL);
1184 	}
1185 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
1186 	if (*smb_ads_cfg.c_site != '\0') {
1187 		if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) {
1188 			smb_ads_close(ah);
1189 			(void) mutex_unlock(&smb_ads_cfg.c_mtx);
1190 			free(ads_host);
1191 			return (NULL);
1192 		}
1193 	} else {
1194 		ah->site = NULL;
1195 	}
1196 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
1197 
1198 	rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL,
1199 	    LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL);
1200 	if (rc != LDAP_SUCCESS) {
1201 		syslog(LOG_ERR, "ldal_sasl_interactive_bind_s failed (%s)",
1202 		    ldap_err2string(rc));
1203 		smb_ads_close(ah);
1204 		free(ads_host);
1205 		return (NULL);
1206 	}
1207 
1208 	free(ads_host);
1209 	return (ah);
1210 }
1211 
1212 /*
1213  * smb_ads_close
1214  * Close connection to ADS server and free memory allocated for ADS handle.
1215  * LDAP unbind is called here.
1216  * Parameters:
1217  *   ah: handle to ADS server
1218  * Returns:
1219  *   void
1220  */
1221 void
1222 smb_ads_close(smb_ads_handle_t *ah)
1223 {
1224 	if (ah == NULL)
1225 		return;
1226 	/* close and free connection resources */
1227 	if (ah->ld)
1228 		(void) ldap_unbind(ah->ld);
1229 
1230 	free(ah->domain);
1231 	free(ah->domain_dn);
1232 	free(ah->hostname);
1233 	free(ah->site);
1234 	free(ah);
1235 }
1236 
1237 /*
1238  * smb_ads_alloc_attr
1239  *
1240  * Since the attrs is a null-terminated array, all elements
1241  * in the array (except the last one) will point to allocated
1242  * memory.
1243  */
1244 static int
1245 smb_ads_alloc_attr(LDAPMod *attrs[], int num)
1246 {
1247 	int i;
1248 
1249 	bzero(attrs, num * sizeof (LDAPMod *));
1250 	for (i = 0; i < (num - 1); i++) {
1251 		attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod));
1252 		if (attrs[i] == NULL) {
1253 			smb_ads_free_attr(attrs);
1254 			return (-1);
1255 		}
1256 	}
1257 
1258 	return (0);
1259 }
1260 
1261 /*
1262  * smb_ads_free_attr
1263  * Free memory allocated when publishing a share.
1264  * Parameters:
1265  *   attrs: an array of LDAPMod pointers
1266  * Returns:
1267  *   None
1268  */
1269 static void
1270 smb_ads_free_attr(LDAPMod *attrs[])
1271 {
1272 	int i;
1273 	for (i = 0; attrs[i]; i++) {
1274 		free(attrs[i]);
1275 	}
1276 }
1277 
1278 /*
1279  * smb_ads_get_spnset
1280  *
1281  * Derives the core set of SPNs based on the FQHN.
1282  * The spn_set is a null-terminated array of char pointers.
1283  *
1284  * Returns 0 upon success. Otherwise, returns -1.
1285  */
1286 static int
1287 smb_ads_get_spnset(char *fqhost, char **spn_set)
1288 {
1289 	int i;
1290 
1291 	bzero(spn_set, (SMBKRB5_SPN_IDX_MAX + 1) * sizeof (char *));
1292 	for (i = 0; i < SMBKRB5_SPN_IDX_MAX; i++) {
1293 		if ((spn_set[i] = smb_krb5_get_spn(i, fqhost)) == NULL) {
1294 			smb_ads_free_spnset(spn_set);
1295 			return (-1);
1296 		}
1297 	}
1298 
1299 	return (0);
1300 }
1301 
1302 /*
1303  * smb_ads_free_spnset
1304  *
1305  * Free the memory allocated for the set of SPNs.
1306  */
1307 static void
1308 smb_ads_free_spnset(char **spn_set)
1309 {
1310 	int i;
1311 	for (i = 0; spn_set[i]; i++)
1312 		free(spn_set[i]);
1313 }
1314 
1315 /*
1316  * Returns share DN in an allocated buffer.  The format of the DN is
1317  * cn=<sharename>,<container RDNs>,<domain DN>
1318  *
1319  * If the domain DN is not included in the container parameter,
1320  * then it will be appended to create the share DN.
1321  *
1322  * The caller must free the allocated buffer.
1323  */
1324 static char *
1325 smb_ads_get_sharedn(const char *sharename, const char *container,
1326     const char *domain_dn)
1327 {
1328 	char *share_dn;
1329 	int rc, offset, container_len, domain_len;
1330 	boolean_t append_domain = B_TRUE;
1331 
1332 	container_len = strlen(container);
1333 	domain_len = strlen(domain_dn);
1334 
1335 	if (container_len >= domain_len) {
1336 
1337 		/* offset to last domain_len characters */
1338 		offset = container_len - domain_len;
1339 
1340 		if (smb_strcasecmp(container + offset,
1341 		    domain_dn, domain_len) == 0)
1342 			append_domain = B_FALSE;
1343 	}
1344 
1345 	if (append_domain)
1346 		rc = asprintf(&share_dn, "cn=%s,%s,%s", sharename,
1347 		    container, domain_dn);
1348 	else
1349 		rc = asprintf(&share_dn, "cn=%s,%s", sharename,
1350 		    container);
1351 
1352 	return ((rc == -1) ? NULL : share_dn);
1353 }
1354 
1355 /*
1356  * smb_ads_add_share
1357  * Call by smb_ads_publish_share to create share object in ADS.
1358  * This routine specifies the attributes of an ADS LDAP share object. The first
1359  * attribute and values define the type of ADS object, the share object.  The
1360  * second attribute and value define the UNC of the share data for the share
1361  * object. The LDAP synchronous add command is used to add the object into ADS.
1362  * The container location to add the object needs to specified.
1363  * Parameters:
1364  *   ah          : handle to ADS server
1365  *   adsShareName: name of share object to be created in ADS
1366  *   shareUNC    : share name on NetForce
1367  *   adsContainer: location in ADS to create share object
1368  *
1369  * Returns:
1370  *   -1          : error
1371  *    0          : success
1372  */
1373 int
1374 smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName,
1375     const char *unc_name, const char *adsContainer)
1376 {
1377 	LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR];
1378 	int j = 0;
1379 	char *share_dn;
1380 	int ret;
1381 	char *unc_names[] = {(char *)unc_name, NULL};
1382 
1383 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1384 	    ah->domain_dn)) == NULL)
1385 		return (-1);
1386 
1387 	if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) {
1388 		free(share_dn);
1389 		return (-1);
1390 	}
1391 
1392 	attrs[j]->mod_op = LDAP_MOD_ADD;
1393 	attrs[j]->mod_type = "objectClass";
1394 	attrs[j]->mod_values = smb_ads_share_objcls;
1395 
1396 	attrs[++j]->mod_op = LDAP_MOD_ADD;
1397 	attrs[j]->mod_type = "uNCName";
1398 	attrs[j]->mod_values = unc_names;
1399 
1400 	if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) {
1401 		if (ret == LDAP_NO_SUCH_OBJECT) {
1402 			syslog(LOG_ERR, "Failed to publish share %s in" \
1403 			    " AD.  Container does not exist: %s.\n",
1404 			    adsShareName, share_dn);
1405 
1406 		} else {
1407 			syslog(LOG_ERR, "Failed to publish share %s in" \
1408 			    " AD: %s (%s).\n", adsShareName, share_dn,
1409 			    ldap_err2string(ret));
1410 		}
1411 		smb_ads_free_attr(attrs);
1412 		free(share_dn);
1413 		return (ret);
1414 	}
1415 	free(share_dn);
1416 	smb_ads_free_attr(attrs);
1417 
1418 	return (0);
1419 }
1420 
1421 /*
1422  * smb_ads_del_share
1423  * Call by smb_ads_remove_share to remove share object from ADS.  The container
1424  * location to remove the object needs to specified.  The LDAP synchronous
1425  * delete command is used.
1426  * Parameters:
1427  *   ah          : handle to ADS server
1428  *   adsShareName: name of share object in ADS to be removed
1429  *   adsContainer: location of share object in ADS
1430  * Returns:
1431  *   -1          : error
1432  *    0          : success
1433  */
1434 static int
1435 smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName,
1436     const char *adsContainer)
1437 {
1438 	char *share_dn;
1439 	int ret;
1440 
1441 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1442 	    ah->domain_dn)) == NULL)
1443 		return (-1);
1444 
1445 	if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) {
1446 		smb_tracef("ldap_delete: %s", ldap_err2string(ret));
1447 		free(share_dn);
1448 		return (-1);
1449 	}
1450 	free(share_dn);
1451 
1452 	return (0);
1453 }
1454 
1455 
1456 /*
1457  * smb_ads_escape_search_filter_chars
1458  *
1459  * This routine will escape the special characters found in a string
1460  * that will later be passed to the ldap search filter.
1461  *
1462  * RFC 1960 - A String Representation of LDAP Search Filters
1463  * 3.  String Search Filter Definition
1464  * If a value must contain one of the characters '*' OR '(' OR ')',
1465  * these characters
1466  * should be escaped by preceding them with the backslash '\' character.
1467  *
1468  * RFC 2252 - LDAP Attribute Syntax Definitions
1469  * a backslash quoting mechanism is used to escape
1470  * the following separator symbol character (such as "'", "$" or "#") if
1471  * it should occur in that string.
1472  */
1473 static int
1474 smb_ads_escape_search_filter_chars(const char *src, char *dst)
1475 {
1476 	int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */
1477 
1478 	if (src == NULL || dst == NULL)
1479 		return (-1);
1480 
1481 	while (*src) {
1482 		if (!avail) {
1483 			*dst = 0;
1484 			return (-1);
1485 		}
1486 
1487 		switch (*src) {
1488 		case '\\':
1489 		case '\'':
1490 		case '$':
1491 		case '#':
1492 		case '*':
1493 		case '(':
1494 		case ')':
1495 			*dst++ = '\\';
1496 			avail--;
1497 			/* fall through */
1498 
1499 		default:
1500 			*dst++ = *src++;
1501 			avail--;
1502 		}
1503 	}
1504 
1505 	*dst = 0;
1506 
1507 	return (0);
1508 }
1509 
1510 /*
1511  * smb_ads_lookup_share
1512  * The search filter is set to search for a specific share name in the
1513  * specified ADS container.  The LDSAP synchronous search command is used.
1514  * Parameters:
1515  *   ah          : handle to ADS server
1516  *   adsShareName: name of share object in ADS to be searched
1517  *   adsContainer: location of share object in ADS
1518  * Returns:
1519  *   -1          : error
1520  *    0          : not found
1521  *    1          : found
1522  */
1523 int
1524 smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName,
1525     const char *adsContainer, char *unc_name)
1526 {
1527 	char *attrs[4], filter[SMB_ADS_MAXBUFLEN];
1528 	char *share_dn;
1529 	int ret;
1530 	LDAPMessage *res;
1531 	char tmpbuf[SMB_ADS_MAXBUFLEN];
1532 
1533 	if (adsShareName == NULL || adsContainer == NULL)
1534 		return (-1);
1535 
1536 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1537 	    ah->domain_dn)) == NULL)
1538 		return (-1);
1539 
1540 	res = NULL;
1541 	attrs[0] = "cn";
1542 	attrs[1] = "objectClass";
1543 	attrs[2] = "uNCName";
1544 	attrs[3] = NULL;
1545 
1546 	if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) {
1547 		free(share_dn);
1548 		return (-1);
1549 	}
1550 
1551 	(void) snprintf(filter, sizeof (filter),
1552 	    "(&(objectClass=volume)(uNCName=%s))", tmpbuf);
1553 
1554 	if ((ret = ldap_search_s(ah->ld, share_dn,
1555 	    LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) {
1556 		if (ret != LDAP_NO_SUCH_OBJECT)
1557 			smb_tracef("%s: ldap_search: %s", share_dn,
1558 			    ldap_err2string(ret));
1559 
1560 		(void) ldap_msgfree(res);
1561 		free(share_dn);
1562 		return (0);
1563 	}
1564 
1565 	(void) free(share_dn);
1566 
1567 	/* no match is found */
1568 	if (ldap_count_entries(ah->ld, res) == 0) {
1569 		(void) ldap_msgfree(res);
1570 		return (0);
1571 	}
1572 
1573 	/* free the search results */
1574 	(void) ldap_msgfree(res);
1575 
1576 	return (1);
1577 }
1578 
1579 /*
1580  * smb_ads_publish_share
1581  * Publish share into ADS.  If a share name already exist in ADS in the same
1582  * container then the existing share object is removed before adding the new
1583  * share object.
1584  * Parameters:
1585  *   ah          : handle return from smb_ads_open
1586  *   adsShareName: name of share to be added to ADS directory
1587  *   shareUNC    : name of share on client, can be NULL to use the same name
1588  *                 as adsShareName
1589  *   adsContainer: location for share to be added in ADS directory, ie
1590  *                   ou=share_folder
1591  *   uncType     : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR
1592  *                   to use host ip addr for UNC.
1593  * Returns:
1594  *   -1          : error
1595  *    0          : success
1596  */
1597 int
1598 smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName,
1599     const char *shareUNC, const char *adsContainer, const char *hostname)
1600 {
1601 	int ret;
1602 	char unc_name[SMB_ADS_MAXBUFLEN];
1603 
1604 	if (adsShareName == NULL || adsContainer == NULL)
1605 		return (-1);
1606 
1607 	if (shareUNC == 0 || *shareUNC == 0)
1608 		shareUNC = adsShareName;
1609 
1610 	if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1611 	    hostname, shareUNC) < 0)
1612 		return (-1);
1613 
1614 	ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1615 
1616 	switch (ret) {
1617 	case 1:
1618 		(void) smb_ads_del_share(ah, adsShareName, adsContainer);
1619 		ret = smb_ads_add_share(ah, adsShareName, unc_name,
1620 		    adsContainer);
1621 		break;
1622 
1623 	case 0:
1624 		ret = smb_ads_add_share(ah, adsShareName, unc_name,
1625 		    adsContainer);
1626 		if (ret == LDAP_ALREADY_EXISTS)
1627 			ret = -1;
1628 
1629 		break;
1630 
1631 	case -1:
1632 	default:
1633 		/* return with error code */
1634 		ret = -1;
1635 	}
1636 
1637 	return (ret);
1638 }
1639 
1640 /*
1641  * smb_ads_remove_share
1642  * Remove share from ADS.  A search is done first before explicitly removing
1643  * the share.
1644  * Parameters:
1645  *   ah          : handle return from smb_ads_open
1646  *   adsShareName: name of share to be removed from ADS directory
1647  *   adsContainer: location for share to be removed from ADS directory, ie
1648  *                   ou=share_folder
1649  * Returns:
1650  *   -1          : error
1651  *    0          : success
1652  */
1653 int
1654 smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName,
1655     const char *shareUNC, const char *adsContainer, const char *hostname)
1656 {
1657 	int ret;
1658 	char unc_name[SMB_ADS_MAXBUFLEN];
1659 
1660 	if (adsShareName == NULL || adsContainer == NULL)
1661 		return (-1);
1662 	if (shareUNC == 0 || *shareUNC == 0)
1663 		shareUNC = adsShareName;
1664 
1665 	if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1666 	    hostname, shareUNC) < 0)
1667 		return (-1);
1668 
1669 	ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1670 	if (ret == 0)
1671 		return (0);
1672 	if (ret == -1)
1673 		return (-1);
1674 
1675 	return (smb_ads_del_share(ah, adsShareName, adsContainer));
1676 }
1677 
1678 /*
1679  * smb_ads_get_default_comp_container_dn
1680  *
1681  * Build the distinguished name for the default computer conatiner (i.e. the
1682  * pre-defined Computers container).
1683  */
1684 static void
1685 smb_ads_get_default_comp_container_dn(smb_ads_handle_t *ah, char *buf,
1686     size_t buflen)
1687 {
1688 	(void) snprintf(buf, buflen, "cn=%s,%s", SMB_ADS_COMPUTERS_CN,
1689 	    ah->domain_dn);
1690 }
1691 
1692 /*
1693  * smb_ads_get_default_comp_dn
1694  *
1695  * Build the distinguished name for this system.
1696  */
1697 static void
1698 smb_ads_get_default_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen)
1699 {
1700 	char nbname[NETBIOS_NAME_SZ];
1701 	char container_dn[SMB_ADS_DN_MAX];
1702 
1703 	(void) smb_getnetbiosname(nbname, sizeof (nbname));
1704 	smb_ads_get_default_comp_container_dn(ah, container_dn, SMB_ADS_DN_MAX);
1705 	(void) snprintf(buf, buflen, "cn=%s,%s", nbname, container_dn);
1706 }
1707 
1708 /*
1709  * smb_ads_add_computer
1710  *
1711  * Returns 0 upon success. Otherwise, returns -1.
1712  */
1713 static int
1714 smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1715 {
1716 	return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn));
1717 }
1718 
1719 /*
1720  * smb_ads_modify_computer
1721  *
1722  * Returns 0 upon success. Otherwise, returns -1.
1723  */
1724 static int
1725 smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1726 {
1727 	return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn));
1728 }
1729 
1730 /*
1731  * smb_ads_get_dc_level
1732  *
1733  * Returns the functional level of the DC upon success.
1734  * Otherwise, -1 is returned.
1735  */
1736 static int
1737 smb_ads_get_dc_level(smb_ads_handle_t *ah)
1738 {
1739 	LDAPMessage *res, *entry;
1740 	char *attr[2];
1741 	char **vals;
1742 	int rc = -1;
1743 
1744 	res = NULL;
1745 	attr[0] = SMB_ADS_ATTR_DCLEVEL;
1746 	attr[1] = NULL;
1747 	if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr,
1748 	    0, &res) != LDAP_SUCCESS) {
1749 		(void) ldap_msgfree(res);
1750 		return (-1);
1751 	}
1752 
1753 	/* no match for the specified attribute is found */
1754 	if (ldap_count_entries(ah->ld, res) == 0) {
1755 		(void) ldap_msgfree(res);
1756 		return (-1);
1757 	}
1758 
1759 	entry = ldap_first_entry(ah->ld, res);
1760 	if (entry) {
1761 		if ((vals = ldap_get_values(ah->ld, entry,
1762 		    SMB_ADS_ATTR_DCLEVEL)) == NULL) {
1763 			/*
1764 			 * Observed the values aren't populated
1765 			 * by the Windows 2000 server.
1766 			 */
1767 			(void) ldap_msgfree(res);
1768 			return (SMB_ADS_DCLEVEL_W2K);
1769 		}
1770 
1771 		if (vals[0] != NULL)
1772 			rc = atoi(vals[0]);
1773 
1774 		ldap_value_free(vals);
1775 	}
1776 
1777 	(void) ldap_msgfree(res);
1778 	return (rc);
1779 }
1780 
1781 /*
1782  * The fully-qualified hostname returned by this function is often used for
1783  * constructing service principal name.  Return the fully-qualified hostname
1784  * in lower case for RFC 4120 section 6.2.1 conformance.
1785  */
1786 static int
1787 smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len)
1788 {
1789 	if (smb_gethostname(fqhost, len, SMB_CASE_LOWER) != 0)
1790 		return (-1);
1791 
1792 	(void) snprintf(fqhost, len, "%s.%s", fqhost,
1793 	    ah->domain);
1794 
1795 	return (0);
1796 }
1797 
1798 static int
1799 smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
1800 {
1801 	LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR];
1802 	char *sam_val[2], *usr_val[2];
1803 	char *spn_set[SMBKRB5_SPN_IDX_MAX + 1], *ctl_val[2], *fqh_val[2];
1804 	char *encrypt_val[2];
1805 	int j = -1;
1806 	int ret, usrctl_flags = 0;
1807 	char sam_acct[SMB_SAMACCT_MAXLEN];
1808 	char fqhost[MAXHOSTNAMELEN];
1809 	char *user_principal;
1810 	char usrctl_buf[16];
1811 	char encrypt_buf[16];
1812 	int max;
1813 
1814 	if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1815 		return (-1);
1816 
1817 	if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1818 		return (-1);
1819 
1820 	if (smb_ads_get_spnset(fqhost, spn_set) != 0)
1821 		return (-1);
1822 
1823 	user_principal = smb_krb5_get_upn(spn_set[SMBKRB5_SPN_IDX_HOST],
1824 	    ah->domain);
1825 
1826 	if (user_principal == NULL) {
1827 		smb_ads_free_spnset(spn_set);
1828 		return (-1);
1829 	}
1830 
1831 	max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0))
1832 	    - (dclevel >= SMB_ADS_DCLEVEL_W2K8 ?  0 : 1);
1833 
1834 	if (smb_ads_alloc_attr(attrs, max) != 0) {
1835 		free(user_principal);
1836 		smb_ads_free_spnset(spn_set);
1837 		return (-1);
1838 	}
1839 
1840 	/* objectClass attribute is not modifiable. */
1841 	if (op == LDAP_MOD_ADD) {
1842 		attrs[++j]->mod_op = op;
1843 		attrs[j]->mod_type = "objectClass";
1844 		attrs[j]->mod_values = smb_ads_computer_objcls;
1845 	}
1846 
1847 	attrs[++j]->mod_op = op;
1848 	attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT;
1849 	sam_val[0] = sam_acct;
1850 	sam_val[1] = 0;
1851 	attrs[j]->mod_values = sam_val;
1852 
1853 	attrs[++j]->mod_op = op;
1854 	attrs[j]->mod_type = SMB_ADS_ATTR_UPN;
1855 	usr_val[0] = user_principal;
1856 	usr_val[1] = 0;
1857 	attrs[j]->mod_values = usr_val;
1858 
1859 	attrs[++j]->mod_op = op;
1860 	attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
1861 	attrs[j]->mod_values = spn_set;
1862 
1863 	attrs[++j]->mod_op = op;
1864 	attrs[j]->mod_type = SMB_ADS_ATTR_CTL;
1865 	usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1866 	    SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD |
1867 	    SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE);
1868 	(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
1869 	ctl_val[0] = usrctl_buf;
1870 	ctl_val[1] = 0;
1871 	attrs[j]->mod_values = ctl_val;
1872 
1873 	attrs[++j]->mod_op = op;
1874 	attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST;
1875 	fqh_val[0] = fqhost;
1876 	fqh_val[1] = 0;
1877 	attrs[j]->mod_values = fqh_val;
1878 
1879 	/* enctypes support starting in Windows Server 2008 */
1880 	if (dclevel > SMB_ADS_DCLEVEL_W2K3) {
1881 		attrs[++j]->mod_op = op;
1882 		attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES;
1883 		(void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d",
1884 		    SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 +
1885 		    SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC);
1886 		encrypt_val[0] = encrypt_buf;
1887 		encrypt_val[1] = 0;
1888 		attrs[j]->mod_values = encrypt_val;
1889 	}
1890 
1891 	switch (op) {
1892 	case LDAP_MOD_ADD:
1893 		if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1894 			syslog(LOG_NOTICE, "ldap_add: %s",
1895 			    ldap_err2string(ret));
1896 			ret = -1;
1897 		}
1898 		break;
1899 
1900 	case LDAP_MOD_REPLACE:
1901 		if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1902 			syslog(LOG_NOTICE, "ldap_modify: %s",
1903 			    ldap_err2string(ret));
1904 			ret = -1;
1905 		}
1906 		break;
1907 
1908 	default:
1909 		ret = -1;
1910 
1911 	}
1912 
1913 	smb_ads_free_attr(attrs);
1914 	free(user_principal);
1915 	smb_ads_free_spnset(spn_set);
1916 
1917 	return (ret);
1918 }
1919 
1920 /*
1921  * Delete an ADS computer account.
1922  */
1923 static void
1924 smb_ads_del_computer(smb_ads_handle_t *ah, char *dn)
1925 {
1926 	int rc;
1927 
1928 	if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS)
1929 		smb_tracef("ldap_delete: %s", ldap_err2string(rc));
1930 }
1931 
1932 /*
1933  * Gets the value of the given attribute.
1934  */
1935 static smb_ads_qstat_t
1936 smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair)
1937 {
1938 	char **vals;
1939 	smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1940 
1941 	assert(avpair);
1942 	avpair->avp_val = NULL;
1943 	vals = ldap_get_values(ld, entry, avpair->avp_attr);
1944 	if (!vals)
1945 		return (SMB_ADS_STAT_NOT_FOUND);
1946 
1947 	if (!vals[0]) {
1948 		ldap_value_free(vals);
1949 		return (SMB_ADS_STAT_NOT_FOUND);
1950 	}
1951 
1952 	avpair->avp_val = strdup(vals[0]);
1953 	if (!avpair->avp_val)
1954 		rc = SMB_ADS_STAT_ERR;
1955 
1956 	ldap_value_free(vals);
1957 	return (rc);
1958 }
1959 
1960 /*
1961  * Process query's result.
1962  */
1963 static smb_ads_qstat_t
1964 smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
1965     smb_ads_avpair_t *avpair)
1966 {
1967 	char fqhost[MAXHOSTNAMELEN];
1968 	smb_ads_avpair_t dnshost_avp;
1969 	smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1970 	LDAPMessage *entry;
1971 
1972 	if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1973 		return (SMB_ADS_STAT_ERR);
1974 
1975 	if (ldap_count_entries(ah->ld, res) == 0)
1976 		return (SMB_ADS_STAT_NOT_FOUND);
1977 
1978 	if ((entry = ldap_first_entry(ah->ld, res)) == NULL)
1979 		return (SMB_ADS_STAT_ERR);
1980 
1981 	dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
1982 	rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
1983 
1984 	switch (rc) {
1985 	case SMB_ADS_STAT_FOUND:
1986 		/*
1987 		 * Returns SMB_ADS_STAT_DUP to avoid overwriting
1988 		 * the computer account of another system whose
1989 		 * NetBIOS name collides with that of the current
1990 		 * system.
1991 		 */
1992 		if (strcasecmp(dnshost_avp.avp_val, fqhost))
1993 			rc = SMB_ADS_STAT_DUP;
1994 
1995 		free(dnshost_avp.avp_val);
1996 		break;
1997 
1998 	case SMB_ADS_STAT_NOT_FOUND:
1999 		/*
2000 		 * Pre-created computer account doesn't have
2001 		 * the dNSHostname attribute. It's been observed
2002 		 * that the dNSHostname attribute is only set after
2003 		 * a successful domain join.
2004 		 * Returns SMB_ADS_STAT_FOUND as the account is
2005 		 * pre-created for the current system.
2006 		 */
2007 		rc = SMB_ADS_STAT_FOUND;
2008 		break;
2009 
2010 	default:
2011 		break;
2012 	}
2013 
2014 	if (rc != SMB_ADS_STAT_FOUND)
2015 		return (rc);
2016 
2017 	if (avpair)
2018 		rc = smb_ads_getattr(ah->ld, entry, avpair);
2019 
2020 	return (rc);
2021 
2022 }
2023 
2024 /*
2025  * smb_ads_lookup_computer_n_attr
2026  *
2027  * If avpair is NULL, checks the status of the specified computer account.
2028  * Otherwise, looks up the value of the specified computer account's attribute.
2029  * If found, the value field of the avpair will be allocated and set. The
2030  * caller should free the allocated buffer.
2031  *
2032  * Return:
2033  *  SMB_ADS_STAT_FOUND  - if both the computer and the specified attribute is
2034  *                        found.
2035  *  SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute
2036  *                           is not found.
2037  *  SMB_ADS_STAT_DUP - if the computer account is already used by other systems
2038  *                     in the AD. This could happen if the hostname of multiple
2039  *                     systems resolved to the same NetBIOS name.
2040  *  SMB_ADS_STAT_ERR - any failure.
2041  */
2042 static smb_ads_qstat_t
2043 smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair,
2044     int scope, char *dn)
2045 {
2046 	char *attrs[3], filter[SMB_ADS_MAXBUFLEN];
2047 	LDAPMessage *res;
2048 	char sam_acct[SMB_SAMACCT_MAXLEN], sam_acct2[SMB_SAMACCT_MAXLEN];
2049 	smb_ads_qstat_t rc;
2050 
2051 	if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
2052 		return (SMB_ADS_STAT_ERR);
2053 
2054 	res = NULL;
2055 	attrs[0] = SMB_ADS_ATTR_DNSHOST;
2056 	attrs[1] = NULL;
2057 	attrs[2] = NULL;
2058 
2059 	if (avpair) {
2060 		if (!avpair->avp_attr)
2061 			return (SMB_ADS_STAT_ERR);
2062 
2063 		attrs[1] = avpair->avp_attr;
2064 	}
2065 
2066 	if (smb_ads_escape_search_filter_chars(sam_acct, sam_acct2) != 0)
2067 		return (SMB_ADS_STAT_ERR);
2068 
2069 	(void) snprintf(filter, sizeof (filter),
2070 	    "(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT,
2071 	    sam_acct2);
2072 
2073 	if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0,
2074 	    &res) != LDAP_SUCCESS) {
2075 		(void) ldap_msgfree(res);
2076 		return (SMB_ADS_STAT_NOT_FOUND);
2077 	}
2078 
2079 	rc = smb_ads_get_qstat(ah, res, avpair);
2080 	/* free the search results */
2081 	(void) ldap_msgfree(res);
2082 	return (rc);
2083 }
2084 
2085 /*
2086  * smb_ads_find_computer
2087  *
2088  * Starts by searching for the system's AD computer object in the default
2089  * container (i.e. cn=Computers).  If not found, searches the entire directory.
2090  * If found, 'dn' will be set to the distinguished name of the system's AD
2091  * computer object.
2092  */
2093 static smb_ads_qstat_t
2094 smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
2095 {
2096 	smb_ads_qstat_t stat;
2097 	smb_ads_avpair_t avpair;
2098 
2099 	avpair.avp_attr = SMB_ADS_ATTR_DN;
2100 	smb_ads_get_default_comp_container_dn(ah, dn, SMB_ADS_DN_MAX);
2101 	stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_ONELEVEL,
2102 	    dn);
2103 
2104 	if (stat == SMB_ADS_STAT_NOT_FOUND) {
2105 		(void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX);
2106 		stat = smb_ads_lookup_computer_n_attr(ah, &avpair,
2107 		    LDAP_SCOPE_SUBTREE, dn);
2108 	}
2109 
2110 	if (stat == SMB_ADS_STAT_FOUND) {
2111 		(void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX);
2112 		free(avpair.avp_val);
2113 	}
2114 
2115 	return (stat);
2116 }
2117 
2118 /*
2119  * smb_ads_update_computer_cntrl_attr
2120  *
2121  * Modify the user account control attribute of an existing computer
2122  * object on AD.
2123  *
2124  * Returns LDAP error code.
2125  */
2126 static int
2127 smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn)
2128 {
2129 	LDAPMod *attrs[2];
2130 	char *ctl_val[2];
2131 	int ret = 0;
2132 	char usrctl_buf[16];
2133 
2134 	if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
2135 		return (LDAP_NO_MEMORY);
2136 
2137 	attrs[0]->mod_op = LDAP_MOD_REPLACE;
2138 	attrs[0]->mod_type = SMB_ADS_ATTR_CTL;
2139 
2140 	(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags);
2141 	ctl_val[0] = usrctl_buf;
2142 	ctl_val[1] = 0;
2143 	attrs[0]->mod_values = ctl_val;
2144 	if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
2145 		syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
2146 	}
2147 
2148 	smb_ads_free_attr(attrs);
2149 	return (ret);
2150 }
2151 
2152 /*
2153  * smb_ads_lookup_computer_attr_kvno
2154  *
2155  * Lookup the value of the Kerberos version number attribute of the computer
2156  * account.
2157  */
2158 static krb5_kvno
2159 smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
2160 {
2161 	smb_ads_avpair_t avpair;
2162 	int kvno = 1;
2163 
2164 	avpair.avp_attr = SMB_ADS_ATTR_KVNO;
2165 	if (smb_ads_lookup_computer_n_attr(ah, &avpair,
2166 	    LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) {
2167 		kvno = atoi(avpair.avp_val);
2168 		free(avpair.avp_val);
2169 	}
2170 
2171 	return (kvno);
2172 }
2173 
2174 static int
2175 smb_ads_gen_machine_passwd(char *machine_passwd, size_t bufsz)
2176 {
2177 	int i;
2178 	size_t pwdlen;
2179 	uint8_t *random_bytes;
2180 
2181 	errno = 0;
2182 	if (machine_passwd == NULL || bufsz == 0) {
2183 		errno = EINVAL;
2184 		return (-1);
2185 	}
2186 
2187 	pwdlen = bufsz - 1;
2188 	random_bytes = calloc(1, pwdlen);
2189 	if (random_bytes == NULL)
2190 		return (-1);
2191 
2192 	if (pkcs11_get_random(random_bytes, pwdlen) != 0) {
2193 		free(random_bytes);
2194 		return (-1);
2195 	}
2196 
2197 	for (i = 0; i < pwdlen; i++)
2198 		machine_passwd[i] = (random_bytes[i] % SMB_ADS_PWD_CHAR_NUM) +
2199 		    SMB_ADS_PWD_CHAR_START;
2200 
2201 	machine_passwd[pwdlen] = 0;
2202 	bzero(random_bytes, pwdlen);
2203 	free(random_bytes);
2204 	return (0);
2205 }
2206 
2207 /*
2208  * smb_ads_join
2209  *
2210  * Besides the NT-4 style domain join (using MS-RPC), CIFS server also
2211  * provides the domain join using Kerberos Authentication, Keberos
2212  * Change & Set password, and LDAP protocols. Basically, AD join
2213  * operation would require the following tickets to be acquired for the
2214  * the user account that is provided for the domain join.
2215  *
2216  * 1) a Keberos TGT ticket,
2217  * 2) a ldap service ticket, and
2218  * 3) kadmin/changpw service ticket
2219  *
2220  * The ADS client first sends a ldap search request to find out whether
2221  * or not the workstation trust account already exists in the Active Directory.
2222  * The existing computer object for this workstation will be removed and
2223  * a new one will be added. The machine account password is randomly
2224  * generated and set for the newly created computer object using KPASSWD
2225  * protocol (See RFC 3244). Once the password is set, our ADS client
2226  * finalizes the machine account by modifying the user acount control
2227  * attribute of the computer object. Kerberos keys derived from the machine
2228  * account password will be stored locally in /etc/krb5/krb5.keytab file.
2229  * That would be needed while acquiring Kerberos TGT ticket for the host
2230  * principal after the domain join operation.
2231  */
2232 smb_adjoin_status_t
2233 smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd,
2234     size_t len)
2235 {
2236 	smb_ads_handle_t *ah = NULL;
2237 	krb5_context ctx = NULL;
2238 	krb5_principal krb5princs[SMBKRB5_SPN_IDX_MAX];
2239 	krb5_kvno kvno;
2240 	boolean_t des_only, delete = B_TRUE;
2241 	smb_adjoin_status_t rc = SMB_ADJOIN_SUCCESS;
2242 	boolean_t new_acct;
2243 	int dclevel, num, usrctl_flags = 0;
2244 	smb_ads_qstat_t qstat;
2245 	char dn[SMB_ADS_DN_MAX];
2246 	char *tmpfile;
2247 
2248 	/*
2249 	 * Call library functions that can be used to get
2250 	 * the list of encryption algorithms available on the system.
2251 	 * (similar to what 'encrypt -l' CLI does). For now,
2252 	 * unless someone has modified the configuration of the
2253 	 * cryptographic framework (very unlikely), the following is the
2254 	 * list of algorithms available on any system running Nevada
2255 	 * by default.
2256 	 */
2257 	krb5_enctype w2k8enctypes[] = {ENCTYPE_AES256_CTS_HMAC_SHA1_96,
2258 	    ENCTYPE_AES128_CTS_HMAC_SHA1_96, ENCTYPE_ARCFOUR_HMAC,
2259 	    ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5,
2260 	};
2261 
2262 	krb5_enctype pre_w2k8enctypes[] = {ENCTYPE_ARCFOUR_HMAC,
2263 	    ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5,
2264 	};
2265 
2266 	krb5_enctype *encptr;
2267 
2268 	if ((ah = smb_ads_open_main(domain, user, usr_passwd)) == NULL) {
2269 		smb_ccache_remove(SMB_CCACHE_PATH);
2270 		return (SMB_ADJOIN_ERR_GET_HANDLE);
2271 	}
2272 
2273 	if (smb_ads_gen_machine_passwd(machine_passwd, len) != 0) {
2274 		syslog(LOG_NOTICE, "machine password generation: %m");
2275 		smb_ads_close(ah);
2276 		smb_ccache_remove(SMB_CCACHE_PATH);
2277 		return (SMB_ADJOIN_ERR_GEN_PWD);
2278 	}
2279 
2280 	if ((dclevel = smb_ads_get_dc_level(ah)) == -1) {
2281 		smb_ads_close(ah);
2282 		smb_ccache_remove(SMB_CCACHE_PATH);
2283 		return (SMB_ADJOIN_ERR_GET_DCLEVEL);
2284 	}
2285 
2286 	qstat = smb_ads_find_computer(ah, dn);
2287 	switch (qstat) {
2288 	case SMB_ADS_STAT_FOUND:
2289 		new_acct = B_FALSE;
2290 		if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
2291 			smb_ads_close(ah);
2292 			smb_ccache_remove(SMB_CCACHE_PATH);
2293 			return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT);
2294 		}
2295 		break;
2296 
2297 	case SMB_ADS_STAT_NOT_FOUND:
2298 		new_acct = B_TRUE;
2299 		smb_ads_get_default_comp_dn(ah, dn, SMB_ADS_DN_MAX);
2300 		if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
2301 			smb_ads_close(ah);
2302 			smb_ccache_remove(SMB_CCACHE_PATH);
2303 			return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT);
2304 		}
2305 		break;
2306 
2307 	default:
2308 		if (qstat == SMB_ADS_STAT_DUP)
2309 			rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT;
2310 		else
2311 			rc = SMB_ADJOIN_ERR_TRUST_ACCT;
2312 		smb_ads_close(ah);
2313 		smb_ccache_remove(SMB_CCACHE_PATH);
2314 		return (rc);
2315 	}
2316 
2317 	des_only = B_FALSE;
2318 
2319 	if (smb_krb5_ctx_init(&ctx) != 0) {
2320 		rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
2321 		goto adjoin_cleanup;
2322 	}
2323 
2324 	if (smb_krb5_get_principals(ah->domain, ctx, krb5princs) != 0) {
2325 		rc = SMB_ADJOIN_ERR_GET_SPNS;
2326 		goto adjoin_cleanup;
2327 	}
2328 
2329 	if (smb_krb5_setpwd(ctx, krb5princs[SMBKRB5_SPN_IDX_HOST],
2330 	    machine_passwd) != 0) {
2331 		rc = SMB_ADJOIN_ERR_KSETPWD;
2332 		goto adjoin_cleanup;
2333 	}
2334 
2335 	kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
2336 
2337 	/*
2338 	 * Only members of Domain Admins and Enterprise Admins can set
2339 	 * the TRUSTED_FOR_DELEGATION userAccountControl flag.
2340 	 */
2341 	if (smb_ads_update_computer_cntrl_attr(ah,
2342 	    SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
2343 	    SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION, dn)
2344 	    == LDAP_INSUFFICIENT_ACCESS) {
2345 		usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
2346 		    SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
2347 
2348 		syslog(LOG_NOTICE, "Unable to set the "
2349 		    "TRUSTED_FOR_DELEGATION userAccountControl flag on "
2350 		    "the machine account in Active Directory.  Please refer "
2351 		    "to the Troubleshooting guide for more information.");
2352 
2353 	} else {
2354 		usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
2355 		    SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION |
2356 		    SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
2357 	}
2358 
2359 	if (des_only)
2360 		usrctl_flags |= SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY;
2361 
2362 	if (smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn)
2363 	    != 0) {
2364 		rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
2365 		goto adjoin_cleanup;
2366 	}
2367 
2368 	if (dclevel >= SMB_ADS_DCLEVEL_W2K8) {
2369 		num = sizeof (w2k8enctypes) / sizeof (krb5_enctype);
2370 		encptr = w2k8enctypes;
2371 	} else {
2372 		num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype);
2373 		encptr = pre_w2k8enctypes;
2374 	}
2375 
2376 	tmpfile = mktemp(SMBNS_KRB5_KEYTAB_TMP);
2377 	if (tmpfile == NULL)
2378 		tmpfile = SMBNS_KRB5_KEYTAB_TMP;
2379 
2380 	if (smb_krb5_add_keytab_entries(ctx, krb5princs, tmpfile,
2381 	    kvno, machine_passwd, encptr, num) != 0) {
2382 		rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
2383 		goto adjoin_cleanup;
2384 	}
2385 
2386 	delete = B_FALSE;
2387 adjoin_cleanup:
2388 	if (new_acct && delete)
2389 		smb_ads_del_computer(ah, dn);
2390 
2391 	if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) {
2392 		if (rc != SMB_ADJOIN_ERR_GET_SPNS)
2393 			smb_krb5_free_principals(ctx, krb5princs,
2394 			    SMBKRB5_SPN_IDX_MAX);
2395 		smb_krb5_ctx_fini(ctx);
2396 	}
2397 
2398 	/* commit keytab file */
2399 	if (rc == SMB_ADJOIN_SUCCESS) {
2400 		if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) {
2401 			(void) unlink(tmpfile);
2402 			rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB;
2403 		} else {
2404 			/* Set IDMAP config */
2405 			if (smb_config_set_idmap_domain(ah->domain) != 0) {
2406 				rc = SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN;
2407 			} else {
2408 
2409 				/* Refresh IDMAP service */
2410 				if (smb_config_refresh_idmap() != 0)
2411 					rc = SMB_ADJOIN_ERR_IDMAP_REFRESH;
2412 			}
2413 		}
2414 	} else {
2415 		(void) unlink(tmpfile);
2416 	}
2417 
2418 	smb_ads_close(ah);
2419 	smb_ccache_remove(SMB_CCACHE_PATH);
2420 	return (rc);
2421 }
2422 
2423 /*
2424  * smb_ads_join_errmsg
2425  *
2426  * Display error message for the specific adjoin error code.
2427  */
2428 void
2429 smb_ads_join_errmsg(smb_adjoin_status_t status)
2430 {
2431 	int i;
2432 	struct xlate_table {
2433 		smb_adjoin_status_t status;
2434 		char *msg;
2435 	} adjoin_table[] = {
2436 		{ SMB_ADJOIN_ERR_GET_HANDLE, "Failed to connect to an "
2437 		    "Active Directory server." },
2438 		{ SMB_ADJOIN_ERR_GEN_PWD, "Failed to generate machine "
2439 		    "password." },
2440 		{ SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of "
2441 		    "the domain controller. The rootDSE attribute named "
2442 		    "\"domainControllerFunctionality\" is missing from the "
2443 		    "Active Directory." },
2444 		{ SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the "
2445 		    "workstation trust account." },
2446 		{ SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the "
2447 		    "workstation trust account." },
2448 		{ SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the "
2449 		    "workstation trust account because its name is already "
2450 		    "in use." },
2451 		{ SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the "
2452 		    "workstation trust account" },
2453 		{ SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos "
2454 		    "context." },
2455 		{ SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos "
2456 		    "principals." },
2457 		{ SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." },
2458 		{ SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR,  "Failed to modify "
2459 		    "userAccountControl attribute of the workstation trust "
2460 		    "account." },
2461 		{ SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local "
2462 		    "keytab file (i.e /etc/krb5/krb5.keytab)." },
2463 		{ SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap "
2464 		    "configuration." },
2465 		{ SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap "
2466 		    "service." },
2467 		{ SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to "
2468 		    "local keytab file (i.e. /etc/krb5/krb5.keytab)." }
2469 	};
2470 
2471 	for (i = 0; i < sizeof (adjoin_table) / sizeof (adjoin_table[0]); i++) {
2472 		if (adjoin_table[i].status == status)
2473 			syslog(LOG_NOTICE, "%s", adjoin_table[i].msg);
2474 	}
2475 }
2476 
2477 /*
2478  * smb_ads_match_pdc
2479  *
2480  * Returns B_TRUE if the given host's IP address matches the preferred DC's
2481  * IP address. Otherwise, returns B_FALSE.
2482  */
2483 static boolean_t
2484 smb_ads_match_pdc(smb_ads_host_info_t *host)
2485 {
2486 	boolean_t match = B_FALSE;
2487 
2488 	if (!host)
2489 		return (match);
2490 
2491 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
2492 	if (smb_inet_equal(&host->ipaddr, &smb_ads_cfg.c_pdc))
2493 		match = B_TRUE;
2494 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
2495 
2496 	return (match);
2497 }
2498 
2499 /*
2500  * smb_ads_select_dcfromsubnet
2501  *
2502  * This method walks the list of DCs and returns the first DC record that
2503  * responds to ldap ping and is in the same subnet as the host.
2504  *
2505  * Returns a pointer to the found DC record.
2506  * Returns NULL, on error or if no DC record is found.
2507  */
2508 static smb_ads_host_info_t *
2509 smb_ads_select_dcfromsubnet(smb_ads_host_list_t *hlist)
2510 {
2511 	smb_ads_host_info_t *hentry;
2512 	smb_nic_t *lnic;
2513 	smb_niciter_t ni;
2514 	size_t cnt;
2515 	int i;
2516 
2517 	if (smb_nic_getfirst(&ni) != SMB_NIC_SUCCESS)
2518 		return (NULL);
2519 	do {
2520 		lnic = &ni.ni_nic;
2521 		cnt = hlist->ah_cnt;
2522 
2523 		for (i = 0; i < cnt; i++) {
2524 			hentry = &hlist->ah_list[i];
2525 			if ((hentry->ipaddr.a_family == AF_INET) &&
2526 			    (lnic->nic_ip.a_family == AF_INET)) {
2527 				if ((hentry->ipaddr.a_ipv4 &
2528 				    lnic->nic_mask) ==
2529 				    (lnic->nic_ip.a_ipv4 &
2530 				    lnic->nic_mask))
2531 					if (smb_ads_ldap_ping(hentry) == 0)
2532 						return (hentry);
2533 			}
2534 		}
2535 	} while (smb_nic_getnext(&ni) == SMB_NIC_SUCCESS);
2536 
2537 	return (NULL);
2538 }
2539 
2540 /*
2541  * smb_ads_select_dcfromlist
2542  *
2543  * This method walks the list of DCs and returns the first DC that
2544  * responds to ldap ping.
2545  *
2546  * Returns a pointer to the found DC record.
2547  * Returns NULL if no DC record is found.
2548  */
2549 static smb_ads_host_info_t *
2550 smb_ads_select_dcfromlist(smb_ads_host_list_t *hlist)
2551 {
2552 	smb_ads_host_info_t *hentry;
2553 	size_t cnt;
2554 	int i;
2555 
2556 	cnt = hlist->ah_cnt;
2557 	for (i = 0; i < cnt; i++) {
2558 		hentry = &hlist->ah_list[i];
2559 		if (smb_ads_ldap_ping(hentry) == 0)
2560 			return (hentry);
2561 	}
2562 
2563 	return (NULL);
2564 }
2565 
2566 /*
2567  * smb_ads_dc_compare
2568  *
2569  * Comparision function for sorting host entries (SRV records of DC) via qsort.
2570  * RFC 2052/2782 are taken as reference, while implementing this algorithm.
2571  *
2572  * Domain Controllers(DCs) with lowest priority in their SRV DNS records
2573  * are selected first. If they have equal priorities, then DC with highest
2574  * weight in its SRV DNS record is selected. If the priority and weight are
2575  * both equal, then the DC at the top of the list is selected.
2576  */
2577 static int
2578 smb_ads_dc_compare(const void *p, const void *q)
2579 {
2580 	smb_ads_host_info_t *h1 = (smb_ads_host_info_t *)p;
2581 	smb_ads_host_info_t *h2 = (smb_ads_host_info_t *)q;
2582 
2583 	if (h1->priority < h2->priority)
2584 		return (-1);
2585 	if (h1->priority > h2->priority)
2586 		return (1);
2587 
2588 	/* Priorities are equal */
2589 	if (h1->weight < h2->weight)
2590 		return (1);
2591 	if (h1->weight > h2->weight)
2592 		return (-1);
2593 
2594 	return (0);
2595 }
2596 
2597 /*
2598  * smb_ads_select_dc
2599  *
2600  * The list of ADS hosts returned by ADS lookup, is sorted by lowest priority
2601  * and highest weight. On this sorted list, following additional rules are
2602  * applied, to select a DC.
2603  *
2604  *  - If there is a DC in the same subnet, then return the DC,
2605  *    if it responds to ldap ping.
2606  *  - Else, return first DC that responds to ldap ping.
2607  *
2608  * A reference to the host entry from input host list is returned.
2609  *
2610  * Returns NULL on error.
2611  */
2612 static smb_ads_host_info_t *
2613 smb_ads_select_dc(smb_ads_host_list_t *hlist)
2614 {
2615 	smb_ads_host_info_t *hentry = NULL;
2616 
2617 	if (hlist->ah_cnt == 0)
2618 		return (NULL);
2619 
2620 	if (hlist->ah_cnt == 1) {
2621 		hentry = hlist->ah_list;
2622 		if (smb_ads_ldap_ping(hentry) == 0)
2623 			return (hentry);
2624 	}
2625 
2626 	/* Sort the list by priority and weight */
2627 	qsort(hlist->ah_list, hlist->ah_cnt,
2628 	    sizeof (smb_ads_host_info_t), smb_ads_dc_compare);
2629 
2630 	if ((hentry = smb_ads_select_dcfromsubnet(hlist)) != NULL)
2631 		return (hentry);
2632 
2633 	if ((hentry = smb_ads_select_dcfromlist(hlist)) != NULL)
2634 		return (hentry);
2635 
2636 	return (NULL);
2637 }
2638 
2639 /*
2640  * smb_ads_lookup_msdcs
2641  *
2642  * If server argument is set, try to locate the specified DC.
2643  * If it is set to empty string, locate any DCs in the specified domain.
2644  * Returns the discovered DC via buf.
2645  *
2646  * fqdn	  - fully-qualified domain name
2647  * server - fully-qualifed hostname of a DC
2648  * buf    - the hostname of the discovered DC
2649  */
2650 boolean_t
2651 smb_ads_lookup_msdcs(char *fqdn, char *server, char *buf, uint32_t buflen)
2652 {
2653 	smb_ads_host_info_t *hinfo = NULL;
2654 	char *p;
2655 	char *sought_host;
2656 	char ipstr[INET6_ADDRSTRLEN];
2657 
2658 	if (!fqdn || !buf)
2659 		return (B_FALSE);
2660 
2661 	ipstr[0] = '\0';
2662 	*buf = '\0';
2663 	sought_host = (*server == 0 ? NULL : server);
2664 	if ((hinfo = smb_ads_find_host(fqdn, sought_host)) == NULL)
2665 		return (B_FALSE);
2666 
2667 	(void) smb_inet_ntop(&hinfo->ipaddr, ipstr,
2668 	    SMB_IPSTRLEN(hinfo->ipaddr.a_family));
2669 	smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr);
2670 
2671 	(void) strlcpy(buf, hinfo->name, buflen);
2672 	/*
2673 	 * Remove the domain extension
2674 	 */
2675 	if ((p = strchr(buf, '.')) != 0)
2676 		*p = '\0';
2677 
2678 	free(hinfo);
2679 	return (B_TRUE);
2680 }
2681