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