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