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