xref: /illumos-gate/usr/src/cmd/smbsrv/smbd/smbd_join.c (revision f5fc5c043551e3eab595da7d8b5a53479207738f)
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 #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 dinfo;
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 	ipc_usr = smbrdr_ipc_get_user();
124 	if (utf8_strcasecmp(ipc_usr, sam_acct))
125 		return (B_FALSE);
126 
127 	if (!smb_domain_getinfo(&dinfo))
128 		(void) smb_getfqdomainname(dinfo.d_fqdomain, MAXHOSTNAMELEN);
129 
130 	(void) smb_config_getstr(SMB_CI_KPASSWD_DOMAIN, kpasswd_domain,
131 	    sizeof (kpasswd_domain));
132 
133 	if (*kpasswd_domain != '\0' &&
134 	    utf8_strcasecmp(kpasswd_domain, dinfo.d_fqdomain)) {
135 		dom = kpasswd_domain;
136 		new_domain = B_TRUE;
137 	} else {
138 		dom = dinfo.d_fqdomain;
139 	}
140 
141 	/*
142 	 * DC discovery will be triggered if the domain info is not
143 	 * currently cached or the SMB daemon has previously discovered a DC
144 	 * that is different than the kpasswd server.
145 	 */
146 	if (new_domain || utf8_strcasecmp(dinfo.d_dc, kpasswd_srv) != 0) {
147 		if (*dinfo.d_dc != '\0')
148 			mlsvc_disconnect(dinfo.d_dc);
149 
150 		if (!smb_locate_dc(dom, kpasswd_srv, &dinfo)) {
151 			if (!smb_locate_dc(dinfo.d_fqdomain, "", &dinfo)) {
152 				smbrdr_ipc_commit();
153 				return (B_FALSE);
154 			}
155 		}
156 	}
157 
158 	smbrdr_ipc_commit();
159 	if (mlsvc_netlogon(dinfo.d_dc, dinfo.d_nbdomain)) {
160 		syslog(LOG_ERR,
161 		    "failed to establish NETLOGON credential chain");
162 		return (B_TRUE);
163 	} else {
164 		if (new_domain) {
165 			smb_config_setdomaininfo(dinfo.d_nbdomain,
166 			    dinfo.d_fqdomain, dinfo.d_forest, dinfo.d_guid);
167 			(void) smb_config_setstr(SMB_CI_KPASSWD_DOMAIN, "");
168 		}
169 	}
170 
171 	return (new_domain);
172 }
173 
174 /*
175  * smbd_locate_dc_start()
176  *
177  * Initialization of the thread that triggers the initial DC discovery
178  * when SMB daemon starts up.
179  * Returns 0 on success, an error number if thread creation fails.
180  */
181 int
182 smbd_locate_dc_start(void)
183 {
184 	pthread_attr_t tattr;
185 	int rc;
186 
187 	(void) pthread_attr_init(&tattr);
188 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
189 	rc = pthread_create(&smb_locate_dc_thr, &tattr, smbd_locate_dc_thread,
190 	    NULL);
191 	(void) pthread_attr_destroy(&tattr);
192 	return (rc);
193 }
194 
195 /*
196  * smbd_locate_dc_thread()
197  *
198  * If necessary, set up Netlogon credential chain and locate a
199  * domain controller in the given resource domain.
200  *
201  * The domain configuration will be updated upon a successful DC discovery.
202  */
203 /*ARGSUSED*/
204 static void *
205 smbd_locate_dc_thread(void *arg)
206 {
207 	char domain[MAXHOSTNAMELEN];
208 	smb_domain_t new_dinfo;
209 
210 	if (!smb_match_netlogon_seqnum()) {
211 		(void) smbd_set_netlogon_cred();
212 	} else {
213 		if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0) {
214 			(void) smb_getdomainname(domain, MAXHOSTNAMELEN);
215 			(void) utf8_strupr(domain);
216 		}
217 
218 		if (smb_locate_dc(domain, "", &new_dinfo))
219 			smb_config_setdomaininfo(new_dinfo.d_nbdomain,
220 			    new_dinfo.d_fqdomain, new_dinfo.d_forest,
221 			    new_dinfo.d_guid);
222 	}
223 
224 	return (NULL);
225 }
226 
227 
228 /*
229  * Retrieve the kpasswd server from krb5.conf.
230  *
231  * Initialization of the locate dc thread.
232  * Returns 0 on success, an error number if thread creation fails.
233  */
234 static int
235 smbd_get_kpasswd_srv(char *srv, size_t len)
236 {
237 	FILE *fp;
238 	static char buf[512];
239 	char *p;
240 
241 	*srv = '\0';
242 	p = getenv("KRB5_CONFIG");
243 	if (p == NULL || *p == '\0')
244 		p = "/etc/krb5/krb5.conf";
245 
246 	if ((fp = fopen(p, "r")) == NULL)
247 		return (-1);
248 
249 	while (fgets(buf, sizeof (buf), fp)) {
250 
251 		/* Weed out any comment text */
252 		(void) trim_whitespace(buf);
253 		if (*buf == '#')
254 			continue;
255 
256 		if ((p = strstr(buf, "kpasswd_server")) != NULL) {
257 			if ((p = strchr(p, '=')) != NULL) {
258 				(void) trim_whitespace(++p);
259 				(void) strlcpy(srv, p, len);
260 			}
261 			break;
262 		}
263 	}
264 
265 
266 	(void) fclose(fp);
267 	return ((*srv == '\0') ? -1 : 0);
268 }
269 
270 static uint32_t
271 smbd_join_workgroup(smb_joininfo_t *info)
272 {
273 	char nb_domain[SMB_PI_MAX_DOMAIN];
274 	smb_domain_t dinfo;
275 
276 	(void) smb_config_getstr(SMB_CI_DOMAIN_NAME, nb_domain,
277 	    sizeof (nb_domain));
278 
279 	smbd_set_secmode(SMB_SECMODE_WORKGRP);
280 
281 	bzero(&dinfo, sizeof (smb_domain_t));
282 	(void) strlcpy(dinfo.d_nbdomain, info->domain_name,
283 	    sizeof (dinfo.d_nbdomain));
284 	smb_config_setdomaininfo(dinfo.d_nbdomain, dinfo.d_fqdomain,
285 	    dinfo.d_forest, dinfo.d_guid);
286 
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_domain_t domain_info;
301 
302 	/*
303 	 * Ensure that any previous membership of this domain has
304 	 * been cleared from the environment before we start. This
305 	 * will ensure that we don't attempt a NETLOGON_SAMLOGON
306 	 * when attempting to find the PDC.
307 	 */
308 
309 	(void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE);
310 
311 	if (smb_auth_ntlm_hash(info->domain_passwd, passwd_hash)
312 	    != SMBAUTH_SUCCESS) {
313 		syslog(LOG_ERR, "smbd: could not compute ntlm hash for '%s'",
314 		    info->domain_username);
315 		return (NT_STATUS_INTERNAL_ERROR);
316 	}
317 
318 	smbrdr_ipc_set(info->domain_username, passwd_hash);
319 
320 	(void) smbd_get_kpasswd_srv(dc, sizeof (dc));
321 	/* info->domain_name could either be NetBIOS domain name or FQDN */
322 	if (smb_locate_dc(info->domain_name, dc, &domain_info)) {
323 
324 		status = mlsvc_join(&domain_info, info->domain_username,
325 		    info->domain_passwd);
326 
327 		if (status == NT_STATUS_SUCCESS) {
328 			smbd_set_secmode(SMB_SECMODE_DOMAIN);
329 			smb_config_setdomaininfo(domain_info.d_nbdomain,
330 			    domain_info.d_fqdomain, domain_info. d_forest,
331 			    domain_info.d_guid);
332 			smbrdr_ipc_commit();
333 			return (status);
334 		}
335 
336 		smbrdr_ipc_rollback();
337 		syslog(LOG_ERR, "smbd: failed joining %s (%s)",
338 		    info->domain_name, xlate_nt_status(status));
339 		return (status);
340 	}
341 
342 	smbrdr_ipc_rollback();
343 	syslog(LOG_ERR, "smbd: failed locating domain controller for %s",
344 	    info->domain_name);
345 	return (NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
346 }
347