netr_logon.c revision 6600:4e63bcd27ae9
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 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28/*
29 * NETR SamLogon and SamLogoff RPC client functions.
30 */
31
32#include <stdio.h>
33#include <strings.h>
34#include <stdlib.h>
35#include <time.h>
36#include <alloca.h>
37#include <unistd.h>
38#include <netdb.h>
39
40#include <smbsrv/libsmb.h>
41#include <smbsrv/libsmbrdr.h>
42#include <smbsrv/ndl/netlogon.ndl>
43#include <smbsrv/mlsvc_util.h>
44#include <smbsrv/mlsvc.h>
45#include <smbsrv/netrauth.h>
46#include <smbsrv/ntstatus.h>
47#include <smbsrv/smbinfo.h>
48#include <smbsrv/mlrpc.h>
49#include <smbsrv/smb_token.h>
50
51extern int netr_open(char *server, char *domain, mlsvc_handle_t *netr_handle);
52extern int netr_close(mlsvc_handle_t *netr_handle);
53extern DWORD netlogon_auth(char *server, mlsvc_handle_t *netr_handle,
54    DWORD flags);
55extern int netr_setup_authenticator(netr_info_t *, struct netr_authenticator *,
56    struct netr_authenticator *);
57extern DWORD netr_validate_chain(netr_info_t *, struct netr_authenticator *);
58
59static DWORD netr_server_samlogon(mlsvc_handle_t *, netr_info_t *, char *,
60    netr_client_t *, smb_userinfo_t *);
61static void netr_invalidate_chain(void);
62static void netr_interactive_samlogon(netr_info_t *, netr_client_t *,
63    struct netr_logon_info1 *);
64static void netr_network_samlogon(netr_info_t *, netr_client_t *,
65    netr_response_t *, netr_response_t *, struct netr_logon_info2 *);
66static void netr_setup_identity(mlrpc_heap_t *, netr_client_t *,
67    netr_logon_id_t *);
68
69/*
70 * Shared with netr_auth.c
71 */
72extern netr_info_t netr_global_info;
73
74/*
75 * netlogon_logon
76 *
77 * This is the entry point for authenticating a remote logon. The
78 * parameters here all refer to the remote user and workstation, i.e.
79 * the domain is the user's account domain, not our primary domain.
80 * In order to make it easy to track which domain is being used at
81 * each stage, and to reduce the number of things being pushed on the
82 * stack, the client information is bundled up in the clnt structure.
83 *
84 * If the user is successfully authenticated, an access token will be
85 * built and NT_STATUS_SUCCESS will be returned. Otherwise a non-zero
86 * NT status will be returned, in which case the token contents will
87 * be invalid.
88 */
89DWORD
90netlogon_logon(netr_client_t *clnt, smb_userinfo_t *user_info)
91{
92	char resource_domain[SMB_PI_MAX_DOMAIN];
93	mlsvc_handle_t netr_handle;
94	smb_ntdomain_t *di;
95	DWORD status;
96	int retries = 0;
97
98	(void) smb_getdomainname(resource_domain, SMB_PI_MAX_DOMAIN);
99
100	if ((di = smb_getdomaininfo(0)) == NULL)
101		return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
102
103	if ((mlsvc_echo(di->server)) < 0) {
104		/*
105		 * We had a session to the DC but it's not responding.
106		 * So drop the credential chain.
107		 */
108		netr_invalidate_chain();
109		return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
110	}
111
112	do {
113		status = netr_open(di->server, di->domain, &netr_handle);
114		if (status != 0)
115			return (status);
116
117		if ((netr_global_info.flags & NETR_FLG_VALID) == 0 ||
118		    !smb_match_netlogon_seqnum()) {
119			status = netlogon_auth(di->server, &netr_handle,
120			    NETR_FLG_NULL);
121
122			if (status != 0) {
123				(void) netr_close(&netr_handle);
124				return (NT_STATUS_LOGON_FAILURE);
125			}
126
127			netr_global_info.flags |= NETR_FLG_VALID;
128		}
129
130		status = netr_server_samlogon(&netr_handle,
131		    &netr_global_info, di->server, clnt, user_info);
132
133		(void) netr_close(&netr_handle);
134	} while (status == NT_STATUS_INSUFFICIENT_LOGON_INFO && retries++ < 3);
135
136	if (retries >= 3)
137		status = NT_STATUS_LOGON_FAILURE;
138
139	return (status);
140}
141
142static DWORD
143netr_setup_userinfo(struct netr_validation_info3 *info3,
144    smb_userinfo_t *user_info, netr_client_t *clnt, netr_info_t *netr_info)
145{
146	smb_sid_attrs_t *other_grps;
147	char *username, *domain;
148	int i, nbytes;
149	unsigned char rc4key[SMBAUTH_SESSION_KEY_SZ];
150
151	user_info->sid_name_use = SidTypeUser;
152	user_info->rid = info3->UserId;
153	user_info->primary_group_rid = info3->PrimaryGroupId;
154	user_info->domain_sid = smb_sid_dup((smb_sid_t *)info3->LogonDomainId);
155
156	if (user_info->domain_sid == NULL)
157		return (NT_STATUS_NO_MEMORY);
158
159	user_info->user_sid = smb_sid_splice(user_info->domain_sid,
160	    user_info->rid);
161	if (user_info->user_sid == NULL)
162		return (NT_STATUS_NO_MEMORY);
163
164	user_info->pgrp_sid = smb_sid_splice(user_info->domain_sid,
165	    user_info->primary_group_rid);
166	if (user_info->pgrp_sid == NULL)
167		return (NT_STATUS_NO_MEMORY);
168
169	username = (info3->EffectiveName.str)
170	    ? (char *)info3->EffectiveName.str : clnt->username;
171	domain = (info3->LogonDomainName.str)
172	    ? (char *)info3->LogonDomainName.str : clnt->domain;
173
174	if (username)
175		user_info->name = strdup(username);
176	if (domain)
177		user_info->domain_name = strdup(domain);
178
179	if (user_info->name == NULL || user_info->domain_name == NULL)
180		return (NT_STATUS_NO_MEMORY);
181
182	nbytes = info3->GroupCount * sizeof (smb_rid_attrs_t);
183	if (nbytes) {
184		if ((user_info->groups = malloc(nbytes)) != NULL) {
185			user_info->n_groups = info3->GroupCount;
186			(void) memcpy(user_info->groups,
187			    info3->GroupIds, nbytes);
188		} else {
189			return (NT_STATUS_NO_MEMORY);
190		}
191	}
192	nbytes = info3->SidCount * sizeof (smb_sid_attrs_t);
193	if (nbytes) {
194		if ((other_grps = malloc(nbytes)) != NULL) {
195			user_info->other_grps = other_grps;
196			for (i = 0; i < info3->SidCount; i++) {
197				other_grps[i].attrs =
198				    info3->ExtraSids[i].attributes;
199
200				other_grps[i].sid = smb_sid_dup(
201				    (smb_sid_t *)info3->ExtraSids[i].sid);
202
203				if (other_grps[i].sid == NULL)
204					break;
205			}
206			user_info->n_other_grps = i;
207		} else {
208			return (NT_STATUS_NO_MEMORY);
209		}
210	}
211	/*
212	 * The UserSessionKey in NetrSamLogon RPC is obfuscated using the
213	 * 8 byte session key obtained in the NETLOGON credential chain.
214	 * The 8 byte session key is zero extended to 16 bytes. This 16 byte
215	 * key is the key to the RC4 algorithm. The RC4 byte stream is
216	 * exclusively ored with the 16 byte UserSessionKey to recover
217	 * the the clear form.
218	 */
219	if ((user_info->session_key = malloc(SMBAUTH_SESSION_KEY_SZ)) == NULL)
220		return (NT_STATUS_NO_MEMORY);
221	bzero(rc4key, SMBAUTH_SESSION_KEY_SZ);
222	bcopy(netr_info->session_key, rc4key, 8);
223	bcopy(info3->UserSessionKey.data, user_info->session_key,
224	    SMBAUTH_SESSION_KEY_SZ);
225	rand_hash((unsigned char *)user_info->session_key,
226	    SMBAUTH_SESSION_KEY_SZ, rc4key, SMBAUTH_SESSION_KEY_SZ);
227	mlsvc_setadmin_user_info(user_info);
228	return (NT_STATUS_SUCCESS);
229}
230
231/*
232 * netr_server_samlogon
233 *
234 * NetrServerSamLogon RPC: interactive or network. It is assumed that
235 * we have already authenticated with the PDC. If everything works,
236 * we build a user info structure and return it, where the caller will
237 * probably build an access token.
238 *
239 * Returns an NT status. There are numerous possibilities here.
240 * For example:
241 *	NT_STATUS_INVALID_INFO_CLASS
242 *	NT_STATUS_INVALID_PARAMETER
243 *	NT_STATUS_ACCESS_DENIED
244 *	NT_STATUS_PASSWORD_MUST_CHANGE
245 *	NT_STATUS_NO_SUCH_USER
246 *	NT_STATUS_WRONG_PASSWORD
247 *	NT_STATUS_LOGON_FAILURE
248 *	NT_STATUS_ACCOUNT_RESTRICTION
249 *	NT_STATUS_INVALID_LOGON_HOURS
250 *	NT_STATUS_INVALID_WORKSTATION
251 *	NT_STATUS_INTERNAL_ERROR
252 *	NT_STATUS_PASSWORD_EXPIRED
253 *	NT_STATUS_ACCOUNT_DISABLED
254 */
255DWORD
256netr_server_samlogon(mlsvc_handle_t *netr_handle, netr_info_t *netr_info,
257    char *server, netr_client_t *clnt, smb_userinfo_t *user_info)
258{
259	struct netr_SamLogon arg;
260	struct netr_authenticator auth;
261	struct netr_authenticator ret_auth;
262	struct netr_logon_info1 info1;
263	struct netr_logon_info2 info2;
264	struct netr_validation_info3 *info3;
265	netr_response_t nt_rsp;
266	netr_response_t lm_rsp;
267	mlrpc_heapref_t heap;
268	int opnum;
269	int rc, len;
270	DWORD status;
271
272	bzero(&arg, sizeof (struct netr_SamLogon));
273	opnum = NETR_OPNUM_SamLogon;
274	(void) mlsvc_rpc_init(&heap);
275
276	/*
277	 * Should we get the server and hostname from netr_info?
278	 */
279	len = strlen(server) + 4;
280	arg.servername = alloca(len);
281	(void) snprintf((char *)arg.servername, len, "\\\\%s", server);
282
283	arg.hostname = alloca(MLSVC_DOMAIN_NAME_MAX);
284	rc = smb_gethostname((char *)arg.hostname, MLSVC_DOMAIN_NAME_MAX, 0);
285	if (rc != 0) {
286		mlrpc_heap_destroy(heap.heap);
287		return (NT_STATUS_INTERNAL_ERROR);
288	}
289
290	rc = netr_setup_authenticator(netr_info, &auth, &ret_auth);
291	if (rc != SMBAUTH_SUCCESS) {
292		mlrpc_heap_destroy(heap.heap);
293		return (NT_STATUS_INTERNAL_ERROR);
294	}
295
296	arg.auth = &auth;
297	arg.ret_auth = &ret_auth;
298	arg.validation_level = NETR_VALIDATION_LEVEL3;
299	arg.logon_info.logon_level = clnt->logon_level;
300	arg.logon_info.switch_value = clnt->logon_level;
301
302	switch (clnt->logon_level) {
303	case NETR_INTERACTIVE_LOGON:
304		netr_setup_identity(heap.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.heap, clnt, &info2.identity);
311		netr_network_samlogon(netr_info, clnt, &nt_rsp, &lm_rsp,
312		    &info2);
313		arg.logon_info.ru.info2 = &info2;
314		break;
315
316	default:
317		mlrpc_heap_destroy(heap.heap);
318		return (NT_STATUS_INVALID_PARAMETER);
319	}
320
321	rc = mlsvc_rpc_call(netr_handle->context, opnum, &arg, &heap);
322	if (rc != 0) {
323		bzero(netr_info, sizeof (netr_info_t));
324		status = NT_STATUS_INVALID_PARAMETER;
325	} else if (arg.status != 0) {
326		status = NT_SC_VALUE(arg.status);
327
328		/*
329		 * We need to validate the chain even though we have
330		 * a non-zero status. If the status is ACCESS_DENIED
331		 * this will trigger a new credential chain. However,
332		 * a valid credential is returned with some status
333		 * codes; for example, WRONG_PASSWORD.
334		 */
335		(void) netr_validate_chain(netr_info, arg.ret_auth);
336	} else {
337		status = netr_validate_chain(netr_info, arg.ret_auth);
338		if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) {
339			mlsvc_rpc_free(netr_handle->context, &heap);
340			return (status);
341		}
342
343		info3 = arg.ru.info3;
344		status = netr_setup_userinfo(info3, user_info, clnt, netr_info);
345	}
346
347	mlsvc_rpc_free(netr_handle->context, &heap);
348	return (status);
349}
350
351/*
352 * netr_interactive_samlogon
353 *
354 * Set things up for an interactive SamLogon. Copy the NT and LM
355 * passwords to the logon structure and hash them with the session
356 * key.
357 */
358static void
359netr_interactive_samlogon(netr_info_t *netr_info, netr_client_t *clnt,
360    struct netr_logon_info1 *info1)
361{
362	BYTE key[NETR_OWF_PASSWORD_SZ];
363
364	(void) memcpy(&info1->lm_owf_password,
365	    clnt->lm_password.lm_password_val, sizeof (netr_owf_password_t));
366
367	(void) memcpy(&info1->nt_owf_password,
368	    clnt->nt_password.nt_password_val, sizeof (netr_owf_password_t));
369
370	(void) memset(key, 0, NETR_OWF_PASSWORD_SZ);
371	(void) memcpy(key, netr_info->session_key, NETR_SESSION_KEY_SZ);
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*/
389static void
390netr_network_samlogon(netr_info_t *netr_info, netr_client_t *clnt,
391    netr_response_t *ntr, netr_response_t *lmr, struct netr_logon_info2 *info2)
392{
393	bcopy(clnt->challenge_key.challenge_key_val, info2->lm_challenge.data,
394	    8);
395
396	if (clnt->nt_password.nt_password_len == NETR_CR_PASSWORD_SIZE) {
397		ntr->length = NETR_CR_PASSWORD_SIZE;
398		ntr->start = 0;
399		ntr->max_length = NETR_CR_PASSWORD_SIZE;
400		bcopy(clnt->nt_password.nt_password_val, ntr->data,
401		    NETR_CR_PASSWORD_SIZE);
402
403		info2->nt_response.length = NETR_CR_PASSWORD_SIZE;
404		info2->nt_response.max_length = NETR_CR_PASSWORD_SIZE;
405		info2->nt_response.data = ntr;
406	} else {
407		info2->nt_response.length = 0;
408		info2->nt_response.max_length = 0;
409		info2->nt_response.data = 0;
410	}
411
412	if (clnt->lm_password.lm_password_len == NETR_CR_PASSWORD_SIZE) {
413		lmr->length = NETR_CR_PASSWORD_SIZE;
414		lmr->start = 0;
415		lmr->max_length = NETR_CR_PASSWORD_SIZE;
416		bcopy(clnt->lm_password.lm_password_val, lmr->data,
417		    NETR_CR_PASSWORD_SIZE);
418
419		info2->lm_response.length = NETR_CR_PASSWORD_SIZE;
420		info2->lm_response.max_length = NETR_CR_PASSWORD_SIZE;
421		info2->lm_response.data = lmr;
422	} else {
423		info2->lm_response.length = 0;
424		info2->lm_response.max_length = 0;
425		info2->lm_response.data = 0;
426	}
427}
428
429/*
430 * netr_setup_authenticator
431 *
432 * Set up the request and return authenticators. A new credential is
433 * generated from the session key, the current client credential and
434 * the current time, i.e.
435 *
436 *		NewCredential = Cred(SessionKey, OldCredential, time);
437 *
438 * The timestamp, which is used as a random seed, is stored in both
439 * the request and return authenticators.
440 *
441 * If any difficulties occur using the cryptographic framework, the
442 * function returns SMBAUTH_FAILURE.  Otherwise SMBAUTH_SUCCESS is
443 * returned.
444 */
445int
446netr_setup_authenticator(netr_info_t *netr_info,
447    struct netr_authenticator *auth, struct netr_authenticator *ret_auth)
448{
449	bzero(auth, sizeof (struct netr_authenticator));
450
451	netr_info->timestamp = time(0);
452	auth->timestamp = netr_info->timestamp;
453
454	if (netr_gen_credentials(netr_info->session_key,
455	    &netr_info->client_credential,
456	    netr_info->timestamp,
457	    (netr_cred_t *)&auth->credential) != SMBAUTH_SUCCESS)
458		return (SMBAUTH_FAILURE);
459
460	if (ret_auth) {
461		bzero(ret_auth, sizeof (struct netr_authenticator));
462		ret_auth->timestamp = netr_info->timestamp;
463	}
464
465	return (SMBAUTH_SUCCESS);
466}
467
468/*
469 * Validate the returned credentials and update the credential chain.
470 * The server returns an updated client credential rather than a new
471 * server credential.  The server uses (timestamp + 1) when generating
472 * the credential.
473 *
474 * Generate the new seed for the credential chain. The new seed is
475 * formed by adding (timestamp + 1) to the current client credential.
476 * The only quirk is the DWORD style addition.
477 *
478 * Returns NT_STATUS_INSUFFICIENT_LOGON_INFO if auth->credential is a
479 * NULL pointer. The Authenticator field of the SamLogon response packet
480 * sent by the Samba 3 PDC always return NULL pointer if the received
481 * SamLogon request is not immediately followed by the ServerReqChallenge
482 * and ServerAuthenticate2 requests.
483 *
484 * Returns NT_STATUS_SUCCESS if the server returned a valid credential.
485 * Otherwise we retirm NT_STATUS_UNSUCCESSFUL.
486 */
487DWORD
488netr_validate_chain(netr_info_t *netr_info, struct netr_authenticator *auth)
489{
490	netr_cred_t cred;
491	DWORD result = NT_STATUS_SUCCESS;
492	DWORD *dwp;
493
494	++netr_info->timestamp;
495
496	if (netr_gen_credentials(netr_info->session_key,
497	    &netr_info->client_credential,
498	    netr_info->timestamp, &cred) != SMBAUTH_SUCCESS)
499		return (NT_STATUS_INTERNAL_ERROR);
500
501	if (&auth->credential == 0) {
502		/*
503		 * If the validation fails, destroy the credential chain.
504		 * This should trigger a new authentication chain.
505		 */
506		bzero(netr_info, sizeof (netr_info_t));
507		return (NT_STATUS_INSUFFICIENT_LOGON_INFO);
508	}
509
510	result = memcmp(&cred, &auth->credential, sizeof (netr_cred_t));
511	if (result != 0) {
512		/*
513		 * If the validation fails, destroy the credential chain.
514		 * This should trigger a new authentication chain.
515		 */
516		bzero(netr_info, sizeof (netr_info_t));
517		result = NT_STATUS_UNSUCCESSFUL;
518	} else {
519		/*
520		 * Otherwise generate the next step in the chain.
521		 */
522		/*LINTED E_BAD_PTR_CAST_ALIGN*/
523		dwp = (DWORD *)&netr_info->client_credential;
524		dwp[0] += netr_info->timestamp;
525
526		netr_info->flags |= NETR_FLG_VALID;
527	}
528
529	return (result);
530}
531
532/*
533 * netr_invalidate_chain
534 *
535 * Mark the credential chain as invalid so that it will be recreated
536 * on the next attempt.
537 */
538static void
539netr_invalidate_chain(void)
540{
541	netr_global_info.flags &= ~NETR_FLG_VALID;
542}
543
544/*
545 * netr_setup_identity
546 *
547 * Set up the client identity information. All of this information is
548 * specifically related to the client user and workstation attempting
549 * to access this system. It may not be in our primary domain.
550 *
551 * I don't know what logon_id is, it seems to be a unique identifier.
552 * Increment it before each use.
553 */
554static void
555netr_setup_identity(mlrpc_heap_t *heap, netr_client_t *clnt,
556    netr_logon_id_t *identity)
557{
558	static DWORD logon_id;
559
560	if (logon_id == 0)
561		logon_id = 0xDCD0;
562
563	++logon_id;
564	clnt->logon_id = logon_id;
565
566	identity->parameter_control = 0;
567	identity->logon_id.LowPart = logon_id;
568	identity->logon_id.HighPart = 0;
569
570	mlrpc_heap_mkvcs(heap, clnt->domain,
571	    (mlrpc_vcbuf_t *)&identity->domain_name);
572
573	mlrpc_heap_mkvcs(heap, clnt->username,
574	    (mlrpc_vcbuf_t *)&identity->username);
575
576	/*
577	 * Some systems prefix the client workstation name with \\.
578	 * It doesn't seem to make any difference whether it's there
579	 * or not.
580	 */
581	mlrpc_heap_mkvcs(heap, clnt->workstation,
582	    (mlrpc_vcbuf_t *)&identity->workstation);
583}
584