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