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 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <syslog.h> 27 #include <synch.h> 28 #include <pthread.h> 29 #include <unistd.h> 30 #include <string.h> 31 #include <strings.h> 32 #include <sys/errno.h> 33 #include <sys/types.h> 34 #include <netinet/in.h> 35 #include <arpa/nameser.h> 36 #include <resolv.h> 37 #include <netdb.h> 38 #include <assert.h> 39 40 #include <smbsrv/libsmb.h> 41 #include <smbsrv/libsmbrdr.h> 42 #include <smbsrv/libsmbns.h> 43 #include <smbsrv/libmlsvc.h> 44 45 #include <smbsrv/smbinfo.h> 46 #include <smbsrv/ntstatus.h> 47 #include <lsalib.h> 48 49 /* 50 * Domain cache states 51 */ 52 #define SMB_DCACHE_STATE_INVALID 0 53 #define SMB_DCACHE_STATE_UPDATING 1 54 #define SMB_DCACHE_STATE_VALID 2 55 56 typedef struct smb_domain_cache { 57 uint32_t c_state; 58 smb_domain_t c_cache; 59 mutex_t c_mtx; 60 cond_t c_cv; 61 } smb_domain_cache_t; 62 63 static smb_domain_cache_t smb_dcache; 64 65 /* functions to manipulate the domain cache */ 66 static void smb_dcache_init(void); 67 static void smb_dcache_updating(void); 68 static void smb_dcache_invalid(void); 69 static void smb_dcache_valid(smb_domain_t *); 70 static void smb_dcache_set(uint32_t, smb_domain_t *); 71 72 /* 73 * DC Locator 74 */ 75 #define SMB_DCLOCATOR_TIMEOUT 45 76 #define SMB_IS_FQDN(domain) (strchr(domain, '.') != NULL) 77 78 typedef struct smb_dclocator { 79 char sdl_domain[SMB_PI_MAX_DOMAIN]; 80 char sdl_dc[MAXHOSTNAMELEN]; 81 boolean_t sdl_locate; 82 mutex_t sdl_mtx; 83 cond_t sdl_cv; 84 uint32_t sdl_status; 85 } smb_dclocator_t; 86 87 static smb_dclocator_t smb_dclocator; 88 static pthread_t smb_dclocator_thr; 89 90 static void *smb_dclocator_main(void *); 91 static boolean_t smb_dc_discovery(char *, char *, smb_domain_t *); 92 static boolean_t smb_match_domains(char *, char *, uint32_t); 93 static uint32_t smb_domain_query(char *, char *, smb_domain_t *); 94 static void smb_domain_update_tabent(int, lsa_nt_domaininfo_t *); 95 static void smb_domain_populate_table(char *, char *); 96 static boolean_t smb_domain_use_config(char *, smb_domain_t *); 97 98 /* 99 * =================================================================== 100 * API to initialize DC locator thread, trigger DC discovery, and 101 * get the discovered DC and/or domain information. 102 * =================================================================== 103 */ 104 105 /* 106 * smb_dclocator_init 107 * 108 * Initialization of the DC locator thread. 109 * Returns 0 on success, an error number if thread creation fails. 110 */ 111 int 112 smb_dclocator_init(void) 113 { 114 pthread_attr_t tattr; 115 int rc; 116 117 smb_dcache_init(); 118 (void) pthread_attr_init(&tattr); 119 (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); 120 rc = pthread_create(&smb_dclocator_thr, &tattr, 121 smb_dclocator_main, 0); 122 (void) pthread_attr_destroy(&tattr); 123 return (rc); 124 } 125 126 /* 127 * smb_locate_dc 128 * 129 * This is the entry point for discovering a domain controller for the 130 * specified domain. 131 * 132 * The actual work of discovering a DC is handled by DC locator thread. 133 * All we do here is signal the request and wait for a DC or a timeout. 134 * 135 * Input parameters: 136 * domain - domain to be discovered (can either be NetBIOS or DNS domain) 137 * dc - preferred DC. If the preferred DC is set to empty string, it 138 * will attempt to discover any DC in the specified domain. 139 * 140 * Output parameter: 141 * dp - on success, dp will be filled with the discovered DC and domain 142 * information. 143 * Returns B_TRUE if the DC/domain info is available. 144 */ 145 boolean_t 146 smb_locate_dc(char *domain, char *dc, smb_domain_t *dp) 147 { 148 int rc; 149 timestruc_t to; 150 smb_domain_t domain_info; 151 152 if (domain == NULL || *domain == '\0') 153 return (B_FALSE); 154 155 (void) mutex_lock(&smb_dclocator.sdl_mtx); 156 157 if (!smb_dclocator.sdl_locate) { 158 smb_dclocator.sdl_locate = B_TRUE; 159 (void) strlcpy(smb_dclocator.sdl_domain, domain, 160 SMB_PI_MAX_DOMAIN); 161 (void) strlcpy(smb_dclocator.sdl_dc, dc, 162 MAXHOSTNAMELEN); 163 (void) cond_broadcast(&smb_dclocator.sdl_cv); 164 } 165 166 while (smb_dclocator.sdl_locate) { 167 to.tv_sec = SMB_DCLOCATOR_TIMEOUT; 168 to.tv_nsec = 0; 169 rc = cond_reltimedwait(&smb_dclocator.sdl_cv, 170 &smb_dclocator.sdl_mtx, &to); 171 172 if (rc == ETIME) 173 break; 174 } 175 176 if (dp == NULL) 177 dp = &domain_info; 178 rc = smb_domain_getinfo(dp); 179 (void) mutex_unlock(&smb_dclocator.sdl_mtx); 180 181 return (rc); 182 } 183 184 /* 185 * smb_domain_getinfo 186 * 187 * If the DC discovery process is underway, this function will wait on 188 * a condition variable until the state of SMB domain cache sets to 189 * either VALID/INVALID. 190 * 191 * Returns a copy of the domain cache. 192 */ 193 boolean_t 194 smb_domain_getinfo(smb_domain_t *dp) 195 { 196 timestruc_t to; 197 int err; 198 boolean_t rc; 199 200 (void) mutex_lock(&smb_dcache.c_mtx); 201 to.tv_sec = SMB_DCLOCATOR_TIMEOUT; 202 to.tv_nsec = 0; 203 while (smb_dcache.c_state == SMB_DCACHE_STATE_UPDATING) { 204 err = cond_reltimedwait(&smb_dcache.c_cv, &smb_dcache.c_mtx, 205 &to); 206 if (err == ETIME) 207 break; 208 } 209 210 if (smb_dcache.c_state == SMB_DCACHE_STATE_VALID) { 211 bcopy(&smb_dcache.c_cache, dp, sizeof (smb_domain_t)); 212 rc = B_TRUE; 213 } else { 214 bzero(dp, sizeof (smb_domain_t)); 215 rc = B_FALSE; 216 } 217 218 (void) mutex_unlock(&smb_dcache.c_mtx); 219 return (rc); 220 } 221 222 223 /* 224 * ===================================================================== 225 * Private functions used by DC locator thread to manipulate the domain 226 * cache. 227 * ====================================================================== 228 */ 229 230 static void 231 smb_dcache_init(void) 232 { 233 (void) mutex_lock(&smb_dcache.c_mtx); 234 smb_dcache.c_state = SMB_DCACHE_STATE_INVALID; 235 bzero(&smb_dcache.c_cache, sizeof (smb_domain_t)); 236 (void) mutex_unlock(&smb_dcache.c_mtx); 237 } 238 239 /* 240 * Set the cache state to UPDATING 241 */ 242 static void 243 smb_dcache_updating(void) 244 { 245 smb_dcache_set(SMB_DCACHE_STATE_UPDATING, NULL); 246 } 247 248 /* 249 * Set the cache state to INVALID 250 */ 251 static void 252 smb_dcache_invalid(void) 253 { 254 smb_dcache_set(SMB_DCACHE_STATE_INVALID, NULL); 255 } 256 257 /* 258 * Set the cache state to VALID and populate the cache 259 */ 260 static void 261 smb_dcache_valid(smb_domain_t *dp) 262 { 263 smb_dcache_set(SMB_DCACHE_STATE_VALID, dp); 264 } 265 266 /* 267 * This function will update both the state and the contents of the 268 * SMB domain cache. If one attempts to set the state to 269 * SMB_DCACHE_STATE_UPDATING, the domain cache will be updated based 270 * on 'dp' argument. Otherwise, 'dp' is ignored. 271 */ 272 static void 273 smb_dcache_set(uint32_t state, smb_domain_t *dp) 274 { 275 (void) mutex_lock(&smb_dcache.c_mtx); 276 switch (state) { 277 case SMB_DCACHE_STATE_INVALID: 278 break; 279 280 case SMB_DCACHE_STATE_UPDATING: 281 bzero(&smb_dcache.c_cache, sizeof (smb_domain_t)); 282 break; 283 284 case SMB_DCACHE_STATE_VALID: 285 assert(dp); 286 bcopy(dp, &smb_dcache.c_cache, sizeof (smb_domain_t)); 287 break; 288 289 default: 290 (void) mutex_unlock(&smb_dcache.c_mtx); 291 return; 292 293 } 294 295 smb_dcache.c_state = state; 296 (void) cond_broadcast(&smb_dcache.c_cv); 297 (void) mutex_unlock(&smb_dcache.c_mtx); 298 } 299 300 /* 301 * ========================================================== 302 * DC discovery functions 303 * ========================================================== 304 */ 305 306 /* 307 * smb_dclocator_main 308 * 309 * This is the DC discovery thread: it gets woken up whenever someone 310 * wants to locate a domain controller. 311 * 312 * The state of the SMB domain cache will be initialized to 313 * SMB_DCACHE_STATE_UPDATING when the discovery process starts and will be 314 * transitioned to SMB_DCACHE_STATE_VALID/INVALID depending on the outcome of 315 * the discovery. 316 * 317 * If the discovery process is underway, callers of smb_domain_getinfo() 318 * will wait on a condition variable until the state of SMB domain cache 319 * sets to either VALID/INVALID. 320 * 321 * Upon success, the SMB domain cache will be populated with the discovered DC 322 * and domain info. 323 */ 324 /*ARGSUSED*/ 325 static void * 326 smb_dclocator_main(void *arg) 327 { 328 char domain[SMB_PI_MAX_DOMAIN]; 329 char sought_dc[MAXHOSTNAMELEN]; 330 smb_domain_t dinfo; 331 332 for (;;) { 333 (void) mutex_lock(&smb_dclocator.sdl_mtx); 334 335 while (!smb_dclocator.sdl_locate) 336 (void) cond_wait(&smb_dclocator.sdl_cv, 337 &smb_dclocator.sdl_mtx); 338 339 (void) strlcpy(domain, smb_dclocator.sdl_domain, 340 SMB_PI_MAX_DOMAIN); 341 (void) strlcpy(sought_dc, smb_dclocator.sdl_dc, MAXHOSTNAMELEN); 342 (void) mutex_unlock(&smb_dclocator.sdl_mtx); 343 344 smb_dcache_updating(); 345 if (smb_dc_discovery(domain, sought_dc, &dinfo)) 346 smb_dcache_valid(&dinfo); 347 else 348 smb_dcache_invalid(); 349 350 (void) mutex_lock(&smb_dclocator.sdl_mtx); 351 smb_dclocator.sdl_locate = B_FALSE; 352 (void) cond_broadcast(&smb_dclocator.sdl_cv); 353 (void) mutex_unlock(&smb_dclocator.sdl_mtx); 354 } 355 356 /*NOTREACHED*/ 357 return (NULL); 358 } 359 360 /* 361 * smb_dc_discovery 362 * 363 * If FQDN is specified, DC discovery will be done via DNS query only. 364 * If NetBIOS name of a domain is specified, DC discovery thread will 365 * use netlogon protocol to locate a DC. Upon failure, it will 366 * try to resolve it via DNS, i.e. find out if it is the first label 367 * of a DNS domain name. If the corresponding DNS name is found, DC 368 * discovery will be done via DNS query. 369 * 370 * Once the domain controller is found, it then queries the DC for domain 371 * information. If the LSA queries fail, the domain information stored in 372 * SMF might be used to set the SMB domain cache if the the discovered domain 373 * is the same as the previously joined domain. 374 * 375 * If the fully-qualified domain name is derived from the DNS config 376 * file, the NetBIOS domain name specified by the user will be compared 377 * against the NetBIOS domain name obtained via LSA query. If there is 378 * a mismatch, the DC discovery will fail since the discovered DC is 379 * actually for another domain, whose first label of its FQDN somehow 380 * matches with the NetBIOS name of the domain we're interested in. 381 */ 382 static boolean_t 383 smb_dc_discovery(char *domain, char *server, smb_domain_t *dinfo) 384 { 385 char derived_dnsdomain[MAXHOSTNAMELEN]; 386 boolean_t netlogon_ok = B_FALSE; 387 388 *derived_dnsdomain = '\0'; 389 if (!SMB_IS_FQDN(domain)) { 390 if (smb_browser_netlogon(domain, dinfo->d_dc, MAXHOSTNAMELEN)) 391 netlogon_ok = B_TRUE; 392 else if (!smb_match_domains(domain, derived_dnsdomain, 393 MAXHOSTNAMELEN)) 394 return (B_FALSE); 395 } 396 397 if (!netlogon_ok && !smb_ads_lookup_msdcs( 398 (SMB_IS_FQDN(domain) ? domain : derived_dnsdomain), server, 399 dinfo->d_dc, MAXHOSTNAMELEN)) 400 return (B_FALSE); 401 402 if ((smb_domain_query(domain, dinfo->d_dc, dinfo) 403 != NT_STATUS_SUCCESS) && 404 (!smb_domain_use_config(domain, dinfo))) 405 return (B_FALSE); 406 407 if (*derived_dnsdomain != '\0' && 408 utf8_strcasecmp(domain, dinfo->d_nbdomain)) 409 return (B_FALSE); 410 411 /* 412 * Now that we get the fully-qualified DNS name of the 413 * domain via LSA query. Verifies ADS configuration 414 * if we previously locate a DC via NetBIOS. On success, 415 * ADS cache will be populated. 416 */ 417 if (netlogon_ok) { 418 if (smb_ads_lookup_msdcs(dinfo->d_fqdomain, server, 419 dinfo->d_dc, MAXHOSTNAMELEN) == 0) 420 return (B_FALSE); 421 } 422 423 return (B_TRUE); 424 } 425 426 /* 427 * Tries to find a matching DNS domain for the given NetBIOS domain 428 * name by checking the first label of system's configured DNS domains. 429 * If a match is found, it'll be returned in the passed buffer. 430 */ 431 static boolean_t 432 smb_match_domains(char *nb_domain, char *buf, uint32_t len) 433 { 434 struct __res_state res_state; 435 int i; 436 char *entry, *p; 437 char first_label[MAXHOSTNAMELEN]; 438 boolean_t found; 439 440 if (!nb_domain || !buf) 441 return (B_FALSE); 442 443 *buf = '\0'; 444 bzero(&res_state, sizeof (struct __res_state)); 445 if (res_ninit(&res_state)) 446 return (B_FALSE); 447 448 found = B_FALSE; 449 entry = res_state.defdname; 450 for (i = 0; entry != NULL; i++) { 451 (void) strlcpy(first_label, entry, MAXHOSTNAMELEN); 452 if ((p = strchr(first_label, '.')) != NULL) { 453 *p = '\0'; 454 if (strlen(first_label) > 15) 455 first_label[15] = '\0'; 456 } 457 458 if (utf8_strcasecmp(nb_domain, first_label) == 0) { 459 found = B_TRUE; 460 (void) strlcpy(buf, entry, len); 461 break; 462 } 463 464 entry = res_state.dnsrch[i]; 465 } 466 467 468 res_ndestroy(&res_state); 469 return (found); 470 } 471 472 /* 473 * smb_domain_query 474 * 475 * If the the NetBIOS name of an AD domain doesn't match with the 476 * first label of its fully-qualified DNS name, it is not possible 477 * to derive one name format from another. 478 * The missing domain info can be obtained via LSA query, DNS domain info. 479 * 480 * domain - either NetBIOS or fully-qualified domain name 481 * 482 */ 483 static uint32_t 484 smb_domain_query(char *domain, char *server, smb_domain_t *dp) 485 { 486 uint32_t rc; 487 lsa_info_t info; 488 489 rc = lsa_query_dns_domain_info(server, domain, &info); 490 if (rc == NT_STATUS_SUCCESS) { 491 lsa_dns_domaininfo_t *dnsinfo = &info.i_domain.di_dns; 492 (void) strlcpy(dp->d_nbdomain, dnsinfo->d_nbdomain, 493 sizeof (dp->d_nbdomain)); 494 (void) strlcpy(dp->d_fqdomain, dnsinfo->d_fqdomain, 495 sizeof (dp->d_fqdomain)); 496 (void) strlcpy(dp->d_forest, dnsinfo->d_forest, 497 sizeof (dp->d_forest)); 498 ndr_uuid_unparse((ndr_uuid_t *)&dnsinfo->d_guid, dp->d_guid); 499 smb_sid_free(dnsinfo->d_sid); 500 } 501 502 smb_domain_populate_table(domain, server); 503 return (rc); 504 } 505 506 /* 507 * smb_domain_populate_table 508 * 509 * Populates the domain tablele with primary, account and trusted 510 * domain info. 511 * domain - either NetBIOS or fully-qualified domain name. 512 */ 513 static void 514 smb_domain_populate_table(char *domain, char *server) 515 { 516 lsa_info_t info; 517 lsa_nt_domaininfo_t *nt_info; 518 int i; 519 520 if (lsa_query_primary_domain_info(server, domain, &info) 521 == NT_STATUS_SUCCESS) { 522 nt_info = &info.i_domain.di_primary; 523 smb_domain_update_tabent(NT_DOMAIN_PRIMARY, nt_info); 524 lsa_free_info(&info); 525 } 526 527 if (lsa_query_account_domain_info(server, domain, &info) 528 == NT_STATUS_SUCCESS) { 529 nt_info = &info.i_domain.di_account; 530 smb_domain_update_tabent(NT_DOMAIN_ACCOUNT, nt_info); 531 lsa_free_info(&info); 532 } 533 534 if (lsa_enum_trusted_domains(server, domain, &info) 535 == NT_STATUS_SUCCESS) { 536 lsa_trusted_domainlist_t *list = &info.i_domain.di_trust; 537 for (i = 0; i < list->t_num; i++) { 538 nt_info = &list->t_domains[i]; 539 smb_domain_update_tabent(NT_DOMAIN_TRUSTED, nt_info); 540 } 541 542 lsa_free_info(&info); 543 } 544 545 546 } 547 548 static void 549 smb_domain_update_tabent(int domain_type, lsa_nt_domaininfo_t *info) 550 { 551 nt_domain_t *entry; 552 nt_domain_flush(domain_type); 553 entry = nt_domain_new(domain_type, info->n_domain, info->n_sid); 554 (void) nt_domain_add(entry); 555 } 556 557 /* 558 * smb_domain_use_config 559 * 560 * If the domain to be discovered matches the current domain (i.e the 561 * value of either domain or fqdn configuration), the output parameter 562 * 'dinfo' will be set to the information stored in SMF. 563 */ 564 static boolean_t 565 smb_domain_use_config(char *domain, smb_domain_t *dinfo) 566 { 567 smb_domain_t orig; 568 boolean_t use; 569 570 if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) 571 return (B_FALSE); 572 573 smb_config_getdomaininfo(orig.d_nbdomain, orig.d_fqdomain, 574 orig.d_forest, orig.d_guid); 575 576 if (SMB_IS_FQDN(domain)) { 577 use = (utf8_strcasecmp(orig.d_fqdomain, domain) == 0); 578 } else { 579 use = (utf8_strcasecmp(orig.d_nbdomain, domain) == 0); 580 } 581 582 if (use) { 583 (void) strlcpy(dinfo->d_nbdomain, orig.d_nbdomain, 584 sizeof (dinfo->d_nbdomain)); 585 (void) strlcpy(dinfo->d_fqdomain, orig.d_fqdomain, 586 sizeof (dinfo->d_fqdomain)); 587 (void) strlcpy(dinfo->d_forest, orig.d_forest, 588 sizeof (dinfo->d_forest)); 589 (void) bcopy(orig.d_guid, dinfo->d_guid, 590 sizeof (dinfo->d_guid)); 591 } 592 593 return (use); 594 } 595