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