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