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