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