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