You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3474 lines
114 KiB
3474 lines
114 KiB
//depot/Lab03_N/DS/security/protocols/msv_sspi/msvsam.c#37 - edit change 16740 (text)
|
|
/*++
|
|
|
|
Copyright (c) 1987-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
msvsam.c
|
|
|
|
Abstract:
|
|
|
|
Sam account validation interface.
|
|
|
|
These routines are shared by the MSV authentication package and
|
|
the Netlogon service.
|
|
|
|
Author:
|
|
|
|
Cliff Van Dyke (cliffv) 15-Jan-1992
|
|
|
|
Environment:
|
|
|
|
User mode only.
|
|
Contains NT-specific code.
|
|
Requires ANSI C extensions: slash-slash comments, long external names.
|
|
|
|
Revision History:
|
|
Chandana Surlu 21-Jul-96 Stolen from \\kernel\razzle3\src\security\msv1_0\msvsam.c
|
|
JClark 28-Jun-2000 Added WMI Trace Logging Support
|
|
Fixed bug 73583 - Password Expiration and Subauth DLLs
|
|
|
|
--*/
|
|
|
|
#include <global.h>
|
|
#undef EXTERN
|
|
|
|
#include "msp.h"
|
|
#include "nlp.h"
|
|
#include <stddef.h> // offsetof()
|
|
#include <msaudite.h> // SE_AUDITID_xxx
|
|
#include "trace.h" // WMI Tracing goo
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// SubAuth package zero helper routine //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
NTSTATUS
|
|
Msv1_0SubAuthenticationRoutineZero(
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation,
|
|
IN ULONG Flags,
|
|
IN PUSER_ALL_INFORMATION UserAll,
|
|
OUT PULONG WhichFields,
|
|
OUT PULONG UserFlags,
|
|
OUT PBOOLEAN Authoritative,
|
|
OUT PLARGE_INTEGER LogoffTime,
|
|
OUT PLARGE_INTEGER KickoffTime
|
|
);
|
|
|
|
|
|
BOOLEAN
|
|
MsvpCheckPreviousPassword(
|
|
IN BOOLEAN UasCompatibilityRequired,
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation,
|
|
IN SAMPR_HANDLE DomainHandle,
|
|
PUNICODE_STRING UserName
|
|
);
|
|
|
|
|
|
BOOLEAN
|
|
MsvpLm3ValidateResponse (
|
|
IN PNT_OWF_PASSWORD pNtOwfPassword,
|
|
IN PUNICODE_STRING pUserName,
|
|
IN PUNICODE_STRING pLogonDomainName,
|
|
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
|
|
IN PMSV1_0_LM3_RESPONSE pLm3Response,
|
|
OUT PUSER_SESSION_KEY UserSessionKey,
|
|
OUT PLM_SESSION_KEY LmSessionKey
|
|
)
|
|
{
|
|
UCHAR Response[MSV1_0_NTLM3_RESPONSE_LENGTH];
|
|
ULONG i;
|
|
|
|
// compute the response again
|
|
|
|
MsvpLm3Response (
|
|
pNtOwfPassword,
|
|
pUserName,
|
|
pLogonDomainName,
|
|
ChallengeToClient,
|
|
pLm3Response,
|
|
Response,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
);
|
|
|
|
// compare with what we were passed
|
|
|
|
i = (ULONG) RtlCompareMemory(
|
|
pLm3Response->Response,
|
|
Response,
|
|
MSV1_0_NTLM3_RESPONSE_LENGTH
|
|
);
|
|
|
|
#if 0
|
|
//
|
|
// If the NTLMv2 client computes a challenge/response, based on a missing
|
|
// supplied LogonDomainName, it will use NULL and effectively not mix in
|
|
// the LogonDomainName in the hash computation. On the server side, when
|
|
// netlogon calls, we get passed non-NULL, and of course that fails to
|
|
// compute the same hash. One somewhat slimy, but interesting way to fix
|
|
// this problem would be to retry the hash computation against a NULL
|
|
// domain, as below. This may be more attractive than changing netlogon,
|
|
// and trivial to backport & test on Win2k SP3.
|
|
//
|
|
// Scott Field (SField) 11-6-2001
|
|
//
|
|
|
|
if ( (MSV1_0_NTLM3_RESPONSE_LENGTH != i) && pLogonDomainName->Length ) {
|
|
|
|
UNICODE_STRING EmptyString = {0};
|
|
|
|
MsvpLm3Response (
|
|
pNtOwfPassword,
|
|
pUserName,
|
|
&EmptyString,
|
|
ChallengeToClient,
|
|
pLm3Response,
|
|
Response,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
);
|
|
|
|
// compare with what we were passed
|
|
|
|
i = (ULONG) RtlCompareMemory(
|
|
pLm3Response->Response,
|
|
Response,
|
|
MSV1_0_NTLM3_RESPONSE_LENGTH
|
|
);
|
|
}
|
|
#endif
|
|
|
|
SspPrint((SSP_NTLM_V2, "MsvpLm3ValidateResponse returning %s\n", (i == MSV1_0_NTLM3_RESPONSE_LENGTH) ? "true" : "false"));
|
|
|
|
return (BOOLEAN) (i == MSV1_0_NTLM3_RESPONSE_LENGTH);
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
MsvpNtlm3ValidateResponse (
|
|
IN PNT_OWF_PASSWORD pNtOwfPassword,
|
|
IN PUNICODE_STRING pUserName,
|
|
IN PUNICODE_STRING pLogonDomainName,
|
|
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
|
|
IN PMSV1_0_NTLM3_RESPONSE pNtlm3Response,
|
|
IN ULONG Ntlm3ResponseLength,
|
|
IN PMSV1_0_LM3_RESPONSE pLm3Response,
|
|
IN ULONG Lm3ResponseLength,
|
|
OUT PUSER_SESSION_KEY UserSessionKey,
|
|
OUT PLM_SESSION_KEY LmSessionKey
|
|
)
|
|
{
|
|
UCHAR Response[MSV1_0_NTLM3_RESPONSE_LENGTH];
|
|
ULONG i;
|
|
LARGE_INTEGER Time;
|
|
NTSTATUS Status;
|
|
|
|
#ifndef USE_CONSTANT_CHALLENGE
|
|
|
|
const LONGLONG TicksPerSecond = 10*1000*1000; // 100 ns ticks per second
|
|
|
|
#endif
|
|
|
|
|
|
//
|
|
// three success scenarios:
|
|
// 1) NTResponse provided is NTLMv2 and validation against NTResponse succeeds -- compute
|
|
// separate User and Lm sessionkeys.
|
|
// 2) NTResponse provided is NTLMv2 and validation against NTResponse fails, validation
|
|
// against LMResponse succeeds -- compute separate User and Lm sessionkeys in the same
|
|
// manner as #1 above.
|
|
// 3) NTResponse is not provided, and validation against LMResponse succeeds -- compute
|
|
// session keys as such: User session key is derived from LMResponse, and LM session key
|
|
// is a truncated form of User sessionkey.
|
|
//
|
|
|
|
|
|
//
|
|
// check the NTChallengeResponse first, if appropriate...
|
|
//
|
|
|
|
while (Ntlm3ResponseLength >= MSV1_0_NTLM3_MIN_NT_RESPONSE_LENGTH)
|
|
{
|
|
//
|
|
// check version numbers
|
|
//
|
|
|
|
if (pNtlm3Response->RespType > 1 ||
|
|
pNtlm3Response->HiRespType < 1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// check that the timestamp isn't too old
|
|
//
|
|
|
|
Status = NtQuerySystemTime( &Time );
|
|
ASSERT( NT_SUCCESS(Status) );
|
|
|
|
SspPrint((SSP_NTLM_V2, "MsvpNtlm3ValidateResponse: local %#I64x, response %#I64x\n", Time, pNtlm3Response->TimeStamp));
|
|
|
|
#ifndef USE_CONSTANT_CHALLENGE
|
|
//
|
|
// make sure time hasn't expired
|
|
// don't forget that client's clock could be behind ours
|
|
//
|
|
|
|
if (Time.QuadPart > (LONGLONG)pNtlm3Response->TimeStamp)
|
|
{
|
|
if (Time.QuadPart - (LONGLONG)pNtlm3Response->TimeStamp >
|
|
(MSV1_0_MAX_NTLM3_LIFE*TicksPerSecond))
|
|
{
|
|
break;
|
|
}
|
|
} else if ((LONGLONG)pNtlm3Response->TimeStamp - Time.QuadPart >
|
|
(MSV1_0_MAX_NTLM3_LIFE*TicksPerSecond)) {
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
// compute the response itself
|
|
|
|
MsvpNtlm3Response(
|
|
pNtOwfPassword,
|
|
pUserName,
|
|
pLogonDomainName,
|
|
(Ntlm3ResponseLength-sizeof(MSV1_0_NTLM3_RESPONSE)),
|
|
ChallengeToClient,
|
|
pNtlm3Response,
|
|
Response,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
);
|
|
|
|
// compare with what we were passed
|
|
|
|
i = (ULONG) RtlCompareMemory(
|
|
pNtlm3Response->Response,
|
|
Response,
|
|
MSV1_0_NTLM3_RESPONSE_LENGTH
|
|
);
|
|
|
|
//
|
|
// If the NTLMv2 client computes a challenge/response, based on a missing
|
|
// supplied LogonDomainName, it will use NULL and effectively not mix in
|
|
// the LogonDomainName in the hash computation. On the server side, when
|
|
// netlogon calls, we get passed non-NULL, and of course that fails to
|
|
// compute the same hash. One somewhat slimy, but interesting way to fix
|
|
// this problem would be to retry the hash computation against a NULL
|
|
// domain, as below. This may be more attractive than changing netlogon,
|
|
// and trivial to backport & test on Win2k SP3.
|
|
//
|
|
// Scott Field (SField) 11-6-2001
|
|
//
|
|
|
|
if ( (MSV1_0_NTLM3_RESPONSE_LENGTH != i) && pLogonDomainName->Length )
|
|
{
|
|
UNICODE_STRING EmptyString = {0};
|
|
|
|
MsvpNtlm3Response(
|
|
pNtOwfPassword,
|
|
pUserName,
|
|
&EmptyString,
|
|
(Ntlm3ResponseLength-sizeof(MSV1_0_NTLM3_RESPONSE)),
|
|
ChallengeToClient,
|
|
pNtlm3Response,
|
|
Response,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
);
|
|
|
|
// compare with what we were passed
|
|
|
|
i = (ULONG) RtlCompareMemory(
|
|
pNtlm3Response->Response,
|
|
Response,
|
|
MSV1_0_NTLM3_RESPONSE_LENGTH
|
|
);
|
|
}
|
|
|
|
if( i == MSV1_0_NTLM3_RESPONSE_LENGTH )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// if we got here, the NTLMv2 NTChallengeResponse failed or was missing.
|
|
//
|
|
|
|
if ( Lm3ResponseLength == LM_RESPONSE_LENGTH )
|
|
{
|
|
UNICODE_STRING EmptyString = {0};
|
|
|
|
if (!MsvpLm3ValidateResponse(
|
|
pNtOwfPassword,
|
|
pUserName,
|
|
pLogonDomainName,
|
|
ChallengeToClient,
|
|
pLm3Response,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
))
|
|
{
|
|
//
|
|
// try with a NULL domain, per the above. This will carry through
|
|
// to the Ntlm3Response below.
|
|
//
|
|
|
|
if ( (pLogonDomainName->Length == 0) ||
|
|
!MsvpLm3ValidateResponse(
|
|
pNtOwfPassword,
|
|
pUserName,
|
|
&EmptyString,
|
|
ChallengeToClient,
|
|
pLm3Response,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
if ( Ntlm3ResponseLength >= MSV1_0_NTLM3_MIN_NT_RESPONSE_LENGTH )
|
|
{
|
|
//
|
|
// if the NTChallengeResponse was provided, but failed,
|
|
// compute session keys based the same way as a success case would have.
|
|
// this is required as the client does not know that the LM field was used
|
|
// to successfully authenticate.
|
|
//
|
|
|
|
MsvpNtlm3Response(
|
|
pNtOwfPassword,
|
|
pUserName,
|
|
pLogonDomainName,
|
|
(Ntlm3ResponseLength - sizeof(MSV1_0_NTLM3_RESPONSE)),
|
|
ChallengeToClient,
|
|
pNtlm3Response,
|
|
Response,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOLEAN
|
|
MsvpPasswordValidate (
|
|
IN BOOLEAN UasCompatibilityRequired,
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation,
|
|
IN PUSER_INTERNAL1_INFORMATION Passwords,
|
|
OUT PULONG UserFlags,
|
|
OUT PUSER_SESSION_KEY UserSessionKey,
|
|
OUT PLM_SESSION_KEY LmSessionKey
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Process an interactive, network, or session logon. It calls
|
|
SamIUserValidation, validates the passed in credentials, updates the logon
|
|
statistics and packages the result for return to the caller.
|
|
|
|
This routine is called directly from the MSV Authentication package
|
|
on any system where LanMan is not installed. This routine is called
|
|
from the Netlogon Service otherwise.
|
|
|
|
Arguments:
|
|
|
|
UasCompatibilityRequired -- True, if UAS compatibility is required.
|
|
|
|
LogonLevel -- Specifies the level of information given in
|
|
LogonInformation.
|
|
|
|
LogonInformation -- Specifies the description for the user
|
|
logging on. The LogonDomainName field should be ignored.
|
|
The caller is responsible for validating this field.
|
|
|
|
Passwords -- Specifies the passwords for the user account.
|
|
|
|
UserFlags -- Returns flags identifying how the password was validated.
|
|
Returns LOGON_NOENCRYPTION if the password wasn't encrypted
|
|
Returns LOGON_USED_LM_PASSWORD if the LM password from SAM was used.
|
|
|
|
UserSessionKey -- Returns the NT User session key for this network logon
|
|
session.
|
|
|
|
LmSessionKey -- Returns the LM compatible session key for this network
|
|
logon session.
|
|
|
|
Return Value:
|
|
|
|
TRUE -- Password validation is successful
|
|
FALSE -- Password validation failed
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
|
|
PNETLOGON_INTERACTIVE_INFO LogonInteractiveInfo;
|
|
PNETLOGON_NETWORK_INFO LogonNetworkInfo;
|
|
BOOLEAN AlreadyValidated = FALSE;
|
|
BOOLEAN TryLmResponse = TRUE;
|
|
UNICODE_STRING NullUnicodeString;
|
|
|
|
ULONG NtLmProtocolSupported;
|
|
|
|
//
|
|
// Initialization.
|
|
//
|
|
|
|
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
|
|
*UserFlags = LOGON_NTLMV2_ENABLED;
|
|
RtlZeroMemory( UserSessionKey, sizeof(*UserSessionKey) );
|
|
RtlZeroMemory( LmSessionKey, sizeof(*LmSessionKey) );
|
|
RtlInitUnicodeString( &NullUnicodeString, NULL );
|
|
|
|
//
|
|
// Ensure the OWF password is always defined
|
|
//
|
|
|
|
if ( !Passwords->NtPasswordPresent ){
|
|
RtlCopyMemory( &Passwords->NtOwfPassword,
|
|
&NlpNullNtOwfPassword,
|
|
sizeof(Passwords->NtOwfPassword) );
|
|
}
|
|
|
|
if ( !Passwords->LmPasswordPresent ){
|
|
RtlCopyMemory( &Passwords->LmOwfPassword,
|
|
&NlpNullLmOwfPassword,
|
|
sizeof(Passwords->LmOwfPassword) );
|
|
}
|
|
|
|
//
|
|
// Handle interactive/service validation.
|
|
//
|
|
// Simply compare the OWF password passed in with the one from the
|
|
// SAM database.
|
|
//
|
|
|
|
switch ( LogonLevel ) {
|
|
case NetlogonInteractiveInformation:
|
|
case NetlogonServiceInformation:
|
|
|
|
ASSERT( offsetof( NETLOGON_INTERACTIVE_INFO, LmOwfPassword)
|
|
== offsetof( NETLOGON_SERVICE_INFO, LmOwfPassword) );
|
|
ASSERT( offsetof( NETLOGON_INTERACTIVE_INFO, NtOwfPassword)
|
|
== offsetof( NETLOGON_SERVICE_INFO, NtOwfPassword) );
|
|
|
|
LogonInteractiveInfo =
|
|
(PNETLOGON_INTERACTIVE_INFO) LogonInformation;
|
|
|
|
//
|
|
// If we're in UasCompatibilityMode,
|
|
// and we don't have the NT password in SAM (but do have LM password),
|
|
// validate against the LM version of the password.
|
|
//
|
|
|
|
if ( UasCompatibilityRequired &&
|
|
!Passwords->NtPasswordPresent &&
|
|
Passwords->LmPasswordPresent ) {
|
|
|
|
if ( RtlCompareMemory( &Passwords->LmOwfPassword,
|
|
&LogonInteractiveInfo->LmOwfPassword,
|
|
LM_OWF_PASSWORD_LENGTH ) !=
|
|
LM_OWF_PASSWORD_LENGTH ) {
|
|
|
|
return FALSE;
|
|
}
|
|
*UserFlags |= LOGON_USED_LM_PASSWORD;
|
|
|
|
//
|
|
// In all other circumstances, use the NT version of the password.
|
|
// This enforces case sensitivity.
|
|
//
|
|
|
|
} else {
|
|
|
|
if ( RtlCompareMemory( &Passwords->NtOwfPassword,
|
|
&LogonInteractiveInfo->NtOwfPassword,
|
|
NT_OWF_PASSWORD_LENGTH ) !=
|
|
NT_OWF_PASSWORD_LENGTH ) {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
//
|
|
// Handle network logon validation.
|
|
//
|
|
|
|
case NetlogonNetworkInformation:
|
|
{
|
|
BOOLEAN TriedNtLm2 = FALSE;
|
|
|
|
//
|
|
// First, assume the passed password information is a challenge
|
|
// response.
|
|
//
|
|
|
|
LogonNetworkInfo =
|
|
(PNETLOGON_NETWORK_INFO) LogonInformation;
|
|
|
|
SspPrint((SSP_CRED,
|
|
"MsvpPasswordValidate NetworkLogon ParameterControl %#x, user \"%wZ\", domain \"%wZ\", NtRespLen %#x, LmRespLen %#x\n",
|
|
LogonNetworkInfo->Identity.ParameterControl,
|
|
&LogonNetworkInfo->Identity.UserName,
|
|
&LogonNetworkInfo->Identity.LogonDomainName,
|
|
LogonNetworkInfo->NtChallengeResponse.Length,
|
|
LogonNetworkInfo->LmChallengeResponse.Length));
|
|
|
|
// If the NT response is an NTLM3 response, do NTLM3 or NTLM3 with LM OWF
|
|
// if the length is > NT_RESPONSE_LENGTH, then it's an NTLM3 response
|
|
|
|
if (LogonNetworkInfo->NtChallengeResponse.Length > NT_RESPONSE_LENGTH)
|
|
{
|
|
//
|
|
// this routine will try both NTChallengeResponse and LmChallengeResponse
|
|
// validation if appropriate.
|
|
//
|
|
|
|
AlreadyValidated = MsvpNtlm3ValidateResponse(
|
|
&Passwords->NtOwfPassword,
|
|
&LogonNetworkInfo->Identity.UserName,
|
|
&LogonNetworkInfo->Identity.LogonDomainName,
|
|
(PUCHAR)&LogonNetworkInfo->LmChallenge,
|
|
(PMSV1_0_NTLM3_RESPONSE) LogonNetworkInfo->NtChallengeResponse.Buffer,
|
|
LogonNetworkInfo->NtChallengeResponse.Length,
|
|
(PMSV1_0_LM3_RESPONSE) LogonNetworkInfo->LmChallengeResponse.Buffer,
|
|
LogonNetworkInfo->LmChallengeResponse.Length,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
);
|
|
|
|
SspPrint((SSP_NTLM_V2, "MsvpPasswordValidate MsvpNtlm3ValidateResponse(NtResponse) returns %s\n", AlreadyValidated ? "true" : "false"));
|
|
|
|
//
|
|
// because a Subauth may have been used, we will only return failure
|
|
// here if we know the request was NTLMv2.
|
|
//
|
|
|
|
if( AlreadyValidated ||
|
|
(LogonNetworkInfo->Identity.ParameterControl & MSV1_0_USE_CLIENT_CHALLENGE) ) {
|
|
return AlreadyValidated;
|
|
}
|
|
|
|
TriedNtLm2 = TRUE;
|
|
}
|
|
|
|
//
|
|
// check the LM3 response based on NT OWF hash next
|
|
// this will be recieved from Win9x server with NTLMv2 client
|
|
//
|
|
|
|
if ((LogonNetworkInfo->NtChallengeResponse.Length != NT_RESPONSE_LENGTH) &&
|
|
(LogonNetworkInfo->LmChallengeResponse.Length == NT_RESPONSE_LENGTH) &&
|
|
(!TriedNtLm2) )
|
|
{
|
|
//
|
|
// we only reach here if we haven't tried to satisfy the NTLMv2 response.
|
|
// ie: the NTChallengeResponse is not populated with NTLMv2.
|
|
//
|
|
|
|
AlreadyValidated = MsvpNtlm3ValidateResponse (
|
|
&Passwords->NtOwfPassword,
|
|
&LogonNetworkInfo->Identity.UserName,
|
|
&LogonNetworkInfo->Identity.LogonDomainName,
|
|
(PUCHAR)&LogonNetworkInfo->LmChallenge,
|
|
(PMSV1_0_NTLM3_RESPONSE) LogonNetworkInfo->NtChallengeResponse.Buffer,
|
|
LogonNetworkInfo->NtChallengeResponse.Length,
|
|
(PMSV1_0_LM3_RESPONSE) LogonNetworkInfo->LmChallengeResponse.Buffer,
|
|
LogonNetworkInfo->LmChallengeResponse.Length,
|
|
UserSessionKey,
|
|
LmSessionKey
|
|
);
|
|
|
|
SspPrint((SSP_NTLM_V2, "MsvpPasswordValidate MsvpNtlm3ValidateResponse(LmResponse) returns %s\n", AlreadyValidated ? "true" : "false"));
|
|
|
|
if (AlreadyValidated)
|
|
return TRUE;
|
|
}
|
|
|
|
NtLmProtocolSupported = NtLmGlobalLmProtocolSupported;
|
|
|
|
// if we're requiring all clients (Win9x and NT) to have been upgraded, fail out now
|
|
//if (NtLmProtocolSupported >= RefuseNtlm)
|
|
//return FALSE;
|
|
|
|
// if that fails, check the NTLM response if there is one of the
|
|
// appropriate size in either NT response or LM response
|
|
if (!AlreadyValidated &&
|
|
(NtLmProtocolSupported < RefuseNtlm) &&
|
|
(Passwords->NtPasswordPresent || (!Passwords->NtPasswordPresent && !Passwords->LmPasswordPresent)) &&
|
|
(LogonNetworkInfo->NtChallengeResponse.Length == NT_RESPONSE_LENGTH ||
|
|
LogonNetworkInfo->LmChallengeResponse.Length == NT_RESPONSE_LENGTH)) {
|
|
|
|
NT_RESPONSE NtResponse;
|
|
|
|
//
|
|
// NT response is present and hash exists, don't try the LM respnose.
|
|
//
|
|
|
|
if( LogonNetworkInfo->NtChallengeResponse.Length == NT_RESPONSE_LENGTH )
|
|
{
|
|
TryLmResponse = FALSE;
|
|
}
|
|
|
|
//
|
|
// Compute what the response should be.
|
|
//
|
|
|
|
Status = RtlCalculateNtResponse(
|
|
&LogonNetworkInfo->LmChallenge,
|
|
&Passwords->NtOwfPassword,
|
|
&NtResponse );
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
|
|
//
|
|
// If the responses match, the passwords are valid.
|
|
// Try the NT response first, then the LM response
|
|
//
|
|
|
|
if ( RtlCompareMemory(
|
|
LogonNetworkInfo->
|
|
NtChallengeResponse.Buffer,
|
|
&NtResponse,
|
|
LogonNetworkInfo->NtChallengeResponse.Length ) ==
|
|
NT_RESPONSE_LENGTH ) {
|
|
|
|
AlreadyValidated = TRUE;
|
|
|
|
} else if ( RtlCompareMemory(
|
|
LogonNetworkInfo->
|
|
LmChallengeResponse.Buffer,
|
|
&NtResponse,
|
|
LogonNetworkInfo->LmChallengeResponse.Length ) ==
|
|
NT_RESPONSE_LENGTH ) {
|
|
|
|
AlreadyValidated = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we're requiring all Win9x clients to have been upgraded, fail out now
|
|
//if (!AlreadyValidated && NtLmProtocolSupported >= RefuseLm)
|
|
// return FALSE;
|
|
|
|
//
|
|
// if the LM response is the right size
|
|
// validate against the LM version of the response
|
|
// this applies also when both NTOWF and LMOWF are not present in SAM.
|
|
//
|
|
|
|
if (!AlreadyValidated &&
|
|
( TryLmResponse ) &&
|
|
( NtLmProtocolSupported < RefuseLm ) &&
|
|
( LogonNetworkInfo->LmChallengeResponse.Length == LM_RESPONSE_LENGTH ) &&
|
|
( (Passwords->LmPasswordPresent) || (!Passwords->LmPasswordPresent && !Passwords->NtPasswordPresent) )
|
|
) {
|
|
|
|
LM_RESPONSE LmResponse;
|
|
|
|
//
|
|
// Compute what the response should be.
|
|
//
|
|
|
|
Status = RtlCalculateLmResponse(
|
|
&LogonNetworkInfo->LmChallenge,
|
|
&Passwords->LmOwfPassword,
|
|
&LmResponse );
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
|
|
//
|
|
// If the responses match, the passwords are valid.
|
|
//
|
|
|
|
if ( RtlCompareMemory(
|
|
LogonNetworkInfo->
|
|
LmChallengeResponse.Buffer,
|
|
&LmResponse,
|
|
LM_RESPONSE_LENGTH ) ==
|
|
LM_RESPONSE_LENGTH ) {
|
|
|
|
AlreadyValidated = TRUE;
|
|
*UserFlags |= LOGON_USED_LM_PASSWORD;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we haven't already validated this user,
|
|
// Validate a Cleartext password on a Network logon request.
|
|
//
|
|
|
|
if ( !AlreadyValidated ) {
|
|
|
|
// If Cleartext passwords are not allowed,
|
|
// indicate the password doesn't match.
|
|
//
|
|
|
|
if((LogonInfo->ParameterControl & CLEARTEXT_PASSWORD_ALLOWED) == 0){
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Compute the OWF password for the specified Cleartext password and
|
|
// compare that to the OWF password retrieved from SAM.
|
|
//
|
|
|
|
//
|
|
// If we're in UasCompatibilityMode,
|
|
// and we don't have the NT password in SAM or
|
|
// we don't have the NT password supplied by the caller.
|
|
// validate against the LM version of the password.
|
|
//
|
|
// if neither password are present, we validate against
|
|
// the empty computed LMOWF.
|
|
//
|
|
|
|
if ( UasCompatibilityRequired &&
|
|
(NtLmProtocolSupported < RefuseLm) &&
|
|
((Passwords->LmPasswordPresent) || (!Passwords->LmPasswordPresent && !Passwords->NtPasswordPresent)) &&
|
|
(!Passwords->NtPasswordPresent ||
|
|
LogonNetworkInfo->NtChallengeResponse.Length == 0 ) ) {
|
|
|
|
LM_OWF_PASSWORD LmOwfPassword;
|
|
CHAR LmPassword[LM20_PWLEN+1];
|
|
USHORT i;
|
|
|
|
|
|
//
|
|
// Compute the LmOwfPassword for the cleartext password passed in.
|
|
// (Enforce length restrictions on LanMan compatible passwords.)
|
|
//
|
|
|
|
if ( LogonNetworkInfo->LmChallengeResponse.Length >
|
|
sizeof(LmPassword) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
RtlZeroMemory( &LmPassword, sizeof(LmPassword) );
|
|
|
|
for (i = 0; i < LogonNetworkInfo->LmChallengeResponse.Length; i++) {
|
|
LmPassword[i] =
|
|
RtlUpperChar(LogonNetworkInfo->LmChallengeResponse.Buffer[i]);
|
|
}
|
|
|
|
(VOID) RtlCalculateLmOwfPassword( LmPassword, &LmOwfPassword );
|
|
|
|
if ( RtlCompareMemory( &Passwords->LmOwfPassword,
|
|
&LmOwfPassword,
|
|
LM_OWF_PASSWORD_LENGTH ) !=
|
|
LM_OWF_PASSWORD_LENGTH ) {
|
|
|
|
//
|
|
// Try the case preserved clear text password, too.
|
|
// (I know of no client that does this,
|
|
// but it is compatible with the LM 2.x server.)
|
|
//
|
|
|
|
RtlZeroMemory( &LmPassword, sizeof(LmPassword) );
|
|
RtlCopyMemory(
|
|
&LmPassword,
|
|
LogonNetworkInfo->LmChallengeResponse.Buffer,
|
|
LogonNetworkInfo->LmChallengeResponse.Length);
|
|
|
|
(VOID) RtlCalculateLmOwfPassword( LmPassword,
|
|
&LmOwfPassword );
|
|
|
|
if ( RtlCompareMemory( &Passwords->LmOwfPassword,
|
|
&LmOwfPassword,
|
|
LM_OWF_PASSWORD_LENGTH ) !=
|
|
LM_OWF_PASSWORD_LENGTH ) {
|
|
|
|
RtlSecureZeroMemory(LmPassword, sizeof(LmPassword));
|
|
RtlSecureZeroMemory(&LmOwfPassword, sizeof(LmOwfPassword));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
RtlSecureZeroMemory(LmPassword, sizeof(LmPassword));
|
|
RtlSecureZeroMemory(&LmOwfPassword, sizeof(LmOwfPassword));
|
|
|
|
}
|
|
|
|
*UserFlags |= LOGON_USED_LM_PASSWORD;
|
|
|
|
|
|
//
|
|
// In all other circumstances, use the NT version of the password.
|
|
// This enforces case sensitivity.
|
|
//
|
|
|
|
} else {
|
|
NT_OWF_PASSWORD NtOwfPassword;
|
|
|
|
|
|
//
|
|
// Compute the NtOwfPassword for the cleartext password passed in.
|
|
//
|
|
|
|
Status = RtlCalculateNtOwfPassword(
|
|
(PUNICODE_STRING)
|
|
&LogonNetworkInfo->NtChallengeResponse,
|
|
&NtOwfPassword );
|
|
|
|
if ( RtlCompareMemory( &Passwords->NtOwfPassword,
|
|
&NtOwfPassword,
|
|
NT_OWF_PASSWORD_LENGTH ) !=
|
|
NT_OWF_PASSWORD_LENGTH ) {
|
|
|
|
RtlSecureZeroMemory(&NtOwfPassword, sizeof(NtOwfPassword));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
RtlSecureZeroMemory(&NtOwfPassword, sizeof(NtOwfPassword));
|
|
}
|
|
|
|
*UserFlags |= LOGON_NOENCRYPTION;
|
|
}
|
|
|
|
//
|
|
// ASSERT: the network logon has been authenticated
|
|
//
|
|
// Compute the session keys.
|
|
|
|
//
|
|
// If the client negotiated a non-NT protocol,
|
|
// use the lanman session key as the UserSessionKey.
|
|
//
|
|
|
|
if ( LogonNetworkInfo->NtChallengeResponse.Length == 0 ) {
|
|
|
|
ASSERT( sizeof(*UserSessionKey) >= sizeof(*LmSessionKey) );
|
|
|
|
//
|
|
// win9x depends on the last 8 bytes to be zero, zero it out now
|
|
//
|
|
|
|
RtlZeroMemory( UserSessionKey, sizeof(*UserSessionKey) );
|
|
|
|
RtlCopyMemory( UserSessionKey,
|
|
&Passwords->LmOwfPassword,
|
|
sizeof(*LmSessionKey) );
|
|
|
|
} else {
|
|
|
|
//
|
|
// Return the NT UserSessionKey unless this is an account
|
|
// that doesn't have the NT version of the password.
|
|
// (A null password counts as a password).
|
|
//
|
|
|
|
if ( Passwords->NtPasswordPresent || !Passwords->LmPasswordPresent){
|
|
|
|
Status = RtlCalculateUserSessionKeyNt(
|
|
(PNT_RESPONSE) NULL, // Argument not used
|
|
&Passwords->NtOwfPassword,
|
|
UserSessionKey );
|
|
|
|
ASSERT( NT_SUCCESS(Status) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return the LM SessionKey unless this is an account
|
|
// that doesn't have the LM version of the password.
|
|
// (A null password counts as a password).
|
|
//
|
|
|
|
if ( Passwords->LmPasswordPresent || !Passwords->NtPasswordPresent ) {
|
|
RtlCopyMemory( LmSessionKey,
|
|
&Passwords->LmOwfPassword,
|
|
sizeof(*LmSessionKey) );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Any other LogonLevel is an internal error.
|
|
//
|
|
default:
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
MsvpEqualSidPrefix(
|
|
IN PSID DomainSid,
|
|
IN PSID GroupSid
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks to see if the specified group sid came from the
|
|
specified domain by verifying that the domain portion of the group sid
|
|
is equal to the domain sid.
|
|
|
|
Arguments:
|
|
|
|
DomainSid - Sid of the domain for comparison.
|
|
|
|
GroupSid - Sid of the group for comparison
|
|
|
|
Returns:
|
|
|
|
TRUE - The group sid came from the specified domain.
|
|
FALSE - The group sid did not come from the specified domain.
|
|
|
|
--*/
|
|
{
|
|
PISID LocalGroupSid = (PISID) GroupSid;
|
|
PISID LocalDomainSid = (PISID) DomainSid;
|
|
if ((LocalGroupSid->SubAuthorityCount == LocalDomainSid->SubAuthorityCount + 1) &&
|
|
RtlEqualMemory(
|
|
RtlIdentifierAuthoritySid(LocalDomainSid),
|
|
RtlIdentifierAuthoritySid(LocalGroupSid),
|
|
RtlLengthRequiredSid(
|
|
LocalDomainSid->SubAuthorityCount
|
|
) - FIELD_OFFSET(SID,IdentifierAuthority)
|
|
)) {
|
|
return(TRUE);
|
|
}
|
|
return(FALSE);
|
|
}
|
|
|
|
NTSTATUS
|
|
MsvpFilterGroupMembership(
|
|
IN PSID_AND_ATTRIBUTES_LIST CompleteMembership,
|
|
IN PSID LogonDomainId,
|
|
OUT PSAMPR_GET_GROUPS_BUFFER LocalMembership,
|
|
OUT PSID_AND_ATTRIBUTES_LIST GlobalMembership,
|
|
OUT PULONG GlobalMembershipSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine separates the complete transitive group membership into
|
|
portions from this domain and portions from others.
|
|
|
|
Arguments:
|
|
|
|
CompleteMembership - The complete transitive membership.
|
|
|
|
LogonDomainId - SID of the logon domain, used for compressing group
|
|
membership.
|
|
|
|
LocalMembership - Receives a list of rids corresponding to groups in this
|
|
domain. The list should be freed with MIDL_user_free.
|
|
|
|
GlobalMembership - Recevies a list of sids corresponding to groups in
|
|
other domain. The list, but not the sids, should be free with
|
|
MIDL_user_free.
|
|
|
|
GlobalMembershipSize - Size, in bytes, of the sids in the global membership
|
|
and the size of the SID_AND_ATTRIBUTES structures.
|
|
|
|
Returns:
|
|
|
|
STATUS_SUCCESS on success
|
|
STATUS_INSUFFICIENT_RESOURCES on for memory allocation failures.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG LocalCount = 0;
|
|
ULONG GlobalCount = 0;
|
|
ULONG GlobalSize = 0;
|
|
ULONG Index;
|
|
|
|
LocalMembership->MembershipCount = 0;
|
|
LocalMembership->Groups = NULL;
|
|
GlobalMembership->Count = 0;
|
|
GlobalMembership->SidAndAttributes = NULL;
|
|
|
|
|
|
//
|
|
// Define a flag so we don't have to do the comparison twice.
|
|
//
|
|
|
|
#define MSVP_LOCAL_GROUP_ATTR 0x20000000
|
|
|
|
for (Index = 0; Index < CompleteMembership->Count ; Index++ ) {
|
|
ASSERT((CompleteMembership->SidAndAttributes[Index].Attributes & MSVP_LOCAL_GROUP_ATTR) == 0);
|
|
|
|
if (MsvpEqualSidPrefix(
|
|
LogonDomainId,
|
|
CompleteMembership->SidAndAttributes[Index].Sid
|
|
)) {
|
|
|
|
CompleteMembership->SidAndAttributes[Index].Attributes |= MSVP_LOCAL_GROUP_ATTR;
|
|
LocalCount++;
|
|
} else {
|
|
|
|
GlobalCount++;
|
|
GlobalSize += sizeof(SID_AND_ATTRIBUTES) + RtlLengthSid(CompleteMembership->SidAndAttributes[Index].Sid);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Allocate the arrays for the output
|
|
//
|
|
|
|
if (LocalCount != 0)
|
|
{
|
|
LocalMembership->Groups = (PGROUP_MEMBERSHIP) I_NtLmAllocate(LocalCount * sizeof(GROUP_MEMBERSHIP));
|
|
if (LocalMembership->Groups == NULL) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
LocalMembership->MembershipCount = LocalCount;
|
|
}
|
|
if (GlobalCount != 0)
|
|
{
|
|
GlobalMembership->SidAndAttributes = (PSID_AND_ATTRIBUTES) I_NtLmAllocate(GlobalCount * sizeof(SID_AND_ATTRIBUTES));
|
|
if (GlobalMembership->SidAndAttributes == NULL) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
GlobalMembership->Count = GlobalCount;
|
|
}
|
|
|
|
//
|
|
// Loop through again copy the rid or sid into the respective array
|
|
//
|
|
|
|
LocalCount = 0;
|
|
GlobalCount = 0;
|
|
for (Index = 0; Index < CompleteMembership->Count ; Index++ ) {
|
|
|
|
if ((CompleteMembership->SidAndAttributes[Index].Attributes & MSVP_LOCAL_GROUP_ATTR) != 0) {
|
|
|
|
LocalMembership->Groups[LocalCount].Attributes = CompleteMembership->SidAndAttributes[Index].Attributes & ~MSVP_LOCAL_GROUP_ATTR;
|
|
LocalMembership->Groups[LocalCount].RelativeId =
|
|
*RtlSubAuthoritySid(
|
|
CompleteMembership->SidAndAttributes[Index].Sid,
|
|
*RtlSubAuthorityCountSid(
|
|
CompleteMembership->SidAndAttributes[Index].Sid
|
|
) - 1
|
|
);
|
|
LocalCount++;
|
|
} else {
|
|
GlobalMembership->SidAndAttributes[GlobalCount] = CompleteMembership->SidAndAttributes[Index];
|
|
GlobalCount++;
|
|
}
|
|
}
|
|
*GlobalMembershipSize = GlobalSize;
|
|
Cleanup:
|
|
if (!NT_SUCCESS(Status)) {
|
|
if (LocalMembership->Groups != NULL)
|
|
{
|
|
I_NtLmFree(LocalMembership->Groups);
|
|
LocalMembership->Groups = NULL;
|
|
}
|
|
if (GlobalMembership->SidAndAttributes != NULL)
|
|
{
|
|
I_NtLmFree(GlobalMembership->SidAndAttributes);
|
|
GlobalMembership->SidAndAttributes = NULL;
|
|
}
|
|
}
|
|
return(Status);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
MsvpSamValidate (
|
|
IN SAMPR_HANDLE DomainHandle,
|
|
IN BOOLEAN UasCompatibilityRequired,
|
|
IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
|
|
IN PUNICODE_STRING LogonServer,
|
|
IN PUNICODE_STRING LogonDomainName,
|
|
IN PSID LogonDomainId,
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation,
|
|
IN ULONG GuestRelativeId,
|
|
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
|
|
OUT PVOID * ValidationInformation,
|
|
OUT PBOOLEAN Authoritative,
|
|
OUT PBOOLEAN BadPasswordCountZeroed
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Process an interactive, network, or session logon. It calls
|
|
SamIUserValidation, validates the passed in credentials, updates the logon
|
|
statistics and packages the result for return to the caller.
|
|
|
|
This routine is called by MsvSamValidate.
|
|
|
|
Arguments:
|
|
|
|
DomainHandle -- Specifies a handle to the SamDomain to use to
|
|
validate the request.
|
|
|
|
UasCompatibilityRequired -- TRUE iff UasCompatibilityMode is on.
|
|
|
|
SecureChannelType -- The secure channel type this request was made on.
|
|
|
|
When netlogon on the BDC is called, the user is actually
|
|
already authenticated (via PDC) through a prior "net use"
|
|
from the win9x client. Netlogon merely returns the validation
|
|
info to the win9x caller. To do that Netlogon calls
|
|
MsvSamValidate passing NullSecureChannel as the 3rd parameter
|
|
indicating to skip the password check.
|
|
|
|
|
|
LogonServer -- Specifies the server name of the caller.
|
|
|
|
LogonDomainName -- Specifies the domain of the caller.
|
|
|
|
LogonDomainId -- Specifies the DomainId of the domain of the caller.
|
|
|
|
LogonLevel -- Specifies the level of information given in
|
|
LogonInformation.
|
|
|
|
LogonInformation -- Specifies the description for the user
|
|
logging on. The LogonDomainName field should be ignored.
|
|
The caller is responsible for validating this field.
|
|
|
|
GuestRelativeId - If non-zero, specifies the relative ID of the account
|
|
to validate against.
|
|
|
|
ValidationLevel -- Specifies the level of information returned in
|
|
ValidationInformation. Must be NetlogonValidationSamInfo,
|
|
NetlogonValidationSamInfo2 or NetlogonValidationSamInfo4.
|
|
|
|
ValidationInformation -- Returns the requested validation
|
|
information. This buffer must be freed user MIDL_user_free.
|
|
This information is only return on STATUS_SUCCESS.
|
|
|
|
Authoritative -- Returns whether the status returned is an
|
|
authoritative status which should be returned to the original
|
|
caller. If not, this logon request may be tried again on another
|
|
domain controller. This parameter is returned regardless of the
|
|
status code.
|
|
|
|
BadPasswordCountZeroed - Returns TRUE iff we zeroed the BadPasswordCount
|
|
field of this user.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: if there was no error.
|
|
STATUS_INVALID_INFO_CLASS: LogonLevel or ValidationLevel are invalid.
|
|
STATUS_NO_SUCH_USER: The specified user has no account.
|
|
STATUS_WRONG_PASSWORD: The password was invalid.
|
|
|
|
Other return codes from SamIUserValidation
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
NTSTATUS SubAuthExStatus = STATUS_SUCCESS;
|
|
|
|
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
|
|
|
|
SAMPR_HANDLE UserHandle = NULL;
|
|
ULONG RelativeId = GuestRelativeId;
|
|
ULONG SamFlags;
|
|
PSID LocalSidUser = NULL;
|
|
|
|
PSAMPR_USER_INFO_BUFFER UserAllInfo = NULL;
|
|
PSAMPR_USER_ALL_INFORMATION UserAll = NULL;
|
|
SAMPR_GET_GROUPS_BUFFER GroupsBuffer;
|
|
ULONG UserFlags = 0;
|
|
USER_SESSION_KEY UserSessionKey = {0};
|
|
LM_SESSION_KEY LmSessionKey = {0};
|
|
ULONG WhichFields = 0;
|
|
UNICODE_STRING LocalUserName;
|
|
|
|
UNICODE_STRING LocalWorkstation;
|
|
ULONG UserAccountControl;
|
|
LARGE_INTEGER LogonTime;
|
|
LARGE_INTEGER LogoffTime = {0};
|
|
LARGE_INTEGER KickoffTime = {0};
|
|
UNICODE_STRING Upn;
|
|
|
|
LARGE_INTEGER AccountExpires;
|
|
LARGE_INTEGER PasswordMustChange;
|
|
LARGE_INTEGER PasswordLastSet;
|
|
|
|
|
|
PNETLOGON_VALIDATION_SAM_INFO4 ValidationSam = NULL;
|
|
ULONG ValidationSamSize;
|
|
PUCHAR Where;
|
|
ULONG Index;
|
|
|
|
SAMPR_RETURNED_USTRING_ARRAY NameArray;
|
|
SAMPR_ULONG_ARRAY UseArray;
|
|
SID_AND_ATTRIBUTES_LIST GroupMembership;
|
|
SID_AND_ATTRIBUTES_LIST GlobalGroupMembership;
|
|
ULONG GlobalMembershipSize = 0;
|
|
|
|
MSV1_0_VALIDATION_INFO SubAuthValidationInformation;
|
|
BOOLEAN fSubAuthEx = FALSE;
|
|
|
|
BOOLEAN fMachineAccount;
|
|
|
|
ULONG ActionsPerformed = 0;
|
|
PSID UserSid = NULL;
|
|
|
|
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
|
|
|
|
|
|
|
|
//
|
|
// check if caller requested that logon only target specified domain.
|
|
//
|
|
|
|
if( LogonInfo->ParameterControl & MSV1_0_TRY_SPECIFIED_DOMAIN_ONLY &&
|
|
LogonInfo->LogonDomainName.Length ) {
|
|
|
|
//
|
|
// common case is a match for LogonDomainName, so avoid taking locks
|
|
// until mis-match occurs.
|
|
//
|
|
|
|
if(!RtlEqualDomainName( &LogonInfo->LogonDomainName, LogonDomainName )) {
|
|
|
|
WCHAR LocalTarget[ DNS_MAX_NAME_LENGTH + 1 ];
|
|
WCHAR SpecifiedTarget[ DNS_MAX_NAME_LENGTH + 1 ];
|
|
ULONG cchLocalTarget = 0;
|
|
ULONG cchSpecifiedTarget = 0;
|
|
|
|
//
|
|
// pickup the local target name, based on whether this computer is
|
|
// a domain controller.
|
|
//
|
|
|
|
RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
|
|
|
|
if( NlpWorkstation ) {
|
|
|
|
if( (NtLmGlobalUnicodeDnsComputerNameString.Length + sizeof(WCHAR)) <=
|
|
sizeof( LocalTarget ) ) {
|
|
|
|
RtlCopyMemory(
|
|
LocalTarget,
|
|
NtLmGlobalUnicodeDnsComputerName,
|
|
NtLmGlobalUnicodeDnsComputerNameString.Length
|
|
);
|
|
|
|
cchLocalTarget = (NtLmGlobalUnicodeDnsComputerNameString.Length) /
|
|
sizeof(WCHAR);
|
|
}
|
|
|
|
} else {
|
|
if( (NtLmGlobalUnicodeDnsDomainNameString.Length + sizeof(WCHAR)) <=
|
|
sizeof( LocalTarget ) ) {
|
|
|
|
RtlCopyMemory(
|
|
LocalTarget,
|
|
NtLmGlobalUnicodeDnsDomainName,
|
|
NtLmGlobalUnicodeDnsDomainNameString.Length
|
|
);
|
|
|
|
cchLocalTarget = (NtLmGlobalUnicodeDnsDomainNameString.Length) /
|
|
sizeof(WCHAR);
|
|
}
|
|
|
|
}
|
|
|
|
RtlReleaseResource(&NtLmGlobalCritSect);
|
|
|
|
//
|
|
// pull out target name.
|
|
//
|
|
|
|
if( (LogonInfo->LogonDomainName.Length + sizeof(WCHAR)) <= sizeof( SpecifiedTarget ) ) {
|
|
|
|
cchSpecifiedTarget = (LogonInfo->LogonDomainName.Length) / sizeof(WCHAR);
|
|
|
|
RtlCopyMemory(
|
|
SpecifiedTarget,
|
|
LogonInfo->LogonDomainName.Buffer,
|
|
LogonInfo->LogonDomainName.Length
|
|
);
|
|
}
|
|
|
|
if ( cchLocalTarget && cchSpecifiedTarget ) {
|
|
|
|
LocalTarget[ cchLocalTarget ] = L'\0';
|
|
SpecifiedTarget[ cchSpecifiedTarget ] = L'\0';
|
|
|
|
if(!DnsNameCompare_W( LocalTarget, SpecifiedTarget ) ) {
|
|
*Authoritative = FALSE;
|
|
return STATUS_NO_SUCH_USER;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Initialization.
|
|
//
|
|
|
|
RtlZeroMemory(
|
|
&SubAuthValidationInformation,
|
|
sizeof(MSV1_0_VALIDATION_INFO));
|
|
|
|
SubAuthValidationInformation.Authoritative = TRUE;
|
|
SubAuthValidationInformation.WhichFields = 0;
|
|
NameArray.Count = 0;
|
|
NameArray.Element = NULL;
|
|
UseArray.Count = 0;
|
|
UseArray.Element = NULL;
|
|
*BadPasswordCountZeroed = FALSE;
|
|
GroupMembership.Count = 0;
|
|
GroupMembership.SidAndAttributes = NULL;
|
|
GlobalGroupMembership.Count = 0;
|
|
GlobalGroupMembership.SidAndAttributes = NULL;
|
|
GroupsBuffer.MembershipCount = 0;
|
|
GroupsBuffer.Groups = NULL;
|
|
|
|
RtlInitUnicodeString( &Upn, NULL );
|
|
|
|
(VOID) NtQuerySystemTime( &LogonTime );
|
|
|
|
|
|
//
|
|
// Determine what account types are valid.
|
|
//
|
|
// Normal user accounts are always allowed.
|
|
//
|
|
|
|
UserAccountControl = USER_NORMAL_ACCOUNT;
|
|
|
|
*Authoritative = TRUE;
|
|
|
|
switch ( LogonLevel ) {
|
|
case NetlogonInteractiveInformation:
|
|
case NetlogonServiceInformation:
|
|
|
|
break;
|
|
|
|
case NetlogonNetworkInformation:
|
|
//
|
|
// Local user (Temp Duplicate) accounts are only used on the machine
|
|
// being directly logged onto.
|
|
// (Nor are interactive or service logons allowed to them.)
|
|
//
|
|
|
|
if ( SecureChannelType == MsvApSecureChannel ) {
|
|
UserAccountControl |= USER_TEMP_DUPLICATE_ACCOUNT;
|
|
}
|
|
|
|
//
|
|
// Machine accounts can be accessed on network connections.
|
|
//
|
|
|
|
UserAccountControl |= USER_INTERDOMAIN_TRUST_ACCOUNT |
|
|
USER_WORKSTATION_TRUST_ACCOUNT |
|
|
USER_SERVER_TRUST_ACCOUNT;
|
|
break;
|
|
|
|
default:
|
|
*Authoritative = TRUE;
|
|
return STATUS_INVALID_INFO_CLASS;
|
|
}
|
|
|
|
//
|
|
// Check the ValidationLevel
|
|
//
|
|
|
|
switch (ValidationLevel) {
|
|
case NetlogonValidationSamInfo:
|
|
case NetlogonValidationSamInfo2:
|
|
case NetlogonValidationSamInfo4:
|
|
break;
|
|
|
|
default:
|
|
|
|
*Authoritative = TRUE;
|
|
return STATUS_INVALID_INFO_CLASS;
|
|
}
|
|
|
|
//
|
|
// Convert the user name to a RelativeId.
|
|
//
|
|
|
|
if ( RelativeId != 0 ) {
|
|
|
|
UCHAR cDomainSubAuthorities;
|
|
UCHAR SubAuthIndex;
|
|
ULONG cbLocalSidUser;
|
|
PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority;
|
|
|
|
//
|
|
// build a Sid out of the DomainId and the supplied Rid.
|
|
//
|
|
|
|
cDomainSubAuthorities = *RtlSubAuthorityCountSid( LogonDomainId );
|
|
pIdentifierAuthority = RtlIdentifierAuthoritySid( LogonDomainId );
|
|
|
|
cbLocalSidUser = RtlLengthRequiredSid( (ULONG)(cDomainSubAuthorities + 1) );
|
|
LocalSidUser = I_NtLmAllocate( cbLocalSidUser );
|
|
if (LocalSidUser == NULL) {
|
|
*Authoritative = FALSE;
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = RtlInitializeSid(LocalSidUser, pIdentifierAuthority, (UCHAR)((DWORD)cDomainSubAuthorities+1));
|
|
if(!NT_SUCCESS(Status)) {
|
|
*Authoritative = FALSE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// loop copying subauthorities.
|
|
//
|
|
|
|
for( SubAuthIndex = 0 ; SubAuthIndex < cDomainSubAuthorities ; SubAuthIndex++ )
|
|
{
|
|
*RtlSubAuthoritySid( LocalSidUser, (ULONG)SubAuthIndex ) =
|
|
*RtlSubAuthoritySid( LogonDomainId, (ULONG)SubAuthIndex );
|
|
}
|
|
|
|
//
|
|
// append relative ID.
|
|
//
|
|
|
|
*RtlSubAuthoritySid(LocalSidUser, cDomainSubAuthorities) = RelativeId;
|
|
|
|
LocalUserName.Buffer = LocalSidUser;
|
|
LocalUserName.Length = (USHORT)cbLocalSidUser;
|
|
LocalUserName.MaximumLength = (USHORT)cbLocalSidUser;
|
|
|
|
SamFlags = SAM_OPEN_BY_SID;
|
|
} else {
|
|
LocalUserName = LogonInfo->UserName;
|
|
SamFlags = 0;
|
|
}
|
|
|
|
//
|
|
// if this is a domain controller, and, we get a logon request that
|
|
// looks like a possible UPN, set the flag...
|
|
//
|
|
|
|
if( (SamFlags == 0) &&
|
|
(LogonInfo->LogonDomainName.Buffer == NULL) &&
|
|
!NlpWorkstation
|
|
)
|
|
{
|
|
SamFlags |= SAM_OPEN_BY_UPN_OR_ACCOUNTNAME;
|
|
}
|
|
|
|
//
|
|
// Open the user account.
|
|
//
|
|
|
|
if (( LogonInfo->ParameterControl & MSV1_0_SUBAUTHENTICATION_DLL ) ||
|
|
( LogonInfo->ParameterControl & MSV1_0_SUBAUTHENTICATION_DLL_EX ) ||
|
|
NlpSubAuthZeroExists ) {
|
|
|
|
//
|
|
// Fetch all attributes in the presence of a subauthentication DLL
|
|
//
|
|
|
|
Status = I_SamIGetUserLogonInformation(
|
|
DomainHandle,
|
|
SamFlags,
|
|
&LocalUserName,
|
|
&UserAllInfo,
|
|
&GroupMembership,
|
|
&UserHandle
|
|
);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Performance optimization:
|
|
// Fetch only select attributes in the absence of a subauthentication DLL
|
|
//
|
|
|
|
Status = I_SamIGetUserLogonInformationEx(
|
|
DomainHandle,
|
|
SamFlags,
|
|
&LocalUserName,
|
|
USER_ALL_ACCOUNTEXPIRES |
|
|
USER_ALL_BADPASSWORDCOUNT |
|
|
USER_ALL_FULLNAME |
|
|
USER_ALL_HOMEDIRECTORY |
|
|
USER_ALL_HOMEDIRECTORYDRIVE |
|
|
USER_ALL_LASTLOGON |
|
|
USER_ALL_LMPASSWORDPRESENT |
|
|
USER_ALL_LOGONCOUNT |
|
|
USER_ALL_LOGONHOURS |
|
|
USER_ALL_NTPASSWORDPRESENT |
|
|
USER_ALL_OWFPASSWORD |
|
|
USER_ALL_PARAMETERS |
|
|
USER_ALL_PASSWORDCANCHANGE |
|
|
USER_ALL_PASSWORDLASTSET |
|
|
USER_ALL_PASSWORDMUSTCHANGE |
|
|
USER_ALL_PRIMARYGROUPID |
|
|
USER_ALL_PROFILEPATH |
|
|
USER_ALL_SCRIPTPATH |
|
|
USER_ALL_USERACCOUNTCONTROL |
|
|
USER_ALL_USERID |
|
|
USER_ALL_USERNAME |
|
|
USER_ALL_WORKSTATIONS,
|
|
&UserAllInfo,
|
|
&GroupMembership,
|
|
&UserHandle
|
|
);
|
|
}
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
UserHandle = NULL;
|
|
*Authoritative = FALSE;
|
|
Status = STATUS_NO_SUCH_USER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( !NlpWorkstation )
|
|
{
|
|
BOOLEAN UpnDefaulted;
|
|
|
|
//
|
|
// get the UPN. Ignore the failure, as, there is existing logic
|
|
// for dealing with the lack of Upn.
|
|
//
|
|
|
|
I_SamIUPNFromUserHandle(
|
|
UserHandle,
|
|
&UpnDefaulted,
|
|
&Upn
|
|
);
|
|
}
|
|
|
|
UserAll = &UserAllInfo->All;
|
|
|
|
if ( RelativeId != 0 )
|
|
{
|
|
//
|
|
// reset LocalUserName to be an actual username rather than a Sid
|
|
// for Guest logon. This allows proper audit later on.
|
|
//
|
|
|
|
RtlCopyMemory( &LocalUserName, &UserAll->UserName, sizeof(LocalUserName) );
|
|
}
|
|
|
|
//
|
|
// pickup RelativeId from looked up information.
|
|
//
|
|
|
|
RelativeId = UserAll->UserId;
|
|
|
|
//
|
|
// If the account type isn't allowed,
|
|
// Treat this as though the User Account doesn't exist.
|
|
//
|
|
// SubAuthentication packages can be more specific than this test but
|
|
// they can't become less restrictive.
|
|
//
|
|
|
|
if ( (UserAccountControl & UserAll->UserAccountControl) == 0 ) {
|
|
*Authoritative = FALSE;
|
|
Status = STATUS_NO_SUCH_USER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// determine if machine account, if so, certain failures are treated
|
|
// as Authoritative, to prevent fallback to guest and returning incorrect
|
|
// error codes.
|
|
//
|
|
|
|
if ( (UserAll->UserAccountControl & USER_MACHINE_ACCOUNT_MASK) != 0 ) {
|
|
fMachineAccount = TRUE;
|
|
} else {
|
|
fMachineAccount = FALSE;
|
|
}
|
|
|
|
OLD_TO_NEW_LARGE_INTEGER( UserAll->AccountExpires, AccountExpires );
|
|
|
|
//
|
|
// If there is a SubAuthentication DLL,
|
|
// call it to do all the authentication work.
|
|
//
|
|
|
|
if ( (LogonInfo->ParameterControl & MSV1_0_SUBAUTHENTICATION_DLL) &&
|
|
(!(LogonInfo->ParameterControl & MSV1_0_SUBAUTHENTICATION_DLL_EX))) {
|
|
|
|
ULONG LocalUserFlags = 0;
|
|
ULONG Flags = 0;
|
|
|
|
//
|
|
// Ensure the account isn't locked out.
|
|
//
|
|
// admin (rid==500) is not locked out iff in either of the following cases:
|
|
// 1) on console and interactive logon
|
|
// 2) DOMAIN_LOCKOUT_ADMINS is not set
|
|
//
|
|
// We did this regardless before NT 5.0. Now, we do it when either
|
|
// No SubAuth package is specified or
|
|
// New SubAuth package asks us to do account lockout test
|
|
// But, for those who call with old SubAuth packages, they will expect
|
|
// us to do the dirty work.
|
|
//
|
|
|
|
if ( (UserAll->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED) &&
|
|
(SecureChannelType != NullSecureChannel) )
|
|
{
|
|
BOOL LockOut = TRUE;
|
|
|
|
if (RelativeId == DOMAIN_USER_RID_ADMIN) {
|
|
|
|
//
|
|
// if the process is configured to allow over-ride
|
|
// (true if physical console logon), then don't enforce lockout.
|
|
//
|
|
|
|
if ((LogonLevel == NetlogonInteractiveInformation)
|
|
&& ( NtLmCheckProcessOption( MSV1_0_OPTION_DISABLE_ADMIN_LOCKOUT ) & MSV1_0_OPTION_DISABLE_ADMIN_LOCKOUT )) {
|
|
|
|
LockOut = FALSE;
|
|
|
|
} else {
|
|
|
|
PDOMAIN_PASSWORD_INFORMATION DomainPasswordInfo = NULL;
|
|
Status = I_SamrQueryInformationDomain(
|
|
DomainHandle,
|
|
DomainPasswordInformation,
|
|
(PSAMPR_DOMAIN_INFO_BUFFER *)&DomainPasswordInfo );
|
|
if (!NT_SUCCESS(Status)) {
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
*Authoritative = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
if (0 == (DomainPasswordInfo->PasswordProperties & DOMAIN_LOCKOUT_ADMINS)) {
|
|
|
|
LockOut = FALSE;
|
|
}
|
|
SamFreeMemory(DomainPasswordInfo);
|
|
}
|
|
}
|
|
if (LockOut) {
|
|
|
|
//
|
|
// Since the UI strongly encourages admins to disable user
|
|
// accounts rather than delete them. Treat disabled acccount as
|
|
// non-authoritative allowing the search to continue for other
|
|
// accounts by the same name.
|
|
//
|
|
|
|
if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) {
|
|
*Authoritative = fMachineAccount;
|
|
} else {
|
|
*Authoritative = TRUE;
|
|
}
|
|
|
|
Status = STATUS_ACCOUNT_LOCKED_OUT;
|
|
goto Cleanup;
|
|
|
|
}
|
|
}
|
|
|
|
if ( SecureChannelType != MsvApSecureChannel ) {
|
|
Flags |= MSV1_0_PASSTHRU;
|
|
}
|
|
if ( GuestRelativeId != 0 ) {
|
|
Flags |= MSV1_0_GUEST_LOGON;
|
|
}
|
|
|
|
Status = Msv1_0SubAuthenticationRoutine(
|
|
LogonLevel,
|
|
LogonInformation,
|
|
Flags,
|
|
(PUSER_ALL_INFORMATION) UserAll,
|
|
&WhichFields,
|
|
&LocalUserFlags,
|
|
Authoritative,
|
|
&LogoffTime,
|
|
&KickoffTime );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Sanity check what the SubAuthentication package returned
|
|
//
|
|
if ( (WhichFields & ~USER_ALL_PARAMETERS) != 0 ) {
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
*Authoritative = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
UserFlags |= LocalUserFlags;
|
|
|
|
} else { // we may still have an NT 5.0 SubAuth dll
|
|
|
|
//
|
|
// If there is an NT 5.0 SubAuthentication DLL,
|
|
// call it to do all the authentication work.
|
|
//
|
|
|
|
if ( (LogonInfo->ParameterControl & MSV1_0_SUBAUTHENTICATION_DLL_EX))
|
|
{
|
|
ULONG Flags = 0;
|
|
|
|
if ( SecureChannelType != MsvApSecureChannel ) {
|
|
Flags |= MSV1_0_PASSTHRU;
|
|
}
|
|
if ( GuestRelativeId != 0 ) {
|
|
Flags |= MSV1_0_GUEST_LOGON;
|
|
}
|
|
|
|
Status = Msv1_0SubAuthenticationRoutineEx(
|
|
LogonLevel,
|
|
LogonInformation,
|
|
Flags,
|
|
(PUSER_ALL_INFORMATION) UserAll,
|
|
(SAM_HANDLE)UserHandle,
|
|
&SubAuthValidationInformation,
|
|
&ActionsPerformed );
|
|
|
|
*Authoritative = SubAuthValidationInformation.Authoritative;
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
// We need to do this because even if any of the following checks
|
|
// fail, ARAP stills wants the returned blobs from the subauth
|
|
// package to be returned to the caller.
|
|
|
|
fSubAuthEx = TRUE;
|
|
}
|
|
|
|
//
|
|
// Ensure the account isn't locked out.
|
|
//
|
|
// admin (rid==500) is not locked out iff in either of the following cases:
|
|
// 1) on console and interactive logon
|
|
// 2) DOMAIN_LOCKOUT_ADMINS is not set
|
|
//
|
|
|
|
if ((ActionsPerformed & MSV1_0_SUBAUTH_LOCKOUT) == 0)
|
|
{
|
|
if ( (UserAll->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED) &&
|
|
(SecureChannelType != NullSecureChannel) )
|
|
{
|
|
BOOL LockOut = TRUE;
|
|
|
|
if (RelativeId == DOMAIN_USER_RID_ADMIN) {
|
|
|
|
//
|
|
// if the process is configured to allow over-ride
|
|
// (true if physical console logon), then don't enforce lockout.
|
|
//
|
|
|
|
if ((LogonLevel == NetlogonInteractiveInformation)
|
|
&& ( NtLmCheckProcessOption( MSV1_0_OPTION_DISABLE_ADMIN_LOCKOUT ) & MSV1_0_OPTION_DISABLE_ADMIN_LOCKOUT )) {
|
|
|
|
LockOut = FALSE;
|
|
|
|
} else {
|
|
|
|
PDOMAIN_PASSWORD_INFORMATION DomainPasswordInfo = NULL;
|
|
Status = I_SamrQueryInformationDomain(
|
|
DomainHandle,
|
|
DomainPasswordInformation,
|
|
(PSAMPR_DOMAIN_INFO_BUFFER *)&DomainPasswordInfo );
|
|
if (!NT_SUCCESS(Status)) {
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
*Authoritative = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
if (0 == (DomainPasswordInfo->PasswordProperties & DOMAIN_LOCKOUT_ADMINS)) {
|
|
|
|
LockOut = FALSE;
|
|
}
|
|
SamFreeMemory(DomainPasswordInfo);
|
|
}
|
|
}
|
|
|
|
if (LockOut) {
|
|
|
|
//
|
|
// Since the UI strongly encourages admins to disable user
|
|
// accounts rather than delete them. Treat disabled acccount as
|
|
// non-authoritative allowing the search to continue for other
|
|
// accounts by the same name.
|
|
//
|
|
|
|
if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) {
|
|
*Authoritative = fMachineAccount;
|
|
} else {
|
|
*Authoritative = TRUE;
|
|
}
|
|
|
|
Status = STATUS_ACCOUNT_LOCKED_OUT;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check the password if there's no subauth or if subauth did
|
|
// not already check password.
|
|
//
|
|
|
|
if ((ActionsPerformed & MSV1_0_SUBAUTH_PASSWORD) == 0)
|
|
{
|
|
if ( SecureChannelType != NullSecureChannel ) {
|
|
USER_INTERNAL1_INFORMATION Passwords;
|
|
|
|
//
|
|
// Copy the password info to the right structure.
|
|
//
|
|
|
|
Passwords.NtPasswordPresent = UserAll->NtPasswordPresent;
|
|
if ( UserAll->NtPasswordPresent ) {
|
|
Passwords.NtOwfPassword =
|
|
*((PNT_OWF_PASSWORD)(UserAll->NtOwfPassword.Buffer));
|
|
}
|
|
|
|
Passwords.LmPasswordPresent = UserAll->LmPasswordPresent;
|
|
if ( UserAll->LmPasswordPresent ) {
|
|
Passwords.LmOwfPassword =
|
|
*((PLM_OWF_PASSWORD)(UserAll->LmOwfPassword.Buffer));
|
|
}
|
|
|
|
|
|
//
|
|
// If the password specified doesn't match the SAM password,
|
|
// then we've got a password mismatch.
|
|
//
|
|
|
|
if ( ! MsvpPasswordValidate (
|
|
UasCompatibilityRequired,
|
|
LogonLevel,
|
|
LogonInformation,
|
|
&Passwords,
|
|
&UserFlags,
|
|
&UserSessionKey,
|
|
&LmSessionKey ) ) {
|
|
|
|
//
|
|
// If this is a guest logon and the guest account has no password,
|
|
// let the user log on.
|
|
//
|
|
// This special case check is after the MsvpPasswordValidate to
|
|
// give MsvpPasswordValidate every opportunity to compute the
|
|
// correct values for UserSessionKey and LmSessionKey.
|
|
//
|
|
|
|
if ( GuestRelativeId != 0 &&
|
|
!UserAll->NtPasswordPresent &&
|
|
!UserAll->LmPasswordPresent ) {
|
|
|
|
RtlZeroMemory( &UserSessionKey, sizeof(UserSessionKey) );
|
|
RtlZeroMemory( &LmSessionKey, sizeof(LmSessionKey) );
|
|
|
|
|
|
//
|
|
// The password mismatched. We treat STATUS_WRONG_PASSWORD as
|
|
// an authoritative response. Our caller may choose to do otherwise.
|
|
//
|
|
|
|
} else {
|
|
|
|
Status = STATUS_WRONG_PASSWORD;
|
|
|
|
//
|
|
// Since the UI strongly encourages admins to disable user
|
|
// accounts rather than delete them. Treat disabled acccount as
|
|
// non-authoritative allowing the search to continue for other
|
|
// accounts by the same name.
|
|
//
|
|
if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) {
|
|
*Authoritative = fMachineAccount;
|
|
} else {
|
|
*Authoritative = TRUE;
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If SubAuth DLL checked the password, then it implicitly
|
|
// checked the password's expiration, and we don't have to.
|
|
//
|
|
} else { // end if((ActionsPerformed & MSV1_0_SUBAUTH_PASSWORD) == 0)
|
|
ActionsPerformed |= MSV1_0_SUBAUTH_PASSWORD_EXPIRY;
|
|
}
|
|
|
|
//
|
|
// Check if the account is disabled if there's no subauth or if
|
|
// subauth has not already checked.
|
|
//
|
|
|
|
if ((ActionsPerformed & MSV1_0_SUBAUTH_ACCOUNT_DISABLED) == 0)
|
|
{
|
|
if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) {
|
|
//
|
|
// Since the UI strongly encourages admins to disable user
|
|
// accounts rather than delete them. Treat disabled acccount as
|
|
// non-authoritative allowing the search to continue for other
|
|
// accounts by the same name.
|
|
//
|
|
|
|
*Authoritative = fMachineAccount;
|
|
Status = STATUS_ACCOUNT_DISABLED;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Prevent some things from effecting the Administrator user
|
|
//
|
|
|
|
if (RelativeId != DOMAIN_USER_RID_ADMIN) {
|
|
|
|
//
|
|
// Check if the account has expired if there's no subauth or if
|
|
// subauth has not already checked.
|
|
//
|
|
|
|
if ((ActionsPerformed & MSV1_0_SUBAUTH_ACCOUNT_EXPIRY) == 0)
|
|
{
|
|
OLD_TO_NEW_LARGE_INTEGER( UserAll->AccountExpires, AccountExpires );
|
|
|
|
if ( AccountExpires.QuadPart != 0 &&
|
|
LogonTime.QuadPart >= AccountExpires.QuadPart ) {
|
|
*Authoritative = TRUE;
|
|
Status = STATUS_ACCOUNT_EXPIRED;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The password is valid, check to see if the password is expired.
|
|
// (SAM will have appropriately set PasswordMustChange to reflect
|
|
// USER_DONT_EXPIRE_PASSWORD)
|
|
//
|
|
// We only check password expiration if we also checked the password.
|
|
//
|
|
|
|
if ((ActionsPerformed & MSV1_0_SUBAUTH_PASSWORD_EXPIRY) == 0)
|
|
{
|
|
|
|
OLD_TO_NEW_LARGE_INTEGER( UserAll->PasswordMustChange, PasswordMustChange );
|
|
OLD_TO_NEW_LARGE_INTEGER( UserAll->PasswordLastSet, PasswordLastSet );
|
|
|
|
if ( SecureChannelType != NullSecureChannel ) {
|
|
if ( LogonTime.QuadPart >= PasswordMustChange.QuadPart ) {
|
|
|
|
if ( PasswordLastSet.QuadPart == 0 ) {
|
|
Status = STATUS_PASSWORD_MUST_CHANGE;
|
|
} else {
|
|
Status = STATUS_PASSWORD_EXPIRED;
|
|
}
|
|
*Authoritative = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Validate the workstation the user logged on from.
|
|
//
|
|
// Ditch leading \\ on workstation name before passing it to SAM.
|
|
//
|
|
|
|
LocalWorkstation = LogonInfo->Workstation;
|
|
if ( LocalWorkstation.Length > 0 &&
|
|
LocalWorkstation.Buffer[0] == L'\\' &&
|
|
LocalWorkstation.Buffer[1] == L'\\' ) {
|
|
LocalWorkstation.Buffer += 2;
|
|
LocalWorkstation.Length -= 2*sizeof(WCHAR);
|
|
LocalWorkstation.MaximumLength -= 2*sizeof(WCHAR);
|
|
}
|
|
|
|
//
|
|
// Check if SAM found some more specific reason to not allow logon.
|
|
//
|
|
|
|
Status = I_SamIAccountRestrictions(
|
|
UserHandle,
|
|
&LocalWorkstation,
|
|
(PUNICODE_STRING) &UserAll->WorkStations,
|
|
(PLOGON_HOURS) &UserAll->LogonHours,
|
|
&LogoffTime,
|
|
&KickoffTime );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
*Authoritative = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If there is a SubAuthentication package zero, call it
|
|
//
|
|
|
|
if (NlpSubAuthZeroExists) {
|
|
ULONG LocalUserFlags = 0;
|
|
ULONG Flags = 0;
|
|
|
|
if ( SecureChannelType != MsvApSecureChannel ) {
|
|
Flags |= MSV1_0_PASSTHRU;
|
|
}
|
|
if ( GuestRelativeId != 0 ) {
|
|
Flags |= MSV1_0_GUEST_LOGON;
|
|
}
|
|
|
|
Status = Msv1_0SubAuthenticationRoutineZero(
|
|
LogonLevel,
|
|
LogonInformation,
|
|
Flags,
|
|
(PUSER_ALL_INFORMATION) UserAll,
|
|
&WhichFields,
|
|
&LocalUserFlags,
|
|
Authoritative,
|
|
&LogoffTime,
|
|
&KickoffTime );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Sanity check what the SubAuthentication package returned
|
|
//
|
|
|
|
if ( (WhichFields & ~USER_ALL_PARAMETERS) != 0 ) {
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
*Authoritative = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
UserFlags |= LocalUserFlags;
|
|
}
|
|
}
|
|
|
|
if ( AccountExpires.QuadPart != 0
|
|
&& KickoffTime.QuadPart > AccountExpires.QuadPart )
|
|
{
|
|
KickoffTime = AccountExpires;
|
|
}
|
|
|
|
//
|
|
// If the account is a machine account,
|
|
// let the caller know he got the password right.
|
|
// (But don't let him actually log on).
|
|
//
|
|
|
|
// But, for NT 5.0, we must allow accounts with account control
|
|
// USER_WORKSTATION_TRUST_ACCOUNT for remote boot clients who
|
|
// will logon with their machine accounts
|
|
|
|
if ( (UserAll->UserAccountControl & USER_MACHINE_ACCOUNT_MASK) != 0 ) {
|
|
if (UserAll->UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT) {
|
|
Status = STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT;
|
|
} else if (UserAll->UserAccountControl &
|
|
USER_WORKSTATION_TRUST_ACCOUNT) {
|
|
if ( (LogonInfo->ParameterControl & MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT) == 0) {
|
|
Status = STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
|
|
} else {
|
|
|
|
UNICODE_STRING MachineAccountName;
|
|
NTSTATUS TempStatus;
|
|
|
|
//
|
|
// if the password was correct, and it happened to match
|
|
// the machine name, dis-allow it regardless.
|
|
//
|
|
|
|
//
|
|
// Compute the lower case user name.
|
|
//
|
|
|
|
TempStatus = RtlDowncaseUnicodeString( &MachineAccountName,
|
|
(PUNICODE_STRING)&UserAll->UserName,
|
|
TRUE );
|
|
|
|
if( NT_SUCCESS( TempStatus ) )
|
|
{
|
|
USHORT LastChar = MachineAccountName.Length / sizeof(WCHAR);
|
|
|
|
if( LastChar )
|
|
{
|
|
if( MachineAccountName.Buffer[LastChar-1] == L'$' )
|
|
{
|
|
MachineAccountName.Length -= sizeof(WCHAR);
|
|
}
|
|
|
|
if( LastChar > LM20_PWLEN )
|
|
{
|
|
MachineAccountName.Length = LM20_PWLEN * sizeof(WCHAR);
|
|
}
|
|
}
|
|
|
|
if ( UserAll->NtPasswordPresent ) {
|
|
|
|
NT_OWF_PASSWORD NtOwfMachineName;
|
|
NT_OWF_PASSWORD *pOwf;
|
|
|
|
pOwf = ((PNT_OWF_PASSWORD)(UserAll->NtOwfPassword.Buffer));
|
|
|
|
RtlCalculateNtOwfPassword(
|
|
&MachineAccountName,
|
|
&NtOwfMachineName );
|
|
|
|
if ( RtlCompareMemory( pOwf,
|
|
&NtOwfMachineName,
|
|
NT_OWF_PASSWORD_LENGTH ) ==
|
|
NT_OWF_PASSWORD_LENGTH ) {
|
|
|
|
Status = STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
|
|
}
|
|
}
|
|
|
|
|
|
RtlFreeUnicodeString( &MachineAccountName );
|
|
}
|
|
}
|
|
} else if (UserAll->UserAccountControl & USER_SERVER_TRUST_ACCOUNT) {
|
|
if ( (LogonInfo->ParameterControl & MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT) == 0) {
|
|
Status = STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
|
|
} else {
|
|
|
|
//
|
|
// it's a server trust account.
|
|
// treat same way as workstation trust account.
|
|
|
|
UNICODE_STRING MachineAccountName;
|
|
NTSTATUS TempStatus;
|
|
|
|
// NOTE: some code here is duplicated from above.
|
|
// this will be merged when a new Rtl has been added for
|
|
// computing initial machine password from machine acct name.
|
|
//
|
|
|
|
//
|
|
// if the password was correct, and it happened to match
|
|
// the machine name, dis-allow it regardless.
|
|
//
|
|
|
|
//
|
|
// Compute the lower case user name.
|
|
//
|
|
|
|
TempStatus = RtlDowncaseUnicodeString( &MachineAccountName,
|
|
(PUNICODE_STRING)&UserAll->UserName,
|
|
TRUE );
|
|
|
|
if( NT_SUCCESS( TempStatus ) )
|
|
{
|
|
USHORT LastChar = MachineAccountName.Length / sizeof(WCHAR);
|
|
|
|
if( LastChar )
|
|
{
|
|
if( MachineAccountName.Buffer[LastChar-1] == L'$' )
|
|
{
|
|
MachineAccountName.Length -= sizeof(WCHAR);
|
|
}
|
|
|
|
if( LastChar > LM20_PWLEN )
|
|
{
|
|
MachineAccountName.Length = LM20_PWLEN * sizeof(WCHAR);
|
|
}
|
|
}
|
|
|
|
if ( UserAll->NtPasswordPresent ) {
|
|
|
|
NT_OWF_PASSWORD NtOwfMachineName;
|
|
NT_OWF_PASSWORD *pOwf;
|
|
|
|
pOwf = ((PNT_OWF_PASSWORD)(UserAll->NtOwfPassword.Buffer));
|
|
|
|
RtlCalculateNtOwfPassword(
|
|
&MachineAccountName,
|
|
&NtOwfMachineName );
|
|
|
|
if ( RtlCompareMemory( pOwf,
|
|
&NtOwfMachineName,
|
|
NT_OWF_PASSWORD_LENGTH ) ==
|
|
NT_OWF_PASSWORD_LENGTH ) {
|
|
|
|
Status = STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
|
|
}
|
|
}
|
|
|
|
RtlFreeUnicodeString( &MachineAccountName );
|
|
}
|
|
|
|
//
|
|
// Let the client know that this was
|
|
// a server trust account
|
|
//
|
|
|
|
UserFlags |= LOGON_SERVER_TRUST_ACCOUNT;
|
|
}
|
|
|
|
} else {
|
|
Status = STATUS_NO_SUCH_USER;
|
|
}
|
|
if (!NT_SUCCESS(Status)) {
|
|
*Authoritative = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// don't allow blank password logons.
|
|
//
|
|
|
|
if(
|
|
(RelativeId != DOMAIN_USER_RID_GUEST)
|
|
&&
|
|
(!NtLmGlobalAllowBlankPassword)
|
|
&&
|
|
(!UserAll->NtPasswordPresent || (RtlCompareMemory(
|
|
&NlpNullNtOwfPassword,
|
|
UserAll->NtOwfPassword.Buffer,
|
|
NT_OWF_PASSWORD_LENGTH
|
|
) == NT_OWF_PASSWORD_LENGTH))
|
|
&&
|
|
(!UserAll->LmPasswordPresent || (RtlCompareMemory(
|
|
&NlpNullLmOwfPassword,
|
|
UserAll->LmOwfPassword.Buffer,
|
|
LM_OWF_PASSWORD_LENGTH
|
|
) == LM_OWF_PASSWORD_LENGTH))
|
|
&&
|
|
( ((NtLmCheckProcessOption( MSV1_0_OPTION_ALLOW_BLANK_PASSWORD )) & MSV1_0_OPTION_ALLOW_BLANK_PASSWORD) == 0 )
|
|
)
|
|
{
|
|
*Authoritative = FALSE;
|
|
Status = STATUS_ACCOUNT_RESTRICTION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Filter the groups into global groups (from other domains) and local
|
|
// groups (from this domain).
|
|
//
|
|
|
|
Status = MsvpFilterGroupMembership(
|
|
&GroupMembership,
|
|
LogonDomainId,
|
|
&GroupsBuffer,
|
|
&GlobalGroupMembership,
|
|
&GlobalMembershipSize
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
*Authoritative = FALSE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if (NT_SUCCESS(Status) || fSubAuthEx)
|
|
{
|
|
UNICODE_STRING ReturnDnsDomainName;
|
|
BOOLEAN UseDefaultUpn = FALSE;
|
|
|
|
//
|
|
// Allocate a return buffer for validation information.
|
|
// (Return less information for a network logon)
|
|
// (Return UserParameters for a MNS logon)
|
|
//
|
|
|
|
ValidationSamSize = sizeof( NETLOGON_VALIDATION_SAM_INFO4 ) +
|
|
GroupsBuffer.MembershipCount * sizeof(GROUP_MEMBERSHIP) +
|
|
LogonDomainName->Length + sizeof(WCHAR) +
|
|
LogonServer->Length + sizeof(WCHAR) +
|
|
RtlLengthSid( LogonDomainId );
|
|
|
|
//
|
|
// all logon types get the username, as, this could be mapped
|
|
// to guest account, for instance, and logon session needs correct
|
|
// names.
|
|
//
|
|
|
|
ValidationSamSize +=
|
|
UserAll->UserName.Length + sizeof(WCHAR) ;
|
|
|
|
if ( LogonLevel != NetlogonNetworkInformation ) {
|
|
ValidationSamSize +=
|
|
UserAll->FullName.Length + sizeof(WCHAR) +
|
|
UserAll->ScriptPath.Length + sizeof(WCHAR)+
|
|
UserAll->ProfilePath.Length + sizeof(WCHAR) +
|
|
UserAll->HomeDirectory.Length + sizeof(WCHAR) +
|
|
UserAll->HomeDirectoryDrive.Length + sizeof(WCHAR);
|
|
}
|
|
|
|
if ( LogonInfo->ParameterControl & MSV1_0_RETURN_USER_PARAMETERS ) {
|
|
ValidationSamSize +=
|
|
UserAll->Parameters.Length + sizeof(WCHAR);
|
|
} else if ( LogonInfo->ParameterControl & MSV1_0_RETURN_PROFILE_PATH ) {
|
|
ValidationSamSize +=
|
|
UserAll->ProfilePath.Length + sizeof(WCHAR);
|
|
}
|
|
|
|
//
|
|
// If the caller can handle extra groups, let them have the groups from
|
|
// other domains.
|
|
//
|
|
|
|
if ( ValidationLevel == NetlogonValidationSamInfo2 ||
|
|
ValidationLevel == NetlogonValidationSamInfo4 ) {
|
|
ValidationSamSize += GlobalMembershipSize;
|
|
}
|
|
|
|
//
|
|
// If the caller wants the logon domain in DNS and UPN form,
|
|
// grab it.
|
|
//
|
|
|
|
if ( ValidationLevel == NetlogonValidationSamInfo4 ) {
|
|
|
|
//
|
|
// Grab the dns name of the account domain.
|
|
//
|
|
|
|
RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
|
|
|
|
if( NlpWorkstation ) {
|
|
//ReturnDnsDomainName = NtLmGlobalUnicodeDnsComputerNameString;
|
|
//
|
|
// for local accounts, DnsDomainName doesn't exist.
|
|
//
|
|
|
|
RtlInitUnicodeString( &ReturnDnsDomainName, L"" );
|
|
} else {
|
|
ReturnDnsDomainName = NtLmGlobalUnicodeDnsDomainNameString;
|
|
}
|
|
|
|
ValidationSamSize += ReturnDnsDomainName.Length + sizeof(WCHAR);
|
|
|
|
//
|
|
// If we couldn't get the UPN from SAM,
|
|
// build the default one.
|
|
//
|
|
|
|
if( Upn.Buffer != NULL )
|
|
{
|
|
UseDefaultUpn = FALSE;
|
|
|
|
ValidationSamSize += (Upn.Length + sizeof(WCHAR));
|
|
|
|
} else {
|
|
UseDefaultUpn = TRUE;
|
|
|
|
if ( !NlpWorkstation )
|
|
{
|
|
ValidationSamSize +=
|
|
UserAll->UserName.Length +
|
|
sizeof(WCHAR) +
|
|
ReturnDnsDomainName.Length + sizeof(WCHAR);
|
|
}
|
|
}
|
|
}
|
|
|
|
ValidationSamSize = ROUND_UP_COUNT( ValidationSamSize, sizeof(WCHAR) );
|
|
|
|
ValidationSam = MIDL_user_allocate( ValidationSamSize );
|
|
|
|
if ( ValidationSam == NULL ) {
|
|
*Authoritative = FALSE;
|
|
fSubAuthEx = FALSE; // avoid nasty loop condition
|
|
Status = STATUS_NO_MEMORY;
|
|
RtlReleaseResource(&NtLmGlobalCritSect);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Default unused fields (and ExpansionRoom) to zero.
|
|
//
|
|
|
|
RtlZeroMemory( ValidationSam, ValidationSamSize );
|
|
|
|
//
|
|
// Copy the scalars to the validation buffer.
|
|
//
|
|
|
|
Where = (PUCHAR) (ValidationSam + 1);
|
|
|
|
if ((SubAuthValidationInformation.WhichFields & MSV1_0_VALIDATION_LOGOFF_TIME) != 0) {
|
|
NEW_TO_OLD_LARGE_INTEGER( SubAuthValidationInformation.LogoffTime, ValidationSam->LogoffTime );
|
|
}
|
|
else {
|
|
NEW_TO_OLD_LARGE_INTEGER( LogoffTime, ValidationSam->LogoffTime );
|
|
}
|
|
|
|
if ((SubAuthValidationInformation.WhichFields & MSV1_0_VALIDATION_KICKOFF_TIME) != 0) {
|
|
NEW_TO_OLD_LARGE_INTEGER( SubAuthValidationInformation.KickoffTime, ValidationSam->KickOffTime );
|
|
}
|
|
else {
|
|
NEW_TO_OLD_LARGE_INTEGER( KickoffTime, ValidationSam->KickOffTime );
|
|
}
|
|
|
|
if ((SubAuthValidationInformation.WhichFields & MSV1_0_VALIDATION_SESSION_KEY) != 0) {
|
|
ValidationSam->UserSessionKey = SubAuthValidationInformation.SessionKey;
|
|
}
|
|
else {
|
|
ValidationSam->UserSessionKey = UserSessionKey;
|
|
}
|
|
|
|
if ((SubAuthValidationInformation.WhichFields & MSV1_0_VALIDATION_USER_FLAGS) != 0) {
|
|
ValidationSam->UserFlags = SubAuthValidationInformation.UserFlags;
|
|
}
|
|
else {
|
|
ValidationSam->UserFlags = UserFlags;
|
|
}
|
|
|
|
if ((SubAuthValidationInformation.WhichFields & MSV1_0_VALIDATION_USER_ID) != 0) {
|
|
ValidationSam->UserId = SubAuthValidationInformation.UserId;
|
|
}
|
|
else {
|
|
ValidationSam->UserId = UserAll->UserId;
|
|
}
|
|
ValidationSam->LogonTime = UserAll->LastLogon;
|
|
ValidationSam->PasswordLastSet = UserAll->PasswordLastSet;
|
|
ValidationSam->PasswordCanChange = UserAll->PasswordCanChange;
|
|
ValidationSam->PasswordMustChange = UserAll->PasswordMustChange;
|
|
|
|
ValidationSam->LogonCount = UserAll->LogonCount;
|
|
ValidationSam->BadPasswordCount = UserAll->BadPasswordCount;
|
|
ValidationSam->PrimaryGroupId = UserAll->PrimaryGroupId;
|
|
ValidationSam->GroupCount = GroupsBuffer.MembershipCount;
|
|
ASSERT( SAMINFO_LM_SESSION_KEY_SIZE == sizeof(LmSessionKey) );
|
|
RtlCopyMemory( &ValidationSam->ExpansionRoom[SAMINFO_LM_SESSION_KEY],
|
|
&LmSessionKey,
|
|
SAMINFO_LM_SESSION_KEY_SIZE );
|
|
ValidationSam->ExpansionRoom[SAMINFO_USER_ACCOUNT_CONTROL] = UserAll->UserAccountControl;
|
|
|
|
// Save any status for subuath users not returned by the subauth package
|
|
|
|
if (fSubAuthEx)
|
|
{
|
|
ValidationSam->ExpansionRoom[SAMINFO_SUBAUTH_STATUS] = Status;
|
|
}
|
|
|
|
//
|
|
// Copy ULONG aligned data to the validation buffer.
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
Where,
|
|
GroupsBuffer.Groups,
|
|
GroupsBuffer.MembershipCount * sizeof(GROUP_MEMBERSHIP) );
|
|
|
|
ValidationSam->GroupIds = (PGROUP_MEMBERSHIP) Where;
|
|
Where += GroupsBuffer.MembershipCount * sizeof(GROUP_MEMBERSHIP);
|
|
|
|
RtlCopyMemory(
|
|
Where,
|
|
LogonDomainId,
|
|
RtlLengthSid( LogonDomainId ) );
|
|
|
|
ValidationSam->LogonDomainId = (PSID) Where;
|
|
Where += RtlLengthSid( LogonDomainId );
|
|
|
|
//
|
|
// If the client asked for extra information, return that
|
|
// we support it
|
|
//
|
|
|
|
if ( ValidationLevel == NetlogonValidationSamInfo2 ||
|
|
ValidationLevel == NetlogonValidationSamInfo4 ) {
|
|
|
|
ValidationSam->UserFlags |= LOGON_EXTRA_SIDS;
|
|
if (GlobalMembershipSize != 0) {
|
|
ULONG SidLength;
|
|
|
|
ValidationSam->SidCount = GlobalGroupMembership.Count;
|
|
ValidationSam->ExtraSids = (PNETLOGON_SID_AND_ATTRIBUTES) Where;
|
|
Where += ValidationSam->SidCount * sizeof(NETLOGON_SID_AND_ATTRIBUTES);
|
|
|
|
//
|
|
// Copy all the extra sids into the buffer
|
|
//
|
|
|
|
for (Index = 0; Index < ValidationSam->SidCount ; Index++ ) {
|
|
ValidationSam->ExtraSids[Index].Attributes = GlobalGroupMembership.SidAndAttributes[Index].Attributes;
|
|
ValidationSam->ExtraSids[Index].Sid = Where;
|
|
SidLength = RtlLengthSid(GlobalGroupMembership.SidAndAttributes[Index].Sid);
|
|
RtlCopyMemory(
|
|
ValidationSam->ExtraSids[Index].Sid,
|
|
GlobalGroupMembership.SidAndAttributes[Index].Sid,
|
|
SidLength
|
|
);
|
|
|
|
Where += SidLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy WCHAR aligned data to the validation buffer.
|
|
// (Return less information for a network logon)
|
|
//
|
|
|
|
Where = ROUND_UP_POINTER( Where, sizeof(WCHAR) );
|
|
|
|
NlpPutString( &ValidationSam->EffectiveName,
|
|
(PUNICODE_STRING)&UserAll->UserName,
|
|
&Where );
|
|
|
|
if ( LogonLevel != NetlogonNetworkInformation ) {
|
|
|
|
NlpPutString( &ValidationSam->FullName,
|
|
(PUNICODE_STRING)&UserAll->FullName,
|
|
&Where );
|
|
|
|
NlpPutString( &ValidationSam->LogonScript,
|
|
(PUNICODE_STRING)&UserAll->ScriptPath,
|
|
&Where );
|
|
|
|
NlpPutString( &ValidationSam->ProfilePath,
|
|
(PUNICODE_STRING)&UserAll->ProfilePath,
|
|
&Where );
|
|
|
|
NlpPutString( &ValidationSam->HomeDirectory,
|
|
(PUNICODE_STRING)&UserAll->HomeDirectory,
|
|
&Where );
|
|
|
|
NlpPutString( &ValidationSam->HomeDirectoryDrive,
|
|
(PUNICODE_STRING)&UserAll->HomeDirectoryDrive,
|
|
&Where );
|
|
|
|
}
|
|
|
|
if ((SubAuthValidationInformation.WhichFields & MSV1_0_VALIDATION_LOGON_SERVER) != 0) {
|
|
NlpPutString( &ValidationSam->LogonServer,
|
|
&SubAuthValidationInformation.LogonServer,
|
|
&Where );
|
|
}
|
|
else {
|
|
NlpPutString( &ValidationSam->LogonServer,
|
|
LogonServer,
|
|
&Where );
|
|
|
|
}
|
|
if ((SubAuthValidationInformation.WhichFields & MSV1_0_VALIDATION_LOGON_DOMAIN) != 0) {
|
|
NlpPutString( &ValidationSam->LogonDomainName,
|
|
&SubAuthValidationInformation.LogonDomainName,
|
|
&Where );
|
|
}
|
|
else {
|
|
NlpPutString( &ValidationSam->LogonDomainName,
|
|
LogonDomainName,
|
|
&Where );
|
|
}
|
|
|
|
//
|
|
// If the caller wants the logon domain in DNS and UPN form,
|
|
// return them.
|
|
//
|
|
|
|
if ( ValidationLevel == NetlogonValidationSamInfo4 ) {
|
|
|
|
//
|
|
// Copy the DNS domain name into the allocated buffer
|
|
//
|
|
|
|
NlpPutString( &ValidationSam->DnsLogonDomainName,
|
|
&ReturnDnsDomainName,
|
|
&Where );
|
|
|
|
//
|
|
// If we couldn't get the UPN from SAM,
|
|
// copy the default one into the buffer.
|
|
//
|
|
|
|
if ( !NlpWorkstation )
|
|
{
|
|
if( UseDefaultUpn )
|
|
{
|
|
UNICODE_STRING TempString1;
|
|
UNICODE_STRING TempString2;
|
|
UNICODE_STRING TempString3;
|
|
UNICODE_STRING AtString;
|
|
|
|
NlpPutString( &TempString1,
|
|
(PUNICODE_STRING)&UserAll->UserName,
|
|
&Where );
|
|
Where -= sizeof(WCHAR);
|
|
|
|
RtlInitUnicodeString( &AtString, L"@" );
|
|
NlpPutString( &TempString2,
|
|
&AtString,
|
|
&Where );
|
|
Where -= sizeof(WCHAR);
|
|
|
|
NlpPutString( &TempString3,
|
|
&ReturnDnsDomainName,
|
|
&Where );
|
|
|
|
ValidationSam->Upn.Buffer = TempString1.Buffer;
|
|
ValidationSam->Upn.Length =
|
|
TempString1.Length +
|
|
TempString2.Length +
|
|
TempString3.Length;
|
|
ValidationSam->Upn.MaximumLength = ValidationSam->Upn.Length + sizeof(WCHAR);
|
|
} else {
|
|
UNICODE_STRING TempString1;
|
|
|
|
NlpPutString( &TempString1,
|
|
&Upn,
|
|
&Where );
|
|
Where -= sizeof(WCHAR);
|
|
|
|
ValidationSam->Upn.Buffer = TempString1.Buffer;
|
|
ValidationSam->Upn.Length = TempString1.Length;
|
|
ValidationSam->Upn.MaximumLength = ValidationSam->Upn.Length;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Drop the lock that we've held since we grabbed pointer to the globals
|
|
//
|
|
RtlReleaseResource(&NtLmGlobalCritSect);
|
|
}
|
|
|
|
//
|
|
// Kludge: Pass back UserParameters in HomeDirectoryDrive since we
|
|
// can't change the NETLOGON_VALIDATION_SAM_INFO structure between
|
|
// releases NT 3.1 and NT 3.5. HomeDirectoryDrive was NULL for release 3.1
|
|
// so we'll use that field.
|
|
//
|
|
|
|
if ( LogonInfo->ParameterControl & MSV1_0_RETURN_USER_PARAMETERS ) {
|
|
NlpPutString( &ValidationSam->HomeDirectoryDrive,
|
|
(PUNICODE_STRING)&UserAll->Parameters,
|
|
&Where );
|
|
} else if ( LogonInfo->ParameterControl & MSV1_0_RETURN_PROFILE_PATH ) {
|
|
NlpPutString( &ValidationSam->HomeDirectoryDrive,
|
|
(PUNICODE_STRING)&UserAll->ProfilePath,
|
|
&Where );
|
|
ValidationSam->UserFlags |= LOGON_PROFILE_PATH_RETURNED;
|
|
}
|
|
|
|
*Authoritative = TRUE;
|
|
|
|
//
|
|
// For SubAuthEx, we save away the original Status to make decisions
|
|
// later on about additional processing to perform.
|
|
//
|
|
|
|
if( fSubAuthEx ) {
|
|
SubAuthExStatus = Status;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Cleanup up before returning.
|
|
//
|
|
|
|
//
|
|
// If the User Parameters have been changed,
|
|
// write them back to SAM.
|
|
//
|
|
|
|
if ( NT_SUCCESS(Status) &&
|
|
(WhichFields & USER_ALL_PARAMETERS) )
|
|
{
|
|
SAMPR_USER_INFO_BUFFER UserInfo;
|
|
|
|
UserInfo.Parameters.Parameters = UserAll->Parameters;
|
|
|
|
Status = I_SamrSetInformationUser(
|
|
UserHandle,
|
|
UserParametersInformation,
|
|
&UserInfo );
|
|
}
|
|
|
|
//
|
|
// Update the logon statistics.
|
|
//
|
|
|
|
if ( NT_SUCCESS( SubAuthExStatus ) &&
|
|
( NT_SUCCESS(Status)
|
|
|| Status == STATUS_WRONG_PASSWORD
|
|
|| Status == STATUS_NO_LOGON_SERVERS ) ) {
|
|
|
|
SAM_LOGON_STATISTICS LogonStats;
|
|
|
|
RtlZeroMemory(&LogonStats, sizeof(LogonStats));
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
if ( LogonLevel == NetlogonInteractiveInformation ) {
|
|
LogonStats.StatisticsToApply =
|
|
USER_LOGON_INTER_SUCCESS_LOGON;
|
|
} else {
|
|
|
|
//
|
|
// On network logons,
|
|
// only update the statistics on 'success' if explicitly asked,
|
|
// or the Bad Password count will be zeroed.
|
|
//
|
|
LogonStats.StatisticsToApply =
|
|
USER_LOGON_NET_SUCCESS_LOGON | USER_LOGON_NO_WRITE;
|
|
|
|
if ( (LogonInfo->ParameterControl & MSV1_0_UPDATE_LOGON_STATISTICS) ||
|
|
UserAll->BadPasswordCount != 0 ) {
|
|
|
|
LogonStats.StatisticsToApply &= ~USER_LOGON_NO_WRITE;
|
|
}
|
|
}
|
|
|
|
// Tell the caller we zeroed the bad password count
|
|
if ( UserAll->BadPasswordCount != 0 ) {
|
|
*BadPasswordCountZeroed = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (Status == STATUS_WRONG_PASSWORD) {
|
|
LogonStats.StatisticsToApply = USER_LOGON_BAD_PASSWORD_WKSTA;
|
|
LogonStats.Workstation = LogonInfo->Workstation;
|
|
|
|
//
|
|
// if it didn't match one of the (two) previous, it was a bad password.
|
|
//
|
|
|
|
if(!MsvpCheckPreviousPassword(
|
|
UasCompatibilityRequired,
|
|
LogonLevel,
|
|
LogonInformation,
|
|
DomainHandle,
|
|
&LocalUserName
|
|
))
|
|
{
|
|
LogonStats.StatisticsToApply |= USER_LOGON_BAD_PASSWORD;
|
|
}
|
|
|
|
|
|
} else {
|
|
LogonStats.StatisticsToApply = USER_LOGON_NO_LOGON_SERVERS;
|
|
if (LogonLevel == NetlogonInteractiveInformation) {
|
|
LogonStats.StatisticsToApply |= USER_LOGON_INTER_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( LogonStats.StatisticsToApply != 0 ) {
|
|
NTSTATUS LogonStatus;
|
|
|
|
LogonStats.StatisticsToApply |= USER_LOGON_TYPE_NTLM;
|
|
|
|
LogonStatus = I_SamIUpdateLogonStatistics(
|
|
UserHandle,
|
|
&LogonStats );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Audit this logon. We don't audit failures for the guest account because
|
|
// they are so frequent.
|
|
//
|
|
|
|
if (GuestRelativeId == 0 || NT_SUCCESS(Status)) {
|
|
NTSTATUS AuditStatus;
|
|
|
|
AuditStatus = Status;
|
|
|
|
//
|
|
// if there was a possibly un-successful SubAuthEx status, use it
|
|
//
|
|
|
|
if( NT_SUCCESS( AuditStatus ) && fSubAuthEx ) {
|
|
|
|
AuditStatus = SubAuthExStatus;
|
|
}
|
|
|
|
if ( ValidationSam ) {
|
|
|
|
UserSid = NlpMakeDomainRelativeSid(
|
|
ValidationSam->LogonDomainId,
|
|
ValidationSam->UserId
|
|
);
|
|
} else {
|
|
|
|
UserSid = NULL;
|
|
}
|
|
|
|
I_LsaIAuditAccountLogonEx(
|
|
SE_AUDITID_ACCOUNT_LOGON,
|
|
(BOOLEAN) NT_SUCCESS(AuditStatus),
|
|
&NlpMsv1_0PackageName,
|
|
&LocalUserName,
|
|
&LogonInfo->Workstation,
|
|
AuditStatus,
|
|
UserSid
|
|
);
|
|
|
|
if ( ValidationSam && UserSid ) {
|
|
|
|
LsaFunctions->FreeLsaHeap( UserSid );
|
|
UserSid = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Return the validation buffer to the caller.
|
|
//
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
if (ValidationSam != NULL) {
|
|
MIDL_user_free( ValidationSam );
|
|
ValidationSam = NULL;
|
|
}
|
|
}
|
|
|
|
*ValidationInformation = ValidationSam;
|
|
|
|
//
|
|
// Free locally used resources.
|
|
//
|
|
|
|
I_SamIFree_SAMPR_RETURNED_USTRING_ARRAY( &NameArray );
|
|
I_SamIFree_SAMPR_ULONG_ARRAY( &UseArray );
|
|
|
|
if ( UserAllInfo != NULL ) {
|
|
I_SamIFree_SAMPR_USER_INFO_BUFFER( UserAllInfo, UserAllInformation );
|
|
}
|
|
|
|
if( Upn.Buffer != NULL )
|
|
{
|
|
I_SamIFreeVoid( Upn.Buffer );
|
|
}
|
|
|
|
if (GroupMembership.SidAndAttributes != NULL)
|
|
{
|
|
I_SamIFreeSidAndAttributesList(&GroupMembership);
|
|
}
|
|
|
|
if ( GroupsBuffer.Groups != NULL ) {
|
|
I_NtLmFree(GroupsBuffer.Groups);
|
|
}
|
|
|
|
if ( GlobalGroupMembership.SidAndAttributes != NULL ) {
|
|
I_NtLmFree(GlobalGroupMembership.SidAndAttributes);
|
|
}
|
|
|
|
if ( UserHandle != NULL ) {
|
|
I_SamrCloseHandle( &UserHandle );
|
|
}
|
|
|
|
if (SubAuthValidationInformation.LogonDomainName.Buffer != NULL) {
|
|
MIDL_user_free(SubAuthValidationInformation.LogonDomainName.Buffer);
|
|
}
|
|
if (SubAuthValidationInformation.LogonServer.Buffer != NULL) {
|
|
MIDL_user_free(SubAuthValidationInformation.LogonServer.Buffer);
|
|
}
|
|
|
|
if (LocalSidUser != NULL) {
|
|
I_NtLmFree(LocalSidUser);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
BOOLEAN
|
|
MsvpCheckPreviousPassword(
|
|
IN BOOLEAN UasCompatibilityRequired,
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation,
|
|
IN SAMPR_HANDLE DomainHandle,
|
|
PUNICODE_STRING UserName
|
|
)
|
|
{
|
|
PSAMI_PRIVATE_DATA_PASSWORD_RELATIVE_TYPE PrivateData;
|
|
NTSTATUS Status;
|
|
|
|
SAMPR_HANDLE UserHandle = NULL;
|
|
|
|
PSAMPR_USER_INFO_BUFFER UserAllInfo = NULL;
|
|
PSAMPR_USER_ALL_INFORMATION UserAll = NULL;
|
|
SID_AND_ATTRIBUTES_LIST GroupMembership;
|
|
|
|
USER_INTERNAL1_INFORMATION Passwords;
|
|
NT_OWF_PASSWORD OldPasswordData = {0}; // Previous password
|
|
NT_OWF_PASSWORD OldPasswordDataSecond = {0}; // Previous previous password
|
|
|
|
USER_SESSION_KEY UserSessionKey;
|
|
LM_SESSION_KEY LmSessionKey;
|
|
ULONG UserFlags;
|
|
|
|
BOOLEAN TryPrevious = FALSE;
|
|
BOOLEAN TryPreviousPrevious = FALSE;
|
|
BOOLEAN fMatchesOld = FALSE;
|
|
|
|
|
|
GroupMembership.SidAndAttributes = NULL;
|
|
|
|
//
|
|
// query the account, to get the PRIVATEDATA ( password history ).
|
|
//
|
|
|
|
Status = I_SamIGetUserLogonInformationEx(
|
|
DomainHandle,
|
|
SAM_NO_MEMBERSHIPS,
|
|
UserName,
|
|
USER_ALL_USERID | USER_ALL_PRIVATEDATA | USER_ALL_OWFPASSWORD,
|
|
&UserAllInfo,
|
|
&GroupMembership,
|
|
&UserHandle
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
UserAll = &UserAllInfo->All;
|
|
|
|
if (UserAll->PrivateData.Length >= sizeof(SAMI_PRIVATE_DATA_PASSWORD_RELATIVE_TYPE))
|
|
{
|
|
PrivateData= (PSAMI_PRIVATE_DATA_PASSWORD_RELATIVE_TYPE) UserAll->PrivateData.Buffer;
|
|
|
|
if (PrivateData->DataType == SamPrivateDataPassword)
|
|
{
|
|
//
|
|
// The old password is the 2nd entry
|
|
//
|
|
|
|
if (PrivateData->NtPasswordHistory.Length >= 2* sizeof(ENCRYPTED_NT_OWF_PASSWORD))
|
|
{
|
|
//
|
|
// Decrypt the old password with the RID. The history starts
|
|
// at the first byte after the structure.
|
|
//
|
|
|
|
Status = RtlDecryptNtOwfPwdWithIndex(
|
|
(PENCRYPTED_NT_OWF_PASSWORD) (PrivateData + 1) + 1,
|
|
(PLONG)&UserAll->UserId,
|
|
&OldPasswordData
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
TryPrevious = TRUE;
|
|
}
|
|
|
|
// Now check for the second previous password - this will be the third password in history
|
|
if (PrivateData->NtPasswordHistory.Length >= 3 * sizeof(ENCRYPTED_NT_OWF_PASSWORD))
|
|
{
|
|
//
|
|
// Decrypt the old password with the RID. The history starts
|
|
// at the first byte after the structure.
|
|
//
|
|
|
|
Status = RtlDecryptNtOwfPwdWithIndex(
|
|
(PENCRYPTED_NT_OWF_PASSWORD) (PrivateData + 1) + 2,
|
|
(PLONG)&UserAll->UserId,
|
|
&OldPasswordDataSecond
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
TryPreviousPrevious = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// call password validate for each.
|
|
//
|
|
|
|
|
|
Passwords.NtPasswordPresent = TRUE;
|
|
Passwords.LmPasswordPresent = FALSE;
|
|
|
|
if( TryPrevious )
|
|
{
|
|
CopyMemory( &(Passwords.NtOwfPassword), &OldPasswordData, sizeof(OldPasswordData) );
|
|
|
|
//
|
|
// If the password specified doesn't match the SAM password,
|
|
// then we've got a password mismatch.
|
|
//
|
|
|
|
if ( MsvpPasswordValidate (
|
|
UasCompatibilityRequired,
|
|
LogonLevel,
|
|
LogonInformation,
|
|
&Passwords,
|
|
&UserFlags,
|
|
&UserSessionKey,
|
|
&LmSessionKey ) )
|
|
{
|
|
|
|
RtlSecureZeroMemory( &UserSessionKey, sizeof(UserSessionKey) );
|
|
RtlSecureZeroMemory( &LmSessionKey, sizeof(LmSessionKey) );
|
|
|
|
fMatchesOld = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
if( TryPreviousPrevious )
|
|
{
|
|
|
|
CopyMemory( &(Passwords.NtOwfPassword), &OldPasswordDataSecond, sizeof(OldPasswordDataSecond) );
|
|
|
|
//
|
|
// If the password specified doesn't match the SAM password,
|
|
// then we've got a password mismatch.
|
|
//
|
|
|
|
if ( MsvpPasswordValidate (
|
|
UasCompatibilityRequired,
|
|
LogonLevel,
|
|
LogonInformation,
|
|
&Passwords,
|
|
&UserFlags,
|
|
&UserSessionKey,
|
|
&LmSessionKey ) )
|
|
{
|
|
|
|
RtlSecureZeroMemory( &UserSessionKey, sizeof(UserSessionKey) );
|
|
RtlSecureZeroMemory( &LmSessionKey, sizeof(LmSessionKey) );
|
|
|
|
fMatchesOld = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
Cleanup:
|
|
|
|
|
|
RtlSecureZeroMemory( &OldPasswordData, sizeof(OldPasswordData) );
|
|
RtlSecureZeroMemory( &OldPasswordDataSecond, sizeof(OldPasswordDataSecond) );
|
|
RtlSecureZeroMemory( &(Passwords.NtOwfPassword), sizeof(Passwords.NtOwfPassword) );
|
|
|
|
|
|
if (GroupMembership.SidAndAttributes != NULL)
|
|
{
|
|
I_SamIFreeSidAndAttributesList(&GroupMembership);
|
|
}
|
|
|
|
if ( UserAllInfo != NULL ) {
|
|
I_SamIFree_SAMPR_USER_INFO_BUFFER( UserAllInfo, UserAllInformation );
|
|
}
|
|
|
|
if ( UserHandle != NULL ) {
|
|
I_SamrCloseHandle( &UserHandle );
|
|
}
|
|
|
|
return fMatchesOld;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
MsvSamValidate (
|
|
IN SAM_HANDLE DomainHandle,
|
|
IN BOOLEAN UasCompatibilityRequired,
|
|
IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
|
|
IN PUNICODE_STRING LogonServer,
|
|
IN PUNICODE_STRING LogonDomainName,
|
|
IN PSID LogonDomainId,
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation,
|
|
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
|
|
OUT PVOID * ValidationInformation,
|
|
OUT PBOOLEAN Authoritative,
|
|
OUT PBOOLEAN BadPasswordCountZeroed,
|
|
IN DWORD AccountsToTry
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Process an interactive, network, or session logon. It calls
|
|
SamIUserValidation, validates the passed in credentials, updates the logon
|
|
statistics and packages the result for return to the caller.
|
|
|
|
This routine is called directly from the MSV Authentication package
|
|
if the account is defined locally. This routine is called
|
|
from the Netlogon Service otherwise.
|
|
|
|
Arguments:
|
|
|
|
DomainHandle -- Specifies a handle to the SamDomain to use to
|
|
validate the request.
|
|
|
|
UasCompatibilityRequired -- TRUE if UasCompatibilityRequired is on.
|
|
|
|
SecureChannelType -- The secure channel type this request was made on.
|
|
|
|
LogonServer -- Specifies the server name of the caller.
|
|
|
|
LogonDomainName -- Specifies the domain of the caller.
|
|
|
|
LogonDomainId -- Specifies the DomainId of the domain of the caller.
|
|
|
|
LogonLevel -- Specifies the level of information given in
|
|
LogonInformation.
|
|
|
|
LogonInformation -- Specifies the description for the user
|
|
logging on. The LogonDomainName field should be ignored.
|
|
The caller is responsible for validating this field.
|
|
|
|
ValidationLevel -- Specifies the level of information returned in
|
|
ValidationInformation. Must be NetlogonValidationSamInfo,
|
|
NetlogonValidationSamInfo2 or NetlogonValidationSamInfo4
|
|
|
|
ValidationInformation -- Returns the requested validation
|
|
information. This buffer must be freed user MIDL_user_free.
|
|
|
|
Authoritative -- Returns whether the status returned is an
|
|
authoritative status which should be returned to the original
|
|
caller. If not, this logon request may be tried again on another
|
|
domain controller. This parameter is returned regardless of the
|
|
status code.
|
|
|
|
BadPasswordCountZeroed - Returns TRUE if we zeroed the BadPasswordCount
|
|
field of this user.
|
|
|
|
AccountsToTry -- Specifies whether the username specified in
|
|
LogonInformation is to be used to logon, whether to guest account
|
|
is to be used to logon, or both serially.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: if there was no error.
|
|
STATUS_INVALID_INFO_CLASS: LogonLevel or ValidationLevel are invalid.
|
|
STATUS_NO_SUCH_USER: The specified user has no account.
|
|
STATUS_WRONG_PASSWORD: The password was invalid.
|
|
|
|
Other return codes from SamIUserValidation
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
NTSTATUS GuestStatus;
|
|
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
|
|
|
|
//
|
|
// Tracing
|
|
//
|
|
NTLM_TRACE_INFO TraceInfo = {0};
|
|
|
|
//
|
|
// Begin tracing a sam validate call for NTLM
|
|
//
|
|
if (NtlmGlobalEventTraceFlag){
|
|
|
|
|
|
//Header goo
|
|
SET_TRACE_HEADER(TraceInfo,
|
|
NtlmValidateGuid,
|
|
EVENT_TRACE_TYPE_START,
|
|
WNODE_FLAG_TRACED_GUID,
|
|
0);
|
|
|
|
TraceEvent(NtlmGlobalTraceLoggerHandle,
|
|
(PEVENT_TRACE_HEADER)&TraceInfo);
|
|
}
|
|
|
|
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
|
|
|
|
//
|
|
// if simple file/print option is enabled (applies to SSPI callers),
|
|
// check if ForceGuest should occur.
|
|
//
|
|
|
|
if( (LogonInfo->ParameterControl & MSV1_0_ALLOW_FORCE_GUEST) )
|
|
{
|
|
if( NtLmGlobalForceGuest )
|
|
{
|
|
if((LogonInfo->ParameterControl & MSV1_0_DISABLE_PERSONAL_FALLBACK)==0)
|
|
{
|
|
if( (LogonLevel == NetlogonNetworkInformation) &&
|
|
(((NtLmCheckProcessOption( MSV1_0_OPTION_DISABLE_FORCE_GUEST ) & MSV1_0_OPTION_DISABLE_FORCE_GUEST )) == 0)
|
|
)
|
|
{
|
|
AccountsToTry &= ~(MSVSAM_SPECIFIED);
|
|
AccountsToTry |= MSVSAM_GUEST;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Validate the specified user.
|
|
//
|
|
*BadPasswordCountZeroed = FALSE;
|
|
|
|
if ( AccountsToTry & MSVSAM_SPECIFIED ) {
|
|
|
|
//
|
|
// Keep track of the total number of logons attempted.
|
|
//
|
|
|
|
I_SamIIncrementPerformanceCounter(
|
|
MsvLogonCounter
|
|
);
|
|
InterlockedIncrement((LPLONG) &NlpLogonAttemptCount);
|
|
|
|
Status = MsvpSamValidate( (SAMPR_HANDLE) DomainHandle,
|
|
UasCompatibilityRequired,
|
|
SecureChannelType,
|
|
LogonServer,
|
|
LogonDomainName,
|
|
LogonDomainId,
|
|
LogonLevel,
|
|
LogonInformation,
|
|
0,
|
|
ValidationLevel,
|
|
ValidationInformation,
|
|
Authoritative,
|
|
BadPasswordCountZeroed );
|
|
|
|
|
|
//
|
|
// If the SAM database authoritatively handled this logon attempt,
|
|
// just return.
|
|
//
|
|
|
|
if ( *Authoritative ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If the caller only wants to log on as guest,
|
|
// Pretend the first validation simply didn't find the user.
|
|
//
|
|
} else {
|
|
*Authoritative = FALSE;
|
|
Status = STATUS_NO_SUCH_USER;
|
|
}
|
|
|
|
//
|
|
// If guest accounts are not allowed,
|
|
// return now.
|
|
//
|
|
|
|
if ( LogonLevel != NetlogonNetworkInformation ||
|
|
SecureChannelType != MsvApSecureChannel ||
|
|
( LogonInfo->ParameterControl & MSV1_0_DONT_TRY_GUEST_ACCOUNT ) ||
|
|
(AccountsToTry & MSVSAM_GUEST) == 0 ) {
|
|
|
|
goto Cleanup;
|
|
//return Status;
|
|
}
|
|
|
|
//
|
|
// Try the Guest Account.
|
|
//
|
|
|
|
GuestStatus = MsvpSamValidate( (SAMPR_HANDLE) DomainHandle,
|
|
UasCompatibilityRequired,
|
|
SecureChannelType,
|
|
LogonServer,
|
|
LogonDomainName,
|
|
LogonDomainId,
|
|
LogonLevel,
|
|
LogonInformation,
|
|
DOMAIN_USER_RID_GUEST,
|
|
ValidationLevel,
|
|
ValidationInformation,
|
|
Authoritative,
|
|
BadPasswordCountZeroed );
|
|
|
|
if ( NT_SUCCESS(GuestStatus) ) {
|
|
PNETLOGON_VALIDATION_SAM_INFO4 ValidationInfo;
|
|
|
|
ASSERT ((ValidationLevel == NetlogonValidationSamInfo) ||
|
|
(ValidationLevel == NetlogonValidationSamInfo2) ||
|
|
(ValidationLevel == NetlogonValidationSamInfo4) );
|
|
ValidationInfo =
|
|
(PNETLOGON_VALIDATION_SAM_INFO4) *ValidationInformation;
|
|
ValidationInfo->UserFlags |= LOGON_GUEST;
|
|
|
|
Status = GuestStatus;
|
|
goto Cleanup;
|
|
//return GuestStatus;
|
|
}
|
|
|
|
//
|
|
// Failed Guest logon attempts are never authoritative and the status from
|
|
// the original logon attempt is more significant than the Guest logon
|
|
// status.
|
|
//
|
|
*Authoritative = FALSE;
|
|
Cleanup:
|
|
|
|
//
|
|
// Trace the end of this call
|
|
//
|
|
if (NtlmGlobalEventTraceFlag){
|
|
UINT32 Success;
|
|
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo =
|
|
(PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
|
|
|
|
SET_TRACE_HEADER(TraceInfo,
|
|
NtlmValidateGuid,
|
|
EVENT_TRACE_TYPE_END,
|
|
WNODE_FLAG_TRACED_GUID|WNODE_FLAG_USE_MOF_PTR,
|
|
9);
|
|
//
|
|
// Build the "success" trace state bit mask
|
|
// 1bit - Success
|
|
// 2bit - Authoritative
|
|
//
|
|
Success = (Status == STATUS_SUCCESS)?1:0;
|
|
Success |= (*Authoritative)?2:0;
|
|
|
|
SET_TRACE_DATA(TraceInfo,
|
|
TRACE_VALIDATE_SUCCESS,
|
|
Success);
|
|
|
|
SET_TRACE_USTRING(TraceInfo,
|
|
TRACE_VALIDATE_SERVER,
|
|
(*LogonServer));
|
|
|
|
SET_TRACE_USTRING(TraceInfo,
|
|
TRACE_VALIDATE_DOMAIN,
|
|
(*LogonDomainName));
|
|
|
|
SET_TRACE_USTRING(TraceInfo,
|
|
TRACE_VALIDATE_USERNAME,
|
|
LogonInfo->UserName);
|
|
|
|
SET_TRACE_USTRING(TraceInfo,
|
|
TRACE_VALIDATE_WORKSTATION,
|
|
LogonInfo->Workstation);
|
|
|
|
TraceEvent(
|
|
NtlmGlobalTraceLoggerHandle,
|
|
(PEVENT_TRACE_HEADER)&TraceInfo
|
|
);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
MsvSamLogoff (
|
|
IN SAM_HANDLE DomainHandle,
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Process an interactive, network, or session logoff. It simply updates
|
|
the logon statistics for the user account.
|
|
|
|
This routine is called directly from the MSV Authentication package
|
|
if the user was logged on not using the Netlogon service. This routine
|
|
is called from the Netlogon Service otherwise.
|
|
|
|
Arguments:
|
|
|
|
DomainHandle -- Specifies a handle to the SamDomain containing
|
|
the user to logoff.
|
|
|
|
LogonLevel -- Specifies the level of information given in
|
|
LogonInformation.
|
|
|
|
LogonInformation -- Specifies the description for the user
|
|
logging on. The LogonDomainName field should be ignored.
|
|
The caller is responsible for validating this field.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: if there was no error.
|
|
STATUS_INVALID_INFO_CLASS: LogonLevel or ValidationLevel are invalid.
|
|
STATUS_NO_SUCH_USER: The specified user has no account.
|
|
STATUS_WRONG_PASSWORD: The password was invalid.
|
|
|
|
Other return codes from SamIUserValidation
|
|
|
|
--*/
|
|
{
|
|
return(STATUS_SUCCESS);
|
|
UNREFERENCED_PARAMETER( DomainHandle );
|
|
UNREFERENCED_PARAMETER( LogonLevel );
|
|
UNREFERENCED_PARAMETER( LogonInformation );
|
|
}
|
|
|
|
|
|
ULONG
|
|
MsvGetLogonAttemptCount (
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Return the number of logon attempts since the last reboot.
|
|
|
|
Arguments:
|
|
|
|
NONE
|
|
|
|
Return Value:
|
|
|
|
Returns the number of logon attempts since the last reboot.
|
|
|
|
--*/
|
|
{
|
|
|
|
//
|
|
// Keep track of the total number of logons attempted.
|
|
//
|
|
|
|
return NlpLogonAttemptCount;
|
|
}
|
|
|