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