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