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 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 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 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 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 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 * 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 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 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 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 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 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 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