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