Leaked source code of windows server 2003
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.
 
 
 
 
 
 

6965 lines
209 KiB

/*++
Copyright (c) 1989 - 1999 Microsoft Corporation
Module Name:
nlmain.c
Abstract:
This file contains the initialization and dispatch routines
for the LAN Manager portions of the MSV1_0 authentication package.
Author:
Jim Kelly 11-Apr-1991
Revision History:
25-Apr-1991 (cliffv)
Added interactive logon support for PDK.
Chandana Surlu 21-Jul-1996
Stolen from \\kernel\razzle3\src\security\msv1_0\nlmain.c
JClark 28-Jun-2000
Added WMI Trace Logging Support
--*/
#include <global.h>
#include "msp.h"
#undef EXTERN
#define NLP_ALLOCATE
#include "nlp.h"
#undef NLP_ALLOCATE
#include <lmsname.h> // Service Names
#include <safeboot.h>
#include <confname.h> // NETSETUPP_NETLOGON_JD_STOPPED
#include "nlpcache.h" // logon cache prototypes
#include "trace.h" // wmi tracing goo
#include "msvwow.h"
NTSTATUS
NlpMapLogonDomain(
OUT PUNICODE_STRING MappedDomain,
IN PUNICODE_STRING LogonDomain
);
NTSTATUS
NlInitialize(
VOID
)
/*++
Routine Description:
Initialize NETLOGON portion of msv1_0 authentication package.
Arguments:
None.
Return Status:
STATUS_SUCCESS - Indicates NETLOGON successfully initialized.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
LPWSTR ComputerName;
DWORD ComputerNameLength = MAX_COMPUTERNAME_LENGTH + 1;
NT_PRODUCT_TYPE NtProductType;
UNICODE_STRING TempUnicodeString;
HKEY Key;
int err;
ULONG Size;
ULONG Type;
ULONG Value;
//
// Initialize global data
//
NlpEnumerationHandle = 0;
NlpLogonAttemptCount = 0;
NlpComputerName.Buffer = NULL;
RtlInitUnicodeString( &NlpPrimaryDomainName, NULL );
NlpSamDomainName.Buffer = NULL;
NlpSamDomainId = NULL;
NlpSamDomainHandle = NULL;
//
// Get the name of this machine.
//
ComputerName = I_NtLmAllocate(
ComputerNameLength * sizeof(WCHAR) );
if (ComputerName == NULL ||
!GetComputerNameW( ComputerName, &ComputerNameLength )) {
SspPrint((SSP_MISC, "Cannot get computername %lX\n", GetLastError() ));
NlpLanmanInstalled = FALSE;
I_NtLmFree( ComputerName );
ComputerName = NULL;
} else {
NlpLanmanInstalled = TRUE;
}
//
// For Safe mode boot (minimal, no networking)
// turn off the lanmaninstalled flag, since no network components will
// be started.
//
err = RegOpenKeyExW(
HKEY_LOCAL_MACHINE,
L"System\\CurrentControlSet\\Control\\SafeBoot\\Option",
0,
KEY_READ,
&Key );
if ( err == ERROR_SUCCESS )
{
Value = 0 ;
Size = sizeof( ULONG );
err = RegQueryValueExW(
Key,
L"OptionValue",
0,
&Type,
(PUCHAR) &Value,
&Size );
RegCloseKey( Key );
if ( err == ERROR_SUCCESS )
{
NtLmGlobalSafeBoot = TRUE;
if ( Value == SAFEBOOT_MINIMAL )
{
NlpLanmanInstalled = FALSE ;
}
}
}
RtlInitUnicodeString( &NlpComputerName, ComputerName );
//
// Determine if this machine is running Windows NT or Lanman NT.
// LanMan NT runs on a domain controller.
//
if ( !RtlGetNtProductType( &NtProductType ) ) {
SspPrint((SSP_MISC, "Nt Product Type undefined (WinNt assumed)\n" ));
NtProductType = NtProductWinNt;
}
NlpWorkstation = (BOOLEAN)(NtProductType != NtProductLanManNt);
InitializeListHead(&NlpActiveLogonListAnchor);
//
// Initialize any locks.
//
__try
{
RtlInitializeResource(&NlpActiveLogonLock);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
}
ASSERT( NT_SUCCESS(Status) );
//
// initialize the cache - creates a critical section is all
//
NlpCacheInitialize();
//
// Attempt to load Netlogon.dll
//
NlpLoadNetlogonDll();
#ifdef COMPILED_BY_DEVELOPER
SspPrint((SSP_CRITICAL, "COMPILED_BY_DEVELOPER breakpoint.\n"));
DbgBreakPoint();
#endif // COMPILED_BY_DEVELOPER
//
// Initialize useful encryption constants
//
Status = RtlCalculateLmOwfPassword( "", &NlpNullLmOwfPassword );
ASSERT( NT_SUCCESS(Status) );
RtlInitUnicodeString(&TempUnicodeString, NULL);
Status = RtlCalculateNtOwfPassword(&TempUnicodeString,
&NlpNullNtOwfPassword);
ASSERT( NT_SUCCESS(Status) );
//
// Initialize the SubAuthentication Dlls
//
Msv1_0SubAuthenticationInitialization();
#ifdef notdef
//
// If we weren't successful,
// Clean up global resources we intended to initialize.
//
if ( !NT_SUCCESS(Status) ) {
if ( NlpComputerName.Buffer != NULL ) {
MIDL_user_free( NlpComputerName.Buffer );
}
}
#endif // notdef
return STATUS_SUCCESS;
}
NTSTATUS
NlWaitForEvent(
LPWSTR EventName,
ULONG Timeout
)
/*++
Routine Description:
Wait up to Timeout seconds for EventName to be triggered.
Arguments:
EventName - Name of event to wait on
Timeout - Timeout for event (in seconds).
Return Status:
STATUS_SUCCESS - Indicates NETLOGON successfully initialized.
STATUS_NETLOGON_NOT_STARTED - Timeout occurred.
--*/
{
NTSTATUS Status;
HANDLE EventHandle;
OBJECT_ATTRIBUTES EventAttributes;
UNICODE_STRING EventNameString;
LARGE_INTEGER LocalTimeout;
//
// Create an event for us to wait on.
//
RtlInitUnicodeString( &EventNameString, EventName );
InitializeObjectAttributes( &EventAttributes, &EventNameString, 0, 0, NULL );
Status = NtCreateEvent(
&EventHandle,
SYNCHRONIZE,
&EventAttributes,
NotificationEvent,
(BOOLEAN) FALSE // The event is initially not signaled
);
if ( !NT_SUCCESS(Status)) {
//
// If the event already exists, the server beat us to creating it.
// Just open it.
//
if ( Status == STATUS_OBJECT_NAME_EXISTS ||
Status == STATUS_OBJECT_NAME_COLLISION ) {
Status = NtOpenEvent( &EventHandle,
SYNCHRONIZE,
&EventAttributes );
}
if ( !NT_SUCCESS(Status)) {
SspPrint((SSP_MISC, "OpenEvent failed %lx\n", Status ));
return Status;
}
}
//
// Wait for NETLOGON to initialize. Wait a maximum of Timeout seconds.
//
LocalTimeout.QuadPart = ((LONGLONG)(Timeout)) * (-10000000);
Status = NtWaitForSingleObject( EventHandle, (BOOLEAN)FALSE, &LocalTimeout);
(VOID) NtClose( EventHandle );
if ( !NT_SUCCESS(Status) || Status == STATUS_TIMEOUT ) {
if ( Status == STATUS_TIMEOUT ) {
Status = STATUS_NETLOGON_NOT_STARTED; // Map to an error condition
}
return Status;
}
return STATUS_SUCCESS;
}
BOOLEAN
NlDoingSetup(
VOID
)
/*++
Routine Description:
Returns TRUE if we're running setup.
Arguments:
NONE.
Return Status:
TRUE - We're currently running setup
FALSE - We're not running setup or aren't sure.
--*/
{
LONG RegStatus;
HKEY KeyHandle = NULL;
DWORD ValueType;
DWORD Value;
DWORD ValueSize;
//
// Open the key for HKLM\SYSTEM\Setup
//
RegStatus = RegOpenKeyExA(
HKEY_LOCAL_MACHINE,
"SYSTEM\\Setup",
0, //Reserved
KEY_QUERY_VALUE,
&KeyHandle );
if ( RegStatus != ERROR_SUCCESS ) {
SspPrint((SSP_INIT, "NlDoingSetup: Cannot open registy key 'HKLM\\SYSTEM\\Setup' %ld.\n",
RegStatus ));
return FALSE;
}
//
// Get the value that says whether we're doing setup.
//
ValueSize = sizeof(Value);
RegStatus = RegQueryValueExA(
KeyHandle,
"SystemSetupInProgress",
0,
&ValueType,
(LPBYTE)&Value,
&ValueSize );
RegCloseKey( KeyHandle );
if ( RegStatus != ERROR_SUCCESS ) {
SspPrint((SSP_INIT, "NlDoingSetup: Cannot query value of 'HKLM\\SYSTEM\\Setup\\SystemSetupInProgress' %ld.\n",
RegStatus ));
return FALSE;
}
if ( ValueType != REG_DWORD ) {
SspPrint((SSP_INIT, "NlDoingSetup: value of 'HKLM\\SYSTEM\\Setup\\SystemSetupInProgress'is not a REG_DWORD %ld.\n",
ValueType ));
return FALSE;
}
if ( ValueSize != sizeof(Value) ) {
SspPrint((SSP_INIT, "NlDoingSetup: value size of 'HKLM\\SYSTEM\\Setup\\SystemSetupInProgress'is not 4 %ld.\n",
ValueSize ));
return FALSE;
}
if ( Value != 1 ) {
// KdPrint(( "NlDoingSetup: not doing setup\n" ));
return FALSE;
}
SspPrint((SSP_INIT, "NlDoingSetup: doing setup\n" ));
return TRUE;
}
NTSTATUS
NlWaitForNetlogon(
ULONG Timeout
)
/*++
Routine Description:
Wait up to Timeout seconds for the netlogon service to start.
Arguments:
Timeout - Timeout for event (in seconds).
Return Status:
STATUS_SUCCESS - Indicates NETLOGON successfully initialized.
STATUS_NETLOGON_NOT_STARTED - Timeout occurred.
--*/
{
NTSTATUS Status;
NET_API_STATUS NetStatus;
SC_HANDLE ScManagerHandle = NULL;
SC_HANDLE ServiceHandle = NULL;
SERVICE_STATUS ServiceStatus;
LPQUERY_SERVICE_CONFIG ServiceConfig;
LPQUERY_SERVICE_CONFIG AllocServiceConfig = NULL;
QUERY_SERVICE_CONFIG DummyServiceConfig;
DWORD ServiceConfigSize;
//
// If the netlogon service is currently running,
// skip the rest of the tests.
//
Status = NlWaitForEvent( L"\\NETLOGON_SERVICE_STARTED", 0 );
if ( NT_SUCCESS(Status) ) {
return Status;
}
//
// If we're in setup,
// don't bother waiting for netlogon to start.
//
if ( NlDoingSetup() ) {
return STATUS_NETLOGON_NOT_STARTED;
}
//
// Open a handle to the Netlogon Service.
//
ScManagerHandle = OpenSCManager(
NULL,
NULL,
SC_MANAGER_CONNECT );
if (ScManagerHandle == NULL) {
SspPrint((SSP_MISC, "NlWaitForNetlogon: OpenSCManager failed: "
"%lu\n", GetLastError()));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
ServiceHandle = OpenService(
ScManagerHandle,
SERVICE_NETLOGON,
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG );
if ( ServiceHandle == NULL ) {
SspPrint((SSP_MISC, "NlWaitForNetlogon: OpenService failed: "
"%lu\n", GetLastError()));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
//
// If the Netlogon service isn't configured to be automatically started
// by the service controller, don't bother waiting for it to start.
//
// ?? Pass "DummyServiceConfig" and "sizeof(..)" since QueryService config
// won't allow a null pointer, yet.
if ( QueryServiceConfig(
ServiceHandle,
&DummyServiceConfig,
sizeof(DummyServiceConfig),
&ServiceConfigSize )) {
ServiceConfig = &DummyServiceConfig;
} else {
NetStatus = GetLastError();
if ( NetStatus != ERROR_INSUFFICIENT_BUFFER ) {
SspPrint((SSP_MISC, "NlWaitForNetlogon: QueryServiceConfig failed: "
"%lu\n", NetStatus));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
AllocServiceConfig = I_NtLmAllocate( ServiceConfigSize );
ServiceConfig = AllocServiceConfig;
if ( AllocServiceConfig == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
if ( !QueryServiceConfig(
ServiceHandle,
ServiceConfig,
ServiceConfigSize,
&ServiceConfigSize )) {
SspPrint((SSP_MISC, "NlWaitForNetlogon: QueryServiceConfig "
"failed again: %lu\n", GetLastError()));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
}
if ( ServiceConfig->dwStartType != SERVICE_AUTO_START ) {
SspPrint((SSP_MISC, "NlWaitForNetlogon: Netlogon start type invalid:"
"%lu\n", ServiceConfig->dwStartType ));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
//
// Loop waiting for the netlogon service to start.
// (Convert Timeout to a number of 10 second iterations)
//
Timeout = (Timeout+9)/10;
for (;;) {
//
// Query the status of the Netlogon service.
//
if (! QueryServiceStatus( ServiceHandle, &ServiceStatus )) {
SspPrint((SSP_MISC, "NlWaitForNetlogon: QueryServiceStatus failed: "
"%lu\n", GetLastError() ));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
//
// Return or continue waiting depending on the state of
// the netlogon service.
//
switch( ServiceStatus.dwCurrentState) {
case SERVICE_RUNNING:
Status = STATUS_SUCCESS;
goto Cleanup;
case SERVICE_STOPPED:
//
// If Netlogon failed to start,
// error out now. The caller has waited long enough to start.
//
if ( ServiceStatus.dwWin32ExitCode != ERROR_SERVICE_NEVER_STARTED ){
SspPrint((SSP_MISC, "NlWaitForNetlogon: "
"Netlogon service couldn't start: %lu %lx\n",
ServiceStatus.dwWin32ExitCode,
ServiceStatus.dwWin32ExitCode ));
if ( ServiceStatus.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR ) {
SspPrint((SSP_MISC, " Service specific error code: %lu %lx\n",
ServiceStatus.dwServiceSpecificExitCode,
ServiceStatus.dwServiceSpecificExitCode ));
}
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
//
// If Netlogon has never been started on this boot,
// continue waiting for it to start.
//
break;
//
// If Netlogon is trying to start up now,
// continue waiting for it to start.
//
case SERVICE_START_PENDING:
break;
//
// Any other state is bogus.
//
default:
SspPrint((SSP_MISC, "NlWaitForNetlogon: "
"Invalid service state: %lu\n",
ServiceStatus.dwCurrentState ));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
//
// Wait ten seconds for the netlogon service to start.
// If it has successfully started, just return now.
//
Status = NlWaitForEvent( L"\\NETLOGON_SERVICE_STARTED", 10 );
if ( Status != STATUS_NETLOGON_NOT_STARTED ) {
goto Cleanup;
}
//
// If we've waited long enough for netlogon to start,
// time out now.
//
if ( (--Timeout) == 0 ) {
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
}
/* NOT REACHED */
Cleanup:
if ( ScManagerHandle != NULL ) {
(VOID) CloseServiceHandle(ScManagerHandle);
}
if ( ServiceHandle != NULL ) {
(VOID) CloseServiceHandle(ServiceHandle);
}
if ( AllocServiceConfig != NULL ) {
I_NtLmFree( AllocServiceConfig );
}
return Status;
}
NTSTATUS
NlSamInitialize(
ULONG Timeout
)
/*++
Routine Description:
Initialize the MSV1_0 Authentication Package's communication to the SAM
database. This initialization will take place once immediately prior
to the first actual use of the SAM database.
Arguments:
Timeout - Timeout for event (in seconds).
Return Status:
STATUS_SUCCESS - Indicates NETLOGON successfully initialized.
--*/
{
NTSTATUS Status;
//
// locals that are staging area for globals.
//
UNICODE_STRING PrimaryDomainName;
PSID SamDomainId = NULL;
UNICODE_STRING SamDomainName;
SAMPR_HANDLE SamDomainHandle = NULL;
UNICODE_STRING DnsTreeName;
PLSAPR_POLICY_INFORMATION PolicyPrimaryDomainInfo = NULL;
PLSAPR_POLICY_INFORMATION PolicyAccountDomainInfo = NULL;
SAMPR_HANDLE SamHandle = NULL;
#ifdef SAM
PSAMPR_DOMAIN_INFO_BUFFER DomainInfo = NULL;
#endif // SAM
PrimaryDomainName.Buffer = NULL;
SamDomainName.Buffer = NULL;
DnsTreeName.Buffer = NULL;
//
// Wait for SAM to finish initialization.
//
Status = NlWaitForEvent( L"\\SAM_SERVICE_STARTED", Timeout );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
//
// Determine the DomainName and DomainId of the Account Database
//
Status = I_LsarQueryInformationPolicy( NtLmGlobalPolicyHandle,
PolicyAccountDomainInformation,
&PolicyAccountDomainInfo );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
if ( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid == NULL ||
PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainName.Length == 0 ) {
SspPrint((SSP_MISC, "Account domain info from LSA invalid.\n"));
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
Status = I_LsarQueryInformationPolicy(
NtLmGlobalPolicyHandle,
PolicyPrimaryDomainInformation,
&PolicyPrimaryDomainInfo );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
if ( PolicyPrimaryDomainInfo->PolicyPrimaryDomainInfo.Name.Length == 0 )
{
SspPrint((SSP_CRITICAL, "Primary domain info from LSA invalid.\n"));
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// save PrimaryDomainName
//
PrimaryDomainName.Length = PolicyPrimaryDomainInfo->PolicyPrimaryDomainInfo.Name.Length;
PrimaryDomainName.MaximumLength = PrimaryDomainName.Length;
PrimaryDomainName.Buffer =
(PWSTR)I_NtLmAllocate( PrimaryDomainName.MaximumLength );
if ( PrimaryDomainName.Buffer == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( PrimaryDomainName.Buffer,
PolicyPrimaryDomainInfo->PolicyPrimaryDomainInfo.Name.Buffer,
PrimaryDomainName.Length );
//
// Save the domain id of this domain
//
SamDomainId = I_NtLmAllocate(
RtlLengthSid( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid )
);
if ( SamDomainId == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( SamDomainId,
PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid,
RtlLengthSid( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid ));
//
// Save the name of the account database on this machine.
//
// On a workstation, the account database is refered to by the machine
// name and not the database name.
// The above being true, the machine name is set to MACHINENAME during
// setup and for the duration when the machine has a real machine name
// until the end of setup, NlpSamDomainName will still have MACHINENAME.
// This is not what the caller expects to authenticate against, so we
// force a look from the Lsa all the time.
// We assume that NlpSamDomainName will get the right info from the Lsa
SamDomainName.Length = PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainName.Length;
SamDomainName.MaximumLength = (USHORT)
(SamDomainName.Length + sizeof(WCHAR));
SamDomainName.Buffer =
I_NtLmAllocate( SamDomainName.MaximumLength );
if ( SamDomainName.Buffer == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( SamDomainName.Buffer,
PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainName.Buffer,
SamDomainName.MaximumLength );
//
// Open our connection with SAM
//
Status = I_SamIConnect( NULL, // No server name
&SamHandle,
SAM_SERVER_CONNECT,
(BOOLEAN) TRUE ); // Indicate we are privileged
if ( !NT_SUCCESS(Status) ) {
SamHandle = NULL;
SspPrint((SSP_CRITICAL, "Cannot SamIConnect %lX\n", Status));
goto Cleanup;
}
//
// Open the domain.
//
Status = I_SamrOpenDomain( SamHandle,
DOMAIN_ALL_ACCESS,
SamDomainId,
&SamDomainHandle );
if ( !NT_SUCCESS(Status) ) {
SamDomainHandle = NULL;
SspPrint((SSP_CRITICAL, "Cannot SamrOpenDomain %lX\n", Status));
goto Cleanup;
}
//
// query the TreeName (since SAM was not up during package initialization)
// update the various globals.
//
if( !NlpSamInitialized )
{
//
// make the query before taking the exclusive lock, to avoid possible
// deadlock conditions.
//
SsprQueryTreeName( &DnsTreeName );
}
Status = STATUS_SUCCESS;
RtlAcquireResourceExclusive(&NtLmGlobalCritSect, TRUE);
if( !NlpSamInitialized ) {
NlpPrimaryDomainName = PrimaryDomainName;
NlpSamDomainId = SamDomainId;
NlpSamDomainName = SamDomainName;
NlpSamDomainHandle = SamDomainHandle;
if( NtLmGlobalUnicodeDnsTreeName.Buffer )
{
NtLmFree( NtLmGlobalUnicodeDnsTreeName.Buffer );
}
NtLmGlobalUnicodeDnsTreeName = DnsTreeName;
SsprUpdateTargetInfo();
NlpSamInitialized = TRUE;
//
// mark locals invalid so they don't get freed.
//
PrimaryDomainName.Buffer = NULL;
SamDomainId = NULL;
SamDomainName.Buffer = NULL;
SamDomainHandle = NULL;
DnsTreeName.Buffer = NULL;
}
RtlReleaseResource(&NtLmGlobalCritSect);
Cleanup:
if( DnsTreeName.Buffer )
{
NtLmFree( DnsTreeName.Buffer );
}
if ( PrimaryDomainName.Buffer != NULL ) {
I_NtLmFree( PrimaryDomainName.Buffer );
}
if ( SamDomainName.Buffer != NULL ) {
I_NtLmFree( SamDomainName.Buffer );
}
if ( SamDomainHandle != NULL ) {
(VOID) I_SamrCloseHandle( &SamDomainHandle );
}
if ( SamDomainId != NULL ) {
I_NtLmFree( SamDomainId );
}
if ( PolicyAccountDomainInfo != NULL ) {
I_LsaIFree_LSAPR_POLICY_INFORMATION( PolicyAccountDomainInformation,
PolicyAccountDomainInfo );
}
if ( PolicyPrimaryDomainInfo != NULL ) {
I_LsaIFree_LSAPR_POLICY_INFORMATION( PolicyPrimaryDomainInformation,
PolicyPrimaryDomainInfo );
}
if ( SamHandle != NULL ) {
(VOID) I_SamrCloseHandle( &SamHandle );
}
return Status;
}
NTSTATUS
MspLm20Challenge (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0Lm20ChallengeRequest. It is called by
the LanMan server to determine the Challenge to pass back to a
redirector trying to establish a connection to the server. The server
is responsible remembering this Challenge and passing in back to this
authentication package on a subsequent MsV1_0Lm20Logon request.
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
NTSTATUS Status;
PMSV1_0_LM20_CHALLENGE_REQUEST ChallengeRequest;
PMSV1_0_LM20_CHALLENGE_RESPONSE ChallengeResponse;
CLIENT_BUFFER_DESC ClientBufferDesc;
UNREFERENCED_PARAMETER( ClientBufferBase );
ASSERT( sizeof(LM_CHALLENGE) == MSV1_0_CHALLENGE_LENGTH );
NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
if ( SubmitBufferSize < sizeof(MSV1_0_LM20_CHALLENGE_REQUEST) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
ChallengeRequest = (PMSV1_0_LM20_CHALLENGE_REQUEST) ProtocolSubmitBuffer;
ASSERT( ChallengeRequest->MessageType == MsV1_0Lm20ChallengeRequest );
//
// Allocate a buffer to return to the caller.
//
*ReturnBufferSize = sizeof(MSV1_0_LM20_CHALLENGE_RESPONSE);
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_LM20_CHALLENGE_RESPONSE),
*ReturnBufferSize );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
ChallengeResponse = (PMSV1_0_LM20_CHALLENGE_RESPONSE) ClientBufferDesc.MsvBuffer;
//
// Fill in the return buffer.
//
ChallengeResponse->MessageType = MsV1_0Lm20ChallengeRequest;
//
// Compute a random seed.
//
Status = SspGenerateRandomBits(
ChallengeResponse->ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// Flush the buffer to the client's address space.
//
Status = NlpFlushClientBuffer( &ClientBufferDesc,
ProtocolReturnBuffer );
Cleanup:
//
// If we weren't successful, free the buffer in the clients address space.
//
if ( !NT_SUCCESS(Status) ) {
NlpFreeClientBuffer( &ClientBufferDesc );
}
//
// Return status to the caller.
//
*ProtocolStatus = Status;
return STATUS_SUCCESS;
}
NTSTATUS
MspLm20GetChallengeResponse (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0Lm20GetChallengeResponse. It is called by
the LanMan redirector to determine the Challenge Response to pass to a
server when trying to establish a connection to the server.
This routine is passed a Challenge from the server. This routine encrypts
the challenge with either the specified password or with the password
implied by the specified Logon Id.
Two Challenge responses are returned. One is based on the Unicode password
as given to the Authentication package. The other is based on that
password converted to a multi-byte character set (e.g., ASCII) and upper
cased. The redirector should use whichever (or both) challenge responses
as it needs them.
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
NTSTATUS Status;
PMSV1_0_GETCHALLENRESP_REQUEST GetRespRequest;
CLIENT_BUFFER_DESC ClientBufferDesc;
PMSV1_0_GETCHALLENRESP_RESPONSE GetRespResponse;
PMSV1_0_PRIMARY_CREDENTIAL Credential = NULL;
PMSV1_0_PRIMARY_CREDENTIAL PrimaryCredential = NULL;
MSV1_0_PRIMARY_CREDENTIAL BuiltCredential = {0};
//
// Responses to return to the caller.
//
LM_RESPONSE LmResponse;
STRING LmResponseString;
NT_RESPONSE NtResponse;
STRING NtResponseString;
PMSV1_0_NTLM3_RESPONSE pNtlm3Response = NULL;
UNICODE_STRING UserName;
UNICODE_STRING LogonDomainName;
USER_SESSION_KEY UserSessionKey;
UCHAR LanmanSessionKey[MSV1_0_LANMAN_SESSION_KEY_LENGTH];
UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH];
UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH];
ULONG NtLmProtocolSupported;
//
// Initialization
//
NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
RtlInitUnicodeString( &UserName, NULL );
RtlInitUnicodeString( &LogonDomainName, NULL );
RtlZeroMemory( &UserSessionKey, sizeof(UserSessionKey) );
RtlZeroMemory( LanmanSessionKey, sizeof(LanmanSessionKey) );
//
// If no credentials are associated with the client, a null session
// will be used. For a downlevel server, the null session response is
// a 1-byte null string (\0). Initialize LmResponseString to the
// null session response.
//
RtlInitString( &LmResponseString, "" );
LmResponseString.Length = 1;
//
// Initialize the NT response to the NT null session credentials,
// which are zero length.
//
RtlInitString( &NtResponseString, NULL );
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
if ( SubmitBufferSize < sizeof(MSV1_0_GETCHALLENRESP_REQUEST_V1) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
GetRespRequest = (PMSV1_0_GETCHALLENRESP_REQUEST) ProtocolSubmitBuffer;
ASSERT( GetRespRequest->MessageType == MsV1_0Lm20GetChallengeResponse );
if ( (GetRespRequest->ParameterControl & USE_PRIMARY_PASSWORD) == 0 ) {
RELOCATE_ONE( &GetRespRequest->Password );
}
//
// If we don't support the request (such as the caller is asking for an
// LM challenge response and we do't support it, return an error here.
//
NtLmProtocolSupported = NtLmGlobalLmProtocolSupported;
//
// allow protocol to be downgraded to NTLM from NTLMv2 if so requested.
//
if ( (NtLmProtocolSupported >= UseNtlm3)
&& (ClientRequest == (PLSA_CLIENT_REQUEST) -1)
&& (GetRespRequest->ParameterControl & GCR_ALLOW_NTLM) )
{
NtLmProtocolSupported = NoLm;
}
if ( (GetRespRequest->ParameterControl & RETURN_NON_NT_USER_SESSION_KEY)
&& (NtLmProtocolSupported >= NoLm)
// datagram can not negotiate, allow LM key
&& !( (ClientRequest == (PLSA_CLIENT_REQUEST) -1)
&& (GetRespRequest->ParameterControl & GCR_ALLOW_LM) ) )
{
Status = STATUS_NOT_SUPPORTED;
SspPrint((SSP_CRITICAL,
"MspLm20GetChallengeResponse: cannot do non NT user session key: "
"ClientRequest %p, NtLmProtocolSupported %#x, ParameterControl %#x\n",
ClientRequest, NtLmProtocolSupported, GetRespRequest->ParameterControl));
goto Cleanup;
}
if( GetRespRequest->ParameterControl & GCR_MACHINE_CREDENTIAL )
{
SECPKG_CLIENT_INFO ClientInfo;
LUID SystemLuid = SYSTEM_LUID;
//
// if caller wants machine cred, check they are SYSTEM.
// if so, whack the LogonId to point at the machine logon.
//
Status = LsaFunctions->GetClientInfo( &ClientInfo );
if( !NT_SUCCESS(Status) )
{
goto Cleanup;
}
if(!RtlEqualLuid( &ClientInfo.LogonId, &SystemLuid ))
{
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
GetRespRequest->LogonId = NtLmGlobalLuidMachineLogon;
}
//
// if caller wants NTLM++, so be it...
//
if ( (GetRespRequest->ParameterControl & GCR_NTLM3_PARMS) ) {
PMSV1_0_AV_PAIR pAV;
UCHAR TargetInfoBuffer[3*sizeof(MSV1_0_AV_PAIR) + (DNS_MAX_NAME_LENGTH+CNLEN+2)*sizeof(WCHAR)];
NULL_RELOCATE_ONE( &GetRespRequest->UserName );
NULL_RELOCATE_ONE( &GetRespRequest->LogonDomainName );
NULL_RELOCATE_ONE( &GetRespRequest->ServerName );
// if target is just a domain name or domain name followed by
// server name, make it into an AV pair list
if (!(GetRespRequest->ParameterControl & GCR_TARGET_INFO)) {
UNICODE_STRING DomainName;
UNICODE_STRING ServerName = { 0, 0, NULL};
unsigned int i;
//
// check length of name to make sure it fits in my buffer
//
if (GetRespRequest->ServerName.Length > (DNS_MAX_NAME_LENGTH+CNLEN+2)*sizeof(WCHAR)) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// init AV list in temp buffer
//
pAV = MsvpAvlInit(TargetInfoBuffer);
//
// see if there's a NULL in the middle of the server name
// that indicates that it's really a domain name followed by a server name
//
DomainName = GetRespRequest->ServerName;
for (i = 0; i < (DomainName.Length/sizeof(WCHAR)); i++) {
if ( DomainName.Buffer[i] == L'\0' )
{
// take length of domain name without the NULL
DomainName.Length = (USHORT) i*sizeof(WCHAR);
// adjust server name and length to point after the domain name
ServerName.Length = (USHORT) (GetRespRequest->ServerName.Length - (i+1) * sizeof(WCHAR));
ServerName.Buffer = GetRespRequest->ServerName.Buffer + (i+1);
break;
}
}
//
// strip off possible trailing null after the server name
//
for (i = 0; i < (ServerName.Length / sizeof(WCHAR)); i++) {
if (ServerName.Buffer[i] == L'\0')
{
ServerName.Length = (USHORT)i*sizeof(WCHAR);
break;
}
}
//
// put both names in the AV list (if both exist)
//
MsvpAvlAdd(pAV, MsvAvNbDomainName, &DomainName, sizeof(TargetInfoBuffer));
if (ServerName.Length > 0) {
MsvpAvlAdd(pAV, MsvAvNbComputerName, &ServerName, sizeof(TargetInfoBuffer));
}
//
// make the request point at AV list instead of names.
//
GetRespRequest->ServerName.Length = (USHORT)MsvpAvlLen(pAV, sizeof(TargetInfoBuffer));
GetRespRequest->ServerName.Buffer = (PWCHAR)pAV;
}
//
// if we're only using NTLMv2 or better, then complain if either
// computer name or server name missing
//
if (NtLmProtocolSupported >= RefuseNtlm3NoTarget) {
pAV = (PMSV1_0_AV_PAIR)GetRespRequest->ServerName.Buffer;
if ((pAV==NULL) ||
MsvpAvlGet(pAV, MsvAvNbDomainName, GetRespRequest->ServerName.Length) == NULL ||
MsvpAvlGet(pAV, MsvAvNbComputerName, GetRespRequest->ServerName.Length) == NULL) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
}
}
//
// If the caller wants information from the credentials of a specified
// LogonId, get those credentials from the LSA.
//
// If there are no such credentials,
// tell the caller to use the NULL session.
//
if ( ((GetRespRequest->ParameterControl & PRIMARY_CREDENTIAL_NEEDED) != 0 ) && ((GetRespRequest->ParameterControl & NULL_SESSION_REQUESTED) == 0)) {
Status = NlpGetPrimaryCredential(
&GetRespRequest->LogonId,
&PrimaryCredential,
NULL );
if ( NT_SUCCESS(Status) ) {
if ( GetRespRequest->ParameterControl & RETURN_PRIMARY_USERNAME ) {
UserName = PrimaryCredential->UserName;
}
if ( GetRespRequest->ParameterControl &
RETURN_PRIMARY_LOGON_DOMAINNAME ) {
#ifndef DONT_MAP_DOMAIN_ON_REQUEST
//
// Map the user's logon domain against the current mapping
// in the registry.
//
Status = NlpMapLogonDomain(
&LogonDomainName,
&PrimaryCredential->LogonDomainName
);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
#else
LogonDomainName = PrimaryCredential->LogonDomainName;
#endif
}
} else if ( Status == STATUS_NO_SUCH_LOGON_SESSION ||
Status == STATUS_UNSUCCESSFUL ) {
//
// Clean up the status code
//
Status = STATUS_NO_SUCH_LOGON_SESSION;
//
// If the caller wants at least the password from the primary
// credential, just use a NULL session primary credential.
//
if ( (GetRespRequest->ParameterControl & USE_PRIMARY_PASSWORD ) ==
USE_PRIMARY_PASSWORD ) {
PrimaryCredential = NULL;
//
// If part of the information was supplied by the caller,
// report the error to the caller.
//
} else {
SspPrint((SSP_CRITICAL, "MspLm20GetChallengeResponse: cannot "
" GetPrimaryCredential %lx\n", Status ));
goto Cleanup;
}
} else {
SspPrint((SSP_CRITICAL, "MspLm20GetChallengeResponse: cannot "
" GetPrimaryCredential %lx\n", Status ));
goto Cleanup;
}
Credential = PrimaryCredential;
}
//
// If the caller passed in a password to use,
// use it to build a credential.
//
if ( (GetRespRequest->ParameterControl & USE_PRIMARY_PASSWORD) == 0 ) {
NlpPutOwfsInPrimaryCredential( &(GetRespRequest->Password),
(BOOLEAN) ((GetRespRequest->ParameterControl & GCR_USE_OWF_PASSWORD) != 0),
&BuiltCredential );
//
// Use the newly allocated credential to get the password information
// from.
//
Credential = &BuiltCredential;
}
//
// Build the appropriate response.
//
if ( Credential != NULL ) {
SspPrint((SSP_CRED, "MspLm20GetChallengeResponse: LogonId %#x:%#x, ParameterControl %#x, %wZ\\%wZ; Credential %wZ\\%wZ; %wZ\\%wZ\n",
GetRespRequest->LogonId.HighPart, GetRespRequest->LogonId.LowPart, GetRespRequest->ParameterControl,
&GetRespRequest->LogonDomainName, &GetRespRequest->UserName,
&Credential->LogonDomainName, &Credential->UserName,
&LogonDomainName, &UserName));
//
// If the DC is asserted to have been upgraded, we should use NTLM3
// if caller supplies the NTLM3 parameters
//
if ((NtLmProtocolSupported >= UseNtlm3) &&
(GetRespRequest->ParameterControl & GCR_NTLM3_PARMS)
) {
USHORT Ntlm3ResponseSize;
UNICODE_STRING Ntlm3UserName;
UNICODE_STRING Ntlm3LogonDomainName;
UNICODE_STRING Ntlm3ServerName;
// use the server name supplied by the caller
Ntlm3ServerName = GetRespRequest->ServerName;
// even if user name and domain are supplied, use current logged
// in user if so requested
if (GetRespRequest->ParameterControl & USE_PRIMARY_PASSWORD) {
Ntlm3UserName = Credential->UserName;
Ntlm3LogonDomainName = Credential->LogonDomainName;
} else {
Ntlm3UserName = GetRespRequest->UserName;
Ntlm3LogonDomainName = GetRespRequest->LogonDomainName;
}
//
// Allocate the response
//
Ntlm3ResponseSize =
sizeof(MSV1_0_NTLM3_RESPONSE) + Ntlm3ServerName.Length;
pNtlm3Response = (*Lsa.AllocatePrivateHeap)( Ntlm3ResponseSize );
if ( pNtlm3Response == NULL ) {
SspPrint((SSP_CRITICAL, "MspLm20GetChallengeResponse: No memory %ld\n",
Ntlm3ResponseSize ));
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
//
// Upcase LogonDomainName and UserName if outbound buffers use OEM
// character set
//
if (GetRespRequest->ParameterControl & GCR_USE_OEM_SET) {
Status = RtlUpcaseUnicodeString(&Ntlm3LogonDomainName, &Ntlm3LogonDomainName, FALSE);
if (NT_SUCCESS(Status)) {
RtlUpcaseUnicodeString(&Ntlm3UserName, &Ntlm3UserName, FALSE);
}
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
}
MsvpLm20GetNtlm3ChallengeResponse(
&Credential->NtOwfPassword,
&Ntlm3UserName,
&Ntlm3LogonDomainName,
&Ntlm3ServerName,
GetRespRequest->ChallengeToClient,
pNtlm3Response,
(PMSV1_0_LM3_RESPONSE)&LmResponse,
&UserSessionKey,
(PLM_SESSION_KEY)LanmanSessionKey
);
NtResponseString.Buffer = (PCHAR) pNtlm3Response;
NtResponseString.Length = Ntlm3ResponseSize;
LmResponseString.Buffer = (PCHAR) &LmResponse;
LmResponseString.Length = sizeof(LmResponse);
} else {
//
// if requested, generate our own challenge, and mix it with that
// of the server's
//
if (GetRespRequest->ParameterControl & GENERATE_CLIENT_CHALLENGE) {
Status = SspGenerateRandomBits(ChallengeFromClient, MSV1_0_CHALLENGE_LENGTH);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
#ifdef USE_CONSTANT_CHALLENGE
RtlZeroMemory(ChallengeFromClient, MSV1_0_CHALLENGE_LENGTH);
#endif
RtlCopyMemory(
ChallengeToClient,
GetRespRequest->ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
MsvpCalculateNtlm2Challenge (
GetRespRequest->ChallengeToClient,
ChallengeFromClient,
GetRespRequest->ChallengeToClient
);
}
Status = RtlCalculateNtResponse(
(PNT_CHALLENGE) GetRespRequest->ChallengeToClient,
&Credential->NtOwfPassword,
&NtResponse );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// send the client challenge back in the LM response slot if we made one
//
if (GetRespRequest->ParameterControl & GENERATE_CLIENT_CHALLENGE) {
RtlZeroMemory(
&LmResponse,
sizeof(LmResponse)
);
RtlCopyMemory(
&LmResponse,
ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
//
// Return the LM response if policy set that way for backwards compatibility.
//
} else if ((NtLmProtocolSupported <= AllowLm) ) {
Status = RtlCalculateLmResponse(
(PLM_CHALLENGE) GetRespRequest->ChallengeToClient,
&Credential->LmOwfPassword,
&LmResponse );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
//
// Can't return LM response -- so use NT response
// (to allow LM_KEY generatation)
//
} else {
RtlCopyMemory(
&LmResponse,
&NtResponse,
sizeof(LmResponse)
);
}
NtResponseString.Buffer = (PCHAR) &NtResponse;
NtResponseString.Length = sizeof(NtResponse);
LmResponseString.Buffer = (PCHAR) &LmResponse;
LmResponseString.Length = sizeof(LmResponse);
//
// Compute the session keys
//
if (GetRespRequest->ParameterControl & GENERATE_CLIENT_CHALLENGE) {
//
// assert: we're talking to an NT4-SP4 or later server
// and the user's DC hasn't been upgraded to NTLM++
// generate session key from MD4(NT hash) -
// aka NtUserSessionKey - that is different for each session
//
Status = RtlCalculateUserSessionKeyNt(
&NtResponse,
&Credential->NtOwfPassword,
&UserSessionKey );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
MsvpCalculateNtlm2SessionKeys(
&UserSessionKey,
ChallengeToClient,
ChallengeFromClient,
(PUSER_SESSION_KEY)&UserSessionKey,
(PLM_SESSION_KEY)LanmanSessionKey
);
} else if ( GetRespRequest->ParameterControl & RETURN_NON_NT_USER_SESSION_KEY){
//
// If the redir didn't negotiate an NT protocol with the server,
// use the lanman session key.
//
if ( Credential->LmPasswordPresent ) {
ASSERT( sizeof(UserSessionKey) >= sizeof(LanmanSessionKey) );
RtlCopyMemory( &UserSessionKey,
&Credential->LmOwfPassword,
sizeof(LanmanSessionKey) );
}
} else {
if ( !Credential->NtPasswordPresent ) {
RtlCopyMemory( &Credential->NtOwfPassword,
&NlpNullNtOwfPassword,
sizeof(Credential->NtOwfPassword) );
}
Status = RtlCalculateUserSessionKeyNt(
&NtResponse,
&Credential->NtOwfPassword,
&UserSessionKey );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
}
if ( Credential->LmPasswordPresent ) {
RtlCopyMemory( LanmanSessionKey,
&Credential->LmOwfPassword,
sizeof(LanmanSessionKey) );
}
} // UseNtlm3
}
//
// Allocate a buffer to return to the caller.
//
*ReturnBufferSize = sizeof(MSV1_0_GETCHALLENRESP_RESPONSE) +
LogonDomainName.Length + sizeof(WCHAR) +
UserName.Length + sizeof(WCHAR) +
NtResponseString.Length + sizeof(WCHAR) +
LmResponseString.Length + sizeof(WCHAR);
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_GETCHALLENRESP_RESPONSE),
*ReturnBufferSize );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
GetRespResponse = (PMSV1_0_GETCHALLENRESP_RESPONSE) ClientBufferDesc.MsvBuffer;
//
// Fill in the return buffer.
//
GetRespResponse->MessageType = MsV1_0Lm20GetChallengeResponse;
RtlCopyMemory( GetRespResponse->UserSessionKey,
&UserSessionKey,
sizeof(UserSessionKey));
RtlCopyMemory( GetRespResponse->LanmanSessionKey,
LanmanSessionKey,
sizeof(LanmanSessionKey) );
//
// Copy the logon domain name (the string may be empty)
//
NlpPutClientString( &ClientBufferDesc,
&GetRespResponse->LogonDomainName,
&LogonDomainName );
//
// Copy the user name (the string may be empty)
//
NlpPutClientString( &ClientBufferDesc,
&GetRespResponse->UserName,
&UserName );
//
// Copy the Challenge Responses to the client buffer.
//
NlpPutClientString(
&ClientBufferDesc,
(PUNICODE_STRING)
&GetRespResponse->CaseSensitiveChallengeResponse,
(PUNICODE_STRING) &NtResponseString );
NlpPutClientString(
&ClientBufferDesc,
(PUNICODE_STRING)
&GetRespResponse->CaseInsensitiveChallengeResponse,
(PUNICODE_STRING)&LmResponseString );
//
// Flush the buffer to the client's address space.
//
Status = NlpFlushClientBuffer( &ClientBufferDesc,
ProtocolReturnBuffer );
Cleanup:
//
// If we weren't successful, free the buffer in the clients address space.
//
if ( !NT_SUCCESS(Status) ) {
NlpFreeClientBuffer( &ClientBufferDesc );
}
//
// Cleanup locally used resources
//
if ( PrimaryCredential != NULL ) {
RtlZeroMemory(PrimaryCredential, sizeof(*PrimaryCredential));
(*Lsa.FreeLsaHeap)( PrimaryCredential );
}
#ifndef DONT_MAP_DOMAIN_ON_REQUEST
if (LogonDomainName.Buffer != NULL) {
NtLmFree(LogonDomainName.Buffer);
}
#endif
if ( pNtlm3Response != NULL ) {
(*Lsa.FreePrivateHeap)( pNtlm3Response );
}
RtlSecureZeroMemory(&BuiltCredential, sizeof(BuiltCredential));
//
// Return status to the caller.
//
*ProtocolStatus = Status;
return STATUS_SUCCESS;
}
NTSTATUS
MspLm20EnumUsers (
IN PLSA_CLIENT_REQUEST pClientRequest,
IN PVOID pProtocolSubmitBuffer,
IN PVOID pClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID* ppProtocolReturnBuffer,
OUT PULONG pReturnBufferSize,
OUT PNTSTATUS pProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0Lm20EnumerateUsers. This routine
enumerates all of the interactive, service, and batch logons to the MSV1_0
authentication package.
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
--*/
{
NTSTATUS Status;
PMSV1_0_ENUMUSERS_REQUEST pEnumRequest = NULL;
PMSV1_0_ENUMUSERS_RESPONSE pEnumResponse = NULL;
CLIENT_BUFFER_DESC ClientBufferDesc;
ULONG LogonCount = 0;
BOOLEAN bActiveLogonsAreLocked = FALSE;
PUCHAR pWhere;
LIST_ENTRY* pScan = NULL;
ACTIVE_LOGON* pActiveLogon = NULL;
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
NlpInitClientBuffer( &ClientBufferDesc, pClientRequest );
UNREFERENCED_PARAMETER( pClientBufferBase );
if ( SubmitBufferSize < sizeof(MSV1_0_ENUMUSERS_REQUEST) )
{
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
pEnumRequest = (PMSV1_0_ENUMUSERS_REQUEST) pProtocolSubmitBuffer;
ASSERT( pEnumRequest->MessageType == MsV1_0EnumerateUsers );
//
// Count the current number of active logons
//
NlpLockActiveLogonsRead();
bActiveLogonsAreLocked = TRUE;
for ( pScan = NlpActiveLogonListAnchor.Flink;
pScan != &NlpActiveLogonListAnchor;
pScan = pScan->Flink )
{
pActiveLogon = CONTAINING_RECORD(pScan, ACTIVE_LOGON, ListEntry);
//
// don't count the machine account logon.
//
if ( RtlEqualLuid(&NtLmGlobalLuidMachineLogon, &pActiveLogon->LogonId) )
{
continue;
}
LogonCount ++;
}
//
// Allocate a buffer to return to the caller.
//
*pReturnBufferSize = sizeof(MSV1_0_ENUMUSERS_RESPONSE) +
LogonCount * (sizeof(LUID) + sizeof(ULONG));
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_ENUMUSERS_RESPONSE),
*pReturnBufferSize );
if ( !NT_SUCCESS( Status ) )
{
goto Cleanup;
}
pEnumResponse = (PMSV1_0_ENUMUSERS_RESPONSE) ClientBufferDesc.MsvBuffer;
//
// Fill in the return buffer.
//
pEnumResponse->MessageType = MsV1_0EnumerateUsers;
pEnumResponse->NumberOfLoggedOnUsers = LogonCount;
pWhere = (PUCHAR)(pEnumResponse + 1);
//
// Loop through the Active Logon Table copying the LogonId of each session.
//
pEnumResponse->LogonIds = (PLUID) (ClientBufferDesc.UserBuffer +
(pWhere - ClientBufferDesc.MsvBuffer));
for ( pScan = NlpActiveLogonListAnchor.Flink;
pScan != &NlpActiveLogonListAnchor;
pScan = pScan->Flink )
{
pActiveLogon = CONTAINING_RECORD(pScan, ACTIVE_LOGON, ListEntry);
//
// don't count the machine account logon.
//
if ( RtlEqualLuid(&NtLmGlobalLuidMachineLogon, &pActiveLogon->LogonId) )
{
continue;
}
*((PLUID)pWhere) = pActiveLogon->LogonId,
pWhere += sizeof(LUID);
}
//
// Loop through the Active Logon Table copying the EnumHandle of
// each session.
//
pEnumResponse->EnumHandles = (PULONG)(ClientBufferDesc.UserBuffer +
(pWhere - ClientBufferDesc.MsvBuffer));
for ( pScan = NlpActiveLogonListAnchor.Flink;
pScan != &NlpActiveLogonListAnchor;
pScan = pScan->Flink )
{
pActiveLogon = CONTAINING_RECORD(pScan, ACTIVE_LOGON, ListEntry);
//
// don't count the machine account logon.
//
if ( RtlEqualLuid(&NtLmGlobalLuidMachineLogon, &pActiveLogon->LogonId) )
{
continue;
}
*((PULONG)pWhere) = pActiveLogon->EnumHandle,
pWhere += sizeof(ULONG);
}
//
// Flush the buffer to the client's address space.
//
Status = NlpFlushClientBuffer( &ClientBufferDesc,
ppProtocolReturnBuffer );
Cleanup:
//
// Be sure to unlock the lock on the Active logon list.
//
if ( bActiveLogonsAreLocked )
{
NlpUnlockActiveLogons();
}
//
// If we weren't successful, free the buffer in the clients address space.
//
if ( !NT_SUCCESS(Status) )
{
NlpFreeClientBuffer( &ClientBufferDesc );
}
//
// Return status to the caller.
//
*pProtocolStatus = Status;
return STATUS_SUCCESS;
}
NTSTATUS
MspLm20GetUserInfo (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0GetUserInfo. This routine
returns information describing a particular Logon Id.
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
NTSTATUS Status;
PMSV1_0_GETUSERINFO_REQUEST pGetInfoRequest;
PMSV1_0_GETUSERINFO_RESPONSE pGetInfoResponse = NULL;
CLIENT_BUFFER_DESC ClientBufferDesc;
BOOLEAN bActiveLogonsAreLocked = FALSE;
PACTIVE_LOGON pActiveLogon = NULL;
ULONG SidLength;
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
UNREFERENCED_PARAMETER( ClientBufferBase );
if ( SubmitBufferSize < sizeof(MSV1_0_GETUSERINFO_REQUEST) )
{
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
pGetInfoRequest = (PMSV1_0_GETUSERINFO_REQUEST) ProtocolSubmitBuffer;
ASSERT( pGetInfoRequest->MessageType == MsV1_0GetUserInfo );
//
// Find the Active logon entry for this particular Logon Id.
//
NlpLockActiveLogonsRead();
bActiveLogonsAreLocked = TRUE;
pActiveLogon = NlpFindActiveLogon( &pGetInfoRequest->LogonId );
if (!pActiveLogon)
{
Status = STATUS_NO_SUCH_LOGON_SESSION;
goto Cleanup;
}
//
// Allocate a buffer to return to the caller.
//
SidLength = RtlLengthSid( pActiveLogon->UserSid );
*ReturnBufferSize = sizeof(MSV1_0_GETUSERINFO_RESPONSE) +
pActiveLogon->UserName.Length + sizeof(WCHAR) +
pActiveLogon->LogonDomainName.Length + sizeof(WCHAR) +
pActiveLogon->LogonServer.Length + sizeof(WCHAR) +
SidLength;
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_GETUSERINFO_RESPONSE),
*ReturnBufferSize );
if ( !NT_SUCCESS( Status ) )
{
goto Cleanup;
}
pGetInfoResponse = (PMSV1_0_GETUSERINFO_RESPONSE) ClientBufferDesc.MsvBuffer;
//
// Fill in the return buffer.
//
pGetInfoResponse->MessageType = MsV1_0GetUserInfo;
pGetInfoResponse->LogonType = pActiveLogon->LogonType;
//
// Copy ULONG aligned data first
//
pGetInfoResponse->UserSid = ClientBufferDesc.UserBuffer +
ClientBufferDesc.StringOffset;
RtlCopyMemory( ClientBufferDesc.MsvBuffer + ClientBufferDesc.StringOffset,
pActiveLogon->UserSid,
SidLength );
ClientBufferDesc.StringOffset += SidLength;
//
// Copy WCHAR aligned data
//
NlpPutClientString( &ClientBufferDesc,
&pGetInfoResponse->UserName,
&pActiveLogon->UserName );
NlpPutClientString( &ClientBufferDesc,
&pGetInfoResponse->LogonDomainName,
&pActiveLogon->LogonDomainName );
NlpPutClientString( &ClientBufferDesc,
&pGetInfoResponse->LogonServer,
&pActiveLogon->LogonServer );
//
// Flush the buffer to the client's address space.
//
Status = NlpFlushClientBuffer( &ClientBufferDesc,
ProtocolReturnBuffer );
Cleanup:
//
// Be sure to unlock the lock on the Active logon list.
//
if ( bActiveLogonsAreLocked )
{
NlpUnlockActiveLogons();
}
//
// If we weren't successful, free the buffer in the clients address space.
//
if ( !NT_SUCCESS(Status))
{
NlpFreeClientBuffer( &ClientBufferDesc );
}
//
// Return status to the caller.
//
*ProtocolStatus = Status;
return STATUS_SUCCESS;
}
NTSTATUS
MspLm20ReLogonUsers (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0RelogonUsers. For each logon session
which was validated by the specified domain controller, the logon session
is re-established with that same domain controller.
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
--*/
{
UNREFERENCED_PARAMETER( ClientRequest );
UNREFERENCED_PARAMETER( ProtocolSubmitBuffer);
UNREFERENCED_PARAMETER( ClientBufferBase);
UNREFERENCED_PARAMETER( SubmitBufferSize);
UNREFERENCED_PARAMETER( ReturnBufferSize);
*ProtocolReturnBuffer = NULL;
*ProtocolStatus = STATUS_NOT_IMPLEMENTED;
return STATUS_SUCCESS;
}
NTSTATUS
MspLm20GenericPassthrough (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0Lm20GenericPassthrough. It is called by
a client wishing to make a CallAuthenticationPackage call against
a domain controller.
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PMSV1_0_PASSTHROUGH_REQUEST PassthroughRequest;
PMSV1_0_PASSTHROUGH_RESPONSE PassthroughResponse;
CLIENT_BUFFER_DESC ClientBufferDesc;
BOOLEAN Authoritative;
PNETLOGON_VALIDATION_GENERIC_INFO ValidationGeneric = NULL;
NETLOGON_GENERIC_INFO LogonGeneric;
PNETLOGON_LOGON_IDENTITY_INFO LogonInformation;
//
// WMI tracing helper struct
//
NTLM_TRACE_INFO TraceInfo = {0};
//
// Begin tracing a logon user
//
if (NtlmGlobalEventTraceFlag) {
//
// Trace header goo
//
SET_TRACE_HEADER(TraceInfo,
NtlmGenericPassthroughGuid,
EVENT_TRACE_TYPE_START,
WNODE_FLAG_TRACED_GUID,
0);
TraceEvent(NtlmGlobalTraceLoggerHandle,
(PEVENT_TRACE_HEADER)&TraceInfo);
}
NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
*ProtocolStatus = STATUS_SUCCESS;
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
if ( SubmitBufferSize < sizeof(MSV1_0_PASSTHROUGH_REQUEST) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
PassthroughRequest = (PMSV1_0_PASSTHROUGH_REQUEST) ProtocolSubmitBuffer;
RELOCATE_ONE( &PassthroughRequest->DomainName );
RELOCATE_ONE( &PassthroughRequest->PackageName );
//
// Make sure the buffer fits in the supplied size
//
if (PassthroughRequest->LogonData != NULL) {
if (PassthroughRequest->LogonData + PassthroughRequest->DataLength <
PassthroughRequest->LogonData ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if ((ULONG_PTR)ClientBufferBase + SubmitBufferSize < (ULONG_PTR)ClientBufferBase ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if (PassthroughRequest->LogonData + PassthroughRequest->DataLength >
(PUCHAR) ClientBufferBase + SubmitBufferSize) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Reset the pointers for the validation data
//
PassthroughRequest->LogonData =
(PUCHAR) PassthroughRequest -
(ULONG_PTR) ClientBufferBase +
(ULONG_PTR) PassthroughRequest->LogonData;
}
//
// Build the structure to pass to Netlogon
//
RtlZeroMemory(
&LogonGeneric,
sizeof(LogonGeneric)
);
LogonGeneric.Identity.LogonDomainName = PassthroughRequest->DomainName;
LogonGeneric.PackageName = PassthroughRequest->PackageName;
LogonGeneric.LogonData = PassthroughRequest->LogonData;
LogonGeneric.DataLength = PassthroughRequest->DataLength;
LogonInformation =
(PNETLOGON_LOGON_IDENTITY_INFO) &LogonGeneric;
//
// Call Netlogon to remote the request
//
//
// Wait for NETLOGON to finish initialization.
//
if ( !NlpNetlogonInitialized ) {
Status = NlWaitForNetlogon( NETLOGON_STARTUP_TIME );
if ( !NT_SUCCESS(Status) ) {
if ( Status != STATUS_NETLOGON_NOT_STARTED ) {
goto Cleanup;
}
} else {
NlpNetlogonInitialized = TRUE;
}
}
if ( NlpNetlogonInitialized ) {
//
// Trace the domain name and package name
//
if (NtlmGlobalEventTraceFlag){
//Header goo
SET_TRACE_HEADER(TraceInfo,
NtlmGenericPassthroughGuid,
EVENT_TRACE_TYPE_INFO,
WNODE_FLAG_TRACED_GUID|WNODE_FLAG_USE_MOF_PTR,
4);
SET_TRACE_USTRING(TraceInfo,
TRACE_PASSTHROUGH_DOMAIN,
LogonGeneric.Identity.LogonDomainName);
SET_TRACE_USTRING(TraceInfo,
TRACE_PASSTHROUGH_PACKAGE,
LogonGeneric.PackageName);
TraceEvent(
NtlmGlobalTraceLoggerHandle,
(PEVENT_TRACE_HEADER)&TraceInfo
);
}
Status = (*NlpNetLogonSamLogon)(
NULL, // Server name
NULL, // Computer name
NULL, // Authenticator
NULL, // ReturnAuthenticator
NetlogonGenericInformation,
(LPBYTE) &LogonInformation,
NetlogonValidationGenericInfo2,
(LPBYTE *) &ValidationGeneric,
&Authoritative );
//
// Reset Netlogon initialized flag if local netlogon cannot be
// reached.
// (Use a more explicit status code)
//
if ( Status == RPC_NT_SERVER_UNAVAILABLE ||
Status == RPC_NT_UNKNOWN_IF ||
Status == STATUS_NETLOGON_NOT_STARTED ) {
Status = STATUS_NETLOGON_NOT_STARTED;
NlpNetlogonInitialized = FALSE;
}
} else {
//
// no netlogon: see if the request is destined for the local domain,
// to allow WORKGROUP support.
//
if ( LogonInformation->LogonDomainName.Length == 0 ||
(LogonInformation->LogonDomainName.Length != 0 &&
RtlEqualDomainName( &NlpSamDomainName,
&LogonInformation->LogonDomainName ) )
) {
PNETLOGON_GENERIC_INFO GenericInfo;
NETLOGON_VALIDATION_GENERIC_INFO GenericValidation;
NTSTATUS ProtocolStatus;
GenericInfo = (PNETLOGON_GENERIC_INFO) LogonInformation;
GenericValidation.ValidationData = NULL;
GenericValidation.DataLength = 0;
//
// unwrap passthrough message and pass it off to dispatch.
//
Status = LsaICallPackagePassthrough(
&GenericInfo->PackageName,
0, // Indicate pointers are relative.
GenericInfo->LogonData,
GenericInfo->DataLength,
(PVOID *) &GenericValidation.ValidationData,
&GenericValidation.DataLength,
&ProtocolStatus
);
if (NT_SUCCESS( Status ) )
Status = ProtocolStatus;
//
// If the call succeeded, allocate the return message.
//
if (NT_SUCCESS(Status)) {
PNETLOGON_VALIDATION_GENERIC_INFO ReturnInfo;
ULONG ValidationLength;
ValidationLength = sizeof(*ReturnInfo) + GenericValidation.DataLength;
ReturnInfo = (PNETLOGON_VALIDATION_GENERIC_INFO) MIDL_user_allocate(
ValidationLength
);
if (ReturnInfo != NULL) {
if ( GenericValidation.DataLength == 0 ||
GenericValidation.ValidationData == NULL ) {
ReturnInfo->DataLength = 0;
ReturnInfo->ValidationData = NULL;
} else {
ReturnInfo->DataLength = GenericValidation.DataLength;
ReturnInfo->ValidationData = (PUCHAR) (ReturnInfo + 1);
RtlCopyMemory(
ReturnInfo->ValidationData,
GenericValidation.ValidationData,
ReturnInfo->DataLength );
}
ValidationGeneric = ReturnInfo;
} else {
Status = STATUS_INSUFFICIENT_RESOURCES;
}
if (GenericValidation.ValidationData != NULL) {
LsaIFreeReturnBuffer(GenericValidation.ValidationData);
}
}
} else {
Status = STATUS_NETLOGON_NOT_STARTED;
}
}
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
//
// Allocate a buffer to return to the caller.
//
*ReturnBufferSize = sizeof(MSV1_0_PASSTHROUGH_RESPONSE) +
ValidationGeneric->DataLength;
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_PASSTHROUGH_RESPONSE),
*ReturnBufferSize );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
PassthroughResponse = (PMSV1_0_PASSTHROUGH_RESPONSE) ClientBufferDesc.MsvBuffer;
//
// Fill in the return buffer.
//
PassthroughResponse->MessageType = MsV1_0GenericPassthrough;
PassthroughResponse->DataLength = ValidationGeneric->DataLength;
PassthroughResponse->ValidationData = ClientBufferDesc.UserBuffer + sizeof(MSV1_0_PASSTHROUGH_RESPONSE);
RtlCopyMemory(
PassthroughResponse + 1,
ValidationGeneric->ValidationData,
ValidationGeneric->DataLength
);
//
// Flush the buffer to the client's address space.
//
Status = NlpFlushClientBuffer( &ClientBufferDesc,
ProtocolReturnBuffer );
Cleanup:
if (ValidationGeneric != NULL) {
MIDL_user_free(ValidationGeneric);
}
if ( !NT_SUCCESS(Status)) {
NlpFreeClientBuffer( &ClientBufferDesc );
}
if (NtlmGlobalEventTraceFlag){
//
// Trace header goo
//
SET_TRACE_HEADER(TraceInfo,
NtlmGenericPassthroughGuid,
EVENT_TRACE_TYPE_END,
WNODE_FLAG_TRACED_GUID,
0);
TraceEvent(NtlmGlobalTraceLoggerHandle,
(PEVENT_TRACE_HEADER)&TraceInfo);
}
*ProtocolStatus = Status;
return(STATUS_SUCCESS);
}
NTSTATUS
MspLm20CacheLogon (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0Lm20CacheLogon. It is called by
a client wishing to cache logon information in the logon cache
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PMSV1_0_CACHE_LOGON_REQUEST CacheRequest;
PNETLOGON_INTERACTIVE_INFO LogonInfo;
NETLOGON_VALIDATION_SAM_INFO4 ValidationInfo;
PVOID SupplementalCacheData = NULL;
ULONG SupplementalCacheDataLength = 0;
ULONG CacheRequestFlags = 0;
//
// NOTE: this entry point only allows callers within the LSA process
//
if (ClientRequest != NULL) {
*ProtocolStatus = STATUS_ACCESS_DENIED;
return(STATUS_SUCCESS);
}
CacheRequest = (PMSV1_0_CACHE_LOGON_REQUEST) ProtocolSubmitBuffer;
if ( SubmitBufferSize <= sizeof( MSV1_0_CACHE_LOGON_REQUEST_OLD ) ||
SubmitBufferSize > sizeof( MSV1_0_CACHE_LOGON_REQUEST ))
{
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if ( SubmitBufferSize >= sizeof( MSV1_0_CACHE_LOGON_REQUEST_W2K ))
{
SupplementalCacheData = CacheRequest->SupplementalCacheData;
SupplementalCacheDataLength = CacheRequest->SupplementalCacheDataLength;
if ( SubmitBufferSize == sizeof( MSV1_0_CACHE_LOGON_REQUEST ))
{
CacheRequestFlags = CacheRequest->RequestFlags;
}
}
LogonInfo = (PNETLOGON_INTERACTIVE_INFO) CacheRequest->LogonInformation;
if( (CacheRequestFlags & MSV1_0_CACHE_LOGON_REQUEST_INFO4) == 0 )
{
RtlZeroMemory( &ValidationInfo, sizeof(ValidationInfo));
RtlCopyMemory( &ValidationInfo,
CacheRequest->ValidationInformation,
sizeof(NETLOGON_VALIDATION_SAM_INFO2) );
} else {
RtlCopyMemory( &ValidationInfo,
CacheRequest->ValidationInformation,
sizeof(NETLOGON_VALIDATION_SAM_INFO4) );
}
*ProtocolStatus = STATUS_SUCCESS;
if (( CacheRequestFlags & MSV1_0_CACHE_LOGON_DELETE_ENTRY) != 0 )
{
*ProtocolStatus = NlpDeleteCacheEntry(
0, // no reason supplied, most likely this is STATUS_ACCOUNT_DISABLED
(USHORT) -1, // authoritative and indicates this is from CallAuthPackage
0, // no logon type supplied
FALSE, // not invalidated by NTLM
LogonInfo
);
}
else
//
// Actually add the cache entry
//
{
*ProtocolStatus = NlpAddCacheEntry(
LogonInfo,
&ValidationInfo,
SupplementalCacheData,
SupplementalCacheDataLength,
CacheRequestFlags
);
}
Cleanup:
return (STATUS_SUCCESS);
UNREFERENCED_PARAMETER( ClientRequest);
UNREFERENCED_PARAMETER( ProtocolReturnBuffer);
UNREFERENCED_PARAMETER( ClientBufferBase);
UNREFERENCED_PARAMETER( ReturnBufferSize);
}
NTSTATUS
MspLm20CacheLookup (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0Lm20CacheLookup. It is called by
a client wishing to extract cache logon information and optionally
verify the credential.
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PMSV1_0_CACHE_LOOKUP_REQUEST CacheRequest;
PMSV1_0_CACHE_LOOKUP_RESPONSE CacheResponse;
NETLOGON_LOGON_IDENTITY_INFO LogonInfo;
PNETLOGON_VALIDATION_SAM_INFO4 ValidationInfo = NULL;
CACHE_PASSWORDS cachePasswords;
CLIENT_BUFFER_DESC ClientBufferDesc;
PNT_OWF_PASSWORD pNtOwfPassword = NULL;
NT_OWF_PASSWORD ComputedNtOwfPassword;
PVOID SupplementalCacheData = NULL;
ULONG SupplementalCacheDataLength;
//
// Ensure the client is from the LSA process
//
if (ClientRequest != NULL) {
*ProtocolStatus = STATUS_ACCESS_DENIED;
return(STATUS_SUCCESS);
}
NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
*ProtocolStatus = STATUS_SUCCESS;
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
if ( SubmitBufferSize < sizeof(MSV1_0_CACHE_LOOKUP_REQUEST) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
CacheRequest = (PMSV1_0_CACHE_LOOKUP_REQUEST) ProtocolSubmitBuffer;
RtlZeroMemory(
&LogonInfo,
sizeof(LogonInfo)
);
//
// NOTE: this submit call only supports in-process calls within the LSA
// so buffers within the submit buffer are assumed to be valid and
// hence not validated in the same way that out-proc calls are.
//
LogonInfo.LogonDomainName = CacheRequest->DomainName;
LogonInfo.UserName = CacheRequest->UserName;
if( CacheRequest->CredentialType != MSV1_0_CACHE_LOOKUP_CREDTYPE_NONE &&
CacheRequest->CredentialType != MSV1_0_CACHE_LOOKUP_CREDTYPE_RAW &&
CacheRequest->CredentialType != MSV1_0_CACHE_LOOKUP_CREDTYPE_NTOWF ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// get the cache entry
//
*ProtocolStatus = NlpGetCacheEntry(
&LogonInfo,
MSV1_0_CACHE_LOGON_REQUEST_SMARTCARD_ONLY, // allow smartcard only cache entry
NULL, // no need for credentials domain name
NULL, // no need for credentials user name
&ValidationInfo,
&cachePasswords,
&SupplementalCacheData,
&SupplementalCacheDataLength
);
if (!NT_SUCCESS(*ProtocolStatus)) {
goto Cleanup;
}
if( CacheRequest->CredentialType == MSV1_0_CACHE_LOOKUP_CREDTYPE_NONE ) {
if( CacheRequest->CredentialInfoLength != 0 ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
}
//
// verify the password, if necessary.
//
if ( CacheRequest->CredentialType == MSV1_0_CACHE_LOOKUP_CREDTYPE_RAW ) {
//
// convert RAW to NTOWF.
//
UNICODE_STRING TempPassword;
if ( CacheRequest->CredentialInfoLength > 0xFFFF ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
TempPassword.Buffer = (PWSTR)&CacheRequest->CredentialSubmitBuffer;
TempPassword.Length = (USHORT)CacheRequest->CredentialInfoLength;
TempPassword.MaximumLength = TempPassword.Length;
pNtOwfPassword = &ComputedNtOwfPassword;
Status = RtlCalculateNtOwfPassword( &TempPassword, pNtOwfPassword );
if( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// now, convert the request to NT_OWF style.
//
CacheRequest->CredentialType = MSV1_0_CACHE_LOOKUP_CREDTYPE_NTOWF;
CacheRequest->CredentialInfoLength = sizeof( NT_OWF_PASSWORD );
}
if ( CacheRequest->CredentialType == MSV1_0_CACHE_LOOKUP_CREDTYPE_NTOWF ) {
if( CacheRequest->CredentialInfoLength != sizeof( NT_OWF_PASSWORD ) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if( !cachePasswords.SecretPasswords.NtPasswordPresent ) {
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
if( pNtOwfPassword == NULL ) {
pNtOwfPassword = (PNT_OWF_PASSWORD)&CacheRequest->CredentialSubmitBuffer;
}
Status = NlpComputeSaltedHashedPassword(
pNtOwfPassword,
pNtOwfPassword,
&ValidationInfo->EffectiveName
);
if (!NT_SUCCESS( Status )) {
goto Cleanup;
}
if (RtlCompareMemory(
pNtOwfPassword,
&cachePasswords.SecretPasswords.NtOwfPassword,
sizeof( NT_OWF_PASSWORD )
) != sizeof(NT_OWF_PASSWORD) )
{
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
}
//
// Return the validation info here.
//
*ReturnBufferSize = sizeof(MSV1_0_CACHE_LOOKUP_RESPONSE);
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_CACHE_LOOKUP_RESPONSE),
*ReturnBufferSize );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
CacheResponse = (PMSV1_0_CACHE_LOOKUP_RESPONSE) ClientBufferDesc.MsvBuffer;
//
// Fill in the return buffer.
//
CacheResponse->MessageType = MsV1_0CacheLookup;
CacheResponse->ValidationInformation = ValidationInfo;
CacheResponse->SupplementalCacheData = SupplementalCacheData;
CacheResponse->SupplementalCacheDataLength = SupplementalCacheDataLength;
//
// Flush the buffer to the client's address space.
//
Status = NlpFlushClientBuffer( &ClientBufferDesc,
ProtocolReturnBuffer );
Cleanup:
if ( !NT_SUCCESS(Status)) {
NlpFreeClientBuffer( &ClientBufferDesc );
if (ValidationInfo != NULL) {
MIDL_user_free( ValidationInfo );
}
if (SupplementalCacheData != NULL) {
MIDL_user_free( SupplementalCacheData );
}
}
RtlSecureZeroMemory( &ComputedNtOwfPassword, sizeof( ComputedNtOwfPassword ) );
RtlSecureZeroMemory( &cachePasswords, sizeof(cachePasswords) );
return(STATUS_SUCCESS);
UNREFERENCED_PARAMETER( ClientBufferBase);
}
NTSTATUS
MspSetProcessOption(
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0SetProcessOption.
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PMSV1_0_SETPROCESSOPTION_REQUEST SetProcessOptionRequest;
*ProtocolStatus = STATUS_UNSUCCESSFUL;
UNREFERENCED_PARAMETER(ClientBufferBase);
UNREFERENCED_PARAMETER(ReturnBufferSize);
UNREFERENCED_PARAMETER(ProtocolReturnBuffer);
UNREFERENCED_PARAMETER(ClientRequest);
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
if ( SubmitBufferSize < sizeof(MSV1_0_SETPROCESSOPTION_REQUEST) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
SetProcessOptionRequest = (PMSV1_0_SETPROCESSOPTION_REQUEST) ProtocolSubmitBuffer;
if( NtLmSetProcessOption(
SetProcessOptionRequest->ProcessOptions,
SetProcessOptionRequest->DisableOptions
) )
{
*ProtocolStatus = STATUS_SUCCESS;
}
Status = STATUS_SUCCESS;
Cleanup:
return(Status);
}
NTSTATUS
LsaApLogonUserEx2 (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN SECURITY_LOGON_TYPE LogonType,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProfileBuffer,
OUT PULONG ProfileBufferSize,
OUT PLUID LogonId,
OUT PNTSTATUS SubStatus,
OUT PLSA_TOKEN_INFORMATION_TYPE TokenInformationType,
OUT PVOID *TokenInformation,
OUT PUNICODE_STRING *AccountName,
OUT PUNICODE_STRING *AuthenticatingAuthority,
OUT PUNICODE_STRING *MachineName,
OUT PSECPKG_PRIMARY_CRED PrimaryCredentials,
OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY * SupplementalCredentials
)
/*++
Routine Description:
This routine is used to authenticate a user logon attempt. This is
the user's initial logon. A new LSA logon session will be established
for the user and validation information for the user will be returned.
Arguments:
ClientRequest - Is a pointer to an opaque data structure
representing the client's request.
LogonType - Identifies the type of logon being attempted.
ProtocolSubmitBuffer - Supplies the authentication
information specific to the authentication package.
ClientBufferBase - Provides the address within the client
process at which the authentication information was resident.
This may be necessary to fix-up any pointers within the
authentication information buffer.
SubmitBufferSize - Indicates the Size, in bytes,
of the authentication information buffer.
ProfileBuffer - Is used to return the address of the profile
buffer in the client process. The authentication package is
responsible for allocating and returning the profile buffer
within the client process. However, if the LSA subsequently
encounters an error which prevents a successful logon, then
the LSA will take care of deallocating that buffer. This
buffer is expected to have been allocated with the
AllocateClientBuffer() service.
The format and semantics of this buffer are specific to the
authentication package.
ProfileBufferSize - Receives the Size (in bytes) of the
returned profile buffer.
SubStatus - If the logon failed due to account restrictions, the
reason for the failure should be returned via this parameter.
The reason is authentication-package specific. The substatus
values for authentication package "MSV1.0" are:
STATUS_INVALID_LOGON_HOURS
STATUS_INVALID_WORKSTATION
STATUS_PASSWORD_EXPIRED
STATUS_ACCOUNT_DISABLED
TokenInformationLevel - If the logon is successful, this field is
used to indicate what level of information is being returned
for inclusion in the Token to be created. This information
is returned via the TokenInformation parameter.
TokenInformation - If the logon is successful, this parameter is
used by the authentication package to return information to
be included in the token. The format and content of the
buffer returned is indicated by the TokenInformationLevel
return value.
AccountName - A Unicode string describing the account name
being logged on to. This parameter must always be returned
regardless of the success or failure of the operation.
AuthenticatingAuthority - A Unicode string describing the Authenticating
Authority for the logon. This string may optionally be omitted.
PrimaryCredentials - Returns primary credentials for handing to other
packages.
SupplementalCredentials - Array of supplemental credential blobs for
other packages.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_QUOTA_EXCEEDED - This error indicates that the logon
could not be completed because the client does not have
sufficient quota to allocate the return buffer.
STATUS_NO_LOGON_SERVERS - Indicates that no domain controllers
are currently able to service the authentication request.
STATUS_LOGON_FAILURE - Indicates the logon attempt failed. No
indication as to the reason for failure is given, but typical
reasons include mispelled usernames, mispelled passwords.
STATUS_ACCOUNT_RESTRICTION - Indicates the user account and
password were legitimate, but that the user account has some
restriction preventing successful logon at this time.
STATUS_BAD_VALIDATION_CLASS - The authentication information
provided is not a validation class known to the specified
authentication package.
STATUS_INVALID_LOGON_CLASS - LogonType was invalid.
STATUS_LOGON_SESSION_COLLISION- Internal Error: A LogonId was selected for
this logon session. The selected LogonId already exists.
STATUS_NETLOGON_NOT_STARTED - The Sam Server or Netlogon service was
required to perform this function. The required server was not running.
STATUS_NO_MEMORY - Insufficient virtual memory or pagefile quota exists.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
LSA_TOKEN_INFORMATION_TYPE LsaTokenInformationType = LsaTokenInformationV2;
PNETLOGON_VALIDATION_SAM_INFO4 NlpUser = NULL;
ULONG ActiveLogonEntrySize;
PACTIVE_LOGON pActiveLogonEntry = NULL;
BOOLEAN bLogonEntryLinked = FALSE;
BOOLEAN LogonSessionCreated = FALSE;
BOOLEAN LogonCredentialAdded = FALSE;
ULONG Flags = 0;
BOOLEAN Authoritative = FALSE;
BOOLEAN BadPasswordCountZeroed;
BOOLEAN StandaloneWorkstation = FALSE;
PSID UserSid = NULL;
PMSV1_0_PRIMARY_CREDENTIAL Credential = NULL;
ULONG CredentialSize = 0;
PSECURITY_SEED_AND_LENGTH SeedAndLength;
UCHAR Seed;
PUNICODE_STRING WorkStationName = NULL;
// Need to figure out whether to delete the profile buffer
BOOLEAN fSubAuthEx = FALSE;
//
// deferred NTLM3 checks.
//
BOOLEAN fNtLm3 = FALSE;
//
// Whether to wait for network & netlogon. If we are attempting
// forced cached credentials logon, we will avoid doing so.
//
BOOLEAN fWaitForNetwork = TRUE;
BOOLEAN CacheTried = FALSE;
//
// Temporary storage while we try to figure
// out what our username and authenticating
// authority is.
//
UNICODE_STRING TmpName = { 0, 0, NULL };
WCHAR TmpNameBuffer[UNLEN];
UNICODE_STRING TmpAuthority = { 0, 0, NULL };
WCHAR TmpAuthorityBuffer[DNS_MAX_NAME_LENGTH];
//
// Logon Information.
//
NETLOGON_LOGON_INFO_CLASS LogonLevel = 0;
NETLOGON_INTERACTIVE_INFO LogonInteractive;
NETLOGON_NETWORK_INFO LogonNetwork = {0};
PNETLOGON_LOGON_IDENTITY_INFO LogonInformation = NULL;
PMSV1_0_LM20_LOGON NetworkAuthentication = NULL;
//
// Secret information, if we are doing a service logon
//
LSAPR_HANDLE SecretHandle;
PLSAPR_CR_CIPHER_VALUE SecretCurrent = NULL;
UNICODE_STRING Prefix, SavedPassword = {0};
BOOLEAN ServiceSecretLogon = FALSE;
PMSV1_0_INTERACTIVE_LOGON Authentication = NULL;
//
// Credential manager stored credentials.
//
UNICODE_STRING CredmanUserName = {0, 0, NULL };
UNICODE_STRING CredmanDomainName = {0, 0, NULL };
UNICODE_STRING CredmanPassword = {0, 0, NULL };
BOOLEAN TryCacheFirst = FALSE;
NTSTATUS NetlogonStatus = STATUS_SUCCESS;
LM_RESPONSE LmResponse = {0};
NT_RESPONSE NtResponse = {0};
//
// interactive cached logon users can be mapped, therefore accountname
// can differ from credentails username. used to build primary credentials
//
UNICODE_STRING CredentialUserName = {0};
UNICODE_STRING CredentialDomainName = {0};
PUNICODE_STRING CredentialUserToUse = NULL;
PUNICODE_STRING CredentialDomainToUse = NULL;
//
// WMI tracing helper struct
//
NTLM_TRACE_INFO TraceInfo = {0};
#if _WIN64
PVOID pTempSubmitBuffer = ProtocolSubmitBuffer;
SECPKG_CALL_INFO CallInfo;
BOOL fAllocatedSubmitBuffer = FALSE;
if ( ClientRequest == (PLSA_CLIENT_REQUEST)( -1 ) )
{
//
// if the call originated inproc, the buffers have already been
// marshalled/etc.
//
ZeroMemory( &CallInfo, sizeof(CallInfo) );
} else {
if (!LsaFunctions->GetCallInfo(&CallInfo))
{
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
}
#endif
//
// CachedInteractive logons are treated same as Interactive except
// that we avoid hitting the network.
//
if (LogonType == CachedInteractive) {
fWaitForNetwork = FALSE;
LogonType = Interactive;
}
//
// Begin tracing a logon user
//
if (NtlmGlobalEventTraceFlag){
//
// Trace header goo
//
SET_TRACE_HEADER(TraceInfo,
NtlmLogonGuid,
EVENT_TRACE_TYPE_START,
WNODE_FLAG_TRACED_GUID,
0);
TraceEvent(NtlmGlobalTraceLoggerHandle,
(PEVENT_TRACE_HEADER)&TraceInfo);
}
//
// Initialize
//
*ProfileBuffer = NULL;
*SubStatus = STATUS_SUCCESS;
*AuthenticatingAuthority = NULL;
*AccountName = NULL;
TmpName.Buffer = TmpNameBuffer;
TmpName.MaximumLength = UNLEN * sizeof( WCHAR );
TmpName.Length = 0;
TmpAuthority.Buffer = TmpAuthorityBuffer;
TmpAuthority.MaximumLength = DNS_MAX_NAME_LENGTH * sizeof( WCHAR );
TmpAuthority.Length = 0;
CredmanUserName.Buffer = NULL;
CredmanDomainName.Buffer = NULL;
CredmanPassword.Buffer = NULL;
*SupplementalCredentials = 0;
RtlZeroMemory(
PrimaryCredentials,
sizeof(SECPKG_PRIMARY_CRED)
);
//
// Check the Authentication information and build a LogonInformation
// structure to pass to SAM or Netlogon.
//
// NOTE: Netlogon treats Service and Batch logons as if they are
// Interactive.
//
switch ( LogonType ) {
case Service:
case Interactive:
case Batch:
case NetworkCleartext:
case RemoteInteractive:
{
MSV1_0_PRIMARY_CREDENTIAL BuiltCredential;
#if _WIN64
//
// Expand the ProtocolSubmitBuffer to 64-bit pointers if this
// call came from a WOW client.
//
if (CallInfo.Attributes & SECPKG_CALL_WOWCLIENT)
{
Authentication =
(PMSV1_0_INTERACTIVE_LOGON) ProtocolSubmitBuffer;
Status = MsvConvertWOWInteractiveLogonBuffer(
ProtocolSubmitBuffer,
ClientBufferBase,
&SubmitBufferSize,
&pTempSubmitBuffer
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
fAllocatedSubmitBuffer = TRUE;
//
// Some macros below expand out to use ProtocolSubmitBuffer directly.
// We've secretly replaced their usual ProtocolSubmitBuffer with
// pTempSubmitBuffer -- let's see if they can tell the difference.
//
ProtocolSubmitBuffer = pTempSubmitBuffer;
}
#endif // _WIN64
WorkStationName = &NlpComputerName;
//
// Ensure this is really an interactive logon.
//
Authentication =
(PMSV1_0_INTERACTIVE_LOGON) ProtocolSubmitBuffer;
if (SubmitBufferSize < sizeof(MSV1_0_INTERACTIVE_LOGON)) {
SspPrint((SSP_CRITICAL, "LsaApLogonUser: bogus interactive logon info size %#x\n", SubmitBufferSize));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if ( (Authentication->MessageType != MsV1_0InteractiveLogon ) &&
(Authentication->MessageType != MsV1_0WorkstationUnlockLogon) ) {
SspPrint((SSP_CRITICAL, "LsaApLogonUser: Bad Validation Class %d\n", Authentication->MessageType));
Status = STATUS_BAD_VALIDATION_CLASS;
goto Cleanup;
}
//
// If the password length is greater than 255 (i.e., the
// upper byte of the length is non-zero) then the password
// has been run-encoded for privacy reasons. Get the
// run-encode seed out of the upper-byte of the length
// for later use.
//
//
SeedAndLength = (PSECURITY_SEED_AND_LENGTH)
&Authentication->Password.Length;
Seed = SeedAndLength->Seed;
SeedAndLength->Seed = 0;
//
// Enforce length restrictions on username and password.
//
if ( Authentication->UserName.Length > (UNLEN*sizeof(WCHAR)) ||
Authentication->Password.Length > (PWLEN*sizeof(WCHAR)) )
{
SspPrint((SSP_CRITICAL, "LsaApLogonUser: Name or password too long\n"));
Status = STATUS_NAME_TOO_LONG;
goto Cleanup;
}
//
// Relocate any pointers to be relative to 'Authentication'
//
NULL_RELOCATE_ONE( &Authentication->LogonDomainName );
RELOCATE_ONE( &Authentication->UserName );
NULL_RELOCATE_ONE( &Authentication->Password );
if ( (Authentication->LogonDomainName.Length <= sizeof(WCHAR)) &&
(Authentication->Password.Length <= sizeof(WCHAR))
)
{
Status = CredpProcessUserNameCredential(
&Authentication->UserName,
&CredmanUserName,
&CredmanDomainName,
&CredmanPassword
);
if ( NT_SUCCESS(Status) )
{
Authentication->UserName = CredmanUserName;
Authentication->LogonDomainName = CredmanDomainName;
Authentication->Password = CredmanPassword;
} else if (Status == STATUS_NOT_SUPPORTED)
{
SspPrint((SSP_CRITICAL, "LsaApLogonUser: unsupported marshalled cred\n"));
goto Cleanup;
}
Status = STATUS_SUCCESS;
}
#if 0
//
// Handle UPN and composite NETBIOS syntax
//
{
UNICODE_STRING User = Authentication->UserName;
UNICODE_STRING Domain = Authentication->LogonDomainName;
Status =
NtLmParseName(
&User,
&Domain,
FALSE
);
if(NT_SUCCESS(Status)){
Authentication->UserName = User;
Authentication->LogonDomainName = Domain;
}
}
#endif
if ( LogonType == Service )
{
SECPKG_CALL_INFO CallInfo;
if ( LsaFunctions->GetCallInfo(&CallInfo) &&
(CallInfo.Attributes & SECPKG_CALL_IS_TCB) )
{
//
// If we have a service logon, the password we got is likely the name of the secret
// that is holding the account password. Make sure to read that secret here
//
RtlInitUnicodeString( &Prefix, L"_SC_" );
if ( RtlPrefixUnicodeString( &Prefix, &Authentication->Password, TRUE ) )
{
Status = LsarOpenSecret( NtLmGlobalPolicyHandle,
( PLSAPR_UNICODE_STRING )&Authentication->Password,
SECRET_QUERY_VALUE,
&SecretHandle );
if ( NT_SUCCESS( Status ) )
{
Status = LsarQuerySecret( SecretHandle,
&SecretCurrent,
NULL,
NULL,
NULL );
if ( NT_SUCCESS( Status ) && (SecretCurrent != NULL) )
{
RtlCopyMemory( &SavedPassword,
&Authentication->Password,
sizeof( UNICODE_STRING ) );
Authentication->Password.Length = ( USHORT )SecretCurrent->Length;
Authentication->Password.MaximumLength =
( USHORT )SecretCurrent->MaximumLength;
Authentication->Password.Buffer = ( USHORT * )SecretCurrent->Buffer;
ServiceSecretLogon = TRUE;
Seed = 0; // do not run RtlRunDecodeUnicodeString for this password
}
LsarClose( &SecretHandle );
}
}
}
if ( !NT_SUCCESS( Status ) ) {
SspPrint((SSP_CRITICAL, "LsaApLogonUser: failed to querying service password\n"));
goto Cleanup;
}
}
//
// Now decode the password, if necessary
//
if (Seed != 0) {
try {
RtlRunDecodeUnicodeString( Seed, &Authentication->Password);
} except(EXCEPTION_EXECUTE_HANDLER) {
SspPrint((SSP_CRITICAL, "LsaApLogonUser: failed to decode password\n"));
Status = STATUS_ILL_FORMED_PASSWORD;
goto Cleanup;
}
}
//
// Copy out the user name and Authenticating Authority so we can audit them.
//
RtlCopyUnicodeString( &TmpName, &Authentication->UserName );
if ( Authentication->LogonDomainName.Buffer != NULL ) {
RtlCopyUnicodeString( &TmpAuthority, &Authentication->LogonDomainName );
}
//
// Put the password in the PrimaryCredential to pass to the sundry security packages.
//
PrimaryCredentials->Password.Length = PrimaryCredentials->Password.MaximumLength =
Authentication->Password.Length;
PrimaryCredentials->Password.Buffer = (*Lsa.AllocateLsaHeap)(Authentication->Password.Length);
if (PrimaryCredentials->Password.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
PrimaryCredentials->Password.Buffer,
Authentication->Password.Buffer,
Authentication->Password.Length
);
PrimaryCredentials->Flags = PRIMARY_CRED_CLEAR_PASSWORD;
//
// We're all done with the cleartext password
// Don't let it get to the pagefile.
//
try {
if ( Authentication->Password.Buffer != NULL ) {
RtlEraseUnicodeString( &Authentication->Password );
}
} except(EXCEPTION_EXECUTE_HANDLER) {
SspPrint((SSP_CRITICAL, "LsaApLogonUser: failed to decode password\n"));
Status = STATUS_ILL_FORMED_PASSWORD;
goto Cleanup;
}
//
// Compute the OWF of the password.
//
NlpPutOwfsInPrimaryCredential( &PrimaryCredentials->Password,
(BOOLEAN) (PrimaryCredentials->Flags & PRIMARY_CRED_OWF_PASSWORD),
&BuiltCredential );
//
// Define the description of the user to log on.
//
LogonLevel = NetlogonInteractiveInformation;
LogonInformation =
(PNETLOGON_LOGON_IDENTITY_INFO) &LogonInteractive;
LogonInteractive.Identity.LogonDomainName =
Authentication->LogonDomainName;
LogonInteractive.Identity.ParameterControl = 0;
LogonInteractive.Identity.UserName = Authentication->UserName;
LogonInteractive.Identity.Workstation = NlpComputerName;
LogonInteractive.LmOwfPassword = BuiltCredential.LmOwfPassword;
LogonInteractive.NtOwfPassword = BuiltCredential.NtOwfPassword;
RtlSecureZeroMemory(&BuiltCredential, sizeof(BuiltCredential));
}
break;
case Network:
{
PMSV1_0_LM20_LOGON Authentication;
BOOLEAN EnforceTcb = FALSE;
//
// Expand the ProtocolSubmitBuffer to 64-bit pointers if this
// call came from a WOW client.
//
#if _WIN64
if (CallInfo.Attributes & SECPKG_CALL_WOWCLIENT)
{
Authentication =
(PMSV1_0_LM20_LOGON) ProtocolSubmitBuffer;
Status = MsvConvertWOWNetworkLogonBuffer(
ProtocolSubmitBuffer,
ClientBufferBase,
&SubmitBufferSize,
&pTempSubmitBuffer
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
fAllocatedSubmitBuffer = TRUE;
//
// Some macros below expand out to use ProtocolSubmitBuffer directly.
// We've secretly replaced their usual ProtocolSubmitBuffer with
// pTempSubmitBuffer -- let's see if they can tell the difference.
//
ProtocolSubmitBuffer = pTempSubmitBuffer;
}
#endif
//
// Ensure this is really a network logon request.
//
Authentication =
(PMSV1_0_LM20_LOGON) ProtocolSubmitBuffer;
NetworkAuthentication = Authentication;
if (SubmitBufferSize < sizeof(MSV1_0_LM20_LOGON)) {
SspPrint((SSP_CRITICAL, "LsaApLogonUser: bogus network logon info size %#x\n", SubmitBufferSize));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if ( Authentication->MessageType != MsV1_0Lm20Logon &&
Authentication->MessageType != MsV1_0SubAuthLogon &&
Authentication->MessageType != MsV1_0NetworkLogon )
{
SspPrint((SSP_CRITICAL, "LsaApLogonUser: Bad Validation Class\n"));
Status = STATUS_BAD_VALIDATION_CLASS;
goto Cleanup;
}
//
// Relocate any pointers to be relative to 'Authentication'
//
NULL_RELOCATE_ONE( &Authentication->LogonDomainName );
NULL_RELOCATE_ONE( &Authentication->UserName );
RELOCATE_ONE( &Authentication->Workstation );
#if 0
//
// Handle UPN and composite NETBIOS syntax
//
{
UNICODE_STRING User = Authentication->UserName;
UNICODE_STRING Domain = Authentication->LogonDomainName;
Status =
NtLmParseName(
&User,
&Domain,
FALSE
);
if(NT_SUCCESS(Status)){
Authentication->UserName = User;
Authentication->LogonDomainName = Domain;
}
}
#endif
//
// Copy out the user name and Authenticating Authority so we can audit them.
//
if ( Authentication->UserName.Buffer != NULL ) {
RtlCopyUnicodeString( &TmpName, &Authentication->UserName );
}
if ( Authentication->LogonDomainName.Buffer != NULL ) {
RtlCopyUnicodeString( &TmpAuthority, &Authentication->LogonDomainName );
}
NULL_RELOCATE_ONE((PUNICODE_STRING)&Authentication->CaseSensitiveChallengeResponse );
NULL_RELOCATE_ONE((PUNICODE_STRING)&Authentication->CaseInsensitiveChallengeResponse );
//
// Define the description of the user to log on.
//
LogonLevel = NetlogonNetworkInformation;
LogonInformation =
(PNETLOGON_LOGON_IDENTITY_INFO) &LogonNetwork;
LogonNetwork.Identity.LogonDomainName =
Authentication->LogonDomainName;
if ( Authentication->ParameterControl & MSV1_0_CLEARTEXT_PASSWORD_SUPPLIED )
{
NT_OWF_PASSWORD NtOwfPassword;
LM_OWF_PASSWORD LmOwfPassword;
CHAR LmPassword[LM20_PWLEN + 1] = {0};
UCHAR Challenge[MSV1_0_CHALLENGE_LENGTH] = {0};
ULONG LmProtocolSupported = NtLmGlobalLmProtocolSupported;
//
// trash the challenge, to avoid allowing a challenge/response
// replay through this (untrusted) interface.
//
Status = SspGenerateRandomBits(Authentication->ChallengeToClient, sizeof(Authentication->ChallengeToClient));
if (NT_SUCCESS(Status))
{
SspPrint((SSP_WARNING, "LsaApLogonUserEx2: ClearText password supplied, ChallengeToClient trashed\n"));
Authentication->ParameterControl &= ~MSV1_0_USE_CLIENT_CHALLENGE;
RtlCopyMemory(
LmPassword,
Authentication->CaseInsensitiveChallengeResponse.Buffer,
min(LM20_PWLEN, Authentication->CaseInsensitiveChallengeResponse.Length)
);
Status = RtlCalculateLmOwfPassword(LmPassword, &LmOwfPassword);
}
if (NT_SUCCESS(Status))
{
Status = RtlCalculateNtOwfPassword(
(UNICODE_STRING*) &Authentication->CaseSensitiveChallengeResponse,
&NtOwfPassword
);
}
if (NT_SUCCESS(Status))
{
if (LmProtocolSupported < NoLm)
{
Status = RtlCalculateLmResponse(
(PLM_CHALLENGE) Authentication->ChallengeToClient,
&LmOwfPassword,
&LmResponse
);
RtlCopyMemory(Challenge, Authentication->ChallengeToClient, sizeof(Challenge));
}
else if (LmProtocolSupported == NoLm)
{
Authentication->ParameterControl |= MSV1_0_USE_CLIENT_CHALLENGE;
Status = SspGenerateRandomBits(&LmResponse, MSV1_0_CHALLENGE_LENGTH);
if (NT_SUCCESS(Status))
{
MsvpCalculateNtlm2Challenge(
Authentication->ChallengeToClient,
(UCHAR*) &LmResponse,
Challenge
);
}
}
else if (LmProtocolSupported >= UseNtlm3)
{
MsvpLm3Response(
&NtOwfPassword,
&Authentication->UserName,
&Authentication->LogonDomainName,
Authentication->ChallengeToClient,
(MSV1_0_LM3_RESPONSE*) &LmResponse,
(UCHAR*) &LmResponse,
NULL,
NULL
);
}
}
if (NT_SUCCESS(Status))
{
Authentication->ParameterControl &= ~(MSV1_0_CLEARTEXT_PASSWORD_SUPPLIED | MSV1_0_CLEARTEXT_PASSWORD_ALLOWED);
Authentication->CaseInsensitiveChallengeResponse.MaximumLength =
Authentication->CaseInsensitiveChallengeResponse.Length = sizeof(LmResponse);
Authentication->CaseInsensitiveChallengeResponse.Buffer = (CHAR*) &LmResponse;
if (LmProtocolSupported < UseNtlm3)
{
Status = RtlCalculateNtResponse(
(PNT_CHALLENGE) Challenge,
&NtOwfPassword,
&NtResponse
);
if (NT_SUCCESS(Status))
{
Authentication->CaseSensitiveChallengeResponse.MaximumLength =
Authentication->CaseSensitiveChallengeResponse.Length = sizeof(NtResponse);
Authentication->CaseSensitiveChallengeResponse.Buffer = (CHAR*) &NtResponse;
}
}
else
{
Authentication->CaseSensitiveChallengeResponse.MaximumLength =
Authentication->CaseSensitiveChallengeResponse.Length = 0;
}
}
RtlSecureZeroMemory(&NtOwfPassword, sizeof(NtOwfPassword));
RtlSecureZeroMemory(&LmOwfPassword, sizeof(LmOwfPassword));
RtlSecureZeroMemory(LmPassword, sizeof(LmPassword));
if (!NT_SUCCESS(Status))
{
SspPrint((SSP_CRITICAL, "LsaApLogonUserEx2 failed cleartext logon\n"));
goto Cleanup;
}
} else {
//
// if cleartext was not supplied, caller must be trusted for inproc calls.
//
if ( ClientRequest != (PLSA_CLIENT_REQUEST)( -1 ) )
{
EnforceTcb = TRUE;
}
}
if ( Authentication->MessageType == MsV1_0Lm20Logon ) {
LogonNetwork.Identity.ParameterControl = MSV1_0_CLEARTEXT_PASSWORD_ALLOWED;
} else {
ASSERT( CLEARTEXT_PASSWORD_ALLOWED == MSV1_0_CLEARTEXT_PASSWORD_ALLOWED );
LogonNetwork.Identity.ParameterControl =
Authentication->ParameterControl;
// For NT 5.0 SubAuth Packages, there is a SubAuthPackageId. Stuff
// that into ParameterControl so pre 5.0 MsvSamValidate won't choke.
if ( Authentication->MessageType == MsV1_0SubAuthLogon )
{
PMSV1_0_SUBAUTH_LOGON SubAuthentication =
(PMSV1_0_SUBAUTH_LOGON) ProtocolSubmitBuffer;
// Need to not delete return buffers even in case of error
// for MsV1_0SubAuthLogon (includes arap).
fSubAuthEx = TRUE;
LogonNetwork.Identity.ParameterControl |=
(SubAuthentication->SubAuthPackageId << MSV1_0_SUBAUTHENTICATION_DLL_SHIFT) | MSV1_0_SUBAUTHENTICATION_DLL_EX;
EnforceTcb = TRUE ;
} else {
if ( Authentication->ParameterControl & MSV1_0_SUBAUTHENTICATION_DLL )
{
EnforceTcb = TRUE;
}
}
}
if ( EnforceTcb )
{
SECPKG_CALL_INFO CallInfo;
if (!LsaFunctions->GetCallInfo(&CallInfo) ||
(CallInfo.Attributes & SECPKG_CALL_IS_TCB) == 0)
{
SspPrint((SSP_CRITICAL, "LsaApLogonUser: subauth/chalresp caller isn't privileged\n"));
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
}
LogonNetwork.Identity.UserName = Authentication->UserName;
LogonNetwork.Identity.Workstation = Authentication->Workstation;
WorkStationName = &Authentication->Workstation;
LogonNetwork.NtChallengeResponse =
Authentication->CaseSensitiveChallengeResponse;
LogonNetwork.LmChallengeResponse =
Authentication->CaseInsensitiveChallengeResponse;
ASSERT( LM_CHALLENGE_LENGTH ==
sizeof(Authentication->ChallengeToClient) );
//
// If using client challenge, then mix it with the server's challenge
// to get the challenge we pass on. It would make more sense to do this
// in MsvpPasswordValidate, except that would require the DCs to be upgraded.
// Doing it here only requires agreement between the client and server, because
// the modified challenge will be passed on to the DCs.
//
if ((Authentication->ParameterControl & MSV1_0_USE_CLIENT_CHALLENGE) &&
(Authentication->CaseSensitiveChallengeResponse.Length == NT_RESPONSE_LENGTH) &&
(Authentication->CaseInsensitiveChallengeResponse.Length >= MSV1_0_CHALLENGE_LENGTH))
{
MsvpCalculateNtlm2Challenge (
Authentication->ChallengeToClient,
(PUCHAR) Authentication->CaseInsensitiveChallengeResponse.Buffer,
(PUCHAR) &LogonNetwork.LmChallenge
);
} else {
RtlCopyMemory(
&LogonNetwork.LmChallenge,
Authentication->ChallengeToClient,
LM_CHALLENGE_LENGTH );
}
//
// if using NTLM3, then check that the target info is for this machine.
//
if ((Authentication->ParameterControl & MSV1_0_USE_CLIENT_CHALLENGE) &&
(Authentication->CaseSensitiveChallengeResponse.Length >= sizeof(MSV1_0_NTLM3_RESPONSE)))
{
fNtLm3 = TRUE;
//
// defer NTLM3 checks until later on when SAM initialized.
//
}
//
// Enforce length restrictions on username
//
if ( Authentication->UserName.Length > (UNLEN*sizeof(WCHAR)) )
{
SspPrint((SSP_CRITICAL, "LsaApLogonUser: Name too long\n"));
Status = STATUS_NAME_TOO_LONG;
goto Cleanup;
}
//
// If this is a null session logon,
// just build a NULL token.
//
if ( Authentication->UserName.Length == 0 &&
Authentication->CaseSensitiveChallengeResponse.Length == 0 &&
(Authentication->CaseInsensitiveChallengeResponse.Length == 0 ||
(Authentication->CaseInsensitiveChallengeResponse.Length == 1 &&
*Authentication->CaseInsensitiveChallengeResponse.Buffer == '\0') ) ) {
LsaTokenInformationType = LsaTokenInformationNull;
}
}
break;
default:
Status = STATUS_INVALID_LOGON_TYPE;
goto CleanupShort;
}
//
// Allocate a LogonId for this logon session.
//
Status = NtAllocateLocallyUniqueId( LogonId );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
NEW_TO_OLD_LARGE_INTEGER( (*LogonId), LogonInformation->LogonId );
PrimaryCredentials->LogonId = *LogonId;
PrimaryCredentials->Flags |= (RPC_C_AUTHN_WINNT << PRIMARY_CRED_LOGON_PACKAGE_SHIFT);
//
// Create a new logon session
//
Status = (*Lsa.CreateLogonSession)( LogonId );
if ( !NT_SUCCESS(Status) ) {
SspPrint((SSP_CRITICAL, "LsaApLogonUser: Collision from CreateLogonSession %x\n", Status));
goto Cleanup;
}
LogonSessionCreated = TRUE;
//
// Don't worry about SAM or the LSA if this is a Null Session logon.
//
// The server does a Null Session logon during initialization.
// It shouldn't have to wait for SAM to initialize.
//
if ( LsaTokenInformationType != LsaTokenInformationNull ) {
//
// If Sam is not yet initialized,
// do it now.
//
if ( !NlpSamInitialized ) {
Status = NlSamInitialize( SAM_STARTUP_TIME );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
}
//
// If this is a workstation,
// differentiate between a standalone workstation and a member
// workstation.
//
// (This is is done on every logon, rather than during initialization,
// to allow the value to be changed via the UI).
//
if ( NlpWorkstation ) {
RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
StandaloneWorkstation = (BOOLEAN) (NtLmGlobalTargetFlags == NTLMSSP_TARGET_TYPE_SERVER);
RtlReleaseResource(&NtLmGlobalCritSect);
} else {
StandaloneWorkstation = FALSE;
}
}
//
// Try again to load netlogon.dll
//
if ( NlpNetlogonDllHandle == NULL ) {
NlpLoadNetlogonDll();
}
//
// do NTLM3 processing that was deferred until now due to initialization
// requirements.
//
if ( fNtLm3 )
{
PMSV1_0_AV_PAIR pAV;
PMSV1_0_NTLM3_RESPONSE pResp;
LONG iRespLen;
ULONG NtLmProtocolSupported = NtLmGlobalLmProtocolSupported;
//
// get the computer name from the response
//
pResp = (PMSV1_0_NTLM3_RESPONSE)
NetworkAuthentication->CaseSensitiveChallengeResponse.Buffer;
iRespLen = NetworkAuthentication->CaseSensitiveChallengeResponse.Length -
sizeof(MSV1_0_NTLM3_RESPONSE);
pAV = MsvpAvlGet((PMSV1_0_AV_PAIR)pResp->Buffer, MsvAvNbComputerName, iRespLen);
//
// if there is one (OK to be missing), see that it is us
// REVIEW -- only allow it to be missing if registry says OK?
//
if (pAV) {
UNICODE_STRING Candidate;
Candidate.Buffer = (PWSTR)(pAV+1);
Candidate.Length = (USHORT)(pAV->AvLen);
Candidate.MaximumLength = Candidate.Length;
if(!RtlEqualUnicodeString( &NlpComputerName, &Candidate, TRUE ))
{
SspPrint((SSP_WARNING, "LsaApLogonUserEx2 failed NbComputerName compare\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
} else if (NtLmProtocolSupported >= RefuseNtlm3NoTarget) {
SspPrint((SSP_WARNING, "LsaApLogonUserEx2 no target supplied\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// get the domain name from the response
//
pAV = MsvpAvlGet((PMSV1_0_AV_PAIR)pResp->Buffer, MsvAvNbDomainName, iRespLen);
//
// must exist and must be us.
//
if (pAV) {
UNICODE_STRING Candidate;
Candidate.Buffer = (PWSTR)(pAV+1);
Candidate.Length = pAV->AvLen;
Candidate.MaximumLength = pAV->AvLen;
if( StandaloneWorkstation ) {
if( !RtlEqualDomainName(&NlpComputerName, &Candidate) ) {
SspPrint((SSP_WARNING, "LsaApLogonUserEx2 failed NbDomainName compare\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
} else {
if( !RtlEqualDomainName(&NlpPrimaryDomainName, &Candidate) ) {
SspPrint((SSP_WARNING, "LsaApLogonUserEx2 failed PrimaryDomainName compare\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
}
} else {
SspPrint((SSP_WARNING, "LsaApLogonUserEx2 domain name not supplied\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
}
//
// Do the actual logon now.
//
//
// If a null token is being built,
// don't authenticate at all.
//
if ( LsaTokenInformationType == LsaTokenInformationNull ) {
/* Nothing to do here. */
//
// Call Sam directly to get the validation information when:
//
// The network is not installed, OR
// This is a standalone workstation (not a member of a domain).
// This is a workstation and we're logging onto an account on the
// workstation.
//
} else if ( NlpNetlogonDllHandle == NULL || !NlpLanmanInstalled ||
//
// StandaloneWorkstation and NtLmGlobalUnicodeDnsDomainNameString.Length != 0 means
// the machine is joined to MIT realms
//
(StandaloneWorkstation && (NtLmGlobalUnicodeDnsDomainNameString.Length == 0))
|| ( NlpWorkstation
&& (LogonInformation->LogonDomainName.Length != 0)
&& RtlEqualDomainName( &NlpSamDomainName,
&LogonInformation->LogonDomainName )) ) {
// Allow guest logons only
DWORD AccountsToTry = MSVSAM_SPECIFIED | MSVSAM_GUEST;
if ((LogonType == Network) &&
(LogonNetwork.Identity.ParameterControl & MSV1_0_TRY_GUEST_ACCOUNT_ONLY))
{
AccountsToTry = MSVSAM_GUEST;
}
//
// for local logons, CachedInteractive is not supported.
//
if ( !fWaitForNetwork )
{
Status = STATUS_NOT_SUPPORTED;
goto Cleanup;
}
TryCacheFirst = FALSE;
Authoritative = FALSE;
//
// Get the Validation information from the local SAM database
//
Status = MsvSamValidate(
NlpSamDomainHandle,
NlpUasCompatibilityRequired,
MsvApSecureChannel,
&NlpComputerName, // Logon Server is this machine
&NlpSamDomainName,
NlpSamDomainId,
LogonLevel,
LogonInformation,
NetlogonValidationSamInfo4,
(PVOID *) &NlpUser,
&Authoritative,
&BadPasswordCountZeroed,
AccountsToTry);
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
// So we don't get a LOGON COLLISION from the old msv package
Flags |= LOGON_BY_LOCAL;
//
// If we couldn't validate via one of the above mechanisms,
// call the local Netlogon service to get the validation information.
//
} else {
//
// machines joined to MIT realms
//
if (StandaloneWorkstation) // NtLmGlobalUnicodeDnsDomainNameString.Length != 0
{
fWaitForNetwork = FALSE; // no netlogon service
TryCacheFirst = TRUE; // need local fallback when cachedlogon fails
Status = STATUS_NO_LOGON_SERVERS; // fake it
}
if ( fWaitForNetwork )
{
if ( (NtLmCheckProcessOption( MSV1_0_OPTION_TRY_CACHE_FIRST ) & MSV1_0_OPTION_TRY_CACHE_FIRST) )
{
TryCacheFirst = TRUE;
}
}
//
// If we are attempting cached credentials logon avoid getting stuck
// on netlogon or the network.
//
RetryNonCached:
if (fWaitForNetwork && !TryCacheFirst) {
//
// Wait for NETLOGON to finish initialization.
//
if ( !NlpNetlogonInitialized ) {
Status = NlWaitForNetlogon( NETLOGON_STARTUP_TIME );
if ( !NT_SUCCESS(Status) ) {
if ( Status != STATUS_NETLOGON_NOT_STARTED ) {
goto Cleanup;
}
} else {
NlpNetlogonInitialized = TRUE;
}
}
//
// Actually call the netlogon service.
//
if ( NlpNetlogonInitialized ) {
Authoritative = FALSE;
Status = (*NlpNetLogonSamLogon)(
NULL, // Server name
NULL, // Computer name
NULL, // Authenticator
NULL, // ReturnAuthenticator
LogonLevel,
(LPBYTE) &LogonInformation,
NetlogonValidationSamInfo4,
(LPBYTE *) &NlpUser,
&Authoritative );
//
// save the result from netlogon.
//
NetlogonStatus = Status;
//
// Reset Netlogon initialized flag if local netlogon cannot be
// reached.
// (Use a more explicit status code)
//
if ( !NT_SUCCESS(Status) )
{
switch (Status)
{
//
// for documented errors that netlogon can return
// for authoritative failures, leave the status code as-is.
//
case STATUS_NO_TRUST_LSA_SECRET:
case STATUS_TRUSTED_DOMAIN_FAILURE:
case STATUS_INVALID_INFO_CLASS:
case STATUS_TRUSTED_RELATIONSHIP_FAILURE:
case STATUS_ACCESS_DENIED:
case STATUS_NO_SUCH_USER:
case STATUS_WRONG_PASSWORD:
case STATUS_INVALID_LOGON_HOURS:
case STATUS_PASSWORD_EXPIRED:
case STATUS_ACCOUNT_DISABLED:
case STATUS_INVALID_PARAMETER:
case STATUS_PASSWORD_MUST_CHANGE:
case STATUS_ACCOUNT_EXPIRED:
case STATUS_ACCOUNT_LOCKED_OUT:
case STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT:
case STATUS_NOLOGON_SERVER_TRUST_ACCOUNT:
case STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT:
case STATUS_INVALID_WORKSTATION:
case STATUS_DLL_NOT_FOUND: // subauth dll not found
case STATUS_PROCEDURE_NOT_FOUND: // returned when subauth registry is not found or procedure is not found
case STATUS_ACCOUNT_RESTRICTION: // Other Org check failed
case STATUS_AUTHENTICATION_FIREWALL_FAILED: // Other Org check failed
{
break;
}
//
// for errors that are known to occur during unexpected
// conditions, over-ride status to allow cache lookup.
//
case RPC_NT_SERVER_UNAVAILABLE:
case RPC_NT_UNKNOWN_IF:
case RPC_NT_CALL_CANCELLED:
{
NetlogonStatus = STATUS_NO_LOGON_SERVERS;
Status = NetlogonStatus;
NlpNetlogonInitialized = FALSE;
break;
}
// default will catch a host of RPC related errors.
// some mentioned below.
//case EPT_NT_NOT_REGISTERED:
//case RPC_NT_CALL_FAILED_DNE:
//case RPC_NT_SERVER_TOO_BUSY:
//case RPC_NT_CALL_FAILED:
// case STATUS_NETLOGON_NOT_STARTED:
default:
{
Status = STATUS_NETLOGON_NOT_STARTED;
NlpNetlogonInitialized = FALSE;
break;
}
} // switch
} // if
}
else
{
NetlogonStatus = STATUS_NETLOGON_NOT_STARTED;
Status = NetlogonStatus;
}
} else {
//
// We want to force cached credentials path by behaving as if no
// network logon servers were available.
//
NetlogonStatus = STATUS_NO_LOGON_SERVERS;
Status = NetlogonStatus;
}
//
// If this is the requested domain,
// go directly to SAM if the netlogon service isn't available.
//
// We want to go to the netlogon service if it is available since it
// does special handling of bad passwords and account lockout. However,
// if the netlogon service is down, the local SAM database makes a
// better cache than any other mechanism.
//
if ( (!NlpNetlogonInitialized
&& (LogonInformation->LogonDomainName.Length != 0)
&& RtlEqualDomainName( &NlpSamDomainName,
&LogonInformation->LogonDomainName ))
|| (StandaloneWorkstation && !TryCacheFirst) // fallback case for machines joined to MIT realms
) {
// Allow guest logons only
DWORD AccountsToTry = MSVSAM_SPECIFIED | MSVSAM_GUEST;
if ((LogonType == Network) &&
(LogonNetwork.Identity.ParameterControl & MSV1_0_TRY_GUEST_ACCOUNT_ONLY))
{
AccountsToTry = MSVSAM_GUEST;
}
//
// we aren't trying to satisfy from cache.
//
TryCacheFirst = FALSE;
Authoritative = FALSE;
//
// Get the Validation information from the local SAM database
//
Status = MsvSamValidate(
NlpSamDomainHandle,
NlpUasCompatibilityRequired,
MsvApSecureChannel,
&NlpComputerName, // Logon Server is this machine
&NlpSamDomainName,
NlpSamDomainId,
LogonLevel,
LogonInformation,
NetlogonValidationSamInfo4,
(PVOID *) &NlpUser,
&Authoritative,
&BadPasswordCountZeroed,
AccountsToTry);
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
// So we don't get a LOGON COLLISION from the old msv package
Flags |= LOGON_BY_LOCAL;
//
// If Netlogon was successful,
// add this user to the logon cache.
//
} else if ( NT_SUCCESS( Status ) ) {
//
// Indicate this session was validated by the Netlogon
// service.
//
Flags |= LOGON_BY_NETLOGON;
//
// Cache interactive logon information.
//
// NOTE: Batch and Service logons are treated
// the same as Interactive here.
//
if (LogonType == Interactive ||
LogonType == Service ||
LogonType == Batch ||
LogonType == RemoteInteractive) {
NTSTATUS ntStatus;
LogonInteractive.Identity.ParameterControl = RPC_C_AUTHN_WINNT;
ntStatus = NlpAddCacheEntry(
&LogonInteractive,
NlpUser,
NULL,
0,
MSV1_0_CACHE_LOGON_REQUEST_INFO4
);
}
//
// If Netlogon is simply not available at this time,
// try to logon through the cache.
//
// STATUS_NO_LOGON_SERVERS indicates the netlogon service couldn't
// contact a DC to handle this request.
//
// STATUS_NETLOGON_NOT_STARTED indicates the local netlogon service
// isn't running.
//
//
// We use the cache for ANY logon type. This not only allows a
// user to logon interactively, but it allows that same user to
// connect from another machine while the DC is down.
//
} else if ( Status == STATUS_NO_LOGON_SERVERS ||
Status == STATUS_NETLOGON_NOT_STARTED ) {
NTSTATUS ntStatus;
CACHE_PASSWORDS cachePasswords;
ULONG LocalFlags = 0;
CacheTried = TRUE;
//
// reset Status to NetlogonStatus if an error was encountered.
//
if (!NT_SUCCESS( NetlogonStatus ))
{
Status = NetlogonStatus;
}
//
// Try to logon via the cache.
//
//
ntStatus = NlpGetCacheEntry(
LogonInformation,
0, // no lookup flags
&CredentialDomainName,
&CredentialUserName,
&NlpUser,
&cachePasswords,
NULL, // SupplementalCacheData
NULL // SupplementalCacheDataLength
);
if (!NT_SUCCESS(ntStatus)) {
//
// The original status code is more interesting than
// the fact that the cache didn't work.
//
NlpUser = NULL; // NlpGetCacheEntry dirties this
goto Cleanup;
}
if ( LogonType != Network )
{
//
// The cache information contains salted hashed passwords,
// so modify the logon information similarly.
//
ntStatus = NlpComputeSaltedHashedPassword(
&LogonInteractive.NtOwfPassword,
&LogonInteractive.NtOwfPassword,
&NlpUser->EffectiveName
);
if (!NT_SUCCESS(ntStatus)) {
goto Cleanup;
}
ntStatus = NlpComputeSaltedHashedPassword(
&LogonInteractive.LmOwfPassword,
&LogonInteractive.LmOwfPassword,
&NlpUser->EffectiveName
);
if (!NT_SUCCESS(ntStatus)) {
goto Cleanup;
}
} else {
PMSV1_0_PRIMARY_CREDENTIAL TempPrimaryCredential;
ULONG PrimaryCredentialSize;
if (!UserSid)
{
UserSid = NlpMakeDomainRelativeSid(NlpUser->LogonDomainId, NlpUser->UserId);
if (UserSid == NULL)
{
Status = STATUS_NO_MEMORY;
SspPrint((SSP_CRITICAL, "LsaApLogonUser: NlpMakeDomainRelativeSid no memory\n"));
goto Cleanup;
}
}
//
// because the cache no longer stores OWFs, the cached salted OWF
// is not useful for validation for network logon.
// The only place we can get a OWF to match is the active logon
// cache
//
ntStatus = NlpGetPrimaryCredentialByUserSid(
UserSid,
&TempPrimaryCredential,
&PrimaryCredentialSize
);
if (!NT_SUCCESS(ntStatus))
{
Status = STATUS_WRONG_PASSWORD;
goto Cleanup;
}
//
// copy out the OWFs, then free the allocated buffer.
//
if (TempPrimaryCredential->NtPasswordPresent)
{
RtlCopyMemory(&cachePasswords.SecretPasswords.NtOwfPassword, &TempPrimaryCredential->NtOwfPassword, sizeof(NT_OWF_PASSWORD));
cachePasswords.SecretPasswords.NtPasswordPresent = TRUE;
}
else
{
cachePasswords.SecretPasswords.NtPasswordPresent = FALSE;
}
if (TempPrimaryCredential->LmPasswordPresent)
{
RtlCopyMemory(&cachePasswords.SecretPasswords.LmOwfPassword, &TempPrimaryCredential->LmOwfPassword, sizeof(LM_OWF_PASSWORD));
cachePasswords.SecretPasswords.LmPasswordPresent = TRUE;
}
else
{
cachePasswords.SecretPasswords.LmPasswordPresent = FALSE;
}
RtlZeroMemory(TempPrimaryCredential, PrimaryCredentialSize);
(*Lsa.FreeLsaHeap)(TempPrimaryCredential);
}
//
// Now we have the information from the cache, validate the
// user's password
//
if (!MsvpPasswordValidate(
NlpUasCompatibilityRequired,
LogonLevel,
(PVOID)LogonInformation,
&cachePasswords.SecretPasswords,
&LocalFlags,
&NlpUser->UserSessionKey,
(PLM_SESSION_KEY)
&NlpUser->ExpansionRoom[SAMINFO_LM_SESSION_KEY]
)) {
Status = STATUS_WRONG_PASSWORD;
goto Cleanup;
}
Status = STATUS_SUCCESS;
//
// successful logon from cache. Don't retry if a failure occurs below.
//
TryCacheFirst = FALSE;
//
// The cache always returns a NETLOGONV_VALIDATION_SAM_INFO2
// structure so set the LOGON_EXTRA_SIDS flag, whether or not
// there are extra sids. Also, if there was a package ID indicated
// put it in the PrimaryCredentials and remove it from the
// NlpUser structure so it doesn't confuse anyone else.
//
PrimaryCredentials->Flags &= ~PRIMARY_CRED_PACKAGE_MASK;
PrimaryCredentials->Flags |= NlpUser->UserFlags & PRIMARY_CRED_PACKAGE_MASK;
PrimaryCredentials->Flags |= PRIMARY_CRED_CACHED_LOGON;
NlpUser->UserFlags &= ~PRIMARY_CRED_PACKAGE_MASK;
NlpUser->UserFlags |= LOGON_CACHED_ACCOUNT | LOGON_EXTRA_SIDS | LocalFlags;
Flags |= LOGON_BY_CACHE;
//
// If the account is permanently dead on the domain controller,
// Flush this entry from the cache.
//
// Notice that STATUS_INVALID_LOGON_HOURS is not in the list below.
// This ensures a user will be able to remove his portable machine
// from the net and use it after hours.
//
// Notice the STATUS_WRONG_PASSWORD is not in the list below.
// We're as likely to flush the cache for typo'd passwords as anything
// else. What we'd really like to do is flush the cache if the
// password on the DC is different than the one in cache; but that's
// impossible to detect.
//
// ONLY DO THIS FOR INTERACTIVE LOGONS
// (not Service or Batch).
//
} else if ( ((LogonType == Interactive) || (LogonType == RemoteInteractive)) &&
(Status == STATUS_NO_SUCH_USER ||
Status == STATUS_INVALID_WORKSTATION ||
Status == STATUS_PASSWORD_EXPIRED ||
Status == STATUS_ACCOUNT_DISABLED ||
Status == STATUS_ACCOUNT_RESTRICTION || // Other Org check failed
Status == STATUS_AUTHENTICATION_FIREWALL_FAILED) ) { // Other Org check failed
//
// Delete the cache entry
NTSTATUS ntStatus;
ntStatus = NlpDeleteCacheEntry(
Status, // reason
Authoritative ? 1 : 0,
LogonType, // logon type
TRUE, // invalidated by NTLM
&LogonInteractive
);
if (!NT_SUCCESS(ntStatus))
{
SspPrint((SSP_CRITICAL, "LsaApLogonUser: NlpDeleteCacheEntry returns %x\n", ntStatus));
//
// netlogon returns non-authoritative no_such_user error and
// there is a matching MIT cache entry, try cached logon
//
if (fWaitForNetwork && (Status == STATUS_NO_SUCH_USER) && (!Authoritative)
&& (ntStatus == STATUS_NO_LOGON_SERVERS))
{
fWaitForNetwork = FALSE; // fake it
Status = STATUS_NO_LOGON_SERVERS;
goto RetryNonCached;
}
}
goto Cleanup;
} else {
goto Cleanup;
}
}
//
// if this is PersonalSKU, only allow DOMAIN_USER_RID_ADMIN to logon
// if doing safe-mode boot (NtLmGlobalSafeBoot == TRUE)
//
if ( NlpUser &&
NlpUser->UserId == DOMAIN_USER_RID_ADMIN &&
!NtLmGlobalSafeBoot &&
NtLmGlobalPersonalSKU &&
NlpSamDomainId &&
RtlEqualSid( NlpUser->LogonDomainId, NlpSamDomainId )
)
{
Status = STATUS_ACCOUNT_RESTRICTION;
SspPrint((SSP_CRITICAL,
"LsaApLogonUser: For Personal SKU Administrator cannot log on except during safe mode boot\n"));
goto Cleanup;
}
//
// For everything except network logons,
// save the credentials in the LSA,
// create active logon table entry,
// return the interactive profile buffer.
//
if ( LogonType == Interactive ||
LogonType == Service ||
LogonType == Batch ||
LogonType == NetworkCleartext ||
LogonType == RemoteInteractive
)
{
PUCHAR pWhere;
USHORT LogonCount;
ULONG UserSidSize;
UNICODE_STRING SamAccountName;
UNICODE_STRING NetbiosDomainName;
UNICODE_STRING DnsDomainName;
UNICODE_STRING Upn;
UNICODE_STRING LogonServer;
//
// Grab the various forms of the account name
//
NlpGetAccountNames( LogonInformation,
NlpUser,
&SamAccountName,
&NetbiosDomainName,
&DnsDomainName,
&Upn );
if (CredentialUserName.Length == 0)
{
CredentialUserToUse = &SamAccountName;
}
else
{
CredentialUserToUse = &CredentialUserName;
}
if (CredentialDomainName.Length == 0)
{
CredentialDomainToUse = &NetbiosDomainName;
}
else
{
CredentialDomainToUse = &CredentialDomainName;
}
//
// Build the NTLM primary credential using NetbiosDomainName\SamAccountName
//
//
#ifdef MAP_DOMAIN_NAMES_AT_LOGON
{
UNICODE_STRING MappedDomain;
RtlInitUnicodeString(
&MappedDomain,
NULL
);
Status = NlpMapLogonDomain(
&MappedDomain,
&NetbiosDomainName );
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
Status = NlpMakePrimaryCredential( &MappedDomain,
&SamAccountName,
&PrimaryCredentials->Password,
&Credential,
&CredentialSize );
if (MappedDomain.Buffer != NULL) {
NtLmFree(MappedDomain.Buffer);
}
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
}
#else
Status = NlpMakePrimaryCredential(&NetbiosDomainName,
&SamAccountName,
&PrimaryCredentials->Password,
&Credential,
&CredentialSize);
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
#endif
//
// Add additional names to the logon session name map. Ignore failure
// as that just means GetUserNameEx calls for these name formats later
// on will be satisfied by hitting the wire.
//
if (NlpUser->FullName.Length != 0)
{
I_LsaIAddNameToLogonSession(LogonId, NameDisplay, &NlpUser->FullName);
}
if (Upn.Length != 0)
{
I_LsaIAddNameToLogonSession(LogonId, NameUserPrincipal, &Upn);
}
if (DnsDomainName.Length != 0)
{
I_LsaIAddNameToLogonSession(LogonId, NameDnsDomain, &DnsDomainName);
}
//
// Fill the username and domain name into the primary credential
// that's passed to the other security packages.
//
// The names filled in are the effective names after authentication.
// For instance, it isn't the UPN passed to this function.
//
PrimaryCredentials->DownlevelName.Length = CredentialUserToUse->Length;
PrimaryCredentials->DownlevelName.MaximumLength = PrimaryCredentials->DownlevelName.Length; // + sizeof(WCHAR);
PrimaryCredentials->DownlevelName.Buffer = (*Lsa.AllocateLsaHeap)(PrimaryCredentials->DownlevelName.MaximumLength);
if (PrimaryCredentials->DownlevelName.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
PrimaryCredentials->DownlevelName.Buffer,
CredentialUserToUse->Buffer,
CredentialUserToUse->Length
);
PrimaryCredentials->DomainName.Length = CredentialDomainToUse->Length;
PrimaryCredentials->DomainName.MaximumLength = PrimaryCredentials->DomainName.Length; // + sizeof(WCHAR);
PrimaryCredentials->DomainName.Buffer = (*Lsa.AllocateLsaHeap)(PrimaryCredentials->DomainName.Length);
if (PrimaryCredentials->DomainName.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
PrimaryCredentials->DomainName.Buffer,
CredentialDomainToUse->Buffer,
CredentialDomainToUse->Length
);
RtlCopyMemory(&LogonServer, &NlpUser->LogonServer, sizeof(UNICODE_STRING));
if ( LogonServer.Length != 0 ) {
PrimaryCredentials->LogonServer.Length = PrimaryCredentials->LogonServer.MaximumLength =
LogonServer.Length;
PrimaryCredentials->LogonServer.Buffer = (*Lsa.AllocateLsaHeap)(LogonServer.Length);
if (PrimaryCredentials->LogonServer.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
PrimaryCredentials->LogonServer.Buffer,
LogonServer.Buffer,
LogonServer.Length
);
}
//
// Save the credential in the LSA.
//
Status = NlpAddPrimaryCredential( LogonId,
Credential,
CredentialSize );
if ( !NT_SUCCESS( Status ) ) {
SspPrint((SSP_CRITICAL, "LsaApLogonUser: error from AddCredential %lX\n",
Status));
goto Cleanup;
}
LogonCredentialAdded = TRUE;
//
// Build a Sid for this user.
//
if (!UserSid)
{
UserSid = NlpMakeDomainRelativeSid(NlpUser->LogonDomainId,
NlpUser->UserId);
if (UserSid == NULL)
{
Status = STATUS_NO_MEMORY;
SspPrint((SSP_CRITICAL, "LsaApLogonUser: No memory\n"));
goto Cleanup;
}
}
PrimaryCredentials->UserSid = UserSid;
UserSid = NULL;
UserSidSize = RtlLengthSid( PrimaryCredentials->UserSid );
//
// Allocate an entry for the active logon table.
//
ActiveLogonEntrySize = ROUND_UP_COUNT(sizeof(ACTIVE_LOGON), ALIGN_DWORD) +
ROUND_UP_COUNT(UserSidSize, sizeof(WCHAR)) +
SamAccountName.Length + sizeof(WCHAR) +
NetbiosDomainName.Length + sizeof(WCHAR) +
NlpUser->LogonServer.Length + sizeof(WCHAR);
pActiveLogonEntry = I_NtLmAllocate( ActiveLogonEntrySize );
if ( pActiveLogonEntry == NULL )
{
Status = STATUS_NO_MEMORY;
SspPrint((SSP_CRITICAL, "LsaApLogonUser: No memory %ld\n", ActiveLogonEntrySize));
goto Cleanup;
}
//
// Fill in the logon table entry.
//
pWhere = (PUCHAR)(pActiveLogonEntry + 1);
pActiveLogonEntry->Signature = NTLM_ACTIVE_LOGON_MAGIC_SIGNATURE;
OLD_TO_NEW_LARGE_INTEGER(
LogonInformation->LogonId,
pActiveLogonEntry->LogonId );
pActiveLogonEntry->Flags = Flags;
pActiveLogonEntry->LogonType = LogonType;
//
// Copy DWORD aligned fields first.
//
pWhere = ROUND_UP_POINTER( pWhere, ALIGN_DWORD );
Status = RtlCopySid(UserSidSize, (PSID)pWhere, PrimaryCredentials->UserSid);
if ( !NT_SUCCESS(Status) )
{
goto Cleanup;
}
pActiveLogonEntry->UserSid = (PSID) pWhere;
pWhere += UserSidSize;
//
// Copy WCHAR aligned fields
//
pWhere = ROUND_UP_POINTER( pWhere, ALIGN_WCHAR );
NlpPutString( &pActiveLogonEntry->UserName,
&SamAccountName,
&pWhere );
NlpPutString( &pActiveLogonEntry->LogonDomainName,
&NetbiosDomainName,
&pWhere );
NlpPutString( &pActiveLogonEntry->LogonServer,
&NlpUser->LogonServer,
&pWhere );
//
// Get the next enumeration handle for this session.
//
pActiveLogonEntry->EnumHandle = (ULONG)InterlockedIncrement((PLONG)&NlpEnumerationHandle);
NlpLockActiveLogonsRead();
//
// Insert this entry into the active logon table.
//
if (NlpFindActiveLogon( LogonId ))
{
//
// This Logon ID is already in use.
//
NlpUnlockActiveLogons();
Status = STATUS_LOGON_SESSION_COLLISION;
SspPrint((SSP_CRITICAL,
"LsaApLogonUserEx2: Collision from NlpFindActiveLogon for %#x:%#x\n",
LogonId->HighPart, LogonId->LowPart));
goto Cleanup;
}
//
// LogonId is unique, the same LogonId wouldn't be added twice
//
NlpLockActiveLogonsReadToWrite();
InsertTailList(&NlpActiveLogonListAnchor, &pActiveLogonEntry->ListEntry);
NlpUnlockActiveLogons();
SspPrint((SSP_LOGON_SESS, "LsaApLogonUserEx2 inserted %#x:%#x\n",
pActiveLogonEntry->LogonId.HighPart, pActiveLogonEntry->LogonId.LowPart));
bLogonEntryLinked = TRUE;
//
// Ensure the LogonCount is at least as big as it is for this
// machine.
//
LogonCount = (USHORT) NlpCountActiveLogon( &NetbiosDomainName,
&SamAccountName );
if ( NlpUser->LogonCount < LogonCount )
{
NlpUser->LogonCount = LogonCount;
}
//
// Alocate the profile buffer to return to the client
//
Status = NlpAllocateInteractiveProfile(
ClientRequest,
(PMSV1_0_INTERACTIVE_PROFILE *) ProfileBuffer,
ProfileBufferSize,
NlpUser );
if ( !NT_SUCCESS( Status ) )
{
SspPrint((SSP_CRITICAL,
"LsaApLogonUser: Allocate Profile Failed: %lx\n", Status));
goto Cleanup;
}
} else if ( LogonType == Network ) {
//
// if doing client challenge, and it's a vanilla NTLM response,
// and it's not a null session, compute unique per-session session keys
// N.B: not needed if it's NTLM++, not possible if LM
//
if ((NetworkAuthentication->ParameterControl & MSV1_0_USE_CLIENT_CHALLENGE) &&
(NetworkAuthentication->CaseSensitiveChallengeResponse.Length == NT_RESPONSE_LENGTH ) && // vanilla NTLM response
(NetworkAuthentication->CaseInsensitiveChallengeResponse.Length >= MSV1_0_CHALLENGE_LENGTH ) &&
(NlpUser != NULL)) // NULL session iff NlpUser == NULL
{
MsvpCalculateNtlm2SessionKeys(
&NlpUser->UserSessionKey,
NetworkAuthentication->ChallengeToClient,
(PUCHAR) NetworkAuthentication->CaseInsensitiveChallengeResponse.Buffer,
(PUSER_SESSION_KEY) &NlpUser->UserSessionKey,
(PLM_SESSION_KEY)&NlpUser->ExpansionRoom[SAMINFO_LM_SESSION_KEY]
);
}
//
// Alocate the profile buffer to return to the client
//
Status = NlpAllocateNetworkProfile(
ClientRequest,
(PMSV1_0_LM20_LOGON_PROFILE *) ProfileBuffer,
ProfileBufferSize,
NlpUser,
LogonNetwork.Identity.ParameterControl );
if ( !NT_SUCCESS( Status ) ) {
SspPrint((SSP_CRITICAL,
"LsaApLogonUser: Allocate Profile Failed: %lx. This could also be a status for a subauth logon.\n", Status));
goto Cleanup;
}
if ( NlpUser != NULL )
{
UNICODE_STRING SamAccountName;
UNICODE_STRING NetbiosDomainName;
UNICODE_STRING DnsDomainName;
UNICODE_STRING Upn;
UNICODE_STRING LogonServer;
//
// Grab the various forms of the account name
//
NlpGetAccountNames( LogonInformation,
NlpUser,
&SamAccountName,
&NetbiosDomainName,
&DnsDomainName,
&Upn );
//
// Add additional names to the logon session name map. Ignore failure
// as that just means GetUserNameEx calls for these name formats later
// on will be satisfied by hitting the wire.
//
if (NlpUser->FullName.Length != 0)
{
I_LsaIAddNameToLogonSession(LogonId, NameDisplay, &NlpUser->FullName);
}
if (Upn.Length != 0)
{
I_LsaIAddNameToLogonSession(LogonId, NameUserPrincipal, &Upn);
}
if (DnsDomainName.Length != 0)
{
I_LsaIAddNameToLogonSession(LogonId, NameDnsDomain, &DnsDomainName);
}
//
// Fill the username and domain name into the primary credential
// that's passed to the other security packages.
//
// The names filled in are the effective names after authentication.
// For instance, it isn't the UPN passed to this function.
//
if ( SamAccountName.Length == 0 )
{
SamAccountName = TmpName;
}
PrimaryCredentials->DownlevelName.Length = PrimaryCredentials->DownlevelName.MaximumLength =
SamAccountName.Length;
PrimaryCredentials->DownlevelName.Buffer = (*Lsa.AllocateLsaHeap)(SamAccountName.Length);
if (PrimaryCredentials->DownlevelName.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
PrimaryCredentials->DownlevelName.Buffer,
SamAccountName.Buffer,
SamAccountName.Length
);
PrimaryCredentials->DomainName.Length = PrimaryCredentials->DomainName.MaximumLength =
NetbiosDomainName.Length;
PrimaryCredentials->DomainName.Buffer = (*Lsa.AllocateLsaHeap)(NetbiosDomainName.Length);
if (PrimaryCredentials->DomainName.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
PrimaryCredentials->DomainName.Buffer,
NetbiosDomainName.Buffer,
NetbiosDomainName.Length
);
RtlCopyMemory(&LogonServer, &NlpUser->LogonServer, sizeof(UNICODE_STRING));
if ( LogonServer.Length != 0 ) {
PrimaryCredentials->LogonServer.Length = PrimaryCredentials->LogonServer.MaximumLength =
LogonServer.Length;
PrimaryCredentials->LogonServer.Buffer = (*Lsa.AllocateLsaHeap)(LogonServer.Length);
if (PrimaryCredentials->LogonServer.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
PrimaryCredentials->LogonServer.Buffer,
LogonServer.Buffer,
LogonServer.Length
);
}
//
// Build a Sid for this user.
//
UserSid = NlpMakeDomainRelativeSid( NlpUser->LogonDomainId,
NlpUser->UserId );
if ( UserSid == NULL ) {
Status = STATUS_NO_MEMORY;
SspPrint((SSP_CRITICAL, "LsaApLogonUser: No memory\n"));
goto Cleanup;
}
PrimaryCredentials->UserSid = UserSid;
UserSid = NULL;
}
}
//
// Build the token information to return to the LSA
//
switch (LsaTokenInformationType) {
case LsaTokenInformationV2:
Status = NlpMakeTokenInformationV2(
NlpUser,
(PLSA_TOKEN_INFORMATION_V2 *)TokenInformation );
if ( !NT_SUCCESS( Status ) ) {
SspPrint((SSP_CRITICAL,
"LsaApLogonUser: MakeTokenInformationV2 Failed: %lx\n", Status));
goto Cleanup;
}
break;
case LsaTokenInformationNull:
{
PLSA_TOKEN_INFORMATION_NULL VNull;
VNull = (*Lsa.AllocateLsaHeap)(sizeof(LSA_TOKEN_INFORMATION_NULL) );
if ( VNull == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
VNull->Groups = NULL;
VNull->ExpirationTime.HighPart = 0x7FFFFFFF;
VNull->ExpirationTime.LowPart = 0xFFFFFFFF;
*TokenInformation = VNull;
}
}
*TokenInformationType = LsaTokenInformationType;
Status = STATUS_SUCCESS;
Cleanup:
//
// if we tried to logon from cache, try again if it failed.
//
if ( fWaitForNetwork && TryCacheFirst && CacheTried && !NT_SUCCESS(Status) )
{
if (CredentialUserName.Buffer)
{
NtLmFreePrivateHeap(CredentialUserName.Buffer);
RtlZeroMemory(&CredentialUserName, sizeof(CredentialUserName));
}
if (CredentialDomainName.Buffer)
{
NtLmFreePrivateHeap(CredentialDomainName.Buffer);
RtlZeroMemory(&CredentialDomainName, sizeof(CredentialDomainName));
}
if ( NlpUser != NULL )
{
MIDL_user_free( NlpUser );
NlpUser = NULL;
}
TryCacheFirst = FALSE;
goto RetryNonCached;
}
NtLmFreePrivateHeap( CredmanUserName.Buffer );
NtLmFreePrivateHeap( CredmanDomainName.Buffer );
NtLmFreePrivateHeap( CredmanPassword.Buffer );
//
// Restore the saved password
//
if ( ServiceSecretLogon ) {
RtlCopyMemory( &Authentication->Password,
&SavedPassword,
sizeof( UNICODE_STRING ) );
//
// Free the secret value we read...
//
LsaIFree_LSAPR_CR_CIPHER_VALUE( SecretCurrent );
}
//
// If the logon wasn't successful,
// cleanup resources we would have returned to the caller.
//
if ( !NT_SUCCESS(Status) ) {
if ( LogonSessionCreated ) {
(VOID)(*Lsa.DeleteLogonSession)( LogonId );
}
if ( pActiveLogonEntry != NULL ) {
if ( bLogonEntryLinked ) {
LsaApLogonTerminated( LogonId );
} else {
if ( LogonCredentialAdded ) {
(VOID) NlpDeletePrimaryCredential(
LogonId );
}
I_NtLmFree( pActiveLogonEntry );
}
}
// Special case for MsV1_0SubAuthLogon (includes arap).
// (Don't free ProfileBuffer during error conditions which may not be fatal)
if (!fSubAuthEx)
{
if ( *ProfileBuffer != NULL ) {
if (ClientRequest != (PLSA_CLIENT_REQUEST) (-1))
(VOID)(*Lsa.FreeClientBuffer)( ClientRequest, *ProfileBuffer );
else
(VOID)(*Lsa.FreeLsaHeap)( *ProfileBuffer );
*ProfileBuffer = NULL;
}
}
if (PrimaryCredentials->DownlevelName.Buffer != NULL) {
(*Lsa.FreeLsaHeap)(PrimaryCredentials->DownlevelName.Buffer);
}
if (PrimaryCredentials->DomainName.Buffer != NULL) {
(*Lsa.FreeLsaHeap)(PrimaryCredentials->DomainName.Buffer);
}
if (PrimaryCredentials->Password.Buffer != NULL) {
RtlZeroMemory(
PrimaryCredentials->Password.Buffer,
PrimaryCredentials->Password.Length
);
(*Lsa.FreeLsaHeap)(PrimaryCredentials->Password.Buffer);
}
if (PrimaryCredentials->LogonServer.Buffer != NULL) {
(*Lsa.FreeLsaHeap)(PrimaryCredentials->LogonServer.Buffer);
}
RtlZeroMemory(
PrimaryCredentials,
sizeof(SECPKG_PRIMARY_CRED)
);
}
//
// Copy out Authenticating authority and user name.
//
if ( NT_SUCCESS(Status) && LsaTokenInformationType != LsaTokenInformationNull ) {
//
// Use the information from the NlpUser structure, since it gives
// us accurate information about what account we're logging on to,
// rather than who we were.
//
if ( LogonType != Network )
{
TmpName = NlpUser->EffectiveName;
}
else
{
//
// older servers may not return the effectivename for non-guest network logon.
//
if( NlpUser->EffectiveName.Length != 0 )
{
TmpName = NlpUser->EffectiveName;
}
}
TmpAuthority = NlpUser->LogonDomainName;
}
*AccountName = (*Lsa.AllocateLsaHeap)( sizeof( UNICODE_STRING ) );
if ( *AccountName != NULL ) {
(*AccountName)->Buffer = (*Lsa.AllocateLsaHeap)(TmpName.Length + sizeof( UNICODE_NULL) );
if ( (*AccountName)->Buffer != NULL ) {
(*AccountName)->MaximumLength = TmpName.Length + sizeof( UNICODE_NULL );
RtlCopyUnicodeString( *AccountName, &TmpName );
} else if (NT_SUCCESS(Status)) {
Status = STATUS_NO_MEMORY;
} else {
RtlInitUnicodeString( *AccountName, NULL );
}
} else if (NT_SUCCESS(Status)) {
Status = STATUS_NO_MEMORY;
}
*AuthenticatingAuthority = (*Lsa.AllocateLsaHeap)( sizeof( UNICODE_STRING ) );
if ( *AuthenticatingAuthority != NULL ) {
(*AuthenticatingAuthority)->Buffer = (*Lsa.AllocateLsaHeap)( TmpAuthority.Length + sizeof( UNICODE_NULL ) );
if ( (*AuthenticatingAuthority)->Buffer != NULL ) {
(*AuthenticatingAuthority)->MaximumLength = (USHORT)(TmpAuthority.Length + sizeof( UNICODE_NULL ));
RtlCopyUnicodeString( *AuthenticatingAuthority, &TmpAuthority );
} else if (NT_SUCCESS(Status)) {
Status = STATUS_NO_MEMORY;
} else {
RtlInitUnicodeString( *AuthenticatingAuthority, NULL );
}
} else if (NT_SUCCESS(Status)) {
Status = STATUS_NO_MEMORY;
}
*MachineName = NULL;
if (WorkStationName != NULL) {
*MachineName = (*Lsa.AllocateLsaHeap)( sizeof( UNICODE_STRING ) );
if ( *MachineName != NULL ) {
(*MachineName)->Buffer = (*Lsa.AllocateLsaHeap)( WorkStationName->Length + sizeof( UNICODE_NULL ) );
if ( (*MachineName)->Buffer != NULL ) {
(*MachineName)->MaximumLength = (USHORT)(WorkStationName->Length + sizeof( UNICODE_NULL ));
RtlCopyUnicodeString( *MachineName, WorkStationName );
} else if (NT_SUCCESS(Status)) {
Status = STATUS_NO_MEMORY;
} else {
RtlInitUnicodeString( *MachineName, NULL );
}
} else if (NT_SUCCESS(Status)) {
Status = STATUS_NO_MEMORY;
}
}
//
// Map status codes to prevent specific information from being
// released about this user.
//
switch (Status) {
case STATUS_WRONG_PASSWORD:
case STATUS_NO_SUCH_USER:
case STATUS_DOMAIN_TRUST_INCONSISTENT:
//
// sleep 3 seconds to "discourage" dictionary attacks.
// Don't worry about interactive logon dictionary attacks.
// They will be slow anyway.
//
// per bug 171041, SField, RichardW, CliffV all decided this
// delay has almost zero value for Win2000. Offline attacks at
// sniffed wire traffic are more efficient and viable. Further,
// opimizations in logon code path make failed interactive logons
// very fast.
//
// if (LogonType != Interactive) {
// Sleep( 3000 );
// }
//
// This is for auditing. Make sure to clear it out before
// passing it out of LSA to the caller.
//
*SubStatus = Status;
Status = STATUS_LOGON_FAILURE;
break;
case STATUS_INVALID_LOGON_HOURS:
case STATUS_INVALID_WORKSTATION:
case STATUS_PASSWORD_EXPIRED:
case STATUS_ACCOUNT_DISABLED:
*SubStatus = Status;
Status = STATUS_ACCOUNT_RESTRICTION;
break;
//
// This means that the Other Organization check failed.
// It would probably be better to set the substatus
// to a more descriptive error but that could potentially
// create compatibility problems.
//
case STATUS_ACCOUNT_RESTRICTION:
*SubStatus = STATUS_ACCOUNT_RESTRICTION;
break;
default:
break;
}
//
// Cleanup locally used resources
//
if ( Credential != NULL ) {
RtlZeroMemory(Credential, CredentialSize);
(*Lsa.FreeLsaHeap)( Credential );
}
if ( NlpUser != NULL ) {
MIDL_user_free( NlpUser );
}
if ( UserSid != NULL ) {
(*Lsa.FreeLsaHeap)( UserSid );
}
//
// Cleanup short was added to avoid returning from the middle of the function.
//
CleanupShort:
//
// End tracing a logon user
//
if (NtlmGlobalEventTraceFlag) {
UNICODE_STRING strTempDomain = {0};
//
// Trace header goo
//
SET_TRACE_HEADER(TraceInfo,
NtlmLogonGuid,
EVENT_TRACE_TYPE_END,
WNODE_FLAG_TRACED_GUID|WNODE_FLAG_USE_MOF_PTR,
6);
SET_TRACE_DATA(TraceInfo,
TRACE_LOGON_STATUS,
Status);
SET_TRACE_DATA(TraceInfo,
TRACE_LOGON_TYPE,
LogonType);
SET_TRACE_USTRING(TraceInfo,
TRACE_LOGON_USERNAME,
(**AccountName));
if (AuthenticatingAuthority)
strTempDomain = **AuthenticatingAuthority;
SET_TRACE_USTRING(TraceInfo,
TRACE_LOGON_DOMAINNAME,
strTempDomain);
TraceEvent(NtlmGlobalTraceLoggerHandle,
(PEVENT_TRACE_HEADER)&TraceInfo);
}
#if _WIN64
//
// Do this last since some of the cleanup code above may refer to addresses
// inside the pTempSubmitBuffer/ProtocolSubmitBuffer (e.g., copying out the
// Workstation name, etc).
//
if (fAllocatedSubmitBuffer)
{
NtLmFreePrivateHeap( pTempSubmitBuffer );
}
#endif // _WIN64
if (CredentialUserName.Buffer)
{
NtLmFreePrivateHeap(CredentialUserName.Buffer);
}
if (CredentialDomainName.Buffer)
{
NtLmFreePrivateHeap(CredentialDomainName.Buffer);
}
//
// Return status to the caller
//
return Status;
}
VOID
LsaApLogonTerminated (
IN PLUID pLogonId
)
/*++
Routine Description:
This routine is used to notify each authentication package when a logon
session terminates. A logon session terminates when the last token
referencing the logon session is deleted.
Arguments:
pLogonId - Is the logon ID that just logged off.
Return Status:
None.
--*/
{
PACTIVE_LOGON pActiveLogon = NULL;
//
// Find the entry and de-link it from the active logon table.
//
// this scheme assumes we won't be called concurrently, multiple times,
// for the same LogonId. (would need to take write lock up front to support that).
// (note that it's quite possible to frequently attempt deleting logon sessions
// associated with other packages, which would not exist in the NTLM universe).
//
NlpLockActiveLogonsRead();
if ( NULL == (pActiveLogon = NlpFindActiveLogon( pLogonId )) )
{
NlpUnlockActiveLogons();
return;
}
NlpLockActiveLogonsReadToWrite();
//
// comment out the following assuming the same logon session can not be
// deleted twice
//
#if 0
if ( NULL == (pActiveLogon = NlpFindActiveLogon( pLogonId )) )
{
NlpUnlockActiveLogons();
return;
}
#endif
RemoveEntryList(&pActiveLogon->ListEntry);
NlpUnlockActiveLogons();
//
// Delete the credential.
//
// (Currently the LSA deletes all of the credentials before calling
// the authentication package. This line is added to be compatible
// with a more reasonable LSA.)
//
(VOID) NlpDeletePrimaryCredential( &pActiveLogon->LogonId );
//
// Deallocate the now orphaned entry.
//
I_NtLmFree( pActiveLogon );
//
// NB: We don't delete the logon session or credentials.
// That will be done by the LSA itself after we return.
//
return;
}
//+-------------------------------------------------------------------------
//
// Function: SspAcceptCredentials
//
// Synopsis: This routine is called after another package has logged
// a user on. The other package provides a user name and
// password and the Kerberos package will create a logon
// session for this user.
//
// Effects: Creates a logon session
//
// Arguments: LogonType - Type of logon, such as network or interactive
// PrimaryCredentials - Primary credentials for the account,
// containing a domain name, password, SID, etc.
// SupplementalCredentials - If present, contains credentials
// from the account itself.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
SspAcceptCredentials(
IN SECURITY_LOGON_TYPE LogonType,
IN PSECPKG_PRIMARY_CRED pPrimaryCredentials,
IN PSECPKG_SUPPLEMENTAL_CRED pSupplementalCredentials
)
{
NTSTATUS Status = STATUS_SUCCESS;
PMSV1_0_PRIMARY_CREDENTIAL pCredential = NULL;
ULONG CredentialSize = 0;
LUID SystemLuid = SYSTEM_LUID;
LUID NetworkServiceLuid = NETWORKSERVICE_LUID;
UNICODE_STRING DomainNameToUse;
UNICODE_STRING RealDomainName = {0};
PACTIVE_LOGON pActiveLogon = NULL;
PACTIVE_LOGON pActiveLogonEntry = NULL;
ULONG ActiveLogonEntrySize;
ULONG UserSidSize;
PUCHAR pWhere = NULL;
BOOLEAN bLogonEntryLinked = FALSE;
PMSV1_0_SUPPLEMENTAL_CREDENTIAL pMsvCredentials = NULL;
LUID CredentialLuid;
CredentialLuid = pPrimaryCredentials->LogonId;
//
// If there is no cleartext password, bail out here because we
// can't build a real credential.
//
if ((pPrimaryCredentials->Flags & PRIMARY_CRED_CLEAR_PASSWORD) == 0)
{
ASSERT((!(pPrimaryCredentials->Flags & PRIMARY_CRED_OWF_PASSWORD)) && "OWF password is not supported yet");
if (!ARGUMENT_PRESENT(pSupplementalCredentials))
{
Status = STATUS_SUCCESS;
goto Cleanup;
}
else
{
//
// Validate the MSV credentials
//
pMsvCredentials = (PMSV1_0_SUPPLEMENTAL_CREDENTIAL) pSupplementalCredentials->Credentials;
if (pSupplementalCredentials->CredentialSize < sizeof(MSV1_0_SUPPLEMENTAL_CREDENTIAL))
{
//
// LOGLOG: bad credentials - ignore them
//
Status = STATUS_SUCCESS;
goto Cleanup;
}
if (pMsvCredentials->Version != MSV1_0_CRED_VERSION)
{
Status = STATUS_SUCCESS;
goto Cleanup;
}
}
}
//
// stash the credential associated with SYSTEM under another logonID
// this is done so we can utilize that credential at a later time if
// requested by the caller.
//
if (RtlEqualLuid(
&CredentialLuid,
&SystemLuid
))
{
CredentialLuid = NtLmGlobalLuidMachineLogon;
}
if ( NtLmLocklessGlobalPreferredDomainString.Buffer != NULL )
{
DomainNameToUse = NtLmLocklessGlobalPreferredDomainString;
}
else
{
//
// pick the correct names that are updated by policy callback
//
if (RtlEqualLuid(&pPrimaryCredentials->LogonId, &SystemLuid)
|| RtlEqualLuid(&pPrimaryCredentials->LogonId, &NetworkServiceLuid))
{
RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
Status = NtLmDuplicateUnicodeString(
&RealDomainName,
&NtLmGlobalUnicodePrimaryDomainNameString
);
RtlReleaseResource(&NtLmGlobalCritSect);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
DomainNameToUse = RealDomainName;
}
else
{
DomainNameToUse = pPrimaryCredentials->DomainName;
}
}
//
// Build the primary credential
//
if ((pPrimaryCredentials->Flags & PRIMARY_CRED_CLEAR_PASSWORD) != 0)
{
Status = NlpMakePrimaryCredential( &DomainNameToUse,
&pPrimaryCredentials->DownlevelName,
&pPrimaryCredentials->Password,
&pCredential,
&CredentialSize );
}
else
{
ASSERT(pMsvCredentials && "SspAcceptCredentials must have supplemental credentials");
Status = NlpMakePrimaryCredentialFromMsvCredential(
&DomainNameToUse,
&pPrimaryCredentials->DownlevelName,
pMsvCredentials,
&pCredential,
&CredentialSize );
}
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// If this is an update, just change the password
//
if ((pPrimaryCredentials->Flags & PRIMARY_CRED_UPDATE) != 0)
{
Status = NlpChangePwdCredByLogonId(
&CredentialLuid,
pCredential,
0 == (pPrimaryCredentials->Flags & PRIMARY_CRED_CLEAR_PASSWORD) // need notification only when there is no cleartext password
);
goto Cleanup;
}
//
// Now create an entry in the active logon list
//
UserSidSize = RtlLengthSid( pPrimaryCredentials->UserSid );
//
// Allocate an entry for the active logon table.
//
ActiveLogonEntrySize = ROUND_UP_COUNT(sizeof(ACTIVE_LOGON), ALIGN_DWORD) +
ROUND_UP_COUNT(UserSidSize, sizeof(WCHAR)) +
pPrimaryCredentials->DownlevelName.Length + sizeof(WCHAR) +
DomainNameToUse.Length + sizeof(WCHAR) +
pPrimaryCredentials->LogonServer.Length + sizeof(WCHAR);
pActiveLogonEntry = I_NtLmAllocate( ActiveLogonEntrySize );
if ( pActiveLogonEntry == NULL )
{
Status = STATUS_NO_MEMORY;
SspPrint((SSP_CRITICAL, "SpAcceptCredentials: no memory %ld\n", ActiveLogonEntrySize));
goto Cleanup;
}
pActiveLogonEntry->Signature = NTLM_ACTIVE_LOGON_MAGIC_SIGNATURE;
//
// Fill in the logon table entry.
//
pWhere = (PUCHAR) (pActiveLogonEntry + 1);
OLD_TO_NEW_LARGE_INTEGER(
CredentialLuid,
pActiveLogonEntry->LogonId
);
//
// Indicate that this was a logon by another package because we don't want to
// notify Netlogon of the logoff.
//
pActiveLogonEntry->Flags = LOGON_BY_OTHER_PACKAGE;
pActiveLogonEntry->LogonType = LogonType;
//
// Copy DWORD aligned fields first.
//
pWhere = ROUND_UP_POINTER( pWhere, ALIGN_DWORD );
Status = RtlCopySid(UserSidSize, (PSID)pWhere, pPrimaryCredentials->UserSid);
if ( !NT_SUCCESS(Status) )
{
goto Cleanup;
}
pActiveLogonEntry->UserSid = (PSID) pWhere;
pWhere += UserSidSize;
//
// Copy WCHAR aligned fields
//
pWhere = ROUND_UP_POINTER( pWhere, ALIGN_WCHAR );
NlpPutString( &pActiveLogonEntry->UserName,
&pPrimaryCredentials->DownlevelName,
&pWhere );
NlpPutString( &pActiveLogonEntry->LogonDomainName,
&DomainNameToUse,
&pWhere );
NlpPutString( &pActiveLogonEntry->LogonServer,
&pPrimaryCredentials->LogonServer,
&pWhere );
//
// Insert this entry into the active logon table.
//
// (sfield) LONGHORN: determine if/why there would ever be a collision.
// LSA should enforce no collision is possible via locally unique id..
//
NlpLockActiveLogonsRead();
pActiveLogon = NlpFindActiveLogon( &CredentialLuid );
if (pActiveLogon)
{
//
// This Logon ID is already in use.
//
//
// Check to see if this was someone we logged on
//
if ((pActiveLogon->Flags & (LOGON_BY_CACHE | LOGON_BY_NETLOGON | LOGON_BY_LOCAL)) != 0)
{
//
// Unlock early since we hold a write lock
//
NlpUnlockActiveLogons();
//
// We did the logon, so don't bother to add it again.
//
Status = STATUS_SUCCESS;
}
else
{
//
// Unlock early since we hold a write lock
//
NlpUnlockActiveLogons();
Status = STATUS_LOGON_SESSION_COLLISION;
SspPrint((SSP_CRITICAL,
"SpAcceptCredentials: Collision from NlpFindActiveLogon for %#x:%#x\n",
pActiveLogonEntry->LogonId.HighPart, pActiveLogonEntry->LogonId.LowPart));
}
goto Cleanup;
}
pActiveLogonEntry->EnumHandle = (ULONG)InterlockedIncrement( (PLONG)&NlpEnumerationHandle );
NlpLockActiveLogonsReadToWrite();
//
// if we worry about two SspAcceptCredentials accepting the same LogonId
// at the same time, first it is extremely unlikely, second, it is benign
//
// if (!NlpFindActiveLogon( &CredentialLuid )) // must sure this LogonId is not in the list
// {
// InsertTailList(&NlpActiveLogonListAnchor, &pActiveLogonEntry->ListEntry);
// }
//
InsertTailList(&NlpActiveLogonListAnchor, &pActiveLogonEntry->ListEntry);
NlpUnlockActiveLogons();
SspPrint((SSP_LOGON_SESS, "SpAcceptCredentials inserted %#x:%#x\n",
pActiveLogonEntry->LogonId.HighPart, pActiveLogonEntry->LogonId.LowPart));
bLogonEntryLinked = TRUE;
//
// Save the credential in the LSA.
//
Status = NlpAddPrimaryCredential(
&CredentialLuid,
pCredential,
CredentialSize
);
if ( !NT_SUCCESS( Status ) )
{
SspPrint((SSP_CRITICAL, "SpAcceptCredentials: error from AddCredential %lX\n",
Status));
goto Cleanup;
}
pActiveLogonEntry = NULL;
Cleanup:
if (pActiveLogonEntry)
{
if (bLogonEntryLinked)
{
LsaApLogonTerminated( &CredentialLuid );
}
else
{
I_NtLmFree( pActiveLogonEntry );
}
}
if ( pCredential )
{
RtlZeroMemory(pCredential, CredentialSize);
LsaFunctions->FreeLsaHeap( pCredential );
}
if (RealDomainName.Buffer)
{
NtLmFreePrivateHeap(RealDomainName.Buffer);
}
return (Status);
}
//+-------------------------------------------------------------------------
//
// Function: NlpMapLogonDomain
//
// Synopsis: This routine is called while MSV1_0 package is logging
// a user on. The logon domain name is mapped to another
// domain to be stored in the credential.
//
// Effects: Allocates output string
//
// Arguments: MappedDomain - Receives mapped domain name
// LogonDomain - Domain to which user is logging on
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
NlpMapLogonDomain(
OUT PUNICODE_STRING MappedDomain,
IN PUNICODE_STRING LogonDomain
)
{
NTSTATUS Status = STATUS_SUCCESS;
if ( (NtLmLocklessGlobalMappedDomainString.Buffer == NULL) ||
!RtlEqualDomainName( LogonDomain, &NtLmLocklessGlobalMappedDomainString )
)
{
Status = NtLmDuplicateUnicodeString(
MappedDomain,
LogonDomain
);
goto Cleanup;
}
if ( NtLmLocklessGlobalPreferredDomainString.Buffer == NULL )
{
Status = NtLmDuplicateUnicodeString(
MappedDomain,
LogonDomain
);
} else {
Status = NtLmDuplicateUnicodeString(
MappedDomain,
&NtLmLocklessGlobalPreferredDomainString
);
}
Cleanup:
return(Status);
}
// calculate NTLM2 challenge from client and server challenges
VOID
MsvpCalculateNtlm2Challenge (
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
IN UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH],
OUT UCHAR Challenge[MSV1_0_CHALLENGE_LENGTH]
)
{
MD5_CTX Md5Context;
SspPrint((SSP_NTLM_V2, "MsvpCalculateNtlm2Challenge mixing ChallengeFromClient and ChallengeToClient\n"));
MD5Init(
&Md5Context
);
MD5Update(
&Md5Context,
ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
MD5Update(
&Md5Context,
ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
MD5Final(
&Md5Context
);
ASSERT(MD5DIGESTLEN >= MSV1_0_CHALLENGE_LENGTH);
RtlCopyMemory(
Challenge,
Md5Context.digest,
MSV1_0_CHALLENGE_LENGTH
);
}
// calculate NTLM2 session keys from User session key given
// to us by the system with the user's account
VOID
MsvpCalculateNtlm2SessionKeys (
IN PUSER_SESSION_KEY NtUserSessionKey,
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
IN UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH],
OUT PUSER_SESSION_KEY LocalUserSessionKey,
OUT PLM_SESSION_KEY LocalLmSessionKey
)
{
// SESSKEY = HMAC(NtUserSessionKey, (ChallengeToClient, ChallengeFromClient))
// Lm session key is first 8 bytes of session key
HMACMD5_CTX HMACMD5Context;
HMACMD5Init(
&HMACMD5Context,
(PUCHAR)NtUserSessionKey,
sizeof(*NtUserSessionKey)
);
HMACMD5Update(
&HMACMD5Context,
ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Final(
&HMACMD5Context,
(PUCHAR)LocalUserSessionKey
);
RtlCopyMemory(
LocalLmSessionKey,
LocalUserSessionKey,
sizeof(*LocalLmSessionKey)
);
}
// calculate NTLM3 OWF from credentials
VOID
MsvpCalculateNtlm3Owf (
IN PNT_OWF_PASSWORD pNtOwfPassword,
IN PUNICODE_STRING pUserName,
IN PUNICODE_STRING pLogonDomainName,
OUT UCHAR Ntlm3Owf[MSV1_0_NTLM3_OWF_LENGTH]
)
{
HMACMD5_CTX HMACMD5Context;
WCHAR UCUserName[UNLEN+1];
UNICODE_STRING UCUserNameString;
UCUserNameString.Length = 0;
UCUserNameString.MaximumLength = UNLEN;
UCUserNameString.Buffer = UCUserName;
RtlUpcaseUnicodeString(
&UCUserNameString,
pUserName,
FALSE
);
// Calculate NTLM3 OWF -- HMAC(MD4(P), (UserName, LogonDomainName))
HMACMD5Init(
&HMACMD5Context,
(PUCHAR)pNtOwfPassword,
sizeof(*pNtOwfPassword)
);
HMACMD5Update(
&HMACMD5Context,
(PUCHAR)UCUserNameString.Buffer,
pUserName->Length
);
HMACMD5Update(
&HMACMD5Context,
(PUCHAR)pLogonDomainName->Buffer,
pLogonDomainName->Length
);
HMACMD5Final(
&HMACMD5Context,
Ntlm3Owf
);
}
// calculate LM3 response from credentials
VOID
MsvpLm3Response (
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 UCHAR Response[MSV1_0_NTLM3_RESPONSE_LENGTH],
OUT PUSER_SESSION_KEY UserSessionKey,
OUT PLM_SESSION_KEY LmSessionKey
)
{
HMACMD5_CTX HMACMD5Context;
UCHAR Ntlm3Owf[MSV1_0_NTLM3_OWF_LENGTH];
SspPrint((SSP_NTLM_V2, "MsvpLm3Response: %wZ\\%wZ\n", pLogonDomainName, pUserName));
// get NTLM3 OWF
MsvpCalculateNtlm3Owf (
pNtOwfPassword,
pUserName,
pLogonDomainName,
Ntlm3Owf
);
// Calculate NTLM3 Response
// HMAC(Ntlm3Owf, (NS, V, HV, T, NC, S))
HMACMD5Init(
&HMACMD5Context,
Ntlm3Owf,
MSV1_0_NTLM3_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
(PUCHAR)pLm3Response->ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
ASSERT(MD5DIGESTLEN == MSV1_0_NTLM3_RESPONSE_LENGTH);
HMACMD5Final(
&HMACMD5Context,
Response
);
if( (UserSessionKey != NULL) && (LmSessionKey != NULL) )
{
// now compute the session keys
// HMAC(Kr, R)
HMACMD5Init(
&HMACMD5Context,
Ntlm3Owf,
MSV1_0_NTLM3_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
Response,
MSV1_0_NTLM3_RESPONSE_LENGTH
);
ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);
HMACMD5Final(
&HMACMD5Context,
(PUCHAR)UserSessionKey
);
ASSERT(MSV1_0_LANMAN_SESSION_KEY_LENGTH <= MSV1_0_USER_SESSION_KEY_LENGTH);
RtlCopyMemory(
LmSessionKey,
UserSessionKey,
MSV1_0_LANMAN_SESSION_KEY_LENGTH);
}
return;
}
VOID
MsvpNtlm3Response (
IN PNT_OWF_PASSWORD pNtOwfPassword,
IN PUNICODE_STRING pUserName,
IN PUNICODE_STRING pLogonDomainName,
IN ULONG ServerNameLength,
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
IN PMSV1_0_NTLM3_RESPONSE pNtlm3Response,
OUT UCHAR Response[MSV1_0_NTLM3_RESPONSE_LENGTH],
OUT PUSER_SESSION_KEY UserSessionKey,
OUT PLM_SESSION_KEY LmSessionKey
)
{
HMACMD5_CTX HMACMD5Context;
UCHAR Ntlm3Owf[MSV1_0_NTLM3_OWF_LENGTH];
SspPrint((SSP_NTLM_V2, "MsvpNtlm3Response: %wZ\\%wZ\n", pLogonDomainName, pUserName));
// get NTLM3 OWF
MsvpCalculateNtlm3Owf (
pNtOwfPassword,
pUserName,
pLogonDomainName,
Ntlm3Owf
);
// Calculate NTLM3 Response
// HMAC(Ntlm3Owf, (NS, V, HV, T, NC, S))
HMACMD5Init(
&HMACMD5Context,
Ntlm3Owf,
MSV1_0_NTLM3_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
ChallengeToClient,
MSV1_0_CHALLENGE_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
&pNtlm3Response->RespType,
(MSV1_0_NTLM3_INPUT_LENGTH + ServerNameLength)
);
ASSERT(MD5DIGESTLEN == MSV1_0_NTLM3_RESPONSE_LENGTH);
HMACMD5Final(
&HMACMD5Context,
Response
);
// now compute the session keys
// HMAC(Kr, R)
HMACMD5Init(
&HMACMD5Context,
Ntlm3Owf,
MSV1_0_NTLM3_OWF_LENGTH
);
HMACMD5Update(
&HMACMD5Context,
Response,
MSV1_0_NTLM3_RESPONSE_LENGTH
);
ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);
HMACMD5Final(
&HMACMD5Context,
(PUCHAR)UserSessionKey
);
ASSERT(MSV1_0_LANMAN_SESSION_KEY_LENGTH <= MSV1_0_USER_SESSION_KEY_LENGTH);
RtlCopyMemory(
LmSessionKey,
UserSessionKey,
MSV1_0_LANMAN_SESSION_KEY_LENGTH);
return;
}
NTSTATUS
MsvpLm20GetNtlm3ChallengeResponse (
IN PNT_OWF_PASSWORD pNtOwfPassword,
IN PUNICODE_STRING pUserName,
IN PUNICODE_STRING pLogonDomainName,
IN PUNICODE_STRING pServerName,
IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH],
OUT PMSV1_0_NTLM3_RESPONSE pNtlm3Response,
OUT PMSV1_0_LM3_RESPONSE pLm3Response,
OUT PUSER_SESSION_KEY UserSessionKey,
OUT PLM_SESSION_KEY LmSessionKey
)
/*++
Routine Description:
This routine calculates the NT and LM response for the NTLM3
authentication protocol
It generates the time stamp, version numbers, and
client challenge, and the NTLM3 and LM3 responses.
--*/
{
NTSTATUS Status;
// fill in version numbers, timestamp, and client's challenge
pNtlm3Response->RespType = 1;
pNtlm3Response->HiRespType = 1;
pNtlm3Response->Flags = 0;
pNtlm3Response->MsgWord = 0;
Status = NtQuerySystemTime ( (PLARGE_INTEGER)&pNtlm3Response->TimeStamp );
if (NT_SUCCESS(Status)) {
Status = SspGenerateRandomBits(
pNtlm3Response->ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
}
if (!NT_SUCCESS(Status)) {
return Status;
}
#ifdef USE_CONSTANT_CHALLENGE
pNtlm3Response->TimeStamp = 0;
RtlZeroMemory(
pNtlm3Response->ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
#endif
RtlCopyMemory(
pNtlm3Response->Buffer,
pServerName->Buffer,
pServerName->Length
);
// Calculate NTLM3 response, filling in response field
MsvpNtlm3Response (
pNtOwfPassword,
pUserName,
pLogonDomainName,
pServerName->Length,
ChallengeToClient,
pNtlm3Response,
pNtlm3Response->Response,
UserSessionKey,
LmSessionKey
);
// Use same challenge to compute the LM3 response
RtlCopyMemory(
pLm3Response->ChallengeFromClient,
pNtlm3Response->ChallengeFromClient,
MSV1_0_CHALLENGE_LENGTH
);
// Calculate LM3 response
MsvpLm3Response (
pNtOwfPassword,
pUserName,
pLogonDomainName,
ChallengeToClient,
pLm3Response,
pLm3Response->Response,
NULL,
NULL
);
return STATUS_SUCCESS;
}
// MsvAvInit -- function to initialize AV pair list
PMSV1_0_AV_PAIR
MsvpAvlInit(
IN void * pAvList
)
{
PMSV1_0_AV_PAIR pAvPair;
pAvPair = (PMSV1_0_AV_PAIR)pAvList;
pAvPair->AvId = MsvAvEOL;
pAvPair->AvLen = 0;
return pAvPair;
}
// MsvpAvGet -- function to find a particular AV pair by ID
PMSV1_0_AV_PAIR
MsvpAvlGet(
IN PMSV1_0_AV_PAIR pAvList, // first pair of AV pair list
IN MSV1_0_AVID AvId, // AV pair to find
IN LONG cAvList // size of AV list
)
{
MSV1_0_AV_PAIR AvPair = {0};
MSV1_0_AV_PAIR* pAvPair = NULL;
if (cAvList < sizeof(AvPair)) {
return NULL;
}
pAvPair = pAvList;
RtlCopyMemory(
&AvPair,
pAvPair,
sizeof(AvPair)
);
while (1) {
if ( (cAvList <= 0) || (((ULONG) cAvList < AvPair.AvLen + sizeof(MSV1_0_AV_PAIR))) ) {
return NULL;
}
if (AvPair.AvId == AvId)
return pAvPair;
if (AvPair.AvId == MsvAvEOL)
return NULL;
cAvList -= (AvPair.AvLen + sizeof(MSV1_0_AV_PAIR));
if (cAvList <= 0)
return NULL;
pAvPair = (PMSV1_0_AV_PAIR) ((PUCHAR) pAvPair + AvPair.AvLen + sizeof(MSV1_0_AV_PAIR));
RtlCopyMemory(
&AvPair,
pAvPair,
sizeof(AvPair)
);
}
}
// MsvpAvlLen -- function to find length of a AV list
ULONG
MsvpAvlLen(
IN PMSV1_0_AV_PAIR pAvList, // first pair of AV pair list
IN LONG cAvList // max size of AV list
)
{
PMSV1_0_AV_PAIR pCurPair;
// find the EOL
pCurPair = MsvpAvlGet(pAvList, MsvAvEOL, cAvList);
if( pCurPair == NULL )
return 0;
// compute length (not forgetting the EOL pair)
return (ULONG)(((PUCHAR)pCurPair - (PUCHAR)pAvList) + sizeof(MSV1_0_AV_PAIR));
}
// MsvpAvlAdd -- function to add an AV pair to a list
// assumes buffer is long enough!
// returns NULL on failure.
PMSV1_0_AV_PAIR
MsvpAvlAdd(
IN PMSV1_0_AV_PAIR pAvList, // first pair of AV pair list
IN MSV1_0_AVID AvId, // AV pair to add
IN PUNICODE_STRING pString, // value of pair
IN LONG cAvList // max size of AV list
)
{
PMSV1_0_AV_PAIR pCurPair;
// find the EOL
pCurPair = MsvpAvlGet(pAvList, MsvAvEOL, cAvList);
if( pCurPair == NULL )
return NULL;
//
// append the new AvPair (assume the buffer is long enough!)
//
pCurPair->AvId = (USHORT)AvId;
pCurPair->AvLen = (USHORT)pString->Length;
memcpy(pCurPair+1, pString->Buffer, pCurPair->AvLen);
// top it off with a new EOL
pCurPair = (PMSV1_0_AV_PAIR)((PUCHAR)pCurPair + sizeof(MSV1_0_AV_PAIR) + pCurPair->AvLen);
pCurPair->AvId = MsvAvEOL;
pCurPair->AvLen = 0;
return pCurPair;
}
// MsvpAvlSize -- fucntion to calculate length needed for an AV list
ULONG
MsvpAvlSize(
IN ULONG iPairs, // number of AV pairs response will include
IN ULONG iPairsLen // total size of values for the pairs
)
{
return (
iPairs * sizeof(MSV1_0_AV_PAIR) + // space for the pairs' headers
iPairsLen + // space for pairs' values
sizeof(MSV1_0_AV_PAIR) // space for the EOL
);
}
NTSTATUS
MsvpAvlToString(
IN PUNICODE_STRING AvlString,
IN MSV1_0_AVID AvId,
IN OUT LPWSTR *szAvlString
)
{
PMSV1_0_AV_PAIR pAV;
MSV1_0_AV_PAIR AV = {0};
*szAvlString = NULL;
if ( AvlString->Buffer == NULL || AvlString->Length == 0 )
{
return STATUS_SUCCESS;
}
pAV = MsvpAvlGet(
(PMSV1_0_AV_PAIR)AvlString->Buffer,
AvId,
AvlString->Length
);
if ( pAV != NULL )
{
LPWSTR szResult;
RtlCopyMemory(&AV, pAV, sizeof(AV));
szResult = NtLmAllocate( AV.AvLen + sizeof(WCHAR) );
if ( szResult == NULL )
{
SspPrint(( SSP_CRITICAL, "MsvpAvlToString: Error allocating memory\n"));
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory( szResult, ( pAV + 1 ), AV.AvLen );
szResult[ AV.AvLen /sizeof(WCHAR) ] = L'\0';
*szAvlString = szResult;
}
return STATUS_SUCCESS;
}
NTSTATUS
MsvpAvlToFlag(
IN PUNICODE_STRING AvlString,
IN MSV1_0_AVID AvId,
IN OUT ULONG *ulAvlFlag
)
{
PMSV1_0_AV_PAIR pAV;
*ulAvlFlag = 0;
if ( AvlString->Buffer == NULL || AvlString->Length == 0 )
{
return STATUS_SUCCESS;
}
pAV = MsvpAvlGet(
(PMSV1_0_AV_PAIR)AvlString->Buffer,
AvId,
AvlString->Length
);
if ( pAV != NULL )
{
if( pAV->AvLen == sizeof( *ulAvlFlag ) )
{
CopyMemory( ulAvlFlag, (pAV+1), sizeof(ULONG) );
return STATUS_SUCCESS;
}
}
return STATUS_NOT_FOUND;
}