xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c (revision da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0)
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 2007 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/ndl/netlogon.ndl>
42 #include <smbsrv/mlsvc_util.h>
43 #include <smbsrv/mlsvc.h>
44 #include <smbsrv/netrauth.h>
45 #include <smbsrv/ntstatus.h>
46 #include <smbsrv/smbinfo.h>
47 #include <smbsrv/mlrpc.h>
48 #include <smbsrv/smb_token.h>
49 
50 extern int netr_open(char *server, char *domain, mlsvc_handle_t *netr_handle);
51 extern int netr_close(mlsvc_handle_t *netr_handle);
52 extern DWORD netlogon_auth(char *server, mlsvc_handle_t *netr_handle,
53     DWORD flags);
54 extern int netr_setup_authenticator(netr_info_t *, struct netr_authenticator *,
55     struct netr_authenticator *);
56 extern DWORD netr_validate_chain(netr_info_t *, struct netr_authenticator *);
57 
58 static DWORD netr_server_samlogon(mlsvc_handle_t *, netr_info_t *, char *,
59     netr_client_t *, smb_userinfo_t *);
60 static void netr_invalidate_chain(void);
61 static void netr_interactive_samlogon(netr_info_t *, netr_client_t *,
62     struct netr_logon_info1 *);
63 static void netr_network_samlogon(netr_info_t *, netr_client_t *,
64     netr_response_t *, netr_response_t *, struct netr_logon_info2 *);
65 static void netr_setup_identity(mlrpc_heap_t *, netr_client_t *,
66     netr_logon_id_t *);
67 
68 /*
69  * Shared with netr_auth.c
70  */
71 extern netr_info_t netr_global_info;
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 DWORD
89 netlogon_logon(netr_client_t *clnt, smb_userinfo_t *user_info)
90 {
91 	char resource_domain[SMB_PI_MAX_DOMAIN];
92 	mlsvc_handle_t netr_handle;
93 	smb_ntdomain_t *di;
94 	DWORD status;
95 	int retries = 0;
96 
97 	smb_config_rdlock();
98 	(void) strlcpy(resource_domain, smb_config_getstr(SMB_CI_DOMAIN_NAME),
99 	    sizeof (resource_domain));
100 	smb_config_unlock();
101 
102 	/*
103 	 * If the SMB info cache is not valid,
104 	 * try to locate a domain controller.
105 	 */
106 	if ((di = smb_getdomaininfo(0)) == NULL) {
107 		(void) mlsvc_locate_domain_controller(resource_domain);
108 
109 		if ((di = smb_getdomaininfo(0)) == NULL)
110 			return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
111 	}
112 
113 	if ((mlsvc_echo(di->server)) < 0) {
114 		/*
115 		 * We had a session to the DC but it's not responding.
116 		 * So drop the credential chain and find another DC.
117 		 */
118 		netr_invalidate_chain();
119 		(void) mlsvc_locate_domain_controller(resource_domain);
120 
121 		if ((di = smb_getdomaininfo(0)) == NULL)
122 			return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
123 	}
124 
125 	do {
126 		status = netr_open(di->server, di->domain, &netr_handle);
127 		if (status != 0)
128 			return (status);
129 
130 		if ((netr_global_info.flags & NETR_FLG_VALID) == 0) {
131 			status = netlogon_auth(di->server, &netr_handle,
132 			    NETR_FLG_NULL);
133 
134 			if (status != 0) {
135 				(void) netr_close(&netr_handle);
136 				return (NT_STATUS_LOGON_FAILURE);
137 			}
138 
139 			netr_global_info.flags |= NETR_FLG_VALID;
140 		}
141 
142 		status = netr_server_samlogon(&netr_handle,
143 		    &netr_global_info, di->server, clnt, user_info);
144 
145 		(void) netr_close(&netr_handle);
146 	} while (status == NT_STATUS_INSUFFICIENT_LOGON_INFO && retries++ < 3);
147 
148 	if (retries >= 3)
149 		status = NT_STATUS_LOGON_FAILURE;
150 
151 	return (status);
152 }
153 
154 static DWORD
155 netr_setup_userinfo(struct netr_validation_info3 *info3,
156     smb_userinfo_t *user_info, netr_client_t *clnt)
157 {
158 	smb_sid_attrs_t *other_grps;
159 	char *username, *domain;
160 	int i, nbytes;
161 
162 	user_info->sid_name_use = SidTypeUser;
163 	user_info->rid = info3->UserId;
164 	user_info->primary_group_rid = info3->PrimaryGroupId;
165 	user_info->domain_sid = nt_sid_dup((nt_sid_t *)info3->LogonDomainId);
166 
167 	if (user_info->domain_sid == NULL)
168 		return (NT_STATUS_NO_MEMORY);
169 
170 	user_info->user_sid = nt_sid_splice(user_info->domain_sid,
171 	    user_info->rid);
172 	if (user_info->user_sid == NULL)
173 		return (NT_STATUS_NO_MEMORY);
174 
175 	user_info->pgrp_sid = nt_sid_splice(user_info->domain_sid,
176 	    user_info->primary_group_rid);
177 	if (user_info->pgrp_sid == NULL)
178 		return (NT_STATUS_NO_MEMORY);
179 
180 	username = (info3->EffectiveName.str)
181 	    ? (char *)info3->EffectiveName.str : clnt->username;
182 	domain = (info3->LogonDomainName.str)
183 	    ? (char *)info3->LogonDomainName.str : clnt->domain;
184 
185 	if (username)
186 		user_info->name = strdup(username);
187 	if (domain)
188 		user_info->domain_name = strdup(domain);
189 
190 	if (user_info->name == NULL || user_info->domain_name == NULL)
191 		return (NT_STATUS_NO_MEMORY);
192 
193 	nbytes = info3->GroupCount * sizeof (smb_rid_attrs_t);
194 	if (nbytes) {
195 		if ((user_info->groups = malloc(nbytes)) != NULL) {
196 			user_info->n_groups = info3->GroupCount;
197 			(void) memcpy(user_info->groups,
198 			    info3->GroupIds, nbytes);
199 		} else {
200 			return (NT_STATUS_NO_MEMORY);
201 		}
202 	}
203 
204 	nbytes = info3->SidCount * sizeof (smb_sid_attrs_t);
205 	if (nbytes) {
206 		if ((other_grps = malloc(nbytes)) != NULL) {
207 			user_info->other_grps = other_grps;
208 			for (i = 0; i < info3->SidCount; i++) {
209 				other_grps[i].attrs =
210 				    info3->ExtraSids[i].attributes;
211 
212 				other_grps[i].sid = nt_sid_dup(
213 				    (nt_sid_t *)info3->ExtraSids[i].sid);
214 
215 				if (other_grps[i].sid == NULL)
216 					break;
217 			}
218 			user_info->n_other_grps = i;
219 		} else {
220 			return (NT_STATUS_NO_MEMORY);
221 		}
222 	}
223 
224 	mlsvc_setadmin_user_info(user_info);
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 DWORD
253 netr_server_samlogon(mlsvc_handle_t *netr_handle, netr_info_t *netr_info,
254     char *server, netr_client_t *clnt, smb_userinfo_t *user_info)
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 	netr_response_t nt_rsp;
263 	netr_response_t lm_rsp;
264 	mlrpc_heapref_t heap;
265 	int opnum;
266 	int rc, len;
267 	DWORD status;
268 
269 	bzero(&arg, sizeof (struct netr_SamLogon));
270 	opnum = NETR_OPNUM_SamLogon;
271 	(void) mlsvc_rpc_init(&heap);
272 
273 	/*
274 	 * Should we get the server and hostname from netr_info?
275 	 */
276 	len = strlen(server) + 4;
277 	arg.servername = alloca(len);
278 	(void) snprintf((char *)arg.servername, len, "\\\\%s", server);
279 
280 	arg.hostname = alloca(MLSVC_DOMAIN_NAME_MAX);
281 	rc = smb_gethostname((char *)arg.hostname, MLSVC_DOMAIN_NAME_MAX, 0);
282 	if (rc != 0) {
283 		mlrpc_heap_destroy(heap.heap);
284 		return (NT_STATUS_INTERNAL_ERROR);
285 	}
286 
287 	rc = netr_setup_authenticator(netr_info, &auth, &ret_auth);
288 	if (rc != SMBAUTH_SUCCESS) {
289 		mlrpc_heap_destroy(heap.heap);
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 	switch (clnt->logon_level) {
300 	case NETR_INTERACTIVE_LOGON:
301 		netr_setup_identity(heap.heap, clnt, &info1.identity);
302 		netr_interactive_samlogon(netr_info, clnt, &info1);
303 		arg.logon_info.ru.info1 = &info1;
304 		break;
305 
306 	case NETR_NETWORK_LOGON:
307 		netr_setup_identity(heap.heap, clnt, &info2.identity);
308 		netr_network_samlogon(netr_info, clnt, &nt_rsp, &lm_rsp,
309 		    &info2);
310 		arg.logon_info.ru.info2 = &info2;
311 		break;
312 
313 	default:
314 		mlrpc_heap_destroy(heap.heap);
315 		return (NT_STATUS_INVALID_PARAMETER);
316 	}
317 
318 	rc = mlsvc_rpc_call(netr_handle->context, opnum, &arg, &heap);
319 	if (rc != 0) {
320 		bzero(netr_info, sizeof (netr_info_t));
321 		status = NT_STATUS_INVALID_PARAMETER;
322 	} else if (arg.status != 0) {
323 		status = NT_SC_VALUE(arg.status);
324 
325 		/*
326 		 * We need to validate the chain even though we have
327 		 * a non-zero status. If the status is ACCESS_DENIED
328 		 * this will trigger a new credential chain. However,
329 		 * a valid credential is returned with some status
330 		 * codes; for example, WRONG_PASSWORD.
331 		 */
332 		(void) netr_validate_chain(netr_info, arg.ret_auth);
333 	} else {
334 		status = netr_validate_chain(netr_info, arg.ret_auth);
335 		if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) {
336 			mlsvc_rpc_free(netr_handle->context, &heap);
337 			return (status);
338 		}
339 
340 		info3 = arg.ru.info3;
341 		status = netr_setup_userinfo(info3, user_info, clnt);
342 	}
343 
344 	mlsvc_rpc_free(netr_handle->context, &heap);
345 	return (status);
346 }
347 
348 /*
349  * netr_interactive_samlogon
350  *
351  * Set things up for an interactive SamLogon. Copy the NT and LM
352  * passwords to the logon structure and hash them with the session
353  * key.
354  */
355 static void
356 netr_interactive_samlogon(netr_info_t *netr_info, netr_client_t *clnt,
357     struct netr_logon_info1 *info1)
358 {
359 	BYTE key[NETR_OWF_PASSWORD_SZ];
360 
361 	(void) memcpy(&info1->lm_owf_password,
362 	    clnt->lm_password.lm_password_val, sizeof (netr_owf_password_t));
363 
364 	(void) memcpy(&info1->nt_owf_password,
365 	    clnt->nt_password.nt_password_val, sizeof (netr_owf_password_t));
366 
367 	(void) memset(key, 0, NETR_OWF_PASSWORD_SZ);
368 	(void) memcpy(key, netr_info->session_key, NETR_SESSION_KEY_SZ);
369 
370 	rand_hash((unsigned char *)&info1->lm_owf_password,
371 	    NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ);
372 
373 	rand_hash((unsigned char *)&info1->nt_owf_password,
374 	    NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ);
375 }
376 
377 /*
378  * netr_network_samlogon
379  *
380  * Set things up for a network SamLogon.  We provide a copy of the random
381  * challenge, that we sent to the client, to the domain controller.  This
382  * is the key that the client will have used to encrypt the NT and LM
383  * passwords.  Note that Windows 9x clients may not provide both passwords.
384  */
385 /*ARGSUSED*/
386 static void
387 netr_network_samlogon(netr_info_t *netr_info, netr_client_t *clnt,
388     netr_response_t *ntr, netr_response_t *lmr, struct netr_logon_info2 *info2)
389 {
390 	bcopy(clnt->challenge_key.challenge_key_val, info2->lm_challenge.data,
391 	    8);
392 
393 	if (clnt->nt_password.nt_password_len == NETR_CR_PASSWORD_SIZE) {
394 		ntr->length = NETR_CR_PASSWORD_SIZE;
395 		ntr->start = 0;
396 		ntr->max_length = NETR_CR_PASSWORD_SIZE;
397 		bcopy(clnt->nt_password.nt_password_val, ntr->data,
398 		    NETR_CR_PASSWORD_SIZE);
399 
400 		info2->nt_response.length = NETR_CR_PASSWORD_SIZE;
401 		info2->nt_response.max_length = NETR_CR_PASSWORD_SIZE;
402 		info2->nt_response.data = ntr;
403 	} else {
404 		info2->nt_response.length = 0;
405 		info2->nt_response.max_length = 0;
406 		info2->nt_response.data = 0;
407 	}
408 
409 	if (clnt->lm_password.lm_password_len == NETR_CR_PASSWORD_SIZE) {
410 		lmr->length = NETR_CR_PASSWORD_SIZE;
411 		lmr->start = 0;
412 		lmr->max_length = NETR_CR_PASSWORD_SIZE;
413 		bcopy(clnt->lm_password.lm_password_val, lmr->data,
414 		    NETR_CR_PASSWORD_SIZE);
415 
416 		info2->lm_response.length = NETR_CR_PASSWORD_SIZE;
417 		info2->lm_response.max_length = NETR_CR_PASSWORD_SIZE;
418 		info2->lm_response.data = lmr;
419 	} else {
420 		info2->lm_response.length = 0;
421 		info2->lm_response.max_length = 0;
422 		info2->lm_response.data = 0;
423 	}
424 }
425 
426 /*
427  * netr_setup_authenticator
428  *
429  * Set up the request and return authenticators. A new credential is
430  * generated from the session key, the current client credential and
431  * the current time, i.e.
432  *
433  *		NewCredential = Cred(SessionKey, OldCredential, time);
434  *
435  * The timestamp, which is used as a random seed, is stored in both
436  * the request and return authenticators.
437  *
438  * If any difficulties occur using the cryptographic framework, the
439  * function returns SMBAUTH_FAILURE.  Otherwise SMBAUTH_SUCCESS is
440  * returned.
441  */
442 int
443 netr_setup_authenticator(netr_info_t *netr_info,
444     struct netr_authenticator *auth, struct netr_authenticator *ret_auth)
445 {
446 	bzero(auth, sizeof (struct netr_authenticator));
447 
448 #ifdef _BIG_ENDIAN
449 	netr_info->timestamp = 0;
450 #else
451 	netr_info->timestamp = time(0) << 8;
452 #endif
453 	auth->timestamp = netr_info->timestamp;
454 
455 	if (netr_gen_credentials(netr_info->session_key,
456 	    &netr_info->client_credential,
457 	    netr_info->timestamp,
458 	    (netr_cred_t *)&auth->credential) != SMBAUTH_SUCCESS)
459 		return (SMBAUTH_FAILURE);
460 
461 	if (ret_auth) {
462 		bzero(ret_auth, sizeof (struct netr_authenticator));
463 		ret_auth->timestamp = netr_info->timestamp;
464 	}
465 
466 	return (SMBAUTH_SUCCESS);
467 }
468 
469 /*
470  * Validate the returned credentials and update the credential chain.
471  * The server returns an updated client credential rather than a new
472  * server credential.  The server uses (timestamp + 1) when generating
473  * the credential.
474  *
475  * Generate the new seed for the credential chain. The new seed is
476  * formed by adding (timestamp + 1) to the current client credential.
477  * The only quirk is the DWORD style addition.
478  *
479  * Returns NT_STATUS_INSUFFICIENT_LOGON_INFO if auth->credential is a
480  * NULL pointer. The Authenticator field of the SamLogon response packet
481  * sent by the Samba 3 PDC always return NULL pointer if the received
482  * SamLogon request is not immediately followed by the ServerReqChallenge
483  * and ServerAuthenticate2 requests.
484  *
485  * Returns NT_STATUS_SUCCESS if the server returned a valid credential.
486  * Otherwise we retirm NT_STATUS_UNSUCCESSFUL.
487  */
488 DWORD
489 netr_validate_chain(netr_info_t *netr_info, struct netr_authenticator *auth)
490 {
491 	netr_cred_t cred;
492 	DWORD result = NT_STATUS_SUCCESS;
493 	DWORD *dwp;
494 
495 	++netr_info->timestamp;
496 
497 	if (netr_gen_credentials(netr_info->session_key,
498 	    &netr_info->client_credential,
499 	    netr_info->timestamp, &cred) != SMBAUTH_SUCCESS)
500 		return (NT_STATUS_INTERNAL_ERROR);
501 
502 	if (&auth->credential == 0) {
503 		/*
504 		 * If the validation fails, destroy the credential chain.
505 		 * This should trigger a new authentication chain.
506 		 */
507 		bzero(netr_info, sizeof (netr_info_t));
508 		return (NT_STATUS_INSUFFICIENT_LOGON_INFO);
509 	}
510 
511 	result = memcmp(&cred, &auth->credential, sizeof (netr_cred_t));
512 	if (result != 0) {
513 		/*
514 		 * If the validation fails, destroy the credential chain.
515 		 * This should trigger a new authentication chain.
516 		 */
517 		bzero(netr_info, sizeof (netr_info_t));
518 		result = NT_STATUS_UNSUCCESSFUL;
519 	} else {
520 		/*
521 		 * Otherwise generate the next step in the chain.
522 		 */
523 		/*LINTED E_BAD_PTR_CAST_ALIGN*/
524 		dwp = (DWORD *)&netr_info->client_credential;
525 		dwp[0] += netr_info->timestamp;
526 
527 		netr_info->flags |= NETR_FLG_VALID;
528 	}
529 
530 	return (result);
531 }
532 
533 /*
534  * netr_invalidate_chain
535  *
536  * Mark the credential chain as invalid so that it will be recreated
537  * on the next attempt.
538  */
539 static void
540 netr_invalidate_chain(void)
541 {
542 	netr_global_info.flags &= ~NETR_FLG_VALID;
543 }
544 
545 /*
546  * netr_setup_identity
547  *
548  * Set up the client identity information. All of this information is
549  * specifically related to the client user and workstation attempting
550  * to access this system. It may not be in our primary domain.
551  *
552  * I don't know what logon_id is, it seems to be a unique identifier.
553  * Increment it before each use.
554  */
555 static void
556 netr_setup_identity(mlrpc_heap_t *heap, netr_client_t *clnt,
557     netr_logon_id_t *identity)
558 {
559 	static DWORD logon_id;
560 
561 	if (logon_id == 0)
562 		logon_id = 0xDCD0;
563 
564 	++logon_id;
565 	clnt->logon_id = logon_id;
566 
567 	identity->parameter_control = 0;
568 	identity->logon_id.LowPart = logon_id;
569 	identity->logon_id.HighPart = 0;
570 
571 	mlrpc_heap_mkvcs(heap, clnt->domain,
572 	    (mlrpc_vcbuf_t *)&identity->domain_name);
573 
574 	mlrpc_heap_mkvcs(heap, clnt->username,
575 	    (mlrpc_vcbuf_t *)&identity->username);
576 
577 	/*
578 	 * Some systems prefix the client workstation name with \\.
579 	 * It doesn't seem to make any difference whether it's there
580 	 * or not.
581 	 */
582 	mlrpc_heap_mkvcs(heap, clnt->workstation,
583 	    (mlrpc_vcbuf_t *)&identity->workstation);
584 }
585