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
smb_dclocator_init(void)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
smb_locate_dc(char * domain,smb_domainex_t * dp)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
smb_ddiscover_refresh()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
smb_ddiscover_bad_dc(char * bad_dc)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 *
smb_ddiscover_service(void * arg)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
smb_ddiscover_main(char * domain,smb_domainex_t * dxi)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
smb_ddiscover_qinfo(char * domain,char * server,smb_domainex_t * dxi)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
smb_ddiscover_enum_trusted(char * domain,char * server,smb_domainex_t * dxi)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
smb_ddiscover_use_config(char * domain,smb_domainex_t * dxi)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
smb_domainex_free(smb_domainex_t * dxi)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
smb_set_krb5_realm(char * domain)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