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 2009 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 * DC Locator 51 */ 52 #define SMB_DCLOCATOR_TIMEOUT 45 53 #define SMB_IS_FQDN(domain) (strchr(domain, '.') != NULL) 54 55 typedef struct smb_dclocator { 56 char sdl_domain[SMB_PI_MAX_DOMAIN]; 57 char sdl_dc[MAXHOSTNAMELEN]; 58 boolean_t sdl_locate; 59 mutex_t sdl_mtx; 60 cond_t sdl_cv; 61 uint32_t sdl_status; 62 } smb_dclocator_t; 63 64 static smb_dclocator_t smb_dclocator; 65 static pthread_t smb_dclocator_thr; 66 67 static void *smb_dclocator_main(void *); 68 static void smb_domain_update(char *, char *); 69 static boolean_t smb_domain_query_dns(char *, char *, smb_domain_t *); 70 static boolean_t smb_domain_query_nbt(char *, char *, smb_domain_t *); 71 static boolean_t smb_domain_match(char *, char *, uint32_t); 72 static uint32_t smb_domain_query(char *, char *, smb_domain_t *); 73 static void smb_domain_enum_trusted(char *, char *, smb_trusted_domains_t *); 74 static uint32_t smb_domain_use_config(char *, nt_domain_t *); 75 static void smb_domain_free(smb_domain_t *di); 76 77 /* 78 * =================================================================== 79 * API to initialize DC locator thread, trigger DC discovery, and 80 * get the discovered DC and/or domain information. 81 * =================================================================== 82 */ 83 84 /* 85 * smb_dclocator_init 86 * 87 * Initialization of the DC locator thread. 88 * Returns 0 on success, an error number if thread creation fails. 89 */ 90 int 91 smb_dclocator_init(void) 92 { 93 pthread_attr_t tattr; 94 int rc; 95 96 (void) pthread_attr_init(&tattr); 97 (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); 98 rc = pthread_create(&smb_dclocator_thr, &tattr, 99 smb_dclocator_main, 0); 100 (void) pthread_attr_destroy(&tattr); 101 return (rc); 102 } 103 104 /* 105 * smb_locate_dc 106 * 107 * This is the entry point for discovering a domain controller for the 108 * specified domain. 109 * 110 * The actual work of discovering a DC is handled by DC locator thread. 111 * All we do here is signal the request and wait for a DC or a timeout. 112 * 113 * Input parameters: 114 * domain - domain to be discovered (can either be NetBIOS or DNS domain) 115 * dc - preferred DC. If the preferred DC is set to empty string, it 116 * will attempt to discover any DC in the specified domain. 117 * 118 * Output parameter: 119 * dp - on success, dp will be filled with the discovered DC and domain 120 * information. 121 * Returns B_TRUE if the DC/domain info is available. 122 */ 123 boolean_t 124 smb_locate_dc(char *domain, char *dc, smb_domain_t *dp) 125 { 126 int rc; 127 timestruc_t to; 128 smb_domain_t domain_info; 129 130 if (domain == NULL || *domain == '\0') 131 return (B_FALSE); 132 133 (void) mutex_lock(&smb_dclocator.sdl_mtx); 134 135 if (!smb_dclocator.sdl_locate) { 136 smb_dclocator.sdl_locate = B_TRUE; 137 (void) strlcpy(smb_dclocator.sdl_domain, domain, 138 SMB_PI_MAX_DOMAIN); 139 (void) strlcpy(smb_dclocator.sdl_dc, dc, MAXHOSTNAMELEN); 140 (void) cond_broadcast(&smb_dclocator.sdl_cv); 141 } 142 143 while (smb_dclocator.sdl_locate) { 144 to.tv_sec = SMB_DCLOCATOR_TIMEOUT; 145 to.tv_nsec = 0; 146 rc = cond_reltimedwait(&smb_dclocator.sdl_cv, 147 &smb_dclocator.sdl_mtx, &to); 148 149 if (rc == ETIME) 150 break; 151 } 152 153 if (dp == NULL) 154 dp = &domain_info; 155 rc = smb_domain_getinfo(dp); 156 157 (void) mutex_unlock(&smb_dclocator.sdl_mtx); 158 159 return (rc); 160 } 161 162 /* 163 * Returns a copy of primary domain information plus 164 * the selected domain controller 165 */ 166 boolean_t 167 smb_domain_getinfo(smb_domain_t *dp) 168 { 169 return (nt_domain_get_primary(dp)); 170 } 171 172 /* 173 * ========================================================== 174 * DC discovery functions 175 * ========================================================== 176 */ 177 178 /* 179 * This is the DC discovery thread: it gets woken up whenever someone 180 * wants to locate a domain controller. 181 * 182 * Upon success, the SMB domain cache will be populated with the discovered 183 * DC and domain info. 184 */ 185 /*ARGSUSED*/ 186 static void * 187 smb_dclocator_main(void *arg) 188 { 189 char domain[SMB_PI_MAX_DOMAIN]; 190 char sought_dc[MAXHOSTNAMELEN]; 191 192 for (;;) { 193 (void) mutex_lock(&smb_dclocator.sdl_mtx); 194 195 while (!smb_dclocator.sdl_locate) 196 (void) cond_wait(&smb_dclocator.sdl_cv, 197 &smb_dclocator.sdl_mtx); 198 199 (void) strlcpy(domain, smb_dclocator.sdl_domain, 200 SMB_PI_MAX_DOMAIN); 201 (void) strlcpy(sought_dc, smb_dclocator.sdl_dc, MAXHOSTNAMELEN); 202 (void) mutex_unlock(&smb_dclocator.sdl_mtx); 203 204 smb_domain_update(domain, sought_dc); 205 206 (void) mutex_lock(&smb_dclocator.sdl_mtx); 207 smb_dclocator.sdl_locate = B_FALSE; 208 (void) cond_broadcast(&smb_dclocator.sdl_cv); 209 (void) mutex_unlock(&smb_dclocator.sdl_mtx); 210 } 211 212 /*NOTREACHED*/ 213 return (NULL); 214 } 215 216 /* 217 * Discovers a domain controller for the specified domain either via 218 * DNS or NetBIOS. After the domain controller is discovered successfully 219 * primary and trusted domain infromation will be queried using RPC queries. 220 * If the RPC queries fail, the domain information stored in SMF might be used 221 * if the the discovered domain is the same as the previously joined domain. 222 * If everything is successful domain cache will be updated with all the 223 * obtained information. 224 */ 225 static void 226 smb_domain_update(char *domain, char *server) 227 { 228 smb_domain_t di; 229 boolean_t query_ok; 230 231 bzero(&di, sizeof (smb_domain_t)); 232 233 nt_domain_start_update(); 234 235 if (SMB_IS_FQDN(domain)) 236 query_ok = smb_domain_query_dns(domain, server, &di); 237 else 238 query_ok = smb_domain_query_nbt(domain, server, &di); 239 240 if (query_ok) 241 nt_domain_update(&di); 242 243 nt_domain_end_update(); 244 245 smb_domain_free(&di); 246 247 if (query_ok) 248 nt_domain_save(); 249 } 250 251 /* 252 * Discovers a DC for the specified domain via DNS. If a DC is found 253 * primary and trusted domains information will be queried. 254 */ 255 static boolean_t 256 smb_domain_query_dns(char *domain, char *server, smb_domain_t *di) 257 { 258 uint32_t status; 259 if (!smb_ads_lookup_msdcs(domain, server, di->d_dc, MAXHOSTNAMELEN)) 260 return (B_FALSE); 261 262 status = smb_domain_query(domain, di->d_dc, di); 263 return (status == NT_STATUS_SUCCESS); 264 } 265 266 /* 267 * Discovers a DC for the specified domain using NETLOGON protocol. 268 * If a DC cannot be found using NETLOGON then it will 269 * try to resolve it via DNS, i.e. find out if it is the first label 270 * of a DNS domain name. If the corresponding DNS name is found, DC 271 * discovery will be done via DNS query. 272 * 273 * If the fully-qualified domain name is derived from the DNS config 274 * file, the NetBIOS domain name specified by the user will be compared 275 * against the NetBIOS domain name obtained via LSA query. If there is 276 * a mismatch, the DC discovery will fail since the discovered DC is 277 * actually for another domain, whose first label of its FQDN somehow 278 * matches with the NetBIOS name of the domain we're interested in. 279 */ 280 281 static boolean_t 282 smb_domain_query_nbt(char *domain, char *server, smb_domain_t *di) 283 { 284 char dnsdomain[MAXHOSTNAMELEN]; 285 uint32_t status; 286 287 *dnsdomain = '\0'; 288 289 if (!smb_browser_netlogon(domain, di->d_dc, MAXHOSTNAMELEN)) { 290 if (!smb_domain_match(domain, dnsdomain, MAXHOSTNAMELEN)) 291 return (B_FALSE); 292 293 if (!smb_ads_lookup_msdcs(dnsdomain, server, di->d_dc, 294 MAXHOSTNAMELEN)) 295 return (B_FALSE); 296 } 297 298 status = smb_domain_query(domain, di->d_dc, di); 299 if (status != NT_STATUS_SUCCESS) 300 return (B_FALSE); 301 302 if ((*dnsdomain != '\0') && 303 utf8_strcasecmp(domain, di->d_info.di_nbname)) 304 return (B_FALSE); 305 306 /* 307 * Now that we get the fully-qualified DNS name of the 308 * domain via LSA query. Verifies ADS configuration 309 * if we previously locate a DC via NetBIOS. On success, 310 * ADS cache will be populated. 311 */ 312 if (smb_ads_lookup_msdcs(di->d_info.di_fqname, server, 313 di->d_dc, MAXHOSTNAMELEN) == 0) 314 return (B_FALSE); 315 316 return (B_TRUE); 317 } 318 319 /* 320 * Tries to find a matching DNS domain for the given NetBIOS domain 321 * name by checking the first label of system's configured DNS domains. 322 * If a match is found, it'll be returned in the passed buffer. 323 */ 324 static boolean_t 325 smb_domain_match(char *nb_domain, char *buf, uint32_t len) 326 { 327 struct __res_state res_state; 328 int i; 329 char *entry, *p; 330 char first_label[MAXHOSTNAMELEN]; 331 boolean_t found; 332 333 if (!nb_domain || !buf) 334 return (B_FALSE); 335 336 *buf = '\0'; 337 bzero(&res_state, sizeof (struct __res_state)); 338 if (res_ninit(&res_state)) 339 return (B_FALSE); 340 341 found = B_FALSE; 342 entry = res_state.defdname; 343 for (i = 0; entry != NULL; i++) { 344 (void) strlcpy(first_label, entry, MAXHOSTNAMELEN); 345 if ((p = strchr(first_label, '.')) != NULL) { 346 *p = '\0'; 347 if (strlen(first_label) > 15) 348 first_label[15] = '\0'; 349 } 350 351 if (utf8_strcasecmp(nb_domain, first_label) == 0) { 352 found = B_TRUE; 353 (void) strlcpy(buf, entry, len); 354 break; 355 } 356 357 entry = res_state.dnsrch[i]; 358 } 359 360 361 res_ndestroy(&res_state); 362 return (found); 363 } 364 365 /* 366 * Obtain primary and trusted domain information using LSA queries. 367 * 368 * Disconnect any existing connection with the domain controller. 369 * This will ensure that no stale connection will be used, it will 370 * also pickup any configuration changes in either side by trying 371 * to establish a new connection. 372 * 373 * domain - either NetBIOS or fully-qualified domain name 374 */ 375 static uint32_t 376 smb_domain_query(char *domain, char *server, smb_domain_t *di) 377 { 378 uint32_t status; 379 380 mlsvc_disconnect(server); 381 382 status = lsa_query_dns_domain_info(server, domain, &di->d_info); 383 if (status != NT_STATUS_SUCCESS) { 384 status = smb_domain_use_config(domain, &di->d_info); 385 if (status != NT_STATUS_SUCCESS) 386 status = lsa_query_primary_domain_info(server, domain, 387 &di->d_info); 388 } 389 390 if (status == NT_STATUS_SUCCESS) 391 smb_domain_enum_trusted(domain, server, &di->d_trusted); 392 393 return (status); 394 } 395 396 /* 397 * Obtain trusted domains information using LSA queries. 398 * 399 * domain - either NetBIOS or fully-qualified domain name. 400 */ 401 static void 402 smb_domain_enum_trusted(char *domain, char *server, smb_trusted_domains_t *list) 403 { 404 uint32_t status; 405 406 status = lsa_enum_trusted_domains_ex(server, domain, list); 407 if (status != NT_STATUS_SUCCESS) 408 (void) lsa_enum_trusted_domains(server, domain, list); 409 } 410 411 /* 412 * If the domain to be discovered matches the current domain (i.e the 413 * value of either domain or fqdn configuration), the output parameter 414 * 'dinfo' will be set to the information stored in SMF. 415 */ 416 static uint32_t 417 smb_domain_use_config(char *domain, nt_domain_t *dinfo) 418 { 419 boolean_t use; 420 421 bzero(dinfo, sizeof (nt_domain_t)); 422 423 if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) 424 return (NT_STATUS_UNSUCCESSFUL); 425 426 smb_config_getdomaininfo(dinfo->di_nbname, dinfo->di_fqname, 427 NULL, NULL, NULL); 428 429 if (SMB_IS_FQDN(domain)) 430 use = (utf8_strcasecmp(dinfo->di_fqname, domain) == 0); 431 else 432 use = (utf8_strcasecmp(dinfo->di_nbname, domain) == 0); 433 434 if (use) 435 smb_config_getdomaininfo(NULL, NULL, dinfo->di_sid, 436 dinfo->di_u.di_dns.ddi_forest, 437 dinfo->di_u.di_dns.ddi_guid); 438 439 return ((use) ? NT_STATUS_SUCCESS : NT_STATUS_UNSUCCESSFUL); 440 } 441 442 static void 443 smb_domain_free(smb_domain_t *di) 444 { 445 free(di->d_trusted.td_domains); 446 } 447