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