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