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