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