xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_domain.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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