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