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
smb_dclocator_init(void)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
smb_locate_dc(char * domain,char * dc,smb_domainex_t * dp)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
smb_ddiscover_wait(void)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 *
smb_ddiscover_service(void * arg)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
smb_ddiscover_main(char * domain,char * server)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
smb_ddiscover_dns(char * domain,char * server,smb_domainex_t * dxi)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
smb_ddiscover_nbt(char * domain,char * server,smb_domainex_t * dxi)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
smb_ddiscover_domain_match(char * nb_domain,char * buf,uint32_t len)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
smb_ddiscover_qinfo(char * domain,char * server,smb_domainex_t * dxi)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
smb_ddiscover_enum_trusted(char * domain,char * server,smb_domainex_t * dxi)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
smb_ddiscover_use_config(char * domain,smb_domainex_t * dxi)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
smb_domainex_free(smb_domainex_t * dxi)459 smb_domainex_free(smb_domainex_t *dxi)
460 {
461 free(dxi->d_trusted.td_domains);
462 }
463