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