xref: /illumos-gate/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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 2019 Nexenta by DDN, 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 	host->flags = dci->Flags;
480 
481 	(void) mutex_lock(&smb_ads_cached_host_mtx);
482 	if (!smb_ads_cached_host_info)
483 		smb_ads_cached_host_info = smb_ads_dup_host_info(host);
484 	host = smb_ads_dup_host_info(smb_ads_cached_host_info);
485 	(void) mutex_unlock(&smb_ads_cached_host_mtx);
486 
487 out:
488 	DsFreeDcInfo(dci);
489 	return (host);
490 }
491 
492 /*
493  * Return the number of dots in a string.
494  */
495 static int
496 smb_ads_count_dots(const char *s)
497 {
498 	int ndots = 0;
499 
500 	while (*s) {
501 		if (*s++ == '.')
502 			ndots++;
503 	}
504 
505 	return (ndots);
506 }
507 
508 /*
509  * Convert a domain name in dot notation to distinguished name format,
510  * for example: sun.com -> dc=sun,dc=com.
511  *
512  * Returns a pointer to an allocated buffer containing the distinguished
513  * name.
514  */
515 static char *
516 smb_ads_convert_domain(const char *domain_name)
517 {
518 	const char *s;
519 	char *dn_name;
520 	char buf[2];
521 	int ndots;
522 	int len;
523 
524 	if (domain_name == NULL || *domain_name == 0)
525 		return (NULL);
526 
527 	ndots = smb_ads_count_dots(domain_name);
528 	++ndots;
529 	len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1;
530 
531 	if ((dn_name = malloc(len)) == NULL)
532 		return (NULL);
533 
534 	bzero(dn_name, len);
535 	(void) strlcpy(dn_name, "dc=", len);
536 
537 	buf[1] = '\0';
538 	s = domain_name;
539 
540 	while (*s) {
541 		if (*s == '.') {
542 			(void) strlcat(dn_name, ",dc=", len);
543 		} else {
544 			buf[0] = *s;
545 			(void) strlcat(dn_name, buf, len);
546 		}
547 		++s;
548 	}
549 
550 	return (dn_name);
551 }
552 
553 /*
554  * smb_ads_free_cached_host
555  *
556  * Free the memory use by the global smb_ads_cached_host_info & set it to NULL.
557  */
558 static void
559 smb_ads_free_cached_host(void)
560 {
561 	(void) mutex_lock(&smb_ads_cached_host_mtx);
562 	if (smb_ads_cached_host_info) {
563 		free(smb_ads_cached_host_info);
564 		smb_ads_cached_host_info = NULL;
565 	}
566 	(void) mutex_unlock(&smb_ads_cached_host_mtx);
567 }
568 
569 /*
570  * smb_ads_open
571  * Open a LDAP connection to an ADS server if the system is in domain mode.
572  * Acquire both Kerberos TGT and LDAP service tickets for the host principal.
573  *
574  * This function should only be called after the system is successfully joined
575  * to a domain.
576  */
577 smb_ads_handle_t *
578 smb_ads_open(void)
579 {
580 	char domain[MAXHOSTNAMELEN];
581 	smb_ads_handle_t *h;
582 	smb_ads_status_t err;
583 
584 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
585 		return (NULL);
586 
587 	if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0)
588 		return (NULL);
589 
590 	err = smb_ads_open_main(&h, domain, NULL, NULL);
591 	if (err != 0) {
592 		smb_ads_log_errmsg(err);
593 		return (NULL);
594 	}
595 
596 	return (h);
597 }
598 
599 static int
600 smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts)
601 {
602 	NOTE(ARGUNUSED(ld, defaults));
603 	sasl_interact_t *interact;
604 
605 	if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE)
606 		return (LDAP_PARAM_ERROR);
607 
608 	/* There should be no extra arguemnts for SASL/GSSAPI authentication */
609 	for (interact = prompts; interact->id != SASL_CB_LIST_END;
610 	    interact++) {
611 		interact->result = NULL;
612 		interact->len = 0;
613 	}
614 	return (LDAP_SUCCESS);
615 }
616 
617 /*
618  * smb_ads_open_main
619  * Open a LDAP connection to an ADS server.
620  * If ADS is enabled and the administrative username, password, and
621  * ADS domain are defined then query DNS to find an ADS server if this is the
622  * very first call to this routine.  After an ADS server is found then this
623  * server will be used everytime this routine is called until the system is
624  * rebooted or the ADS server becomes unavailable then an ADS server will
625  * be queried again.  After the connection is made then an ADS handle
626  * is created to be returned.
627  *
628  * After the LDAP connection, the LDAP version will be set to 3 using
629  * ldap_set_option().
630  *
631  * The LDAP connection is bound before the ADS handle is returned.
632  * Parameters:
633  *   domain - fully-qualified domain name
634  *   user   - the user account for whom the Kerberos TGT ticket and ADS
635  *            service tickets are acquired.
636  *   password - password of the specified user
637  *
638  * Returns:
639  *   NULL              : can't connect to ADS server or other errors
640  *   smb_ads_handle_t* : handle to ADS server
641  */
642 static int
643 smb_ads_open_main(smb_ads_handle_t **hp, char *domain, char *user,
644     char *password)
645 {
646 	smb_ads_handle_t *ah;
647 	LDAP *ld;
648 	int version = 3;
649 	smb_ads_host_info_t *ads_host = NULL;
650 	int err, rc;
651 
652 	*hp = NULL;
653 
654 	if (user != NULL) {
655 		err = smb_kinit(domain, user, password);
656 		if (err != 0)
657 			return (err);
658 		user = NULL;
659 		password = NULL;
660 	}
661 
662 	ads_host = smb_ads_find_host(domain);
663 	if (ads_host == NULL)
664 		return (SMB_ADS_CANT_LOCATE_DC);
665 
666 	ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t));
667 	if (ah == NULL) {
668 		free(ads_host);
669 		return (ENOMEM);
670 	}
671 
672 	(void) memset(ah, 0, sizeof (smb_ads_handle_t));
673 
674 	if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) {
675 		syslog(LOG_ERR, "smbns: ldap_init failed");
676 		smb_ads_free_cached_host();
677 		free(ah);
678 		free(ads_host);
679 		return (SMB_ADS_LDAP_INIT);
680 	}
681 
682 	if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version)
683 	    != LDAP_SUCCESS) {
684 		smb_ads_free_cached_host();
685 		free(ah);
686 		free(ads_host);
687 		(void) ldap_unbind(ld);
688 		return (SMB_ADS_LDAP_SETOPT);
689 	}
690 
691 	(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
692 	ah->ld = ld;
693 	ah->domain = strdup(domain);
694 
695 	if (ah->domain == NULL) {
696 		smb_ads_close(ah);
697 		free(ads_host);
698 		return (SMB_ADS_LDAP_SETOPT);
699 	}
700 
701 	/*
702 	 * ah->domain is often used for generating service principal name.
703 	 * Convert it to lower case for RFC 4120 section 6.2.1 conformance.
704 	 */
705 	(void) smb_strlwr(ah->domain);
706 	ah->domain_dn = smb_ads_convert_domain(domain);
707 	if (ah->domain_dn == NULL) {
708 		smb_ads_close(ah);
709 		free(ads_host);
710 		return (SMB_ADS_LDAP_SET_DOM);
711 	}
712 
713 	ah->hostname = strdup(ads_host->name);
714 	if (ah->hostname == NULL) {
715 		smb_ads_close(ah);
716 		free(ads_host);
717 		return (ENOMEM);
718 	}
719 	(void) mutex_lock(&smb_ads_cfg.c_mtx);
720 	if (*smb_ads_cfg.c_site != '\0') {
721 		if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) {
722 			smb_ads_close(ah);
723 			(void) mutex_unlock(&smb_ads_cfg.c_mtx);
724 			free(ads_host);
725 			return (ENOMEM);
726 		}
727 	} else {
728 		ah->site = NULL;
729 	}
730 	(void) mutex_unlock(&smb_ads_cfg.c_mtx);
731 
732 	rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL,
733 	    LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL);
734 	if (rc != LDAP_SUCCESS) {
735 		syslog(LOG_ERR, "smbns: ldap_sasl_..._bind_s failed (%s)",
736 		    ldap_err2string(rc));
737 		smb_ads_close(ah);
738 		free(ads_host);
739 		return (SMB_ADS_LDAP_SASL_BIND);
740 	}
741 
742 	free(ads_host);
743 	*hp = ah;
744 
745 	return (SMB_ADS_SUCCESS);
746 }
747 
748 /*
749  * smb_ads_close
750  * Close connection to ADS server and free memory allocated for ADS handle.
751  * LDAP unbind is called here.
752  * Parameters:
753  *   ah: handle to ADS server
754  * Returns:
755  *   void
756  */
757 void
758 smb_ads_close(smb_ads_handle_t *ah)
759 {
760 	if (ah == NULL)
761 		return;
762 	/* close and free connection resources */
763 	if (ah->ld)
764 		(void) ldap_unbind(ah->ld);
765 
766 	free(ah->domain);
767 	free(ah->domain_dn);
768 	free(ah->hostname);
769 	free(ah->site);
770 	free(ah);
771 }
772 
773 /*
774  * smb_ads_alloc_attr
775  *
776  * Since the attrs is a null-terminated array, all elements
777  * in the array (except the last one) will point to allocated
778  * memory.
779  */
780 static int
781 smb_ads_alloc_attr(LDAPMod *attrs[], int num)
782 {
783 	int i;
784 
785 	bzero(attrs, num * sizeof (LDAPMod *));
786 	for (i = 0; i < (num - 1); i++) {
787 		attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod));
788 		if (attrs[i] == NULL) {
789 			smb_ads_free_attr(attrs);
790 			return (-1);
791 		}
792 	}
793 
794 	return (0);
795 }
796 
797 /*
798  * smb_ads_free_attr
799  * Free memory allocated when publishing a share.
800  * Parameters:
801  *   attrs: an array of LDAPMod pointers
802  * Returns:
803  *   None
804  */
805 static void
806 smb_ads_free_attr(LDAPMod *attrs[])
807 {
808 	int i;
809 	for (i = 0; attrs[i]; i++) {
810 		free(attrs[i]);
811 	}
812 }
813 
814 /*
815  * Returns share DN in an allocated buffer.  The format of the DN is
816  * cn=<sharename>,<container RDNs>,<domain DN>
817  *
818  * If the domain DN is not included in the container parameter,
819  * then it will be appended to create the share DN.
820  *
821  * The caller must free the allocated buffer.
822  */
823 static char *
824 smb_ads_get_sharedn(const char *sharename, const char *container,
825     const char *domain_dn)
826 {
827 	char *share_dn;
828 	int rc, offset, container_len, domain_len;
829 	boolean_t append_domain = B_TRUE;
830 
831 	container_len = strlen(container);
832 	domain_len = strlen(domain_dn);
833 
834 	if (container_len >= domain_len) {
835 
836 		/* offset to last domain_len characters */
837 		offset = container_len - domain_len;
838 
839 		if (smb_strcasecmp(container + offset,
840 		    domain_dn, domain_len) == 0)
841 			append_domain = B_FALSE;
842 	}
843 
844 	if (append_domain)
845 		rc = asprintf(&share_dn, "cn=%s,%s,%s", sharename,
846 		    container, domain_dn);
847 	else
848 		rc = asprintf(&share_dn, "cn=%s,%s", sharename,
849 		    container);
850 
851 	return ((rc == -1) ? NULL : share_dn);
852 }
853 
854 /*
855  * smb_ads_add_share
856  * Call by smb_ads_publish_share to create share object in ADS.
857  * This routine specifies the attributes of an ADS LDAP share object. The first
858  * attribute and values define the type of ADS object, the share object.  The
859  * second attribute and value define the UNC of the share data for the share
860  * object. The LDAP synchronous add command is used to add the object into ADS.
861  * The container location to add the object needs to specified.
862  * Parameters:
863  *   ah          : handle to ADS server
864  *   adsShareName: name of share object to be created in ADS
865  *   shareUNC    : share name on NetForce
866  *   adsContainer: location in ADS to create share object
867  *
868  * Returns:
869  *   -1          : error
870  *    0          : success
871  */
872 int
873 smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName,
874     const char *unc_name, const char *adsContainer)
875 {
876 	LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR];
877 	int j = 0;
878 	char *share_dn;
879 	int ret;
880 	char *unc_names[] = {(char *)unc_name, NULL};
881 
882 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
883 	    ah->domain_dn)) == NULL)
884 		return (-1);
885 
886 	if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) {
887 		free(share_dn);
888 		return (-1);
889 	}
890 
891 	attrs[j]->mod_op = LDAP_MOD_ADD;
892 	attrs[j]->mod_type = "objectClass";
893 	attrs[j]->mod_values = smb_ads_share_objcls;
894 
895 	attrs[++j]->mod_op = LDAP_MOD_ADD;
896 	attrs[j]->mod_type = "uNCName";
897 	attrs[j]->mod_values = unc_names;
898 
899 	if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) {
900 		if (ret == LDAP_NO_SUCH_OBJECT) {
901 			syslog(LOG_ERR, "Failed to publish share %s in" \
902 			    " AD.  Container does not exist: %s.\n",
903 			    adsShareName, share_dn);
904 
905 		} else {
906 			syslog(LOG_ERR, "Failed to publish share %s in" \
907 			    " AD: %s (%s).\n", adsShareName, share_dn,
908 			    ldap_err2string(ret));
909 		}
910 		smb_ads_free_attr(attrs);
911 		free(share_dn);
912 		return (ret);
913 	}
914 	free(share_dn);
915 	smb_ads_free_attr(attrs);
916 
917 	return (0);
918 }
919 
920 /*
921  * smb_ads_del_share
922  * Call by smb_ads_remove_share to remove share object from ADS.  The container
923  * location to remove the object needs to specified.  The LDAP synchronous
924  * delete command is used.
925  * Parameters:
926  *   ah          : handle to ADS server
927  *   adsShareName: name of share object in ADS to be removed
928  *   adsContainer: location of share object in ADS
929  * Returns:
930  *   -1          : error
931  *    0          : success
932  */
933 static int
934 smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName,
935     const char *adsContainer)
936 {
937 	char *share_dn;
938 	int ret;
939 
940 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
941 	    ah->domain_dn)) == NULL)
942 		return (-1);
943 
944 	if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) {
945 		smb_tracef("ldap_delete: %s", ldap_err2string(ret));
946 		free(share_dn);
947 		return (-1);
948 	}
949 	free(share_dn);
950 
951 	return (0);
952 }
953 
954 
955 /*
956  * smb_ads_escape_search_filter_chars
957  *
958  * This routine will escape the special characters found in a string
959  * that will later be passed to the ldap search filter.
960  *
961  * RFC 1960 - A String Representation of LDAP Search Filters
962  * 3.  String Search Filter Definition
963  * If a value must contain one of the characters '*' OR '(' OR ')',
964  * these characters
965  * should be escaped by preceding them with the backslash '\' character.
966  *
967  * RFC 2252 - LDAP Attribute Syntax Definitions
968  * a backslash quoting mechanism is used to escape
969  * the following separator symbol character (such as "'", "$" or "#") if
970  * it should occur in that string.
971  */
972 static int
973 smb_ads_escape_search_filter_chars(const char *src, char *dst)
974 {
975 	int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */
976 
977 	if (src == NULL || dst == NULL)
978 		return (-1);
979 
980 	while (*src) {
981 		if (!avail) {
982 			*dst = 0;
983 			return (-1);
984 		}
985 
986 		switch (*src) {
987 		case '\\':
988 		case '\'':
989 		case '$':
990 		case '#':
991 		case '*':
992 		case '(':
993 		case ')':
994 			*dst++ = '\\';
995 			avail--;
996 			/* fall through */
997 
998 		default:
999 			*dst++ = *src++;
1000 			avail--;
1001 		}
1002 	}
1003 
1004 	*dst = 0;
1005 
1006 	return (0);
1007 }
1008 
1009 /*
1010  * smb_ads_lookup_share
1011  * The search filter is set to search for a specific share name in the
1012  * specified ADS container.  The LDSAP synchronous search command is used.
1013  * Parameters:
1014  *   ah          : handle to ADS server
1015  *   adsShareName: name of share object in ADS to be searched
1016  *   adsContainer: location of share object in ADS
1017  * Returns:
1018  *   -1          : error
1019  *    0          : not found
1020  *    1          : found
1021  */
1022 int
1023 smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName,
1024     const char *adsContainer, char *unc_name)
1025 {
1026 	char *attrs[4], filter[SMB_ADS_MAXBUFLEN];
1027 	char *share_dn;
1028 	int ret;
1029 	LDAPMessage *res;
1030 	char tmpbuf[SMB_ADS_MAXBUFLEN];
1031 
1032 	if (adsShareName == NULL || adsContainer == NULL)
1033 		return (-1);
1034 
1035 	if ((share_dn = smb_ads_get_sharedn(adsShareName, adsContainer,
1036 	    ah->domain_dn)) == NULL)
1037 		return (-1);
1038 
1039 	res = NULL;
1040 	attrs[0] = "cn";
1041 	attrs[1] = "objectClass";
1042 	attrs[2] = "uNCName";
1043 	attrs[3] = NULL;
1044 
1045 	if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) {
1046 		free(share_dn);
1047 		return (-1);
1048 	}
1049 
1050 	(void) snprintf(filter, sizeof (filter),
1051 	    "(&(objectClass=volume)(uNCName=%s))", tmpbuf);
1052 
1053 	if ((ret = ldap_search_s(ah->ld, share_dn,
1054 	    LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) {
1055 		if (ret != LDAP_NO_SUCH_OBJECT)
1056 			smb_tracef("%s: ldap_search: %s", share_dn,
1057 			    ldap_err2string(ret));
1058 
1059 		(void) ldap_msgfree(res);
1060 		free(share_dn);
1061 		return (0);
1062 	}
1063 
1064 	(void) free(share_dn);
1065 
1066 	/* no match is found */
1067 	if (ldap_count_entries(ah->ld, res) == 0) {
1068 		(void) ldap_msgfree(res);
1069 		return (0);
1070 	}
1071 
1072 	/* free the search results */
1073 	(void) ldap_msgfree(res);
1074 
1075 	return (1);
1076 }
1077 
1078 /*
1079  * smb_ads_publish_share
1080  * Publish share into ADS.  If a share name already exist in ADS in the same
1081  * container then the existing share object is removed before adding the new
1082  * share object.
1083  * Parameters:
1084  *   ah          : handle return from smb_ads_open
1085  *   adsShareName: name of share to be added to ADS directory
1086  *   shareUNC    : name of share on client, can be NULL to use the same name
1087  *                 as adsShareName
1088  *   adsContainer: location for share to be added in ADS directory, ie
1089  *                   ou=share_folder
1090  *   uncType     : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR
1091  *                   to use host ip addr for UNC.
1092  * Returns:
1093  *   -1          : error
1094  *    0          : success
1095  */
1096 int
1097 smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName,
1098     const char *shareUNC, const char *adsContainer, const char *hostname)
1099 {
1100 	int ret;
1101 	char unc_name[SMB_ADS_MAXBUFLEN];
1102 
1103 	if (adsShareName == NULL || adsContainer == NULL)
1104 		return (-1);
1105 
1106 	if (shareUNC == 0 || *shareUNC == 0)
1107 		shareUNC = adsShareName;
1108 
1109 	if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1110 	    hostname, shareUNC) < 0)
1111 		return (-1);
1112 
1113 	ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1114 
1115 	switch (ret) {
1116 	case 1:
1117 		(void) smb_ads_del_share(ah, adsShareName, adsContainer);
1118 		ret = smb_ads_add_share(ah, adsShareName, unc_name,
1119 		    adsContainer);
1120 		break;
1121 
1122 	case 0:
1123 		ret = smb_ads_add_share(ah, adsShareName, unc_name,
1124 		    adsContainer);
1125 		if (ret == LDAP_ALREADY_EXISTS)
1126 			ret = -1;
1127 
1128 		break;
1129 
1130 	case -1:
1131 	default:
1132 		/* return with error code */
1133 		ret = -1;
1134 	}
1135 
1136 	return (ret);
1137 }
1138 
1139 /*
1140  * smb_ads_remove_share
1141  * Remove share from ADS.  A search is done first before explicitly removing
1142  * the share.
1143  * Parameters:
1144  *   ah          : handle return from smb_ads_open
1145  *   adsShareName: name of share to be removed from ADS directory
1146  *   adsContainer: location for share to be removed from ADS directory, ie
1147  *                   ou=share_folder
1148  * Returns:
1149  *   -1          : error
1150  *    0          : success
1151  */
1152 int
1153 smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName,
1154     const char *shareUNC, const char *adsContainer, const char *hostname)
1155 {
1156 	int ret;
1157 	char unc_name[SMB_ADS_MAXBUFLEN];
1158 
1159 	if (adsShareName == NULL || adsContainer == NULL)
1160 		return (-1);
1161 	if (shareUNC == 0 || *shareUNC == 0)
1162 		shareUNC = adsShareName;
1163 
1164 	if (smb_ads_build_unc_name(unc_name, sizeof (unc_name),
1165 	    hostname, shareUNC) < 0)
1166 		return (-1);
1167 
1168 	ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name);
1169 	if (ret == 0)
1170 		return (0);
1171 	if (ret == -1)
1172 		return (-1);
1173 
1174 	return (smb_ads_del_share(ah, adsShareName, adsContainer));
1175 }
1176 
1177 /*
1178  * smb_ads_get_default_comp_container_dn
1179  *
1180  * Build the distinguished name for the default computer conatiner (i.e. the
1181  * pre-defined Computers container).
1182  */
1183 static void
1184 smb_ads_get_default_comp_container_dn(smb_ads_handle_t *ah, char *buf,
1185     size_t buflen)
1186 {
1187 	(void) snprintf(buf, buflen, "cn=%s,%s", SMB_ADS_COMPUTERS_CN,
1188 	    ah->domain_dn);
1189 }
1190 
1191 /*
1192  * smb_ads_get_default_comp_dn
1193  *
1194  * Build the distinguished name for this system.
1195  */
1196 static void
1197 smb_ads_get_default_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen)
1198 {
1199 	char nbname[NETBIOS_NAME_SZ];
1200 	char container_dn[SMB_ADS_DN_MAX];
1201 
1202 	(void) smb_getnetbiosname(nbname, sizeof (nbname));
1203 	smb_ads_get_default_comp_container_dn(ah, container_dn, SMB_ADS_DN_MAX);
1204 	(void) snprintf(buf, buflen, "cn=%s,%s", nbname, container_dn);
1205 }
1206 
1207 /*
1208  * smb_ads_add_computer
1209  *
1210  * Returns 0 upon success. Otherwise, returns -1.
1211  */
1212 static int
1213 smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1214 {
1215 	return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn));
1216 }
1217 
1218 /*
1219  * smb_ads_modify_computer
1220  *
1221  * Returns 0 upon success. Otherwise, returns -1.
1222  */
1223 static int
1224 smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn)
1225 {
1226 	return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn));
1227 }
1228 
1229 /*
1230  * smb_ads_get_dc_level
1231  *
1232  * Returns the functional level of the DC upon success.
1233  * Otherwise, -1 is returned.
1234  */
1235 static int
1236 smb_ads_get_dc_level(smb_ads_handle_t *ah)
1237 {
1238 	LDAPMessage *res, *entry;
1239 	char *attr[2];
1240 	char **vals;
1241 	int rc = -1;
1242 
1243 	res = NULL;
1244 	attr[0] = SMB_ADS_ATTR_DCLEVEL;
1245 	attr[1] = NULL;
1246 	if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr,
1247 	    0, &res) != LDAP_SUCCESS) {
1248 		(void) ldap_msgfree(res);
1249 		return (-1);
1250 	}
1251 
1252 	/* no match for the specified attribute is found */
1253 	if (ldap_count_entries(ah->ld, res) == 0) {
1254 		(void) ldap_msgfree(res);
1255 		return (-1);
1256 	}
1257 
1258 	entry = ldap_first_entry(ah->ld, res);
1259 	if (entry) {
1260 		if ((vals = ldap_get_values(ah->ld, entry,
1261 		    SMB_ADS_ATTR_DCLEVEL)) == NULL) {
1262 			/*
1263 			 * Observed the values aren't populated
1264 			 * by the Windows 2000 server.
1265 			 */
1266 			(void) ldap_msgfree(res);
1267 			return (SMB_ADS_DCLEVEL_W2K);
1268 		}
1269 
1270 		if (vals[0] != NULL)
1271 			rc = atoi(vals[0]);
1272 
1273 		ldap_value_free(vals);
1274 	}
1275 
1276 	(void) ldap_msgfree(res);
1277 	return (rc);
1278 }
1279 
1280 /*
1281  * The fully-qualified hostname returned by this function is often used for
1282  * constructing service principal name.  Return the fully-qualified hostname
1283  * in lower case for RFC 4120 section 6.2.1 conformance.
1284  */
1285 static int
1286 smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len)
1287 {
1288 	if (smb_gethostname(fqhost, len, SMB_CASE_LOWER) != 0)
1289 		return (-1);
1290 
1291 	(void) strlcat(fqhost, ".", len);
1292 	(void) strlcat(fqhost, ah->domain, len);
1293 
1294 	return (0);
1295 }
1296 
1297 static int
1298 smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn)
1299 {
1300 	LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR];
1301 	char *sam_val[2];
1302 	char *ctl_val[2], *fqh_val[2];
1303 	char *encrypt_val[2];
1304 	int j = -1;
1305 	int ret, usrctl_flags = 0;
1306 	char sam_acct[SMB_SAMACCT_MAXLEN];
1307 	char fqhost[MAXHOSTNAMELEN];
1308 	char usrctl_buf[16];
1309 	char encrypt_buf[16];
1310 	int max;
1311 	smb_krb5_pn_set_t spn, upn;
1312 
1313 	if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1314 		return (-1);
1315 
1316 	if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1317 		return (-1);
1318 
1319 	/* The SPN attribute is multi-valued and must be 1 or greater */
1320 	if (smb_krb5_get_pn_set(&spn, SMB_PN_SPN_ATTR, ah->domain) == 0)
1321 		return (-1);
1322 
1323 	/* The UPN attribute is single-valued and cannot be zero */
1324 	if (smb_krb5_get_pn_set(&upn, SMB_PN_UPN_ATTR, ah->domain) != 1) {
1325 		smb_krb5_free_pn_set(&spn);
1326 		smb_krb5_free_pn_set(&upn);
1327 		return (-1);
1328 	}
1329 
1330 	max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0))
1331 	    - (dclevel >= SMB_ADS_DCLEVEL_W2K8 ?  0 : 1);
1332 
1333 	if (smb_ads_alloc_attr(attrs, max) != 0) {
1334 		smb_krb5_free_pn_set(&spn);
1335 		smb_krb5_free_pn_set(&upn);
1336 		return (-1);
1337 	}
1338 
1339 	/* objectClass attribute is not modifiable. */
1340 	if (op == LDAP_MOD_ADD) {
1341 		attrs[++j]->mod_op = op;
1342 		attrs[j]->mod_type = "objectClass";
1343 		attrs[j]->mod_values = smb_ads_computer_objcls;
1344 	}
1345 
1346 	attrs[++j]->mod_op = op;
1347 	attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT;
1348 	sam_val[0] = sam_acct;
1349 	sam_val[1] = 0;
1350 	attrs[j]->mod_values = sam_val;
1351 
1352 	attrs[++j]->mod_op = op;
1353 	attrs[j]->mod_type = SMB_ADS_ATTR_UPN;
1354 	attrs[j]->mod_values = upn.s_pns;
1355 
1356 	attrs[++j]->mod_op = op;
1357 	attrs[j]->mod_type = SMB_ADS_ATTR_SPN;
1358 	attrs[j]->mod_values =  spn.s_pns;
1359 
1360 	attrs[++j]->mod_op = op;
1361 	attrs[j]->mod_type = SMB_ADS_ATTR_CTL;
1362 	usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1363 	    SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD |
1364 	    SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE);
1365 	(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags);
1366 	ctl_val[0] = usrctl_buf;
1367 	ctl_val[1] = 0;
1368 	attrs[j]->mod_values = ctl_val;
1369 
1370 	attrs[++j]->mod_op = op;
1371 	attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST;
1372 	fqh_val[0] = fqhost;
1373 	fqh_val[1] = 0;
1374 	attrs[j]->mod_values = fqh_val;
1375 
1376 	/* enctypes support starting in Windows Server 2008 */
1377 	if (dclevel > SMB_ADS_DCLEVEL_W2K3) {
1378 		attrs[++j]->mod_op = op;
1379 		attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES;
1380 		(void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d",
1381 		    SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 +
1382 		    SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC);
1383 		encrypt_val[0] = encrypt_buf;
1384 		encrypt_val[1] = 0;
1385 		attrs[j]->mod_values = encrypt_val;
1386 	}
1387 
1388 	switch (op) {
1389 	case LDAP_MOD_ADD:
1390 		if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1391 			syslog(LOG_NOTICE, "ldap_add: %s",
1392 			    ldap_err2string(ret));
1393 			ret = -1;
1394 		}
1395 		break;
1396 
1397 	case LDAP_MOD_REPLACE:
1398 		if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1399 			syslog(LOG_NOTICE, "ldap_modify: %s",
1400 			    ldap_err2string(ret));
1401 			ret = -1;
1402 		}
1403 		break;
1404 
1405 	default:
1406 		ret = -1;
1407 
1408 	}
1409 
1410 	smb_ads_free_attr(attrs);
1411 	smb_krb5_free_pn_set(&spn);
1412 	smb_krb5_free_pn_set(&upn);
1413 
1414 	return (ret);
1415 }
1416 
1417 /*
1418  * Delete an ADS computer account.
1419  */
1420 static void
1421 smb_ads_del_computer(smb_ads_handle_t *ah, char *dn)
1422 {
1423 	int rc;
1424 
1425 	if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS)
1426 		smb_tracef("ldap_delete: %s", ldap_err2string(rc));
1427 }
1428 
1429 /*
1430  * Gets the value of the given attribute.
1431  */
1432 static smb_ads_qstat_t
1433 smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair)
1434 {
1435 	char **vals;
1436 	smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1437 
1438 	assert(avpair);
1439 	avpair->avp_val = NULL;
1440 	vals = ldap_get_values(ld, entry, avpair->avp_attr);
1441 	if (!vals)
1442 		return (SMB_ADS_STAT_NOT_FOUND);
1443 
1444 	if (!vals[0]) {
1445 		ldap_value_free(vals);
1446 		return (SMB_ADS_STAT_NOT_FOUND);
1447 	}
1448 
1449 	avpair->avp_val = strdup(vals[0]);
1450 	if (!avpair->avp_val)
1451 		rc = SMB_ADS_STAT_ERR;
1452 
1453 	ldap_value_free(vals);
1454 	return (rc);
1455 }
1456 
1457 /*
1458  * Process query's result.
1459  */
1460 static smb_ads_qstat_t
1461 smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res,
1462     smb_ads_avpair_t *avpair)
1463 {
1464 	char fqhost[MAXHOSTNAMELEN];
1465 	smb_ads_avpair_t dnshost_avp;
1466 	smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND;
1467 	LDAPMessage *entry;
1468 
1469 	if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN))
1470 		return (SMB_ADS_STAT_ERR);
1471 
1472 	if (ldap_count_entries(ah->ld, res) == 0)
1473 		return (SMB_ADS_STAT_NOT_FOUND);
1474 
1475 	if ((entry = ldap_first_entry(ah->ld, res)) == NULL)
1476 		return (SMB_ADS_STAT_ERR);
1477 
1478 	dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST;
1479 	rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp);
1480 
1481 	switch (rc) {
1482 	case SMB_ADS_STAT_FOUND:
1483 		/*
1484 		 * Returns SMB_ADS_STAT_DUP to avoid overwriting
1485 		 * the computer account of another system whose
1486 		 * NetBIOS name collides with that of the current
1487 		 * system.
1488 		 */
1489 		if (strcasecmp(dnshost_avp.avp_val, fqhost))
1490 			rc = SMB_ADS_STAT_DUP;
1491 
1492 		free(dnshost_avp.avp_val);
1493 		break;
1494 
1495 	case SMB_ADS_STAT_NOT_FOUND:
1496 		/*
1497 		 * Pre-created computer account doesn't have
1498 		 * the dNSHostname attribute. It's been observed
1499 		 * that the dNSHostname attribute is only set after
1500 		 * a successful domain join.
1501 		 * Returns SMB_ADS_STAT_FOUND as the account is
1502 		 * pre-created for the current system.
1503 		 */
1504 		rc = SMB_ADS_STAT_FOUND;
1505 		break;
1506 
1507 	default:
1508 		break;
1509 	}
1510 
1511 	if (rc != SMB_ADS_STAT_FOUND)
1512 		return (rc);
1513 
1514 	if (avpair)
1515 		rc = smb_ads_getattr(ah->ld, entry, avpair);
1516 
1517 	return (rc);
1518 
1519 }
1520 
1521 /*
1522  * smb_ads_lookup_computer_n_attr
1523  *
1524  * If avpair is NULL, checks the status of the specified computer account.
1525  * Otherwise, looks up the value of the specified computer account's attribute.
1526  * If found, the value field of the avpair will be allocated and set. The
1527  * caller should free the allocated buffer.
1528  *
1529  * Return:
1530  *  SMB_ADS_STAT_FOUND  - if both the computer and the specified attribute is
1531  *                        found.
1532  *  SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute
1533  *                           is not found.
1534  *  SMB_ADS_STAT_DUP - if the computer account is already used by other systems
1535  *                     in the AD. This could happen if the hostname of multiple
1536  *                     systems resolved to the same NetBIOS name.
1537  *  SMB_ADS_STAT_ERR - any failure.
1538  */
1539 static smb_ads_qstat_t
1540 smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair,
1541     int scope, char *dn)
1542 {
1543 	char *attrs[3], filter[SMB_ADS_MAXBUFLEN];
1544 	LDAPMessage *res;
1545 	char sam_acct[SMB_SAMACCT_MAXLEN], sam_acct2[SMB_SAMACCT_MAXLEN];
1546 	smb_ads_qstat_t rc;
1547 
1548 	if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0)
1549 		return (SMB_ADS_STAT_ERR);
1550 
1551 	res = NULL;
1552 	attrs[0] = SMB_ADS_ATTR_DNSHOST;
1553 	attrs[1] = NULL;
1554 	attrs[2] = NULL;
1555 
1556 	if (avpair) {
1557 		if (!avpair->avp_attr)
1558 			return (SMB_ADS_STAT_ERR);
1559 
1560 		attrs[1] = avpair->avp_attr;
1561 	}
1562 
1563 	if (smb_ads_escape_search_filter_chars(sam_acct, sam_acct2) != 0)
1564 		return (SMB_ADS_STAT_ERR);
1565 
1566 	(void) snprintf(filter, sizeof (filter),
1567 	    "(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT,
1568 	    sam_acct2);
1569 
1570 	if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0,
1571 	    &res) != LDAP_SUCCESS) {
1572 		(void) ldap_msgfree(res);
1573 		return (SMB_ADS_STAT_NOT_FOUND);
1574 	}
1575 
1576 	rc = smb_ads_get_qstat(ah, res, avpair);
1577 	/* free the search results */
1578 	(void) ldap_msgfree(res);
1579 	return (rc);
1580 }
1581 
1582 /*
1583  * smb_ads_find_computer
1584  *
1585  * Starts by searching for the system's AD computer object in the default
1586  * container (i.e. cn=Computers).  If not found, searches the entire directory.
1587  * If found, 'dn' will be set to the distinguished name of the system's AD
1588  * computer object.
1589  */
1590 static smb_ads_qstat_t
1591 smb_ads_find_computer(smb_ads_handle_t *ah, char *dn)
1592 {
1593 	smb_ads_qstat_t stat;
1594 	smb_ads_avpair_t avpair;
1595 
1596 	avpair.avp_attr = SMB_ADS_ATTR_DN;
1597 	smb_ads_get_default_comp_container_dn(ah, dn, SMB_ADS_DN_MAX);
1598 	stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_ONELEVEL,
1599 	    dn);
1600 
1601 	if (stat == SMB_ADS_STAT_NOT_FOUND) {
1602 		(void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX);
1603 		stat = smb_ads_lookup_computer_n_attr(ah, &avpair,
1604 		    LDAP_SCOPE_SUBTREE, dn);
1605 	}
1606 
1607 	if (stat == SMB_ADS_STAT_FOUND) {
1608 		(void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX);
1609 		free(avpair.avp_val);
1610 	}
1611 
1612 	return (stat);
1613 }
1614 
1615 /*
1616  * smb_ads_update_computer_cntrl_attr
1617  *
1618  * Modify the user account control attribute of an existing computer
1619  * object on AD.
1620  *
1621  * Returns LDAP error code.
1622  */
1623 static int
1624 smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn)
1625 {
1626 	LDAPMod *attrs[2];
1627 	char *ctl_val[2];
1628 	int ret = 0;
1629 	char usrctl_buf[16];
1630 
1631 	if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0)
1632 		return (LDAP_NO_MEMORY);
1633 
1634 	attrs[0]->mod_op = LDAP_MOD_REPLACE;
1635 	attrs[0]->mod_type = SMB_ADS_ATTR_CTL;
1636 
1637 	(void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags);
1638 	ctl_val[0] = usrctl_buf;
1639 	ctl_val[1] = 0;
1640 	attrs[0]->mod_values = ctl_val;
1641 	if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) {
1642 		syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret));
1643 	}
1644 
1645 	smb_ads_free_attr(attrs);
1646 	return (ret);
1647 }
1648 
1649 /*
1650  * smb_ads_lookup_computer_attr_kvno
1651  *
1652  * Lookup the value of the Kerberos version number attribute of the computer
1653  * account.
1654  */
1655 static krb5_kvno
1656 smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn)
1657 {
1658 	smb_ads_avpair_t avpair;
1659 	int kvno = 1;
1660 
1661 	avpair.avp_attr = SMB_ADS_ATTR_KVNO;
1662 	if (smb_ads_lookup_computer_n_attr(ah, &avpair,
1663 	    LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) {
1664 		kvno = atoi(avpair.avp_val);
1665 		free(avpair.avp_val);
1666 	}
1667 
1668 	return (kvno);
1669 }
1670 
1671 /*
1672  * smb_ads_join
1673  *
1674  * Besides the NT-4 style domain join (using MS-RPC), CIFS server also
1675  * provides the domain join using Kerberos Authentication, Keberos
1676  * Change & Set password, and LDAP protocols. Basically, AD join
1677  * operation would require the following tickets to be acquired for the
1678  * the user account that is provided for the domain join.
1679  *
1680  * 1) a Keberos TGT ticket,
1681  * 2) a ldap service ticket, and
1682  * 3) kadmin/changpw service ticket
1683  *
1684  * The ADS client first sends a ldap search request to find out whether
1685  * or not the workstation trust account already exists in the Active Directory.
1686  * The existing computer object for this workstation will be removed and
1687  * a new one will be added. The machine account password is randomly
1688  * generated and set for the newly created computer object using KPASSWD
1689  * protocol (See RFC 3244). Once the password is set, our ADS client
1690  * finalizes the machine account by modifying the user acount control
1691  * attribute of the computer object. Kerberos keys derived from the machine
1692  * account password will be stored locally in /etc/krb5/krb5.keytab file.
1693  * That would be needed while acquiring Kerberos TGT ticket for the host
1694  * principal after the domain join operation.
1695  */
1696 smb_ads_status_t
1697 smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd)
1698 {
1699 	smb_ads_handle_t *ah = NULL;
1700 	krb5_context ctx = NULL;
1701 	krb5_principal *krb5princs = NULL;
1702 	krb5_kvno kvno;
1703 	boolean_t delete = B_TRUE;
1704 	smb_ads_status_t rc;
1705 	boolean_t new_acct;
1706 	int dclevel, num, usrctl_flags = 0;
1707 	smb_ads_qstat_t qstat;
1708 	char dn[SMB_ADS_DN_MAX];
1709 	char tmpfile[] = SMBNS_KRB5_KEYTAB_TMP;
1710 	int cnt, x;
1711 	smb_krb5_pn_set_t spns;
1712 	krb5_enctype *encptr;
1713 
1714 	rc = smb_ads_open_main(&ah, domain, user, usr_passwd);
1715 	if (rc != 0) {
1716 		smb_ccache_remove(SMB_CCACHE_PATH);
1717 		return (rc);
1718 	}
1719 
1720 	if ((dclevel = smb_ads_get_dc_level(ah)) == -1) {
1721 		smb_ads_close(ah);
1722 		smb_ccache_remove(SMB_CCACHE_PATH);
1723 		return (SMB_ADJOIN_ERR_GET_DCLEVEL);
1724 	}
1725 
1726 	qstat = smb_ads_find_computer(ah, dn);
1727 	switch (qstat) {
1728 	case SMB_ADS_STAT_FOUND:
1729 		new_acct = B_FALSE;
1730 		if (smb_ads_modify_computer(ah, dclevel, dn) != 0) {
1731 			smb_ads_close(ah);
1732 			smb_ccache_remove(SMB_CCACHE_PATH);
1733 			return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT);
1734 		}
1735 		break;
1736 
1737 	case SMB_ADS_STAT_NOT_FOUND:
1738 		new_acct = B_TRUE;
1739 		smb_ads_get_default_comp_dn(ah, dn, SMB_ADS_DN_MAX);
1740 		if (smb_ads_add_computer(ah, dclevel, dn) != 0) {
1741 			smb_ads_close(ah);
1742 			smb_ccache_remove(SMB_CCACHE_PATH);
1743 			return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT);
1744 		}
1745 		break;
1746 
1747 	default:
1748 		if (qstat == SMB_ADS_STAT_DUP)
1749 			rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT;
1750 		else
1751 			rc = SMB_ADJOIN_ERR_TRUST_ACCT;
1752 		smb_ads_close(ah);
1753 		smb_ccache_remove(SMB_CCACHE_PATH);
1754 		return (rc);
1755 	}
1756 
1757 	if (smb_krb5_ctx_init(&ctx) != 0) {
1758 		rc = SMB_ADJOIN_ERR_INIT_KRB_CTX;
1759 		goto adjoin_cleanup;
1760 	}
1761 
1762 	if (smb_krb5_get_pn_set(&spns, SMB_PN_KEYTAB_ENTRY, ah->domain) == 0) {
1763 		rc = SMB_ADJOIN_ERR_GET_SPNS;
1764 		goto adjoin_cleanup;
1765 	}
1766 
1767 	if (smb_krb5_get_kprincs(ctx, spns.s_pns, spns.s_cnt, &krb5princs)
1768 	    != 0) {
1769 		smb_krb5_free_pn_set(&spns);
1770 		rc = SMB_ADJOIN_ERR_GET_SPNS;
1771 		goto adjoin_cleanup;
1772 	}
1773 
1774 	cnt = spns.s_cnt;
1775 	smb_krb5_free_pn_set(&spns);
1776 
1777 	/* New machine_passwd was filled in by our caller. */
1778 	if (smb_krb5_setpwd(ctx, ah->domain, machine_passwd) != 0) {
1779 		rc = SMB_ADJOIN_ERR_KSETPWD;
1780 		goto adjoin_cleanup;
1781 	}
1782 
1783 	kvno = smb_ads_lookup_computer_attr_kvno(ah, dn);
1784 
1785 	/*
1786 	 * Only members of Domain Admins and Enterprise Admins can set
1787 	 * the TRUSTED_FOR_DELEGATION userAccountControl flag.
1788 	 * Try to set this, but don't fail the join if we can't.
1789 	 * Look into just removing this...
1790 	 */
1791 	usrctl_flags = (
1792 	    SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT |
1793 	    SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION |
1794 	    SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD);
1795 set_ctl_again:
1796 	x = smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn);
1797 	if (x != LDAP_SUCCESS && (usrctl_flags &
1798 	    SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION) != 0) {
1799 		syslog(LOG_NOTICE, "Unable to set the "
1800 "TRUSTED_FOR_DELEGATION userAccountControl flag on the "
1801 "machine account in Active Directory.  It may be necessary "
1802 "to set that via Active Directory administration.");
1803 		usrctl_flags &=
1804 		    ~SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION;
1805 		goto set_ctl_again;
1806 	}
1807 	if (x != LDAP_SUCCESS) {
1808 		rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR;
1809 		goto adjoin_cleanup;
1810 	}
1811 
1812 	if (mktemp(tmpfile) == NULL) {
1813 		rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
1814 		goto adjoin_cleanup;
1815 	}
1816 
1817 	encptr = smb_ads_get_enctypes(dclevel, &num);
1818 	if (smb_krb5_kt_populate(ctx, ah->domain, krb5princs, cnt,
1819 	    tmpfile, kvno, machine_passwd, encptr, num) != 0) {
1820 		rc = SMB_ADJOIN_ERR_WRITE_KEYTAB;
1821 		goto adjoin_cleanup;
1822 	}
1823 
1824 	delete = B_FALSE;
1825 	rc = SMB_ADS_SUCCESS;
1826 
1827 adjoin_cleanup:
1828 	if (new_acct && delete)
1829 		smb_ads_del_computer(ah, dn);
1830 
1831 	if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) {
1832 		if (rc != SMB_ADJOIN_ERR_GET_SPNS)
1833 			smb_krb5_free_kprincs(ctx, krb5princs, cnt);
1834 		smb_krb5_ctx_fini(ctx);
1835 	}
1836 
1837 	/* commit keytab file */
1838 	if (rc == SMB_ADS_SUCCESS) {
1839 		if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) {
1840 			(void) unlink(tmpfile);
1841 			rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB;
1842 		}
1843 	} else {
1844 		(void) unlink(tmpfile);
1845 	}
1846 
1847 	smb_ads_close(ah);
1848 	smb_ccache_remove(SMB_CCACHE_PATH);
1849 	return (rc);
1850 }
1851 
1852 struct xlate_table {
1853 	int err;
1854 	const char * const msg;
1855 };
1856 
1857 static const struct xlate_table
1858 adjoin_table[] = {
1859 	{ SMB_ADS_SUCCESS, "Success" },
1860 	{ SMB_ADS_KRB5_INIT_CTX,
1861 	    "Failed creating a Kerberos context." },
1862 	{ SMB_ADS_KRB5_CC_DEFAULT,
1863 	    "Failed to resolve default credential cache." },
1864 	{ SMB_ADS_KRB5_PARSE_PRINCIPAL,
1865 	    "Failed parsing the user principal name." },
1866 	{ SMB_ADS_KRB5_GET_INIT_CREDS_PW,
1867 	    "Failed getting initial credentials.  (Wrong password?)" },
1868 	{ SMB_ADS_KRB5_CC_INITIALIZE,
1869 	    "Failed initializing the credential cache." },
1870 	{ SMB_ADS_KRB5_CC_STORE_CRED,
1871 	    "Failed to update the credential cache." },
1872 	{ SMB_ADS_CANT_LOCATE_DC,
1873 	    "Failed to locate a domain controller." },
1874 	{ SMB_ADS_LDAP_INIT,
1875 	    "Failed to create an LDAP handle." },
1876 	{ SMB_ADS_LDAP_SETOPT,
1877 	    "Failed to set an LDAP option." },
1878 	{ SMB_ADS_LDAP_SET_DOM,
1879 	    "Failed to set the LDAP handle DN." },
1880 	{ SMB_ADS_LDAP_SASL_BIND,
1881 	    "Failed to bind the LDAP handle. "
1882 	    "Usually indicates an authentication problem." },
1883 
1884 	{ SMB_ADJOIN_ERR_GEN_PWD,
1885 	    "Failed to generate machine password." },
1886 	{ SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of "
1887 	    "the domain controller. The rootDSE attribute named "
1888 	    "\"domainControllerFunctionality\" is missing from the "
1889 	    "Active Directory." },
1890 	{ SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the "
1891 	    "workstation trust account." },
1892 	{ SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the "
1893 	    "workstation trust account." },
1894 	{ SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the "
1895 	    "workstation trust account because its name is already "
1896 	    "in use." },
1897 	{ SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the "
1898 	    "workstation trust account" },
1899 	{ SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos "
1900 	    "context." },
1901 	{ SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos "
1902 	    "principals." },
1903 	{ SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." },
1904 	{ SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR,  "Failed to modify "
1905 	    "userAccountControl attribute of the workstation trust "
1906 	    "account." },
1907 	{ SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local "
1908 	    "keytab file (i.e /etc/krb5/krb5.keytab)." },
1909 	{ SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap "
1910 	    "configuration." },
1911 	{ SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap "
1912 	    "service." },
1913 	{ SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to "
1914 	    "local keytab file (i.e. /etc/krb5/krb5.keytab)." },
1915 	{ SMB_ADJOIN_ERR_AUTH_NETLOGON,
1916 	    "Failed to authenticate using the new computer account." },
1917 	{ SMB_ADJOIN_ERR_STORE_PROPS,
1918 	    "Failed to store computer account information locally." },
1919 	{ 0, NULL }
1920 };
1921 
1922 /*
1923  * smb_ads_strerror
1924  *
1925  * Lookup an error message for the specific adjoin error code.
1926  */
1927 const char *
1928 smb_ads_strerror(int err)
1929 {
1930 	const struct xlate_table *xt;
1931 
1932 	if (err > 0 && err < SMB_ADS_ERRNO_GAP)
1933 		return (strerror(err));
1934 
1935 	for (xt = adjoin_table; xt->msg; xt++)
1936 		if (xt->err == err)
1937 			return (xt->msg);
1938 
1939 	return ("Unknown error code.");
1940 }
1941 
1942 void
1943 smb_ads_log_errmsg(smb_ads_status_t err)
1944 {
1945 	const char *s = smb_ads_strerror(err);
1946 	syslog(LOG_NOTICE, "%s", s);
1947 }
1948 
1949 
1950 /*
1951  * smb_ads_lookup_msdcs
1952  *
1953  * If server argument is set, try to locate the specified DC.
1954  * If it is set to empty string, locate any DCs in the specified domain.
1955  * Returns the discovered DC via buf.
1956  *
1957  * fqdn	  - fully-qualified domain name
1958  * dci    - the name and address of the found DC
1959  */
1960 uint32_t
1961 smb_ads_lookup_msdcs(char *fqdn, smb_dcinfo_t *dci)
1962 {
1963 	smb_ads_host_info_t *hinfo = NULL;
1964 	char ipstr[INET6_ADDRSTRLEN];
1965 
1966 	if (!fqdn || !dci)
1967 		return (NT_STATUS_INTERNAL_ERROR);
1968 
1969 	ipstr[0] = '\0';
1970 	if ((hinfo = smb_ads_find_host(fqdn)) == NULL)
1971 		return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
1972 
1973 	(void) smb_inet_ntop(&hinfo->ipaddr, ipstr,
1974 	    SMB_IPSTRLEN(hinfo->ipaddr.a_family));
1975 	smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr);
1976 
1977 	(void) strlcpy(dci->dc_name, hinfo->name, sizeof (dci->dc_name));
1978 	dci->dc_addr = hinfo->ipaddr;
1979 	dci->dc_flags = hinfo->flags;
1980 
1981 	free(hinfo);
1982 	return (NT_STATUS_SUCCESS);
1983 }
1984 
1985 static krb5_enctype *
1986 smb_ads_get_enctypes(int dclevel, int *num)
1987 {
1988 	krb5_enctype *encptr;
1989 
1990 	if (dclevel >= SMB_ADS_DCLEVEL_W2K8) {
1991 		*num = sizeof (w2k8enctypes) / sizeof (krb5_enctype);
1992 		encptr = w2k8enctypes;
1993 	} else {
1994 		*num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype);
1995 		encptr = pre_w2k8enctypes;
1996 	}
1997 
1998 	return (encptr);
1999 }
2000