xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c (revision de81e71e031139a0a7f13b7bf64152c3faa76698)
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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <syslog.h>
27 #include <synch.h>
28 #include <pthread.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <strings.h>
32 #include <sys/errno.h>
33 #include <sys/types.h>
34 #include <netinet/in.h>
35 #include <arpa/nameser.h>
36 #include <resolv.h>
37 #include <netdb.h>
38 #include <assert.h>
39 
40 #include <smbsrv/libsmb.h>
41 #include <smbsrv/libsmbrdr.h>
42 #include <smbsrv/libsmbns.h>
43 #include <smbsrv/libmlsvc.h>
44 
45 #include <smbsrv/smbinfo.h>
46 #include <smbsrv/ntstatus.h>
47 #include <lsalib.h>
48 
49 /*
50  * Domain cache states
51  */
52 #define	SMB_DCACHE_STATE_INVALID	0
53 #define	SMB_DCACHE_STATE_UPDATING	1
54 #define	SMB_DCACHE_STATE_VALID		2
55 
56 typedef struct smb_domain_cache {
57 	uint32_t	c_state;
58 	smb_domain_t	c_cache;
59 	mutex_t		c_mtx;
60 	cond_t		c_cv;
61 } smb_domain_cache_t;
62 
63 static smb_domain_cache_t smb_dcache;
64 
65 /* functions to manipulate the domain cache */
66 static void smb_dcache_init(void);
67 static void smb_dcache_updating(void);
68 static void smb_dcache_invalid(void);
69 static void smb_dcache_valid(smb_domain_t *);
70 static void smb_dcache_set(uint32_t, smb_domain_t *);
71 
72 /*
73  * DC Locator
74  */
75 #define	SMB_DCLOCATOR_TIMEOUT	45
76 #define	SMB_IS_FQDN(domain)	(strchr(domain, '.') != NULL)
77 
78 typedef struct smb_dclocator {
79 	char sdl_domain[SMB_PI_MAX_DOMAIN];
80 	char sdl_dc[MAXHOSTNAMELEN];
81 	boolean_t sdl_locate;
82 	mutex_t sdl_mtx;
83 	cond_t sdl_cv;
84 	uint32_t sdl_status;
85 } smb_dclocator_t;
86 
87 static smb_dclocator_t smb_dclocator;
88 static pthread_t smb_dclocator_thr;
89 
90 static void *smb_dclocator_main(void *);
91 static boolean_t smb_dc_discovery(char *, char *, smb_domain_t *);
92 static boolean_t smb_match_domains(char *, char *, uint32_t);
93 static uint32_t smb_domain_query(char *, char *, smb_domain_t *);
94 static void smb_domain_update_tabent(int, lsa_nt_domaininfo_t *);
95 static void smb_domain_populate_table(char *, char *);
96 static boolean_t smb_domain_use_config(char *, smb_domain_t *);
97 
98 /*
99  * ===================================================================
100  * API to initialize DC locator thread, trigger DC discovery, and
101  * get the discovered DC and/or domain information.
102  * ===================================================================
103  */
104 
105 /*
106  * smb_dclocator_init
107  *
108  * Initialization of the DC locator thread.
109  * Returns 0 on success, an error number if thread creation fails.
110  */
111 int
112 smb_dclocator_init(void)
113 {
114 	pthread_attr_t tattr;
115 	int rc;
116 
117 	smb_dcache_init();
118 	(void) pthread_attr_init(&tattr);
119 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
120 	rc = pthread_create(&smb_dclocator_thr, &tattr,
121 	    smb_dclocator_main, 0);
122 	(void) pthread_attr_destroy(&tattr);
123 	return (rc);
124 }
125 
126 /*
127  * smb_locate_dc
128  *
129  * This is the entry point for discovering a domain controller for the
130  * specified domain.
131  *
132  * The actual work of discovering a DC is handled by DC locator thread.
133  * All we do here is signal the request and wait for a DC or a timeout.
134  *
135  * Input parameters:
136  *  domain - domain to be discovered (can either be NetBIOS or DNS domain)
137  *  dc - preferred DC. If the preferred DC is set to empty string, it
138  *       will attempt to discover any DC in the specified domain.
139  *
140  * Output parameter:
141  *  dp - on success, dp will be filled with the discovered DC and domain
142  *       information.
143  * Returns B_TRUE if the DC/domain info is available.
144  */
145 boolean_t
146 smb_locate_dc(char *domain, char *dc, smb_domain_t *dp)
147 {
148 	int rc;
149 	timestruc_t to;
150 	smb_domain_t domain_info;
151 
152 	if (domain == NULL || *domain == '\0')
153 		return (B_FALSE);
154 
155 	(void) mutex_lock(&smb_dclocator.sdl_mtx);
156 
157 	if (!smb_dclocator.sdl_locate) {
158 		smb_dclocator.sdl_locate = B_TRUE;
159 		(void) strlcpy(smb_dclocator.sdl_domain, domain,
160 		    SMB_PI_MAX_DOMAIN);
161 		(void) strlcpy(smb_dclocator.sdl_dc, dc,
162 		    MAXHOSTNAMELEN);
163 		(void) cond_broadcast(&smb_dclocator.sdl_cv);
164 	}
165 
166 	while (smb_dclocator.sdl_locate) {
167 		to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
168 		to.tv_nsec = 0;
169 		rc = cond_reltimedwait(&smb_dclocator.sdl_cv,
170 		    &smb_dclocator.sdl_mtx, &to);
171 
172 		if (rc == ETIME)
173 			break;
174 	}
175 
176 	if (dp == NULL)
177 		dp = &domain_info;
178 	rc = smb_domain_getinfo(dp);
179 	(void) mutex_unlock(&smb_dclocator.sdl_mtx);
180 
181 	return (rc);
182 }
183 
184 /*
185  * smb_domain_getinfo
186  *
187  * If the DC discovery process is underway, this function will wait on
188  * a condition variable until the state of SMB domain cache sets to
189  * either VALID/INVALID.
190  *
191  * Returns a copy of the domain cache.
192  */
193 boolean_t
194 smb_domain_getinfo(smb_domain_t *dp)
195 {
196 	timestruc_t to;
197 	int err;
198 	boolean_t rc;
199 
200 	(void) mutex_lock(&smb_dcache.c_mtx);
201 	to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
202 	to.tv_nsec = 0;
203 	while (smb_dcache.c_state == SMB_DCACHE_STATE_UPDATING) {
204 		err = cond_reltimedwait(&smb_dcache.c_cv, &smb_dcache.c_mtx,
205 		    &to);
206 		if (err == ETIME)
207 			break;
208 	}
209 
210 	if (smb_dcache.c_state == SMB_DCACHE_STATE_VALID) {
211 		bcopy(&smb_dcache.c_cache, dp, sizeof (smb_domain_t));
212 		rc = B_TRUE;
213 	} else {
214 		bzero(dp, sizeof (smb_domain_t));
215 		rc = B_FALSE;
216 	}
217 
218 	(void) mutex_unlock(&smb_dcache.c_mtx);
219 	return (rc);
220 }
221 
222 
223 /*
224  * =====================================================================
225  * Private functions used by DC locator thread to manipulate the domain
226  * cache.
227  * ======================================================================
228  */
229 
230 static void
231 smb_dcache_init(void)
232 {
233 	(void) mutex_lock(&smb_dcache.c_mtx);
234 	smb_dcache.c_state = SMB_DCACHE_STATE_INVALID;
235 	bzero(&smb_dcache.c_cache, sizeof (smb_domain_t));
236 	(void) mutex_unlock(&smb_dcache.c_mtx);
237 }
238 
239 /*
240  * Set the cache state to UPDATING
241  */
242 static void
243 smb_dcache_updating(void)
244 {
245 	smb_dcache_set(SMB_DCACHE_STATE_UPDATING, NULL);
246 }
247 
248 /*
249  * Set the cache state to INVALID
250  */
251 static void
252 smb_dcache_invalid(void)
253 {
254 	smb_dcache_set(SMB_DCACHE_STATE_INVALID, NULL);
255 }
256 
257 /*
258  * Set the cache state to VALID and populate the cache
259  */
260 static void
261 smb_dcache_valid(smb_domain_t *dp)
262 {
263 	smb_dcache_set(SMB_DCACHE_STATE_VALID, dp);
264 }
265 
266 /*
267  * This function will update both the state and the contents of the
268  * SMB domain cache.  If one attempts to set the state to
269  * SMB_DCACHE_STATE_UPDATING, the domain cache will be updated based
270  * on 'dp' argument. Otherwise, 'dp' is ignored.
271  */
272 static void
273 smb_dcache_set(uint32_t state, smb_domain_t *dp)
274 {
275 	(void) mutex_lock(&smb_dcache.c_mtx);
276 	switch (state) {
277 	case  SMB_DCACHE_STATE_INVALID:
278 		break;
279 
280 	case SMB_DCACHE_STATE_UPDATING:
281 		bzero(&smb_dcache.c_cache, sizeof (smb_domain_t));
282 		break;
283 
284 	case SMB_DCACHE_STATE_VALID:
285 		assert(dp);
286 		bcopy(dp, &smb_dcache.c_cache, sizeof (smb_domain_t));
287 		break;
288 
289 	default:
290 		(void) mutex_unlock(&smb_dcache.c_mtx);
291 		return;
292 
293 	}
294 
295 	smb_dcache.c_state = state;
296 	(void) cond_broadcast(&smb_dcache.c_cv);
297 	(void) mutex_unlock(&smb_dcache.c_mtx);
298 }
299 
300 /*
301  * ==========================================================
302  * DC discovery functions
303  * ==========================================================
304  */
305 
306 /*
307  * smb_dclocator_main
308  *
309  * This is the DC discovery thread: it gets woken up whenever someone
310  * wants to locate a domain controller.
311  *
312  * The state of the SMB domain cache will be initialized to
313  * SMB_DCACHE_STATE_UPDATING when the discovery process starts and will be
314  * transitioned to SMB_DCACHE_STATE_VALID/INVALID depending on the outcome of
315  * the discovery.
316  *
317  * If the discovery process is underway, callers of smb_domain_getinfo()
318  * will wait on a condition variable until the state of SMB domain cache
319  * sets to either VALID/INVALID.
320  *
321  * Upon success, the SMB domain cache will be populated with the discovered DC
322  * and domain info.
323  */
324 /*ARGSUSED*/
325 static void *
326 smb_dclocator_main(void *arg)
327 {
328 	char domain[SMB_PI_MAX_DOMAIN];
329 	char sought_dc[MAXHOSTNAMELEN];
330 	smb_domain_t dinfo;
331 
332 	for (;;) {
333 		(void) mutex_lock(&smb_dclocator.sdl_mtx);
334 
335 		while (!smb_dclocator.sdl_locate)
336 			(void) cond_wait(&smb_dclocator.sdl_cv,
337 			    &smb_dclocator.sdl_mtx);
338 
339 		(void) strlcpy(domain, smb_dclocator.sdl_domain,
340 		    SMB_PI_MAX_DOMAIN);
341 		(void) strlcpy(sought_dc, smb_dclocator.sdl_dc, MAXHOSTNAMELEN);
342 		(void) mutex_unlock(&smb_dclocator.sdl_mtx);
343 
344 		smb_dcache_updating();
345 		if (smb_dc_discovery(domain, sought_dc, &dinfo))
346 			smb_dcache_valid(&dinfo);
347 		else
348 			smb_dcache_invalid();
349 
350 		(void) mutex_lock(&smb_dclocator.sdl_mtx);
351 		smb_dclocator.sdl_locate = B_FALSE;
352 		(void) cond_broadcast(&smb_dclocator.sdl_cv);
353 		(void) mutex_unlock(&smb_dclocator.sdl_mtx);
354 	}
355 
356 	/*NOTREACHED*/
357 	return (NULL);
358 }
359 
360 /*
361  * smb_dc_discovery
362  *
363  * If FQDN is specified, DC discovery will be done via DNS query only.
364  * If NetBIOS name of a domain is specified, DC discovery thread will
365  * use netlogon protocol to locate a DC. Upon failure, it will
366  * try to resolve it via DNS, i.e. find out if it is the first label
367  * of a DNS domain name. If the corresponding DNS name is found, DC
368  * discovery will be done via DNS query.
369  *
370  * Once the domain controller is found, it then queries the DC for domain
371  * information. If the LSA queries fail, the domain information stored in
372  * SMF might be used to set the SMB domain cache if the the discovered domain
373  * is the same as the previously joined domain.
374  *
375  * If the fully-qualified domain name is derived from the DNS config
376  * file, the NetBIOS domain name specified by the user will be compared
377  * against the NetBIOS domain name obtained via LSA query.  If there is
378  * a mismatch, the DC discovery will fail since the discovered DC is
379  * actually for another domain, whose first label of its FQDN somehow
380  * matches with the NetBIOS name of the domain we're interested in.
381  */
382 static boolean_t
383 smb_dc_discovery(char *domain, char *server, smb_domain_t *dinfo)
384 {
385 	char derived_dnsdomain[MAXHOSTNAMELEN];
386 	boolean_t netlogon_ok = B_FALSE;
387 
388 	*derived_dnsdomain = '\0';
389 	if (!SMB_IS_FQDN(domain)) {
390 		if (smb_browser_netlogon(domain, dinfo->d_dc, MAXHOSTNAMELEN))
391 			netlogon_ok = B_TRUE;
392 		else if (!smb_match_domains(domain, derived_dnsdomain,
393 		    MAXHOSTNAMELEN))
394 			return (B_FALSE);
395 	}
396 
397 	if (!netlogon_ok && !smb_ads_lookup_msdcs(
398 	    (SMB_IS_FQDN(domain) ? domain : derived_dnsdomain), server,
399 	    dinfo->d_dc, MAXHOSTNAMELEN))
400 		return (B_FALSE);
401 
402 	if ((smb_domain_query(domain, dinfo->d_dc, dinfo)
403 	    != NT_STATUS_SUCCESS) &&
404 	    (!smb_domain_use_config(domain, dinfo)))
405 			return (B_FALSE);
406 
407 	if (*derived_dnsdomain != '\0' &&
408 	    utf8_strcasecmp(domain, dinfo->d_nbdomain))
409 		return (B_FALSE);
410 
411 	/*
412 	 * Now that we get the fully-qualified DNS name of the
413 	 * domain via LSA query. Verifies ADS configuration
414 	 * if we previously locate a DC via NetBIOS. On success,
415 	 * ADS cache will be populated.
416 	 */
417 	if (netlogon_ok) {
418 		if (smb_ads_lookup_msdcs(dinfo->d_fqdomain, server,
419 		    dinfo->d_dc, MAXHOSTNAMELEN) == 0)
420 			return (B_FALSE);
421 	}
422 
423 	return (B_TRUE);
424 }
425 
426 /*
427  * Tries to find a matching DNS domain for the given NetBIOS domain
428  * name by checking the first label of system's configured DNS domains.
429  * If a match is found, it'll be returned in the passed buffer.
430  */
431 static boolean_t
432 smb_match_domains(char *nb_domain, char *buf, uint32_t len)
433 {
434 	struct __res_state res_state;
435 	int i;
436 	char *entry, *p;
437 	char first_label[MAXHOSTNAMELEN];
438 	boolean_t found;
439 
440 	if (!nb_domain || !buf)
441 		return (B_FALSE);
442 
443 	*buf = '\0';
444 	bzero(&res_state, sizeof (struct __res_state));
445 	if (res_ninit(&res_state))
446 		return (B_FALSE);
447 
448 	found = B_FALSE;
449 	entry = res_state.defdname;
450 	for (i = 0; entry != NULL; i++) {
451 		(void) strlcpy(first_label, entry, MAXHOSTNAMELEN);
452 		if ((p = strchr(first_label, '.')) != NULL) {
453 			*p = '\0';
454 			if (strlen(first_label) > 15)
455 				first_label[15] = '\0';
456 		}
457 
458 		if (utf8_strcasecmp(nb_domain, first_label) == 0) {
459 			found = B_TRUE;
460 			(void) strlcpy(buf, entry, len);
461 			break;
462 		}
463 
464 		entry = res_state.dnsrch[i];
465 	}
466 
467 
468 	res_ndestroy(&res_state);
469 	return (found);
470 }
471 
472 /*
473  * smb_domain_query
474  *
475  * If the the NetBIOS name of an AD domain doesn't match with the
476  * first label of its fully-qualified DNS name, it is not possible
477  * to derive one name format from another.
478  * The missing domain info can be obtained via LSA query, DNS domain info.
479  *
480  * domain - either NetBIOS or fully-qualified domain name
481  *
482  */
483 static uint32_t
484 smb_domain_query(char *domain, char *server, smb_domain_t *dp)
485 {
486 	uint32_t rc;
487 	lsa_info_t info;
488 
489 	rc = lsa_query_dns_domain_info(server, domain, &info);
490 	if (rc == NT_STATUS_SUCCESS) {
491 		lsa_dns_domaininfo_t *dnsinfo = &info.i_domain.di_dns;
492 		(void) strlcpy(dp->d_nbdomain, dnsinfo->d_nbdomain,
493 		    sizeof (dp->d_nbdomain));
494 		(void) strlcpy(dp->d_fqdomain, dnsinfo->d_fqdomain,
495 		    sizeof (dp->d_fqdomain));
496 		(void) strlcpy(dp->d_forest, dnsinfo->d_forest,
497 		    sizeof (dp->d_forest));
498 		ndr_uuid_unparse((ndr_uuid_t *)&dnsinfo->d_guid, dp->d_guid);
499 		smb_sid_free(dnsinfo->d_sid);
500 	}
501 
502 	smb_domain_populate_table(domain, server);
503 	return (rc);
504 }
505 
506 /*
507  * smb_domain_populate_table
508  *
509  * Populates the domain tablele with primary, account and trusted
510  * domain info.
511  * domain - either NetBIOS or fully-qualified domain name.
512  */
513 static void
514 smb_domain_populate_table(char *domain, char *server)
515 {
516 	lsa_info_t info;
517 	lsa_nt_domaininfo_t *nt_info;
518 	int i;
519 
520 	if (lsa_query_primary_domain_info(server, domain, &info)
521 	    == NT_STATUS_SUCCESS) {
522 		nt_domain_flush(NT_DOMAIN_PRIMARY);
523 
524 		nt_info = &info.i_domain.di_primary;
525 		smb_domain_update_tabent(NT_DOMAIN_PRIMARY, nt_info);
526 		lsa_free_info(&info);
527 	}
528 
529 	if (lsa_query_account_domain_info(server, domain, &info)
530 	    == NT_STATUS_SUCCESS) {
531 		nt_domain_flush(NT_DOMAIN_ACCOUNT);
532 
533 		nt_info = &info.i_domain.di_account;
534 		smb_domain_update_tabent(NT_DOMAIN_ACCOUNT, nt_info);
535 		lsa_free_info(&info);
536 	}
537 
538 	if (lsa_enum_trusted_domains(server, domain, &info)
539 	    == NT_STATUS_SUCCESS) {
540 		lsa_trusted_domainlist_t *list = &info.i_domain.di_trust;
541 
542 		nt_domain_flush(NT_DOMAIN_TRUSTED);
543 
544 		for (i = 0; i < list->t_num; i++) {
545 			nt_info = &list->t_domains[i];
546 			smb_domain_update_tabent(NT_DOMAIN_TRUSTED, nt_info);
547 		}
548 
549 		lsa_free_info(&info);
550 	}
551 
552 	nt_domain_save();
553 }
554 
555 static void
556 smb_domain_update_tabent(int domain_type, lsa_nt_domaininfo_t *info)
557 {
558 	nt_domain_t *entry;
559 
560 	entry = nt_domain_new(domain_type, info->n_domain, info->n_sid);
561 	(void) nt_domain_add(entry);
562 }
563 
564 /*
565  * smb_domain_use_config
566  *
567  * If the domain to be discovered matches the current domain (i.e the
568  * value of either domain or fqdn configuration), the output parameter
569  * 'dinfo' will be set to the information stored in SMF.
570  */
571 static boolean_t
572 smb_domain_use_config(char *domain, smb_domain_t *dinfo)
573 {
574 	smb_domain_t orig;
575 	boolean_t use;
576 
577 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
578 		return (B_FALSE);
579 
580 	smb_config_getdomaininfo(orig.d_nbdomain, orig.d_fqdomain,
581 	    orig.d_forest, orig.d_guid);
582 
583 	if (SMB_IS_FQDN(domain)) {
584 		use = (utf8_strcasecmp(orig.d_fqdomain, domain) == 0);
585 	} else {
586 		use = (utf8_strcasecmp(orig.d_nbdomain, domain) == 0);
587 	}
588 
589 	if (use) {
590 		(void) strlcpy(dinfo->d_nbdomain, orig.d_nbdomain,
591 		    sizeof (dinfo->d_nbdomain));
592 		(void) strlcpy(dinfo->d_fqdomain, orig.d_fqdomain,
593 		    sizeof (dinfo->d_fqdomain));
594 		(void) strlcpy(dinfo->d_forest, orig.d_forest,
595 		    sizeof (dinfo->d_forest));
596 		(void) bcopy(orig.d_guid, dinfo->d_guid,
597 		    sizeof (dinfo->d_guid));
598 	}
599 
600 	return (use);
601 }
602