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