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