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