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