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