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
52static DWORD
53mlsvc_join_rpc(smb_domainex_t *dxi,
54	char *admin_user, char *admin_pw,
55	char *machine_name, char *machine_pw);
56static DWORD
57mlsvc_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 */
64DWORD
65mlsvc_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 */
92void
93mlsvc_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
277out:
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
295static DWORD
296mlsvc_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
368out_user_handle:
369	(void) samr_close_handle(&user_handle);
370out_domain_handle:
371	(void) samr_close_handle(&domain_handle);
372out_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 */
389static DWORD
390mlsvc_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
416void
417mlsvc_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 */
431boolean_t
432ndr_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 */
445boolean_t
446ndr_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
454int32_t
455ndr_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