xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c (revision 495ee6847d0d3e288f47ba026d98a830e51cbc06)
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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
24  */
25 
26 /*
27  * Utility functions to support the RPC interface library.
28  */
29 
30 #include <stdio.h>
31 #include <stdarg.h>
32 #include <strings.h>
33 #include <unistd.h>
34 #include <netdb.h>
35 #include <stdlib.h>
36 #include <sys/time.h>
37 #include <sys/systm.h>
38 #include <note.h>
39 #include <syslog.h>
40 
41 #include <smbsrv/libsmb.h>
42 #include <smbsrv/libsmbns.h>
43 #include <smbsrv/libmlsvc.h>
44 #include <smbsrv/ntaccess.h>
45 #include <smbsrv/smbinfo.h>
46 #include <smbsrv/netrauth.h>
47 #include <libsmbrdr.h>
48 #include <lsalib.h>
49 #include <samlib.h>
50 #include <mlsvc.h>
51 
52 static DWORD
53 mlsvc_join_rpc(smb_domainex_t *dxi,
54 	char *admin_user, char *admin_pw,
55 	char *machine_name, char *machine_pw);
56 static DWORD
57 mlsvc_join_noauth(smb_domainex_t *dxi,
58 	char *machine_name, char *machine_pw);
59 
60 
61 DWORD
62 mlsvc_netlogon(char *server, char *domain)
63 {
64 	mlsvc_handle_t netr_handle;
65 	DWORD status;
66 
67 	status = netr_open(server, domain, &netr_handle);
68 	if (status != 0) {
69 		syslog(LOG_NOTICE, "Failed to connect to %s "
70 		    "for domain %s (%s)", server, domain,
71 		    xlate_nt_status(status));
72 		return (status);
73 	}
74 
75 	status = netlogon_auth(server, &netr_handle, NETR_FLG_INIT);
76 	if (status != NT_STATUS_SUCCESS) {
77 		syslog(LOG_NOTICE, "Failed to establish NETLOGON "
78 		    "credential chain with DC: %s (%s)", server,
79 		    xlate_nt_status(status));
80 		syslog(LOG_NOTICE, "The machine account information on the "
81 		    "domain controller does not match the local storage.");
82 		syslog(LOG_NOTICE, "To correct this, use 'smbadm join'");
83 	}
84 	(void) netr_close(&netr_handle);
85 
86 	return (status);
87 }
88 
89 /*
90  * Join the specified domain.  The method varies depending on whether
91  * we're using "secure join" (using an administrative account to join)
92  * or "unsecure join" (using a pre-created machine account).  In the
93  * latter case, the machine account is created "by hand" before this
94  * machine attempts to join, and we just change the password from the
95  * (weak) default password for a new machine account to a random one.
96  *
97  * Returns NT status codes.
98  */
99 void
100 mlsvc_join(smb_joininfo_t *info, smb_joinres_t *res)
101 {
102 	static unsigned char zero_hash[SMBAUTH_HASH_SZ];
103 	char machine_name[SMB_SAMACCT_MAXLEN];
104 	char machine_pw[NETR_MACHINE_ACCT_PASSWD_MAX];
105 	unsigned char passwd_hash[SMBAUTH_HASH_SZ];
106 	smb_domainex_t dxi;
107 	smb_domain_t *di = &dxi.d_primary;
108 	DWORD status;
109 	int rc;
110 
111 	bzero(&dxi, sizeof (dxi));
112 
113 	/*
114 	 * Domain join support: AD (Kerberos+LDAP) or MS-RPC?
115 	 */
116 	boolean_t ads_enabled = smb_config_get_ads_enable();
117 
118 	if (smb_getsamaccount(machine_name, sizeof (machine_name)) != 0) {
119 		res->status = NT_STATUS_INVALID_COMPUTER_NAME;
120 		return;
121 	}
122 
123 	(void) smb_gen_random_passwd(machine_pw, sizeof (machine_pw));
124 
125 	/*
126 	 * Ensure that any previous membership of this domain has
127 	 * been cleared from the environment before we start. This
128 	 * will ensure that we don't attempt a NETLOGON_SAMLOGON
129 	 * when attempting to find the PDC.
130 	 */
131 	(void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_FALSE);
132 
133 	if (info->domain_username[0] != '\0') {
134 		(void) smb_auth_ntlm_hash(info->domain_passwd, passwd_hash);
135 		smb_ipc_set(info->domain_username, passwd_hash);
136 	} else {
137 		smb_ipc_set(MLSVC_ANON_USER, zero_hash);
138 	}
139 
140 	/*
141 	 * Tentatively set the idmap domain to the one we're joining,
142 	 * so that the DC locator in idmap knows what to look for.
143 	 * Ditto the SMB server domain.
144 	 */
145 	if (smb_config_set_idmap_domain(info->domain_name) != 0)
146 		syslog(LOG_NOTICE, "Failed to set idmap domain name");
147 	if (smb_config_refresh_idmap() != 0)
148 		syslog(LOG_NOTICE, "Failed to refresh idmap service");
149 
150 	/* Clear DNS local (ADS) lookup cache. */
151 	smb_ads_refresh(B_FALSE);
152 
153 	/*
154 	 * Locate a DC for this domain.  Intentionally bypass the
155 	 * ddiscover service here because we're still joining.
156 	 * This also allows better reporting of any failures.
157 	 */
158 	status = smb_ads_lookup_msdcs(info->domain_name, &dxi.d_dci);
159 	if (status != NT_STATUS_SUCCESS) {
160 		syslog(LOG_ERR,
161 		    "smbd: failed to locate AD server for domain %s (%s)",
162 		    info->domain_name, xlate_nt_status(status));
163 		goto out;
164 	}
165 
166 	/*
167 	 * Found a DC.  Report what we found along with the return status
168 	 * so that admin will know which AD server we were talking to.
169 	 */
170 	(void) strlcpy(res->dc_name, dxi.d_dci.dc_name, MAXHOSTNAMELEN);
171 	syslog(LOG_INFO, "smbd: found AD server %s", dxi.d_dci.dc_name);
172 
173 	/*
174 	 * Domain discovery needs to authenticate with the AD server.
175 	 * Disconnect any existing connection with the domain controller
176 	 * to make sure we won't use any prior authentication context
177 	 * our redirector might have.
178 	 */
179 	mlsvc_disconnect(dxi.d_dci.dc_name);
180 
181 	/*
182 	 * Get the domain policy info (domain SID etc).
183 	 * Here too, bypass the smb_ddiscover_service.
184 	 */
185 	status = smb_ddiscover_main(info->domain_name, &dxi);
186 	if (status != NT_STATUS_SUCCESS) {
187 		syslog(LOG_ERR,
188 		    "smbd: failed getting domain info for %s (%s)",
189 		    info->domain_name, xlate_nt_status(status));
190 		goto out;
191 	}
192 	/*
193 	 * After a successful smbd_ddiscover_main() call
194 	 * we should call smb_domain_save() to update the
195 	 * data shown by smbadm list.  Do that at the end,
196 	 * only if all goes well with joining the domain.
197 	 */
198 
199 	/*
200 	 * Create or update our machine account on the DC.
201 	 * A non-null user means we do "secure join".
202 	 */
203 	if (info->domain_username[0] != '\0') {
204 		/*
205 		 * If enabled, try to join using AD Services.
206 		 */
207 		status = NT_STATUS_UNSUCCESSFUL;
208 		if (ads_enabled) {
209 			res->join_err = smb_ads_join(di->di_fqname,
210 			    info->domain_username, info->domain_passwd,
211 			    machine_pw);
212 			if (res->join_err == SMB_ADS_SUCCESS) {
213 				status = NT_STATUS_SUCCESS;
214 			}
215 		} else {
216 			syslog(LOG_DEBUG, "use_ads=false (do RPC join)");
217 
218 			/*
219 			 * If ADS was disabled, join using RPC.
220 			 */
221 			status = mlsvc_join_rpc(&dxi,
222 			    info->domain_username,
223 			    info->domain_passwd,
224 			    machine_name, machine_pw);
225 		}
226 
227 	} else {
228 		/*
229 		 * Doing "Unsecure join" (pre-created account)
230 		 */
231 		status = mlsvc_join_noauth(&dxi, machine_name, machine_pw);
232 	}
233 
234 	if (status != NT_STATUS_SUCCESS)
235 		goto out;
236 
237 	/*
238 	 * Make sure we can authenticate using the
239 	 * (new, or updated) machine account.
240 	 */
241 	(void) smb_auth_ntlm_hash(machine_pw, passwd_hash);
242 	smb_ipc_set(machine_name, passwd_hash);
243 	rc = smbrdr_logon(dxi.d_dci.dc_name, di->di_nbname, machine_name);
244 	if (rc != 0) {
245 		syslog(LOG_NOTICE, "Authenticate with "
246 		    "new/updated machine account: %s",
247 		    strerror(rc));
248 		res->join_err = SMB_ADJOIN_ERR_AUTH_NETLOGON;
249 		status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
250 		goto out;
251 	}
252 
253 	/*
254 	 * Store the new machine account password, and
255 	 * SMB_CI_DOMAIN_MEMB etc.
256 	 */
257 	rc = smb_setdomainprops(NULL, dxi.d_dci.dc_name, machine_pw);
258 	if (rc != 0) {
259 		syslog(LOG_NOTICE,
260 		    "Failed to save machine account password");
261 		res->join_err = SMB_ADJOIN_ERR_STORE_PROPS;
262 		status = NT_STATUS_INTERNAL_DB_ERROR;
263 		goto out;
264 	}
265 
266 	/*
267 	 * Update idmap config?
268 	 * Already set the domain_name above.
269 	 */
270 
271 	/*
272 	 * Save the SMB server config.  Sets: SMB_CI_DOMAIN_*
273 	 * Should unify SMB vs idmap configs.
274 	 */
275 	smb_config_setdomaininfo(di->di_nbname, di->di_fqname,
276 	    di->di_sid,
277 	    di->di_u.di_dns.ddi_forest,
278 	    di->di_u.di_dns.ddi_guid);
279 	smb_ipc_commit();
280 	smb_domain_save();
281 
282 	status = 0;
283 
284 out:
285 
286 	if (status != 0) {
287 		/*
288 		 * Undo the tentative domain settings.
289 		 */
290 		(void) smb_config_set_idmap_domain("");
291 		(void) smb_config_refresh_idmap();
292 		smb_ipc_rollback();
293 	}
294 
295 	/* Avoid leaving cleartext passwords around. */
296 	bzero(machine_pw, sizeof (machine_pw));
297 	bzero(passwd_hash, sizeof (passwd_hash));
298 
299 	res->status = status;
300 }
301 
302 static DWORD
303 mlsvc_join_rpc(smb_domainex_t *dxi,
304 	char *admin_user, char *admin_pw,
305 	char *machine_name,  char *machine_pw)
306 {
307 	mlsvc_handle_t samr_handle;
308 	mlsvc_handle_t domain_handle;
309 	mlsvc_handle_t user_handle;
310 	smb_account_t ainfo;
311 	char *server = dxi->d_dci.dc_name;
312 	smb_domain_t *di = &dxi->d_primary;
313 	DWORD account_flags;
314 	DWORD rid;
315 	DWORD status;
316 	int rc;
317 
318 	/* Caller did smb_ipc_set() so we don't need the pw for now. */
319 	_NOTE(ARGUNUSED(admin_pw));
320 
321 	rc = samr_open(server, di->di_nbname, admin_user,
322 	    MAXIMUM_ALLOWED, &samr_handle);
323 	if (rc != 0) {
324 		syslog(LOG_NOTICE, "sam_connect to server %s failed", server);
325 		return (RPC_NT_SERVER_UNAVAILABLE);
326 	}
327 	/* have samr_handle */
328 
329 	status = samr_open_domain(&samr_handle, MAXIMUM_ALLOWED,
330 	    (struct samr_sid *)di->di_binsid, &domain_handle);
331 	if (status != NT_STATUS_SUCCESS)
332 		goto out_samr_handle;
333 	/* have domain_handle */
334 
335 	account_flags = SAMR_AF_WORKSTATION_TRUST_ACCOUNT;
336 	status = samr_create_user(&domain_handle, machine_name,
337 	    account_flags, &rid, &user_handle);
338 	if (status == NT_STATUS_USER_EXISTS) {
339 		status = samr_lookup_domain_names(&domain_handle,
340 		    machine_name, &ainfo);
341 		if (status != NT_STATUS_SUCCESS)
342 			goto out_domain_handle;
343 		status = samr_open_user(&domain_handle, MAXIMUM_ALLOWED,
344 		    ainfo.a_rid, &user_handle);
345 	}
346 	if (status != NT_STATUS_SUCCESS) {
347 		syslog(LOG_NOTICE,
348 		    "smbd: failed to open machine account (%s)",
349 		    xlate_nt_status(status));
350 		goto out_domain_handle;
351 	}
352 
353 	/*
354 	 * The account exists, and we have user_handle open
355 	 * on that account.  Set the password and flags.
356 	 */
357 
358 	status = netr_set_user_password(&user_handle, machine_pw);
359 	if (status != NT_STATUS_SUCCESS) {
360 		syslog(LOG_NOTICE,
361 		    "smbd: failed to set machine account password (%s)",
362 		    xlate_nt_status(status));
363 		goto out_user_handle;
364 	}
365 
366 	account_flags |= SAMR_AF_DONT_EXPIRE_PASSWD;
367 	status = netr_set_user_control(&user_handle, account_flags);
368 	if (status != NT_STATUS_SUCCESS) {
369 		syslog(LOG_NOTICE,
370 		    "Set machine account control flags: %s",
371 		    xlate_nt_status(status));
372 		goto out_user_handle;
373 	}
374 
375 out_user_handle:
376 	(void) samr_close_handle(&user_handle);
377 out_domain_handle:
378 	(void) samr_close_handle(&domain_handle);
379 out_samr_handle:
380 	(void) samr_close_handle(&samr_handle);
381 
382 	return (status);
383 }
384 
385 /*
386  * Doing "Unsecure join" (using a pre-created machine account).
387  * All we need to do is change the password from the default
388  * to a random string.
389  *
390  * Note: this is a work in progres.  Nexenta issue 11960
391  * (allow joining an AD domain using a pre-created computer account)
392  * It turns out that to change the machine account password,
393  * we need to use a different RPC call, performed over the
394  * NetLogon secure channel.  (See netr_server_password_set2)
395  */
396 static DWORD
397 mlsvc_join_noauth(smb_domainex_t *dxi,
398 	char *machine_name, char *machine_pw)
399 {
400 	char old_pw[SMB_SAMACCT_MAXLEN];
401 	DWORD status;
402 
403 	/*
404 	 * Compose the current (default) password for the
405 	 * pre-created machine account, which is just the
406 	 * account name in lower case, truncated to 14
407 	 * characters.
408 	 */
409 	if (smb_gethostname(old_pw, sizeof (old_pw), SMB_CASE_LOWER) != 0)
410 		return (NT_STATUS_INTERNAL_ERROR);
411 	old_pw[14] = '\0';
412 
413 	status = netr_change_password(dxi->d_dci.dc_name, machine_name,
414 	    old_pw, machine_pw);
415 	if (status != NT_STATUS_SUCCESS) {
416 		syslog(LOG_NOTICE,
417 		    "Change machine account password: %s",
418 		    xlate_nt_status(status));
419 	}
420 	return (status);
421 }
422 
423 void
424 mlsvc_disconnect(const char *server)
425 {
426 	smbrdr_disconnect(server);
427 }
428