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