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