xref: /illumos-gate/usr/src/cmd/smbsrv/smbd/smbd_join.c (revision 3db3f65c6274eb042354801a308c8e9bc4994553)
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 	char snli_dc[MAXHOSTNAMELEN];
60 	unsigned snli_flags;
61 	mutex_t snli_locate_mtx;
62 	cond_t snli_locate_cv;
63 	mutex_t snli_query_mtx;
64 	cond_t snli_query_cv;
65 	uint32_t snli_status;
66 } smb_netlogon_info_t;
67 
68 static smb_netlogon_info_t smb_netlogon_info;
69 
70 /* NT4 domain support is not yet available. */
71 static boolean_t nt4_domain_support = B_FALSE;
72 
73 static pthread_t lsa_monitor_thr;
74 static pthread_t dc_browser_thr;
75 
76 static void *smb_netlogon_lsa_monitor(void *arg);
77 static void *smb_netlogon_dc_browser(void *arg);
78 
79 /*
80  * Inline convenience function to find out if the domain information is
81  * valid. The caller can decide whether or not to wait.
82  */
83 static boolean_t
84 smb_ntdomain_is_valid(uint32_t timeout)
85 {
86 	smb_ntdomain_t *info;
87 
88 	if ((info = smb_getdomaininfo(timeout)) != 0) {
89 		if (info->ipaddr != 0)
90 			return (B_TRUE);
91 	}
92 
93 	return (B_FALSE);
94 }
95 
96 /*
97  * Retrieve the kpasswd server from krb5.conf.
98  */
99 static int
100 smbd_get_kpasswd_srv(char *srv, size_t len)
101 {
102 	FILE *fp;
103 	static char buf[512];
104 	char *p;
105 
106 	*srv = '\0';
107 	p = getenv("KRB5_CONFIG");
108 	if (p == NULL || *p == '\0')
109 		p = "/etc/krb5/krb5.conf";
110 
111 	if ((fp = fopen(p, "r")) == NULL)
112 		return (-1);
113 
114 	while (fgets(buf, sizeof (buf), fp)) {
115 
116 		/* Weed out any comment text */
117 		(void) trim_whitespace(buf);
118 		if (*buf == '#')
119 			continue;
120 
121 		if ((p = strstr(buf, "kpasswd_server")) != NULL) {
122 			if ((p = strchr(p, '=')) != NULL) {
123 				(void) trim_whitespace(++p);
124 				(void) strlcpy(srv, p, len);
125 			}
126 			break;
127 		}
128 	}
129 
130 	(void) fclose(fp);
131 	return ((*srv == '\0') ? -1 : 0);
132 }
133 
134 /*
135  * smbd_join
136  *
137  * Joins the specified domain/workgroup
138  */
139 uint32_t
140 smbd_join(smb_joininfo_t *info)
141 {
142 	smb_ntdomain_t *pi;
143 	uint32_t status;
144 	unsigned char passwd_hash[SMBAUTH_HASH_SZ];
145 	char plain_passwd[PASS_LEN + 1];
146 	char plain_user[PASS_LEN + 1];
147 	char nbt_domain[SMB_PI_MAX_DOMAIN];
148 	char fqdn[MAXHOSTNAMELEN];
149 	char dc[MAXHOSTNAMELEN];
150 	char kpasswd_domain[MAXHOSTNAMELEN];
151 
152 	(void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain,
153 	    MAXHOSTNAMELEN);
154 
155 	if (info->mode == SMB_SECMODE_WORKGRP) {
156 		if ((smb_config_get_secmode() == SMB_SECMODE_DOMAIN) &&
157 		    kpasswd_domain == '\0') {
158 
159 			if (smb_ads_domain_change_cleanup("") != 0) {
160 				syslog(LOG_ERR, "smbd: unable to remove the"
161 				    " old keys from the Kerberos keytab. "
162 				    "Please remove the old keys for your "
163 				    "host principal.");
164 			}
165 		}
166 
167 		(void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nbt_domain,
168 		    sizeof (nbt_domain));
169 
170 		(void) smb_config_set_secmode(info->mode);
171 		(void) smb_config_setstr(SMB_CI_DOMAIN_NAME, info->domain_name);
172 
173 		if (strcasecmp(nbt_domain, info->domain_name))
174 			smb_browser_reconfig();
175 
176 		return (NT_STATUS_SUCCESS);
177 	}
178 
179 	/*
180 	 * Ensure that any previous membership of this domain has
181 	 * been cleared from the environment before we start. This
182 	 * will ensure that we don't attempt a NETLOGON_SAMLOGON
183 	 * when attempting to find the PDC.
184 	 */
185 	(void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE);
186 
187 	(void) strlcpy(plain_user, info->domain_username, sizeof (plain_user));
188 	(void) strlcpy(plain_passwd, info->domain_passwd,
189 	    sizeof (plain_passwd));
190 	(void) smb_resolve_netbiosname(info->domain_name, nbt_domain,
191 	    sizeof (nbt_domain));
192 
193 	if (smb_resolve_fqdn(info->domain_name, fqdn, sizeof (fqdn)) != 1) {
194 		syslog(LOG_ERR, "smbd: fully-qualified domain name is unknown");
195 		return (NT_STATUS_INVALID_PARAMETER);
196 	}
197 
198 	if (smb_ads_domain_change_cleanup(fqdn)) {
199 		syslog(LOG_ERR, "smbd: unable to remove the old keys from the"
200 		    " Kerberos keytab. Please remove the old keys for your "
201 		    "host principal.");
202 		return (NT_STATUS_INTERNAL_ERROR);
203 	}
204 
205 	if (smb_auth_ntlm_hash(plain_passwd, passwd_hash) != SMBAUTH_SUCCESS) {
206 		status = NT_STATUS_INTERNAL_ERROR;
207 		syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'",
208 		    plain_user);
209 		return (status);
210 	}
211 
212 	smbrdr_ipc_set(plain_user, passwd_hash);
213 
214 	(void) smbd_get_kpasswd_srv(dc, sizeof (dc));
215 	if (smbd_locate_dc(nbt_domain, dc)) {
216 		if ((pi = smb_getdomaininfo(0)) == 0) {
217 			status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
218 			if (*dc == '\0')
219 				syslog(LOG_ERR, "smbd: could not get domain "
220 				    "controller information for '%s'",
221 				    info->domain_name);
222 			else
223 				syslog(LOG_ERR, "smbd: could not get the "
224 				    "specified domain controller information "
225 				    "'%s'", dc);
226 			return (status);
227 		}
228 
229 		/*
230 		 * Temporary delay before creating
231 		 * the workstation trust account.
232 		 */
233 		(void) sleep(2);
234 		status = mlsvc_join(pi->server, pi->domain,
235 		    plain_user, plain_passwd);
236 
237 		if (status == NT_STATUS_SUCCESS) {
238 			(void) smb_config_set_secmode(SMB_SECMODE_DOMAIN);
239 			(void) smb_config_setstr(SMB_CI_DOMAIN_NAME,
240 			    info->domain_name);
241 			smbrdr_ipc_commit();
242 			return (status);
243 		}
244 
245 		smbrdr_ipc_rollback();
246 		syslog(LOG_ERR, "smbd: failed joining %s (%s)",
247 		    info->domain_name, xlate_nt_status(status));
248 		return (status);
249 	}
250 
251 	smbrdr_ipc_rollback();
252 	syslog(LOG_ERR, "smbd: failed locating domain controller for %s",
253 	    info->domain_name);
254 	return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
255 }
256 
257 /*
258  * smbd_locate_dc
259  *
260  * This is the entry point for discovering a domain controller for the
261  * specified domain. The caller may block here for around 30 seconds if
262  * the system has to go to the network and find a domain controller.
263  * Sometime it would be good to change this to smb_locate_pdc and allow
264  * the caller to specify whether or not he wants to wait for a response.
265  *
266  * The actual work of discovering a DC is handled by other threads.
267  * All we do here is signal the request and wait for a DC or a timeout.
268  *
269  * domain - domain to be discovered
270  * dc - preferred DC. If the preferred DC is set to empty string, it
271  *      will attempt to discover any DC in the specified domain.
272  *
273  * Returns B_TRUE if a domain controller is available.
274  */
275 boolean_t
276 smbd_locate_dc(char *domain, char *dc)
277 {
278 	int rc;
279 	timestruc_t to;
280 
281 	if (domain == NULL || *domain == '\0')
282 		return (B_FALSE);
283 
284 	(void) mutex_lock(&smb_netlogon_info.snli_locate_mtx);
285 
286 	if ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) == 0) {
287 		smb_netlogon_info.snli_flags |= SMB_NETLF_LOCATE_DC;
288 		(void) strlcpy(smb_netlogon_info.snli_domain, domain,
289 		    SMB_PI_MAX_DOMAIN);
290 		(void) strlcpy(smb_netlogon_info.snli_dc, dc,
291 		    MAXHOSTNAMELEN);
292 		(void) cond_broadcast(&smb_netlogon_info.snli_locate_cv);
293 	}
294 
295 	while (smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) {
296 		to.tv_sec = SMB_NETLOGON_TIMEOUT;
297 		to.tv_nsec = 0;
298 		rc = cond_reltimedwait(&smb_netlogon_info.snli_locate_cv,
299 		    &smb_netlogon_info.snli_locate_mtx, &to);
300 
301 		if (rc == ETIME)
302 			break;
303 	}
304 
305 	(void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx);
306 
307 	return (smb_ntdomain_is_valid(0));
308 }
309 
310 /*
311  * smb_netlogon_init
312  *
313  * Initialization of the DC browser and LSA monitor threads.
314  * Returns 0 on success, an error number if thread creation fails.
315  */
316 int
317 smb_netlogon_init(void)
318 {
319 	pthread_attr_t tattr;
320 	int rc;
321 
322 	(void) pthread_attr_init(&tattr);
323 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
324 	rc = pthread_create(&lsa_monitor_thr, &tattr,
325 	    smb_netlogon_lsa_monitor, 0);
326 	if (rc != 0)
327 		goto nli_exit;
328 	rc = pthread_create(&dc_browser_thr, &tattr,
329 	    smb_netlogon_dc_browser, 0);
330 	if (rc != 0) {
331 		(void) pthread_cancel(lsa_monitor_thr);
332 		(void) pthread_join(lsa_monitor_thr, NULL);
333 	}
334 
335 nli_exit:
336 	(void) pthread_attr_destroy(&tattr);
337 	return (rc);
338 }
339 
340 /*
341  * smb_netlogon_dc_browser
342  *
343  * This is the DC browser thread: it gets woken up whenever someone
344  * wants to locate a domain controller.
345  *
346  * With the introduction of Windows 2000, NetBIOS is no longer a
347  * requirement for NT domains. If NetBIOS has been disabled on the
348  * network there will be no browsers and we won't get any response
349  * to netlogon requests. So we try to find a DC controller via ADS
350  * first. If ADS is disabled or the DNS query fails, we drop back
351  * to the netlogon protocol.
352  *
353  * This function will block for up to 30 seconds waiting for the PDC
354  * to be discovered. Sometime it would be good to change this to
355  * smb_locate_pdc and allow the caller to specify whether or not he
356  * wants to wait for a response.
357  *
358  */
359 /*ARGSUSED*/
360 static void *
361 smb_netlogon_dc_browser(void *arg)
362 {
363 	boolean_t rc;
364 	char resource_domain[SMB_PI_MAX_DOMAIN];
365 	char dc[MAXHOSTNAMELEN];
366 
367 	for (;;) {
368 		(void) mutex_lock(&smb_netlogon_info.snli_locate_mtx);
369 
370 		while ((smb_netlogon_info.snli_flags & SMB_NETLF_LOCATE_DC) ==
371 		    0) {
372 			(void) cond_wait(&smb_netlogon_info.snli_locate_cv,
373 			    &smb_netlogon_info.snli_locate_mtx);
374 		}
375 
376 		(void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx);
377 		(void) strlcpy(resource_domain, smb_netlogon_info.snli_domain,
378 		    SMB_PI_MAX_DOMAIN);
379 		(void) strlcpy(dc, smb_netlogon_info.snli_dc, MAXHOSTNAMELEN);
380 
381 		smb_setdomaininfo(NULL, NULL, 0);
382 		if ((smb_msdcs_lookup_ads(resource_domain, dc) == 0) &&
383 		    (nt4_domain_support)) {
384 			/* Try to locate a DC via NetBIOS */
385 			smb_browser_netlogon(resource_domain);
386 		}
387 
388 		rc = smb_ntdomain_is_valid(SMB_NETLOGON_TIMEOUT);
389 
390 		(void) mutex_lock(&smb_netlogon_info.snli_locate_mtx);
391 		smb_netlogon_info.snli_flags &= ~SMB_NETLF_LOCATE_DC;
392 		(void) cond_broadcast(&smb_netlogon_info.snli_locate_cv);
393 		(void) mutex_unlock(&smb_netlogon_info.snli_locate_mtx);
394 
395 		if (rc != B_TRUE) {
396 			/*
397 			 * Notify the LSA monitor to update the
398 			 * primary and trusted domain information.
399 			 */
400 			(void) mutex_lock(&smb_netlogon_info.snli_query_mtx);
401 			smb_netlogon_info.snli_flags |= SMB_NETLF_LSA_QUERY;
402 			(void) cond_broadcast(&smb_netlogon_info.snli_query_cv);
403 			(void) mutex_unlock(&smb_netlogon_info.snli_query_mtx);
404 		}
405 	}
406 
407 	/*NOTREACHED*/
408 	return (NULL);
409 }
410 
411 /*
412  * smb_netlogon_lsa_monitor
413  *
414  * This monitor should run as a separate thread. It waits on a condition
415  * variable until someone indicates that the LSA domain information needs
416  * to be refreshed. It then queries the DC for the NT domain information:
417  * primary, account and trusted domains. The condition variable should be
418  * signaled whenever a DC is selected.
419  *
420  * Note that the LSA query calls require the DC information and this task
421  * may end up blocked on the DC location protocol, which is why this
422  * monitor is run as a separate thread. This should only happen if the DC
423  * goes down immediately after we located it.
424  */
425 /*ARGSUSED*/
426 static void *
427 smb_netlogon_lsa_monitor(void *arg)
428 {
429 	uint32_t status;
430 
431 	for (;;) {
432 		(void) mutex_lock(&smb_netlogon_info.snli_query_mtx);
433 
434 		while ((smb_netlogon_info.snli_flags & SMB_NETLF_LSA_QUERY) ==
435 		    0) {
436 			(void) cond_wait(&smb_netlogon_info.snli_query_cv,
437 			    &smb_netlogon_info.snli_query_mtx);
438 		}
439 
440 		smb_netlogon_info.snli_flags &= ~SMB_NETLF_LSA_QUERY;
441 		(void) mutex_unlock(&smb_netlogon_info.snli_query_mtx);
442 
443 		/*
444 		 * Skip the LSA query if Authenticated IPC is supported
445 		 * and the credential is not yet set.
446 		 */
447 		if (smbrdr_ipc_skip_lsa_query() == 0) {
448 			status = lsa_query_primary_domain_info();
449 			if (status == NT_STATUS_SUCCESS) {
450 				if (lsa_query_account_domain_info()
451 				    != NT_STATUS_SUCCESS) {
452 					syslog(LOG_DEBUG,
453 					    "NetlogonLSAMonitor: query "
454 					    "account info failed");
455 				}
456 				if (lsa_enum_trusted_domains()
457 				    != NT_STATUS_SUCCESS) {
458 					syslog(LOG_DEBUG,
459 					    "NetlogonLSAMonitor: enum "
460 					    "trusted domain failed");
461 				}
462 			} else {
463 				syslog(LOG_DEBUG,
464 				    "NetlogonLSAMonitor: update failed");
465 			}
466 		}
467 	}
468 
469 	/*NOTREACHED*/
470 	return (NULL);
471 }
472 
473 
474 /*
475  * smb_set_netlogon_cred
476  *
477  * If the system is joined to an AD domain via kclient, SMB daemon will need
478  * to establish the NETLOGON credential chain.
479  *
480  * Since the kclient has updated the machine password stored in SMF
481  * repository, the cached ipc_info must be updated accordingly by calling
482  * smbrdr_ipc_commit.
483  *
484  * Due to potential replication delays in a multiple DC environment, the
485  * NETLOGON rpc request must be sent to the DC, to which the KPASSWD request
486  * is sent. If the DC discovered by the SMB daemon is different than the
487  * kpasswd server, the current connection with the DC will be torn down
488  * and a DC discovery process will be triggered to locate the kpasswd
489  * server.
490  *
491  * If joining a new domain, the domain_name property must be set after a
492  * successful credential chain setup.
493  */
494 void
495 smb_set_netlogon_cred(void)
496 {
497 	smb_ntdomain_t *dp;
498 	smb_ntdomain_t domain_info;
499 	char kpasswd_srv[MAXHOSTNAMELEN];
500 	char kpasswd_domain[MAXHOSTNAMELEN];
501 	char sam_acct[MLSVC_ACCOUNT_NAME_MAX];
502 	char *ipc_usr, *dom;
503 	boolean_t new_domain = B_FALSE;
504 
505 	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
506 		return;
507 
508 	if (smb_match_netlogon_seqnum())
509 		return;
510 
511 	(void) smb_config_getstr(SMB_CI_KPASSWD_SRV, kpasswd_srv,
512 	    sizeof (kpasswd_srv));
513 
514 	if (*kpasswd_srv == '\0')
515 		return;
516 
517 	/*
518 	 * If the domain join initiated by smbadm join CLI is in
519 	 * progress, don't do anything.
520 	 */
521 	(void) smb_gethostname(sam_acct, MLSVC_ACCOUNT_NAME_MAX - 1, 0);
522 	(void) strlcat(sam_acct, "$", MLSVC_ACCOUNT_NAME_MAX);
523 	ipc_usr = smbrdr_ipc_get_user();
524 	if (strcasecmp(ipc_usr, sam_acct))
525 		return;
526 
527 	if ((dp = smb_getdomaininfo(0)) == NULL) {
528 		*domain_info.server = '\0';
529 		(void) smb_getdomainname(domain_info.domain,
530 		    sizeof (domain_info.domain));
531 		dp = &domain_info;
532 	}
533 
534 	(void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain,
535 	    sizeof (kpasswd_domain));
536 
537 	if (*kpasswd_domain != '\0' &&
538 	    strncasecmp(kpasswd_domain, dp->domain, strlen(dp->domain))) {
539 		dom = kpasswd_domain;
540 		new_domain = B_TRUE;
541 	} else {
542 		dom = dp->domain;
543 	}
544 
545 	/*
546 	 * DC discovery will be triggered if the domain info is not
547 	 * currently cached or the SMB daemon has previously discovered a DC
548 	 * that is different than the kpasswd server.
549 	 */
550 	if (new_domain || strcasecmp(dp->server, kpasswd_srv) != 0) {
551 		if (*dp->server != '\0')
552 			mlsvc_disconnect(dp->server);
553 
554 		if (!smbd_locate_dc(dom, kpasswd_srv))
555 			(void) smbd_locate_dc(dp->domain, "");
556 
557 		if ((dp = smb_getdomaininfo(0)) == NULL) {
558 			smbrdr_ipc_commit();
559 			return;
560 		}
561 	}
562 
563 	smbrdr_ipc_commit();
564 	if (mlsvc_netlogon(dp->server, dp->domain)) {
565 		syslog(LOG_ERR, "NETLOGON credential chain establishment"
566 		    " failed");
567 	} else {
568 		if (new_domain) {
569 			(void) smb_config_setstr(SMB_CI_DOMAIN_NAME,
570 			    kpasswd_domain);
571 			(void) smb_config_setstr(SMB_CI_KPASSWD_DOMAIN,
572 			    "");
573 		}
574 	}
575 
576 }
577