xref: /illumos-gate/usr/src/cmd/smbsrv/smbd/smbd_join.c (revision 7b209c2c)
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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <syslog.h>
29 #include <synch.h>
30 #include <pthread.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <sys/errno.h>
35 
36 #include <smbsrv/libsmb.h>
37 #include <smbsrv/libsmbrdr.h>
38 #include <smbsrv/libsmbns.h>
39 #include <smbsrv/libmlsvc.h>
40 
41 #include <smbsrv/smbinfo.h>
42 #include <smbsrv/ntstatus.h>
43 #include <smbsrv/lsalib.h>
44 
45 /*
46  * Maximum time to wait for a domain controller (30 seconds).
47  */
48 #define	SMB_NETLOGON_TIMEOUT	30
49 
50 /*
51  * Flags used in conjunction with the location and query condition
52  * variables.
53  */
54 #define	SMB_NETLF_LOCATE_DC	0x00000001
55 #define	SMB_NETLF_LSA_QUERY	0x00000002
56 
57 typedef struct smb_netlogon_info {
58 	char snli_domain[SMB_PI_MAX_DOMAIN];
59 	unsigned snli_flags;
60 	mutex_t snli_locate_mtx;
61 	cond_t snli_locate_cv;
62 	mutex_t snli_query_mtx;
63 	cond_t snli_query_cv;
64 	uint32_t snli_status;
65 } smb_netlogon_info_t;
66 
67 static smb_netlogon_info_t smb_netlogon_info;
68 
69 static pthread_t lsa_monitor_thr;
70 static pthread_t dc_browser_thr;
71 
72 static void *smb_netlogon_lsa_monitor(void *arg);
73 static void *smb_netlogon_dc_browser(void *arg);
74 
75 /*
76  * Inline convenience function to find out if the domain information is
77  * valid. The caller can decide whether or not to wait.
78  */
79 static boolean_t
80 smb_ntdomain_is_valid(uint32_t timeout)
81 {
82 	smb_ntdomain_t *info;
83 
84 	if ((info = smb_getdomaininfo(timeout)) != 0) {
85 		if (info->ipaddr != 0)
86 			return (B_TRUE);
87 	}
88 
89 	return (B_FALSE);
90 }
91 
92 /*
93  * smbd_join
94  *
95  * Joins the specified domain/workgroup
96  */
97 uint32_t
98 smbd_join(smb_joininfo_t *info)
99 {
100 	smb_ntdomain_t *pi;
101 	uint32_t status;
102 	unsigned char passwd_hash[SMBAUTH_HASH_SZ];
103 	char plain_passwd[PASS_LEN + 1];
104 	char plain_user[PASS_LEN + 1];
105 	char nbt_domain[SMB_PI_MAX_DOMAIN];
106 	char fqdn[MAXHOSTNAMELEN];
107 
108 	if (info->mode == SMB_SECMODE_WORKGRP) {
109 		if (smb_config_get_secmode() == SMB_SECMODE_DOMAIN) {
110 			if (ads_domain_change_cleanup("") != 0) {
111 				syslog(LOG_ERR, "smbd: unable to remove the"
112 				    " old keys from the Kerberos keytab. "
113 				    "Please remove the old keys for your "
114 				    "host principal.");
115 			}
116 		}
117 
118 		(void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nbt_domain,
119 		    sizeof (nbt_domain));
120 
121 		(void) smb_config_set_secmode(info->mode);
122 		(void) smb_config_setstr(SMB_CI_DOMAIN_NAME, info->domain_name);
123 
124 		if (strcasecmp(nbt_domain, info->domain_name))
125 			smb_browser_reconfig();
126 
127 		return (NT_STATUS_SUCCESS);
128 	}
129 
130 	/*
131 	 * Ensure that any previous membership of this domain has
132 	 * been cleared from the environment before we start. This
133 	 * will ensure that we don't attempt a NETLOGON_SAMLOGON
134 	 * when attempting to find the PDC.
135 	 */
136 	(void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE);
137 
138 	(void) strlcpy(plain_user, info->domain_username, sizeof (plain_user));
139 	(void) strlcpy(plain_passwd, info->domain_passwd,
140 	    sizeof (plain_passwd));
141 	(void) smb_resolve_netbiosname(info->domain_name, nbt_domain,
142 	    sizeof (nbt_domain));
143 
144 	if (smb_resolve_fqdn(info->domain_name, fqdn, sizeof (fqdn)) != 1) {
145 		syslog(LOG_ERR, "smbd: fully-qualified domain name is unknown");
146 		return (NT_STATUS_INVALID_PARAMETER);
147 	}
148 
149 	if (ads_domain_change_cleanup(fqdn)) {
150 		syslog(LOG_ERR, "smbd: unable to remove the old keys from the"
151 		    " Kerberos keytab. Please remove the old keys for your "
152 		    "host principal.");
153 		return (NT_STATUS_INTERNAL_ERROR);
154 	}
155 
156 	if (smb_auth_ntlm_hash(plain_passwd, passwd_hash) != SMBAUTH_SUCCESS) {
157 		status = NT_STATUS_INTERNAL_ERROR;
158 		syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'",
159 		    plain_user);
160 		return (status);
161 	}
162 
163 	smbrdr_ipc_set(plain_user, passwd_hash);
164 
165 	if (locate_resource_pdc(nbt_domain)) {
166 		if ((pi = smb_getdomaininfo(0)) == 0) {
167 			status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
168 			syslog(LOG_ERR, "smbd: could not get domain controller"
169 			    "information for '%s'", info->domain_name);
170 			return (status);
171 		}
172 
173 		/*
174 		 * Temporary delay before creating
175 		 * the workstation trust account.
176 		 */
177 		(void) sleep(2);
178 		status = mlsvc_join(pi->server, pi->domain,
179 		    plain_user, plain_passwd);
180 
181 		if (status == NT_STATUS_SUCCESS) {
182 			(void) smb_config_set_secmode(SMB_SECMODE_DOMAIN);
183 			(void) smb_config_setstr(SMB_CI_DOMAIN_NAME,
184 			    info->domain_name);
185 			smbrdr_ipc_commit();
186 			return (status);
187 		}
188 
189 		smbrdr_ipc_rollback();
190 		syslog(LOG_ERR, "smbd: failed joining %s (%s)",
191 		    info->domain_name, xlate_nt_status(status));
192 		return (status);
193 	}
194 
195 	smbrdr_ipc_rollback();
196 	syslog(LOG_ERR, "smbd: failed locating domain controller for %s",
197 	    info->domain_name);
198 	return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
199 }
200 
201 /*
202  * locate_resource_pdc
203  *
204  * This is the entry point for discovering a domain controller for the
205  * specified domain. The caller may block here for around 30 seconds if
206  * the system has to go to the network and find a domain controller.
207  * Sometime it would be good to change this to smb_locate_pdc and allow
208  * the caller to specify whether or not he wants to wait for a response.
209  *
210  * The actual work of discovering a DC is handled by other threads.
211  * All we do here is signal the request and wait for a DC or a timeout.
212  *
213  * Returns B_TRUE if a domain controller is available.
214  */
215 boolean_t
216 locate_resource_pdc(char *domain)
217 {
218 	int rc;
219 	timestruc_t to;
220 
221 	if (domain == NULL || *domain == '\0')
222 		return (B_FALSE);
223 
224 	(void) mutex_lock(&smb_netlogon_info.snli_locate_mtx);
225 
226 	if ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) == 0) {
227 		smb_netlogon_info.snli_flags |= SMB_NETLF_LOCATE_DC;
228 		(void) strlcpy(smb_netlogon_info.snli_domain, domain,
229 		    SMB_PI_MAX_DOMAIN);
230 		(void) cond_broadcast(&smb_netlogon_info.snli_locate_cv);
231 	}
232 
233 	while (smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) {
234 		to.tv_sec = SMB_NETLOGON_TIMEOUT;
235 		to.tv_nsec = 0;
236 		rc = cond_reltimedwait(&smb_netlogon_info.snli_locate_cv,
237 		    &smb_netlogon_info.snli_locate_mtx, &to);
238 
239 		if (rc == ETIME)
240 			break;
241 	}
242 
243 	(void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx);
244 
245 	return (smb_ntdomain_is_valid(0));
246 }
247 
248 /*
249  * smb_netlogon_init
250  *
251  * Initialization of the DC browser and LSA monitor threads.
252  * Returns 0 on success, an error number if thread creation fails.
253  */
254 int
255 smb_netlogon_init(void)
256 {
257 	pthread_attr_t tattr;
258 	int rc;
259 
260 	(void) pthread_attr_init(&tattr);
261 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
262 	rc = pthread_create(&lsa_monitor_thr, &tattr,
263 	    smb_netlogon_lsa_monitor, 0);
264 	if (rc != 0)
265 		goto nli_exit;
266 	rc = pthread_create(&dc_browser_thr, &tattr,
267 	    smb_netlogon_dc_browser, 0);
268 	if (rc != 0) {
269 		(void) pthread_cancel(lsa_monitor_thr);
270 		(void) pthread_join(lsa_monitor_thr, NULL);
271 	}
272 
273 nli_exit:
274 	(void) pthread_attr_destroy(&tattr);
275 	return (rc);
276 }
277 
278 /*
279  * smb_netlogon_dc_browser
280  *
281  * This is the DC browser thread: it gets woken up whenever someone
282  * wants to locate a domain controller.
283  *
284  * With the introduction of Windows 2000, NetBIOS is no longer a
285  * requirement for NT domains. If NetBIOS has been disabled on the
286  * network there will be no browsers and we won't get any response
287  * to netlogon requests. So we try to find a DC controller via ADS
288  * first. If ADS is disabled or the DNS query fails, we drop back
289  * to the netlogon protocol.
290  *
291  * This function will block for up to 30 seconds waiting for the PDC
292  * to be discovered. Sometime it would be good to change this to
293  * smb_locate_pdc and allow the caller to specify whether or not he
294  * wants to wait for a response.
295  *
296  */
297 /*ARGSUSED*/
298 static void *
299 smb_netlogon_dc_browser(void *arg)
300 {
301 	boolean_t rc;
302 	char resource_domain[SMB_PI_MAX_DOMAIN];
303 
304 	for (;;) {
305 		(void) mutex_lock(&smb_netlogon_info.snli_locate_mtx);
306 
307 		while ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) ==
308 		    0) {
309 			(void) cond_wait(&smb_netlogon_info.snli_locate_cv,
310 			    &smb_netlogon_info.snli_locate_mtx);
311 		}
312 
313 		(void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx);
314 
315 		(void) strlcpy(resource_domain, smb_netlogon_info.snli_domain,
316 		    SMB_PI_MAX_DOMAIN);
317 
318 		smb_setdomaininfo(NULL, NULL, 0);
319 		if (msdcs_lookup_ads(resource_domain) == 0) {
320 			/* Try to locate a DC via NetBIOS */
321 			smb_browser_netlogon(resource_domain);
322 		}
323 
324 		rc = smb_ntdomain_is_valid(SMB_NETLOGON_TIMEOUT);
325 
326 		(void) mutex_lock(&smb_netlogon_info.snli_locate_mtx);
327 		smb_netlogon_info.snli_flags &= ~SMB_NETLF_LOCATE_DC;
328 		(void) cond_broadcast(&smb_netlogon_info.snli_locate_cv);
329 		(void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx);
330 
331 		if (rc != B_TRUE) {
332 			/*
333 			 * Notify the LSA monitor to update the
334 			 * primary and trusted domain information.
335 			 */
336 			(void) mutex_lock(&smb_netlogon_info.snli_query_mtx);
337 			smb_netlogon_info.snli_flags |= SMB_NETLF_LSA_QUERY;
338 			(void) cond_broadcast(&smb_netlogon_info.snli_query_cv);
339 			(void) mutex_unlock(&smb_netlogon_info.snli_query_mtx);
340 		}
341 	}
342 
343 	/*NOTREACHED*/
344 	return (NULL);
345 }
346 
347 /*
348  * smb_netlogon_lsa_monitor
349  *
350  * This monitor should run as a separate thread. It waits on a condition
351  * variable until someone indicates that the LSA domain information needs
352  * to be refreshed. It then queries the DC for the NT domain information:
353  * primary, account and trusted domains. The condition variable should be
354  * signaled whenever a DC is selected.
355  *
356  * Note that the LSA query calls require the DC information and this task
357  * may end up blocked on the DC location protocol, which is why this
358  * monitor is run as a separate thread. This should only happen if the DC
359  * goes down immediately after we located it.
360  */
361 /*ARGSUSED*/
362 static void *
363 smb_netlogon_lsa_monitor(void *arg)
364 {
365 	uint32_t status;
366 
367 	for (;;) {
368 		(void) mutex_lock(&smb_netlogon_info.snli_query_mtx);
369 
370 		while ((smb_netlogon_info.snli_flags & SMB_NETLF_LSA_QUERY) ==
371 		    0) {
372 			(void) cond_wait(&smb_netlogon_info.snli_query_cv,
373 			    &smb_netlogon_info.snli_query_mtx);
374 		}
375 
376 		smb_netlogon_info.snli_flags &= ~SMB_NETLF_LSA_QUERY;
377 		(void) mutex_unlock(&smb_netlogon_info.snli_query_mtx);
378 
379 		/*
380 		 * Skip the LSA query if Authenticated IPC is supported
381 		 * and the credential is not yet set.
382 		 */
383 		if (smbrdr_ipc_skip_lsa_query() == 0) {
384 			status = lsa_query_primary_domain_info();
385 			if (status == NT_STATUS_SUCCESS) {
386 				if (lsa_query_account_domain_info()
387 				    != NT_STATUS_SUCCESS) {
388 					syslog(LOG_DEBUG,
389 					    "NetlogonLSAMonitor: query "
390 					    "account info failed");
391 				}
392 				if (lsa_enum_trusted_domains()
393 				    != NT_STATUS_SUCCESS) {
394 					syslog(LOG_DEBUG,
395 					    "NetlogonLSAMonitor: enum "
396 					    "trusted domain failed");
397 				}
398 			} else {
399 				syslog(LOG_DEBUG,
400 				    "NetlogonLSAMonitor: update failed");
401 			}
402 		}
403 	}
404 
405 	/*NOTREACHED*/
406 	return (NULL);
407 }
408