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