xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c (revision 87c723434df4be9c0f7ef119567cb6e2da776a36)
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 2011 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 boolean_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 	boolean_t discovered;
239 
240 	bzero(&dxi, sizeof (smb_domainex_t));
241 
242 	if (smb_domain_start_update() != SMB_DOMAIN_SUCCESS)
243 		return;
244 
245 	if (SMB_IS_FQDN(domain))
246 		discovered = smb_ddiscover_dns(domain, server, &dxi);
247 	else
248 		discovered = smb_ddiscover_nbt(domain, server, &dxi);
249 
250 	if (discovered)
251 		smb_domain_update(&dxi);
252 
253 	smb_domain_end_update();
254 
255 	smb_domainex_free(&dxi);
256 
257 	if (discovered)
258 		smb_domain_save();
259 }
260 
261 /*
262  * Discovers a DC for the specified domain via DNS. If a DC is found
263  * primary and trusted domains information will be queried.
264  */
265 static boolean_t
266 smb_ddiscover_dns(char *domain, char *server, smb_domainex_t *dxi)
267 {
268 	uint32_t status;
269 
270 	if (!smb_ads_lookup_msdcs(domain, server, dxi->d_dc, MAXHOSTNAMELEN))
271 		return (B_FALSE);
272 
273 	status = smb_ddiscover_qinfo(domain, dxi->d_dc, dxi);
274 	return (status == NT_STATUS_SUCCESS);
275 }
276 
277 /*
278  * Discovers a DC for the specified domain using NETLOGON protocol.
279  * If a DC cannot be found using NETLOGON then it will
280  * try to resolve it via DNS, i.e. find out if it is the first label
281  * of a DNS domain name. If the corresponding DNS name is found, DC
282  * discovery will be done via DNS query.
283  *
284  * If the fully-qualified domain name is derived from the DNS config
285  * file, the NetBIOS domain name specified by the user will be compared
286  * against the NetBIOS domain name obtained via LSA query.  If there is
287  * a mismatch, the DC discovery will fail since the discovered DC is
288  * actually for another domain, whose first label of its FQDN somehow
289  * matches with the NetBIOS name of the domain we're interested in.
290  */
291 static boolean_t
292 smb_ddiscover_nbt(char *domain, char *server, smb_domainex_t *dxi)
293 {
294 	char dnsdomain[MAXHOSTNAMELEN];
295 	uint32_t status;
296 
297 	*dnsdomain = '\0';
298 
299 	if (!smb_browser_netlogon(domain, dxi->d_dc, MAXHOSTNAMELEN)) {
300 		if (!smb_ddiscover_domain_match(domain, dnsdomain,
301 		    MAXHOSTNAMELEN))
302 			return (B_FALSE);
303 
304 		if (!smb_ads_lookup_msdcs(dnsdomain, server, dxi->d_dc,
305 		    MAXHOSTNAMELEN))
306 			return (B_FALSE);
307 	}
308 
309 	status = smb_ddiscover_qinfo(domain, dxi->d_dc, dxi);
310 	if (status != NT_STATUS_SUCCESS)
311 		return (B_FALSE);
312 
313 	if ((*dnsdomain != '\0') &&
314 	    smb_strcasecmp(domain, dxi->d_primary.di_nbname, 0))
315 		return (B_FALSE);
316 
317 	/*
318 	 * Now that we get the fully-qualified DNS name of the
319 	 * domain via LSA query. Verifies ADS configuration
320 	 * if we previously locate a DC via NetBIOS. On success,
321 	 * ADS cache will be populated.
322 	 */
323 	if (smb_ads_lookup_msdcs(dxi->d_primary.di_fqname, server,
324 	    dxi->d_dc, MAXHOSTNAMELEN) == 0)
325 		return (B_FALSE);
326 
327 	return (B_TRUE);
328 }
329 
330 /*
331  * Tries to find a matching DNS domain for the given NetBIOS domain
332  * name by checking the first label of system's configured DNS domains.
333  * If a match is found, it'll be returned in the passed buffer.
334  */
335 static boolean_t
336 smb_ddiscover_domain_match(char *nb_domain, char *buf, uint32_t len)
337 {
338 	struct __res_state res_state;
339 	int i;
340 	char *entry, *p;
341 	char first_label[MAXHOSTNAMELEN];
342 	boolean_t found;
343 
344 	if (!nb_domain || !buf)
345 		return (B_FALSE);
346 
347 	*buf = '\0';
348 	bzero(&res_state, sizeof (struct __res_state));
349 	if (res_ninit(&res_state))
350 		return (B_FALSE);
351 
352 	found = B_FALSE;
353 	entry = res_state.defdname;
354 	for (i = 0; entry != NULL; i++) {
355 		(void) strlcpy(first_label, entry, MAXHOSTNAMELEN);
356 		if ((p = strchr(first_label, '.')) != NULL) {
357 			*p = '\0';
358 			if (strlen(first_label) > 15)
359 				first_label[15] = '\0';
360 		}
361 
362 		if (smb_strcasecmp(nb_domain, first_label, 0) == 0) {
363 			found = B_TRUE;
364 			(void) strlcpy(buf, entry, len);
365 			break;
366 		}
367 
368 		entry = res_state.dnsrch[i];
369 	}
370 
371 
372 	res_ndestroy(&res_state);
373 	return (found);
374 }
375 
376 /*
377  * Obtain primary and trusted domain information using LSA queries.
378  *
379  * Disconnect any existing connection with the domain controller.
380  * This will ensure that no stale connection will be used, it will
381  * also pickup any configuration changes in either side by trying
382  * to establish a new connection.
383  *
384  * domain - either NetBIOS or fully-qualified domain name
385  */
386 static uint32_t
387 smb_ddiscover_qinfo(char *domain, char *server, smb_domainex_t *dxi)
388 {
389 	uint32_t status;
390 
391 	mlsvc_disconnect(server);
392 
393 	status = lsa_query_dns_domain_info(server, domain, &dxi->d_primary);
394 	if (status != NT_STATUS_SUCCESS) {
395 		status = smb_ddiscover_use_config(domain, dxi);
396 		if (status != NT_STATUS_SUCCESS)
397 			status = lsa_query_primary_domain_info(server, domain,
398 			    &dxi->d_primary);
399 	}
400 
401 	if (status == NT_STATUS_SUCCESS)
402 		smb_ddiscover_enum_trusted(domain, server, dxi);
403 
404 	return (status);
405 }
406 
407 /*
408  * Obtain trusted domains information using LSA queries.
409  *
410  * domain - either NetBIOS or fully-qualified domain name.
411  */
412 static void
413 smb_ddiscover_enum_trusted(char *domain, char *server, smb_domainex_t *dxi)
414 {
415 	smb_trusted_domains_t *list;
416 	uint32_t status;
417 
418 	list = &dxi->d_trusted;
419 	status = lsa_enum_trusted_domains_ex(server, domain, list);
420 	if (status != NT_STATUS_SUCCESS)
421 		(void) lsa_enum_trusted_domains(server, domain, list);
422 }
423 
424 /*
425  * If the domain to be discovered matches the current domain (i.e the
426  * value of either domain or fqdn configuration), then get the primary
427  * domain information from SMF.
428  */
429 static uint32_t
430 smb_ddiscover_use_config(char *domain, smb_domainex_t *dxi)
431 {
432 	boolean_t use;
433 	smb_domain_t *dinfo;
434 
435 	dinfo = &dxi->d_primary;
436 	bzero(dinfo, sizeof (smb_domain_t));
437 
438 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
439 		return (NT_STATUS_UNSUCCESSFUL);
440 
441 	smb_config_getdomaininfo(dinfo->di_nbname, dinfo->di_fqname,
442 	    NULL, NULL, NULL);
443 
444 	if (SMB_IS_FQDN(domain))
445 		use = (smb_strcasecmp(dinfo->di_fqname, domain, 0) == 0);
446 	else
447 		use = (smb_strcasecmp(dinfo->di_nbname, domain, 0) == 0);
448 
449 	if (use)
450 		smb_config_getdomaininfo(NULL, NULL, dinfo->di_sid,
451 		    dinfo->di_u.di_dns.ddi_forest,
452 		    dinfo->di_u.di_dns.ddi_guid);
453 
454 	return ((use) ? NT_STATUS_SUCCESS : NT_STATUS_UNSUCCESSFUL);
455 }
456 
457 static void
458 smb_domainex_free(smb_domainex_t *dxi)
459 {
460 	free(dxi->d_trusted.td_domains);
461 }
462