xref: /illumos-gate/usr/src/cmd/smbsrv/smbd/smbd_join.c (revision b77b9231da168bb31490f65bf2697f6031b7f601)
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) 2007, 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 
34 #include <smbsrv/libsmb.h>
35 #include <smbsrv/libsmbns.h>
36 #include <smbsrv/libmlsvc.h>
37 #include <smbsrv/smbinfo.h>
38 #include "smbd.h"
39 
40 #define	SMBD_DC_MONITOR_ATTEMPTS		3
41 #define	SMBD_DC_MONITOR_RETRY_INTERVAL		3	/* seconds */
42 #define	SMBD_DC_MONITOR_INTERVAL		60	/* seconds */
43 
44 extern smbd_t smbd;
45 
46 static mutex_t smbd_dc_mutex;
47 static cond_t smbd_dc_cv;
48 
49 static void *smbd_dc_monitor(void *);
50 static void smbd_dc_update(void);
51 static boolean_t smbd_set_netlogon_cred(void);
52 static int smbd_get_kpasswd_srv(char *, size_t);
53 static uint32_t smbd_join_workgroup(smb_joininfo_t *);
54 static uint32_t smbd_join_domain(smb_joininfo_t *);
55 
56 /*
57  * Launch the DC discovery and monitor thread.
58  */
59 int
60 smbd_dc_monitor_init(void)
61 {
62 	pthread_attr_t	attr;
63 	int		rc;
64 
65 	(void) smb_config_getstr(SMB_CI_ADS_SITE, smbd.s_site,
66 	    MAXHOSTNAMELEN);
67 	(void) smb_config_getip(SMB_CI_DOMAIN_SRV, &smbd.s_pdc);
68 	smb_ads_init();
69 
70 	if (smbd.s_secmode != SMB_SECMODE_DOMAIN)
71 		return (0);
72 
73 	(void) pthread_attr_init(&attr);
74 	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
75 	rc = pthread_create(&smbd.s_dc_monitor_tid, &attr, smbd_dc_monitor,
76 	    NULL);
77 	(void) pthread_attr_destroy(&attr);
78 	return (rc);
79 }
80 
81 void
82 smbd_dc_monitor_refresh(void)
83 {
84 	char		site[MAXHOSTNAMELEN];
85 	smb_inaddr_t	pdc;
86 
87 	site[0] = '\0';
88 	bzero(&pdc, sizeof (smb_inaddr_t));
89 	(void) smb_config_getstr(SMB_CI_ADS_SITE, site, MAXHOSTNAMELEN);
90 	(void) smb_config_getip(SMB_CI_DOMAIN_SRV, &pdc);
91 
92 	(void) mutex_lock(&smbd_dc_mutex);
93 
94 	if ((bcmp(&smbd.s_pdc, &pdc, sizeof (smb_inaddr_t)) != 0) ||
95 	    (smb_strcasecmp(smbd.s_site, site, 0) != 0)) {
96 		bcopy(&pdc, &smbd.s_pdc, sizeof (smb_inaddr_t));
97 		(void) strlcpy(smbd.s_site, site, MAXHOSTNAMELEN);
98 		smbd.s_pdc_changed = B_TRUE;
99 		(void) cond_signal(&smbd_dc_cv);
100 	}
101 
102 	(void) mutex_unlock(&smbd_dc_mutex);
103 }
104 
105 /*ARGSUSED*/
106 static void *
107 smbd_dc_monitor(void *arg)
108 {
109 	boolean_t	ds_not_responding = B_FALSE;
110 	boolean_t	ds_cfg_changed = B_FALSE;
111 	timestruc_t	delay;
112 	int		i;
113 
114 	smbd_dc_update();
115 	smbd_online_wait("smbd_dc_monitor");
116 
117 	while (smbd_online()) {
118 		delay.tv_sec = SMBD_DC_MONITOR_INTERVAL;
119 		delay.tv_nsec = 0;
120 
121 		(void) mutex_lock(&smbd_dc_mutex);
122 		(void) cond_reltimedwait(&smbd_dc_cv, &smbd_dc_mutex, &delay);
123 
124 		if (smbd.s_pdc_changed) {
125 			smbd.s_pdc_changed = B_FALSE;
126 			ds_cfg_changed = B_TRUE;
127 		}
128 
129 		(void) mutex_unlock(&smbd_dc_mutex);
130 
131 		for (i = 0; i < SMBD_DC_MONITOR_ATTEMPTS; ++i) {
132 			if (dssetup_check_service() == 0) {
133 				ds_not_responding = B_FALSE;
134 				break;
135 			}
136 
137 			ds_not_responding = B_TRUE;
138 			(void) sleep(SMBD_DC_MONITOR_RETRY_INTERVAL);
139 		}
140 
141 		if (ds_not_responding)
142 			smb_log(smbd.s_loghd, LOG_NOTICE,
143 			    "smbd_dc_monitor: domain service not responding");
144 
145 		if (ds_not_responding || ds_cfg_changed) {
146 			ds_cfg_changed = B_FALSE;
147 			smb_ads_refresh();
148 			smbd_dc_update();
149 		}
150 	}
151 
152 	smbd.s_dc_monitor_tid = 0;
153 	return (NULL);
154 }
155 
156 /*
157  * Locate a domain controller in the current resource domain and Update
158  * the Netlogon credential chain.
159  *
160  * The domain configuration will be updated upon successful DC discovery.
161  */
162 static void
163 smbd_dc_update(void)
164 {
165 	char		domain[MAXHOSTNAMELEN];
166 	smb_domainex_t	info;
167 	smb_domain_t	*primary;
168 
169 
170 	if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0) {
171 		(void) smb_getdomainname(domain, MAXHOSTNAMELEN);
172 		(void) smb_strupr(domain);
173 	}
174 
175 	if (!smb_locate_dc(domain, "", &info)) {
176 		smb_log(smbd.s_loghd, LOG_NOTICE,
177 		    "smbd_dc_update: %s: locate failed", domain);
178 	} else {
179 		primary = &info.d_primary;
180 
181 		smb_config_setdomaininfo(primary->di_nbname,
182 		    primary->di_fqname,
183 		    primary->di_sid,
184 		    primary->di_u.di_dns.ddi_forest,
185 		    primary->di_u.di_dns.ddi_guid);
186 
187 		smb_log(smbd.s_loghd, LOG_NOTICE,
188 		    "smbd_dc_update: %s: located %s", domain, info.d_dc);
189 	}
190 
191 	if (smbd_set_netlogon_cred()) {
192 		/*
193 		 * Restart required because the domain changed
194 		 * or the credential chain setup failed.
195 		 */
196 		smb_log(smbd.s_loghd, LOG_NOTICE,
197 		    "smbd_dc_update: %s: smb/server restart required");
198 
199 		if (smb_smf_restart_service() != 0)
200 			smb_log(smbd.s_loghd, LOG_ERR,
201 			    "restart failed: run 'svcs -xv smb/server'"
202 			    " for more information");
203 	}
204 }
205 
206 /*
207  * smbd_join
208  *
209  * Joins the specified domain/workgroup.
210  *
211  * If the security mode or domain name is being changed,
212  * the caller must restart the service.
213  */
214 uint32_t
215 smbd_join(smb_joininfo_t *info)
216 {
217 	uint32_t status;
218 
219 	dssetup_clear_domain_info();
220 	if (info->mode == SMB_SECMODE_WORKGRP)
221 		status = smbd_join_workgroup(info);
222 	else
223 		status = smbd_join_domain(info);
224 
225 	return (status);
226 }
227 
228 /*
229  * smbd_set_netlogon_cred
230  *
231  * If the system is joined to an AD domain via kclient, SMB daemon will need
232  * to establish the NETLOGON credential chain.
233  *
234  * Since the kclient has updated the machine password stored in SMF
235  * repository, the cached ipc_info must be updated accordingly by calling
236  * smb_ipc_commit.
237  *
238  * Due to potential replication delays in a multiple DC environment, the
239  * NETLOGON rpc request must be sent to the DC, to which the KPASSWD request
240  * is sent. If the DC discovered by the SMB daemon is different than the
241  * kpasswd server, the current connection with the DC will be torn down
242  * and a DC discovery process will be triggered to locate the kpasswd
243  * server.
244  *
245  * If joining a new domain, the domain_name property must be set after a
246  * successful credential chain setup.
247  */
248 static boolean_t
249 smbd_set_netlogon_cred(void)
250 {
251 	char kpasswd_srv[MAXHOSTNAMELEN];
252 	char kpasswd_domain[MAXHOSTNAMELEN];
253 	char sam_acct[SMB_SAMACCT_MAXLEN];
254 	char ipc_usr[SMB_USERNAME_MAXLEN];
255 	char *dom;
256 	boolean_t new_domain = B_FALSE;
257 	smb_domainex_t dxi;
258 	smb_domain_t *di;
259 
260 	if (smb_match_netlogon_seqnum())
261 		return (B_FALSE);
262 
263 	(void) smb_config_getstr(SMB_CI_KPASSWD_SRV, kpasswd_srv,
264 	    sizeof (kpasswd_srv));
265 
266 	if (*kpasswd_srv == '\0')
267 		return (B_FALSE);
268 
269 	/*
270 	 * If the domain join initiated by smbadm join CLI is in
271 	 * progress, don't do anything.
272 	 */
273 	(void) smb_getsamaccount(sam_acct, sizeof (sam_acct));
274 	smb_ipc_get_user(ipc_usr, SMB_USERNAME_MAXLEN);
275 	if (smb_strcasecmp(ipc_usr, sam_acct, 0))
276 		return (B_FALSE);
277 
278 	di = &dxi.d_primary;
279 	if (!smb_domain_getinfo(&dxi))
280 		(void) smb_getfqdomainname(di->di_fqname, MAXHOSTNAMELEN);
281 
282 	(void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain,
283 	    sizeof (kpasswd_domain));
284 
285 	if (*kpasswd_domain != '\0' &&
286 	    smb_strcasecmp(kpasswd_domain, di->di_fqname, 0)) {
287 		dom = kpasswd_domain;
288 		new_domain = B_TRUE;
289 	} else {
290 		dom = di->di_fqname;
291 	}
292 
293 	/*
294 	 * DC discovery will be triggered if the domain info is not
295 	 * currently cached or the SMB daemon has previously discovered a DC
296 	 * that is different than the kpasswd server.
297 	 */
298 	if (new_domain || smb_strcasecmp(dxi.d_dc, kpasswd_srv, 0) != 0) {
299 		if (*dxi.d_dc != '\0')
300 			mlsvc_disconnect(dxi.d_dc);
301 
302 		if (!smb_locate_dc(dom, kpasswd_srv, &dxi)) {
303 			if (!smb_locate_dc(di->di_fqname, "", &dxi)) {
304 				smb_ipc_commit();
305 				return (B_FALSE);
306 			}
307 		}
308 	}
309 
310 	smb_ipc_commit();
311 	if (mlsvc_netlogon(dxi.d_dc, di->di_nbname)) {
312 		syslog(LOG_NOTICE,
313 		    "failed to establish NETLOGON credential chain");
314 		return (B_TRUE);
315 	} else {
316 		if (new_domain) {
317 			smb_config_setdomaininfo(di->di_nbname, di->di_fqname,
318 			    di->di_sid,
319 			    di->di_u.di_dns.ddi_forest,
320 			    di->di_u.di_dns.ddi_guid);
321 			(void) smb_config_setstr(SMB_CI_KPASSWD_DOMAIN, "");
322 		}
323 	}
324 
325 	return (new_domain);
326 }
327 
328 /*
329  * Retrieve the kpasswd server from krb5.conf.
330  *
331  * Initialization of the locate dc thread.
332  * Returns 0 on success, an error number if thread creation fails.
333  */
334 static int
335 smbd_get_kpasswd_srv(char *srv, size_t len)
336 {
337 	FILE *fp;
338 	static char buf[512];
339 	char *p;
340 
341 	*srv = '\0';
342 	p = getenv("KRB5_CONFIG");
343 	if (p == NULL || *p == '\0')
344 		p = "/etc/krb5/krb5.conf";
345 
346 	if ((fp = fopen(p, "r")) == NULL)
347 		return (-1);
348 
349 	while (fgets(buf, sizeof (buf), fp)) {
350 
351 		/* Weed out any comment text */
352 		(void) trim_whitespace(buf);
353 		if (*buf == '#')
354 			continue;
355 
356 		if ((p = strstr(buf, "kpasswd_server")) != NULL) {
357 			if ((p = strchr(p, '=')) != NULL) {
358 				(void) trim_whitespace(++p);
359 				(void) strlcpy(srv, p, len);
360 			}
361 			break;
362 		}
363 	}
364 
365 
366 	(void) fclose(fp);
367 	return ((*srv == '\0') ? -1 : 0);
368 }
369 
370 static uint32_t
371 smbd_join_workgroup(smb_joininfo_t *info)
372 {
373 	char nb_domain[SMB_PI_MAX_DOMAIN];
374 
375 	(void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nb_domain,
376 	    sizeof (nb_domain));
377 
378 	smbd_set_secmode(SMB_SECMODE_WORKGRP);
379 	smb_config_setdomaininfo(info->domain_name, "", "", "", "");
380 
381 	if (strcasecmp(nb_domain, info->domain_name))
382 		smb_browser_reconfig();
383 
384 	return (NT_STATUS_SUCCESS);
385 }
386 
387 static uint32_t
388 smbd_join_domain(smb_joininfo_t *info)
389 {
390 	uint32_t status;
391 	unsigned char passwd_hash[SMBAUTH_HASH_SZ];
392 	char dc[MAXHOSTNAMELEN];
393 	smb_domainex_t dxi;
394 	smb_domain_t *di;
395 
396 	/*
397 	 * Ensure that any previous membership of this domain has
398 	 * been cleared from the environment before we start. This
399 	 * will ensure that we don't attempt a NETLOGON_SAMLOGON
400 	 * when attempting to find the PDC.
401 	 */
402 
403 	(void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE);
404 
405 	if (smb_auth_ntlm_hash(info->domain_passwd, passwd_hash)
406 	    != SMBAUTH_SUCCESS) {
407 		syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'",
408 		    info->domain_username);
409 		return (NT_STATUS_INTERNAL_ERROR);
410 	}
411 
412 	smb_ipc_set(info->domain_username, passwd_hash);
413 
414 	(void) smbd_get_kpasswd_srv(dc, sizeof (dc));
415 	/* info->domain_name could either be NetBIOS domain name or FQDN */
416 	if (smb_locate_dc(info->domain_name, dc, &dxi)) {
417 		status = mlsvc_join(&dxi, info->domain_username,
418 		    info->domain_passwd);
419 
420 		if (status == NT_STATUS_SUCCESS) {
421 			di = &dxi.d_primary;
422 			smbd_set_secmode(SMB_SECMODE_DOMAIN);
423 			smb_config_setdomaininfo(di->di_nbname, di->di_fqname,
424 			    di->di_sid,
425 			    di->di_u.di_dns.ddi_forest,
426 			    di->di_u.di_dns.ddi_guid);
427 			smb_ipc_commit();
428 			return (status);
429 		}
430 
431 		smb_ipc_rollback();
432 		syslog(LOG_ERR, "smbd: failed joining %s (%s)",
433 		    info->domain_name, xlate_nt_status(status));
434 		return (status);
435 	}
436 
437 	smb_ipc_rollback();
438 	syslog(LOG_ERR, "smbd: failed locating domain controller for %s",
439 	    info->domain_name);
440 	return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
441 }
442