xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c (revision c3d26abc9ee97b4f60233556aadeb57e0bd30bb9)
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 /*
23  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
25  */
26 
27 #include <syslog.h>
28 #include <synch.h>
29 #include <pthread.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <strings.h>
33 #include <sys/errno.h>
34 #include <sys/types.h>
35 #include <netinet/in.h>
36 #include <arpa/nameser.h>
37 #include <resolv.h>
38 #include <netdb.h>
39 #include <assert.h>
40 
41 #include <smbsrv/libsmb.h>
42 #include <smbsrv/libsmbns.h>
43 #include <smbsrv/libmlsvc.h>
44 
45 #include <smbsrv/smbinfo.h>
46 #include <lsalib.h>
47 #include <mlsvc.h>
48 
49 /*
50  * DC Locator
51  */
52 #define	SMB_DCLOCATOR_TIMEOUT	45	/* seconds */
53 #define	SMB_IS_FQDN(domain)	(strchr(domain, '.') != NULL)
54 
55 typedef struct smb_dclocator {
56 	smb_dcinfo_t	sdl_dci; /* .dc_name .dc_addr */
57 	char		sdl_domain[SMB_PI_MAX_DOMAIN];
58 	boolean_t	sdl_locate;
59 	boolean_t	sdl_bad_dc;
60 	boolean_t	sdl_cfg_chg;
61 	mutex_t		sdl_mtx;
62 	cond_t		sdl_cv;
63 	uint32_t	sdl_status;
64 } smb_dclocator_t;
65 
66 static smb_dclocator_t smb_dclocator;
67 static pthread_t smb_dclocator_thr;
68 
69 static void *smb_ddiscover_service(void *);
70 static uint32_t smb_ddiscover_qinfo(char *, char *, smb_domainex_t *);
71 static void smb_ddiscover_enum_trusted(char *, char *, smb_domainex_t *);
72 static uint32_t smb_ddiscover_use_config(char *, smb_domainex_t *);
73 static void smb_domainex_free(smb_domainex_t *);
74 static void smb_set_krb5_realm(char *);
75 
76 /*
77  * ===================================================================
78  * API to initialize DC locator thread, trigger DC discovery, and
79  * get the discovered DC and/or domain information.
80  * ===================================================================
81  */
82 
83 /*
84  * Initialization of the DC locator thread.
85  * Returns 0 on success, an error number if thread creation fails.
86  */
87 int
88 smb_dclocator_init(void)
89 {
90 	pthread_attr_t tattr;
91 	int rc;
92 
93 	(void) pthread_attr_init(&tattr);
94 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
95 	rc = pthread_create(&smb_dclocator_thr, &tattr,
96 	    smb_ddiscover_service, &smb_dclocator);
97 	(void) pthread_attr_destroy(&tattr);
98 	return (rc);
99 }
100 
101 /*
102  * This is the entry point for discovering a domain controller for the
103  * specified domain.  Called during join domain, and then periodically
104  * by smbd_dc_update (the "DC monitor" thread).
105  *
106  * The actual work of discovering a DC is handled by DC locator thread.
107  * All we do here is signal the request and wait for a DC or a timeout.
108  *
109  * Input parameters:
110  *  domain - domain to be discovered (can either be NetBIOS or DNS domain)
111  *
112  * Output parameter:
113  *  dp - on success, dp will be filled with the discovered DC and domain
114  *       information.
115  *
116  * Returns B_TRUE if the DC/domain info is available.
117  */
118 boolean_t
119 smb_locate_dc(char *domain, smb_domainex_t *dp)
120 {
121 	int rc;
122 	boolean_t rv;
123 	timestruc_t to;
124 	smb_domainex_t domain_info;
125 
126 	if (domain == NULL || *domain == '\0') {
127 		syslog(LOG_DEBUG, "smb_locate_dc NULL dom");
128 		smb_set_krb5_realm(NULL);
129 		return (B_FALSE);
130 	}
131 
132 	(void) mutex_lock(&smb_dclocator.sdl_mtx);
133 
134 	if (strcmp(smb_dclocator.sdl_domain, domain)) {
135 		(void) strlcpy(smb_dclocator.sdl_domain, domain,
136 		    sizeof (smb_dclocator.sdl_domain));
137 		smb_dclocator.sdl_cfg_chg = B_TRUE;
138 		syslog(LOG_DEBUG, "smb_locate_dc new dom=%s", domain);
139 		smb_set_krb5_realm(domain);
140 	}
141 
142 	if (!smb_dclocator.sdl_locate) {
143 		smb_dclocator.sdl_locate = B_TRUE;
144 		(void) cond_broadcast(&smb_dclocator.sdl_cv);
145 	}
146 
147 	while (smb_dclocator.sdl_locate) {
148 		to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
149 		to.tv_nsec = 0;
150 		rc = cond_reltimedwait(&smb_dclocator.sdl_cv,
151 		    &smb_dclocator.sdl_mtx, &to);
152 
153 		if (rc == ETIME) {
154 			syslog(LOG_NOTICE, "smb_locate_dc timeout");
155 			rv = B_FALSE;
156 			goto out;
157 		}
158 	}
159 	if (smb_dclocator.sdl_status != 0) {
160 		syslog(LOG_NOTICE, "smb_locate_dc status 0x%x",
161 		    smb_dclocator.sdl_status);
162 		rv = B_FALSE;
163 		goto out;
164 	}
165 
166 	if (dp == NULL)
167 		dp = &domain_info;
168 	rv = smb_domain_getinfo(dp);
169 
170 out:
171 	(void) mutex_unlock(&smb_dclocator.sdl_mtx);
172 
173 	return (rv);
174 }
175 
176 /*
177  * Tell the domain discovery service to run again now,
178  * and assume changed configuration (i.e. a new DC).
179  * Like the first part of smb_locate_dc().
180  *
181  * Note: This is called from the service refresh handler
182  * and the door handler to tell the ddiscover thread to
183  * request the new DC from idmap.  Therefore, we must not
184  * trigger a new idmap discovery run from here, or that
185  * would start a ping-pong match.
186  */
187 /* ARGSUSED */
188 void
189 smb_ddiscover_refresh()
190 {
191 
192 	(void) mutex_lock(&smb_dclocator.sdl_mtx);
193 
194 	if (smb_dclocator.sdl_cfg_chg == B_FALSE) {
195 		smb_dclocator.sdl_cfg_chg = B_TRUE;
196 		syslog(LOG_DEBUG, "smb_ddiscover_refresh set cfg changed");
197 	}
198 	if (!smb_dclocator.sdl_locate) {
199 		smb_dclocator.sdl_locate = B_TRUE;
200 		(void) cond_broadcast(&smb_dclocator.sdl_cv);
201 	}
202 
203 	(void) mutex_unlock(&smb_dclocator.sdl_mtx);
204 }
205 
206 /*
207  * Called by our client-side threads after they fail to connect to
208  * the DC given to them by smb_locate_dc().  This is often called
209  * after some delay, because the connection timeout delays these
210  * threads for a while, so it's quite common that the DC locator
211  * service has already started looking for a new DC.  These late
212  * notifications should not continually restart the DC locator.
213  */
214 void
215 smb_ddiscover_bad_dc(char *bad_dc)
216 {
217 
218 	assert(bad_dc[0] != '\0');
219 
220 	(void) mutex_lock(&smb_dclocator.sdl_mtx);
221 
222 	syslog(LOG_DEBUG, "smb_ddiscover_bad_dc, cur=%s, bad=%s",
223 	    smb_dclocator.sdl_dci.dc_name, bad_dc);
224 
225 	if (strcmp(smb_dclocator.sdl_dci.dc_name, bad_dc)) {
226 		/*
227 		 * The "bad" DC is no longer the current one.
228 		 * Probably a late "bad DC" report.
229 		 */
230 		goto out;
231 	}
232 	if (smb_dclocator.sdl_bad_dc) {
233 		/* Someone already marked the current DC as "bad". */
234 		syslog(LOG_DEBUG, "smb_ddiscover_bad_dc repeat");
235 		goto out;
236 	}
237 
238 	/*
239 	 * Mark the current DC as "bad" and let the DC Locator
240 	 * run again if it's not already.
241 	 */
242 	syslog(LOG_INFO, "smb_ddiscover, bad DC: %s", bad_dc);
243 	smb_dclocator.sdl_bad_dc = B_TRUE;
244 
245 	/* In-line smb_ddiscover_kick */
246 	if (!smb_dclocator.sdl_locate) {
247 		smb_dclocator.sdl_locate = B_TRUE;
248 		(void) cond_broadcast(&smb_dclocator.sdl_cv);
249 	}
250 
251 out:
252 	(void) mutex_unlock(&smb_dclocator.sdl_mtx);
253 }
254 
255 /*
256  * If domain discovery is running, wait for it to finish.
257  */
258 int
259 smb_ddiscover_wait(void)
260 {
261 	timestruc_t to;
262 	int rc = 0;
263 
264 	(void) mutex_lock(&smb_dclocator.sdl_mtx);
265 
266 	if (smb_dclocator.sdl_locate) {
267 		to.tv_sec = SMB_DCLOCATOR_TIMEOUT;
268 		to.tv_nsec = 0;
269 		rc = cond_reltimedwait(&smb_dclocator.sdl_cv,
270 		    &smb_dclocator.sdl_mtx, &to);
271 	}
272 
273 	(void) mutex_unlock(&smb_dclocator.sdl_mtx);
274 
275 	return (rc);
276 }
277 
278 
279 /*
280  * ==========================================================
281  * DC discovery functions
282  * ==========================================================
283  */
284 
285 /*
286  * This is the domain and DC discovery service: it gets woken up whenever
287  * there is need to locate a domain controller.
288  *
289  * Upon success, the SMB domain cache will be populated with the discovered
290  * DC and domain info.
291  */
292 /*ARGSUSED*/
293 static void *
294 smb_ddiscover_service(void *arg)
295 {
296 	smb_domainex_t dxi;
297 	smb_dclocator_t *sdl = arg;
298 	uint32_t status;
299 	boolean_t bad_dc;
300 	boolean_t cfg_chg;
301 
302 	for (;;) {
303 		/*
304 		 * Wait to be signaled for work by one of:
305 		 * smb_locate_dc(), smb_ddiscover_refresh(),
306 		 * smb_ddiscover_bad_dc()
307 		 */
308 		syslog(LOG_DEBUG, "smb_ddiscover_service waiting");
309 
310 		(void) mutex_lock(&sdl->sdl_mtx);
311 		while (!sdl->sdl_locate)
312 			(void) cond_wait(&sdl->sdl_cv,
313 			    &sdl->sdl_mtx);
314 
315 		if (!smb_config_getbool(SMB_CI_DOMAIN_MEMB)) {
316 			sdl->sdl_status = NT_STATUS_INVALID_SERVER_STATE;
317 			syslog(LOG_DEBUG, "smb_ddiscover_service: "
318 			    "not a domain member");
319 			goto wait_again;
320 		}
321 
322 		/*
323 		 * Want to know if these change below.
324 		 * Note: mutex held here
325 		 */
326 	find_again:
327 		bad_dc = sdl->sdl_bad_dc;
328 		sdl->sdl_bad_dc = B_FALSE;
329 		if (bad_dc) {
330 			/*
331 			 * Need to clear the current DC name or
332 			 * ddiscover_bad_dc will keep setting bad_dc
333 			 */
334 			sdl->sdl_dci.dc_name[0] = '\0';
335 		}
336 		cfg_chg = sdl->sdl_cfg_chg;
337 		sdl->sdl_cfg_chg = B_FALSE;
338 
339 		(void) mutex_unlock(&sdl->sdl_mtx);
340 
341 		syslog(LOG_DEBUG, "smb_ddiscover_service running "
342 		    "cfg_chg=%d bad_dc=%d", (int)cfg_chg, (int)bad_dc);
343 
344 		/*
345 		 * Clear the cached DC now so that we'll ask idmap again.
346 		 * If our current DC gave us errors, force rediscovery.
347 		 */
348 		smb_ads_refresh(bad_dc);
349 
350 		/*
351 		 * Search for the DC, save the result.
352 		 */
353 		bzero(&dxi, sizeof (dxi));
354 		status = smb_ddiscover_main(sdl->sdl_domain, &dxi);
355 		if (status == 0)
356 			smb_domain_save();
357 		(void) mutex_lock(&sdl->sdl_mtx);
358 		sdl->sdl_status = status;
359 		if (status == 0)
360 			sdl->sdl_dci = dxi.d_dci;
361 
362 		/*
363 		 * Run again if either of cfg_chg or bad_dc
364 		 * was turned on during smb_ddiscover_main().
365 		 * Note: mutex held here.
366 		 */
367 		if (sdl->sdl_bad_dc) {
368 			syslog(LOG_DEBUG, "smb_ddiscover_service "
369 			    "restart because bad_dc was set");
370 			goto find_again;
371 		}
372 		if (sdl->sdl_cfg_chg) {
373 			syslog(LOG_DEBUG, "smb_ddiscover_service "
374 			    "restart because cfg_chg was set");
375 			goto find_again;
376 		}
377 
378 	wait_again:
379 		sdl->sdl_locate = B_FALSE;
380 		sdl->sdl_bad_dc = B_FALSE;
381 		sdl->sdl_cfg_chg = B_FALSE;
382 		(void) cond_broadcast(&sdl->sdl_cv);
383 		(void) mutex_unlock(&sdl->sdl_mtx);
384 	}
385 
386 	/*NOTREACHED*/
387 	return (NULL);
388 }
389 
390 /*
391  * Discovers a domain controller for the specified domain via DNS.
392  * After the domain controller is discovered successfully primary and
393  * trusted domain infromation will be queried using RPC queries.
394  *
395  * Caller should zero out *dxi before calling, and after a
396  * successful return should call:  smb_domain_save()
397  */
398 uint32_t
399 smb_ddiscover_main(char *domain, smb_domainex_t *dxi)
400 {
401 	uint32_t status;
402 
403 	if (domain[0] == '\0') {
404 		syslog(LOG_DEBUG, "smb_ddiscover_main NULL domain");
405 		return (NT_STATUS_INTERNAL_ERROR);
406 	}
407 
408 	if (smb_domain_start_update() != SMB_DOMAIN_SUCCESS) {
409 		syslog(LOG_DEBUG, "smb_ddiscover_main can't get lock");
410 		return (NT_STATUS_INTERNAL_ERROR);
411 	}
412 
413 	status = smb_ads_lookup_msdcs(domain, &dxi->d_dci);
414 	if (status != 0) {
415 		syslog(LOG_DEBUG, "smb_ddiscover_main can't find DC (%s)",
416 		    xlate_nt_status(status));
417 		goto out;
418 	}
419 
420 	status = smb_ddiscover_qinfo(domain, dxi->d_dci.dc_name, dxi);
421 	if (status != 0) {
422 		syslog(LOG_DEBUG,
423 		    "smb_ddiscover_main can't get domain info (%s)",
424 		    xlate_nt_status(status));
425 		goto out;
426 	}
427 
428 	smb_domain_update(dxi);
429 
430 out:
431 	smb_domain_end_update();
432 
433 	/* Don't need the trusted domain list anymore. */
434 	smb_domainex_free(dxi);
435 
436 	return (status);
437 }
438 
439 /*
440  * Obtain primary and trusted domain information using LSA queries.
441  *
442  * domain - either NetBIOS or fully-qualified domain name
443  */
444 static uint32_t
445 smb_ddiscover_qinfo(char *domain, char *server, smb_domainex_t *dxi)
446 {
447 	uint32_t ret, tmp;
448 
449 	/* If we must return failure, use this first one. */
450 	ret = lsa_query_dns_domain_info(server, domain, &dxi->d_primary);
451 	if (ret == NT_STATUS_SUCCESS)
452 		goto success;
453 	tmp = smb_ddiscover_use_config(domain, dxi);
454 	if (tmp == NT_STATUS_SUCCESS)
455 		goto success;
456 	tmp = lsa_query_primary_domain_info(server, domain, &dxi->d_primary);
457 	if (tmp == NT_STATUS_SUCCESS)
458 		goto success;
459 
460 	/* All of the above failed. */
461 	return (ret);
462 
463 success:
464 	smb_ddiscover_enum_trusted(domain, server, dxi);
465 	return (NT_STATUS_SUCCESS);
466 }
467 
468 /*
469  * Obtain trusted domains information using LSA queries.
470  *
471  * domain - either NetBIOS or fully-qualified domain name.
472  */
473 static void
474 smb_ddiscover_enum_trusted(char *domain, char *server, smb_domainex_t *dxi)
475 {
476 	smb_trusted_domains_t *list;
477 	uint32_t status;
478 
479 	list = &dxi->d_trusted;
480 	status = lsa_enum_trusted_domains_ex(server, domain, list);
481 	if (status != NT_STATUS_SUCCESS)
482 		(void) lsa_enum_trusted_domains(server, domain, list);
483 }
484 
485 /*
486  * If the domain to be discovered matches the current domain (i.e the
487  * value of either domain or fqdn configuration), then get the primary
488  * domain information from SMF.
489  */
490 static uint32_t
491 smb_ddiscover_use_config(char *domain, smb_domainex_t *dxi)
492 {
493 	boolean_t use;
494 	smb_domain_t *dinfo;
495 
496 	dinfo = &dxi->d_primary;
497 	bzero(dinfo, sizeof (smb_domain_t));
498 
499 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
500 		return (NT_STATUS_UNSUCCESSFUL);
501 
502 	smb_config_getdomaininfo(dinfo->di_nbname, dinfo->di_fqname,
503 	    NULL, NULL, NULL);
504 
505 	if (SMB_IS_FQDN(domain))
506 		use = (smb_strcasecmp(dinfo->di_fqname, domain, 0) == 0);
507 	else
508 		use = (smb_strcasecmp(dinfo->di_nbname, domain, 0) == 0);
509 
510 	if (use)
511 		smb_config_getdomaininfo(NULL, NULL, dinfo->di_sid,
512 		    dinfo->di_u.di_dns.ddi_forest,
513 		    dinfo->di_u.di_dns.ddi_guid);
514 
515 	return ((use) ? NT_STATUS_SUCCESS : NT_STATUS_UNSUCCESSFUL);
516 }
517 
518 static void
519 smb_domainex_free(smb_domainex_t *dxi)
520 {
521 	free(dxi->d_trusted.td_domains);
522 	dxi->d_trusted.td_domains = NULL;
523 }
524 
525 static void
526 smb_set_krb5_realm(char *domain)
527 {
528 	static char realm[MAXHOSTNAMELEN];
529 
530 	if (domain == NULL || domain[0] == '\0') {
531 		(void) unsetenv("KRB5_DEFAULT_REALM");
532 		return;
533 	}
534 
535 	/* In case krb5.conf is not configured, set the default realm. */
536 	(void) strlcpy(realm, domain, sizeof (realm));
537 	(void) smb_strupr(realm);
538 
539 	(void) setenv("KRB5_DEFAULT_REALM", realm, 1);
540 }
541