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