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