xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c (revision 8d0c3d29bb99f6521f2dc5058a7e4debebad7899)
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  */
25 
26 /*
27  * NETR SamLogon and SamLogoff RPC client functions.
28  */
29 
30 #include <stdio.h>
31 #include <strings.h>
32 #include <stdlib.h>
33 #include <time.h>
34 #include <alloca.h>
35 #include <unistd.h>
36 #include <netdb.h>
37 #include <thread.h>
38 
39 #include <smbsrv/libsmb.h>
40 #include <smbsrv/libmlrpc.h>
41 #include <smbsrv/libmlsvc.h>
42 #include <smbsrv/ndl/netlogon.ndl>
43 #include <smbsrv/netrauth.h>
44 #include <smbsrv/smbinfo.h>
45 #include <smbsrv/smb_token.h>
46 #include <mlsvc.h>
47 
48 #define	NETLOGON_ATTEMPTS	2
49 
50 static uint32_t netlogon_logon(smb_logon_t *, smb_token_t *);
51 static uint32_t netr_server_samlogon(mlsvc_handle_t *, netr_info_t *, char *,
52     smb_logon_t *, smb_token_t *);
53 static void netr_invalidate_chain(void);
54 static void netr_interactive_samlogon(netr_info_t *, smb_logon_t *,
55     struct netr_logon_info1 *);
56 static void netr_network_samlogon(ndr_heap_t *, netr_info_t *,
57     smb_logon_t *, struct netr_logon_info2 *);
58 static void netr_setup_identity(ndr_heap_t *, smb_logon_t *,
59     netr_logon_id_t *);
60 static boolean_t netr_isadmin(struct netr_validation_info3 *);
61 static uint32_t netr_setup_domain_groups(struct netr_validation_info3 *,
62     smb_ids_t *);
63 static uint32_t netr_setup_token_wingrps(struct netr_validation_info3 *,
64     smb_token_t *);
65 
66 /*
67  * Shared with netr_auth.c
68  */
69 extern netr_info_t netr_global_info;
70 
71 static mutex_t netlogon_mutex;
72 static cond_t netlogon_cv;
73 static boolean_t netlogon_busy = B_FALSE;
74 static boolean_t netlogon_abort = B_FALSE;
75 
76 /*
77  * Abort impending domain logon requests.
78  */
79 void
80 smb_logon_abort(void)
81 {
82 	(void) mutex_lock(&netlogon_mutex);
83 	if (netlogon_busy && !netlogon_abort)
84 		syslog(LOG_DEBUG, "logon abort");
85 	netlogon_abort = B_TRUE;
86 	(void) cond_broadcast(&netlogon_cv);
87 	(void) mutex_unlock(&netlogon_mutex);
88 }
89 
90 /*
91  * This is the entry point for authenticating domain users.
92  *
93  * If we are not going to attempt to authenticate the user,
94  * this function must return without updating the status.
95  *
96  * If the user is successfully authenticated, we build an
97  * access token and the status will be NT_STATUS_SUCCESS.
98  * Otherwise, the token contents are invalid.
99  */
100 void
101 smb_logon_domain(smb_logon_t *user_info, smb_token_t *token)
102 {
103 	uint32_t	status;
104 	int		i;
105 
106 	if (user_info->lg_secmode != SMB_SECMODE_DOMAIN)
107 		return;
108 
109 	if (user_info->lg_domain_type == SMB_DOMAIN_LOCAL)
110 		return;
111 
112 	for (i = 0; i < NETLOGON_ATTEMPTS; ++i) {
113 		(void) mutex_lock(&netlogon_mutex);
114 		while (netlogon_busy && !netlogon_abort)
115 			(void) cond_wait(&netlogon_cv, &netlogon_mutex);
116 
117 		if (netlogon_abort) {
118 			(void) mutex_unlock(&netlogon_mutex);
119 			user_info->lg_status = NT_STATUS_REQUEST_ABORTED;
120 			return;
121 		}
122 
123 		netlogon_busy = B_TRUE;
124 		(void) mutex_unlock(&netlogon_mutex);
125 
126 		status = netlogon_logon(user_info, token);
127 
128 		(void) mutex_lock(&netlogon_mutex);
129 		netlogon_busy = B_FALSE;
130 		if (netlogon_abort)
131 			status = NT_STATUS_REQUEST_ABORTED;
132 		(void) cond_signal(&netlogon_cv);
133 		(void) mutex_unlock(&netlogon_mutex);
134 
135 		if (status != NT_STATUS_CANT_ACCESS_DOMAIN_INFO)
136 			break;
137 	}
138 
139 	if (status != NT_STATUS_SUCCESS)
140 		syslog(LOG_INFO, "logon[%s\\%s]: %s", user_info->lg_e_domain,
141 		    user_info->lg_e_username, xlate_nt_status(status));
142 
143 	user_info->lg_status = status;
144 }
145 
146 static uint32_t
147 netlogon_logon(smb_logon_t *user_info, smb_token_t *token)
148 {
149 	char resource_domain[SMB_PI_MAX_DOMAIN];
150 	char server[NETBIOS_NAME_SZ * 2];
151 	mlsvc_handle_t netr_handle;
152 	smb_domainex_t di;
153 	uint32_t status;
154 	int retries = 0, server_changed = 0;
155 
156 	(void) smb_getdomainname(resource_domain, SMB_PI_MAX_DOMAIN);
157 
158 	if (!smb_domain_getinfo(&di))
159 		return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
160 
161 	if (mlsvc_ping(di.d_dc) < 0) {
162 		/*
163 		 * We had a session to the DC but it's not responding.
164 		 * So drop the credential chain.
165 		 */
166 		netr_invalidate_chain();
167 		return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
168 	}
169 
170 	do {
171 		if (netr_open(di.d_dc, di.d_primary.di_nbname, &netr_handle)
172 		    != 0)
173 			return (NT_STATUS_OPEN_FAILED);
174 
175 		if (di.d_dc && (*netr_global_info.server != '\0')) {
176 			(void) snprintf(server, sizeof (server),
177 			    "\\\\%s", di.d_dc);
178 			server_changed = strncasecmp(netr_global_info.server,
179 			    server, strlen(server));
180 		}
181 
182 		if (server_changed ||
183 		    (netr_global_info.flags & NETR_FLG_VALID) == 0 ||
184 		    !smb_match_netlogon_seqnum()) {
185 			status = netlogon_auth(di.d_dc, &netr_handle,
186 			    NETR_FLG_NULL);
187 
188 			if (status != 0) {
189 				(void) netr_close(&netr_handle);
190 				return (NT_STATUS_LOGON_FAILURE);
191 			}
192 
193 			netr_global_info.flags |= NETR_FLG_VALID;
194 		}
195 
196 		status = netr_server_samlogon(&netr_handle,
197 		    &netr_global_info, di.d_dc, user_info, token);
198 
199 		(void) netr_close(&netr_handle);
200 	} while (status == NT_STATUS_INSUFFICIENT_LOGON_INFO && retries++ < 3);
201 
202 	if (retries >= 3)
203 		status = NT_STATUS_LOGON_FAILURE;
204 
205 	return (status);
206 }
207 
208 static uint32_t
209 netr_setup_token(struct netr_validation_info3 *info3, smb_logon_t *user_info,
210     netr_info_t *netr_info, smb_token_t *token)
211 {
212 	char *username, *domain;
213 	unsigned char rc4key[SMBAUTH_SESSION_KEY_SZ];
214 	smb_sid_t *domsid;
215 	uint32_t status;
216 	char nbdomain[NETBIOS_NAME_SZ];
217 
218 	domsid = (smb_sid_t *)info3->LogonDomainId;
219 
220 	token->tkn_user.i_sid = smb_sid_splice(domsid, info3->UserId);
221 	if (token->tkn_user.i_sid == NULL)
222 		return (NT_STATUS_NO_MEMORY);
223 
224 	token->tkn_primary_grp.i_sid = smb_sid_splice(domsid,
225 	    info3->PrimaryGroupId);
226 	if (token->tkn_primary_grp.i_sid == NULL)
227 		return (NT_STATUS_NO_MEMORY);
228 
229 	username = (info3->EffectiveName.str)
230 	    ? (char *)info3->EffectiveName.str : user_info->lg_e_username;
231 
232 	if (info3->LogonDomainName.str) {
233 		domain = (char *)info3->LogonDomainName.str;
234 	} else if (*user_info->lg_e_domain != '\0') {
235 		domain = user_info->lg_e_domain;
236 	} else {
237 		(void) smb_getdomainname(nbdomain, sizeof (nbdomain));
238 		domain = nbdomain;
239 	}
240 
241 	if (username)
242 		token->tkn_account_name = strdup(username);
243 	if (domain)
244 		token->tkn_domain_name = strdup(domain);
245 
246 	if (token->tkn_account_name == NULL || token->tkn_domain_name == NULL)
247 		return (NT_STATUS_NO_MEMORY);
248 
249 	status = netr_setup_token_wingrps(info3, token);
250 	if (status != NT_STATUS_SUCCESS)
251 		return (status);
252 
253 	/*
254 	 * The UserSessionKey in NetrSamLogon RPC is obfuscated using the
255 	 * session key obtained in the NETLOGON credential chain.
256 	 * An 8 byte session key is zero extended to 16 bytes. This 16 byte
257 	 * key is the key to the RC4 algorithm. The RC4 byte stream is
258 	 * exclusively ored with the 16 byte UserSessionKey to recover
259 	 * the the clear form.
260 	 */
261 	if ((token->tkn_session_key = malloc(SMBAUTH_SESSION_KEY_SZ)) == NULL)
262 		return (NT_STATUS_NO_MEMORY);
263 	bzero(rc4key, SMBAUTH_SESSION_KEY_SZ);
264 	bcopy(netr_info->session_key.key, rc4key, netr_info->session_key.len);
265 	bcopy(info3->UserSessionKey.data, token->tkn_session_key,
266 	    SMBAUTH_SESSION_KEY_SZ);
267 	rand_hash((unsigned char *)token->tkn_session_key,
268 	    SMBAUTH_SESSION_KEY_SZ, rc4key, SMBAUTH_SESSION_KEY_SZ);
269 
270 	return (NT_STATUS_SUCCESS);
271 }
272 
273 /*
274  * netr_server_samlogon
275  *
276  * NetrServerSamLogon RPC: interactive or network. It is assumed that
277  * we have already authenticated with the PDC. If everything works,
278  * we build a user info structure and return it, where the caller will
279  * probably build an access token.
280  *
281  * Returns an NT status. There are numerous possibilities here.
282  * For example:
283  *	NT_STATUS_INVALID_INFO_CLASS
284  *	NT_STATUS_INVALID_PARAMETER
285  *	NT_STATUS_ACCESS_DENIED
286  *	NT_STATUS_PASSWORD_MUST_CHANGE
287  *	NT_STATUS_NO_SUCH_USER
288  *	NT_STATUS_WRONG_PASSWORD
289  *	NT_STATUS_LOGON_FAILURE
290  *	NT_STATUS_ACCOUNT_RESTRICTION
291  *	NT_STATUS_INVALID_LOGON_HOURS
292  *	NT_STATUS_INVALID_WORKSTATION
293  *	NT_STATUS_INTERNAL_ERROR
294  *	NT_STATUS_PASSWORD_EXPIRED
295  *	NT_STATUS_ACCOUNT_DISABLED
296  */
297 uint32_t
298 netr_server_samlogon(mlsvc_handle_t *netr_handle, netr_info_t *netr_info,
299     char *server, smb_logon_t *user_info, smb_token_t *token)
300 {
301 	struct netr_SamLogon arg;
302 	struct netr_authenticator auth;
303 	struct netr_authenticator ret_auth;
304 	struct netr_logon_info1 info1;
305 	struct netr_logon_info2 info2;
306 	struct netr_validation_info3 *info3;
307 	ndr_heap_t *heap;
308 	int opnum;
309 	int rc, len;
310 	uint32_t status;
311 
312 	bzero(&arg, sizeof (struct netr_SamLogon));
313 	opnum = NETR_OPNUM_SamLogon;
314 
315 	/*
316 	 * Should we get the server and hostname from netr_info?
317 	 */
318 
319 	len = strlen(server) + 4;
320 	arg.servername = ndr_rpc_malloc(netr_handle, len);
321 	arg.hostname = ndr_rpc_malloc(netr_handle, NETBIOS_NAME_SZ);
322 	if (arg.servername == NULL || arg.hostname == NULL) {
323 		ndr_rpc_release(netr_handle);
324 		return (NT_STATUS_INTERNAL_ERROR);
325 	}
326 
327 	(void) snprintf((char *)arg.servername, len, "\\\\%s", server);
328 	if (smb_getnetbiosname((char *)arg.hostname, NETBIOS_NAME_SZ) != 0) {
329 		ndr_rpc_release(netr_handle);
330 		return (NT_STATUS_INTERNAL_ERROR);
331 	}
332 
333 	rc = netr_setup_authenticator(netr_info, &auth, &ret_auth);
334 	if (rc != SMBAUTH_SUCCESS) {
335 		ndr_rpc_release(netr_handle);
336 		return (NT_STATUS_INTERNAL_ERROR);
337 	}
338 
339 	arg.auth = &auth;
340 	arg.ret_auth = &ret_auth;
341 	arg.validation_level = NETR_VALIDATION_LEVEL3;
342 	arg.logon_info.logon_level = user_info->lg_level;
343 	arg.logon_info.switch_value = user_info->lg_level;
344 
345 	heap = ndr_rpc_get_heap(netr_handle);
346 
347 	switch (user_info->lg_level) {
348 	case NETR_INTERACTIVE_LOGON:
349 		netr_setup_identity(heap, user_info, &info1.identity);
350 		netr_interactive_samlogon(netr_info, user_info, &info1);
351 		arg.logon_info.ru.info1 = &info1;
352 		break;
353 
354 	case NETR_NETWORK_LOGON:
355 		netr_setup_identity(heap, user_info, &info2.identity);
356 		netr_network_samlogon(heap, netr_info, user_info, &info2);
357 		arg.logon_info.ru.info2 = &info2;
358 		break;
359 
360 	default:
361 		ndr_rpc_release(netr_handle);
362 		return (NT_STATUS_INVALID_PARAMETER);
363 	}
364 
365 	rc = ndr_rpc_call(netr_handle, opnum, &arg);
366 	if (rc != 0) {
367 		bzero(netr_info, sizeof (netr_info_t));
368 		status = NT_STATUS_INVALID_PARAMETER;
369 	} else if (arg.status != 0) {
370 		status = NT_SC_VALUE(arg.status);
371 
372 		/*
373 		 * We need to validate the chain even though we have
374 		 * a non-zero status. If the status is ACCESS_DENIED
375 		 * this will trigger a new credential chain. However,
376 		 * a valid credential is returned with some status
377 		 * codes; for example, WRONG_PASSWORD.
378 		 */
379 		(void) netr_validate_chain(netr_info, arg.ret_auth);
380 	} else {
381 		status = netr_validate_chain(netr_info, arg.ret_auth);
382 		if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) {
383 			ndr_rpc_release(netr_handle);
384 			return (status);
385 		}
386 
387 		info3 = arg.ru.info3;
388 		status = netr_setup_token(info3, user_info, netr_info, token);
389 	}
390 
391 	ndr_rpc_release(netr_handle);
392 	return (status);
393 }
394 
395 /*
396  * netr_interactive_samlogon
397  *
398  * Set things up for an interactive SamLogon. Copy the NT and LM
399  * passwords to the logon structure and hash them with the session
400  * key.
401  */
402 static void
403 netr_interactive_samlogon(netr_info_t *netr_info, smb_logon_t *user_info,
404     struct netr_logon_info1 *info1)
405 {
406 	BYTE key[NETR_OWF_PASSWORD_SZ];
407 
408 	(void) memcpy(&info1->lm_owf_password,
409 	    user_info->lg_lm_password.val, sizeof (netr_owf_password_t));
410 
411 	(void) memcpy(&info1->nt_owf_password,
412 	    user_info->lg_nt_password.val, sizeof (netr_owf_password_t));
413 
414 	(void) memset(key, 0, NETR_OWF_PASSWORD_SZ);
415 	(void) memcpy(key, netr_info->session_key.key,
416 	    netr_info->session_key.len);
417 
418 	rand_hash((unsigned char *)&info1->lm_owf_password,
419 	    NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ);
420 
421 	rand_hash((unsigned char *)&info1->nt_owf_password,
422 	    NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ);
423 }
424 
425 /*
426  * netr_network_samlogon
427  *
428  * Set things up for a network SamLogon.  We provide a copy of the random
429  * challenge, that we sent to the client, to the domain controller.  This
430  * is the key that the client will have used to encrypt the NT and LM
431  * passwords.  Note that Windows 9x clients may not provide both passwords.
432  */
433 /*ARGSUSED*/
434 static void
435 netr_network_samlogon(ndr_heap_t *heap, netr_info_t *netr_info,
436     smb_logon_t *user_info, struct netr_logon_info2 *info2)
437 {
438 	uint32_t len;
439 
440 	bcopy(user_info->lg_challenge_key.val, info2->lm_challenge.data, 8);
441 
442 	if ((len = user_info->lg_nt_password.len) != 0) {
443 		ndr_heap_mkvcb(heap, user_info->lg_nt_password.val, len,
444 		    (ndr_vcbuf_t *)&info2->nt_response);
445 	} else {
446 		bzero(&info2->nt_response, sizeof (netr_vcbuf_t));
447 	}
448 
449 	if ((len = user_info->lg_lm_password.len) != 0) {
450 		ndr_heap_mkvcb(heap, user_info->lg_lm_password.val, len,
451 		    (ndr_vcbuf_t *)&info2->lm_response);
452 	} else {
453 		bzero(&info2->lm_response, sizeof (netr_vcbuf_t));
454 	}
455 }
456 
457 /*
458  * netr_setup_authenticator
459  *
460  * Set up the request and return authenticators. A new credential is
461  * generated from the session key, the current client credential and
462  * the current time, i.e.
463  *
464  *		NewCredential = Cred(SessionKey, OldCredential, time);
465  *
466  * The timestamp, which is used as a random seed, is stored in both
467  * the request and return authenticators.
468  *
469  * If any difficulties occur using the cryptographic framework, the
470  * function returns SMBAUTH_FAILURE.  Otherwise SMBAUTH_SUCCESS is
471  * returned.
472  */
473 int
474 netr_setup_authenticator(netr_info_t *netr_info,
475     struct netr_authenticator *auth, struct netr_authenticator *ret_auth)
476 {
477 	bzero(auth, sizeof (struct netr_authenticator));
478 
479 	netr_info->timestamp = time(0);
480 	auth->timestamp = netr_info->timestamp;
481 
482 	if (netr_gen_credentials(netr_info->session_key.key,
483 	    &netr_info->client_credential,
484 	    netr_info->timestamp,
485 	    (netr_cred_t *)&auth->credential) != SMBAUTH_SUCCESS)
486 		return (SMBAUTH_FAILURE);
487 
488 	if (ret_auth) {
489 		bzero(ret_auth, sizeof (struct netr_authenticator));
490 		ret_auth->timestamp = netr_info->timestamp;
491 	}
492 
493 	return (SMBAUTH_SUCCESS);
494 }
495 
496 /*
497  * Validate the returned credentials and update the credential chain.
498  * The server returns an updated client credential rather than a new
499  * server credential.  The server uses (timestamp + 1) when generating
500  * the credential.
501  *
502  * Generate the new seed for the credential chain. The new seed is
503  * formed by adding (timestamp + 1) to the current client credential.
504  * The only quirk is the uint32_t style addition.
505  *
506  * Returns NT_STATUS_INSUFFICIENT_LOGON_INFO if auth->credential is a
507  * NULL pointer. The Authenticator field of the SamLogon response packet
508  * sent by the Samba 3 PDC always return NULL pointer if the received
509  * SamLogon request is not immediately followed by the ServerReqChallenge
510  * and ServerAuthenticate2 requests.
511  *
512  * Returns NT_STATUS_SUCCESS if the server returned a valid credential.
513  * Otherwise we retirm NT_STATUS_UNSUCCESSFUL.
514  */
515 uint32_t
516 netr_validate_chain(netr_info_t *netr_info, struct netr_authenticator *auth)
517 {
518 	netr_cred_t cred;
519 	uint32_t result = NT_STATUS_SUCCESS;
520 	uint32_t *dwp;
521 
522 	++netr_info->timestamp;
523 
524 	if (netr_gen_credentials(netr_info->session_key.key,
525 	    &netr_info->client_credential,
526 	    netr_info->timestamp, &cred) != SMBAUTH_SUCCESS)
527 		return (NT_STATUS_INTERNAL_ERROR);
528 
529 	if (&auth->credential == 0) {
530 		/*
531 		 * If the validation fails, destroy the credential chain.
532 		 * This should trigger a new authentication chain.
533 		 */
534 		bzero(netr_info, sizeof (netr_info_t));
535 		return (NT_STATUS_INSUFFICIENT_LOGON_INFO);
536 	}
537 
538 	result = memcmp(&cred, &auth->credential, sizeof (netr_cred_t));
539 	if (result != 0) {
540 		/*
541 		 * If the validation fails, destroy the credential chain.
542 		 * This should trigger a new authentication chain.
543 		 */
544 		bzero(netr_info, sizeof (netr_info_t));
545 		result = NT_STATUS_UNSUCCESSFUL;
546 	} else {
547 		/*
548 		 * Otherwise generate the next step in the chain.
549 		 */
550 		/*LINTED E_BAD_PTR_CAST_ALIGN*/
551 		dwp = (uint32_t *)&netr_info->client_credential;
552 		dwp[0] += netr_info->timestamp;
553 
554 		netr_info->flags |= NETR_FLG_VALID;
555 	}
556 
557 	return (result);
558 }
559 
560 /*
561  * netr_invalidate_chain
562  *
563  * Mark the credential chain as invalid so that it will be recreated
564  * on the next attempt.
565  */
566 static void
567 netr_invalidate_chain(void)
568 {
569 	netr_global_info.flags &= ~NETR_FLG_VALID;
570 }
571 
572 /*
573  * netr_setup_identity
574  *
575  * Set up the client identity information. All of this information is
576  * specifically related to the client user and workstation attempting
577  * to access this system. It may not be in our primary domain.
578  *
579  * I don't know what logon_id is, it seems to be a unique identifier.
580  * Increment it before each use.
581  */
582 static void
583 netr_setup_identity(ndr_heap_t *heap, smb_logon_t *user_info,
584     netr_logon_id_t *identity)
585 {
586 	static mutex_t logon_id_mutex;
587 	static uint32_t logon_id;
588 
589 	(void) mutex_lock(&logon_id_mutex);
590 
591 	if (logon_id == 0)
592 		logon_id = 0xDCD0;
593 
594 	++logon_id;
595 	user_info->lg_logon_id = logon_id;
596 
597 	(void) mutex_unlock(&logon_id_mutex);
598 
599 	identity->parameter_control = 0;
600 	identity->logon_id.LowPart = logon_id;
601 	identity->logon_id.HighPart = 0;
602 
603 	ndr_heap_mkvcs(heap, user_info->lg_domain,
604 	    (ndr_vcstr_t *)&identity->domain_name);
605 
606 	ndr_heap_mkvcs(heap, user_info->lg_username,
607 	    (ndr_vcstr_t *)&identity->username);
608 
609 	/*
610 	 * Some systems prefix the client workstation name with \\.
611 	 * It doesn't seem to make any difference whether it's there
612 	 * or not.
613 	 */
614 	ndr_heap_mkvcs(heap, user_info->lg_workstation,
615 	    (ndr_vcstr_t *)&identity->workstation);
616 }
617 
618 /*
619  * Sets up domain, local and well-known group membership for the given
620  * token. Two assumptions have been made here:
621  *
622  *   a) token already contains a valid user SID so that group
623  *      memberships can be established
624  *
625  *   b) token belongs to a domain user
626  */
627 static uint32_t
628 netr_setup_token_wingrps(struct netr_validation_info3 *info3,
629     smb_token_t *token)
630 {
631 	smb_ids_t tkn_grps;
632 	uint32_t status;
633 
634 	tkn_grps.i_cnt = 0;
635 	tkn_grps.i_ids = NULL;
636 
637 	status = netr_setup_domain_groups(info3, &tkn_grps);
638 	if (status != NT_STATUS_SUCCESS) {
639 		smb_ids_free(&tkn_grps);
640 		return (status);
641 	}
642 
643 	status = smb_sam_usr_groups(token->tkn_user.i_sid, &tkn_grps);
644 	if (status != NT_STATUS_SUCCESS) {
645 		smb_ids_free(&tkn_grps);
646 		return (status);
647 	}
648 
649 	if (netr_isadmin(info3))
650 		token->tkn_flags |= SMB_ATF_ADMIN;
651 
652 	status = smb_wka_token_groups(token->tkn_flags, &tkn_grps);
653 	if (status == NT_STATUS_SUCCESS)
654 		token->tkn_win_grps = tkn_grps;
655 	else
656 		smb_ids_free(&tkn_grps);
657 
658 	return (status);
659 }
660 
661 /*
662  * Converts groups information in the returned structure by domain controller
663  * (info3) to an internal representation (gids)
664  */
665 static uint32_t
666 netr_setup_domain_groups(struct netr_validation_info3 *info3, smb_ids_t *gids)
667 {
668 	smb_sid_t *domain_sid;
669 	smb_id_t *ids;
670 	int i, total_cnt;
671 
672 	if ((i = info3->GroupCount) == 0)
673 		i++;
674 	i += info3->SidCount;
675 
676 	total_cnt = gids->i_cnt + i;
677 
678 	gids->i_ids = realloc(gids->i_ids, total_cnt * sizeof (smb_id_t));
679 	if (gids->i_ids == NULL)
680 		return (NT_STATUS_NO_MEMORY);
681 
682 	domain_sid = (smb_sid_t *)info3->LogonDomainId;
683 
684 	ids = gids->i_ids + gids->i_cnt;
685 	for (i = 0; i < info3->GroupCount; i++, gids->i_cnt++, ids++) {
686 		ids->i_sid = smb_sid_splice(domain_sid, info3->GroupIds[i].rid);
687 		if (ids->i_sid == NULL)
688 			return (NT_STATUS_NO_MEMORY);
689 
690 		ids->i_attrs = info3->GroupIds[i].attributes;
691 	}
692 
693 	if (info3->GroupCount == 0) {
694 		/*
695 		 * if there's no global group should add the primary group.
696 		 */
697 		ids->i_sid = smb_sid_splice(domain_sid, info3->PrimaryGroupId);
698 		if (ids->i_sid == NULL)
699 			return (NT_STATUS_NO_MEMORY);
700 
701 		ids->i_attrs = 0x7;
702 		gids->i_cnt++;
703 		ids++;
704 	}
705 
706 	/* Add the extra SIDs */
707 	for (i = 0; i < info3->SidCount; i++, gids->i_cnt++, ids++) {
708 		ids->i_sid = smb_sid_dup((smb_sid_t *)info3->ExtraSids[i].sid);
709 		if (ids->i_sid == NULL)
710 			return (NT_STATUS_NO_MEMORY);
711 
712 		ids->i_attrs = info3->ExtraSids[i].attributes;
713 	}
714 
715 	return (NT_STATUS_SUCCESS);
716 }
717 
718 /*
719  * Determines if the given user is the domain Administrator or a
720  * member of Domain Admins
721  */
722 static boolean_t
723 netr_isadmin(struct netr_validation_info3 *info3)
724 {
725 	smb_domain_t di;
726 	int i;
727 
728 	if (!smb_domain_lookup_sid((smb_sid_t *)info3->LogonDomainId, &di))
729 		return (B_FALSE);
730 
731 	if (di.di_type != SMB_DOMAIN_PRIMARY)
732 		return (B_FALSE);
733 
734 	if ((info3->UserId == DOMAIN_USER_RID_ADMIN) ||
735 	    (info3->PrimaryGroupId == DOMAIN_GROUP_RID_ADMINS))
736 		return (B_TRUE);
737 
738 	for (i = 0; i < info3->GroupCount; i++)
739 		if (info3->GroupIds[i].rid == DOMAIN_GROUP_RID_ADMINS)
740 			return (B_TRUE);
741 
742 	return (B_FALSE);
743 }
744