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 34 #include <smbsrv/libsmb.h> 35 #include <smbsrv/libsmbrdr.h> 36 #include <smbsrv/libsmbns.h> 37 #include <smbsrv/libmlsvc.h> 38 39 #include <smbsrv/smbinfo.h> 40 #include <smbsrv/ntstatus.h> 41 #include <smbsrv/lsalib.h> 42 43 /* 44 * Maximum time to wait for a domain controller (30 seconds). 45 */ 46 #define SMB_NETLOGON_TIMEOUT 30 47 48 /* 49 * Flags used in conjunction with the location and query condition 50 * variables. 51 */ 52 #define SMB_NETLF_LOCATE_DC 0x00000001 53 #define SMB_NETLF_LSA_QUERY 0x00000002 54 55 typedef struct smb_netlogon_info { 56 char snli_domain[SMB_PI_MAX_DOMAIN]; 57 char snli_dc[MAXHOSTNAMELEN]; 58 unsigned snli_flags; 59 mutex_t snli_locate_mtx; 60 cond_t snli_locate_cv; 61 mutex_t snli_query_mtx; 62 cond_t snli_query_cv; 63 uint32_t snli_status; 64 } smb_netlogon_info_t; 65 66 static smb_netlogon_info_t smb_netlogon_info; 67 68 /* NT4 domain support is not yet available. */ 69 static boolean_t nt4_domain_support = B_FALSE; 70 71 static pthread_t lsa_monitor_thr; 72 static pthread_t dc_browser_thr; 73 static pthread_t locate_dc_thr; 74 75 static void *smb_netlogon_lsa_monitor(void *arg); 76 static void *smb_netlogon_dc_browser(void *arg); 77 78 /* 79 * Inline convenience function to find out if the domain information is 80 * valid. The caller can decide whether or not to wait. 81 */ 82 static boolean_t 83 smb_ntdomain_is_valid(uint32_t timeout) 84 { 85 smb_ntdomain_t *info; 86 87 if ((info = smb_getdomaininfo(timeout)) != 0) { 88 if (info->ipaddr != 0) 89 return (B_TRUE); 90 } 91 92 return (B_FALSE); 93 } 94 95 /* 96 * Retrieve the kpasswd server from krb5.conf. 97 */ 98 static int 99 smbd_get_kpasswd_srv(char *srv, size_t len) 100 { 101 FILE *fp; 102 static char buf[512]; 103 char *p; 104 105 *srv = '\0'; 106 p = getenv("KRB5_CONFIG"); 107 if (p == NULL || *p == '\0') 108 p = "/etc/krb5/krb5.conf"; 109 110 if ((fp = fopen(p, "r")) == NULL) 111 return (-1); 112 113 while (fgets(buf, sizeof (buf), fp)) { 114 115 /* Weed out any comment text */ 116 (void) trim_whitespace(buf); 117 if (*buf == '#') 118 continue; 119 120 if ((p = strstr(buf, "kpasswd_server")) != NULL) { 121 if ((p = strchr(p, '=')) != NULL) { 122 (void) trim_whitespace(++p); 123 (void) strlcpy(srv, p, len); 124 } 125 break; 126 } 127 } 128 129 (void) fclose(fp); 130 return ((*srv == '\0') ? -1 : 0); 131 } 132 133 /* 134 * smbd_join 135 * 136 * Joins the specified domain/workgroup 137 */ 138 uint32_t 139 smbd_join(smb_joininfo_t *info) 140 { 141 smb_ntdomain_t *pi; 142 uint32_t status; 143 unsigned char passwd_hash[SMBAUTH_HASH_SZ]; 144 char plain_passwd[PASS_LEN + 1]; 145 char plain_user[PASS_LEN + 1]; 146 char nbt_domain[SMB_PI_MAX_DOMAIN]; 147 char fqdn[MAXHOSTNAMELEN]; 148 char dc[MAXHOSTNAMELEN]; 149 char kpasswd_domain[MAXHOSTNAMELEN]; 150 151 (void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain, 152 MAXHOSTNAMELEN); 153 154 dssetup_clear_domain_info(); 155 156 if (info->mode == SMB_SECMODE_WORKGRP) { 157 if ((smb_config_get_secmode() == SMB_SECMODE_DOMAIN) && 158 kpasswd_domain == '\0') { 159 160 if (smb_ads_domain_change_cleanup("") != 0) { 161 syslog(LOG_ERR, "smbd: unable to remove the" 162 " old keys from the Kerberos keytab. " 163 "Please remove the old keys for your " 164 "host principal."); 165 } 166 } 167 168 (void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nbt_domain, 169 sizeof (nbt_domain)); 170 171 (void) smb_config_set_secmode(info->mode); 172 (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, info->domain_name); 173 174 if (strcasecmp(nbt_domain, info->domain_name)) 175 smb_browser_reconfig(); 176 177 return (NT_STATUS_SUCCESS); 178 } 179 180 /* 181 * Ensure that any previous membership of this domain has 182 * been cleared from the environment before we start. This 183 * will ensure that we don't attempt a NETLOGON_SAMLOGON 184 * when attempting to find the PDC. 185 */ 186 (void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE); 187 188 (void) strlcpy(plain_user, info->domain_username, sizeof (plain_user)); 189 (void) strlcpy(plain_passwd, info->domain_passwd, 190 sizeof (plain_passwd)); 191 (void) smb_resolve_netbiosname(info->domain_name, nbt_domain, 192 sizeof (nbt_domain)); 193 194 if (smb_resolve_fqdn(info->domain_name, fqdn, sizeof (fqdn)) != 1) { 195 syslog(LOG_ERR, "smbd: fully-qualified domain name is unknown"); 196 return (NT_STATUS_INVALID_PARAMETER); 197 } 198 199 if (smb_ads_domain_change_cleanup(fqdn)) { 200 syslog(LOG_ERR, "smbd: unable to remove the old keys from the" 201 " Kerberos keytab. Please remove the old keys for your " 202 "host principal."); 203 return (NT_STATUS_INTERNAL_ERROR); 204 } 205 206 if (smb_auth_ntlm_hash(plain_passwd, passwd_hash) != SMBAUTH_SUCCESS) { 207 status = NT_STATUS_INTERNAL_ERROR; 208 syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'", 209 plain_user); 210 return (status); 211 } 212 213 smbrdr_ipc_set(plain_user, passwd_hash); 214 215 (void) smbd_get_kpasswd_srv(dc, sizeof (dc)); 216 if (smbd_locate_dc(nbt_domain, dc)) { 217 if ((pi = smb_getdomaininfo(0)) == 0) { 218 status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; 219 if (*dc == '\0') 220 syslog(LOG_ERR, "smbd: could not get domain " 221 "controller information for '%s'", 222 info->domain_name); 223 else 224 syslog(LOG_ERR, "smbd: could not get the " 225 "specified domain controller information " 226 "'%s'", dc); 227 return (status); 228 } 229 230 /* 231 * Temporary delay before creating 232 * the workstation trust account. 233 */ 234 (void) sleep(2); 235 status = mlsvc_join(pi->server, pi->domain, 236 plain_user, plain_passwd); 237 238 if (status == NT_STATUS_SUCCESS) { 239 (void) smb_config_set_secmode(SMB_SECMODE_DOMAIN); 240 (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, 241 info->domain_name); 242 smbrdr_ipc_commit(); 243 return (status); 244 } 245 246 smbrdr_ipc_rollback(); 247 syslog(LOG_ERR, "smbd: failed joining %s (%s)", 248 info->domain_name, xlate_nt_status(status)); 249 return (status); 250 } 251 252 smbrdr_ipc_rollback(); 253 syslog(LOG_ERR, "smbd: failed locating domain controller for %s", 254 info->domain_name); 255 return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND); 256 } 257 258 /* 259 * smbd_locate_dc 260 * 261 * This is the entry point for discovering a domain controller for the 262 * specified domain. The caller may block here for around 30 seconds if 263 * the system has to go to the network and find a domain controller. 264 * Sometime it would be good to change this to smb_locate_pdc and allow 265 * the caller to specify whether or not he wants to wait for a response. 266 * 267 * The actual work of discovering a DC is handled by other threads. 268 * All we do here is signal the request and wait for a DC or a timeout. 269 * 270 * domain - domain to be discovered 271 * dc - preferred DC. If the preferred DC is set to empty string, it 272 * will attempt to discover any DC in the specified domain. 273 * 274 * Returns B_TRUE if a domain controller is available. 275 */ 276 boolean_t 277 smbd_locate_dc(char *domain, char *dc) 278 { 279 int rc; 280 timestruc_t to; 281 282 if (domain == NULL || *domain == '\0') 283 return (B_FALSE); 284 285 (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); 286 287 if ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) == 0) { 288 smb_netlogon_info.snli_flags |= SMB_NETLF_LOCATE_DC; 289 (void) strlcpy(smb_netlogon_info.snli_domain, domain, 290 SMB_PI_MAX_DOMAIN); 291 (void) strlcpy(smb_netlogon_info.snli_dc, dc, 292 MAXHOSTNAMELEN); 293 (void) cond_broadcast(&smb_netlogon_info.snli_locate_cv); 294 } 295 296 while (smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) { 297 to.tv_sec = SMB_NETLOGON_TIMEOUT; 298 to.tv_nsec = 0; 299 rc = cond_reltimedwait(&smb_netlogon_info.snli_locate_cv, 300 &smb_netlogon_info.snli_locate_mtx, &to); 301 302 if (rc == ETIME) 303 break; 304 } 305 306 (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); 307 308 return (smb_ntdomain_is_valid(0)); 309 } 310 311 /* 312 * smb_netlogon_init 313 * 314 * Initialization of the DC browser and LSA monitor threads. 315 * Returns 0 on success, an error number if thread creation fails. 316 */ 317 int 318 smb_netlogon_init(void) 319 { 320 pthread_attr_t tattr; 321 int rc; 322 323 (void) pthread_attr_init(&tattr); 324 (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); 325 rc = pthread_create(&lsa_monitor_thr, &tattr, 326 smb_netlogon_lsa_monitor, 0); 327 if (rc != 0) 328 goto nli_exit; 329 rc = pthread_create(&dc_browser_thr, &tattr, 330 smb_netlogon_dc_browser, 0); 331 if (rc != 0) { 332 (void) pthread_cancel(lsa_monitor_thr); 333 (void) pthread_join(lsa_monitor_thr, NULL); 334 } 335 336 nli_exit: 337 (void) pthread_attr_destroy(&tattr); 338 return (rc); 339 } 340 341 /* 342 * smb_netlogon_dc_browser 343 * 344 * This is the DC browser thread: it gets woken up whenever someone 345 * wants to locate a domain controller. 346 * 347 * With the introduction of Windows 2000, NetBIOS is no longer a 348 * requirement for NT domains. If NetBIOS has been disabled on the 349 * network there will be no browsers and we won't get any response 350 * to netlogon requests. So we try to find a DC controller via ADS 351 * first. If ADS is disabled or the DNS query fails, we drop back 352 * to the netlogon protocol. 353 * 354 * This function will block for up to 30 seconds waiting for the PDC 355 * to be discovered. Sometime it would be good to change this to 356 * smb_locate_pdc and allow the caller to specify whether or not he 357 * wants to wait for a response. 358 * 359 */ 360 /*ARGSUSED*/ 361 static void * 362 smb_netlogon_dc_browser(void *arg) 363 { 364 boolean_t rc; 365 char resource_domain[SMB_PI_MAX_DOMAIN]; 366 char dc[MAXHOSTNAMELEN]; 367 368 for (;;) { 369 (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); 370 371 while ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) == 372 0) { 373 (void) cond_wait(&smb_netlogon_info.snli_locate_cv, 374 &smb_netlogon_info.snli_locate_mtx); 375 } 376 377 (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); 378 (void) strlcpy(resource_domain, smb_netlogon_info.snli_domain, 379 SMB_PI_MAX_DOMAIN); 380 (void) strlcpy(dc, smb_netlogon_info.snli_dc, MAXHOSTNAMELEN); 381 382 smb_setdomaininfo(NULL, NULL, 0); 383 if ((smb_msdcs_lookup_ads(resource_domain, dc) == 0) && 384 (nt4_domain_support)) { 385 /* Try to locate a DC via NetBIOS */ 386 smb_browser_netlogon(resource_domain); 387 } 388 389 rc = smb_ntdomain_is_valid(SMB_NETLOGON_TIMEOUT); 390 391 (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); 392 smb_netlogon_info.snli_flags &= ~SMB_NETLF_LOCATE_DC; 393 (void) cond_broadcast(&smb_netlogon_info.snli_locate_cv); 394 (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); 395 396 if (rc != B_TRUE) { 397 /* 398 * Notify the LSA monitor to update the 399 * primary and trusted domain information. 400 */ 401 (void) mutex_lock(&smb_netlogon_info.snli_query_mtx); 402 smb_netlogon_info.snli_flags |= SMB_NETLF_LSA_QUERY; 403 (void) cond_broadcast(&smb_netlogon_info.snli_query_cv); 404 (void) mutex_unlock(&smb_netlogon_info.snli_query_mtx); 405 } 406 } 407 408 /*NOTREACHED*/ 409 return (NULL); 410 } 411 412 /* 413 * smb_netlogon_lsa_monitor 414 * 415 * This monitor should run as a separate thread. It waits on a condition 416 * variable until someone indicates that the LSA domain information needs 417 * to be refreshed. It then queries the DC for the NT domain information: 418 * primary, account and trusted domains. The condition variable should be 419 * signaled whenever a DC is selected. 420 * 421 * Note that the LSA query calls require the DC information and this task 422 * may end up blocked on the DC location protocol, which is why this 423 * monitor is run as a separate thread. This should only happen if the DC 424 * goes down immediately after we located it. 425 */ 426 /*ARGSUSED*/ 427 static void * 428 smb_netlogon_lsa_monitor(void *arg) 429 { 430 uint32_t status; 431 432 for (;;) { 433 (void) mutex_lock(&smb_netlogon_info.snli_query_mtx); 434 435 while ((smb_netlogon_info.snli_flags & SMB_NETLF_LSA_QUERY) == 436 0) { 437 (void) cond_wait(&smb_netlogon_info.snli_query_cv, 438 &smb_netlogon_info.snli_query_mtx); 439 } 440 441 smb_netlogon_info.snli_flags &= ~SMB_NETLF_LSA_QUERY; 442 (void) mutex_unlock(&smb_netlogon_info.snli_query_mtx); 443 444 /* 445 * Skip the LSA query if Authenticated IPC is supported 446 * and the credential is not yet set. 447 */ 448 if (smbrdr_ipc_skip_lsa_query() == 0) { 449 status = lsa_query_primary_domain_info(); 450 if (status == NT_STATUS_SUCCESS) { 451 if (lsa_query_account_domain_info() 452 != NT_STATUS_SUCCESS) { 453 syslog(LOG_DEBUG, 454 "NetlogonLSAMonitor: query " 455 "account info failed"); 456 } 457 if (lsa_enum_trusted_domains() 458 != NT_STATUS_SUCCESS) { 459 syslog(LOG_DEBUG, 460 "NetlogonLSAMonitor: enum " 461 "trusted domain failed"); 462 } 463 } else { 464 syslog(LOG_DEBUG, 465 "NetlogonLSAMonitor: update failed"); 466 } 467 } 468 } 469 470 /*NOTREACHED*/ 471 return (NULL); 472 } 473 474 475 /* 476 * smb_set_netlogon_cred 477 * 478 * If the system is joined to an AD domain via kclient, SMB daemon will need 479 * to establish the NETLOGON credential chain. 480 * 481 * Since the kclient has updated the machine password stored in SMF 482 * repository, the cached ipc_info must be updated accordingly by calling 483 * smbrdr_ipc_commit. 484 * 485 * Due to potential replication delays in a multiple DC environment, the 486 * NETLOGON rpc request must be sent to the DC, to which the KPASSWD request 487 * is sent. If the DC discovered by the SMB daemon is different than the 488 * kpasswd server, the current connection with the DC will be torn down 489 * and a DC discovery process will be triggered to locate the kpasswd 490 * server. 491 * 492 * If joining a new domain, the domain_name property must be set after a 493 * successful credential chain setup. 494 */ 495 void 496 smb_set_netlogon_cred(void) 497 { 498 smb_ntdomain_t *dp; 499 smb_ntdomain_t domain_info; 500 char kpasswd_srv[MAXHOSTNAMELEN]; 501 char kpasswd_domain[MAXHOSTNAMELEN]; 502 char sam_acct[MLSVC_ACCOUNT_NAME_MAX]; 503 char *ipc_usr, *dom; 504 boolean_t new_domain = B_FALSE; 505 506 if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) 507 return; 508 509 if (smb_match_netlogon_seqnum()) 510 return; 511 512 (void) smb_config_getstr(SMB_CI_KPASSWD_SRV, kpasswd_srv, 513 sizeof (kpasswd_srv)); 514 515 if (*kpasswd_srv == '\0') 516 return; 517 518 /* 519 * If the domain join initiated by smbadm join CLI is in 520 * progress, don't do anything. 521 */ 522 (void) smb_gethostname(sam_acct, MLSVC_ACCOUNT_NAME_MAX - 1, 0); 523 (void) strlcat(sam_acct, "$", MLSVC_ACCOUNT_NAME_MAX); 524 ipc_usr = smbrdr_ipc_get_user(); 525 if (strcasecmp(ipc_usr, sam_acct)) 526 return; 527 528 if ((dp = smb_getdomaininfo(0)) == NULL) { 529 *domain_info.server = '\0'; 530 (void) smb_getdomainname(domain_info.domain, 531 sizeof (domain_info.domain)); 532 dp = &domain_info; 533 } 534 535 (void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain, 536 sizeof (kpasswd_domain)); 537 538 if (*kpasswd_domain != '\0' && 539 strncasecmp(kpasswd_domain, dp->domain, strlen(dp->domain))) { 540 dom = kpasswd_domain; 541 new_domain = B_TRUE; 542 } else { 543 dom = dp->domain; 544 } 545 546 /* 547 * DC discovery will be triggered if the domain info is not 548 * currently cached or the SMB daemon has previously discovered a DC 549 * that is different than the kpasswd server. 550 */ 551 if (new_domain || strcasecmp(dp->server, kpasswd_srv) != 0) { 552 if (*dp->server != '\0') 553 mlsvc_disconnect(dp->server); 554 555 if (!smbd_locate_dc(dom, kpasswd_srv)) 556 (void) smbd_locate_dc(dp->domain, ""); 557 558 if ((dp = smb_getdomaininfo(0)) == NULL) { 559 smbrdr_ipc_commit(); 560 return; 561 } 562 } 563 564 smbrdr_ipc_commit(); 565 if (mlsvc_netlogon(dp->server, dp->domain)) { 566 syslog(LOG_ERR, "NETLOGON credential chain establishment" 567 " failed"); 568 } else { 569 if (new_domain) { 570 (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, 571 kpasswd_domain); 572 (void) smb_config_setstr(SMB_CI_KPASSWD_DOMAIN, 573 ""); 574 } 575 } 576 577 } 578 579 /* 580 * smbd_locate_dc_thread() 581 * 582 * If necessary, set up Netlogon credential chain and locate a 583 * domain controller in the given resource domain. 584 */ 585 static void * 586 smbd_locate_dc_thread(void *arg) 587 { 588 char *resource_domain = (char *)arg; 589 590 if (!smb_match_netlogon_seqnum()) 591 smb_set_netlogon_cred(); 592 else 593 (void) smbd_locate_dc(resource_domain, ""); 594 595 (void) lsa_query_primary_domain_info(); 596 597 return (NULL); 598 } 599 600 /* 601 * smbd_locate_dc_start() 602 * 603 * Initialization of the locate dc thread. 604 * Returns 0 on success, an error number if thread creation fails. 605 */ 606 int 607 smbd_locate_dc_start(char *resource_domain) 608 { 609 pthread_attr_t tattr; 610 int rc; 611 612 (void) pthread_attr_init(&tattr); 613 (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); 614 rc = pthread_create(&locate_dc_thr, &tattr, smbd_locate_dc_thread, 615 resource_domain); 616 (void) pthread_attr_destroy(&tattr); 617 return (rc); 618 } 619