4562 lines
116 KiB
C
4562 lines
116 KiB
C
/*++
|
||
|
||
Copyright (c) 1991 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
nlpcache.c
|
||
|
||
Abstract:
|
||
|
||
This module contains routines which implement user account caching:
|
||
|
||
NlpCacheInitialize
|
||
NlpCacheTerminate
|
||
NlpAddCacheEntry
|
||
NlpGetCacheEntry
|
||
NlpDeleteCacheEntry
|
||
NlpChangeCachePassword
|
||
|
||
|
||
The cache contains the most recent validated logon information. There is
|
||
only 1 (that's right - one) cache slot. This will probably change though
|
||
|
||
Author:
|
||
|
||
Richard L Firth (rfirth) 17-Dec-1991
|
||
|
||
BUGBUG - To Be Done:
|
||
|
||
1. call Lsar routines instead of going via Rpc. Use:
|
||
LsaIOpenPolicyTruseted()
|
||
Lsar routines to do work
|
||
LsaIFree() to free returned buffers
|
||
|
||
2. Security - stop anybody viewing cache in registry
|
||
|
||
Revision History:
|
||
|
||
17-Dec-1991 rfirth
|
||
Created
|
||
|
||
--*/
|
||
|
||
#include "msp.h"
|
||
#include "nlp.h"
|
||
#include "nlpcache.h"
|
||
#include <ntrpcp.h> // MIDL_user_{allocate!free}
|
||
|
||
//
|
||
// manifests
|
||
//
|
||
|
||
#if DBG
|
||
#include <stdio.h>
|
||
#endif
|
||
|
||
//
|
||
// Revision numbers
|
||
//
|
||
// The first release of NT (NT1.0) didn't explicitly store
|
||
// a revision number. However, we are designating that release
|
||
// to be revision 0x00010000 (1.0). NT1.0A is being designated
|
||
// as revision 0x00010002 (1.2). Previous to build 622 is revision
|
||
// 0x00010001 (1.1).
|
||
//
|
||
|
||
#define NLP_CACHE_REVISION_NT_1_0 (0x00010000)
|
||
#define NLP_CACHE_REVISION_NT_1_0A (0x00010001)
|
||
#define NLP_CACHE_REVISION_NT_1_0B (0x00010002)
|
||
#define NLP_CACHE_REVISION (NLP_CACHE_REVISION_NT_1_0B)
|
||
|
||
|
||
|
||
|
||
|
||
//
|
||
// The logon cache may be controlled via a value in the registry.
|
||
// If the registry key does not exist, then this default constant defines
|
||
// how many logon cache entries will be active. The max constant
|
||
// places an upper limit on how many cache entries we will support.
|
||
// If the user specifies more than the max value, we will use the
|
||
// max value instead.
|
||
//
|
||
|
||
#define NLP_DEFAULT_LOGON_CACHE_COUNT (10)
|
||
#define NLP_MAX_LOGON_CACHE_COUNT (50)
|
||
|
||
|
||
|
||
//
|
||
// macros
|
||
//
|
||
|
||
#define AllocateCacheEntry(n) (PLOGON_CACHE_ENTRY)RtlAllocateHeap(MspHeap, 0, n)
|
||
#define FreeCacheEntry(p) RtlFreeHeap(MspHeap, 0, (PVOID)p)
|
||
#define AllocateFromHeap(n) RtlAllocateHeap(MspHeap, 0, n)
|
||
#define FreeToHeap(p) RtlFreeHeap(MspHeap, 0, (PVOID)p)
|
||
|
||
//
|
||
// guard against simultaneous access
|
||
//
|
||
|
||
#define ENTER_CACHE() RtlEnterCriticalSection(&NlpLogonCacheCritSec)
|
||
#define LEAVE_CACHE() RtlLeaveCriticalSection(&NlpLogonCacheCritSec)
|
||
|
||
#define INVALIDATE_HANDLE(handle) (*((PHANDLE)(&handle)) = INVALID_HANDLE_VALUE)
|
||
#define IS_VALID_HANDLE(handle) (handle != INVALID_HANDLE_VALUE)
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// datatypes //
|
||
// //
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
typedef enum _NLP_SET_TIME_HINT {
|
||
NLP_SMALL_TIME,
|
||
NLP_BIG_TIME,
|
||
NLP_NOW_TIME
|
||
} NLP_SET_TIME_HINT, *PNLP_SET_TIME_HINT;
|
||
|
||
#define BIG_PART_1 0x7fffffff // largest positive large int is 63 bits on
|
||
#define BIG_PART_2 0xffffffff
|
||
#define SMALL_PART_1 0x0 // smallest positive large int is 64 bits off
|
||
#define SMALL_PART_2 0x0
|
||
|
||
|
||
//
|
||
// This structure is saved on disk and provides information
|
||
// about the rest of the cache. This structure is in a value
|
||
// named "NL$Control" under the cache registry key.
|
||
//
|
||
|
||
typedef struct _NLP_CACHE_CONTROL {
|
||
|
||
//
|
||
// Revision of the cache on-disk structure
|
||
//
|
||
|
||
ULONG Revision;
|
||
|
||
//
|
||
// The current on-disk size of the cache (number of entries)
|
||
//
|
||
|
||
ULONG Entries;
|
||
|
||
} NLP_CACHE_CONTROL, *PNLP_CACHE_CONTROL;
|
||
|
||
|
||
//
|
||
// This data structure is a single cache table entry (CTE)
|
||
// Each entry in the cache has a corresponding CTE.
|
||
//
|
||
|
||
typedef struct _NLP_CTE {
|
||
|
||
//
|
||
// CTEs are linked on either an invalid list (in any order)
|
||
// or on a valid list (in ascending order of time).
|
||
// This makes it easy to figure out which entry is to be
|
||
// flushed when adding to the cache.
|
||
//
|
||
|
||
LIST_ENTRY Link;
|
||
|
||
|
||
//
|
||
// Time the cache entry was established.
|
||
// This is used to determine which cache
|
||
// entry is the oldest, and therefore will
|
||
// be flushed from the cache first to make
|
||
// room for new entries.
|
||
//
|
||
|
||
LARGE_INTEGER Time;
|
||
|
||
|
||
//
|
||
// This field contains the index of the CTE within the
|
||
// CTE table. This index is used to generate the names
|
||
// of the entrie's secret key and cache key in the registry.
|
||
// This field is valid even if the entry is marked Inactive.
|
||
//
|
||
|
||
ULONG Index;
|
||
|
||
//
|
||
// Normally, we walk the active and inactive lists
|
||
// to find entries. When growing or shrinking the
|
||
// cache, however, it is nice to be able to walk the
|
||
// table using indexes. In this case, it is nice to
|
||
// have a local way of determining whether an entry
|
||
// is on the active or inactive list. This field
|
||
// provides that capability.
|
||
//
|
||
// TRUE ==> on active list
|
||
// FALSE ==> not on active list
|
||
//
|
||
|
||
BOOLEAN Active;
|
||
|
||
|
||
} NLP_CTE, *PNLP_CTE;
|
||
|
||
//
|
||
// This structure is used for keeping track of all information that
|
||
// is stored on backing store.
|
||
//
|
||
|
||
typedef struct _NLP_CACHE_AND_SECRETS {
|
||
PLOGON_CACHE_ENTRY CacheEntry;
|
||
ULONG EntrySize;
|
||
PUNICODE_STRING NewSecret;
|
||
PUNICODE_STRING OldSecret;
|
||
BOOLEAN Active;
|
||
} NLP_CACHE_AND_SECRETS, *PNLP_CACHE_AND_SECRETS;
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Local Prototypes //
|
||
// //
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
NTSTATUS
|
||
NlpInternalCacheInitialize(
|
||
VOID
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpOpenCache( VOID );
|
||
|
||
VOID
|
||
NlpCloseCache( VOID );
|
||
|
||
|
||
NTSTATUS
|
||
NlpGetCacheControlInfo( VOID );
|
||
|
||
|
||
NTSTATUS
|
||
NlpBuildCteTable( VOID );
|
||
|
||
NTSTATUS
|
||
NlpChangeCacheSizeIfNecessary( VOID );
|
||
|
||
NTSTATUS
|
||
NlpWriteCacheControl( VOID );
|
||
|
||
VOID
|
||
NlpMakeCacheEntryName(
|
||
IN ULONG EntryIndex,
|
||
OUT PUNICODE_STRING Name
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpMakeNewCacheEntry(
|
||
ULONG Index
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpEliminateCacheEntry(
|
||
IN ULONG Index
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpConvert1_0To1_0B( VOID );
|
||
|
||
NTSTATUS
|
||
NlpOpen_Nt1_0_Secret( VOID );
|
||
|
||
NTSTATUS
|
||
NlpReadCacheEntryByIndex(
|
||
IN ULONG Index,
|
||
OUT PLOGON_CACHE_ENTRY* CacheEntry,
|
||
OUT PULONG EntrySize
|
||
);
|
||
|
||
VOID
|
||
NlpAddEntryToActiveList(
|
||
IN ULONG Index
|
||
);
|
||
|
||
VOID
|
||
NlpAddEntryToInactiveList(
|
||
IN ULONG Index
|
||
);
|
||
|
||
VOID
|
||
NlpGetFreeEntryIndex(
|
||
OUT PULONG Index
|
||
);
|
||
|
||
|
||
NTSTATUS
|
||
NlpBuildCacheEntry(
|
||
IN PNETLOGON_INTERACTIVE_INFO LogonInfo,
|
||
IN PNETLOGON_VALIDATION_SAM_INFO2 AccountInfo,
|
||
OUT PLOGON_CACHE_ENTRY* ppCacheEntry,
|
||
OUT PULONG pEntryLength
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpOpenCache( VOID );
|
||
|
||
VOID
|
||
NlpCloseCache( VOID );
|
||
|
||
NTSTATUS
|
||
NlpOpenSecret(
|
||
IN ULONG Index
|
||
);
|
||
|
||
VOID
|
||
NlpCloseSecret( VOID );
|
||
|
||
NTSTATUS
|
||
NlpWriteSecret(
|
||
IN PUNICODE_STRING NewSecret,
|
||
IN PUNICODE_STRING OldSecret
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpReadSecret(
|
||
OUT PUNICODE_STRING* NewSecret,
|
||
OUT PUNICODE_STRING* OldSecret
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpMakeSecretPassword(
|
||
OUT PUNICODE_STRING Passwords,
|
||
IN PNT_OWF_PASSWORD NtOwfPassword OPTIONAL,
|
||
IN PLM_OWF_PASSWORD LmOwfPassword OPTIONAL
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpReadCacheEntry(
|
||
IN PUNICODE_STRING DomainName,
|
||
IN PUNICODE_STRING UserName,
|
||
OUT PULONG Index,
|
||
OUT PLOGON_CACHE_ENTRY* CacheEntry,
|
||
OUT PULONG EntrySize
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpWriteCacheEntry(
|
||
IN ULONG Index,
|
||
IN PLOGON_CACHE_ENTRY Entry,
|
||
IN ULONG EntrySize
|
||
);
|
||
|
||
VOID
|
||
NlpCopyAndUpdateAccountInfo(
|
||
IN USHORT Length,
|
||
IN PUNICODE_STRING pUnicodeString,
|
||
IN OUT PUCHAR* pSource,
|
||
IN OUT PUCHAR* pDest
|
||
);
|
||
|
||
VOID
|
||
NlpSetTimeField(
|
||
OUT POLD_LARGE_INTEGER pTimeField,
|
||
IN NLP_SET_TIME_HINT Hint
|
||
);
|
||
|
||
NTSTATUS
|
||
NlpBuildAccountInfo(
|
||
IN PLOGON_CACHE_ENTRY pCacheEntry,
|
||
IN ULONG EntryLength,
|
||
OUT PNETLOGON_VALIDATION_SAM_INFO2* AccountInfo
|
||
);
|
||
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Diagnostic support services prototypes //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
#if DBG
|
||
PCHAR
|
||
DumpOwfPasswordToString(
|
||
OUT PCHAR Buffer,
|
||
IN PLM_OWF_PASSWORD Password
|
||
);
|
||
|
||
VOID
|
||
DumpLogonInfo(
|
||
IN PNETLOGON_LOGON_IDENTITY_INFO LogonInfo
|
||
);
|
||
|
||
char*
|
||
MapWeekday(
|
||
IN CSHORT Weekday
|
||
);
|
||
|
||
VOID
|
||
DumpTime(
|
||
IN LPSTR String,
|
||
IN POLD_LARGE_INTEGER OldTime
|
||
);
|
||
|
||
VOID
|
||
DumpGroupIds(
|
||
IN LPSTR String,
|
||
IN ULONG Count,
|
||
IN PGROUP_MEMBERSHIP GroupIds
|
||
);
|
||
|
||
VOID
|
||
DumpSessKey(
|
||
IN LPSTR String,
|
||
IN PUSER_SESSION_KEY Key
|
||
);
|
||
|
||
VOID
|
||
DumpSid(
|
||
LPSTR String,
|
||
PISID Sid
|
||
);
|
||
|
||
VOID
|
||
DumpAccountInfo(
|
||
IN PNETLOGON_VALIDATION_SAM_INFO2 AccountInfo
|
||
);
|
||
|
||
VOID
|
||
DumpCacheEntry(
|
||
IN ULONG Index,
|
||
IN PLOGON_CACHE_ENTRY pEntry
|
||
);
|
||
|
||
#endif //DBG
|
||
|
||
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// global data //
|
||
// //
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
//
|
||
// This boolean indicates whether or not we have been able to
|
||
// initialize caching yet. It turn out that during authentication
|
||
// package load time, we can't do everything we would like to (like
|
||
// call LSA RPC routines). So, we delay initializing until we can
|
||
// call LSA. All publicly exposed interfaces must check this value
|
||
// before assuming work can be done.
|
||
//
|
||
|
||
BOOLEAN NlpInitializationNotYetPerformed = TRUE;
|
||
|
||
|
||
RTL_CRITICAL_SECTION NlpLogonCacheCritSec;
|
||
|
||
|
||
|
||
HANDLE NlpCacheHandle = (HANDLE) INVALID_HANDLE_VALUE;
|
||
LSA_HANDLE NlpLsaHandle = (LSA_HANDLE) INVALID_HANDLE_VALUE;
|
||
LSA_HANDLE NlpSecretHandle = (LSA_HANDLE) INVALID_HANDLE_VALUE;
|
||
|
||
//
|
||
// control information about the cache (number of entries, etc).
|
||
//
|
||
|
||
NLP_CACHE_CONTROL NlpCacheControl;
|
||
|
||
//
|
||
// This structure is generated and maintained only in memory.
|
||
// It indicates which cache entries are valid and which aren't.
|
||
// It also indicates what time each entry was established so we
|
||
// know which order to discard them in.
|
||
//
|
||
// This field is a pointer to an array of CTEs. The number of CTEs
|
||
// in the array is in NlpCacheControl.Entries. This structure is
|
||
// allocated at initialization time.
|
||
//
|
||
|
||
PNLP_CTE NlpCteTable;
|
||
|
||
|
||
//
|
||
// The Cache Table Entries in NlpCteTable are linked on either an
|
||
// active or inactive list. The entries on the active list are in
|
||
// ascending time order - so the last one on the list is the first
|
||
// one to be discarded when a flush is needed to add a new entry.
|
||
//
|
||
|
||
LIST_ENTRY NlpActiveCtes;
|
||
LIST_ENTRY NlpInactiveCtes;
|
||
|
||
|
||
|
||
|
||
#if DBG
|
||
#ifdef DUMP_CACHE_INFO
|
||
ULONG DumpCacheInfo = 1;
|
||
#else
|
||
ULONG DumpCacheInfo = 0;
|
||
#endif
|
||
#endif
|
||
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Services Exported by this module //
|
||
// //
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlpCacheInitialize(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called to initialize cached logon processing.
|
||
|
||
Unfortunately, there isn't much we can do when we are called.
|
||
(we can't open LSA, for example). So, defer initialization
|
||
until later.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
return RtlInitializeCriticalSection(&NlpLogonCacheCritSec);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpCacheTerminate(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Called when process detaches
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpCacheTerminate\n");
|
||
}
|
||
#endif
|
||
|
||
if (!NlpInitializationNotYetPerformed) {
|
||
NlpCloseCache();
|
||
NlpCloseSecret();
|
||
|
||
if (IS_VALID_HANDLE(NlpLsaHandle)) {
|
||
LsaClose( NlpLsaHandle );
|
||
}
|
||
if (IS_VALID_HANDLE(NlpCacheHandle)) {
|
||
NtClose( NlpCacheHandle );
|
||
}
|
||
|
||
FreeToHeap( NlpCteTable );
|
||
}
|
||
|
||
return RtlDeleteCriticalSection(&NlpLogonCacheCritSec);
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpGetCacheEntry(
|
||
IN PNETLOGON_LOGON_IDENTITY_INFO LogonInfo,
|
||
OUT PNETLOGON_VALIDATION_SAM_INFO2* AccountInfo,
|
||
OUT PCACHE_PASSWORDS Passwords
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
If the user logging on has information stored in the cache,
|
||
then it is retrieved. Also returns the cached password from
|
||
'secret' storage
|
||
|
||
Arguments:
|
||
|
||
LogonInfo - pointer to NETLOGON_IDENTITY_INFO structure which contains
|
||
the domain name, user name for this user
|
||
|
||
AccountInfo - pointer to NETLOGON_VALIDATION_SAM_INFO2 structure to
|
||
receive this user's specific interactive logon information
|
||
|
||
Passwords - pointer to CACHE_PASSWORDS structure to receive passwords
|
||
returned from secret storage
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
*AccountInfo points to a NETLOGON_VALIDATION_SAM_INFO2
|
||
structure. This must be freed by caller
|
||
|
||
*Passwords contain USER_INTERNAL1_INFORMATION structure
|
||
which contains NT OWF password and LM OWF password. These
|
||
must be used to validate the logon
|
||
|
||
Failure = STATUS_LOGON_FAILURE
|
||
The user logging on isn't in the cache.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
PNETLOGON_VALIDATION_SAM_INFO2
|
||
SamInfo;
|
||
|
||
PLOGON_CACHE_ENTRY
|
||
CacheEntry;
|
||
|
||
ULONG
|
||
EntrySize,
|
||
Index;
|
||
|
||
PUNICODE_STRING
|
||
CurrentSecret = NULL,
|
||
OldSecret = NULL;
|
||
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpGetCacheEntry\n");
|
||
DumpLogonInfo(LogonInfo);
|
||
}
|
||
#endif
|
||
|
||
if (NlpInitializationNotYetPerformed) {
|
||
NtStatus = NlpInternalCacheInitialize();
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
}
|
||
|
||
if (NlpCacheControl.Entries == 0) {
|
||
return(STATUS_LOGON_FAILURE);
|
||
}
|
||
|
||
ENTER_CACHE();
|
||
|
||
|
||
|
||
//
|
||
// Find the cache entry and open its secret (if found)
|
||
//
|
||
|
||
NtStatus = NlpReadCacheEntry(&LogonInfo->LogonDomainName,
|
||
&LogonInfo->UserName,
|
||
&Index,
|
||
&CacheEntry,
|
||
&EntrySize);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = NlpBuildAccountInfo(CacheEntry, EntrySize, &SamInfo);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
|
||
NtStatus = NlpReadSecret(&CurrentSecret, &OldSecret);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (CurrentSecret) {
|
||
RtlCopyMemory((PVOID)Passwords,
|
||
(PVOID)CurrentSecret->Buffer,
|
||
(ULONG)CurrentSecret->Length
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// free structure allocated by NlpReadCacheEntry
|
||
//
|
||
|
||
FreeToHeap(CacheEntry);
|
||
}
|
||
|
||
//
|
||
// free structures allocated by NlpReadSecret
|
||
//
|
||
|
||
if (CurrentSecret) {
|
||
MIDL_user_free(CurrentSecret);
|
||
}
|
||
if (OldSecret) {
|
||
MIDL_user_free(OldSecret);
|
||
}
|
||
|
||
|
||
LEAVE_CACHE();
|
||
|
||
*AccountInfo = SamInfo; // undefined if error
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpAddCacheEntry(
|
||
IN PNETLOGON_INTERACTIVE_INFO LogonInfo,
|
||
IN PNETLOGON_VALIDATION_SAM_INFO2 AccountInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Adds this domain:user interactive logon information to the cache.
|
||
|
||
Arguments:
|
||
|
||
LogonInfo - pointer to NETLOGON_INTERACTIVE_INFO structure which contains
|
||
the domain name, user name and password for this user. These
|
||
are what the user typed to WinLogon
|
||
|
||
AccountInfo - pointer to NETLOGON_VALIDATION_SAM_INFO2 structure which
|
||
contains this user's specific interactive logon information
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
AccountInfo successfully added to cache
|
||
|
||
Failure = STATUS_NO_MEMORY
|
||
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
PLOGON_CACHE_ENTRY
|
||
CacheEntry;
|
||
|
||
ULONG
|
||
EntrySize,
|
||
Index;
|
||
|
||
PUNICODE_STRING
|
||
CurrentSecret = NULL,
|
||
OldSecret = NULL;
|
||
|
||
UNICODE_STRING
|
||
Passwords;
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpAddCacheEntry\n");
|
||
DumpLogonInfo(&LogonInfo->Identity);
|
||
DumpAccountInfo(AccountInfo);
|
||
}
|
||
#endif
|
||
|
||
if (NlpInitializationNotYetPerformed) {
|
||
NtStatus = NlpInternalCacheInitialize();
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
}
|
||
|
||
if (NlpCacheControl.Entries == 0) {
|
||
return(STATUS_SUCCESS);
|
||
}
|
||
ENTER_CACHE();
|
||
|
||
//
|
||
// See if this entry already exists in the cache.
|
||
// If so, use the same index.
|
||
//
|
||
|
||
NtStatus = NlpReadCacheEntry(&LogonInfo->Identity.LogonDomainName,
|
||
&LogonInfo->Identity.UserName,
|
||
&Index,
|
||
&CacheEntry,
|
||
&EntrySize
|
||
);
|
||
|
||
//
|
||
// If we didn't find an entry, then we need to allocate an
|
||
// entry.
|
||
//
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
|
||
NlpGetFreeEntryIndex( &Index );
|
||
|
||
} else {
|
||
|
||
//
|
||
// We already have an entry for this user.
|
||
// Discard the structure we got back but
|
||
// use the same index.
|
||
//
|
||
|
||
FreeToHeap( CacheEntry );
|
||
}
|
||
|
||
|
||
//
|
||
// we have to store the information in two parts - the NETLOGON_VALIDATION_SAM_INFO2
|
||
// (or part thereof) goes in the registry in the \Machine\Security\Cache
|
||
// leaf. We also need to store the password the user used to logon. This
|
||
// goes in the LSA 'secret' storage. We have the familiar integrity problem
|
||
// of having to store 2 bits of info in 2 separate locations.
|
||
//
|
||
// Since the secret storage stores a new and an old value together, we will
|
||
// use this to remember the previous password. If we update the password in
|
||
// secret storage, but fail to add the cache entry to the registry, then we
|
||
// back out the password to the previous (old) value
|
||
//
|
||
|
||
NtStatus = NlpBuildCacheEntry(LogonInfo, AccountInfo, &CacheEntry, &EntrySize);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = NlpOpenSecret( Index );
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpReadSecret(&CurrentSecret, &OldSecret);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// LsaSetSecret takes 2 UNICODE_STRING arguments. Convert the
|
||
// encrypted NT One Way Function (OWF) and LM OWF passwords to
|
||
// a single UNICODE_STRING
|
||
//
|
||
|
||
NtStatus = NlpMakeSecretPassword(&Passwords,
|
||
&LogonInfo->NtOwfPassword,
|
||
&LogonInfo->LmOwfPassword
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpWriteSecret(&Passwords, CurrentSecret);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpWriteCacheEntry(Index, CacheEntry, EntrySize);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Try to restore old secrets
|
||
//
|
||
|
||
NtStatus = NlpWriteSecret(CurrentSecret, OldSecret);
|
||
ASSERT(NT_SUCCESS(NtStatus));
|
||
}
|
||
}
|
||
|
||
//
|
||
// free the buffer allocated to store the passwords
|
||
//
|
||
|
||
FreeToHeap(Passwords.Buffer);
|
||
}
|
||
|
||
//
|
||
// free secret strings returned from NlpReadSecret
|
||
//
|
||
|
||
if (CurrentSecret) {
|
||
MIDL_user_free(CurrentSecret);
|
||
}
|
||
if (OldSecret) {
|
||
MIDL_user_free(OldSecret);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NlpCteTable[Index].Time = CacheEntry->Time;
|
||
NlpAddEntryToActiveList( Index );
|
||
}
|
||
|
||
FreeCacheEntry(CacheEntry);
|
||
}
|
||
|
||
|
||
LEAVE_CACHE();
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpDeleteCacheEntry(
|
||
IN PNETLOGON_INTERACTIVE_INFO LogonInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Deletes a user account from the local user account cache, if the corresponding
|
||
entry can be found. We actually just null out the current contents instead of
|
||
destroying the storage - this should save us some time when we next come to
|
||
add an entry to the cache
|
||
|
||
Arguments:
|
||
|
||
LogonInfo - pointer to NETLOGON_INTERACTIVE_INFO structure which contains
|
||
the domain name, user name and password for this user
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
|
||
Failure =
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
PLOGON_CACHE_ENTRY
|
||
CacheEntry;
|
||
|
||
ULONG
|
||
EntrySize,
|
||
Index;
|
||
|
||
|
||
if (NlpInitializationNotYetPerformed) {
|
||
NtStatus = NlpInternalCacheInitialize();
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
}
|
||
|
||
|
||
if (NlpCacheControl.Entries == 0) {
|
||
return(STATUS_SUCCESS);
|
||
}
|
||
ENTER_CACHE();
|
||
|
||
//
|
||
// See if this entry exists in the cache.
|
||
//
|
||
|
||
NtStatus = NlpReadCacheEntry( &LogonInfo->Identity.LogonDomainName,
|
||
&LogonInfo->Identity.UserName,
|
||
&Index,
|
||
&CacheEntry,
|
||
&EntrySize
|
||
);
|
||
|
||
//
|
||
// If we didn't find an entry, then there is nothing to do.
|
||
//
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
LEAVE_CACHE();
|
||
return(STATUS_SUCCESS);
|
||
}
|
||
|
||
//
|
||
// Mark it as invalid.
|
||
//
|
||
|
||
CacheEntry->Valid = FALSE;
|
||
|
||
NtStatus = NlpWriteCacheEntry( Index, CacheEntry, EntrySize );
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Put the CTE entry on the inactive list.
|
||
//
|
||
|
||
NlpAddEntryToInactiveList( Index );
|
||
}
|
||
|
||
//
|
||
// Free the structure returned from NlpReadCacheEntry()
|
||
//
|
||
|
||
FreeToHeap( CacheEntry );
|
||
|
||
|
||
|
||
LEAVE_CACHE();
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpChangeCachePassword(
|
||
IN PUNICODE_STRING DomainName,
|
||
IN PUNICODE_STRING UserName,
|
||
IN PLM_OWF_PASSWORD LmOwfPassword,
|
||
IN PNT_OWF_PASSWORD NtOwfPassword
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Update a cached password to the specified value, if we have
|
||
the specified account cached.
|
||
|
||
Arguments:
|
||
|
||
|
||
DomainName - The name of the domain in which the account exists.
|
||
|
||
UserName - The name of the account whose password is to be changed.
|
||
|
||
LmOwfPassword - The new LM compatible password.
|
||
|
||
NtOwfPassword - The new NT compatible password.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
PLOGON_CACHE_ENTRY
|
||
CacheEntry;
|
||
|
||
ULONG
|
||
EntrySize,
|
||
Index;
|
||
|
||
PUNICODE_STRING
|
||
CurrentSecret = NULL,
|
||
OldSecret = NULL;
|
||
|
||
UNICODE_STRING
|
||
Passwords;
|
||
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpChangeCachePassword\n");
|
||
}
|
||
#endif
|
||
|
||
if (NlpInitializationNotYetPerformed) {
|
||
NtStatus = NlpInternalCacheInitialize();
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
|
||
if (NlpCacheControl.Entries == 0) {
|
||
return;
|
||
}
|
||
|
||
ENTER_CACHE();
|
||
|
||
|
||
NtStatus = NlpReadCacheEntry( DomainName,
|
||
UserName,
|
||
&Index,
|
||
&CacheEntry,
|
||
&EntrySize);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
|
||
NtStatus = NlpOpenSecret( Index );
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = NlpReadSecret(&CurrentSecret, &OldSecret);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = NlpMakeSecretPassword( &Passwords,
|
||
NtOwfPassword,
|
||
LmOwfPassword );
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpWriteSecret(&Passwords, CurrentSecret);
|
||
|
||
//
|
||
// free the buffer allocated to store the passwords
|
||
//
|
||
|
||
FreeToHeap(Passwords.Buffer);
|
||
}
|
||
|
||
//
|
||
// free strings returned by NlpReadSecret
|
||
//
|
||
|
||
if (CurrentSecret) {
|
||
MIDL_user_free(CurrentSecret);
|
||
}
|
||
if (OldSecret) {
|
||
MIDL_user_free(OldSecret);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
//
|
||
// free structure allocated by NlpReadCacheEntry
|
||
//
|
||
|
||
FreeToHeap(CacheEntry);
|
||
}
|
||
|
||
LEAVE_CACHE();
|
||
|
||
return;
|
||
|
||
}
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Services Internal to this module //
|
||
// //
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
NTSTATUS
|
||
NlpInternalCacheInitialize(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called to initialize cached logon processing.
|
||
|
||
This routine will automatically adjust the size of the logon
|
||
cache if necessary to accomodate a new user-specified length
|
||
(specified in the Winlogon part of the registry).
|
||
|
||
NOTE: If called too early, this routine won't be able to call
|
||
LSA's RPC routines. In this case, initialization is
|
||
defered until later.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
OBJECT_ATTRIBUTES
|
||
ObjectAttributes;
|
||
|
||
//DbgPrint("\n\n\n REMEMBER TO TAKE THIS BREAKPOINT OUT BEFORE CHECKIN.\n\n\n");
|
||
//DumpCacheInfo = 1; // Remember to take this out too !!!!!!
|
||
//DbgBreakPoint(); // Remember to take this out before checking
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpCacheInitialize\n");
|
||
}
|
||
#endif
|
||
|
||
|
||
//
|
||
// Upon return from this routine, if logon caching is enabled,
|
||
// the following will be true:
|
||
//
|
||
// A handle to the LsaPolicy object will be open (NlpLsaHandle).
|
||
//
|
||
// A handle to the registry key in which all cache entries
|
||
// are held will be open (NlpCacheHandle).
|
||
//
|
||
// A global structure defining how many cache entries there are
|
||
// will be initialized (NlpCacheControl).
|
||
//
|
||
// The Cache Table Entry table (CTE table) will be initialized
|
||
// (NlpCteTable).
|
||
//
|
||
// The active and inactive CTE lists will be built
|
||
// (NlpActiveCtes and NlpInactiveCtes).
|
||
//
|
||
|
||
|
||
|
||
|
||
|
||
ENTER_CACHE();
|
||
|
||
//
|
||
// Check again if the cache is initialized now that the crit sect is locked.
|
||
//
|
||
|
||
if (NlpInitializationNotYetPerformed) {
|
||
|
||
//
|
||
// Open the local system's policy object
|
||
//
|
||
|
||
InitializeObjectAttributes(&ObjectAttributes,
|
||
NULL, // name
|
||
0,
|
||
0,
|
||
NULL
|
||
);
|
||
NtStatus = LsaOpenPolicy(NULL, // local LSA
|
||
&ObjectAttributes,
|
||
POLICY_VIEW_LOCAL_INFORMATION | POLICY_CREATE_SECRET,
|
||
&NlpLsaHandle
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Successfully, or unsucessfully,
|
||
// The definition of "initialized" is we could call LSA's RPC
|
||
// routines.
|
||
//
|
||
|
||
NlpInitializationNotYetPerformed = FALSE;
|
||
|
||
//
|
||
// Open the registry key containing cache entries.
|
||
// This will remain open.
|
||
//
|
||
|
||
NtStatus = NlpOpenCache();
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Get information on the current cache structure
|
||
// (number of entries, et cetera). This information is
|
||
// placed in a global variable for use throughout this
|
||
// module.
|
||
//
|
||
|
||
NtStatus = NlpGetCacheControlInfo();
|
||
|
||
|
||
//
|
||
// Now build the CTE table
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpBuildCteTable();
|
||
}
|
||
|
||
//
|
||
// If we were successful, then see if we need to change
|
||
// the cache due to new user-specified cache size.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpChangeCacheSizeIfNecessary();
|
||
}
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
NlpCloseCache();
|
||
}
|
||
}
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
LsaClose( NlpLsaHandle );
|
||
}
|
||
}
|
||
|
||
//
|
||
// If we had an error, then set our entry count to zero
|
||
// to prevent using any cache information.
|
||
//
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
NlpCacheControl.Entries = 0;
|
||
}
|
||
|
||
} else {
|
||
NtStatus = STATUS_SUCCESS;
|
||
}
|
||
|
||
LEAVE_CACHE();
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlpBuildCacheEntry(
|
||
IN PNETLOGON_INTERACTIVE_INFO LogonInfo,
|
||
IN PNETLOGON_VALIDATION_SAM_INFO2 AccountInfo,
|
||
OUT PLOGON_CACHE_ENTRY* ppCacheEntry,
|
||
OUT PULONG pEntryLength
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Builds a LOGON_CACHE_ENTRY from a NETLOGON_VALIDATION_SAM_INFO2 structure.
|
||
We only cache those fields that we cannot easily re-invent
|
||
|
||
Arguments:
|
||
|
||
LogonInfo - pointer to NETLOGON_INTERACTIVE_INFO structure containing
|
||
user's name and logon domain name
|
||
|
||
AccountInfo - pointer to NETLOGON_VALIDATION_SAM_INFO2 from successful
|
||
logon
|
||
|
||
ppCacheEntry - pointer to place to return pointer to allocated
|
||
LOGON_CACHE_ENTRY
|
||
|
||
pEntryLength - size of the buffer returned in *ppCacheEntry
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
*ppCacheEntry contains pointer to allocated LOGON_CACHE_ENTRY
|
||
structure
|
||
|
||
Failure = STATUS_NO_MEMORY
|
||
*ppCacheEntry undefined
|
||
|
||
--*/
|
||
|
||
{
|
||
PLOGON_CACHE_ENTRY pEntry;
|
||
ULONG length;
|
||
PCHAR dataptr;
|
||
|
||
#if DBG
|
||
NTSTATUS NtStatus;
|
||
#endif
|
||
|
||
//
|
||
// assumes GROUP_MEMBERSHIP is integral multiple of DWORDs
|
||
//
|
||
|
||
length = ROUND_UP_COUNT(sizeof(LOGON_CACHE_ENTRY), sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(LogonInfo->Identity.LogonDomainName.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(LogonInfo->Identity.UserName.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(AccountInfo->EffectiveName.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(AccountInfo->FullName.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(AccountInfo->LogonScript.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(AccountInfo->ProfilePath.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(AccountInfo->HomeDirectory.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(AccountInfo->HomeDirectoryDrive.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(AccountInfo->LogonDomainName.Length, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(AccountInfo->GroupCount * sizeof(GROUP_MEMBERSHIP), sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(RtlLengthSid(AccountInfo->LogonDomainId), sizeof(ULONG));
|
||
|
||
if (AccountInfo->UserFlags & LOGON_EXTRA_SIDS) {
|
||
if (AccountInfo->SidCount) {
|
||
ULONG i;
|
||
length += ROUND_UP_COUNT(AccountInfo->SidCount * sizeof(ULONG), sizeof(ULONG));
|
||
for (i = 0; i < AccountInfo->SidCount ; i++ ) {
|
||
length += ROUND_UP_COUNT(RtlLengthSid(AccountInfo->ExtraSids[i].Sid), sizeof(ULONG));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
pEntry = AllocateCacheEntry(length);
|
||
if (pEntry == NULL) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
RtlZeroMemory( pEntry, length );
|
||
pEntry->Revision = NLP_CACHE_REVISION;
|
||
NtQuerySystemTime( &pEntry->Time );
|
||
pEntry->Valid = TRUE;
|
||
pEntry->Unused1 = 0;
|
||
pEntry->Unused2 = 0;
|
||
|
||
|
||
dataptr = (PCHAR)(pEntry + 1);
|
||
*pEntryLength = length;
|
||
|
||
ASSERT(!((ULONG)dataptr & (sizeof(ULONG) - 1)));
|
||
|
||
//
|
||
// each of these (unicode) strings and other structures are copied to the
|
||
// end of the fixed LOGON_CACHE_ENTRY structure, each aligned on DWORD
|
||
// boundaries
|
||
//
|
||
|
||
length = pEntry->UserNameLength = LogonInfo->Identity.UserName.Length;
|
||
RtlCopyMemory(dataptr, LogonInfo->Identity.UserName.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
length = pEntry->DomainNameLength = LogonInfo->Identity.LogonDomainName.Length;
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, LogonInfo->Identity.LogonDomainName.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
length = pEntry->EffectiveNameLength = AccountInfo->EffectiveName.Length;
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, AccountInfo->EffectiveName.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
length = pEntry->FullNameLength = AccountInfo->FullName.Length;
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, AccountInfo->FullName.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
length = pEntry->LogonScriptLength = AccountInfo->LogonScript.Length;
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, AccountInfo->LogonScript.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
length = pEntry->ProfilePathLength = AccountInfo->ProfilePath.Length;
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, AccountInfo->ProfilePath.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
length = pEntry->HomeDirectoryLength = AccountInfo->HomeDirectory.Length;
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, AccountInfo->HomeDirectory.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
length = pEntry->HomeDirectoryDriveLength = AccountInfo->HomeDirectoryDrive.Length;
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, AccountInfo->HomeDirectoryDrive.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
pEntry->UserId = AccountInfo->UserId;
|
||
pEntry->PrimaryGroupId = AccountInfo->PrimaryGroupId;
|
||
|
||
length = pEntry->GroupCount = AccountInfo->GroupCount;
|
||
length *= sizeof(GROUP_MEMBERSHIP);
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, AccountInfo->GroupIds, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
length = pEntry->LogonDomainNameLength = AccountInfo->LogonDomainName.Length;
|
||
if (length) {
|
||
RtlCopyMemory(dataptr, AccountInfo->LogonDomainName.Buffer, length);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
}
|
||
|
||
if (AccountInfo->UserFlags & LOGON_EXTRA_SIDS) {
|
||
length = pEntry->SidCount = AccountInfo->SidCount;
|
||
length *= sizeof(ULONG);
|
||
if (length) {
|
||
ULONG i, sidLength;
|
||
PULONG sidAttributes = (PULONG) dataptr;
|
||
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
//
|
||
// Now copy over all the SIDs
|
||
//
|
||
|
||
for (i = 0; i < AccountInfo->SidCount ; i++ ) {
|
||
sidAttributes[i] = AccountInfo->ExtraSids[i].Attributes;
|
||
sidLength = RtlLengthSid(AccountInfo->ExtraSids[i].Sid);
|
||
RtlCopySid(sidLength,(PSID) dataptr,AccountInfo->ExtraSids[i].Sid);
|
||
dataptr = ROUND_UP_POINTER(dataptr + sidLength, sizeof(ULONG));
|
||
}
|
||
pEntry->SidLength = (ULONG) (dataptr - (PCHAR) sidAttributes);
|
||
} else {
|
||
pEntry->SidLength = 0;
|
||
}
|
||
} else {
|
||
pEntry->SidCount = 0;
|
||
pEntry->SidLength = 0;
|
||
}
|
||
|
||
pEntry->LogonDomainIdLength = (USHORT) RtlLengthSid(AccountInfo->LogonDomainId);
|
||
|
||
#if DBG
|
||
NtStatus = RtlCopySid(pEntry->LogonDomainIdLength,
|
||
(PSID)dataptr,
|
||
AccountInfo->LogonDomainId
|
||
);
|
||
ASSERT(NT_SUCCESS(NtStatus));
|
||
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpBuildCacheEntry:\n");
|
||
DumpCacheEntry(999, pEntry);
|
||
}
|
||
#else
|
||
RtlCopySid(*pEntryLength - ((ULONG)dataptr - (ULONG)pEntry),
|
||
(PSID)dataptr,
|
||
AccountInfo->LogonDomainId
|
||
);
|
||
#endif
|
||
|
||
|
||
*ppCacheEntry = pEntry;
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("BuildCacheEntry:\n");
|
||
DumpCacheEntry(999,pEntry);
|
||
}
|
||
#endif
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpOpenCache( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Opens the registry node for read or write (depending on Switch) and opens
|
||
the secret storage in the same mode. If successful, the NlpCacheHandle
|
||
is valid.
|
||
|
||
Arguments:
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
NlpCacheHandle contains handle to use for reading/writing
|
||
registry
|
||
|
||
Failure =
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS NtStatus;
|
||
ULONG Disposition;
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
UNICODE_STRING ObjectName;
|
||
|
||
ObjectName.Length = ObjectName.MaximumLength = CACHE_NAME_SIZE;
|
||
ObjectName.Buffer = CACHE_NAME;
|
||
|
||
InitializeObjectAttributes(&ObjectAttributes,
|
||
&ObjectName,
|
||
OBJ_CASE_INSENSITIVE,
|
||
0, // RootDirectory
|
||
NULL // BUGBUG - put security descriptor here
|
||
);
|
||
NtStatus = NtCreateKey(&NlpCacheHandle,
|
||
(KEY_WRITE | KEY_READ),
|
||
&ObjectAttributes,
|
||
CACHE_TITLE_INDEX,
|
||
NULL, // class name
|
||
0, // create options
|
||
&Disposition
|
||
);
|
||
|
||
return NtStatus;
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpCloseCache( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Closes handles opened by NlpOpenCache
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
#if DBG
|
||
NTSTATUS NtStatus;
|
||
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("CloseCache: Closing NlpCacheHandle (%#08x)\n", NlpCacheHandle);
|
||
}
|
||
|
||
if (IS_VALID_HANDLE(NlpCacheHandle)) {
|
||
NtStatus = NtClose(NlpCacheHandle);
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("CloseCache: NtClose returns %#08x\n", NtStatus);
|
||
}
|
||
ASSERT(NT_SUCCESS(NtStatus));
|
||
INVALIDATE_HANDLE(NlpCacheHandle);
|
||
}
|
||
#else
|
||
if (IS_VALID_HANDLE(NlpCacheHandle)) {
|
||
NtClose(NlpCacheHandle);
|
||
INVALIDATE_HANDLE(NlpCacheHandle);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpOpenSecret(
|
||
IN ULONG Index
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Opens a cache entry's secret storage object for read (in order to LsaQuerySecret) and
|
||
write (in order to LsaSetSecret). If successful, the handle value
|
||
is placed in the global variable NlpSecretHandle.
|
||
|
||
If the secret does not exist, it will be created.
|
||
|
||
|
||
Arguments:
|
||
|
||
Index - The index of the entry being opened. This is used to build
|
||
a name of the object.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
NlpSecretHandle can be used to read/write secret storage
|
||
|
||
Failure =
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
ValueName;
|
||
|
||
WCHAR
|
||
NameBuffer[32];
|
||
|
||
|
||
//
|
||
// Close previous handle if necessary
|
||
//
|
||
|
||
if (IS_VALID_HANDLE(NlpSecretHandle)) {
|
||
LsaClose( NlpSecretHandle );
|
||
}
|
||
|
||
|
||
ValueName.Buffer = &NameBuffer[0];
|
||
ValueName.MaximumLength = 32;
|
||
ValueName.Length = 0;
|
||
NlpMakeCacheEntryName( Index, &ValueName );
|
||
|
||
|
||
|
||
|
||
NtStatus = LsaOpenSecret(NlpLsaHandle,
|
||
&ValueName,
|
||
SECRET_QUERY_VALUE | SECRET_SET_VALUE,
|
||
&NlpSecretHandle
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
if (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
|
||
NtStatus = LsaCreateSecret(NlpLsaHandle,
|
||
&ValueName,
|
||
SECRET_SET_VALUE | SECRET_QUERY_VALUE,
|
||
&NlpSecretHandle
|
||
);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
INVALIDATE_HANDLE(NlpSecretHandle);
|
||
}
|
||
} else {
|
||
INVALIDATE_HANDLE(NlpSecretHandle);
|
||
}
|
||
}
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpCloseSecret( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Closes the handles opened via NlpOpenSecret
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
if (IS_VALID_HANDLE(NlpSecretHandle)) {
|
||
NtStatus = LsaClose(NlpSecretHandle);
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("CloseSecret: LsaClose returns %#08x\n", NtStatus);
|
||
}
|
||
#endif
|
||
ASSERT(NT_SUCCESS(NtStatus));
|
||
INVALIDATE_HANDLE(NlpSecretHandle);
|
||
}
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpWriteSecret(
|
||
IN PUNICODE_STRING NewSecret,
|
||
IN PUNICODE_STRING OldSecret
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Writes the password (and optionally the previous password) to the LSA
|
||
secret storage
|
||
|
||
Arguments:
|
||
|
||
NewSecret - pointer to UNICODE_STRING containing current password
|
||
OldSecret - pointer to UNICODE_STRING containing previous password
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success =
|
||
Failure =
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
return LsaSetSecret(NlpSecretHandle, NewSecret, OldSecret);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpReadSecret(
|
||
OUT PUNICODE_STRING* NewSecret,
|
||
OUT PUNICODE_STRING* OldSecret
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Reads the new and old secrets (UNICODE_STRINGs) for the
|
||
currently open LSA secret
|
||
|
||
The Lsa routine returns us pointers to UNICODE strings
|
||
|
||
Arguments:
|
||
|
||
NewSecret - pointer to returned pointer to UNICODE_STRING containing
|
||
most recent password (if any)
|
||
|
||
OldSecret - pointer to returned pointer to UNICODE_STRING containing
|
||
previous password (if any)
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success
|
||
Failure
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
LARGE_INTEGER
|
||
NewTime,
|
||
OldTime;
|
||
|
||
NtStatus = LsaQuerySecret(NlpSecretHandle,
|
||
NewSecret,
|
||
&NewTime,
|
||
OldSecret,
|
||
&OldTime
|
||
);
|
||
#if DBG
|
||
{
|
||
char newNt[80];
|
||
char newLm[80];
|
||
char oldNt[80];
|
||
char oldLm[80];
|
||
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpReadSecret: NewSecret.Nt = \"%s\"\n"
|
||
" NewSecret.Lm = \"%s\"\n"
|
||
" OldSecret.Nt = \"%s\"\n"
|
||
" OldSecret.Lm = \"%s\"\n",
|
||
*NewSecret
|
||
? DumpOwfPasswordToString(newNt, (PLM_OWF_PASSWORD)((*NewSecret)->Buffer))
|
||
: "",
|
||
*NewSecret
|
||
? DumpOwfPasswordToString(newLm, (PLM_OWF_PASSWORD)((*NewSecret)->Buffer)+1)
|
||
: "",
|
||
*OldSecret
|
||
? DumpOwfPasswordToString(oldNt, (PLM_OWF_PASSWORD)((*OldSecret)->Buffer))
|
||
: "",
|
||
*OldSecret
|
||
? DumpOwfPasswordToString(oldLm, (PLM_OWF_PASSWORD)((*OldSecret)->Buffer)+1)
|
||
: ""
|
||
);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
return NtStatus;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpMakeSecretPassword(
|
||
OUT PUNICODE_STRING Passwords,
|
||
IN PNT_OWF_PASSWORD NtOwfPassword OPTIONAL,
|
||
IN PLM_OWF_PASSWORD LmOwfPassword OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Converts a (fixed length structure) NT_OWF_PASSWORD and a LM_OWF_PASSWORD
|
||
to a UNICODE_STRING. Allocates memory for the unicode string in this function
|
||
|
||
The calling function must free up the string buffer allocated in this routine.
|
||
The caller uses FreeToHeap (RtlFreeHeap)
|
||
|
||
Arguments:
|
||
|
||
Passwords - returned UNICODE_STRING which actually contains a
|
||
CACHE_PASSWORDS structure
|
||
NtOwfPassword - pointer to encrypted, fixed-length NT password
|
||
LmOwfPassword - pointer to encrypted, fixed-length LM password
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
Passwords created OK
|
||
|
||
Failure = STATUS_NO_MEMORY
|
||
Not enough storage to create Passwords
|
||
|
||
--*/
|
||
|
||
{
|
||
PCACHE_PASSWORDS pwd;
|
||
|
||
pwd = (PCACHE_PASSWORDS)AllocateFromHeap(sizeof(*pwd));
|
||
if (pwd == NULL) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
//
|
||
// concatenate the fixed length NT_OWF_PASSWORD and LM_OWF_PASSWORD structures
|
||
// into a buffer which we then use as a UNICODE_STRING
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(NtOwfPassword)) {
|
||
RtlCopyMemory((PVOID)&pwd->SecretPasswords.NtOwfPassword,
|
||
(PVOID)NtOwfPassword,
|
||
sizeof(*NtOwfPassword)
|
||
);
|
||
pwd->SecretPasswords.NtPasswordPresent = TRUE;
|
||
} else {
|
||
RtlZeroMemory((PVOID)&pwd->SecretPasswords.NtOwfPassword,
|
||
sizeof(pwd->SecretPasswords.NtOwfPassword)
|
||
);
|
||
pwd->SecretPasswords.NtPasswordPresent = FALSE;
|
||
}
|
||
|
||
if (ARGUMENT_PRESENT(LmOwfPassword)) {
|
||
RtlCopyMemory((PVOID)&pwd->SecretPasswords.LmOwfPassword,
|
||
(PVOID)LmOwfPassword,
|
||
sizeof(*LmOwfPassword)
|
||
);
|
||
pwd->SecretPasswords.LmPasswordPresent = TRUE;
|
||
} else {
|
||
RtlZeroMemory((PVOID)&pwd->SecretPasswords.LmOwfPassword,
|
||
sizeof(pwd->SecretPasswords.LmOwfPassword)
|
||
);
|
||
pwd->SecretPasswords.LmPasswordPresent = FALSE;
|
||
}
|
||
|
||
Passwords->Length = Passwords->MaximumLength = sizeof(*pwd);
|
||
Passwords->Buffer = (PWSTR)pwd;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpReadCacheEntry(
|
||
IN PUNICODE_STRING DomainName,
|
||
IN PUNICODE_STRING UserName,
|
||
OUT PULONG Index,
|
||
OUT PLOGON_CACHE_ENTRY* CacheEntry,
|
||
OUT PULONG EntrySize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Searches the active entry list for a domain\username
|
||
match in the cache. If a match is found, then it
|
||
is returned.
|
||
|
||
Arguments:
|
||
|
||
DomainName - The name of the domain in which the account exists.
|
||
|
||
UserName - The name of the account whose password is to be changed.
|
||
|
||
Index - receives the index of the entry retrieved.
|
||
|
||
CacheEntry - pointer to place to return pointer to LOGON_CACHE_ENTRY
|
||
|
||
EntrySize - size of returned LOGON_CACHE_ENTRY
|
||
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
*ppEntry points to allocated LOGON_CACHE_ENTRY
|
||
*EntrySize is size of returned data
|
||
|
||
Failure = STATUS_NO_MEMORY
|
||
Couldn't allocate buffer for LOGON_CACHE_ENTRY
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
CachedUser,
|
||
CachedDomain;
|
||
|
||
PNLP_CTE
|
||
Next;
|
||
|
||
|
||
//
|
||
// Walk the active list looking for a domain/name match
|
||
//
|
||
|
||
Next = (PNLP_CTE)NlpActiveCtes.Flink;
|
||
|
||
while (Next != (PNLP_CTE)&NlpActiveCtes) {
|
||
|
||
NtStatus = NlpReadCacheEntryByIndex( Next->Index,
|
||
CacheEntry,
|
||
EntrySize
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
break; // out of while-loop
|
||
}
|
||
|
||
|
||
|
||
CachedUser.Length = (*CacheEntry)->UserNameLength;
|
||
CachedUser.MaximumLength = (*CacheEntry)->UserNameLength;
|
||
|
||
CachedDomain.Length = (*CacheEntry)->DomainNameLength;
|
||
CachedDomain.MaximumLength = (*CacheEntry)->DomainNameLength;
|
||
|
||
if ((*CacheEntry)->Revision == NLP_CACHE_REVISION) {
|
||
CachedUser.Buffer = (PWSTR)((*CacheEntry) + 1);
|
||
} else {
|
||
ASSERT((*CacheEntry)->Revision == NLP_CACHE_REVISION_NT_1_0A);
|
||
CachedUser.Buffer = (PWSTR) ((PBYTE) *CacheEntry + sizeof(LOGON_CACHE_ENTRY_1_0A));
|
||
}
|
||
|
||
CachedDomain.Buffer = (PWSTR)((LPBYTE)CachedUser.Buffer +
|
||
ROUND_UP_COUNT((*CacheEntry)->UserNameLength, sizeof(ULONG)));
|
||
|
||
if (RtlEqualUnicodeString(UserName, &CachedUser, (BOOLEAN) TRUE ) &&
|
||
RtlEqualDomainName(DomainName, &CachedDomain )) {
|
||
|
||
|
||
//
|
||
// found it !
|
||
//
|
||
|
||
break; // out of while-loop
|
||
|
||
}
|
||
|
||
//
|
||
// Not the right entry, free the registry structure
|
||
// and go on to the next one.
|
||
//
|
||
|
||
FreeToHeap( (*CacheEntry) );
|
||
|
||
Next = (PNLP_CTE)(Next->Link.Flink);
|
||
}
|
||
|
||
if (Next != (PNLP_CTE)&NlpActiveCtes && NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// We found a match - Open the corresponding secret
|
||
//
|
||
|
||
(*Index) = Next->Index;
|
||
NtStatus = NlpOpenSecret(Next->Index);
|
||
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
FreeToHeap( (*CacheEntry) );
|
||
return(NtStatus);
|
||
}
|
||
} else {
|
||
NtStatus = STATUS_LOGON_FAILURE;
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpWriteCacheEntry(
|
||
IN ULONG Index,
|
||
IN PLOGON_CACHE_ENTRY Entry,
|
||
IN ULONG EntrySize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Writes a LOGON_CACHE_ENTRY to the registry cache.
|
||
|
||
It is the caller's responsibility to place the corresponding
|
||
CTE table entry in the correct active/inactive list.
|
||
|
||
Arguments:
|
||
Index - Index of entry to write out.
|
||
|
||
Entry - pointer to LOGON_CACHE_ENTRY to write to cache
|
||
|
||
EntrySize - size of this entry (in bytes (must be multiple of 4 thoough))
|
||
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
The LOGON_CACHE_ENTRY is now in the registry (hopefully
|
||
on disk)
|
||
|
||
Failure =
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
ValueName;
|
||
|
||
WCHAR
|
||
NameBuffer[32];
|
||
|
||
ValueName.MaximumLength = 32;
|
||
ValueName.Length = 0;
|
||
ValueName.Buffer = &NameBuffer[0];
|
||
NlpMakeCacheEntryName( Index, &ValueName );
|
||
|
||
NtStatus = NtSetValueKey(NlpCacheHandle,
|
||
&ValueName,
|
||
0, // TitleIndex
|
||
REG_BINARY, // Type
|
||
(PVOID)Entry,
|
||
EntrySize
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtFlushKey(NlpCacheHandle);
|
||
}
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpCopyAndUpdateAccountInfo(
|
||
IN USHORT Length,
|
||
IN PUNICODE_STRING pUnicodeString,
|
||
IN OUT PUCHAR* pSource,
|
||
IN OUT PUCHAR* pDest
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Updates a UNICODE_STRING structure and copies the associated buffer to
|
||
*pDest, if Length is non-zero
|
||
|
||
Arguments:
|
||
|
||
Length - length of UNICODE_STRING.Buffer to copy
|
||
pUnicodeString - pointer to UNICODE_STRING structure to update
|
||
pSource - pointer to pointer to source WCHAR string
|
||
pDest - pointer to pointer to place to copy WCHAR string
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
if string was copied, *Source and *Dest are updated to point to the next
|
||
naturally aligned (DWORD) positions in the input and output buffers resp.
|
||
|
||
--*/
|
||
|
||
{
|
||
PUCHAR source = *pSource;
|
||
PUCHAR dest = *pDest;
|
||
|
||
pUnicodeString->Length = Length;
|
||
pUnicodeString->MaximumLength = Length;
|
||
pUnicodeString->Buffer = (PWSTR)dest;
|
||
if (Length) {
|
||
RtlCopyMemory(dest, source, Length);
|
||
*pSource = ROUND_UP_POINTER(source + Length, sizeof(ULONG));
|
||
*pDest = ROUND_UP_POINTER(dest + Length, sizeof(ULONG));
|
||
}
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpSetTimeField(
|
||
OUT POLD_LARGE_INTEGER pTimeField,
|
||
IN NLP_SET_TIME_HINT Hint
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Sets a LARGE_INTEGER time field to one of 3 values:
|
||
NLP_BIG_TIME = maximum positive large integer (0x7fffffffffffffff)
|
||
NLP_SMALL_TIME = smallest positive large integer (0)
|
||
NLP_NOW_TIME = current system time
|
||
|
||
Arguments:
|
||
|
||
pTimeField - pointer to LARGE_INTEGER structure to update
|
||
Hint - NLP_BIG_TIME, NLP_SMALL_TIME or NLP_NOW_TIME
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
LARGE_INTEGER Time;
|
||
|
||
switch (Hint) {
|
||
case NLP_SMALL_TIME:
|
||
pTimeField->HighPart = SMALL_PART_1;
|
||
pTimeField->LowPart = SMALL_PART_2;
|
||
break;
|
||
|
||
case NLP_BIG_TIME:
|
||
pTimeField->HighPart = BIG_PART_1;
|
||
pTimeField->LowPart = BIG_PART_2;
|
||
break;
|
||
|
||
case NLP_NOW_TIME:
|
||
NtQuerySystemTime(&Time);
|
||
NEW_TO_OLD_LARGE_INTEGER( Time, (*pTimeField) );
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpBuildAccountInfo(
|
||
IN PLOGON_CACHE_ENTRY pCacheEntry,
|
||
IN ULONG EntryLength,
|
||
OUT PNETLOGON_VALIDATION_SAM_INFO2* AccountInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Performs the reverse of NlpBuildCacheEntry - creates a NETLOGON_VALIDATION_SAM_INFO2
|
||
structure from a cache entry
|
||
|
||
Arguments:
|
||
|
||
pCacheEntry - pointer to LOGON_CACHE_ENTRY
|
||
|
||
EntryLength - inclusive size of *pCacheEntry, including variable data
|
||
|
||
AccountInfo - pointer to place to create NETLOGON_VALIDATION_SAM_INFO2
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
|
||
Failure = STATUS_NO_MEMORY
|
||
|
||
--*/
|
||
|
||
{
|
||
PNETLOGON_VALIDATION_SAM_INFO2 pSamInfo;
|
||
PUCHAR source;
|
||
PUCHAR dest;
|
||
ULONG length;
|
||
ULONG sidLength;
|
||
ULONG commonBits;
|
||
WCHAR computerName[CNLEN+1];
|
||
ULONG computerNameLength;
|
||
#if DBG
|
||
BOOL ok;
|
||
#endif
|
||
|
||
computerName[0] = 0;
|
||
computerNameLength = sizeof(computerName);
|
||
|
||
//
|
||
// will GetComputerName ever fail??? Its only used to fake the logon
|
||
// server name when we logon using the cached information, so its
|
||
// probably ok to use a NULL string if GetComputerName phones in sick
|
||
//
|
||
|
||
#if DBG
|
||
ok =
|
||
#endif
|
||
|
||
GetComputerName((LPWSTR)computerName, (LPDWORD)&computerNameLength);
|
||
|
||
#if DBG
|
||
ASSERT(ok);
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("ComputerName = %ws, length = %d\n", computerName, computerNameLength);
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// commonBits is the size of the variable data area common to both the
|
||
// LOGON_CACHE_ENTRY and NETLOGON_VALIDATION_SAM_INFO2 structures
|
||
//
|
||
|
||
commonBits = ROUND_UP_COUNT(pCacheEntry->EffectiveNameLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->FullNameLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->LogonScriptLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->ProfilePathLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->HomeDirectoryLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->HomeDirectoryDriveLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->GroupCount * sizeof(GROUP_MEMBERSHIP), sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->LogonDomainNameLength, sizeof(ULONG))
|
||
;
|
||
if (pCacheEntry->Revision == NLP_CACHE_REVISION) {
|
||
|
||
commonBits +=
|
||
ROUND_UP_COUNT(sizeof(NETLOGON_SID_AND_ATTRIBUTES) * pCacheEntry->SidCount, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->SidLength, sizeof(ULONG))
|
||
;
|
||
sidLength = pCacheEntry->LogonDomainIdLength;
|
||
|
||
} else {
|
||
|
||
ASSERT(pCacheEntry->Revision == NLP_CACHE_REVISION_NT_1_0A);
|
||
|
||
//
|
||
// sidLength is the size of the SID copied to the LOGON_CACHE_ENTRY
|
||
// structure
|
||
//
|
||
|
||
sidLength = EntryLength - (sizeof(LOGON_CACHE_ENTRY_1_0A)
|
||
+ ROUND_UP_COUNT(pCacheEntry->UserNameLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->DomainNameLength, sizeof(ULONG))
|
||
+ commonBits
|
||
);
|
||
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// length is the required amount of buffer in which to build a working
|
||
// NETLOGON_VALIDATION_SAM_INFO2 structure
|
||
//
|
||
|
||
length = ROUND_UP_COUNT(sizeof(NETLOGON_VALIDATION_SAM_INFO2), sizeof(ULONG))
|
||
+ commonBits
|
||
+ sidLength
|
||
+ computerNameLength * sizeof(WCHAR)
|
||
;
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpBuildAccountInfo: %d bytes required\n", length);
|
||
}
|
||
#endif
|
||
|
||
pSamInfo = (PNETLOGON_VALIDATION_SAM_INFO2)MIDL_user_allocate(length);
|
||
if (pSamInfo == NULL) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
//
|
||
// point source at the first string to be copied out of the variable length
|
||
// data area at the end of the cache entry
|
||
//
|
||
|
||
if (pCacheEntry->Revision == NLP_CACHE_REVISION) {
|
||
source = (PUCHAR)(pCacheEntry + 1);
|
||
} else {
|
||
ASSERT(pCacheEntry->Revision == NLP_CACHE_REVISION_NT_1_0A);
|
||
|
||
source = (PUCHAR) pCacheEntry + sizeof(LOGON_CACHE_ENTRY_1_0A);
|
||
}
|
||
|
||
source += ROUND_UP_COUNT(pCacheEntry->UserNameLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(pCacheEntry->DomainNameLength, sizeof(ULONG));
|
||
|
||
//
|
||
// point dest at the first (aligned) byte at the start of the variable data
|
||
// area at the end of the sam info structure
|
||
//
|
||
|
||
dest = (PUCHAR)(pSamInfo + 1);
|
||
|
||
//
|
||
// pull out the variable length data from the end of the LOGON_CACHE_ENTRY
|
||
// and stick them at the end of the NETLOGON_VALIDATION_SAM_INFO2 structure.
|
||
// These must be copied out IN THE SAME ORDER as NlpBuildCacheEntry put them
|
||
// in. If we want to change the order of things in the buffer, the order
|
||
// must be changed in both routines (this & NlpBuildCacheEntry)
|
||
//
|
||
|
||
//
|
||
// create the UNICODE_STRING structures in the NETLOGON_VALIDATION_SAM_INFO2
|
||
// structure and copy the strings to the end of the buffer. 0 length strings
|
||
// will get a pointer which should be ignored
|
||
//
|
||
|
||
NlpCopyAndUpdateAccountInfo(pCacheEntry->EffectiveNameLength,
|
||
&pSamInfo->EffectiveName,
|
||
&source,
|
||
&dest
|
||
);
|
||
|
||
NlpCopyAndUpdateAccountInfo(pCacheEntry->FullNameLength,
|
||
&pSamInfo->FullName,
|
||
&source,
|
||
&dest
|
||
);
|
||
|
||
NlpCopyAndUpdateAccountInfo(pCacheEntry->LogonScriptLength,
|
||
&pSamInfo->LogonScript,
|
||
&source,
|
||
&dest
|
||
);
|
||
|
||
NlpCopyAndUpdateAccountInfo(pCacheEntry->ProfilePathLength,
|
||
&pSamInfo->ProfilePath,
|
||
&source,
|
||
&dest
|
||
);
|
||
|
||
NlpCopyAndUpdateAccountInfo(pCacheEntry->HomeDirectoryLength,
|
||
&pSamInfo->HomeDirectory,
|
||
&source,
|
||
&dest
|
||
);
|
||
|
||
NlpCopyAndUpdateAccountInfo(pCacheEntry->HomeDirectoryDriveLength,
|
||
&pSamInfo->HomeDirectoryDrive,
|
||
&source,
|
||
&dest
|
||
);
|
||
|
||
//
|
||
// copy the group membership array
|
||
//
|
||
|
||
pSamInfo->GroupIds = (PGROUP_MEMBERSHIP)dest;
|
||
length = pCacheEntry->GroupCount * sizeof(GROUP_MEMBERSHIP);
|
||
RtlCopyMemory(dest, source, length);
|
||
dest = ROUND_UP_POINTER(dest + length, sizeof(ULONG));
|
||
source = ROUND_UP_POINTER(source + length, sizeof(ULONG));
|
||
|
||
//
|
||
// final UNICODE_STRING from LOGON_CACHE_ENTRY. Reorganize this to:
|
||
// strings, groups, SID?
|
||
//
|
||
|
||
NlpCopyAndUpdateAccountInfo(pCacheEntry->LogonDomainNameLength,
|
||
&pSamInfo->LogonDomainName,
|
||
&source,
|
||
&dest
|
||
);
|
||
|
||
|
||
//
|
||
// Copy all the SIDs
|
||
//
|
||
|
||
if (pCacheEntry->Revision == NLP_CACHE_REVISION) {
|
||
pSamInfo->SidCount = pCacheEntry->SidCount;
|
||
|
||
if (pCacheEntry->SidCount) {
|
||
ULONG i, sidLength;
|
||
PULONG SidAttributes = (PULONG) source;
|
||
source = ROUND_UP_POINTER(source + pCacheEntry->SidCount * sizeof(ULONG), sizeof(ULONG));
|
||
|
||
pSamInfo->ExtraSids = (PNETLOGON_SID_AND_ATTRIBUTES) dest;
|
||
dest = ROUND_UP_POINTER(dest + pCacheEntry->SidCount * sizeof(NETLOGON_SID_AND_ATTRIBUTES), sizeof(ULONG));
|
||
|
||
for (i = 0; i < pCacheEntry->SidCount ; i++ ) {
|
||
pSamInfo->ExtraSids[i].Attributes = SidAttributes[i];
|
||
sidLength = RtlLengthSid((PSID) source);
|
||
RtlCopySid(sidLength, (PSID) dest, (PSID) source);
|
||
pSamInfo->ExtraSids[i].Sid = (PSID) dest;
|
||
dest = ROUND_UP_POINTER(dest + sidLength, sizeof(ULONG));
|
||
source = ROUND_UP_POINTER(source + sidLength, sizeof(ULONG));
|
||
}
|
||
|
||
ASSERT((ULONG) (source - (PCHAR) SidAttributes) == pCacheEntry->SidLength);
|
||
|
||
} else {
|
||
pSamInfo->ExtraSids = NULL;
|
||
}
|
||
} else {
|
||
pSamInfo->ExtraSids = NULL;
|
||
pSamInfo->SidCount = 0;
|
||
}
|
||
|
||
|
||
//
|
||
// copy the LogonDomainId SID
|
||
//
|
||
|
||
RtlCopySid(sidLength, (PSID)dest, (PSID)source);
|
||
pSamInfo->LogonDomainId = (PSID)dest;
|
||
dest = ROUND_UP_POINTER(dest + sidLength, sizeof(ULONG));
|
||
|
||
//
|
||
// copy the non-variable fields
|
||
//
|
||
|
||
pSamInfo->UserId = pCacheEntry->UserId;
|
||
pSamInfo->PrimaryGroupId = pCacheEntry->PrimaryGroupId;
|
||
pSamInfo->GroupCount = pCacheEntry->GroupCount;
|
||
|
||
//
|
||
// finally, invent some fields
|
||
//
|
||
|
||
NlpSetTimeField(&pSamInfo->LogonTime, NLP_NOW_TIME);
|
||
NlpSetTimeField(&pSamInfo->LogoffTime, NLP_BIG_TIME);
|
||
NlpSetTimeField(&pSamInfo->KickOffTime, NLP_BIG_TIME);
|
||
NlpSetTimeField(&pSamInfo->PasswordLastSet, NLP_SMALL_TIME);
|
||
NlpSetTimeField(&pSamInfo->PasswordCanChange, NLP_BIG_TIME);
|
||
NlpSetTimeField(&pSamInfo->PasswordMustChange, NLP_BIG_TIME);
|
||
|
||
pSamInfo->LogonCount = 0;
|
||
pSamInfo->BadPasswordCount = 0;
|
||
pSamInfo->UserFlags = LOGON_EXTRA_SIDS;
|
||
|
||
RtlZeroMemory(&pSamInfo->UserSessionKey, sizeof(pSamInfo->UserSessionKey));
|
||
|
||
//
|
||
// final UNICODE_STRING. This one from stack. Note that we have finished
|
||
// with source
|
||
//
|
||
|
||
source = (PUCHAR)computerName;
|
||
NlpCopyAndUpdateAccountInfo((USHORT)(computerNameLength * sizeof(WCHAR)),
|
||
&pSamInfo->LogonServer,
|
||
&source,
|
||
&dest
|
||
);
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpBuildAccountInfo:\n");
|
||
DumpAccountInfo(pSamInfo);
|
||
}
|
||
#endif
|
||
|
||
*AccountInfo = pSamInfo;
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpGetCacheControlInfo( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function retrieves the cache control information from the
|
||
registry. This information is placed in global data for use
|
||
throughout this module. The Cache Table Entry table will also
|
||
be initialized.
|
||
|
||
If this routine returns success, then it may be assumed that
|
||
everything completed successfully.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
CacheControlValueName;
|
||
|
||
ULONG
|
||
RequiredSize;
|
||
|
||
PKEY_VALUE_PARTIAL_INFORMATION
|
||
RegInfo;
|
||
|
||
|
||
//
|
||
// read the current control info, if it is there.
|
||
// If it is not there, then we may be dealing with a down-level
|
||
// system and might have a single cache entry in the registry.
|
||
//
|
||
|
||
RtlInitUnicodeString( &CacheControlValueName, L"NL$Control" );
|
||
NtStatus = NtQueryValueKey(NlpCacheHandle,
|
||
&CacheControlValueName,
|
||
KeyValuePartialInformation,
|
||
NULL,
|
||
0,
|
||
&RequiredSize
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus) || NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
|
||
NTSTATUS TempStatus;
|
||
|
||
//
|
||
// Hmmm - no entry, that means we are dealing with a
|
||
// first release system here (that didn't have
|
||
// this value).
|
||
//
|
||
|
||
|
||
//
|
||
// Set up for 1 cache entry.
|
||
// create the secret and cache key entry
|
||
//
|
||
|
||
TempStatus = NlpMakeNewCacheEntry( 0 );
|
||
|
||
if ( NT_SUCCESS(TempStatus) ) {
|
||
//
|
||
// Now flush out the control information
|
||
//
|
||
|
||
|
||
NlpCacheControl.Revision = NLP_CACHE_REVISION;
|
||
NlpCacheControl.Entries = 1;
|
||
TempStatus = NlpWriteCacheControl();
|
||
|
||
if ( NT_SUCCESS(TempStatus) ) {
|
||
|
||
//
|
||
// If a version 1.0 entry exists,
|
||
// copy the old form of cache entry to the new structure.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
TempStatus = NlpConvert1_0To1_0B();
|
||
}
|
||
}
|
||
}
|
||
|
||
NtStatus = TempStatus;
|
||
|
||
} else if ( NtStatus == STATUS_BUFFER_TOO_SMALL ) {
|
||
|
||
//
|
||
// allocate buffer then do query again, this time receiving data
|
||
//
|
||
|
||
RegInfo = (PKEY_VALUE_PARTIAL_INFORMATION)AllocateFromHeap(RequiredSize);
|
||
if (RegInfo == NULL) {
|
||
NlpCacheControl.Entries = 0;
|
||
return(STATUS_NO_MEMORY);
|
||
}
|
||
|
||
NtStatus = NtQueryValueKey(NlpCacheHandle,
|
||
&CacheControlValueName,
|
||
KeyValuePartialInformation,
|
||
(PVOID)RegInfo,
|
||
RequiredSize,
|
||
&RequiredSize
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
NlpCacheControl.Entries = 0;
|
||
return(NtStatus);
|
||
}
|
||
|
||
//
|
||
// check the revision - we can't deal with up-level revisions.
|
||
//
|
||
|
||
if (RegInfo->DataLength < sizeof(NLP_CACHE_CONTROL)) {
|
||
NlpCacheControl.Entries = 0; // Disable caching
|
||
return(STATUS_UNKNOWN_REVISION);
|
||
}
|
||
|
||
RtlMoveMemory( &NlpCacheControl, &(RegInfo->Data[0]), sizeof(NLP_CACHE_CONTROL) );
|
||
if (NlpCacheControl.Revision > NLP_CACHE_REVISION) {
|
||
NlpCacheControl.Entries = 0; // Disable caching
|
||
return(STATUS_UNKNOWN_REVISION);
|
||
}
|
||
|
||
FreeToHeap( RegInfo );
|
||
|
||
//
|
||
// If this is an older cache, update it with the latest revision
|
||
//
|
||
|
||
if (NlpCacheControl.Revision != NLP_CACHE_REVISION) {
|
||
|
||
NlpCacheControl.Revision = NLP_CACHE_REVISION;
|
||
NtStatus = NlpWriteCacheControl();
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
NlpCacheControl.Entries = 0;
|
||
return(NtStatus);
|
||
}
|
||
}
|
||
|
||
return(STATUS_SUCCESS);
|
||
}
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
NlpCacheControl.Entries = 0; // Disable logon cache
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpBuildCteTable( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function initializes the CTE table from the contents of
|
||
the cache in the registry.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - the cache is initialized.
|
||
|
||
Other - The cache has been disabled.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
PLOGON_CACHE_ENTRY
|
||
CacheEntry;
|
||
|
||
ULONG
|
||
EntrySize,
|
||
i;
|
||
|
||
|
||
//
|
||
// Initialize the active and inactive CTE lists
|
||
//
|
||
|
||
InitializeListHead( &NlpActiveCtes );
|
||
InitializeListHead( &NlpInactiveCtes );
|
||
|
||
|
||
//
|
||
// Allocate a CTE table
|
||
//
|
||
|
||
NlpCteTable = AllocateFromHeap( sizeof( NLP_CTE ) *
|
||
NlpCacheControl.Entries );
|
||
if (NlpCteTable == NULL) {
|
||
|
||
//
|
||
// Can't allocate table, disable caching
|
||
//
|
||
|
||
NlpCacheControl.Entries = 0; // Disable cache
|
||
return(STATUS_NO_MEMORY);
|
||
}
|
||
|
||
for (i=0; i<NlpCacheControl.Entries; i++) {
|
||
|
||
NtStatus = NlpReadCacheEntryByIndex( i,
|
||
&CacheEntry,
|
||
&EntrySize);
|
||
if (!NT_SUCCESS(NtStatus) ) {
|
||
NlpCacheControl.Entries = 0; // Disable cache
|
||
return(NtStatus);
|
||
}
|
||
|
||
//
|
||
//
|
||
if (EntrySize < sizeof(LOGON_CACHE_ENTRY_1_0A)) {
|
||
|
||
//
|
||
// Hmmm, something is bad.
|
||
// disable caching and return an error
|
||
//
|
||
|
||
NlpCacheControl.Entries = 0; // Disable cache
|
||
FreeToHeap( CacheEntry );
|
||
return( STATUS_INTERNAL_DB_CORRUPTION );
|
||
}
|
||
|
||
if (CacheEntry->Revision > NLP_CACHE_REVISION) {
|
||
NlpCacheControl.Entries = 0; // Disable cache
|
||
FreeToHeap( CacheEntry );
|
||
return(STATUS_UNKNOWN_REVISION);
|
||
}
|
||
|
||
NlpCteTable[i].Index = i;
|
||
NlpCteTable[i].Active = CacheEntry->Valid;
|
||
NlpCteTable[i].Time = CacheEntry->Time;
|
||
|
||
InsertTailList( &NlpInactiveCtes, &NlpCteTable[i].Link );
|
||
|
||
if (NlpCteTable[i].Active) {
|
||
NlpAddEntryToActiveList( i );
|
||
}
|
||
|
||
FreeToHeap( CacheEntry );
|
||
|
||
}
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpChangeCacheSizeIfNecessary( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function checks to see if the user has requested a
|
||
different cache size than what we currently have.
|
||
|
||
If so, then we try to grow or shrink our cache appropriately.
|
||
If this succeeds, then the global cache control information is
|
||
updated appropriately. If it fails then one of two things will
|
||
happen:
|
||
|
||
1) If the user was trying to shrink the cache, then it will
|
||
be disabled (entries set to zero).
|
||
|
||
2) If the user was trying to grow the cache, then we will leave
|
||
it as it is.
|
||
|
||
In either of these two failure conditions, an error is returned.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UINT
|
||
CachedLogonsCount;
|
||
|
||
PNLP_CTE
|
||
NewCteTable,
|
||
Next;
|
||
|
||
LIST_ENTRY
|
||
NewActive,
|
||
NewInactive;
|
||
|
||
PNLP_CACHE_AND_SECRETS
|
||
CacheAndSecrets;
|
||
|
||
|
||
ULONG
|
||
ErrorCacheSize,
|
||
EntrySize,
|
||
i,
|
||
j;
|
||
|
||
|
||
// Find out how many logons to cache.
|
||
// This is a user setable value and it may be different than
|
||
// the last time we booted.
|
||
//
|
||
|
||
CachedLogonsCount = GetProfileInt(
|
||
TEXT("Winlogon"),
|
||
TEXT("CachedLogonsCount"),
|
||
NLP_DEFAULT_LOGON_CACHE_COUNT // Default value
|
||
);
|
||
|
||
//
|
||
// Minimize the user-supplied value with the maximum allowable
|
||
// value.
|
||
//
|
||
|
||
if (CachedLogonsCount > NLP_MAX_LOGON_CACHE_COUNT) {
|
||
CachedLogonsCount = NLP_MAX_LOGON_CACHE_COUNT;
|
||
}
|
||
|
||
|
||
//
|
||
// Compare it to what we already have and see if we need
|
||
// to change the size of the cache
|
||
//
|
||
|
||
if (CachedLogonsCount == NlpCacheControl.Entries) {
|
||
|
||
//
|
||
// No change
|
||
//
|
||
|
||
return(STATUS_SUCCESS);
|
||
}
|
||
|
||
//
|
||
// Set the size of the cache to be used in case of error
|
||
// changing the size. If we are trying to grow the cache,
|
||
// then use the existing cache on error. If we are trying
|
||
// to shrink the cache, then disable caching on error.
|
||
//
|
||
|
||
if (CachedLogonsCount > NlpCacheControl.Entries) {
|
||
ErrorCacheSize = NlpCacheControl.Entries;
|
||
} else {
|
||
ErrorCacheSize = 0;
|
||
}
|
||
|
||
//
|
||
// Allocate a CTE table the size of the new table
|
||
//
|
||
|
||
NewCteTable = AllocateFromHeap( sizeof( NLP_CTE ) *
|
||
CachedLogonsCount );
|
||
if (NewCteTable == NULL) {
|
||
|
||
//
|
||
// Can't shrink table, disable caching
|
||
//
|
||
|
||
NlpCacheControl.Entries = ErrorCacheSize;
|
||
return(STATUS_NO_MEMORY);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Now the tricky parts ...
|
||
//
|
||
|
||
if (CachedLogonsCount > NlpCacheControl.Entries) {
|
||
|
||
|
||
//
|
||
// Try to grow the cache -
|
||
// Create additional secrets and cache entries.
|
||
//
|
||
// Copy time fields and set index
|
||
//
|
||
|
||
for (i=0; i < NlpCacheControl.Entries; i++) {
|
||
NewCteTable[i].Index = i;
|
||
NewCteTable[i].Time = NlpCteTable[i].Time;
|
||
}
|
||
|
||
//
|
||
// Place existing entries on either the active or inactive list
|
||
//
|
||
|
||
InitializeListHead( &NewActive );
|
||
for (Next = (PNLP_CTE)NlpActiveCtes.Flink;
|
||
Next != (PNLP_CTE)(&NlpActiveCtes);
|
||
Next = (PNLP_CTE)Next->Link.Flink
|
||
) {
|
||
|
||
InsertTailList( &NewActive, &NewCteTable[Next->Index].Link );
|
||
NewCteTable[Next->Index].Active = TRUE;
|
||
}
|
||
|
||
|
||
InitializeListHead( &NewInactive );
|
||
for (Next = (PNLP_CTE)NlpInactiveCtes.Flink;
|
||
Next != (PNLP_CTE)(&NlpInactiveCtes);
|
||
Next = (PNLP_CTE)Next->Link.Flink
|
||
) {
|
||
|
||
InsertTailList( &NewInactive, &NewCteTable[Next->Index].Link );
|
||
NewCteTable[Next->Index].Active = FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Make all the new table entries.
|
||
// Mark them as invalid.
|
||
//
|
||
|
||
for (i=NlpCacheControl.Entries; i<CachedLogonsCount; i++) {
|
||
|
||
//
|
||
// Add the CTE entry to the inactive list
|
||
//
|
||
|
||
InsertTailList( &NewInactive, &NewCteTable[i].Link );
|
||
NewCteTable[i].Active = FALSE;
|
||
NewCteTable[i].Index = i;
|
||
|
||
NtStatus = NlpMakeNewCacheEntry( i );
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
FreeToHeap( NewCteTable );
|
||
return(NtStatus);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
} else {
|
||
|
||
|
||
//
|
||
// Try to shrink the cache.
|
||
//
|
||
|
||
if (CachedLogonsCount != 0) {
|
||
|
||
//
|
||
// 0 size implies disabling the cache.
|
||
// That is a degenerate case of shrinking that
|
||
// requires only the last few steps of shrinking.
|
||
//
|
||
|
||
|
||
//
|
||
// Allocate an array of pointers for reading registry and secret
|
||
// info into. Clear it to assist in cleanup.
|
||
//
|
||
|
||
CacheAndSecrets = (PNLP_CACHE_AND_SECRETS)
|
||
AllocateFromHeap( sizeof( NLP_CACHE_AND_SECRETS ) *
|
||
CachedLogonsCount );
|
||
|
||
if (CacheAndSecrets == NULL) {
|
||
FreeToHeap( NlpCteTable );
|
||
NlpCacheControl.Entries = ErrorCacheSize;
|
||
return(STATUS_NO_MEMORY);
|
||
}
|
||
RtlZeroMemory( CacheAndSecrets,
|
||
(sizeof( NLP_CACHE_AND_SECRETS ) * CachedLogonsCount) );
|
||
|
||
|
||
//
|
||
// Set up the new CTE table to be inactive
|
||
//
|
||
|
||
InitializeListHead( &NewActive );
|
||
InitializeListHead( &NewInactive );
|
||
for (i=0; i<CachedLogonsCount; i++) {
|
||
InsertTailList( &NewInactive, &NewCteTable[i].Link );
|
||
NewCteTable[i].Index = i;
|
||
NewCteTable[i].Active = FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Walk the current active list, reading
|
||
// entries and copying information into the new CTE table.
|
||
//
|
||
|
||
i = 0;
|
||
Next = (PNLP_CTE)NlpActiveCtes.Flink;
|
||
while (Next != (PNLP_CTE)&NlpActiveCtes && i<CachedLogonsCount) {
|
||
|
||
NtStatus = NlpReadCacheEntryByIndex( Next->Index,
|
||
&CacheAndSecrets[i].CacheEntry,
|
||
&EntrySize
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpOpenSecret( Next->Index );
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpReadSecret( &CacheAndSecrets[i].NewSecret,
|
||
&CacheAndSecrets[i].OldSecret);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
//
|
||
// Only make this entry active if everything was
|
||
// successfully read in.
|
||
//
|
||
|
||
CacheAndSecrets[i].Active = TRUE;
|
||
i++; // advance our new CTE table index
|
||
|
||
}
|
||
NlpCloseSecret();
|
||
}
|
||
}
|
||
|
||
Next = (PNLP_CTE)(Next->Link.Flink);
|
||
|
||
} // end-while
|
||
|
||
//
|
||
// At this point "i" indicates how many CacheAndSecrets entries
|
||
// are active. Furthermore, the entries were assembled
|
||
// in the CacheAndSecrets array in ascending time order, which
|
||
// is the order they need to be placed in the new CTE table.
|
||
//
|
||
|
||
for ( j=0; j<i; j++) {
|
||
|
||
Next = &NewCteTable[j];
|
||
|
||
//
|
||
// The Time field in the original cache entry is not aligned
|
||
// properly, so copy each field individually.
|
||
//
|
||
|
||
Next->Time.LowPart = CacheAndSecrets[j].CacheEntry->Time.LowPart;
|
||
Next->Time.HighPart = CacheAndSecrets[j].CacheEntry->Time.HighPart;
|
||
|
||
//
|
||
// Try writing out the new entry's information
|
||
//
|
||
|
||
NtStatus = NlpWriteCacheEntry( j,
|
||
CacheAndSecrets[j].CacheEntry,
|
||
CacheAndSecrets[j].EntrySize
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = NlpOpenSecret( j );
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = NlpWriteSecret(CacheAndSecrets[j].NewSecret,
|
||
CacheAndSecrets[j].OldSecret);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// move the corresponding entry into the new CTEs
|
||
// active list.
|
||
//
|
||
|
||
Next->Active = TRUE;
|
||
RemoveEntryList( &Next->Link );
|
||
InsertTailList( &NewActive, &Next->Link );
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Free the CacheEntry and secret information
|
||
//
|
||
|
||
if (CacheAndSecrets[j].CacheEntry != NULL) {
|
||
FreeToHeap( CacheAndSecrets[j].CacheEntry );
|
||
}
|
||
if (CacheAndSecrets[j].NewSecret != NULL) {
|
||
MIDL_user_free( CacheAndSecrets[j].NewSecret );
|
||
}
|
||
if (CacheAndSecrets[j].OldSecret != NULL) {
|
||
MIDL_user_free( CacheAndSecrets[j].OldSecret );
|
||
}
|
||
}
|
||
|
||
//
|
||
// Free the CacheAndSecrets array
|
||
// (everything in it has already been freed)
|
||
//
|
||
|
||
if (CacheAndSecrets != NULL) {
|
||
FreeToHeap( CacheAndSecrets );
|
||
}
|
||
|
||
//
|
||
// Change remaining entries to invalid (on disk)
|
||
//
|
||
|
||
for ( j=i; j<CachedLogonsCount; j++) {
|
||
NlpMakeNewCacheEntry( j );
|
||
}
|
||
|
||
} // end-if (CachedLogonsCount != 0)
|
||
|
||
|
||
//
|
||
// Now get rid of extra (no longer needed) entries
|
||
//
|
||
|
||
for ( j=CachedLogonsCount; j<NlpCacheControl.Entries; j++) {
|
||
NlpEliminateCacheEntry( j );
|
||
}
|
||
|
||
|
||
}
|
||
|
||
//
|
||
// We have successfully:
|
||
//
|
||
// Allocated the new CTE table.
|
||
//
|
||
// Filled the CTE table with copies of the currently
|
||
// active CTEs (including putting each CTE on an active
|
||
// or inactive list).
|
||
//
|
||
// Established new CTE entries, including the corresponding
|
||
// secrets and cache keys in the registry, for the
|
||
// new CTEs.
|
||
//
|
||
//
|
||
// All we have left to do is:
|
||
//
|
||
//
|
||
// Update the cache control structure in the registry
|
||
// to indicate we have a new length
|
||
//
|
||
// move the new CTE over to the real Active and Inactive
|
||
// list heads (rather than the local ones we've used so far)
|
||
//
|
||
// deallocate the old CTE table.
|
||
//
|
||
// Re-set the entries count in the in-memory
|
||
// cache-control structure NlpCacheControl.
|
||
//
|
||
|
||
|
||
NlpCacheControl.Entries = CachedLogonsCount;
|
||
NtStatus = NlpWriteCacheControl();
|
||
|
||
if (CachedLogonsCount > 0) { // Only necessary if there is a new CTE table
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
FreeToHeap( NewCteTable );
|
||
NlpCacheControl.Entries = ErrorCacheSize;
|
||
return(NtStatus);
|
||
}
|
||
|
||
InsertHeadList( &NewActive, &NlpActiveCtes );
|
||
RemoveEntryList( &NewActive );
|
||
InsertHeadList( &NewInactive, &NlpInactiveCtes );
|
||
RemoveEntryList( &NewInactive );
|
||
|
||
FreeToHeap( NlpCteTable );
|
||
NlpCteTable = NewCteTable;
|
||
}
|
||
|
||
return(NtStatus);
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlpWriteCacheControl( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function writes a new cache length out to the
|
||
cache control structure stored in the registry.
|
||
|
||
Note:
|
||
When lengthening the cache, call this routine after the cache
|
||
entries and corresponding secrets have been established for
|
||
the new length.
|
||
|
||
When shortening the cache, call this routine before the cache
|
||
entries and corresponding secrets being discarded have actually
|
||
been discarded.
|
||
|
||
This ensures that if the system crashes during the resizing
|
||
operation, it will be in a valid state when the system comes
|
||
back up.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
CacheControlValueName;
|
||
|
||
|
||
RtlInitUnicodeString( &CacheControlValueName, L"NL$Control" );
|
||
NtStatus = NtSetValueKey( NlpCacheHandle,
|
||
&CacheControlValueName, // Name
|
||
0, // TitleIndex
|
||
REG_BINARY, // Type
|
||
&NlpCacheControl, // Data
|
||
sizeof(NLP_CACHE_CONTROL) // DataLength
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtFlushKey( NlpCacheHandle );
|
||
}
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpMakeCacheEntryName(
|
||
IN ULONG EntryIndex,
|
||
OUT PUNICODE_STRING Name
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function builds a name of a cache entry value or secret name
|
||
for a cached entry. The name is based upon the index of the cache
|
||
entry.
|
||
|
||
Names are of the form:
|
||
|
||
"NLP1" through "NLPnnn"
|
||
|
||
where "nnn" is the largest allowable entry count (see
|
||
NLP_MAX_LOGON_CACHE_COUNT).
|
||
|
||
The output UNICODE_STRING buffer is expected to be large enough
|
||
to accept this string with a null termination on it.
|
||
|
||
|
||
Arguments:
|
||
|
||
EntryIndex - The index of the cache entry whose name is desired.
|
||
|
||
Name - A unicode string large enough to accept the name.
|
||
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
TmpString;
|
||
|
||
WCHAR
|
||
TmpStringBuffer[17];
|
||
|
||
ASSERT(Name->MaximumLength >= 7*sizeof(WCHAR) );
|
||
ASSERT( EntryIndex <= NLP_MAX_LOGON_CACHE_COUNT );
|
||
|
||
Name->Length = 0;
|
||
RtlAppendUnicodeToString( Name, L"NL$" );
|
||
|
||
TmpString.MaximumLength = 16;
|
||
TmpString.Length = 0;
|
||
TmpString.Buffer = TmpStringBuffer;
|
||
NtStatus = RtlIntegerToUnicodeString ( (EntryIndex+1), // make 1 based index
|
||
10, // Base 10
|
||
&TmpString
|
||
);
|
||
ASSERT(NT_SUCCESS(NtStatus));
|
||
|
||
RtlAppendUnicodeStringToString( Name, &TmpString );
|
||
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpMakeNewCacheEntry(
|
||
ULONG Index
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine creates a secret and a cache entry value for a
|
||
new cache entry with the specified index.
|
||
|
||
The secret handle is NOT left open.
|
||
|
||
|
||
Arguments:
|
||
|
||
Index - The index of the cache entry whose name is desired.
|
||
|
||
Name - A unicode string large enough to accept the name.
|
||
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
LOGON_CACHE_ENTRY
|
||
Entry;
|
||
|
||
UNICODE_STRING
|
||
ValueName;
|
||
|
||
WCHAR
|
||
NameBuffer[32];
|
||
|
||
LSA_HANDLE
|
||
SecretHandle;
|
||
|
||
ValueName.Length = 0;
|
||
ValueName.MaximumLength = 32;
|
||
ValueName.Buffer = &NameBuffer[0];
|
||
|
||
NlpMakeCacheEntryName( Index, &ValueName );
|
||
|
||
//
|
||
// Create a secret object for the entry
|
||
//
|
||
|
||
NtStatus = LsaCreateSecret( NlpLsaHandle,
|
||
&ValueName,
|
||
SECRET_SET_VALUE,
|
||
&SecretHandle
|
||
);
|
||
if (NtStatus == STATUS_OBJECT_NAME_COLLISION) {
|
||
NtStatus = LsaOpenSecret( NlpLsaHandle,
|
||
&ValueName,
|
||
SECRET_SET_VALUE,
|
||
&SecretHandle
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
LsaSetSecret( SecretHandle, NULL, NULL); // Don't worry about it failing
|
||
|
||
LsaClose( SecretHandle );
|
||
|
||
//
|
||
// Create the cache entry marked as invalid
|
||
//
|
||
|
||
RtlZeroMemory( &Entry, sizeof(Entry) );
|
||
Entry.Revision = NLP_CACHE_REVISION;
|
||
Entry.Valid = FALSE;
|
||
NtStatus = NtSetValueKey( NlpCacheHandle,
|
||
&ValueName, // Name
|
||
0, // TitleIndex
|
||
REG_BINARY, // Type
|
||
&Entry, // Data
|
||
sizeof(LOGON_CACHE_ENTRY) // DataLength
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtFlushKey( NlpCacheHandle );
|
||
}
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpEliminateCacheEntry(
|
||
IN ULONG Index
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Delete the registry value and secret object related to a
|
||
CTE entry.
|
||
|
||
Arguments:
|
||
|
||
Index - The index of the entry whose value and secret are to
|
||
be deleted. This value is used only to build a name with
|
||
(not to reference the CTE table).
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
ValueName;
|
||
|
||
WCHAR
|
||
NameBuffer[32];
|
||
|
||
LSA_HANDLE
|
||
SecretHandle;
|
||
|
||
|
||
ValueName.Buffer = &NameBuffer[0];
|
||
ValueName.MaximumLength = 32;
|
||
ValueName.Length = 0;
|
||
NlpMakeCacheEntryName( Index, &ValueName );
|
||
|
||
NtStatus = LsaOpenSecret(NlpLsaHandle,
|
||
&ValueName,
|
||
DELETE,
|
||
&SecretHandle
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Deleting and object causes its handle to be closed
|
||
//
|
||
|
||
NtStatus = LsaDelete( SecretHandle );
|
||
|
||
|
||
//
|
||
// Now delete the registry value
|
||
//
|
||
|
||
NtStatus = NtDeleteValueKey( NlpCacheHandle, &ValueName );
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpConvert1_0To1_0B( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function retrieves the cache entry used in NT1.0 systems
|
||
and stores it (if found) in the zero'th CTE entry. It also
|
||
copies the secrets from 1.0 storage format to 1.0B format.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - if the entry was successfully upgraded, or if
|
||
it didn't exist.
|
||
|
||
STATUS_NO_MEMORY - if we couldn't allocate memory from heap.
|
||
|
||
other - unexpected error.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
PKEY_VALUE_FULL_INFORMATION
|
||
RegistryStructure;
|
||
|
||
PLOGON_CACHE_ENTRY_1_0
|
||
CacheEntry;
|
||
|
||
PLOGON_CACHE_ENTRY
|
||
NewCacheEntry;
|
||
|
||
UNICODE_STRING
|
||
NullName;
|
||
|
||
PUNICODE_STRING
|
||
CurrentSecret = NULL,
|
||
OldSecret = NULL;
|
||
|
||
ULONG
|
||
RequiredSize,
|
||
VariableSize,
|
||
EntrySize,
|
||
NewSize;
|
||
|
||
|
||
PUCHAR
|
||
Source,
|
||
Dest;
|
||
|
||
|
||
|
||
//
|
||
// This should always try to return at least
|
||
// the KEY_VALUE_FULL_INFORMATION structure, even
|
||
// if there isn't data available.
|
||
//
|
||
|
||
RtlInitUnicodeString(&NullName, NULL);
|
||
NtStatus = NtQueryValueKey(NlpCacheHandle,
|
||
&NullName,
|
||
KeyValueFullInformation,
|
||
NULL,
|
||
0,
|
||
&RequiredSize
|
||
);
|
||
ASSERT(!NT_SUCCESS(NtStatus));
|
||
|
||
if (NtStatus != STATUS_BUFFER_TOO_SMALL) {
|
||
return(NtStatus);
|
||
}
|
||
|
||
RegistryStructure = AllocateFromHeap( RequiredSize );
|
||
if (RegistryStructure == NULL) {
|
||
NtStatus = STATUS_NO_MEMORY;
|
||
} else {
|
||
|
||
|
||
NtStatus = NtQueryValueKey(NlpCacheHandle,
|
||
&NullName,
|
||
KeyValueFullInformation,
|
||
RegistryStructure,
|
||
RequiredSize,
|
||
&RequiredSize
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// If we didn't get any data in the query, then there
|
||
// wasn't a cache entry, don't do anything. Otherwise,
|
||
// copy it to the new scheme.
|
||
//
|
||
|
||
if (RequiredSize > sizeof(KEY_VALUE_FULL_INFORMATION)) {
|
||
|
||
|
||
|
||
//
|
||
// OK, we now have a NT1_0 cache entry.
|
||
// This is the same as a NT1_0A cache entry, except that
|
||
// the fields from SidCount onward are new to NT1_0A and
|
||
// so aren't present in the structure we just read in.
|
||
//
|
||
// Now the challange is to build a new Registry Structure
|
||
// that looks just like the one we just read in, but adds
|
||
// in these new fields.
|
||
//
|
||
// Warning - the fields of CacheEntry are not necessarily
|
||
// aligned nicely because the structure starts
|
||
// at a random offset inside a registry header.
|
||
// Avoid referencing CacheEntry fields.
|
||
//
|
||
|
||
CacheEntry = (PLOGON_CACHE_ENTRY_1_0)
|
||
((PCHAR)(RegistryStructure) +
|
||
(RegistryStructure->DataOffset));
|
||
EntrySize = RegistryStructure->DataLength;
|
||
|
||
Source = (PUCHAR)(((PLOGON_CACHE_ENTRY_1_0)CacheEntry) + 1);
|
||
|
||
|
||
VariableSize = EntrySize -
|
||
ROUND_UP_COUNT(sizeof(LOGON_CACHE_ENTRY_1_0), sizeof(ULONG));
|
||
|
||
NewSize =
|
||
ROUND_UP_COUNT(sizeof(LOGON_CACHE_ENTRY), sizeof(ULONG)) +
|
||
VariableSize;
|
||
|
||
NewCacheEntry = (PLOGON_CACHE_ENTRY)AllocateFromHeap(NewSize);
|
||
if (NewCacheEntry == NULL) {
|
||
NtStatus = STATUS_NO_MEMORY;
|
||
} else {
|
||
|
||
RtlZeroMemory( NewCacheEntry, NewSize );
|
||
|
||
//
|
||
// Copy the fixed-length aspects of the original
|
||
// CacheEntry into the new cache entry.
|
||
//
|
||
|
||
RtlMoveMemory( NewCacheEntry,
|
||
CacheEntry,
|
||
sizeof(LOGON_CACHE_ENTRY_1_0) );
|
||
|
||
|
||
//
|
||
// We have to figure out the length of the LogonDomainId
|
||
//
|
||
{
|
||
ULONG commonBits, sidLength;
|
||
|
||
commonBits =
|
||
ROUND_UP_COUNT(NewCacheEntry->EffectiveNameLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(NewCacheEntry->FullNameLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(NewCacheEntry->LogonScriptLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(NewCacheEntry->ProfilePathLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(NewCacheEntry->HomeDirectoryLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(NewCacheEntry->HomeDirectoryDriveLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(NewCacheEntry->GroupCount * sizeof(GROUP_MEMBERSHIP), sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(NewCacheEntry->LogonDomainNameLength, sizeof(ULONG)
|
||
);
|
||
|
||
//
|
||
// sidLength is the size of the SID copied to the LOGON_CACHE_ENTRY structure
|
||
//
|
||
|
||
sidLength = EntrySize - (sizeof(LOGON_CACHE_ENTRY_1_0)
|
||
+ ROUND_UP_COUNT(NewCacheEntry->UserNameLength, sizeof(ULONG))
|
||
+ ROUND_UP_COUNT(NewCacheEntry->DomainNameLength, sizeof(ULONG))
|
||
+ commonBits
|
||
);
|
||
|
||
NewCacheEntry->LogonDomainIdLength = (USHORT) sidLength;
|
||
}
|
||
|
||
|
||
NtQuerySystemTime( &NewCacheEntry->Time );
|
||
NewCacheEntry->Revision = NLP_CACHE_REVISION;
|
||
NewCacheEntry->Valid = TRUE;
|
||
|
||
Dest = (PUCHAR)(((PLOGON_CACHE_ENTRY)NewCacheEntry) + 1);
|
||
RtlMoveMemory( Dest, Source, VariableSize );
|
||
|
||
|
||
|
||
//
|
||
// put out the secrets first so that if it fails
|
||
// we haven't already validated the cache entry.
|
||
// This is done by tricking the secret routines.
|
||
//
|
||
|
||
|
||
NtStatus = NlpOpen_Nt1_0_Secret();
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = NlpReadSecret(&CurrentSecret, &OldSecret);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Write out the secrets in the zero'th entry
|
||
//
|
||
|
||
NtStatus = NlpOpenSecret( 0 );
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = NlpWriteSecret( CurrentSecret, OldSecret);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = NlpWriteCacheEntry(
|
||
0,
|
||
NewCacheEntry,
|
||
NewSize
|
||
);
|
||
//
|
||
// The CTE table isn't built
|
||
// yet, so don't try to update
|
||
// this entry in the CTE.
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Free the secret buffers
|
||
//
|
||
|
||
if (CurrentSecret) {
|
||
MIDL_user_free(CurrentSecret);
|
||
}
|
||
if (OldSecret) {
|
||
MIDL_user_free(OldSecret);
|
||
}
|
||
}
|
||
|
||
NlpCloseSecret();
|
||
}
|
||
|
||
FreeToHeap( NewCacheEntry );
|
||
}
|
||
}
|
||
}
|
||
|
||
FreeToHeap( RegistryStructure );
|
||
}
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpOpen_Nt1_0_Secret( VOID )
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Opens the secret object for the cache entry of a NT1.0 system.
|
||
This is used to upgrade the system. The secret is opened for
|
||
query access.
|
||
|
||
If the secret does not exist, it is NOT created.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The secret was successfully openned.
|
||
|
||
STATUS_OBJECT_NAME_NOT_FOUND - The secret didn't exist. The handle
|
||
value is not valid.
|
||
|
||
Other error - the handle value is invalid.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
SecretName;
|
||
|
||
|
||
//
|
||
// Close previous handle if necessary
|
||
//
|
||
|
||
if (IS_VALID_HANDLE(NlpSecretHandle)) {
|
||
LsaClose( NlpSecretHandle );
|
||
}
|
||
|
||
SecretName.Length = SecretName.MaximumLength = SECRET_NAME_SIZE;
|
||
SecretName.Buffer = SECRET_NAME;
|
||
NtStatus = LsaOpenSecret(NlpLsaHandle,
|
||
&SecretName,
|
||
SECRET_QUERY_VALUE,
|
||
&NlpSecretHandle
|
||
);
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlpReadCacheEntryByIndex(
|
||
IN ULONG Index,
|
||
OUT PLOGON_CACHE_ENTRY* CacheEntry,
|
||
OUT PULONG EntrySize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Reads a cache entry from registry
|
||
|
||
Arguments:
|
||
|
||
Index - CTE table index of the entry to open.
|
||
This is used to build the entry's value and secret names.
|
||
|
||
CacheEntry - pointer to place to return pointer to LOGON_CACHE_ENTRY
|
||
|
||
EntrySize - size of returned LOGON_CACHE_ENTRY
|
||
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
Success = STATUS_SUCCESS
|
||
*ppEntry points to allocated LOGON_CACHE_ENTRY
|
||
*EntrySize is size of returned data
|
||
|
||
Failure = STATUS_NO_MEMORY
|
||
Couldn't allocate buffer for LOGON_CACHE_ENTRY
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
UNICODE_STRING
|
||
ValueName;
|
||
|
||
WCHAR
|
||
NameBuffer[32];
|
||
|
||
ULONG
|
||
RequiredSize;
|
||
|
||
PKEY_VALUE_FULL_INFORMATION
|
||
RegInfo;
|
||
|
||
PLOGON_CACHE_ENTRY
|
||
RCacheEntry; // CacheEntry in registry buffer
|
||
|
||
ValueName.Buffer = &NameBuffer[0];
|
||
ValueName.MaximumLength = 32;
|
||
ValueName.Length = 0;
|
||
NlpMakeCacheEntryName( Index, &ValueName );
|
||
|
||
//
|
||
// perform first query to find out how much buffer to allocate
|
||
//
|
||
|
||
NtStatus = NtQueryValueKey(NlpCacheHandle,
|
||
&ValueName,
|
||
KeyValueFullInformation,
|
||
NULL,
|
||
0,
|
||
&RequiredSize
|
||
);
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus) || (NtStatus == STATUS_BUFFER_TOO_SMALL)) {
|
||
|
||
//
|
||
// allocate buffer then do query again, this time receiving data
|
||
//
|
||
|
||
RegInfo = (PKEY_VALUE_FULL_INFORMATION)AllocateFromHeap(RequiredSize);
|
||
if (RegInfo == NULL) {
|
||
return(STATUS_NO_MEMORY);
|
||
}
|
||
|
||
NtStatus = NtQueryValueKey(NlpCacheHandle,
|
||
&ValueName,
|
||
KeyValueFullInformation,
|
||
(PVOID)RegInfo,
|
||
RequiredSize,
|
||
&RequiredSize
|
||
);
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DbgPrint("NlpReadCacheEntryByIndex: Index : %d\n"
|
||
" NtQueryValueKey returns: %d bytes\n"
|
||
" DataOffset=%d\n"
|
||
" DataLength=%d\n",
|
||
Index, RequiredSize, RegInfo->DataOffset, RegInfo->DataLength);
|
||
}
|
||
#endif
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RCacheEntry = (PLOGON_CACHE_ENTRY)((PCHAR)RegInfo + RegInfo->DataOffset);
|
||
*EntrySize = RegInfo->DataLength;
|
||
|
||
(*CacheEntry) = (PLOGON_CACHE_ENTRY)AllocateFromHeap( (*EntrySize) );
|
||
if ((*CacheEntry) == NULL) {
|
||
NtStatus = STATUS_NO_MEMORY;
|
||
} else {
|
||
RtlMoveMemory( (*CacheEntry),
|
||
RCacheEntry,
|
||
(*EntrySize) );
|
||
|
||
}
|
||
|
||
FreeToHeap(RegInfo);
|
||
|
||
#if DBG
|
||
if (DumpCacheInfo) {
|
||
DumpCacheEntry(Index, *CacheEntry);
|
||
}
|
||
#endif
|
||
|
||
}
|
||
|
||
}
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpAddEntryToActiveList(
|
||
IN ULONG Index
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Place a CTE entry in the active CTE list.
|
||
This requires placing the entry in the right location in
|
||
the list chronologically. The beginning of the list is
|
||
the most recently updated (or referenced) cache entry.
|
||
The end of the list is the oldest active cache entry.
|
||
|
||
|
||
Note - The entry may be already in the active list (but
|
||
in the wrong place), or may be on the inactive list.
|
||
It will be removed from whichever list it is on.
|
||
|
||
Arguments:
|
||
|
||
Index - CTE table index of the entry to make active..
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PNLP_CTE
|
||
Next;
|
||
|
||
//
|
||
// Remove the entry from its current list, and then place it
|
||
// in the active list.
|
||
//
|
||
|
||
|
||
RemoveEntryList( &NlpCteTable[Index].Link );
|
||
|
||
|
||
//
|
||
// Now walk the active list until we find a place to insert
|
||
// the entry. It must follow all entries with more recent
|
||
// time stamps.
|
||
//
|
||
|
||
Next = (PNLP_CTE)NlpActiveCtes.Flink;
|
||
|
||
while (Next != (PNLP_CTE)&NlpActiveCtes) {
|
||
|
||
if ( NlpCteTable[Index].Time.QuadPart < Next->Time.QuadPart ) {
|
||
|
||
//
|
||
// More recent than this entry - add it here
|
||
//
|
||
|
||
break; // out of while-loop
|
||
|
||
}
|
||
|
||
Next = (PNLP_CTE)(Next->Link.Flink); // Advance to next entry
|
||
}
|
||
|
||
|
||
//
|
||
// Use the preceding entry as the list head.
|
||
//
|
||
|
||
InsertHeadList( Next->Link.Flink, &NlpCteTable[Index].Link );
|
||
|
||
//
|
||
// Mark the entry as valid
|
||
//
|
||
|
||
NlpCteTable[Index].Active = TRUE;
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpAddEntryToInactiveList(
|
||
IN ULONG Index
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Move the CTE entry to the inactive list.
|
||
|
||
It doesn't matter if the entry is already inactive.
|
||
|
||
Arguments:
|
||
|
||
Index - CTE table index of the entry to make inactive.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
//
|
||
// Remove the entry from its current list, and then place it
|
||
// in the inactive list.
|
||
//
|
||
|
||
|
||
RemoveEntryList( &NlpCteTable[Index].Link );
|
||
InsertTailList( &NlpInactiveCtes, &NlpCteTable[Index].Link );
|
||
|
||
|
||
//
|
||
// Mark the entry as invalid
|
||
//
|
||
|
||
NlpCteTable[Index].Active = FALSE;
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpGetFreeEntryIndex(
|
||
OUT PULONG Index
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns the index of either a free entry,
|
||
or, lacking any free entries, the oldest active entry.
|
||
|
||
The entry is left on the list it is already on. If it
|
||
is used by the caller, then the caller must ensure it is
|
||
re-assigned to the active list (using NlpAddEntryToActiveList()).
|
||
|
||
This routine is only callable if the cache is enabled (that is,
|
||
NlpCacheControl.Entries != 0).
|
||
|
||
Arguments:
|
||
|
||
Index - Receives the index of the next available entry.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// See if the Inactive list is empty.
|
||
//
|
||
|
||
if (NlpInactiveCtes.Flink != &NlpInactiveCtes) {
|
||
(*Index) = ((PNLP_CTE)(NlpInactiveCtes.Flink))->Index;
|
||
} else {
|
||
|
||
//
|
||
// Have to return the oldest active entry.
|
||
//
|
||
|
||
(*Index) = ((PNLP_CTE)(NlpActiveCtes.Blink))->Index;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Diagnostic support services //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////
|
||
|
||
//
|
||
// diagnostic dump routines
|
||
//
|
||
|
||
#if DBG
|
||
|
||
PCHAR
|
||
DumpOwfPasswordToString(
|
||
OUT PCHAR Buffer,
|
||
IN PLM_OWF_PASSWORD Password
|
||
)
|
||
{
|
||
int i;
|
||
PCHAR bufptr;
|
||
|
||
for (i = 0, bufptr = Buffer; i < sizeof(*Password); ++i) {
|
||
sprintf(bufptr, "%02.2x ", ((PCHAR)Password)[i] & 0xff);
|
||
bufptr += 3;
|
||
}
|
||
return Buffer;
|
||
}
|
||
|
||
|
||
VOID
|
||
DumpLogonInfo(
|
||
IN PNETLOGON_LOGON_IDENTITY_INFO LogonInfo
|
||
)
|
||
{
|
||
CHAR ntOwfBuffer[64];
|
||
CHAR lmOwfBuffer[64];
|
||
|
||
DbgPrint( "\n"
|
||
"NETLOGON_INTERACTIVE_INFO:\n"
|
||
"DomainName : \"%*.*ws\"\n"
|
||
"UserName : \"%*.*ws\"\n"
|
||
"Parm Ctrl : %u (%x)\n"
|
||
"LogonId : %u.%u (%x.%x)\n"
|
||
"Workstation : \"%*.*ws\"\n",
|
||
LogonInfo->LogonDomainName.Length/sizeof(WCHAR),
|
||
LogonInfo->LogonDomainName.Length/sizeof(WCHAR),
|
||
LogonInfo->LogonDomainName.Buffer,
|
||
LogonInfo->UserName.Length/sizeof(WCHAR),
|
||
LogonInfo->UserName.Length/sizeof(WCHAR),
|
||
LogonInfo->UserName.Buffer,
|
||
LogonInfo->ParameterControl,
|
||
LogonInfo->ParameterControl,
|
||
LogonInfo->LogonId.HighPart,
|
||
LogonInfo->LogonId.LowPart,
|
||
LogonInfo->LogonId.HighPart,
|
||
LogonInfo->LogonId.LowPart,
|
||
LogonInfo->Workstation.Length/sizeof(WCHAR),
|
||
LogonInfo->Workstation.Length/sizeof(WCHAR),
|
||
LogonInfo->Workstation.Buffer
|
||
);
|
||
}
|
||
|
||
|
||
char*
|
||
MapWeekday(
|
||
IN CSHORT Weekday
|
||
)
|
||
{
|
||
switch (Weekday) {
|
||
case 0: return "Sunday";
|
||
case 1: return "Monday";
|
||
case 2: return "Tuesday";
|
||
case 3: return "Wednesday";
|
||
case 4: return "Thursday";
|
||
case 5: return "Friday";
|
||
case 6: return "Saturday";
|
||
}
|
||
return "???";
|
||
}
|
||
|
||
|
||
VOID
|
||
DumpTime(
|
||
IN LPSTR String,
|
||
IN POLD_LARGE_INTEGER OldTime
|
||
)
|
||
{
|
||
TIME_FIELDS tf;
|
||
LARGE_INTEGER Time;
|
||
|
||
OLD_TO_NEW_LARGE_INTEGER( (*OldTime), Time );
|
||
|
||
RtlTimeToTimeFields(&Time, &tf);
|
||
DbgPrint("%s%02d:%02d:%02d.%03d %02d/%02d/%d (%s [%d])\n",
|
||
String,
|
||
tf.Hour,
|
||
tf.Minute,
|
||
tf.Second,
|
||
tf.Milliseconds,
|
||
tf.Month,
|
||
tf.Day,
|
||
tf.Year,
|
||
MapWeekday(tf.Weekday),
|
||
tf.Weekday
|
||
);
|
||
}
|
||
|
||
|
||
VOID
|
||
DumpGroupIds(
|
||
IN LPSTR String,
|
||
IN ULONG Count,
|
||
IN PGROUP_MEMBERSHIP GroupIds
|
||
)
|
||
{
|
||
DbgPrint(String);
|
||
if (!Count) {
|
||
DbgPrint("No group IDs!\n");
|
||
} else {
|
||
char tab[80];
|
||
|
||
memset(tab, ' ', strlen(String));
|
||
// tab[strcspn(String, "%")] = 0;
|
||
tab[strlen(String)] = 0;
|
||
while (Count--) {
|
||
DbgPrint("%d, %d\n", GroupIds->RelativeId, GroupIds->Attributes);
|
||
if (Count) {
|
||
DbgPrint(tab);
|
||
}
|
||
++GroupIds;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
VOID
|
||
DumpSessKey(
|
||
IN LPSTR String,
|
||
IN PUSER_SESSION_KEY Key
|
||
)
|
||
{
|
||
int len;
|
||
DbgPrint(String);
|
||
DbgPrint("%02.2x-%02.2x-%02.2x-%02.2x-%02.2x-%02.2x-%02.2x-%02.2x\n",
|
||
((PUCHAR)&Key->data[0])[0],
|
||
((PUCHAR)&Key->data[0])[1],
|
||
((PUCHAR)&Key->data[0])[2],
|
||
((PUCHAR)&Key->data[0])[3],
|
||
((PUCHAR)&Key->data[0])[4],
|
||
((PUCHAR)&Key->data[0])[5],
|
||
((PUCHAR)&Key->data[0])[6],
|
||
((PUCHAR)&Key->data[0])[7]
|
||
);
|
||
len = strlen(String);
|
||
DbgPrint("%-*.*s", len, len, "");
|
||
DbgPrint("%02.2x-%02.2x-%02.2x-%02.2x-%02.2x-%02.2x-%02.2x-%02.2x\n",
|
||
((PUCHAR)&Key->data[1])[0],
|
||
((PUCHAR)&Key->data[1])[1],
|
||
((PUCHAR)&Key->data[1])[2],
|
||
((PUCHAR)&Key->data[1])[3],
|
||
((PUCHAR)&Key->data[1])[4],
|
||
((PUCHAR)&Key->data[1])[5],
|
||
((PUCHAR)&Key->data[1])[6],
|
||
((PUCHAR)&Key->data[1])[7]
|
||
);
|
||
}
|
||
|
||
|
||
VOID
|
||
DumpSid(
|
||
LPSTR String,
|
||
PISID Sid
|
||
)
|
||
{
|
||
char tab[80];
|
||
int i;
|
||
PULONG psa;
|
||
|
||
DbgPrint(String);
|
||
memset(tab, ' ', strlen(String));
|
||
tab[strlen(String)] = 0;
|
||
DbgPrint( "Revision : %d\n"
|
||
"%s"
|
||
"SubAuthorityCount : %d\n"
|
||
"%s"
|
||
"IdentifierAuthority : %d-%d-%d-%d-%d-%d\n",
|
||
Sid->Revision,
|
||
tab,
|
||
Sid->SubAuthorityCount,
|
||
tab,
|
||
((PUCHAR)&Sid->IdentifierAuthority)[0],
|
||
((PUCHAR)&Sid->IdentifierAuthority)[1],
|
||
((PUCHAR)&Sid->IdentifierAuthority)[2],
|
||
((PUCHAR)&Sid->IdentifierAuthority)[3],
|
||
((PUCHAR)&Sid->IdentifierAuthority)[4],
|
||
((PUCHAR)&Sid->IdentifierAuthority)[5]
|
||
);
|
||
psa = (PULONG)&Sid->SubAuthority;
|
||
for (i=0; i<(int)Sid->SubAuthorityCount; ++i) {
|
||
DbgPrint(
|
||
"%s"
|
||
"SubAuthority : %d\n",
|
||
tab,
|
||
*psa++
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
VOID
|
||
DumpAccountInfo(
|
||
IN PNETLOGON_VALIDATION_SAM_INFO2 AccountInfo
|
||
)
|
||
{
|
||
DbgPrint( "\n"
|
||
"NETLOGON_VALIDATION_SAM_INFO:\n");
|
||
|
||
DumpTime( "LogonTime : ", &AccountInfo->LogonTime);
|
||
|
||
DumpTime( "LogoffTime : ", &AccountInfo->LogoffTime);
|
||
|
||
DumpTime( "KickOffTime : ", &AccountInfo->KickOffTime);
|
||
|
||
DumpTime( "PasswordLastSet : ", &AccountInfo->PasswordLastSet);
|
||
|
||
DumpTime( "PasswordCanChange : ", &AccountInfo->PasswordCanChange);
|
||
|
||
DumpTime( "PasswordMustChange : ", &AccountInfo->PasswordMustChange);
|
||
|
||
DbgPrint( "EffectiveName : \"%*.*ws\"\n"
|
||
"FullName : \"%*.*ws\"\n"
|
||
"LogonScript : \"%*.*ws\"\n"
|
||
"ProfilePath : \"%*.*ws\"\n"
|
||
"HomeDirectory : \"%*.*ws\"\n"
|
||
"HomeDirectoryDrive : \"%*.*ws\"\n"
|
||
"LogonCount : %d\n"
|
||
"BadPasswordCount : %d\n"
|
||
"UserId : %d\n"
|
||
"PrimaryGroupId : %d\n"
|
||
"GroupCount : %d\n",
|
||
AccountInfo->EffectiveName.Length/sizeof(WCHAR),
|
||
AccountInfo->EffectiveName.Length/sizeof(WCHAR),
|
||
AccountInfo->EffectiveName.Buffer,
|
||
AccountInfo->FullName.Length/sizeof(WCHAR),
|
||
AccountInfo->FullName.Length/sizeof(WCHAR),
|
||
AccountInfo->FullName.Buffer,
|
||
AccountInfo->LogonScript.Length/sizeof(WCHAR),
|
||
AccountInfo->LogonScript.Length/sizeof(WCHAR),
|
||
AccountInfo->LogonScript.Buffer,
|
||
AccountInfo->ProfilePath.Length/sizeof(WCHAR),
|
||
AccountInfo->ProfilePath.Length/sizeof(WCHAR),
|
||
AccountInfo->ProfilePath.Buffer,
|
||
AccountInfo->HomeDirectory.Length/sizeof(WCHAR),
|
||
AccountInfo->HomeDirectory.Length/sizeof(WCHAR),
|
||
AccountInfo->HomeDirectory.Buffer,
|
||
AccountInfo->HomeDirectoryDrive.Length/sizeof(WCHAR),
|
||
AccountInfo->HomeDirectoryDrive.Length/sizeof(WCHAR),
|
||
AccountInfo->HomeDirectoryDrive.Buffer,
|
||
AccountInfo->LogonCount,
|
||
AccountInfo->BadPasswordCount,
|
||
AccountInfo->UserId,
|
||
AccountInfo->PrimaryGroupId,
|
||
AccountInfo->GroupCount
|
||
);
|
||
|
||
DumpGroupIds("GroupIds : ",
|
||
AccountInfo->GroupCount,
|
||
AccountInfo->GroupIds
|
||
);
|
||
|
||
DbgPrint( "UserFlags : 0x%08x\n",
|
||
AccountInfo->UserFlags
|
||
);
|
||
|
||
DumpSessKey("UserSessionKey : ", &AccountInfo->UserSessionKey);
|
||
|
||
DbgPrint( "LogonServer : \"%*.*ws\"\n"
|
||
"LogonDomainName : \"%*.*ws\"\n",
|
||
AccountInfo->LogonServer.Length/sizeof(WCHAR),
|
||
AccountInfo->LogonServer.Length/sizeof(WCHAR),
|
||
AccountInfo->LogonServer.Buffer,
|
||
AccountInfo->LogonDomainName.Length/sizeof(WCHAR),
|
||
AccountInfo->LogonDomainName.Length/sizeof(WCHAR),
|
||
AccountInfo->LogonDomainName.Buffer
|
||
);
|
||
|
||
DumpSid( "LogonDomainId : ", (PISID)AccountInfo->LogonDomainId);
|
||
}
|
||
|
||
|
||
VOID
|
||
DumpCacheEntry(
|
||
IN ULONG Index,
|
||
IN PLOGON_CACHE_ENTRY pEntry
|
||
)
|
||
{
|
||
PUCHAR dataptr;
|
||
ULONG length;
|
||
|
||
DbgPrint( "\n"
|
||
"LOGON_CACHE_ENTRY:\n"
|
||
"CTE Index : %d\n", Index);
|
||
|
||
if (pEntry->Valid != TRUE) {
|
||
DbgPrint( "State : INVALID\n");
|
||
return;
|
||
}
|
||
|
||
dataptr = (PUCHAR)(pEntry+1);
|
||
|
||
length = pEntry->UserNameLength;
|
||
|
||
DbgPrint( "State : VALID\n");
|
||
DbgPrint( "UserName : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
length = pEntry->DomainNameLength;
|
||
DbgPrint( "DomainName : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
length = pEntry->EffectiveNameLength;
|
||
DbgPrint( "EffectiveName : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
length = pEntry->FullNameLength;
|
||
DbgPrint( "FullName : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
length = pEntry->LogonScriptLength;
|
||
DbgPrint( "LogonScript : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
length = pEntry->ProfilePathLength;
|
||
DbgPrint( "ProfilePath : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
length = pEntry->HomeDirectoryLength;
|
||
DbgPrint( "HomeDirectory : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
length = pEntry->HomeDirectoryDriveLength;
|
||
DbgPrint( "HomeDirectoryDrive : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
DbgPrint( "UserId : %d\n"
|
||
"PrimaryGroupId : %d\n"
|
||
"GroupCount : %d\n",
|
||
pEntry->UserId,
|
||
pEntry->PrimaryGroupId,
|
||
pEntry->GroupCount
|
||
);
|
||
|
||
DumpGroupIds(
|
||
"GroupIds : ",
|
||
pEntry->GroupCount,
|
||
(PGROUP_MEMBERSHIP)dataptr
|
||
);
|
||
|
||
dataptr = ROUND_UP_POINTER((dataptr+pEntry->GroupCount * sizeof(GROUP_MEMBERSHIP)), sizeof(ULONG));
|
||
|
||
length = pEntry->LogonDomainNameLength;
|
||
DbgPrint( "LogonDomainName : \"%*.*ws\"\n", length/2, length/2, dataptr);
|
||
dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
|
||
|
||
|
||
if (pEntry->SidCount) {
|
||
ULONG i, sidLength;
|
||
PULONG SidAttributes = (PULONG) dataptr;
|
||
|
||
dataptr = ROUND_UP_POINTER(dataptr + pEntry->SidCount * sizeof(ULONG), sizeof(ULONG));
|
||
for (i = 0; i < pEntry->SidCount ; i++ ) {
|
||
sidLength = RtlLengthSid ((PSID) dataptr);
|
||
DumpSid("Sid : ",(PISID) dataptr);
|
||
DbgPrint("\tAttributes = 0x%x\n",SidAttributes[i]);
|
||
dataptr = ROUND_UP_POINTER(dataptr + sidLength, sizeof(ULONG));
|
||
}
|
||
|
||
}
|
||
|
||
DumpSid( "LogonDomainId : ", (PISID)dataptr);
|
||
}
|
||
#endif
|