xref: /illumos-gate/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c (revision c94be9439c4f0773ef60e2cec21d548359cfea20)
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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
24  */
25 
26 #include <sys/param.h>
27 #include <ldap.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <sys/time.h>
34 #include <netdb.h>
35 #include <pthread.h>
36 #include <unistd.h>
37 #include <arpa/nameser.h>
38 #include <resolv.h>
39 #include <sys/synch.h>
40 #include <string.h>
41 #include <strings.h>
42 #include <fcntl.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <assert.h>
46 #include <sasl/sasl.h>
47 #include <note.h>
48 #include <errno.h>
49 #include <cryptoutil.h>
50 #include <ads/dsgetdc.h>
51 
52 #include <smbsrv/libsmbns.h>
53 #include <smbns_dyndns.h>
54 #include <smbns_krb.h>
55 
56 #define	SMB_ADS_AF_UNKNOWN(x)	(((x)->ipaddr.a_family != AF_INET) && \
57 	((x)->ipaddr.a_family != AF_INET6))
58 
59 #define	SMB_ADS_MAXBUFLEN 100
60 #define	SMB_ADS_DN_MAX	300
61 #define	SMB_ADS_MAXMSGLEN 512
62 #define	SMB_ADS_COMPUTERS_CN "Computers"
63 #define	SMB_ADS_COMPUTER_NUM_ATTR 8
64 #define	SMB_ADS_SHARE_NUM_ATTR 3
65 #define	SMB_ADS_SITE_MAX MAXHOSTNAMELEN
66 
67 #define	SMB_ADS_MSDCS_SRV_DC_RR		"_ldap._tcp.dc._msdcs"
68 #define	SMB_ADS_MSDCS_SRV_SITE_RR	"_ldap._tcp.%s._sites.dc._msdcs"
69 
70 /*
71  * domainControllerFunctionality
72  *
73  * This rootDSE attribute indicates the functional level of the DC.
74  */
75 #define	SMB_ADS_ATTR_DCLEVEL	"domainControllerFunctionality"
76 #define	SMB_ADS_DCLEVEL_W2K	0
77 #define	SMB_ADS_DCLEVEL_W2K3	2
78 #define	SMB_ADS_DCLEVEL_W2K8	3
79 #define	SMB_ADS_DCLEVEL_W2K8_R2 4
80 
81 /*
82  * msDs-supportedEncryptionTypes (Windows Server 2008 only)
83  *
84  * This attribute defines the encryption types supported by the system.
85  * Encryption Types:
86  *  - DES cbc mode with CRC-32
87  *  - DES cbc mode with RSA-MD5
88  *  - ArcFour with HMAC/md5
89  *  - AES-128
90  *  - AES-256
91  */
92 #define	SMB_ADS_ATTR_ENCTYPES	"msDs-supportedEncryptionTypes"
93 #define	SMB_ADS_ENC_DES_CRC	1
94 #define	SMB_ADS_ENC_DES_MD5	2
95 #define	SMB_ADS_ENC_RC4		4
96 #define	SMB_ADS_ENC_AES128	8
97 #define	SMB_ADS_ENC_AES256	16
98 
99 static krb5_enctype w2k8enctypes[] = {
100     ENCTYPE_AES256_CTS_HMAC_SHA1_96,
101     ENCTYPE_AES128_CTS_HMAC_SHA1_96,
102     ENCTYPE_ARCFOUR_HMAC,
103     ENCTYPE_DES_CBC_CRC,
104     ENCTYPE_DES_CBC_MD5,
105 };
106 
107 static krb5_enctype pre_w2k8enctypes[] = {
108     ENCTYPE_ARCFOUR_HMAC,
109     ENCTYPE_DES_CBC_CRC,
110     ENCTYPE_DES_CBC_MD5,
111 };
112 
113 #define	SMB_ADS_ATTR_SAMACCT	"sAMAccountName"
114 #define	SMB_ADS_ATTR_UPN	"userPrincipalName"
115 #define	SMB_ADS_ATTR_SPN	"servicePrincipalName"
116 #define	SMB_ADS_ATTR_CTL	"userAccountControl"
117 #define	SMB_ADS_ATTR_DNSHOST	"dNSHostName"
118 #define	SMB_ADS_ATTR_KVNO	"msDS-KeyVersionNumber"
119 #define	SMB_ADS_ATTR_DN		"distinguishedName"
120 
121 /*
122  * UserAccountControl flags: manipulate user account properties.
123  *
124  * The hexadecimal value of the following property flags are based on MSDN
125  * article # 305144.
126  */
127 #define	SMB_ADS_USER_ACCT_CTL_SCRIPT				0x00000001
128 #define	SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE			0x00000002
129 #define	SMB_ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED			0x00000008
130 #define	SMB_ADS_USER_ACCT_CTL_LOCKOUT				0x00000010
131 #define	SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD			0x00000020
132 #define	SMB_ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE		0x00000040
133 #define	SMB_ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED	0x00000080
134 #define	SMB_ADS_USER_ACCT_CTL_TMP_DUP_ACCT			0x00000100
135 #define	SMB_ADS_USER_ACCT_CTL_NORMAL_ACCT			0x00000200
136 #define	SMB_ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT		0x00000800
137 #define	SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT		0x00001000
138 #define	SMB_ADS_USER_ACCT_CTL_SRV_TRUST_ACCT			0x00002000
139 #define	SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD		0x00010000
140 #define	SMB_ADS_USER_ACCT_CTL_MNS_LOGON_ACCT			0x00020000
141 #define	SMB_ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED		0x00040000
142 #define	SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION		0x00080000
143 #define	SMB_ADS_USER_ACCT_CTL_NOT_DELEGATED			0x00100000
144 #define	SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY			0x00200000
145 #define	SMB_ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH			0x00400000
146 #define	SMB_ADS_USER_ACCT_CTL_PASSWD_EXPIRED			0x00800000
147 #define	SMB_ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION	0x01000000
148 
149 /*
150  * Length of "dc=" prefix.
151  */
152 #define	SMB_ADS_DN_PREFIX_LEN	3
153 
154 static char *smb_ads_computer_objcls[] = {
155 	"top", "person", "organizationalPerson",
156 	"user", "computer", NULL
157 };
158 
159 static char *smb_ads_share_objcls[] = {
160 	"top", "leaf", "connectionPoint", "volume", NULL
161 };
162 
163 /* Cached ADS server to communicate with */
164 static smb_ads_host_info_t *smb_ads_cached_host_info = NULL;
165 static mutex_t smb_ads_cached_host_mtx;
166 
167 /*
168  * SMB ADS config cache is maintained to facilitate the detection of
169  * changes in configuration that is relevant to AD selection.
170  */
171 typedef struct smb_ads_config {
172 	char c_site[SMB_ADS_SITE_MAX];
173 	mutex_t c_mtx;
174 } smb_ads_config_t;
175 
176 static smb_ads_config_t smb_ads_cfg;
177 
178 
179 /* attribute/value pair */
180 typedef struct smb_ads_avpair {
181 	char *avp_attr;
182 	char *avp_val;
183 } smb_ads_avpair_t;
184 
185 /* query status */
186 typedef enum smb_ads_qstat {
187 	SMB_ADS_STAT_ERR = -2,
188 	SMB_ADS_STAT_DUP,
189 	SMB_ADS_STAT_NOT_FOUND,
190 	SMB_ADS_STAT_FOUND
191 } smb_ads_qstat_t;
192 
193 typedef struct smb_ads_host_list {
194 	int ah_cnt;
195 	smb_ads_host_info_t *ah_list;
196 } smb_ads_host_list_t;
197 
198 static int smb_ads_open_main(smb_ads_handle_t **, char *, char *, char *);
199 static int smb_ads_add_computer(smb_ads_handle_t *, int, char *);
200 static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *);
201 static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *);
202 static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *,
203     smb_ads_avpair_t *, int, char *);
204 static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *);
205 static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *);
206 static void smb_ads_free_cached_host(void);
207 static int smb_ads_alloc_attr(LDAPMod **, int);
208 static void smb_ads_free_attr(LDAPMod **);
209 static int smb_ads_get_dc_level(smb_ads_handle_t *);
210 static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *, char *);
211 static smb_ads_qstat_t smb_ads_getattr(LDAP *, LDAPMessage *,
212     smb_ads_avpair_t *);
213 static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *, LDAPMessage *,
214     smb_ads_avpair_t *);
215 static boolean_t smb_ads_is_same_domain(char *, char *);
216 static smb_ads_host_info_t *smb_ads_dup_host_info(smb_ads_host_info_t *);
217 static char *smb_ads_get_sharedn(const char *, const char *, const char *);
218 static krb5_enctype *smb_ads_get_enctypes(int, int *);
219 
220 /*
221  * smb_ads_init
222  *
223  * Initializes the ADS config cache.
224  */
225 void
226 smb_ads_init(void)
227 {
228 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
229 	(void) smb_config_getstr(SMB_CI_ADS_SITE,
230 	    smb_ads_cfg.c_site, SMB_ADS_SITE_MAX);
231 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
232 
233 	/* Force -lads to load, for dtrace. */
234 	DsFreeDcInfo(NULL);
235 }
236 
237 void
238 smb_ads_fini(void)
239 {
240 	smb_ads_free_cached_host();
241 }
242 
243 /*
244  * smb_ads_refresh
245  *
246  * This function will be called when smb/server SMF service is refreshed.
247  * (See smbd_join.c)
248  *
249  * Clearing the smb_ads_cached_host_info would allow the next DC
250  * discovery process to pick up an AD based on the new AD configuration.
251  */
252 void
253 smb_ads_refresh(boolean_t force_rediscovery)
254 {
255 	char new_site[SMB_ADS_SITE_MAX];
256 
257 	(void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX);
258 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
259 	(void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX);
260 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
261 
262 	smb_ads_free_cached_host();
263 
264 	if (force_rediscovery) {
265 		(void) _DsForceRediscovery(NULL, 0);
266 	}
267 }
268 
269 
270 /*
271  * smb_ads_build_unc_name
272  *
273  * Construct the UNC name of the share object in the format of
274  * \\hostname.domain\shareUNC
275  *
276  * Returns 0 on success, -1 on error.
277  */
278 int
279 smb_ads_build_unc_name(char *unc_name, int maxlen,
280     const char *hostname, const char *shareUNC)
281 {
282 	char my_domain[MAXHOSTNAMELEN];
283 
284 	if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0)
285 		return (-1);
286 
287 	(void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s",
288 	    hostname, my_domain, shareUNC);
289 	return (0);
290 }
291 
292 /*
293  * The cached ADS host is no longer valid if one of the following criteria
294  * is satisfied:
295  *
296  * 1) not in the specified domain
297  * 2) not the sought host (if specified)
298  * 3) not reachable
299  *
300  * The caller is responsible for acquiring the smb_ads_cached_host_mtx lock
301  * prior to calling this function.
302  *
303  * Return B_TRUE if the cache host is still valid. Otherwise, return B_FALSE.
304  */
305 static boolean_t
306 smb_ads_validate_cache_host(char *domain)
307 {
308 	if (!smb_ads_cached_host_info)
309 		return (B_FALSE);
310 
311 	if (!smb_ads_is_same_domain(smb_ads_cached_host_info->name, domain))
312 		return (B_FALSE);
313 
314 	return (B_TRUE);
315 }
316 
317 /*
318  * smb_ads_match_hosts_same_domain
319  *
320  * Returns true, if the cached ADS host is in the same domain as the
321  * current (given) domain.
322  */
323 static boolean_t
324 smb_ads_is_same_domain(char *cached_host_name, char *current_domain)
325 {
326 	char *cached_host_domain;
327 
328 	if ((cached_host_name == NULL) || (current_domain == NULL))
329 		return (B_FALSE);
330 
331 	cached_host_domain = strchr(cached_host_name, '.');
332 	if (cached_host_domain == NULL)
333 		return (B_FALSE);
334 
335 	++cached_host_domain;
336 	if (smb_strcasecmp(cached_host_domain, current_domain, 0))
337 		return (B_FALSE);
338 
339 	return (B_TRUE);
340 }
341 
342 /*
343  * smb_ads_dup_host_info
344  *
345  * Duplicates the passed smb_ads_host_info_t structure.
346  * Caller must free memory allocated by this method.
347  *
348  * Returns a reference to the duplicated smb_ads_host_info_t structure.
349  * Returns NULL on error.
350  */
351 static smb_ads_host_info_t *
352 smb_ads_dup_host_info(smb_ads_host_info_t *ads_host)
353 {
354 	smb_ads_host_info_t *dup_host;
355 
356 	if (ads_host == NULL)
357 		return (NULL);
358 
359 	dup_host = malloc(sizeof (smb_ads_host_info_t));
360 
361 	if (dup_host != NULL)
362 		bcopy(ads_host, dup_host, sizeof (smb_ads_host_info_t));
363 
364 	return (dup_host);
365 }
366 
367 /*
368  * smb_ads_find_host
369  *
370  * Finds an ADS host in a given domain.
371  *
372  * If the cached host is valid, it will be used. Otherwise, a DC will
373  * be selected based on the following criteria:
374  *
375  * 1) pdc (aka preferred DC) configuration
376  * 2) AD site configuration - the scope of the DNS lookup will be
377  * restricted to the specified site.
378  * 3) DC on the same subnet
379  * 4) DC with the lowest priority/highest weight
380  *
381  * The above items are listed in decreasing preference order. The selected
382  * DC must be online.
383  *
384  * If this function is called during domain join, the specified kpasswd server
385  * takes precedence over preferred DC, AD site, and so on.
386  *
387  * Parameters:
388  *   domain: fully-qualified domain name.
389  *
390  * Returns:
391  *   A copy of the cached host info is returned. The caller is responsible
392  *   for deallocating the memory returned by this function.
393  */
394 /*ARGSUSED*/
395 smb_ads_host_info_t *
396 smb_ads_find_host(char *domain)
397 {
398 	smb_ads_host_info_t *host = NULL;
399 	DOMAIN_CONTROLLER_INFO *dci = NULL;
400 	struct sockaddr_storage *ss;
401 	uint32_t flags = DS_DS_FLAG;
402 	uint32_t status;
403 	int tries;
404 
405 	(void) mutex_lock(&smb_ads_cached_host_mtx);
406 	if (smb_ads_validate_cache_host(domain)) {
407 		host = smb_ads_dup_host_info(smb_ads_cached_host_info);
408 		(void) mutex_unlock(&smb_ads_cached_host_mtx);
409 		return (host);
410 	}
411 
412 	(void) mutex_unlock(&smb_ads_cached_host_mtx);
413 	smb_ads_free_cached_host();
414 
415 	/*
416 	 * The _real_ DC Locator is over in idmapd.
417 	 * Door call over there to get it.
418 	 */
419 	tries = 15;
420 again:
421 	status = _DsGetDcName(
422 	    NULL,	/* ComputerName */
423 	    domain,
424 	    NULL,	/* DomainGuid */
425 	    NULL, 	/* SiteName */
426 	    flags,
427 	    &dci);
428 	switch (status) {
429 	case 0:
430 		break;
431 	/*
432 	 * We can see these errors when joining a domain, if we race
433 	 * asking idmap for the DC before it knows the new domain.
434 	 */
435 	case NT_STATUS_NO_SUCH_DOMAIN:	/* Specified domain unknown */
436 	case NT_STATUS_INVALID_SERVER_STATE:	/*  not in domain mode. */
437 		if (--tries > 0) {
438 			(void) sleep(1);
439 			goto again;
440 		}
441 		/* FALLTHROUGH */
442 	case NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND:
443 	case NT_STATUS_CANT_WAIT:	/* timeout over in idmap */
444 	default:
445 		return (NULL);
446 	}
447 
448 	host = calloc(1, sizeof (*host));
449 	if (host == NULL)
450 		goto out;
451 
452 	(void) strlcpy(host->name, dci->DomainControllerName, MAXHOSTNAMELEN);
453 	ss = (void *)dci->_sockaddr;
454 	switch (ss->ss_family) {
455 	case AF_INET: {
456 		struct sockaddr_in *sin = (void *)ss;
457 		host->port = ntohs(sin->sin_port);
458 		host->ipaddr.a_family = AF_INET;
459 		(void) memcpy(&host->ipaddr.a_ipv4, &sin->sin_addr,
460 		    sizeof (in_addr_t));
461 		break;
462 	}
463 	case AF_INET6: {
464 		struct sockaddr_in6 *sin6 = (void *)ss;
465 		host->port = ntohs(sin6->sin6_port);
466 		host->ipaddr.a_family = AF_INET6;
467 		(void) memcpy(&host->ipaddr.a_ipv6, &sin6->sin6_addr,
468 		    sizeof (in6_addr_t));
469 		break;
470 	}
471 	default:
472 		syslog(LOG_ERR, "no addr for DC %s",
473 		    dci->DomainControllerName);
474 		free(host);
475 		host = NULL;
476 		goto out;
477 	}
478 
479 	(void) mutex_lock(&smb_ads_cached_host_mtx);
480 	if (!smb_ads_cached_host_info)
481 		smb_ads_cached_host_info = smb_ads_dup_host_info(host);
482 	host = smb_ads_dup_host_info(smb_ads_cached_host_info);
483 	(void) mutex_unlock(&smb_ads_cached_host_mtx);
484 
485 out:
486 	DsFreeDcInfo(dci);
487 	return (host);
488 }
489 
490 /*
491  * Return the number of dots in a string.
492  */
493 static int
494 smb_ads_count_dots(const char *s)
495 {
496 	int ndots = 0;
497 
498 	while (*s) {
499 		if (*s++ == '.')
500 			ndots++;
501 	}
502 
503 	return (ndots);
504 }
505 
506 /*
507  * Convert a domain name in dot notation to distinguished name format,
508  * for example: sun.com -> dc=sun,dc=com.
509  *
510  * Returns a pointer to an allocated buffer containing the distinguished
511  * name.
512  */
513 static char *
514 smb_ads_convert_domain(const char *domain_name)
515 {
516 	const char *s;
517 	char *dn_name;
518 	char buf[2];
519 	int ndots;
520 	int len;
521 
522 	if (domain_name == NULL || *domain_name == 0)
523 		return (NULL);
524 
525 	ndots = smb_ads_count_dots(domain_name);
526 	++ndots;
527 	len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1;
528 
529 	if ((dn_name = malloc(len)) == NULL)
530 		return (NULL);
531 
532 	bzero(dn_name, len);
533 	(void) strlcpy(dn_name, "dc=", len);
534 
535 	buf[1] = '\0';
536 	s = domain_name;
537 
538 	while (*s) {
539 		if (*s == '.') {
540 			(void) strlcat(dn_name, ",dc=", len);
541 		} else {
542 			buf[0] = *s;
543 			(void) strlcat(dn_name, buf, len);
544 		}
545 		++s;
546 	}
547 
548 	return (dn_name);
549 }
550 
551 /*
552  * smb_ads_free_cached_host
553  *
554  * Free the memory use by the global smb_ads_cached_host_info & set it to NULL.
555  */
556 static void
557 smb_ads_free_cached_host(void)
558 {
559 	(void) mutex_lock(&smb_ads_cached_host_mtx);
560 	if (smb_ads_cached_host_info) {
561 		free(smb_ads_cached_host_info);
562 		smb_ads_cached_host_info = NULL;
563 	}
564 	(void) mutex_unlock(&smb_ads_cached_host_mtx);
565 }
566 
567 /*
568  * smb_ads_open
569  * Open a LDAP connection to an ADS server if the system is in domain mode.
570  * Acquire both Kerberos TGT and LDAP service tickets for the host principal.
571  *
572  * This function should only be called after the system is successfully joined
573  * to a domain.
574  */
575 smb_ads_handle_t *
576 smb_ads_open(void)
577 {
578 	char domain[MAXHOSTNAMELEN];
579 	smb_ads_handle_t *h;
580 	smb_ads_status_t err;
581 
582 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
583 		return (NULL);
584 
585 	if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
586 		return (NULL);
587 
588 	err = smb_ads_open_main(&h, domain, NULL, NULL);
589 	if (err != 0) {
590 		smb_ads_log_errmsg(err);
591 		return (NULL);
592 	}
593 
594 	return (h);
595 }
596 
597 static int
598 smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts)
599 {
600 	NOTE(ARGUNUSED(ld, defaults));
601 	sasl_interact_t *interact;
602 
603 	if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
604 		return (LDAP_PARAM_ERROR);
605 
606 	/* There should be no extra arguemnts for SASL/GSSAPI authentication */
607 	for (interact = prompts; interact->id != SASL_CB_LIST_END;
608 	    interact++) {
609 		interact->result = NULL;
610 		interact->len = 0;
611 	}
612 	return (LDAP_SUCCESS);
613 }
614 
615 /*
616  * smb_ads_open_main
617  * Open a LDAP connection to an ADS server.
618  * If ADS is enabled and the administrative username, password, and
619  * ADS domain are defined then query DNS to find an ADS server if this is the
620  * very first call to this routine.  After an ADS server is found then this
621  * server will be used everytime this routine is called until the system is
622  * rebooted or the ADS server becomes unavailable then an ADS server will
623  * be queried again.  After the connection is made then an ADS handle
624  * is created to be returned.
625  *
626  * After the LDAP connection, the LDAP version will be set to 3 using
627  * ldap_set_option().
628  *
629  * The LDAP connection is bound before the ADS handle is returned.
630  * Parameters:
631  *   domain - fully-qualified domain name
632  *   user   - the user account for whom the Kerberos TGT ticket and ADS
633  *            service tickets are acquired.
634  *   password - password of the specified user
635  *
636  * Returns:
637  *   NULL              : can't connect to ADS server or other errors
638  *   smb_ads_handle_t* : handle to ADS server
639  */
640 static int
641 smb_ads_open_main(smb_ads_handle_t **hp, char *domain, char *user,
642     char *password)
643 {
644 	smb_ads_handle_t *ah;
645 	LDAP *ld;
646 	int version = 3;
647 	smb_ads_host_info_t *ads_host = NULL;
648 	int err, rc;
649 
650 	*hp = NULL;
651 
652 	if (user != NULL) {
653 		err = smb_kinit(domain, user, password);
654 		if (err != 0)
655 			return (err);
656 		user = NULL;
657 		password = NULL;
658 	}
659 
660 	ads_host = smb_ads_find_host(domain);
661 	if (ads_host == NULL)
662 		return (SMB_ADS_CANT_LOCATE_DC);
663 
664 	ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
665 	if (ah == NULL) {
666 		free(ads_host);
667 		return (ENOMEM);
668 	}
669 
670 	(void) memset(ah, 0, sizeof (smb_ads_handle_t));
671 
672 	if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) {
673 		syslog(LOG_ERR, "smbns: ldap_init failed");
674 		smb_ads_free_cached_host();
675 		free(ah);
676 		free(ads_host);
677 		return (SMB_ADS_LDAP_INIT);
678 	}
679 
680 	if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version)
681 	    != LDAP_SUCCESS) {
682 		smb_ads_free_cached_host();
683 		free(ah);
684 		free(ads_host);
685 		(void) ldap_unbind(ld);
686 		return (SMB_ADS_LDAP_SETOPT);
687 	}
688 
689 	(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
690 	ah->ld = ld;
691 	ah->domain = strdup(domain);
692 
693 	if (ah->domain == NULL) {
694 		smb_ads_close(ah);
695 		free(ads_host);
696 		return (SMB_ADS_LDAP_SETOPT);
697 	}
698 
699 	/*
700 	 * ah->domain is often used for generating service principal name.
701 	 * Convert it to lower case for RFC 4120 section 6.2.1 conformance.
702 	 */
703 	(void) smb_strlwr(ah->domain);
704 	ah->domain_dn = smb_ads_convert_domain(domain);
705 	if (ah->domain_dn == NULL) {
706 		smb_ads_close(ah);
707 		free(ads_host);
708 		return (SMB_ADS_LDAP_SET_DOM);
709 	}
710 
711 	ah->hostname = strdup(ads_host->name);
712 	if (ah->hostname == NULL) {
713 		smb_ads_close(ah);
714 		free(ads_host);
715 		return (ENOMEM);
716 	}
717 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
718 	if (*smb_ads_cfg.c_site != '\0') {
719 		if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) {
720 			smb_ads_close(ah);
721 			(void) mutex_unlock(&smb_ads_cfg.c_mtx);
722 			free(ads_host);
723 			return (ENOMEM);
724 		}
725 	} else {
726 		ah->site = NULL;
727 	}
728 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
729 
730 	rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL,
731 	    LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL);
732 	if (rc != LDAP_SUCCESS) {
733 		syslog(LOG_ERR, "smbns: ldap_sasl_..._bind_s failed (%s)",
734 		    ldap_err2string(rc));
735 		smb_ads_close(ah);
736 		free(ads_host);
737 		return (SMB_ADS_LDAP_SASL_BIND);
738 	}
739 
740 	free(ads_host);
741 	*hp = ah;
742 
743 	return (SMB_ADS_SUCCESS);
744 }
745 
746 /*
747  * smb_ads_close
748  * Close connection to ADS server and free memory allocated for ADS handle.
749  * LDAP unbind is called here.
750  * Parameters:
751  *   ah: handle to ADS server
752  * Returns:
753  *   void
754  */
755 void
756 smb_ads_close(smb_ads_handle_t *ah)
757 {
758 	if (ah == NULL)
759 		return;
760 	/* close and free connection resources */
761 	if (ah->ld)
762 		(void) ldap_unbind(ah->ld);
763 
764 	free(ah->domain);
765 	free(ah->domain_dn);
766 	free(ah->hostname);
767 	free(ah->site);
768 	free(ah);
769 }
770 
771 /*
772  * smb_ads_alloc_attr
773  *
774  * Since the attrs is a null-terminated array, all elements
775  * in the array (except the last one) will point to allocated
776  * memory.
777  */
778 static int
779 smb_ads_alloc_attr(LDAPMod *attrs[], int num)
780 {
781 	int i;
782 
783 	bzero(attrs, num * sizeof (LDAPMod *));
784 	for (i = 0; i < (num - 1); i++) {
785 		attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod));
786 		if (attrs[i] == NULL) {
787 			smb_ads_free_attr(attrs);
788 			return (-1);
789 		}
790 	}
791 
792 	return (0);
793 }
794 
795 /*
796  * smb_ads_free_attr
797  * Free memory allocated when publishing a share.
798  * Parameters:
799  *   attrs: an array of LDAPMod pointers
800  * Returns:
801  *   None
802  */
803 static void
804 smb_ads_free_attr(LDAPMod *attrs[])
805 {
806 	int i;
807 	for (i = 0; attrs[i]; i++) {
808 		free(attrs[i]);
809 	}
810 }
811 
812 /*
813  * Returns share DN in an allocated buffer.  The format of the DN is
814  * cn=<sharename>,<container RDNs>,<domain DN>
815  *
816  * If the domain DN is not included in the container parameter,
817  * then it will be appended to create the share DN.
818  *
819  * The caller must free the allocated buffer.
820  */
821 static char *
822 smb_ads_get_sharedn(const char *sharename, const char *container,
823     const char *domain_dn)
824 {
825 	char *share_dn;
826 	int rc, offset, container_len, domain_len;
827 	boolean_t append_domain = B_TRUE;
828 
829 	container_len = strlen(container);
830 	domain_len = strlen(domain_dn);
831 
832 	if (container_len >= domain_len) {
833 
834 		/* offset to last domain_len characters */
835 		offset = container_len - domain_len;
836 
837 		if (smb_strcasecmp(container + offset,
838 		    domain_dn, domain_len) == 0)
839 			append_domain = B_FALSE;
840 	}
841 
842 	if (append_domain)
843 		rc = asprintf(&share_dn, "cn=%s,%s,%s", sharename,
844 		    container, domain_dn);
845 	else
846 		rc = asprintf(&share_dn, "cn=%s,%s", sharename,
847 		    container);
848 
849 	return ((rc == -1) ? NULL : share_dn);
850 }
851 
852 /*
853  * smb_ads_add_share
854  * Call by smb_ads_publish_share to create share object in ADS.
855  * This routine specifies the attributes of an ADS LDAP share object. The first
856  * attribute and values define the type of ADS object, the share object.  The
857  * second attribute and value define the UNC of the share data for the share
858  * object. The LDAP synchronous add command is used to add the object into ADS.
859  * The container location to add the object needs to specified.
860  * Parameters:
861  *   ah          : handle to ADS server
862  *   adsShareName: name of share object to be created in ADS
863  *   shareUNC    : share name on NetForce
864  *   adsContainer: location in ADS to create share object
865  *
866  * Returns:
867  *   -1          : error
868  *    0          : success
869  */
870 int
871 smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName,
872     const char *unc_name, const char *adsContainer)
873 {
874 	LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR];
875 	int j = 0;
876 	char *share_dn;
877 	int ret;
878 	char *unc_names[] = {(char *)unc_name, NULL};
879 
880 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
881 	    ah->domain_dn)) == NULL)
882 		return (-1);
883 
884 	if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) {
885 		free(share_dn);
886 		return (-1);
887 	}
888 
889 	attrs[j]->mod_op = LDAP_MOD_ADD;
890 	attrs[j]->mod_type = "objectClass";
891 	attrs[j]->mod_values = smb_ads_share_objcls;
892 
893 	attrs[++j]->mod_op = LDAP_MOD_ADD;
894 	attrs[j]->mod_type = "uNCName";
895 	attrs[j]->mod_values = unc_names;
896 
897 	if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) {
898 		if (ret == LDAP_NO_SUCH_OBJECT) {
899 			syslog(LOG_ERR, "Failed to publish share %s in" \
900 			    " AD.  Container does not exist: %s.\n",
901 			    adsShareName, share_dn);
902 
903 		} else {
904 			syslog(LOG_ERR, "Failed to publish share %s in" \
905 			    " AD: %s (%s).\n", adsShareName, share_dn,
906 			    ldap_err2string(ret));
907 		}
908 		smb_ads_free_attr(attrs);
909 		free(share_dn);
910 		return (ret);
911 	}
912 	free(share_dn);
913 	smb_ads_free_attr(attrs);
914 
915 	return (0);
916 }
917 
918 /*
919  * smb_ads_del_share
920  * Call by smb_ads_remove_share to remove share object from ADS.  The container
921  * location to remove the object needs to specified.  The LDAP synchronous
922  * delete command is used.
923  * Parameters:
924  *   ah          : handle to ADS server
925  *   adsShareName: name of share object in ADS to be removed
926  *   adsContainer: location of share object in ADS
927  * Returns:
928  *   -1          : error
929  *    0          : success
930  */
931 static int
932 smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName,
933     const char *adsContainer)
934 {
935 	char *share_dn;
936 	int ret;
937 
938 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
939 	    ah->domain_dn)) == NULL)
940 		return (-1);
941 
942 	if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) {
943 		smb_tracef("ldap_delete: %s", ldap_err2string(ret));
944 		free(share_dn);
945 		return (-1);
946 	}
947 	free(share_dn);
948 
949 	return (0);
950 }
951 
952 
953 /*
954  * smb_ads_escape_search_filter_chars
955  *
956  * This routine will escape the special characters found in a string
957  * that will later be passed to the ldap search filter.
958  *
959  * RFC 1960 - A String Representation of LDAP Search Filters
960  * 3.  String Search Filter Definition
961  * If a value must contain one of the characters '*' OR '(' OR ')',
962  * these characters
963  * should be escaped by preceding them with the backslash '\' character.
964  *
965  * RFC 2252 - LDAP Attribute Syntax Definitions
966  * a backslash quoting mechanism is used to escape
967  * the following separator symbol character (such as "'", "$" or "#") if
968  * it should occur in that string.
969  */
970 static int
971 smb_ads_escape_search_filter_chars(const char *src, char *dst)
972 {
973 	int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */
974 
975 	if (src == NULL || dst == NULL)
976 		return (-1);
977 
978 	while (*src) {
979 		if (!avail) {
980 			*dst = 0;
981 			return (-1);
982 		}
983 
984 		switch (*src) {
985 		case '\\':
986 		case '\'':
987 		case '$':
988 		case '#':
989 		case '*':
990 		case '(':
991 		case ')':
992 			*dst++ = '\\';
993 			avail--;
994 			/* fall through */
995 
996 		default:
997 			*dst++ = *src++;
998 			avail--;
999 		}
1000 	}
1001 
1002 	*dst = 0;
1003 
1004 	return (0);
1005 }
1006 
1007 /*
1008  * smb_ads_lookup_share
1009  * The search filter is set to search for a specific share name in the
1010  * specified ADS container.  The LDSAP synchronous search command is used.
1011  * Parameters:
1012  *   ah          : handle to ADS server
1013  *   adsShareName: name of share object in ADS to be searched
1014  *   adsContainer: location of share object in ADS
1015  * Returns:
1016  *   -1          : error
1017  *    0          : not found
1018  *    1          : found
1019  */
1020 int
1021 smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName,
1022     const char *adsContainer, char *unc_name)
1023 {
1024 	char *attrs[4], filter[SMB_ADS_MAXBUFLEN];
1025 	char *share_dn;
1026 	int ret;
1027 	LDAPMessage *res;
1028 	char tmpbuf[SMB_ADS_MAXBUFLEN];
1029 
1030 	if (adsShareName == NULL || adsContainer == NULL)
1031 		return (-1);
1032 
1033 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1034 	    ah->domain_dn)) == NULL)
1035 		return (-1);
1036 
1037 	res = NULL;
1038 	attrs[0] = "cn";
1039 	attrs[1] = "objectClass";
1040 	attrs[2] = "uNCName";
1041 	attrs[3] = NULL;
1042 
1043 	if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) {
1044 		free(share_dn);
1045 		return (-1);
1046 	}
1047 
1048 	(void) snprintf(filter, sizeof (filter),
1049 	    "(&(objectClass=volume)(uNCName=%s))", tmpbuf);
1050 
1051 	if ((ret = ldap_search_s(ah->ld, share_dn,
1052 	    LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) {
1053 		if (ret != LDAP_NO_SUCH_OBJECT)
1054 			smb_tracef("%s: ldap_search: %s", share_dn,
1055 			    ldap_err2string(ret));
1056 
1057 		(void) ldap_msgfree(res);
1058 		free(share_dn);
1059 		return (0);
1060 	}
1061 
1062 	(void) free(share_dn);
1063 
1064 	/* no match is found */
1065 	if (ldap_count_entries(ah->ld, res) == 0) {
1066 		(void) ldap_msgfree(res);
1067 		return (0);
1068 	}
1069 
1070 	/* free the search results */
1071 	(void) ldap_msgfree(res);
1072 
1073 	return (1);
1074 }
1075 
1076 /*
1077  * smb_ads_publish_share
1078  * Publish share into ADS.  If a share name already exist in ADS in the same
1079  * container then the existing share object is removed before adding the new
1080  * share object.
1081  * Parameters:
1082  *   ah          : handle return from smb_ads_open
1083  *   adsShareName: name of share to be added to ADS directory
1084  *   shareUNC    : name of share on client, can be NULL to use the same name
1085  *                 as adsShareName
1086  *   adsContainer: location for share to be added in ADS directory, ie
1087  *                   ou=share_folder
1088  *   uncType     : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR
1089  *                   to use host ip addr for UNC.
1090  * Returns:
1091  *   -1          : error
1092  *    0          : success
1093  */
1094 int
1095 smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName,
1096     const char *shareUNC, const char *adsContainer, const char *hostname)
1097 {
1098 	int ret;
1099 	char unc_name[SMB_ADS_MAXBUFLEN];
1100 
1101 	if (adsShareName == NULL || adsContainer == NULL)
1102 		return (-1);
1103 
1104 	if (shareUNC == 0 || *shareUNC == 0)
1105 		shareUNC = adsShareName;
1106 
1107 	if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1108 	    hostname, shareUNC) < 0)
1109 		return (-1);
1110 
1111 	ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1112 
1113 	switch (ret) {
1114 	case 1:
1115 		(void) smb_ads_del_share(ah, adsShareName, adsContainer);
1116 		ret = smb_ads_add_share(ah, adsShareName, unc_name,
1117 		    adsContainer);
1118 		break;
1119 
1120 	case 0:
1121 		ret = smb_ads_add_share(ah, adsShareName, unc_name,
1122 		    adsContainer);
1123 		if (ret == LDAP_ALREADY_EXISTS)
1124 			ret = -1;
1125 
1126 		break;
1127 
1128 	case -1:
1129 	default:
1130 		/* return with error code */
1131 		ret = -1;
1132 	}
1133 
1134 	return (ret);
1135 }
1136 
1137 /*
1138  * smb_ads_remove_share
1139  * Remove share from ADS.  A search is done first before explicitly removing
1140  * the share.
1141  * Parameters:
1142  *   ah          : handle return from smb_ads_open
1143  *   adsShareName: name of share to be removed from ADS directory
1144  *   adsContainer: location for share to be removed from ADS directory, ie
1145  *                   ou=share_folder
1146  * Returns:
1147  *   -1          : error
1148  *    0          : success
1149  */
1150 int
1151 smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName,
1152     const char *shareUNC, const char *adsContainer, const char *hostname)
1153 {
1154 	int ret;
1155 	char unc_name[SMB_ADS_MAXBUFLEN];
1156 
1157 	if (adsShareName == NULL || adsContainer == NULL)
1158 		return (-1);
1159 	if (shareUNC == 0 || *shareUNC == 0)
1160 		shareUNC = adsShareName;
1161 
1162 	if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1163 	    hostname, shareUNC) < 0)
1164 		return (-1);
1165 
1166 	ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1167 	if (ret == 0)
1168 		return (0);
1169 	if (ret == -1)
1170 		return (-1);
1171 
1172 	return (smb_ads_del_share(ah, adsShareName, adsContainer));
1173 }
1174 
1175 /*
1176  * smb_ads_get_default_comp_container_dn
1177  *
1178  * Build the distinguished name for the default computer conatiner (i.e. the
1179  * pre-defined Computers container).
1180  */
1181 static void
1182 smb_ads_get_default_comp_container_dn(smb_ads_handle_t *ah, char *buf,
1183     size_t buflen)
1184 {
1185 	(void) snprintf(buf, buflen, "cn=%s,%s", SMB_ADS_COMPUTERS_CN,
1186 	    ah->domain_dn);
1187 }
1188 
1189 /*
1190  * smb_ads_get_default_comp_dn
1191  *
1192  * Build the distinguished name for this system.
1193  */
1194 static void
1195 smb_ads_get_default_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen)
1196 {
1197 	char nbname[NETBIOS_NAME_SZ];
1198 	char container_dn[SMB_ADS_DN_MAX];
1199 
1200 	(void) smb_getnetbiosname(nbname, sizeof (nbname));
1201 	smb_ads_get_default_comp_container_dn(ah, container_dn, SMB_ADS_DN_MAX);
1202 	(void) snprintf(buf, buflen, "cn=%s,%s", nbname, container_dn);
1203 }
1204 
1205 /*
1206  * smb_ads_add_computer
1207  *
1208  * Returns 0 upon success. Otherwise, returns -1.
1209  */
1210 static int
1211 smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1212 {
1213 	return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn));
1214 }
1215 
1216 /*
1217  * smb_ads_modify_computer
1218  *
1219  * Returns 0 upon success. Otherwise, returns -1.
1220  */
1221 static int
1222 smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1223 {
1224 	return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn));
1225 }
1226 
1227 /*
1228  * smb_ads_get_dc_level
1229  *
1230  * Returns the functional level of the DC upon success.
1231  * Otherwise, -1 is returned.
1232  */
1233 static int
1234 smb_ads_get_dc_level(smb_ads_handle_t *ah)
1235 {
1236 	LDAPMessage *res, *entry;
1237 	char *attr[2];
1238 	char **vals;
1239 	int rc = -1;
1240 
1241 	res = NULL;
1242 	attr[0] = SMB_ADS_ATTR_DCLEVEL;
1243 	attr[1] = NULL;
1244 	if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr,
1245 	    0, &res) != LDAP_SUCCESS) {
1246 		(void) ldap_msgfree(res);
1247 		return (-1);
1248 	}
1249 
1250 	/* no match for the specified attribute is found */
1251 	if (ldap_count_entries(ah->ld, res) == 0) {
1252 		(void) ldap_msgfree(res);
1253 		return (-1);
1254 	}
1255 
1256 	entry = ldap_first_entry(ah->ld, res);
1257 	if (entry) {
1258 		if ((vals = ldap_get_values(ah->ld, entry,
1259 		    SMB_ADS_ATTR_DCLEVEL)) == NULL) {
1260 			/*
1261 			 * Observed the values aren't populated
1262 			 * by the Windows 2000 server.
1263 			 */
1264 			(void) ldap_msgfree(res);
1265 			return (SMB_ADS_DCLEVEL_W2K);
1266 		}
1267 
1268 		if (vals[0] != NULL)
1269 			rc = atoi(vals[0]);
1270 
1271 		ldap_value_free(vals);
1272 	}
1273 
1274 	(void) ldap_msgfree(res);
1275 	return (rc);
1276 }
1277 
1278 /*
1279  * The fully-qualified hostname returned by this function is often used for
1280  * constructing service principal name.  Return the fully-qualified hostname
1281  * in lower case for RFC 4120 section 6.2.1 conformance.
1282  */
1283 static int
1284 smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len)
1285 {
1286 	if (smb_gethostname(fqhost, len, SMB_CASE_LOWER) != 0)
1287 		return (-1);
1288 
1289 	(void) strlcat(fqhost, ".", len);
1290 	(void) strlcat(fqhost, ah->domain, len);
1291 
1292 	return (0);
1293 }
1294 
1295 static int
1296 smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
1297 {
1298 	LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR];
1299 	char *sam_val[2];
1300 	char *ctl_val[2], *fqh_val[2];
1301 	char *encrypt_val[2];
1302 	int j = -1;
1303 	int ret, usrctl_flags = 0;
1304 	char sam_acct[SMB_SAMACCT_MAXLEN];
1305 	char fqhost[MAXHOSTNAMELEN];
1306 	char usrctl_buf[16];
1307 	char encrypt_buf[16];
1308 	int max;
1309 	smb_krb5_pn_set_t spn, upn;
1310 
1311 	if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1312 		return (-1);
1313 
1314 	if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1315 		return (-1);
1316 
1317 	/* The SPN attribute is multi-valued and must be 1 or greater */
1318 	if (smb_krb5_get_pn_set(&spn, SMB_PN_SPN_ATTR, ah->domain) == 0)
1319 		return (-1);
1320 
1321 	/* The UPN attribute is single-valued and cannot be zero */
1322 	if (smb_krb5_get_pn_set(&upn, SMB_PN_UPN_ATTR, ah->domain) != 1) {
1323 		smb_krb5_free_pn_set(&spn);
1324 		smb_krb5_free_pn_set(&upn);
1325 		return (-1);
1326 	}
1327 
1328 	max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0))
1329 	    - (dclevel >= SMB_ADS_DCLEVEL_W2K8 ?  0 : 1);
1330 
1331 	if (smb_ads_alloc_attr(attrs, max) != 0) {
1332 		smb_krb5_free_pn_set(&spn);
1333 		smb_krb5_free_pn_set(&upn);
1334 		return (-1);
1335 	}
1336 
1337 	/* objectClass attribute is not modifiable. */
1338 	if (op == LDAP_MOD_ADD) {
1339 		attrs[++j]->mod_op = op;
1340 		attrs[j]->mod_type = "objectClass";
1341 		attrs[j]->mod_values = smb_ads_computer_objcls;
1342 	}
1343 
1344 	attrs[++j]->mod_op = op;
1345 	attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT;
1346 	sam_val[0] = sam_acct;
1347 	sam_val[1] = 0;
1348 	attrs[j]->mod_values = sam_val;
1349 
1350 	attrs[++j]->mod_op = op;
1351 	attrs[j]->mod_type = SMB_ADS_ATTR_UPN;
1352 	attrs[j]->mod_values = upn.s_pns;
1353 
1354 	attrs[++j]->mod_op = op;
1355 	attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
1356 	attrs[j]->mod_values =  spn.s_pns;
1357 
1358 	attrs[++j]->mod_op = op;
1359 	attrs[j]->mod_type = SMB_ADS_ATTR_CTL;
1360 	usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1361 	    SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD |
1362 	    SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE);
1363 	(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
1364 	ctl_val[0] = usrctl_buf;
1365 	ctl_val[1] = 0;
1366 	attrs[j]->mod_values = ctl_val;
1367 
1368 	attrs[++j]->mod_op = op;
1369 	attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST;
1370 	fqh_val[0] = fqhost;
1371 	fqh_val[1] = 0;
1372 	attrs[j]->mod_values = fqh_val;
1373 
1374 	/* enctypes support starting in Windows Server 2008 */
1375 	if (dclevel > SMB_ADS_DCLEVEL_W2K3) {
1376 		attrs[++j]->mod_op = op;
1377 		attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES;
1378 		(void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d",
1379 		    SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 +
1380 		    SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC);
1381 		encrypt_val[0] = encrypt_buf;
1382 		encrypt_val[1] = 0;
1383 		attrs[j]->mod_values = encrypt_val;
1384 	}
1385 
1386 	switch (op) {
1387 	case LDAP_MOD_ADD:
1388 		if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1389 			syslog(LOG_NOTICE, "ldap_add: %s",
1390 			    ldap_err2string(ret));
1391 			ret = -1;
1392 		}
1393 		break;
1394 
1395 	case LDAP_MOD_REPLACE:
1396 		if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1397 			syslog(LOG_NOTICE, "ldap_modify: %s",
1398 			    ldap_err2string(ret));
1399 			ret = -1;
1400 		}
1401 		break;
1402 
1403 	default:
1404 		ret = -1;
1405 
1406 	}
1407 
1408 	smb_ads_free_attr(attrs);
1409 	smb_krb5_free_pn_set(&spn);
1410 	smb_krb5_free_pn_set(&upn);
1411 
1412 	return (ret);
1413 }
1414 
1415 /*
1416  * Delete an ADS computer account.
1417  */
1418 static void
1419 smb_ads_del_computer(smb_ads_handle_t *ah, char *dn)
1420 {
1421 	int rc;
1422 
1423 	if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS)
1424 		smb_tracef("ldap_delete: %s", ldap_err2string(rc));
1425 }
1426 
1427 /*
1428  * Gets the value of the given attribute.
1429  */
1430 static smb_ads_qstat_t
1431 smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair)
1432 {
1433 	char **vals;
1434 	smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1435 
1436 	assert(avpair);
1437 	avpair->avp_val = NULL;
1438 	vals = ldap_get_values(ld, entry, avpair->avp_attr);
1439 	if (!vals)
1440 		return (SMB_ADS_STAT_NOT_FOUND);
1441 
1442 	if (!vals[0]) {
1443 		ldap_value_free(vals);
1444 		return (SMB_ADS_STAT_NOT_FOUND);
1445 	}
1446 
1447 	avpair->avp_val = strdup(vals[0]);
1448 	if (!avpair->avp_val)
1449 		rc = SMB_ADS_STAT_ERR;
1450 
1451 	ldap_value_free(vals);
1452 	return (rc);
1453 }
1454 
1455 /*
1456  * Process query's result.
1457  */
1458 static smb_ads_qstat_t
1459 smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
1460     smb_ads_avpair_t *avpair)
1461 {
1462 	char fqhost[MAXHOSTNAMELEN];
1463 	smb_ads_avpair_t dnshost_avp;
1464 	smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1465 	LDAPMessage *entry;
1466 
1467 	if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1468 		return (SMB_ADS_STAT_ERR);
1469 
1470 	if (ldap_count_entries(ah->ld, res) == 0)
1471 		return (SMB_ADS_STAT_NOT_FOUND);
1472 
1473 	if ((entry = ldap_first_entry(ah->ld, res)) == NULL)
1474 		return (SMB_ADS_STAT_ERR);
1475 
1476 	dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
1477 	rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
1478 
1479 	switch (rc) {
1480 	case SMB_ADS_STAT_FOUND:
1481 		/*
1482 		 * Returns SMB_ADS_STAT_DUP to avoid overwriting
1483 		 * the computer account of another system whose
1484 		 * NetBIOS name collides with that of the current
1485 		 * system.
1486 		 */
1487 		if (strcasecmp(dnshost_avp.avp_val, fqhost))
1488 			rc = SMB_ADS_STAT_DUP;
1489 
1490 		free(dnshost_avp.avp_val);
1491 		break;
1492 
1493 	case SMB_ADS_STAT_NOT_FOUND:
1494 		/*
1495 		 * Pre-created computer account doesn't have
1496 		 * the dNSHostname attribute. It's been observed
1497 		 * that the dNSHostname attribute is only set after
1498 		 * a successful domain join.
1499 		 * Returns SMB_ADS_STAT_FOUND as the account is
1500 		 * pre-created for the current system.
1501 		 */
1502 		rc = SMB_ADS_STAT_FOUND;
1503 		break;
1504 
1505 	default:
1506 		break;
1507 	}
1508 
1509 	if (rc != SMB_ADS_STAT_FOUND)
1510 		return (rc);
1511 
1512 	if (avpair)
1513 		rc = smb_ads_getattr(ah->ld, entry, avpair);
1514 
1515 	return (rc);
1516 
1517 }
1518 
1519 /*
1520  * smb_ads_lookup_computer_n_attr
1521  *
1522  * If avpair is NULL, checks the status of the specified computer account.
1523  * Otherwise, looks up the value of the specified computer account's attribute.
1524  * If found, the value field of the avpair will be allocated and set. The
1525  * caller should free the allocated buffer.
1526  *
1527  * Return:
1528  *  SMB_ADS_STAT_FOUND  - if both the computer and the specified attribute is
1529  *                        found.
1530  *  SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute
1531  *                           is not found.
1532  *  SMB_ADS_STAT_DUP - if the computer account is already used by other systems
1533  *                     in the AD. This could happen if the hostname of multiple
1534  *                     systems resolved to the same NetBIOS name.
1535  *  SMB_ADS_STAT_ERR - any failure.
1536  */
1537 static smb_ads_qstat_t
1538 smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair,
1539     int scope, char *dn)
1540 {
1541 	char *attrs[3], filter[SMB_ADS_MAXBUFLEN];
1542 	LDAPMessage *res;
1543 	char sam_acct[SMB_SAMACCT_MAXLEN], sam_acct2[SMB_SAMACCT_MAXLEN];
1544 	smb_ads_qstat_t rc;
1545 
1546 	if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1547 		return (SMB_ADS_STAT_ERR);
1548 
1549 	res = NULL;
1550 	attrs[0] = SMB_ADS_ATTR_DNSHOST;
1551 	attrs[1] = NULL;
1552 	attrs[2] = NULL;
1553 
1554 	if (avpair) {
1555 		if (!avpair->avp_attr)
1556 			return (SMB_ADS_STAT_ERR);
1557 
1558 		attrs[1] = avpair->avp_attr;
1559 	}
1560 
1561 	if (smb_ads_escape_search_filter_chars(sam_acct, sam_acct2) != 0)
1562 		return (SMB_ADS_STAT_ERR);
1563 
1564 	(void) snprintf(filter, sizeof (filter),
1565 	    "(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT,
1566 	    sam_acct2);
1567 
1568 	if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0,
1569 	    &res) != LDAP_SUCCESS) {
1570 		(void) ldap_msgfree(res);
1571 		return (SMB_ADS_STAT_NOT_FOUND);
1572 	}
1573 
1574 	rc = smb_ads_get_qstat(ah, res, avpair);
1575 	/* free the search results */
1576 	(void) ldap_msgfree(res);
1577 	return (rc);
1578 }
1579 
1580 /*
1581  * smb_ads_find_computer
1582  *
1583  * Starts by searching for the system's AD computer object in the default
1584  * container (i.e. cn=Computers).  If not found, searches the entire directory.
1585  * If found, 'dn' will be set to the distinguished name of the system's AD
1586  * computer object.
1587  */
1588 static smb_ads_qstat_t
1589 smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
1590 {
1591 	smb_ads_qstat_t stat;
1592 	smb_ads_avpair_t avpair;
1593 
1594 	avpair.avp_attr = SMB_ADS_ATTR_DN;
1595 	smb_ads_get_default_comp_container_dn(ah, dn, SMB_ADS_DN_MAX);
1596 	stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_ONELEVEL,
1597 	    dn);
1598 
1599 	if (stat == SMB_ADS_STAT_NOT_FOUND) {
1600 		(void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX);
1601 		stat = smb_ads_lookup_computer_n_attr(ah, &avpair,
1602 		    LDAP_SCOPE_SUBTREE, dn);
1603 	}
1604 
1605 	if (stat == SMB_ADS_STAT_FOUND) {
1606 		(void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX);
1607 		free(avpair.avp_val);
1608 	}
1609 
1610 	return (stat);
1611 }
1612 
1613 /*
1614  * smb_ads_update_computer_cntrl_attr
1615  *
1616  * Modify the user account control attribute of an existing computer
1617  * object on AD.
1618  *
1619  * Returns LDAP error code.
1620  */
1621 static int
1622 smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn)
1623 {
1624 	LDAPMod *attrs[2];
1625 	char *ctl_val[2];
1626 	int ret = 0;
1627 	char usrctl_buf[16];
1628 
1629 	if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
1630 		return (LDAP_NO_MEMORY);
1631 
1632 	attrs[0]->mod_op = LDAP_MOD_REPLACE;
1633 	attrs[0]->mod_type = SMB_ADS_ATTR_CTL;
1634 
1635 	(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags);
1636 	ctl_val[0] = usrctl_buf;
1637 	ctl_val[1] = 0;
1638 	attrs[0]->mod_values = ctl_val;
1639 	if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1640 		syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
1641 	}
1642 
1643 	smb_ads_free_attr(attrs);
1644 	return (ret);
1645 }
1646 
1647 /*
1648  * smb_ads_lookup_computer_attr_kvno
1649  *
1650  * Lookup the value of the Kerberos version number attribute of the computer
1651  * account.
1652  */
1653 static krb5_kvno
1654 smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
1655 {
1656 	smb_ads_avpair_t avpair;
1657 	int kvno = 1;
1658 
1659 	avpair.avp_attr = SMB_ADS_ATTR_KVNO;
1660 	if (smb_ads_lookup_computer_n_attr(ah, &avpair,
1661 	    LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) {
1662 		kvno = atoi(avpair.avp_val);
1663 		free(avpair.avp_val);
1664 	}
1665 
1666 	return (kvno);
1667 }
1668 
1669 /*
1670  * smb_ads_join
1671  *
1672  * Besides the NT-4 style domain join (using MS-RPC), CIFS server also
1673  * provides the domain join using Kerberos Authentication, Keberos
1674  * Change & Set password, and LDAP protocols. Basically, AD join
1675  * operation would require the following tickets to be acquired for the
1676  * the user account that is provided for the domain join.
1677  *
1678  * 1) a Keberos TGT ticket,
1679  * 2) a ldap service ticket, and
1680  * 3) kadmin/changpw service ticket
1681  *
1682  * The ADS client first sends a ldap search request to find out whether
1683  * or not the workstation trust account already exists in the Active Directory.
1684  * The existing computer object for this workstation will be removed and
1685  * a new one will be added. The machine account password is randomly
1686  * generated and set for the newly created computer object using KPASSWD
1687  * protocol (See RFC 3244). Once the password is set, our ADS client
1688  * finalizes the machine account by modifying the user acount control
1689  * attribute of the computer object. Kerberos keys derived from the machine
1690  * account password will be stored locally in /etc/krb5/krb5.keytab file.
1691  * That would be needed while acquiring Kerberos TGT ticket for the host
1692  * principal after the domain join operation.
1693  */
1694 smb_ads_status_t
1695 smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd)
1696 {
1697 	smb_ads_handle_t *ah = NULL;
1698 	krb5_context ctx = NULL;
1699 	krb5_principal *krb5princs = NULL;
1700 	krb5_kvno kvno;
1701 	boolean_t delete = B_TRUE;
1702 	smb_ads_status_t rc;
1703 	boolean_t new_acct;
1704 	int dclevel, num, usrctl_flags = 0;
1705 	smb_ads_qstat_t qstat;
1706 	char dn[SMB_ADS_DN_MAX];
1707 	char tmpfile[] = SMBNS_KRB5_KEYTAB_TMP;
1708 	int cnt, x;
1709 	smb_krb5_pn_set_t spns;
1710 	krb5_enctype *encptr;
1711 
1712 	rc = smb_ads_open_main(&ah, domain, user, usr_passwd);
1713 	if (rc != 0) {
1714 		smb_ccache_remove(SMB_CCACHE_PATH);
1715 		return (rc);
1716 	}
1717 
1718 	if ((dclevel = smb_ads_get_dc_level(ah)) == -1) {
1719 		smb_ads_close(ah);
1720 		smb_ccache_remove(SMB_CCACHE_PATH);
1721 		return (SMB_ADJOIN_ERR_GET_DCLEVEL);
1722 	}
1723 
1724 	qstat = smb_ads_find_computer(ah, dn);
1725 	switch (qstat) {
1726 	case SMB_ADS_STAT_FOUND:
1727 		new_acct = B_FALSE;
1728 		if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
1729 			smb_ads_close(ah);
1730 			smb_ccache_remove(SMB_CCACHE_PATH);
1731 			return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT);
1732 		}
1733 		break;
1734 
1735 	case SMB_ADS_STAT_NOT_FOUND:
1736 		new_acct = B_TRUE;
1737 		smb_ads_get_default_comp_dn(ah, dn, SMB_ADS_DN_MAX);
1738 		if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
1739 			smb_ads_close(ah);
1740 			smb_ccache_remove(SMB_CCACHE_PATH);
1741 			return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT);
1742 		}
1743 		break;
1744 
1745 	default:
1746 		if (qstat == SMB_ADS_STAT_DUP)
1747 			rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT;
1748 		else
1749 			rc = SMB_ADJOIN_ERR_TRUST_ACCT;
1750 		smb_ads_close(ah);
1751 		smb_ccache_remove(SMB_CCACHE_PATH);
1752 		return (rc);
1753 	}
1754 
1755 	if (smb_krb5_ctx_init(&ctx) != 0) {
1756 		rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
1757 		goto adjoin_cleanup;
1758 	}
1759 
1760 	if (smb_krb5_get_pn_set(&spns, SMB_PN_KEYTAB_ENTRY, ah->domain) == 0) {
1761 		rc = SMB_ADJOIN_ERR_GET_SPNS;
1762 		goto adjoin_cleanup;
1763 	}
1764 
1765 	if (smb_krb5_get_kprincs(ctx, spns.s_pns, spns.s_cnt, &krb5princs)
1766 	    != 0) {
1767 		smb_krb5_free_pn_set(&spns);
1768 		rc = SMB_ADJOIN_ERR_GET_SPNS;
1769 		goto adjoin_cleanup;
1770 	}
1771 
1772 	cnt = spns.s_cnt;
1773 	smb_krb5_free_pn_set(&spns);
1774 
1775 	/* New machine_passwd was filled in by our caller. */
1776 	if (smb_krb5_setpwd(ctx, ah->domain, machine_passwd) != 0) {
1777 		rc = SMB_ADJOIN_ERR_KSETPWD;
1778 		goto adjoin_cleanup;
1779 	}
1780 
1781 	kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
1782 
1783 	/*
1784 	 * Only members of Domain Admins and Enterprise Admins can set
1785 	 * the TRUSTED_FOR_DELEGATION userAccountControl flag.
1786 	 * Try to set this, but don't fail the join if we can't.
1787 	 * Look into just removing this...
1788 	 */
1789 	usrctl_flags = (
1790 	    SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1791 	    SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION |
1792 	    SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
1793 set_ctl_again:
1794 	x = smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn);
1795 	if (x != LDAP_SUCCESS && (usrctl_flags &
1796 	    SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION) != 0) {
1797 		syslog(LOG_NOTICE, "Unable to set the "
1798 "TRUSTED_FOR_DELEGATION userAccountControl flag on the "
1799 "machine account in Active Directory.  It may be necessary "
1800 "to set that via Active Directory administration.");
1801 		usrctl_flags &=
1802 		    ~SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION;
1803 		goto set_ctl_again;
1804 	}
1805 	if (x != LDAP_SUCCESS) {
1806 		rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
1807 		goto adjoin_cleanup;
1808 	}
1809 
1810 	if (mktemp(tmpfile) == NULL) {
1811 		rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
1812 		goto adjoin_cleanup;
1813 	}
1814 
1815 	encptr = smb_ads_get_enctypes(dclevel, &num);
1816 	if (smb_krb5_kt_populate(ctx, ah->domain, krb5princs, cnt,
1817 	    tmpfile, kvno, machine_passwd, encptr, num) != 0) {
1818 		rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
1819 		goto adjoin_cleanup;
1820 	}
1821 
1822 	delete = B_FALSE;
1823 	rc = SMB_ADS_SUCCESS;
1824 
1825 adjoin_cleanup:
1826 	if (new_acct && delete)
1827 		smb_ads_del_computer(ah, dn);
1828 
1829 	if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) {
1830 		if (rc != SMB_ADJOIN_ERR_GET_SPNS)
1831 			smb_krb5_free_kprincs(ctx, krb5princs, cnt);
1832 		smb_krb5_ctx_fini(ctx);
1833 	}
1834 
1835 	/* commit keytab file */
1836 	if (rc == SMB_ADS_SUCCESS) {
1837 		if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) {
1838 			(void) unlink(tmpfile);
1839 			rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB;
1840 		}
1841 	} else {
1842 		(void) unlink(tmpfile);
1843 	}
1844 
1845 	smb_ads_close(ah);
1846 	smb_ccache_remove(SMB_CCACHE_PATH);
1847 	return (rc);
1848 }
1849 
1850 struct xlate_table {
1851 	int err;
1852 	const char * const msg;
1853 };
1854 
1855 static const struct xlate_table
1856 adjoin_table[] = {
1857 	{ SMB_ADS_SUCCESS, "Success" },
1858 	{ SMB_ADS_KRB5_INIT_CTX,
1859 	    "Failed creating a Kerberos context." },
1860 	{ SMB_ADS_KRB5_CC_DEFAULT,
1861 	    "Failed to resolve default credential cache." },
1862 	{ SMB_ADS_KRB5_PARSE_PRINCIPAL,
1863 	    "Failed parsing the user principal name." },
1864 	{ SMB_ADS_KRB5_GET_INIT_CREDS_PW,
1865 	    "Failed getting initial credentials.  (Wrong password?)" },
1866 	{ SMB_ADS_KRB5_CC_INITIALIZE,
1867 	    "Failed initializing the credential cache." },
1868 	{ SMB_ADS_KRB5_CC_STORE_CRED,
1869 	    "Failed to update the credential cache." },
1870 	{ SMB_ADS_CANT_LOCATE_DC,
1871 	    "Failed to locate a domain controller." },
1872 	{ SMB_ADS_LDAP_INIT,
1873 	    "Failed to create an LDAP handle." },
1874 	{ SMB_ADS_LDAP_SETOPT,
1875 	    "Failed to set an LDAP option." },
1876 	{ SMB_ADS_LDAP_SET_DOM,
1877 	    "Failed to set the LDAP handle DN." },
1878 	{ SMB_ADS_LDAP_SASL_BIND,
1879 	    "Failed to bind the LDAP handle. "
1880 	    "Usually indicates an authentication problem." },
1881 
1882 	{ SMB_ADJOIN_ERR_GEN_PWD,
1883 	    "Failed to generate machine password." },
1884 	{ SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of "
1885 	    "the domain controller. The rootDSE attribute named "
1886 	    "\"domainControllerFunctionality\" is missing from the "
1887 	    "Active Directory." },
1888 	{ SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the "
1889 	    "workstation trust account." },
1890 	{ SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the "
1891 	    "workstation trust account." },
1892 	{ SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the "
1893 	    "workstation trust account because its name is already "
1894 	    "in use." },
1895 	{ SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the "
1896 	    "workstation trust account" },
1897 	{ SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos "
1898 	    "context." },
1899 	{ SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos "
1900 	    "principals." },
1901 	{ SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." },
1902 	{ SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR,  "Failed to modify "
1903 	    "userAccountControl attribute of the workstation trust "
1904 	    "account." },
1905 	{ SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local "
1906 	    "keytab file (i.e /etc/krb5/krb5.keytab)." },
1907 	{ SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap "
1908 	    "configuration." },
1909 	{ SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap "
1910 	    "service." },
1911 	{ SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to "
1912 	    "local keytab file (i.e. /etc/krb5/krb5.keytab)." },
1913 	{ SMB_ADJOIN_ERR_AUTH_NETLOGON,
1914 	    "Failed to authenticate using the new computer account." },
1915 	{ SMB_ADJOIN_ERR_STORE_PROPS,
1916 	    "Failed to store computer account information locally." },
1917 	{ 0, NULL }
1918 };
1919 
1920 /*
1921  * smb_ads_strerror
1922  *
1923  * Lookup an error message for the specific adjoin error code.
1924  */
1925 const char *
1926 smb_ads_strerror(int err)
1927 {
1928 	const struct xlate_table *xt;
1929 
1930 	if (err > 0 && err < SMB_ADS_ERRNO_GAP)
1931 		return (strerror(err));
1932 
1933 	for (xt = adjoin_table; xt->msg; xt++)
1934 		if (xt->err == err)
1935 			return (xt->msg);
1936 
1937 	return ("Unknown error code.");
1938 }
1939 
1940 void
1941 smb_ads_log_errmsg(smb_ads_status_t err)
1942 {
1943 	const char *s = smb_ads_strerror(err);
1944 	syslog(LOG_NOTICE, "%s", s);
1945 }
1946 
1947 
1948 /*
1949  * smb_ads_lookup_msdcs
1950  *
1951  * If server argument is set, try to locate the specified DC.
1952  * If it is set to empty string, locate any DCs in the specified domain.
1953  * Returns the discovered DC via buf.
1954  *
1955  * fqdn	  - fully-qualified domain name
1956  * dci    - the name and address of the found DC
1957  */
1958 uint32_t
1959 smb_ads_lookup_msdcs(char *fqdn, smb_dcinfo_t *dci)
1960 {
1961 	smb_ads_host_info_t *hinfo = NULL;
1962 	char ipstr[INET6_ADDRSTRLEN];
1963 
1964 	if (!fqdn || !dci)
1965 		return (NT_STATUS_INTERNAL_ERROR);
1966 
1967 	ipstr[0] = '\0';
1968 	if ((hinfo = smb_ads_find_host(fqdn)) == NULL)
1969 		return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
1970 
1971 	(void) smb_inet_ntop(&hinfo->ipaddr, ipstr,
1972 	    SMB_IPSTRLEN(hinfo->ipaddr.a_family));
1973 	smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr);
1974 
1975 	(void) strlcpy(dci->dc_name, hinfo->name, sizeof (dci->dc_name));
1976 	dci->dc_addr = hinfo->ipaddr;
1977 
1978 	free(hinfo);
1979 	return (NT_STATUS_SUCCESS);
1980 }
1981 
1982 static krb5_enctype *
1983 smb_ads_get_enctypes(int dclevel, int *num)
1984 {
1985 	krb5_enctype *encptr;
1986 
1987 	if (dclevel >= SMB_ADS_DCLEVEL_W2K8) {
1988 		*num = sizeof (w2k8enctypes) / sizeof (krb5_enctype);
1989 		encptr = w2k8enctypes;
1990 	} else {
1991 		*num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype);
1992 		encptr = pre_w2k8enctypes;
1993 	}
1994 
1995 	return (encptr);
1996 }
1997