xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/samlib.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
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 /*
23  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
25  */
26 
27 /*
28  * This module provides the high level interface to the SAM RPC
29  * functions.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/isa_defs.h>
34 #include <sys/byteorder.h>
35 
36 #include <alloca.h>
37 
38 #include <smbsrv/libsmb.h>
39 #include <smbsrv/libmlsvc.h>
40 #include <smb/ntaccess.h>
41 #include <lsalib.h>
42 #include <samlib.h>
43 
44 #ifdef _LITTLE_ENDIAN
45 /* little-endian values on little-endian */
46 #define	htolel(x)	((uint32_t)(x))
47 #define	letohl(x)	((uint32_t)(x))
48 #else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
49 /* little-endian values on big-endian (swap) */
50 #define	letohl(x)	BSWAP_32(x)
51 #define	htolel(x)	BSWAP_32(x)
52 #endif	/* (BYTE_ORDER == LITTLE_ENDIAN) */
53 
54 /*
55  * Valid values for the OEM OWF password encryption.
56  */
57 #define	SAM_KEYLEN		16
58 
59 static void samr_fill_userpw(struct samr_user_password *, const char *);
60 static void samr_make_encrypted_password(
61 	struct samr_encr_passwd *epw,
62 	char *new_pw_clear,
63 	uint8_t *crypt_key);
64 
65 
66 /*
67  * Todo: Implement "unjoin" domain, which would use the
68  * sam_remove_trust_account code below.
69  */
70 
71 /*
72  * sam_remove_trust_account
73  *
74  * Attempt to remove the workstation trust account for this system.
75  * Administrator access is required to perform this operation.
76  *
77  * Returns NT status codes.
78  */
79 DWORD
80 sam_remove_trust_account(char *server, char *domain)
81 {
82 	char account_name[SMB_SAMACCT_MAXLEN];
83 
84 	if (smb_getsamaccount(account_name, SMB_SAMACCT_MAXLEN) != 0)
85 		return (NT_STATUS_INTERNAL_ERROR);
86 
87 	return (sam_delete_account(server, domain, account_name));
88 }
89 
90 
91 /*
92  * sam_delete_account
93  *
94  * Attempt to remove an account from the SAM database on the specified
95  * server.
96  *
97  * Returns NT status codes.
98  */
99 DWORD
100 sam_delete_account(char *server, char *domain_name, char *account_name)
101 {
102 	mlsvc_handle_t samr_handle;
103 	mlsvc_handle_t domain_handle;
104 	mlsvc_handle_t user_handle;
105 	smb_account_t ainfo;
106 	smb_sid_t *sid;
107 	DWORD access_mask;
108 	DWORD status;
109 	int rc;
110 	char user[SMB_USERNAME_MAXLEN];
111 
112 	smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);
113 
114 	rc = samr_open(server, domain_name, user, SAM_LOOKUP_INFORMATION,
115 	    &samr_handle);
116 	if (rc != 0)
117 		return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
118 
119 	sid = samr_lookup_domain(&samr_handle, domain_name);
120 	if (sid == NULL) {
121 		status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
122 		goto out_samr_hdl;
123 	}
124 
125 	status = samr_open_domain(&samr_handle, SAM_LOOKUP_INFORMATION,
126 	    (struct samr_sid *)sid, &domain_handle);
127 	if (status != NT_STATUS_SUCCESS)
128 		goto out_sid_ptr;
129 
130 	status = samr_lookup_domain_names(&domain_handle, account_name, &ainfo);
131 	if (status != NT_STATUS_SUCCESS)
132 		goto out_dom_hdl;
133 
134 	access_mask = STANDARD_RIGHTS_EXECUTE | DELETE;
135 	status = samr_open_user(&domain_handle, access_mask,
136 	    ainfo.a_rid, &user_handle);
137 	if (status != NT_STATUS_SUCCESS)
138 		goto out_dom_hdl;
139 
140 	status = samr_delete_user(&user_handle);
141 
142 	(void) samr_close_handle(&user_handle);
143 out_dom_hdl:
144 	(void) samr_close_handle(&domain_handle);
145 out_sid_ptr:
146 	free(sid);
147 out_samr_hdl:
148 	(void) samr_close_handle(&samr_handle);
149 
150 	return (status);
151 }
152 
153 
154 /*
155  * sam_lookup_name
156  *
157  * Lookup an account name in the SAM database on the specified domain
158  * controller. Provides the account RID on success.
159  *
160  * Returns NT status codes.
161  */
162 DWORD
163 sam_lookup_name(char *server, char *domain_name, char *account_name,
164     DWORD *rid_ret)
165 {
166 	mlsvc_handle_t samr_handle;
167 	mlsvc_handle_t domain_handle;
168 	smb_account_t ainfo;
169 	struct samr_sid *domain_sid;
170 	int rc;
171 	DWORD status;
172 	char user[SMB_USERNAME_MAXLEN];
173 
174 	smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);
175 
176 	*rid_ret = 0;
177 
178 	rc = samr_open(server, domain_name, user, SAM_LOOKUP_INFORMATION,
179 	    &samr_handle);
180 
181 	if (rc != 0)
182 		return (NT_STATUS_OPEN_FAILED);
183 
184 	domain_sid = (struct samr_sid *)samr_lookup_domain(&samr_handle,
185 	    domain_name);
186 	if (domain_sid == NULL) {
187 		(void) samr_close_handle(&samr_handle);
188 		return (NT_STATUS_NO_SUCH_DOMAIN);
189 	}
190 
191 	status = samr_open_domain(&samr_handle, SAM_LOOKUP_INFORMATION,
192 	    domain_sid, &domain_handle);
193 	if (status == NT_STATUS_SUCCESS) {
194 		status = samr_lookup_domain_names(&domain_handle,
195 		    account_name, &ainfo);
196 		if (status == NT_STATUS_SUCCESS)
197 			*rid_ret = ainfo.a_rid;
198 
199 		(void) samr_close_handle(&domain_handle);
200 	}
201 
202 	(void) samr_close_handle(&samr_handle);
203 	return (status);
204 }
205 
206 /*
207  * sam_get_local_domains
208  *
209  * Query a remote server to get the list of local domains that it
210  * supports.
211  *
212  * Returns NT status codes.
213  */
214 DWORD
215 sam_get_local_domains(char *server, char *domain_name)
216 {
217 	mlsvc_handle_t samr_handle;
218 	DWORD status;
219 	int rc;
220 	char user[SMB_USERNAME_MAXLEN];
221 
222 	smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);
223 
224 	rc = samr_open(server, domain_name, user, SAM_ENUM_LOCAL_DOMAIN,
225 	    &samr_handle);
226 	if (rc != 0)
227 		return (NT_STATUS_OPEN_FAILED);
228 
229 	status = samr_enum_local_domains(&samr_handle);
230 	(void) samr_close_handle(&samr_handle);
231 	return (status);
232 }
233 
234 /*
235  * Set the account control flags on some account for which we
236  * have already opened a SAM handle with appropriate rights,
237  * passed in here as sam_handle, along with the new flags.
238  */
239 DWORD
240 netr_set_user_control(
241 	mlsvc_handle_t *user_handle,
242 	DWORD UserAccountControl)
243 {
244 	struct samr_SetUserInfo16 info;
245 
246 	info.UserAccountControl = UserAccountControl;
247 	return (samr_set_user_info(user_handle, 16, &info));
248 }
249 
250 /*
251  * Set the password on some account, for which we have already
252  * opened a SAM handle with appropriate rights, passed in here
253  * as sam_handle, along with the new password as cleartext.
254  *
255  * This builds a struct SAMPR_USER_INTERNAL5_INFORMATION [MS-SAMR]
256  * containing the new password, encrypted with our session key.
257  */
258 DWORD
259 netr_set_user_password(
260 	mlsvc_handle_t *user_handle,
261 	char *new_pw_clear)
262 {
263 	unsigned char ssn_key[SMBAUTH_HASH_SZ];
264 	struct samr_SetUserInfo24 info;
265 
266 	if (ndr_rpc_get_ssnkey(user_handle, ssn_key, SMBAUTH_HASH_SZ))
267 		return (NT_STATUS_INTERNAL_ERROR);
268 
269 	(void) memset(&info, 0, sizeof (info));
270 	samr_make_encrypted_password(&info.encr_pw, new_pw_clear, ssn_key);
271 
272 	/* Rather not leave the session key around. */
273 	(void) memset(ssn_key, 0, sizeof (ssn_key));
274 
275 	return (samr_set_user_info(user_handle, 24, &info));
276 }
277 
278 /*
279  * Change a password like NetUserChangePassword(),
280  * but where we already know which AD server to use,
281  * so we don't request the domain name or search for
282  * an AD server for that domain here.
283  */
284 DWORD
285 netr_change_password(
286 	char *server,
287 	char *account,
288 	char *old_pw_clear,
289 	char *new_pw_clear)
290 {
291 	struct samr_encr_passwd epw;
292 	struct samr_encr_hash old_hash;
293 	uint8_t old_nt_hash[SAMR_PWHASH_LEN];
294 	uint8_t new_nt_hash[SAMR_PWHASH_LEN];
295 	mlsvc_handle_t handle;
296 	DWORD status;
297 
298 	/*
299 	 * Create an RPC handle to this server, bound to SAMR.
300 	 */
301 	status = ndr_rpc_bind(&handle, server, "", "", "SAMR");
302 	if (status != NT_STATUS_SUCCESS)
303 		return (status);
304 
305 	/*
306 	 * Encrypt the new p/w (plus random filler) with the
307 	 * old password, and send the old p/w encrypted with
308 	 * the new p/w hash to prove we know the old p/w.
309 	 * Details:  [MS-SAMR 3.1.5.10.3]
310 	 */
311 	(void) smb_auth_ntlm_hash(old_pw_clear, old_nt_hash);
312 	(void) smb_auth_ntlm_hash(new_pw_clear, new_nt_hash);
313 	samr_make_encrypted_password(&epw, new_pw_clear, old_nt_hash);
314 
315 	(void) smb_auth_DES(old_hash.data, SAMR_PWHASH_LEN,
316 	    new_nt_hash, 14, /* key */
317 	    old_nt_hash, SAMR_PWHASH_LEN);
318 
319 	/*
320 	 * Finally, ready to try the OtW call.
321 	 */
322 	status = samr_change_password(
323 	    &handle, server, account,
324 	    &epw, &old_hash);
325 
326 	/* Avoid leaving cleartext (or equivalent) around. */
327 	(void) memset(old_nt_hash, 0, sizeof (old_nt_hash));
328 	(void) memset(new_nt_hash, 0, sizeof (new_nt_hash));
329 
330 	ndr_rpc_unbind(&handle);
331 	return (status);
332 }
333 
334 /*
335  * Build an encrypted password, as used by samr_set_user_info
336  * and samr_change_password.  Note: This builds the unencrypted
337  * form in one union arm, and encrypts it in the other union arm.
338  */
339 void
340 samr_make_encrypted_password(
341 	struct samr_encr_passwd *epw,
342 	char *new_pw_clear,
343 	uint8_t *crypt_key)
344 {
345 	union {
346 		struct samr_user_password u;
347 		struct samr_encr_passwd e;
348 	} pwu;
349 
350 	samr_fill_userpw(&pwu.u, new_pw_clear);
351 
352 	(void) smb_auth_RC4(pwu.e.data, sizeof (pwu.e.data),
353 	    crypt_key, SAMR_PWHASH_LEN,
354 	    pwu.e.data, sizeof (pwu.e.data));
355 
356 	(void) memcpy(epw->data, pwu.e.data, sizeof (pwu.e.data));
357 	(void) memset(pwu.e.data, 0, sizeof (pwu.e.data));
358 }
359 
360 /*
361  * This fills in a samr_user_password (a.k.a. SAMPR_USER_PASSWORD
362  * in the MS Net API) which has the new password "right justified"
363  * in the buffer, and any space on the left filled with random junk
364  * to improve the quality of the encryption that is subsequently
365  * applied to this buffer before it goes over the wire.
366  */
367 static void
368 samr_fill_userpw(struct samr_user_password *upw, const char *new_pw)
369 {
370 	smb_wchar_t *pbuf;
371 	uint32_t pwlen_bytes;
372 	size_t pwlen_wchars;
373 
374 	/*
375 	 * First fill the whole buffer with the random junk.
376 	 * (Slightly less random when debugging:)
377 	 */
378 #ifdef DEBUG
379 	(void) memset(upw->Buffer, '*', sizeof (upw->Buffer));
380 #else
381 	randomize((char *)upw->Buffer, sizeof (upw->Buffer));
382 #endif
383 
384 	/*
385 	 * Now overwrite the last pwlen characters of
386 	 * that buffer with the password, and set the
387 	 * length field so the receiving end knows where
388 	 * the junk ends and the real password starts.
389 	 */
390 	pwlen_wchars = smb_wcequiv_strlen(new_pw) / 2;
391 	if (pwlen_wchars > SAMR_USER_PWLEN)
392 		pwlen_wchars = SAMR_USER_PWLEN;
393 	pwlen_bytes = pwlen_wchars * 2;
394 
395 	pbuf = &upw->Buffer[SAMR_USER_PWLEN - pwlen_wchars];
396 	(void) smb_mbstowcs(pbuf, new_pw, pwlen_wchars);
397 
398 	/* Yes, this is in Bytes, not wchars. */
399 	upw->Length = htolel(pwlen_bytes);
400 }
401