NT4/private/lsa/msv1_0/nlmain.c
2020-09-30 17:12:29 +02:00

3304 lines
90 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1989 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.
--*/
#include "msp.h"
#define NLP_ALLOCATE
#include "nlp.h"
#undef NLP_ALLOCATE
#include <stdlib.h>
#include <rpc.h> // Needed by samrpc.h
#include <samisrv.h> // SamIFree Routines
#include <lsarpc.h> // Lsar routines
#include <lsaisrv.h> // LsaIFree and Trusted Client Routines
#include <lmcons.h>
#include <lmerr.h>
#include <lmapibuf.h> // NetApiBufferFree
#include <lmaccess.h> // NetGetDCName
#include <lmremutl.h> // NetRemoteComputerSupports
#include <lmsname.h> // Service Names
#include <winsvc.h> // Service Controller
#include "nlpcache.h" // logon cache prototypes
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;
LPWSTR ComputerName;
DWORD ComputerNameLength = MAX_COMPUTERNAME_LENGTH + 1;
NT_PRODUCT_TYPE NtProductType;
UNICODE_STRING TempUnicodeString;
//
// Initialize global data
//
NlpEnumerationHandle = 0;
NlpSessionCount = 0;
NlpLogonAttemptCount = 0;
NlpComputerName.Buffer = NULL;
NlpSamDomainName.Buffer = NULL;
NlpSamDomainId = NULL;
NlpSamDomainHandle = NULL;
//
// Get the name of this machine.
//
ComputerName = RtlAllocateHeap(
MspHeap, 0,
ComputerNameLength * sizeof(WCHAR) );
if (ComputerName == NULL ||
!GetComputerNameW( ComputerName, &ComputerNameLength )) {
KdPrint(( "MsV1_0: Cannot get computername %lX\n", GetLastError() ));
NlpLanmanInstalled = FALSE;
RtlFreeHeap( MspHeap, 0, ComputerName );
ComputerName = NULL;
} else {
NlpLanmanInstalled = TRUE;
}
RtlInitUnicodeString( &NlpComputerName, ComputerName );
//
// Determine if this machine is running Windows NT or Lanman NT.
// LanMan NT runs on a domain controller.
//
if ( !RtlGetNtProductType( &NtProductType ) ) {
KdPrint(( "MsV1_0: Nt Product Type undefined (WinNt assumed)\n" ));
NtProductType = NtProductWinNt;
}
NlpWorkstation = (BOOLEAN)(NtProductType != NtProductLanManNt);
//
// Initialize any locks.
//
RtlInitializeCriticalSection(&NlpActiveLogonLock);
RtlInitializeCriticalSection(&NlpSessionCountLock);
//
// initialize the cache - creates a critical section is all
//
NlpCacheInitialize();
//
// Attempt to load Netlogon.dll
//
NlpLoadNetlogonDll();
#ifdef COMPILED_BY_DEVELOPER
KdPrint(("msv1_0: 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)) {
KdPrint(("[MSV1_0] 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;
}
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;
}
//
// Open a handle to the Netlogon Service.
//
ScManagerHandle = OpenSCManager(
NULL,
NULL,
SC_MANAGER_CONNECT );
if (ScManagerHandle == NULL) {
KdPrint(( "[MSV1_0] 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 ) {
KdPrint(( "[MSV1_0] 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 ) {
KdPrint(( "[MSV1_0] NlWaitForNetlogon: QueryServiceConfig failed: "
"%lu\n", NetStatus));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
AllocServiceConfig = RtlAllocateHeap( MspHeap, 0, ServiceConfigSize );
ServiceConfig = AllocServiceConfig;
if ( AllocServiceConfig == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
if ( !QueryServiceConfig(
ServiceHandle,
ServiceConfig,
ServiceConfigSize,
&ServiceConfigSize )) {
KdPrint(( "[MSV1_0] NlWaitForNetlogon: QueryServiceConfig "
"failed again: %lu\n", GetLastError()));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
}
if ( ServiceConfig->dwStartType != SERVICE_AUTO_START ) {
KdPrint(( "[MSV1_0] NlWaitForNetlogon: Netlogon start type invalid:"
"%lu\n", ServiceConfig->dwStartType ));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
//
// Loop waiting for the netlogon service to start.
//
for (;;) {
//
// Query the status of the Netlogon service.
//
if (! QueryServiceStatus( ServiceHandle, &ServiceStatus )) {
KdPrint(( "[MSV1_0] 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 ){
#if DBG
KdPrint(( "[MSV1_0] NlWaitForNetlogon: "
"Netlogon service couldn't start: %lu %lx\n",
ServiceStatus.dwWin32ExitCode,
ServiceStatus.dwWin32ExitCode ));
if ( ServiceStatus.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR ) {
KdPrint(( " Service specific error code: %lu %lx\n",
ServiceStatus.dwServiceSpecificExitCode,
ServiceStatus.dwServiceSpecificExitCode ));
}
#endif // DBG
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:
KdPrint(( "[MSV1_0] NlWaitForNetlogon: "
"Invalid service state: %lu\n",
ServiceStatus.dwCurrentState ));
Status = STATUS_NETLOGON_NOT_STARTED;
goto Cleanup;
}
//
// Wait a second for the netlogon service to start.
// If it has successfully started, just return now.
//
Status = NlWaitForEvent( L"\\NETLOGON_SERVICE_STARTED", 1 );
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 ) {
RtlFreeHeap( MspHeap, 0, 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;
LSA_HANDLE PolicyHandle = NULL;
OBJECT_ATTRIBUTES PolicyObjectAttributes;
PLSAPR_POLICY_INFORMATION PolicyAccountDomainInfo = NULL;
SAMPR_HANDLE SamHandle = NULL;
#ifdef SAM
PSAMPR_DOMAIN_INFO_BUFFER DomainInfo = NULL;
#endif // SAM
//
// 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
//
InitializeObjectAttributes( &PolicyObjectAttributes,
NULL, // Name
0, // Attributes
NULL, // Root
NULL ); // Security Descriptor
Status = LsaIOpenPolicyTrusted(&NlpPolicyHandle);
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
Status = LsarQueryInformationPolicy( NlpPolicyHandle,
PolicyAccountDomainInformation,
&PolicyAccountDomainInfo );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
if ( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid == NULL ||
PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainName.Length == 0 ) {
KdPrint(( "MsV1_0: Account domain info from LSA invalid.\n"));
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// Save the domain id of this domain
//
NlpSamDomainId = RtlAllocateHeap(
MspHeap, 0,
RtlLengthSid( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid ));
if ( NlpSamDomainId == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( NlpSamDomainId,
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.
//
if ( NlpWorkstation ) {
NlpSamDomainName = NlpComputerName;
} else {
NlpSamDomainName.Length = PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainName.Length;
NlpSamDomainName.MaximumLength = (USHORT)
(NlpSamDomainName.Length + sizeof(WCHAR));
NlpSamDomainName.Buffer =
RtlAllocateHeap( MspHeap, 0, NlpSamDomainName.MaximumLength );
if ( NlpSamDomainName.Buffer == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( NlpSamDomainName.Buffer,
PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainName.Buffer,
NlpSamDomainName.MaximumLength );
}
//
// Open our connection with SAM
//
Status = SamIConnect( NULL, // No server name
&SamHandle,
SAM_SERVER_CONNECT,
(BOOLEAN) TRUE ); // Indicate we are privileged
if ( !NT_SUCCESS(Status) ) {
SamHandle = NULL;
KdPrint(( "MsV1_0: Cannot SamIConnect %lX\n", Status));
goto Cleanup;
}
//
// Open the domain.
//
Status = SamrOpenDomain( SamHandle,
DOMAIN_ALL_ACCESS,
NlpSamDomainId,
&NlpSamDomainHandle );
if ( !NT_SUCCESS(Status) ) {
NlpSamDomainHandle = NULL;
KdPrint(( "MsV1_0: Cannot SamrOpenDomain %lX\n", Status));
goto Cleanup;
}
#ifdef SAM
//
// Ensure the role in SAM is compatible with Netlogon's role
// ?? Use DomainUasInformation once it is defined
//
Status = SamrQueryInformationDomain( NlpSamDomainHandle,
DomainGeneralInformation,
&DomainInfo );
if ( !NT_SUCCESS(Status) ) {
DomainInfo = NULL;
KdPrint(( "MsV1_0: Cannot SamrQueryInformationDomain %lX\n", Status));
goto Cleanup;
}
// ?? Doesn't define this properly
NlpUasCompatibilityRequired = DomainInfo->General.UasCompatibilityRequired;
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainInfo, DomainGeneralInformation );
#endif // SAM
Status = STATUS_SUCCESS;
NlpSamInitialized = TRUE;
Cleanup:
//
// If we weren't successful,
// Clean up global resources we intended to initialize.
//
if ( !NT_SUCCESS(Status) ) {
if ( !NlpWorkstation ) {
if ( NlpSamDomainName.Buffer != NULL ) {
RtlFreeHeap( MspHeap, 0, NlpSamDomainName.Buffer );
}
}
if ( NlpSamDomainHandle != NULL ) {
(VOID) SamrCloseHandle( &NlpSamDomainHandle );
}
if ( NlpSamDomainId != NULL ) {
RtlFreeHeap( MspHeap, 0, NlpSamDomainId );
}
if (NlpPolicyHandle != NULL) {
(VOID) LsaClose( NlpPolicyHandle );
NlpPolicyHandle = NULL;
}
}
//
// Free locally used resources.
//
if ( PolicyAccountDomainInfo != NULL ) {
LsaIFree_LSAPR_POLICY_INFORMATION( PolicyAccountDomainInformation,
PolicyAccountDomainInfo );
}
if ( SamHandle != NULL ) {
(VOID) 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;
ULONG Seed[2];
CYPHER_BLOCK Challenge;
UNREFERENCED_PARAMETER( ClientBufferBase );
ASSERT( sizeof(LM_CHALLENGE) == MSV1_0_CHALLENGE_LENGTH );
ASSERT( sizeof(LM_CHALLENGE) == sizeof(CYPHER_BLOCK) );
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 );
//
// Build an 8-byte challenge as follows:
//
// Take the 8-byte time of day.
// Add a monotonically increasing value to ensure no two times are the same
// Use that to OWF encrypt the key.
//
//
// Compute a relatively random seed.
//
#ifdef USE_CONSTANT_CHALLENGE
RtlZeroMemory( &Challenge, sizeof(Challenge) );
#else // USE_CONSTANT_CHALLENGE
RtlZeroMemory(Seed, sizeof(Seed));
ASSERT( sizeof(LARGE_INTEGER) <= sizeof(Seed) );
Status = NtQuerySystemTime ( (PLARGE_INTEGER) Seed );
ASSERT( NT_SUCCESS(Status) );
RtlEnterCriticalSection(&NlpSessionCountLock);
Seed[1] = ++NlpSessionCount;
RtlLeaveCriticalSection(&NlpSessionCountLock);
//
// Encrypt it to obfuscate it a little more.
//
ASSERT( sizeof(BLOCK_KEY) <= sizeof(Seed) );
Status = RtlEncryptStdBlock( (PBLOCK_KEY) Seed, &Challenge );
ASSERT( NT_SUCCESS(Status) );
#endif // USE_CONSTANT_CHALLENGE
//
// 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;
RtlCopyMemory( ChallengeResponse->ChallengeToClient,
&Challenge,
sizeof(Challenge) );
//
// 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;
PMSV1_0_PRIMARY_CREDENTIAL BuiltCredential = NULL;
//
// Responses to return to the caller.
//
LM_RESPONSE LmResponse;
STRING LmResponseString;
NT_RESPONSE NtResponse;
STRING NtResponseString;
UNICODE_STRING UserName;
UNICODE_STRING LogonDomainName;
UNICODE_STRING NullUnicodeString;
USER_SESSION_KEY UserSessionKey;
UCHAR LanmanSessionKey[MSV1_0_LANMAN_SESSION_KEY_LENGTH];
//
// Initialization
//
NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
RtlInitUnicodeString( &UserName, NULL );
RtlInitUnicodeString( &LogonDomainName, NULL );
RtlInitUnicodeString( &NullUnicodeString, 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) ) {
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 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.
//
#define PRIMARY_CREDENTIAL_NEEDED \
(RETURN_PRIMARY_LOGON_DOMAINNAME | \
RETURN_PRIMARY_USERNAME | \
USE_PRIMARY_PASSWORD )
if ( (GetRespRequest->ParameterControl & PRIMARY_CREDENTIAL_NEEDED) != 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 ) {
LogonDomainName = PrimaryCredential->LogonDomainName;
}
#ifdef USE_REDMOND_CLIFFV
//
// Pretend CliffVDom\CliffV is really Redmond\CliffV
//
// This kludge is required since CliffVDom is not politically allowed
// to trust the Redmond domain, but all my resources outside the domain
// are owned by Redmond\CliffV.
//
{
UNICODE_STRING Cliffv;
UNICODE_STRING CliffvDom;
RtlInitUnicodeString( &Cliffv, L"CliffV" );
RtlInitUnicodeString( &CliffvDom, L"CliffVDom" );
if ( RtlEqualUnicodeString( &UserName, &Cliffv, TRUE ) &&
RtlEqualUnicodeString( &LogonDomainName, &CliffvDom, TRUE ) ) {
RtlInitUnicodeString( &LogonDomainName, L"Redmond" );
}
}
#endif // USE_REDMOND_CLIFFV
} 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 {
KdPrint(("MSV1_0: MspLm20GetChallengeResponse: cannot "
" GetPrimaryCredential %lx\n", Status ));
goto Cleanup;
}
} else {
KdPrint(("MSV1_0: 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 ) {
ULONG CredentialSize;
Status = NlpMakePrimaryCredential( &NullUnicodeString,
&NullUnicodeString,
&GetRespRequest->Password,
&BuiltCredential,
&CredentialSize );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// Use the newly allocated credential to get the password information
// from.
//
Credential = BuiltCredential;
}
//
// Build the appropriate response.
//
if ( Credential != NULL ) {
Status = RtlCalculateLmResponse(
(PLM_CHALLENGE) GetRespRequest->ChallengeToClient,
&Credential->LmOwfPassword,
&LmResponse );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
LmResponseString.Buffer = (PUCHAR) &LmResponse;
LmResponseString.Length = sizeof(LmResponse);
Status = RtlCalculateNtResponse(
(PNT_CHALLENGE) GetRespRequest->ChallengeToClient,
&Credential->NtOwfPassword,
&NtResponse );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
NtResponseString.Buffer = (PUCHAR) &NtResponse;
NtResponseString.Length = sizeof(NtResponse);
//
// Compute the session keys
//
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) );
}
}
//
// 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 ( BuiltCredential != NULL ) {
(*Lsa.FreeLsaHeap)( BuiltCredential );
}
if ( PrimaryCredential != NULL ) {
(*Lsa.FreeLsaHeap)( PrimaryCredential );
}
//
// Return status to the caller.
//
*ProtocolStatus = Status;
return STATUS_SUCCESS;
}
NTSTATUS
MspLm20EnumUsers (
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_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 EnumRequest;
PMSV1_0_ENUMUSERS_RESPONSE EnumResponse;
CLIENT_BUFFER_DESC ClientBufferDesc;
ULONG LogonCount = 0;
PACTIVE_LOGON Logon;
BOOLEAN ActiveLogonsAreLocked = FALSE;
PUCHAR Where;
//
// 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_ENUMUSERS_REQUEST) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
EnumRequest = (PMSV1_0_ENUMUSERS_REQUEST) ProtocolSubmitBuffer;
ASSERT( EnumRequest->MessageType == MsV1_0EnumerateUsers );
//
// Count the current number of active logons
//
NlpLockActiveLogons();
ActiveLogonsAreLocked = TRUE;
for( Logon = NlpActiveLogons; Logon != NULL; Logon = Logon->Next ) {
LogonCount ++;
}
//
// Allocate a buffer to return to the caller.
//
*ReturnBufferSize = sizeof(MSV1_0_ENUMUSERS_RESPONSE) +
LogonCount * (sizeof(LUID) + sizeof(ULONG));
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_ENUMUSERS_RESPONSE),
*ReturnBufferSize );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
EnumResponse = (PMSV1_0_ENUMUSERS_RESPONSE) ClientBufferDesc.MsvBuffer;
//
// Fill in the return buffer.
//
EnumResponse->MessageType = MsV1_0EnumerateUsers;
EnumResponse->NumberOfLoggedOnUsers = LogonCount;
Where = (PUCHAR)(EnumResponse + 1);
//
// Loop through the Active Logon Table copying the LogonId of each session.
//
EnumResponse->LogonIds = (PLUID)(ClientBufferDesc.UserBuffer +
(Where - ClientBufferDesc.MsvBuffer));
for( Logon = NlpActiveLogons; Logon != NULL; Logon = Logon->Next ) {
*((PLUID)Where) = Logon->LogonId,
Where += sizeof(LUID);
}
//
// Loop through the Active Logon Table copying the EnumHandle of
// each session.
//
EnumResponse->EnumHandles = (PULONG)(ClientBufferDesc.UserBuffer +
(Where - ClientBufferDesc.MsvBuffer));
for( Logon = NlpActiveLogons; Logon != NULL; Logon = Logon->Next ) {
*((PULONG)Where) = Logon->EnumHandle,
Where += sizeof(ULONG);
}
//
// 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 ( ActiveLogonsAreLocked ) {
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
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 GetInfoRequest;
PMSV1_0_GETUSERINFO_RESPONSE GetInfoResponse = NULL;
CLIENT_BUFFER_DESC ClientBufferDesc;
BOOLEAN ActiveLogonsAreLocked = FALSE;
PACTIVE_LOGON *ActiveLogon;
PACTIVE_LOGON Logon;
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;
}
GetInfoRequest = (PMSV1_0_GETUSERINFO_REQUEST) ProtocolSubmitBuffer;
ASSERT( GetInfoRequest->MessageType == MsV1_0GetUserInfo );
//
// Find the Active logon entry for this particular Logon Id.
//
NlpLockActiveLogons();
ActiveLogonsAreLocked = TRUE;
if (!NlpFindActiveLogon( &GetInfoRequest->LogonId, &ActiveLogon )){
Status = STATUS_NO_SUCH_LOGON_SESSION;
goto Cleanup;
}
Logon = *ActiveLogon;
//
// Allocate a buffer to return to the caller.
//
SidLength = RtlLengthSid( Logon->UserSid );
*ReturnBufferSize = sizeof(MSV1_0_GETUSERINFO_RESPONSE) +
Logon->UserName.Length + sizeof(WCHAR) +
Logon->LogonDomainName.Length + sizeof(WCHAR) +
Logon->LogonServer.Length + sizeof(WCHAR) +
SidLength;
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_GETUSERINFO_RESPONSE),
*ReturnBufferSize );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
GetInfoResponse = (PMSV1_0_GETUSERINFO_RESPONSE) ClientBufferDesc.MsvBuffer;
//
// Fill in the return buffer.
//
GetInfoResponse->MessageType = MsV1_0GetUserInfo;
GetInfoResponse->LogonType = Logon->LogonType;
//
// Copy ULONG aligned data first
//
GetInfoResponse->UserSid = ClientBufferDesc.UserBuffer +
ClientBufferDesc.StringOffset;
RtlCopyMemory( ClientBufferDesc.MsvBuffer + ClientBufferDesc.StringOffset,
Logon->UserSid,
SidLength );
ClientBufferDesc.StringOffset += SidLength;
//
// Copy WCHAR aligned data
//
NlpPutClientString( &ClientBufferDesc,
&GetInfoResponse->UserName,
&Logon->UserName );
NlpPutClientString( &ClientBufferDesc,
&GetInfoResponse->LogonDomainName,
&Logon->LogonDomainName );
NlpPutClientString( &ClientBufferDesc,
&GetInfoResponse->LogonServer,
&Logon->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 ( ActiveLogonsAreLocked ) {
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.
--*/
{
#ifdef LOGON_ENUM_SUPPORTED
NTSTATUS Status;
PMSV1_0_RELOGON_REQUEST RelogonRequest;
PACTIVE_LOGON Logon;
BOOLEAN ActiveLogonsAreLocked = FALSE;
PMSV1_0_PRIMARY_CREDENTIAL Credential = NULL;
//
// Ensure the specified Submit Buffer is of reasonable size and
// relocate all of the pointers to be relative to the LSA allocated
// buffer.
//
UNREFERENCED_PARAMETER( ClientRequest );
UNREFERENCED_PARAMETER( ReturnBufferSize );
if ( SubmitBufferSize < sizeof(MSV1_0_RELOGON_REQUEST) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
RelogonRequest = (PMSV1_0_RELOGON_REQUEST) ProtocolSubmitBuffer;
ASSERT( RelogonRequest->MessageType == MsV1_0ReLogonUsers );
RELOCATE_ONE( &RelogonRequest->LogonServer );
//
// Count the current number of active logons
//
NlpLockActiveLogons();
ActiveLogonsAreLocked = TRUE;
//
// Loop through the Active Logon Table
// relogging on each entry for the specified server.
//
for( Logon = NlpActiveLogons; Logon != NULL; Logon = Logon->Next ) {
NETLOGON_INTERACTIVE_INFO LogonInteractive;
PNETLOGON_VALIDATION_SAM_INFO2 NlpUser;
BOOLEAN Authoritative;
//
// Ensure this entry was originally logged on by the Netlogon Service
// on the specified server.
//
if ( (Logon->Flags & LOGON_BY_NETLOGON) == 0 ||
!RtlEqualComputerName( &RelogonRequest->LogonServer,
&Logon->LogonServer ) ) {
continue;
}
//
// This shouldn't happen since LOGON_BY_NETLOGON is set.
//
if ( NlpNetlogonDllHandle == NULL ) {
continue;
}
//
// Get the OWF password for this session.
//
Status = NlpGetPrimaryCredential( &Logon->LogonId, &Credential, NULL );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// Define the description of the user to log on.
//
LogonInteractive.Identity.LogonDomainName = Logon->LogonDomainName;
LogonInteractive.Identity.ParameterControl = 0;
LogonInteractive.Identity.LogonId = Logon->LogonId;
LogonInteractive.Identity.UserName = Logon->UserName;
LogonInteractive.Identity.Workstation = NlpComputerName;
LogonInteractive.LmOwfPassword = Credential->LmOwfPassword;
LogonInteractive.NtOwfPassword = Credential->NtOwfPassword;
//
// Wait for NETLOGON to finish initialization.
//
if ( !NlpNetlogonInitialized ) {
Status = NlWaitForEvent( L"\\NETLOGON_SERVICE_STARTED", 45 );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
NlpNetlogonInitialized = TRUE;
}
//
// Call the Netlogon Service.
//
// The credentials stored in the Credential structure are really
// the OWF encrypted password.
//
Status = (*NlpNetLogonSamLogon)(
NULL, // Server name
NULL, // Computer name
NULL, // Authenticator
NULL, // ReturnAuthenticator
NetlogonInteractiveInformation,
(LPBYTE) &LogonInteractive,
NetlogonValidationSamInfo2,
(LPBYTE *) &NlpUser,
&Authoritative );
//
// Ignore failures.
//
if ( NT_SUCCESS( Status ) ) {
MIDL_user_free( NlpUser );
}
}
Status = STATUS_SUCCESS;
Cleanup:
//
// Be sure to unlock the lock on the Active logon list.
//
if ( ActiveLogonsAreLocked ) {
NlpUnlockActiveLogons();
}
if ( Credential != NULL ) {
(*Lsa.FreeLsaHeap)( Credential );
}
//
// Return status to the caller.
//
*ProtocolReturnBuffer = NULL;
*ProtocolStatus = Status;
return STATUS_SUCCESS;
#else // LOGON_ENUM_SUPPORTED
UNREFERENCED_PARAMETER( ClientRequest );
UNREFERENCED_PARAMETER( ProtocolSubmitBuffer);
UNREFERENCED_PARAMETER( ClientBufferBase);
UNREFERENCED_PARAMETER( SubmitBufferSize);
UNREFERENCED_PARAMETER( ReturnBufferSize);
*ProtocolReturnBuffer = NULL;
*ProtocolStatus = STATUS_NOT_IMPLEMENTED;
return STATUS_SUCCESS;
#endif // LOGON_ENUM_SUPPORTED
}
NTSTATUS
LsaApLogonUserEx (
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
)
/*++
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.
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;
LSA_TOKEN_INFORMATION_TYPE LsaTokenInformationType = LsaTokenInformationV1;
PNETLOGON_VALIDATION_SAM_INFO2 NlpUser = NULL;
PACTIVE_LOGON LogonEntry = NULL;
BOOLEAN LogonEntryLinked = FALSE;
BOOLEAN LogonSessionCreated = FALSE;
BOOLEAN LogonCredentialAdded = FALSE;
ULONG Flags = 0;
BOOLEAN Authoritative;
BOOLEAN BadPasswordCountZeroed;
BOOLEAN StandaloneWorkstation;
PSID UserSid = NULL;
PMSV1_0_PRIMARY_CREDENTIAL Credential = NULL;
ULONG CredentialSize;
PSECURITY_SEED_AND_LENGTH SeedAndLength;
UCHAR Seed;
PUNICODE_STRING WorkStationName = NULL;
//
// Temporary storage while we try to figure
// out what our username and authenticating
// authority is.
//
UNICODE_STRING TmpName;
WCHAR TmpNameBuffer[UNLEN];
UNICODE_STRING TmpAuthority;
WCHAR TmpAuthorityBuffer[DNLEN];
//
// Logon Information.
//
NETLOGON_LOGON_INFO_CLASS LogonLevel;
NETLOGON_INTERACTIVE_INFO LogonInteractive;
NETLOGON_NETWORK_INFO LogonNetwork;
PNETLOGON_LOGON_IDENTITY_INFO LogonInformation;
//
// 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 = DNLEN * sizeof( WCHAR );
TmpAuthority.Length = 0;
//
// 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 Interactive:
case Service:
case Batch:
{
PMSV1_0_INTERACTIVE_LOGON Authentication;
WorkStationName = &NlpComputerName;
//
// Ensure this is really an interactive logon.
//
Authentication =
(PMSV1_0_INTERACTIVE_LOGON) ProtocolSubmitBuffer;
if ( Authentication->MessageType != MsV1_0InteractiveLogon ) {
KdPrint(("MSV1_0: LsaApLogonUser: Bad Validation Class\n"));
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 ||
Authentication->Password.Length > PWLEN ) {
KdPrint(("MSV1_0: 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 );
//
// Now decode the password, if necessary
//
if (Seed != 0 ) {
try {
RtlRunDecodeUnicodeString( Seed, &Authentication->Password);
} except(EXCEPTION_EXECUTE_HANDLER) {
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 );
}
//
// Build the primary credential
//
Status = NlpMakePrimaryCredential( &Authentication->LogonDomainName,
&Authentication->UserName,
&Authentication->Password,
&Credential,
&CredentialSize );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// 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) {
Status = STATUS_ILL_FORMED_PASSWORD;
goto Cleanup;
}
//
// 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 = Credential->LmOwfPassword;
LogonInteractive.NtOwfPassword = Credential->NtOwfPassword;
}
break;
case Network:
{
PMSV1_0_LM20_LOGON Authentication;
//
// Ensure this is really a network logon request.
//
Authentication =
(PMSV1_0_LM20_LOGON) ProtocolSubmitBuffer;
if ( Authentication->MessageType != MsV1_0Lm20Logon &&
Authentication->MessageType != MsV1_0NetworkLogon ) {
KdPrint(("MSV1_0: 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 );
//
// 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->MessageType == MsV1_0Lm20Logon ) {
LogonNetwork.Identity.ParameterControl = CLEARTEXT_PASSWORD_ALLOWED;
} else {
ASSERT( CLEARTEXT_PASSWORD_ALLOWED == MSV1_0_CLEARTEXT_PASSWORD_ALLOWED );
LogonNetwork.Identity.ParameterControl =
Authentication->ParameterControl;
}
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) );
RtlCopyMemory( &LogonNetwork.LmChallenge,
Authentication->ChallengeToClient,
LM_CHALLENGE_LENGTH );
//
// Enforce length restrictions on username
//
if ( Authentication->UserName.Length > UNLEN ) {
KdPrint(("MSV1_0: 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:
return STATUS_INVALID_LOGON_TYPE;
}
//
// Allocate a LogonId for this logon session.
//
Status = NtAllocateLocallyUniqueId( LogonId );
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
NEW_TO_OLD_LARGE_INTEGER( (*LogonId), LogonInformation->LogonId );
//
// Create a new logon session
//
Status = (*Lsa.CreateLogonSession)( LogonId );
if( !NT_SUCCESS(Status) ) {
KdPrint(( "MSV1_0: LsaApLogonUser: Collision from CreateLogonSession\n"));
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 ) {
//
// Try to load NetlogonDll again if it isn't already.
//
if ( NlpNetlogonDllHandle == NULL ) {
NlpLoadNetlogonDll();
}
//
// If Sam is not yet initialized,
// do it now.
//
if ( !NlpSamInitialized ) {
Status = NlSamInitialize( 120 );
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 && NlpPolicyHandle != NULL ) {
PLSAPR_POLICY_INFORMATION PolicyPrimaryDomainInfo = NULL;
Status = LsarQueryInformationPolicy(
NlpPolicyHandle,
PolicyPrimaryDomainInformation,
&PolicyPrimaryDomainInfo );
if ( NT_SUCCESS(Status) ) {
StandaloneWorkstation =
(PolicyPrimaryDomainInfo->PolicyPrimaryDomainInfo.Sid == NULL);
LsaIFree_LSAPR_POLICY_INFORMATION( PolicyPrimaryDomainInformation,
PolicyPrimaryDomainInfo );
} else {
StandaloneWorkstation = FALSE;
}
} else {
StandaloneWorkstation = FALSE;
}
}
//
// 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 ||
( NlpWorkstation &&
LogonInformation->LogonDomainName.Length != 0 &&
RtlEqualDomainName( &NlpSamDomainName,
&LogonInformation->LogonDomainName )) ) {
//
// Get the Validation information from the local SAM database
//
Status = MsvSamValidate(
NlpSamDomainHandle,
NlpUasCompatibilityRequired,
MsvApSecureChannel,
&NlpComputerName, // Logon Server is this machine
&NlpSamDomainName,
NlpSamDomainId,
LogonLevel,
LogonInformation,
NetlogonValidationSamInfo2,
(PVOID *) &NlpUser,
&Authoritative,
&BadPasswordCountZeroed,
MSVSAM_SPECIFIED | MSVSAM_GUEST);
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// If we couldn't validate via one of the above mechanisms,
// call the local Netlogon service to get the validation information.
//
} else {
//
// Wait for NETLOGON to finish initialization.
//
if ( !NlpNetlogonInitialized ) {
Status = NlWaitForNetlogon( 90 );
if ( !NT_SUCCESS(Status) ) {
if ( Status != STATUS_NETLOGON_NOT_STARTED ) {
goto Cleanup;
}
} else {
NlpNetlogonInitialized = TRUE;
}
}
//
// Actually call the netlogon service.
//
if ( NlpNetlogonInitialized ) {
Status = (*NlpNetLogonSamLogon)(
NULL, // Server name
NULL, // Computer name
NULL, // Authenticator
NULL, // ReturnAuthenticator
LogonLevel,
(LPBYTE) &LogonInformation,
NetlogonValidationSamInfo2,
(LPBYTE *) &NlpUser,
&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;
}
}
//
// 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 ) ) {
//
// Get the Validation information from the local SAM database
//
Status = MsvSamValidate(
NlpSamDomainHandle,
NlpUasCompatibilityRequired,
MsvApSecureChannel,
&NlpComputerName, // Logon Server is this machine
&NlpSamDomainName,
NlpSamDomainId,
LogonLevel,
LogonInformation,
NetlogonValidationSamInfo2,
(PVOID *) &NlpUser,
&Authoritative,
&BadPasswordCountZeroed,
MSVSAM_SPECIFIED | MSVSAM_GUEST);
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// 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 not treated
// the same as Interactive here.
//
if (LogonType == Interactive) {
NTSTATUS ntStatus;
ntStatus = NlpAddCacheEntry(&LogonInteractive, NlpUser);
}
//
// 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.
//
//
// Even though we change the cache only for interactive logons,
// 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;
//
// Try to logon via the cache.
//
//
ntStatus = NlpGetCacheEntry(LogonInformation, &NlpUser, &cachePasswords);
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;
}
//
// Now we have the information from the cache, validate the
// user's password
//
if (!MsvpPasswordValidate(
NlpUasCompatibilityRequired,
LogonLevel,
(PVOID)LogonInformation,
&cachePasswords.SecretPasswords,
&NlpUser->UserFlags,
&NlpUser->UserSessionKey,
(PLM_SESSION_KEY)
&NlpUser->ExpansionRoom[SAMINFO_LM_SESSION_KEY]
)) {
Status = STATUS_WRONG_PASSWORD;
goto Cleanup;
}
Status = STATUS_SUCCESS;
//
// The cache always returns a NETLOGONV_VALIDATION_SAM_INFO2
// structure so set the LOGON_EXTRA_SIDS flag, whether or not
// there are extra sids
//
NlpUser->UserFlags |= LOGON_CACHED_ACCOUNT | LOGON_EXTRA_SIDS;
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 &&
(Status == STATUS_NO_SUCH_USER ||
Status == STATUS_INVALID_WORKSTATION ||
Status == STATUS_PASSWORD_EXPIRED ||
Status == STATUS_ACCOUNT_DISABLED) ) {
//
// Delete the cache entry
NTSTATUS ntStatus;
ntStatus = NlpDeleteCacheEntry(&LogonInteractive);
KdPrint(("MSV1_0: LsaApLogonUser: NlpDeleteCacheEntry returns %x\n", ntStatus));
goto Cleanup;
} else {
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
) {
PACTIVE_LOGON *ActiveLogon;
ULONG LogonEntrySize;
ULONG UserSidSize;
PUCHAR Where;
USHORT LogonCount;
//
// Save the credential in the LSA.
//
Status = NlpAddPrimaryCredential( LogonId,
Credential,
CredentialSize );
if ( !NT_SUCCESS( Status ) ) {
KdPrint(( "MSV1_0: LsaApLogonUser: error from AddCredential %lX\n",
Status));
goto Cleanup;
}
LogonCredentialAdded = TRUE;
//
// Build a Sid for this user.
//
UserSid = NlpMakeDomainRelativeSid( NlpUser->LogonDomainId,
NlpUser->UserId );
if ( UserSid == NULL ) {
Status = STATUS_NO_MEMORY;
KdPrint(("MSV1_0: LsaApLogonUser: No memory\n"));
goto Cleanup;
}
UserSidSize = RtlLengthSid( UserSid );
//
// Allocate an entry for the active logon table.
//
LogonEntrySize = ROUND_UP_COUNT(sizeof(ACTIVE_LOGON), ALIGN_DWORD) +
ROUND_UP_COUNT(UserSidSize, sizeof(WCHAR)) +
NlpUser->EffectiveName.Length + sizeof(WCHAR) +
NlpUser->LogonDomainName.Length + sizeof(WCHAR) +
NlpUser->LogonServer.Length + sizeof(WCHAR);
LogonEntry = RtlAllocateHeap( MspHeap, 0, LogonEntrySize );
if ( LogonEntry == NULL ) {
Status = STATUS_NO_MEMORY;
KdPrint(("MSV1_0: LsaApLogonUser: No memory %ld\n",
sizeof(ACTIVE_LOGON)));
goto Cleanup;
}
//
// Fill in the logon table entry.
//
Where = (PUCHAR)(LogonEntry + 1);
OLD_TO_NEW_LARGE_INTEGER(
LogonInformation->LogonId,
LogonEntry->LogonId );
LogonEntry->Flags = Flags;
LogonEntry->LogonType = LogonType;
//
// Copy DWORD aligned fields first.
//
Where = ROUND_UP_POINTER( Where, ALIGN_DWORD );
Status = RtlCopySid(UserSidSize, (PSID)Where, UserSid);
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
LogonEntry->UserSid = (PSID) Where;
Where += UserSidSize;
//
// Copy WCHAR aligned fields
//
Where = ROUND_UP_POINTER( Where, ALIGN_WCHAR );
NlpPutString( &LogonEntry->UserName,
&NlpUser->EffectiveName,
&Where );
NlpPutString( &LogonEntry->LogonDomainName,
&NlpUser->LogonDomainName,
&Where );
NlpPutString( &LogonEntry->LogonServer,
&NlpUser->LogonServer,
&Where );
//
// Get the next enumeration handle for this session.
//
NlpLockActiveLogons();
NlpEnumerationHandle ++;
LogonEntry->EnumHandle = NlpEnumerationHandle;
//
// Insert this entry into the active logon table.
//
if (NlpFindActiveLogon( LogonId, &ActiveLogon )){
//
// This Logon ID is already in use.
//
NlpUnlockActiveLogons();
Status = STATUS_LOGON_SESSION_COLLISION;
KdPrint((
"MSV1_0: LsaApLogonUser: Collision from NlpFindActiveLogon\n"));
goto Cleanup;
}
LogonEntry->Next = *ActiveLogon;
*ActiveLogon = LogonEntry;
LogonEntryLinked = TRUE;
NlpUnlockActiveLogons();
//
// Ensure the LogonCount is at least as big as it is for this
// machine.
//
LogonCount = (USHORT) NlpCountActiveLogon( &NlpUser->LogonDomainName,
&NlpUser->EffectiveName );
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 ) ) {
KdPrint((
"MSV1_0: LsaApLogonUser: Allocate Profile Failed: %lx\n", Status));
goto Cleanup;
}
} else if ( LogonType == Network ) {
//
// 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 ) ) {
KdPrint((
"MSV1_0: LsaApLogonUser: Allocate Profile Failed: %lx\n", Status));
goto Cleanup;
}
}
//
// Build the token information to return to the LSA
//
switch (LsaTokenInformationType) {
case LsaTokenInformationV1:
Status = NlpMakeTokenInformationV1(
NlpUser,
(PLSA_TOKEN_INFORMATION_V1 *)TokenInformation );
if ( !NT_SUCCESS( Status ) ) {
KdPrint((
"MSV1_0: LsaApLogonUser: MakeTokenInformation 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;
//
// Copy out the AuthenticatingAuthority here. This is for auditing.
//
Status = STATUS_SUCCESS;
Cleanup:
//
// 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 ( LogonEntry != NULL ) {
if ( LogonEntryLinked ) {
LsaApLogonTerminated( LogonId );
} else {
if ( LogonCredentialAdded ) {
(VOID) NlpDeletePrimaryCredential(
LogonId );
}
RtlFreeHeap( MspHeap, 0, LogonEntry );
}
}
if ( *ProfileBuffer != NULL ) {
(VOID)(*Lsa.FreeClientBuffer)( ClientRequest, *ProfileBuffer );
*ProfileBuffer = NULL;
}
}
//
// 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;
}
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 {
RtlInitUnicodeString( *AccountName, NULL );
}
}
*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 {
RtlInitUnicodeString( *AuthenticatingAuthority, NULL );
}
}
*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 {
RtlInitUnicodeString( *MachineName, NULL );
}
}
}
//
// Map status codes to prevent specific information from being
// released about this user.
//
switch (Status) {
case STATUS_WRONG_PASSWORD:
case STATUS_NO_SUCH_USER:
//
// sleep 3 seconds to "discourage" dictionary attacks.
// Don't worry about interactive logon dictionary attacks.
// They will be slow anyway.
//
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 shouldn't happen, but guard against it anyway.
//
case STATUS_ACCOUNT_RESTRICTION:
*SubStatus = STATUS_ACCOUNT_RESTRICTION;
break;
default:
break;
}
//
// Cleanup locally used resources
//
if ( Credential != NULL ) {
(*Lsa.FreeLsaHeap)( Credential );
}
if ( NlpUser != NULL ) {
MIDL_user_free( NlpUser );
}
if ( UserSid != NULL ) {
(*Lsa.FreeLsaHeap)( UserSid );
}
//
// Return status to the caller
//
return Status;
}
VOID
LsaApLogonTerminated (
IN PLUID LogonId
)
/*++
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:
LogonId - Is the logon ID that just logged off.
Return Status:
None.
--*/
{
NTSTATUS Status;
PACTIVE_LOGON LogonEntry;
PACTIVE_LOGON *ActiveLogon;
NETLOGON_INTERACTIVE_INFO LogonInteractive;
PNETLOGON_INTERACTIVE_INFO LogonInteractivePointer;
//
// Find the entry and de-link it from the active logon table.
//
NlpLockActiveLogons();
if ( !NlpFindActiveLogon( LogonId, &ActiveLogon ) ) {
NlpUnlockActiveLogons();
return;
}
LogonEntry = *ActiveLogon;
*ActiveLogon = LogonEntry->Next;
NlpUnlockActiveLogons();
//
// Build the Logon Information structure.
//
LogonInteractive.Identity.LogonDomainName = LogonEntry->LogonDomainName;
LogonInteractive.Identity.ParameterControl = 0;
NEW_TO_OLD_LARGE_INTEGER(
LogonEntry->LogonId,
LogonInteractive.Identity.LogonId );
LogonInteractive.Identity.UserName = LogonEntry->UserName;
LogonInteractive.Identity.Workstation = NlpComputerName;
//
// If this entry was logged on via netlogon,
// tell netlogon the session is terminated.
//
if ( (LogonEntry->Flags & LOGON_BY_NETLOGON) && NlpNetlogonDllHandle != NULL ) {
//
// If netlogon isn't running,
// it isn't worth waiting around just to update the logon statistics.
// just return an error.
//
if ( !NlpNetlogonInitialized ) {
Status = STATUS_NETLOGON_NOT_STARTED; // Map to an error condition
goto Cleanup;
}
//
// Call Netlogon to log off.
//
LogonInteractivePointer = &LogonInteractive;
Status = (*NlpNetLogonSamLogoff) (
NULL, // Server name
NULL, // Computer name
NULL, // Authenticator
NULL, // ReturnAuthenticator
NetlogonInteractiveInformation,
(LPBYTE) &LogonInteractivePointer );
if ( !NT_SUCCESS(Status) ) {
KdPrint((
"MSV1_0: LsaApLogonTerminated:"
" Cannot logoff from Netlogon service %lX\n",
Status ));
}
//
// if we logged on via the cache,
// there are no logon statistics to update.
//
} else if ( LogonEntry->Flags & LOGON_BY_CACHE ) {
/* Nothing to do here */
KdPrint(( "MSV1_0: LsaApLogonTerminated:"
" User who logged on via cache has logged off.\n"));
//
// Otherwise just update the local logon statistics
//
} else {
ASSERT( NlpSamInitialized );
Status = MsvSamLogoff(
NlpSamDomainHandle,
NetlogonInteractiveInformation,
(LPBYTE) &LogonInteractive );
if ( !NT_SUCCESS(Status) ) {
KdPrint((
"MSV1_0: LsaApLogonTerminated:"
" Cannot update Sam Logoff statistics %lX\n",
Status ));
}
}
Cleanup:
//
// 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( &LogonEntry->LogonId );
//
// Deallocate the now orphaned entry.
//
RtlFreeHeap( MspHeap, 0, LogonEntry );
//
// NB: We don't delete the logon session or credentials.
// That will be done by the LSA itself after we return.
//
return;
}