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 unsigned snli_flags; 60 mutex_t snli_locate_mtx; 61 cond_t snli_locate_cv; 62 mutex_t snli_query_mtx; 63 cond_t snli_query_cv; 64 uint32_t snli_status; 65 } smb_netlogon_info_t; 66 67 static smb_netlogon_info_t smb_netlogon_info; 68 69 static pthread_t lsa_monitor_thr; 70 static pthread_t dc_browser_thr; 71 72 static void *smb_netlogon_lsa_monitor(void *arg); 73 static void *smb_netlogon_dc_browser(void *arg); 74 75 /* 76 * Inline convenience function to find out if the domain information is 77 * valid. The caller can decide whether or not to wait. 78 */ 79 static boolean_t 80 smb_ntdomain_is_valid(uint32_t timeout) 81 { 82 smb_ntdomain_t *info; 83 84 if ((info = smb_getdomaininfo(timeout)) != 0) { 85 if (info->ipaddr != 0) 86 return (B_TRUE); 87 } 88 89 return (B_FALSE); 90 } 91 92 /* 93 * smbd_join 94 * 95 * Joins the specified domain/workgroup 96 */ 97 uint32_t 98 smbd_join(smb_joininfo_t *info) 99 { 100 smb_ntdomain_t *pi; 101 uint32_t status; 102 unsigned char passwd_hash[SMBAUTH_HASH_SZ]; 103 char plain_passwd[PASS_LEN + 1]; 104 char plain_user[PASS_LEN + 1]; 105 char nbt_domain[SMB_PI_MAX_DOMAIN]; 106 char fqdn[MAXHOSTNAMELEN]; 107 108 if (info->mode == SMB_SECMODE_WORKGRP) { 109 if (smb_config_get_secmode() == SMB_SECMODE_DOMAIN) { 110 if (ads_domain_change_cleanup("") != 0) { 111 syslog(LOG_ERR, "smbd: unable to remove the" 112 " old keys from the Kerberos keytab. " 113 "Please remove the old keys for your " 114 "host principal."); 115 } 116 } 117 118 (void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nbt_domain, 119 sizeof (nbt_domain)); 120 121 (void) smb_config_set_secmode(info->mode); 122 (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, info->domain_name); 123 124 if (strcasecmp(nbt_domain, info->domain_name)) 125 smb_browser_reconfig(); 126 127 return (NT_STATUS_SUCCESS); 128 } 129 130 /* 131 * Ensure that any previous membership of this domain has 132 * been cleared from the environment before we start. This 133 * will ensure that we don't attempt a NETLOGON_SAMLOGON 134 * when attempting to find the PDC. 135 */ 136 (void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE); 137 138 (void) strlcpy(plain_user, info->domain_username, sizeof (plain_user)); 139 (void) strlcpy(plain_passwd, info->domain_passwd, 140 sizeof (plain_passwd)); 141 (void) smb_resolve_netbiosname(info->domain_name, nbt_domain, 142 sizeof (nbt_domain)); 143 144 if (smb_resolve_fqdn(info->domain_name, fqdn, sizeof (fqdn)) != 1) { 145 syslog(LOG_ERR, "smbd: fully-qualified domain name is unknown"); 146 return (NT_STATUS_INVALID_PARAMETER); 147 } 148 149 if (ads_domain_change_cleanup(fqdn)) { 150 syslog(LOG_ERR, "smbd: unable to remove the old keys from the" 151 " Kerberos keytab. Please remove the old keys for your " 152 "host principal."); 153 return (NT_STATUS_INTERNAL_ERROR); 154 } 155 156 if (smb_auth_ntlm_hash(plain_passwd, passwd_hash) != SMBAUTH_SUCCESS) { 157 status = NT_STATUS_INTERNAL_ERROR; 158 syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'", 159 plain_user); 160 return (status); 161 } 162 163 smbrdr_ipc_set(plain_user, passwd_hash); 164 165 if (locate_resource_pdc(nbt_domain)) { 166 if ((pi = smb_getdomaininfo(0)) == 0) { 167 status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; 168 syslog(LOG_ERR, "smbd: could not get domain controller" 169 "information for '%s'", info->domain_name); 170 return (status); 171 } 172 173 /* 174 * Temporary delay before creating 175 * the workstation trust account. 176 */ 177 (void) sleep(2); 178 status = mlsvc_join(pi->server, pi->domain, 179 plain_user, plain_passwd); 180 181 if (status == NT_STATUS_SUCCESS) { 182 (void) smb_config_set_secmode(SMB_SECMODE_DOMAIN); 183 (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, 184 info->domain_name); 185 smbrdr_ipc_commit(); 186 return (status); 187 } 188 189 smbrdr_ipc_rollback(); 190 syslog(LOG_ERR, "smbd: failed joining %s (%s)", 191 info->domain_name, xlate_nt_status(status)); 192 return (status); 193 } 194 195 smbrdr_ipc_rollback(); 196 syslog(LOG_ERR, "smbd: failed locating domain controller for %s", 197 info->domain_name); 198 return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND); 199 } 200 201 /* 202 * locate_resource_pdc 203 * 204 * This is the entry point for discovering a domain controller for the 205 * specified domain. The caller may block here for around 30 seconds if 206 * the system has to go to the network and find a domain controller. 207 * Sometime it would be good to change this to smb_locate_pdc and allow 208 * the caller to specify whether or not he wants to wait for a response. 209 * 210 * The actual work of discovering a DC is handled by other threads. 211 * All we do here is signal the request and wait for a DC or a timeout. 212 * 213 * Returns B_TRUE if a domain controller is available. 214 */ 215 boolean_t 216 locate_resource_pdc(char *domain) 217 { 218 int rc; 219 timestruc_t to; 220 221 if (domain == NULL || *domain == '\0') 222 return (B_FALSE); 223 224 (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); 225 226 if ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) == 0) { 227 smb_netlogon_info.snli_flags |= SMB_NETLF_LOCATE_DC; 228 (void) strlcpy(smb_netlogon_info.snli_domain, domain, 229 SMB_PI_MAX_DOMAIN); 230 (void) cond_broadcast(&smb_netlogon_info.snli_locate_cv); 231 } 232 233 while (smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) { 234 to.tv_sec = SMB_NETLOGON_TIMEOUT; 235 to.tv_nsec = 0; 236 rc = cond_reltimedwait(&smb_netlogon_info.snli_locate_cv, 237 &smb_netlogon_info.snli_locate_mtx, &to); 238 239 if (rc == ETIME) 240 break; 241 } 242 243 (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); 244 245 return (smb_ntdomain_is_valid(0)); 246 } 247 248 /* 249 * smb_netlogon_init 250 * 251 * Initialization of the DC browser and LSA monitor threads. 252 * Returns 0 on success, an error number if thread creation fails. 253 */ 254 int 255 smb_netlogon_init(void) 256 { 257 pthread_attr_t tattr; 258 int rc; 259 260 (void) pthread_attr_init(&tattr); 261 (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); 262 rc = pthread_create(&lsa_monitor_thr, &tattr, 263 smb_netlogon_lsa_monitor, 0); 264 if (rc != 0) 265 goto nli_exit; 266 rc = pthread_create(&dc_browser_thr, &tattr, 267 smb_netlogon_dc_browser, 0); 268 if (rc != 0) { 269 (void) pthread_cancel(lsa_monitor_thr); 270 (void) pthread_join(lsa_monitor_thr, NULL); 271 } 272 273 nli_exit: 274 (void) pthread_attr_destroy(&tattr); 275 return (rc); 276 } 277 278 /* 279 * smb_netlogon_dc_browser 280 * 281 * This is the DC browser thread: it gets woken up whenever someone 282 * wants to locate a domain controller. 283 * 284 * With the introduction of Windows 2000, NetBIOS is no longer a 285 * requirement for NT domains. If NetBIOS has been disabled on the 286 * network there will be no browsers and we won't get any response 287 * to netlogon requests. So we try to find a DC controller via ADS 288 * first. If ADS is disabled or the DNS query fails, we drop back 289 * to the netlogon protocol. 290 * 291 * This function will block for up to 30 seconds waiting for the PDC 292 * to be discovered. Sometime it would be good to change this to 293 * smb_locate_pdc and allow the caller to specify whether or not he 294 * wants to wait for a response. 295 * 296 */ 297 /*ARGSUSED*/ 298 static void * 299 smb_netlogon_dc_browser(void *arg) 300 { 301 boolean_t rc; 302 char resource_domain[SMB_PI_MAX_DOMAIN]; 303 304 for (;;) { 305 (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); 306 307 while ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) == 308 0) { 309 (void) cond_wait(&smb_netlogon_info.snli_locate_cv, 310 &smb_netlogon_info.snli_locate_mtx); 311 } 312 313 (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); 314 315 (void) strlcpy(resource_domain, smb_netlogon_info.snli_domain, 316 SMB_PI_MAX_DOMAIN); 317 318 smb_setdomaininfo(NULL, NULL, 0); 319 if (msdcs_lookup_ads(resource_domain) == 0) { 320 /* Try to locate a DC via NetBIOS */ 321 smb_browser_netlogon(resource_domain); 322 } 323 324 rc = smb_ntdomain_is_valid(SMB_NETLOGON_TIMEOUT); 325 326 (void) mutex_lock(&smb_netlogon_info.snli_locate_mtx); 327 smb_netlogon_info.snli_flags &= ~SMB_NETLF_LOCATE_DC; 328 (void) cond_broadcast(&smb_netlogon_info.snli_locate_cv); 329 (void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx); 330 331 if (rc != B_TRUE) { 332 /* 333 * Notify the LSA monitor to update the 334 * primary and trusted domain information. 335 */ 336 (void) mutex_lock(&smb_netlogon_info.snli_query_mtx); 337 smb_netlogon_info.snli_flags |= SMB_NETLF_LSA_QUERY; 338 (void) cond_broadcast(&smb_netlogon_info.snli_query_cv); 339 (void) mutex_unlock(&smb_netlogon_info.snli_query_mtx); 340 } 341 } 342 343 /*NOTREACHED*/ 344 return (NULL); 345 } 346 347 /* 348 * smb_netlogon_lsa_monitor 349 * 350 * This monitor should run as a separate thread. It waits on a condition 351 * variable until someone indicates that the LSA domain information needs 352 * to be refreshed. It then queries the DC for the NT domain information: 353 * primary, account and trusted domains. The condition variable should be 354 * signaled whenever a DC is selected. 355 * 356 * Note that the LSA query calls require the DC information and this task 357 * may end up blocked on the DC location protocol, which is why this 358 * monitor is run as a separate thread. This should only happen if the DC 359 * goes down immediately after we located it. 360 */ 361 /*ARGSUSED*/ 362 static void * 363 smb_netlogon_lsa_monitor(void *arg) 364 { 365 uint32_t status; 366 367 for (;;) { 368 (void) mutex_lock(&smb_netlogon_info.snli_query_mtx); 369 370 while ((smb_netlogon_info.snli_flags & SMB_NETLF_LSA_QUERY) == 371 0) { 372 (void) cond_wait(&smb_netlogon_info.snli_query_cv, 373 &smb_netlogon_info.snli_query_mtx); 374 } 375 376 smb_netlogon_info.snli_flags &= ~SMB_NETLF_LSA_QUERY; 377 (void) mutex_unlock(&smb_netlogon_info.snli_query_mtx); 378 379 /* 380 * Skip the LSA query if Authenticated IPC is supported 381 * and the credential is not yet set. 382 */ 383 if (smbrdr_ipc_skip_lsa_query() == 0) { 384 status = lsa_query_primary_domain_info(); 385 if (status == NT_STATUS_SUCCESS) { 386 if (lsa_query_account_domain_info() 387 != NT_STATUS_SUCCESS) { 388 syslog(LOG_DEBUG, 389 "NetlogonLSAMonitor: query " 390 "account info failed"); 391 } 392 if (lsa_enum_trusted_domains() 393 != NT_STATUS_SUCCESS) { 394 syslog(LOG_DEBUG, 395 "NetlogonLSAMonitor: enum " 396 "trusted domain failed"); 397 } 398 } else { 399 syslog(LOG_DEBUG, 400 "NetlogonLSAMonitor: update failed"); 401 } 402 } 403 } 404 405 /*NOTREACHED*/ 406 return (NULL); 407 } 408