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