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