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