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