xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c (revision 44bc9120699af80bb18366ca474cb2c618608ca9)
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 2013 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 <syslog.h>
39 
40 #include <smbsrv/libsmb.h>
41 #include <smbsrv/libsmbns.h>
42 #include <smbsrv/libmlsvc.h>
43 #include <smbsrv/ntaccess.h>
44 #include <smbsrv/smbinfo.h>
45 #include <libsmbrdr.h>
46 #include <lsalib.h>
47 #include <samlib.h>
48 #include <smbsrv/netrauth.h>
49 
50 extern int netr_open(char *, char *, mlsvc_handle_t *);
51 extern int netr_close(mlsvc_handle_t *);
52 extern DWORD netlogon_auth(char *, mlsvc_handle_t *, DWORD);
53 
54 static DWORD
55 mlsvc_join_rpc(smb_domainex_t *dxi,
56 	char *admin_user, char *admin_pw,
57 	char *machine_name, char *machine_pw);
58 static DWORD
59 mlsvc_join_noauth(smb_domainex_t *dxi,
60 	char *machine_name, char *machine_pw);
61 
62 
63 DWORD
64 mlsvc_netlogon(char *server, char *domain)
65 {
66 	mlsvc_handle_t netr_handle;
67 	DWORD status;
68 
69 	if (netr_open(server, domain, &netr_handle) == 0) {
70 		if ((status = netlogon_auth(server, &netr_handle,
71 		    NETR_FLG_INIT)) != NT_STATUS_SUCCESS)
72 			syslog(LOG_NOTICE, "Failed to establish NETLOGON "
73 			    "credential chain");
74 		(void) netr_close(&netr_handle);
75 	} else {
76 		syslog(LOG_NOTICE, "Failed to connect to %s "
77 		    "for domain %s", server, domain);
78 		status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
79 	}
80 
81 	return (status);
82 }
83 
84 /*
85  * Join the specified domain.  The method varies depending on whether
86  * we're using "secure join" (using an administrative account to join)
87  * or "unsecure join" (using a pre-created machine account).  In the
88  * latter case, the machine account is created "by hand" before this
89  * machine attempts to join, and we just change the password from the
90  * (weak) default password for a new machine account to a random one.
91  *
92  * Note that the caller has already done "DC discovery" and passes the
93  * domain info. in the first arg.
94  *
95  * Returns NT status codes.
96  */
97 DWORD
98 mlsvc_join(smb_domainex_t *dxi, char *admin_user, char *admin_pw)
99 {
100 	char machine_name[SMB_SAMACCT_MAXLEN];
101 	char machine_pw[NETR_MACHINE_ACCT_PASSWD_MAX];
102 	unsigned char passwd_hash[SMBAUTH_HASH_SZ];
103 	smb_domain_t *di = &dxi->d_primary;
104 	DWORD status;
105 	int rc;
106 
107 	/*
108 	 * Domain join support: AD (Kerberos+LDAP) or MS-RPC?
109 	 * Leave the AD code path disabled until it can be
110 	 * fixed up so that the SMB server is in complete
111 	 * control of which AD server we talk to.  See:
112 	 * NX 12427 (Re-enable Kerberos+LDAP with...)
113 	 */
114 	boolean_t ads_enabled = smb_config_get_ads_enable();
115 
116 	if (smb_getsamaccount(machine_name, sizeof (machine_name)) != 0)
117 		return (NT_STATUS_INTERNAL_ERROR);
118 
119 	(void) smb_gen_random_passwd(machine_pw, sizeof (machine_pw));
120 
121 	/*
122 	 * A non-null user means we do "secure join".
123 	 */
124 	if (admin_user != NULL && admin_user[0] != '\0') {
125 		/*
126 		 * Doing "secure join", so authenticate as the
127 		 * specified user (with admin. rights).
128 		 */
129 		(void) smb_auth_ntlm_hash(admin_pw, passwd_hash);
130 		smb_ipc_set(admin_user, passwd_hash);
131 
132 		/*
133 		 * If enabled, try to join using AD Services.
134 		 * The ADS code needs work.  Not enabled yet.
135 		 */
136 		status = NT_STATUS_UNSUCCESSFUL;
137 		if (ads_enabled) {
138 			smb_adjoin_status_t err;
139 			err = smb_ads_join(di->di_fqname,
140 			    admin_user, admin_pw, machine_pw);
141 			if (err != SMB_ADJOIN_SUCCESS) {
142 				smb_ads_join_errmsg(err);
143 			} else {
144 				status = NT_STATUS_SUCCESS;
145 			}
146 		}
147 
148 		/*
149 		 * If ADS was disabled or gave an error,
150 		 * fall-back and try to join using RPC.
151 		 */
152 		if (status != NT_STATUS_SUCCESS) {
153 			status = mlsvc_join_rpc(dxi,
154 			    admin_user, admin_pw,
155 			    machine_name, machine_pw);
156 		}
157 
158 	} else {
159 		/*
160 		 * Doing "Unsecure join" (pre-created account)
161 		 */
162 		bzero(passwd_hash, sizeof (passwd_hash));
163 		smb_ipc_set(MLSVC_ANON_USER, passwd_hash);
164 
165 		status = mlsvc_join_noauth(dxi, machine_name, machine_pw);
166 	}
167 
168 	if (status != NT_STATUS_SUCCESS)
169 		goto out;
170 
171 	/*
172 	 * Make sure we can authenticate using the
173 	 * (new, or updated) machine account.
174 	 */
175 	(void) smb_auth_ntlm_hash(machine_pw, passwd_hash);
176 	smb_ipc_set(machine_name, passwd_hash);
177 	rc = smbrdr_logon(dxi->d_dc, di->di_nbname, machine_name);
178 	if (rc != 0) {
179 		syslog(LOG_NOTICE, "Authenticate with "
180 		    "new/updated machine account: %s",
181 		    strerror(rc));
182 		status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
183 		goto out;
184 	}
185 
186 	/*
187 	 * Store the new machine account password.
188 	 */
189 	rc = smb_setdomainprops(NULL, dxi->d_dc, machine_pw);
190 	if (rc != 0) {
191 		syslog(LOG_NOTICE,
192 		    "Failed to save machine account password");
193 		status = NT_STATUS_INTERNAL_DB_ERROR;
194 		goto out;
195 	}
196 
197 	/*
198 	 * Update idmap config
199 	 */
200 	if (smb_config_set_idmap_domain(di->di_fqname) != 0)
201 		syslog(LOG_NOTICE, "Failed to set idmap domain name");
202 	if (smb_config_refresh_idmap() != 0)
203 		syslog(LOG_NOTICE, "Failed to refresh idmap service");
204 
205 	/*
206 	 * Note: The caller (smbd) saves the "secmode" and
207 	 * domain info (via smb_config_setdomaininfo) and
208 	 * and does smb_ipc_commit (or rollback).
209 	 */
210 	status = 0;
211 
212 out:
213 	/* Avoid leaving cleartext passwords around. */
214 	bzero(machine_pw, sizeof (machine_pw));
215 	bzero(passwd_hash, sizeof (passwd_hash));
216 
217 	return (status);
218 }
219 
220 static DWORD
221 mlsvc_join_rpc(smb_domainex_t *dxi,
222 	char *admin_user, char *admin_pw,
223 	char *machine_name,  char *machine_pw)
224 {
225 	mlsvc_handle_t samr_handle;
226 	mlsvc_handle_t domain_handle;
227 	mlsvc_handle_t user_handle;
228 	smb_account_t ainfo;
229 	char *server = dxi->d_dc;
230 	smb_domain_t *di = &dxi->d_primary;
231 	DWORD account_flags;
232 	DWORD rid;
233 	DWORD status;
234 	int rc;
235 
236 	/* Caller did smb_ipc_set() so we don't need the pw for now. */
237 	_NOTE(ARGUNUSED(admin_pw));
238 
239 	rc = samr_open(server, di->di_nbname, admin_user,
240 	    MAXIMUM_ALLOWED, &samr_handle);
241 	if (rc != 0) {
242 		syslog(LOG_NOTICE, "sam_connect to server %s failed", server);
243 		return (RPC_NT_SERVER_UNAVAILABLE);
244 	}
245 	/* have samr_handle */
246 
247 	status = samr_open_domain(&samr_handle, MAXIMUM_ALLOWED,
248 	    (struct samr_sid *)di->di_binsid, &domain_handle);
249 	if (status != NT_STATUS_SUCCESS)
250 		goto out_samr_handle;
251 	/* have domain_handle */
252 
253 	account_flags = SAMR_AF_WORKSTATION_TRUST_ACCOUNT;
254 	status = samr_create_user(&domain_handle, machine_name,
255 	    account_flags, &rid, &user_handle);
256 	if (status == NT_STATUS_USER_EXISTS) {
257 		status = samr_lookup_domain_names(&domain_handle,
258 		    machine_name, &ainfo);
259 		if (status != NT_STATUS_SUCCESS)
260 			goto out_domain_handle;
261 		status = samr_open_user(&domain_handle, MAXIMUM_ALLOWED,
262 		    ainfo.a_rid, &user_handle);
263 	}
264 	if (status != NT_STATUS_SUCCESS) {
265 		syslog(LOG_NOTICE,
266 		    "Create or open machine account: %s",
267 		    xlate_nt_status(status));
268 		goto out_domain_handle;
269 	}
270 
271 	/*
272 	 * The account exists, and we have user_handle open
273 	 * on that account.  Set the password and flags.
274 	 */
275 
276 	status = netr_set_user_password(&user_handle, machine_pw);
277 	if (status != NT_STATUS_SUCCESS) {
278 		syslog(LOG_NOTICE,
279 		    "Set machine account password: %s",
280 		    xlate_nt_status(status));
281 		goto out_user_handle;
282 	}
283 
284 	account_flags |= SAMR_AF_DONT_EXPIRE_PASSWD;
285 	status = netr_set_user_control(&user_handle, account_flags);
286 	if (status != NT_STATUS_SUCCESS) {
287 		syslog(LOG_NOTICE,
288 		    "Set machine account control flags: %s",
289 		    xlate_nt_status(status));
290 		goto out_user_handle;
291 	}
292 
293 out_user_handle:
294 	(void) samr_close_handle(&user_handle);
295 out_domain_handle:
296 	(void) samr_close_handle(&domain_handle);
297 out_samr_handle:
298 	(void) samr_close_handle(&samr_handle);
299 
300 	return (status);
301 }
302 
303 /*
304  * Doing "Unsecure join" (using a pre-created machine account).
305  * All we need to do is change the password from the default
306  * to a random string.
307  *
308  * Note: this is a work in progres.  Nexenta issue 11960
309  * (allow joining an AD domain using a pre-created computer account)
310  * It turns out that to change the machine account password,
311  * we need to use a different RPC call, performed over the
312  * NetLogon secure channel.  (See netr_server_password_set2)
313  */
314 static DWORD
315 mlsvc_join_noauth(smb_domainex_t *dxi,
316 	char *machine_name, char *machine_pw)
317 {
318 	char old_pw[SMB_SAMACCT_MAXLEN];
319 	DWORD status;
320 
321 	/*
322 	 * Compose the current (default) password for the
323 	 * pre-created machine account, which is just the
324 	 * account name in lower case, truncated to 14
325 	 * characters.
326 	 */
327 	if (smb_gethostname(old_pw, sizeof (old_pw), SMB_CASE_LOWER) != 0)
328 		return (NT_STATUS_INTERNAL_ERROR);
329 	old_pw[14] = '\0';
330 
331 	status = netr_change_password(dxi->d_dc, machine_name,
332 	    old_pw, machine_pw);
333 	if (status != NT_STATUS_SUCCESS) {
334 		syslog(LOG_NOTICE,
335 		    "Change machine account password: %s",
336 		    xlate_nt_status(status));
337 	}
338 	return (status);
339 }
340 
341 void
342 mlsvc_disconnect(const char *server)
343 {
344 	smbrdr_disconnect(server);
345 }
346