/*++

Copyright (c) 1991 - 1999  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

Revision History:

   Scott Field (sfield)   04-Jun-99
        Add supplemental cache data.
        Store all cache related data in single location.
        Encrypt interesting elements of cache entry using per-entry key mixed with per-machine key.
        MAC interesting cache elements for integrity check.
        Drastically reduce lock contention.
        Avoid NtFlushKey() for single location cache elements.
        Avoid persisting a new cache entry that matches an existing one.
        Attempt reg query with stack based buffer first.

   Chandana Surlu         21-Jul-96      Stolen from \\kernel\razzle3\src\security\msv1_0\nlpcache.c

--*/

#include <global.h>
#undef EXTERN

#include "msp.h"
#include "nlp.h"
#include "nlpcache.h"



//
// manifests
//

#if DBG
#include <stdio.h>
#endif

//
// Revision numbers
//
//      NT 3.0 didn't explicitly store a revision number.
//          However, we are designating that release to be revision 0x00010000 (1.0).
//      NT 3.5 prior to build 622 is revision 0x00010001 (1.1).
//      NT 3.5 is revision 0x00010002 (1.2).
//      NT 4.0 SP 4 is revision 0x00010003 (1.3)
//      NT 5.0 build 2054+ is revision 0x00010004 (1.4)
//

#define NLP_CACHE_REVISION_NT_1_0         (0x00010000)  // NT 3.0
#define NLP_CACHE_REVISION_NT_1_0B        (0x00010002)  // NT 3.5
#define NLP_CACHE_REVISION_NT_4_SP4       (0x00010003)  // NT 4.0 SP 4 to save passwords as salted.
#define NLP_CACHE_REVISION_NT_5_0         (0x00010004)  // NT 5.0 to support opaque cache data and single location data storage.
#define NLP_CACHE_REVISION                (NLP_CACHE_REVISION_NT_5_0)





//
// 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)


//
// length of per-machine cache encryption key.
//

#define NLP_CACHE_ENCRYPTION_KEY_LEN            (64)

//
// name of LSA secret containing cache encryption key.
//

#define NLP_CACHE_ENCRYPTION_KEY_NAME           L"NL$KM"


//
// macros
//

#define AllocateCacheEntry(n)   (PLOGON_CACHE_ENTRY)I_NtLmAllocate(n)
#define FreeCacheEntry(p)       I_NtLmFree((PVOID)p)
#define AllocateFromHeap(n)     I_NtLmAllocate(n)
#define FreeToHeap(p)           I_NtLmFree((PVOID)p)

//
// guard against simultaneous access
//

#define READ_CACHE()            RtlAcquireResourceShared(&NlpLogonCacheCritSec, TRUE)
#define WRITE_CACHE()           RtlAcquireResourceExclusive(&NlpLogonCacheCritSec, TRUE)
#define READ_TO_WRITE_CACHE()   RtlConvertSharedToExclusive(&NlpLogonCacheCritSec)
#define LEAVE_CACHE()           RtlReleaseResource(&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;
    PLSAPR_CR_CIPHER_VALUE      NewSecret;
    PLSAPR_CR_CIPHER_VALUE      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
NlpCacheKeyInitialize(
    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
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_INFO4 AccountInfo,
    IN  ULONG CacheFlags,
    OUT PLOGON_CACHE_ENTRY* ppCacheEntry,
    OUT PULONG pEntryLength
    );

BOOLEAN
NlpCompareCacheEntry(
    IN  PLOGON_CACHE_ENTRY CacheEntry1,
    IN  ULONG EntrySize1,
    IN  PLOGON_CACHE_ENTRY CacheEntry2,
    IN  ULONG EntrySize2
    );

NTSTATUS
NlpEncryptCacheEntry(
    IN  PLOGON_CACHE_ENTRY CacheEntry,
    IN  ULONG EntrySize
    );

NTSTATUS
NlpDecryptCacheEntry(
    IN  PLOGON_CACHE_ENTRY CacheEntry,
    IN  ULONG EntrySize
    );

NTSTATUS
NlpAddSupplementalCacheData(
    IN      PVOID SupplementalCacheData,
    IN      ULONG SupplementalCacheDataLength,
    IN OUT  PLOGON_CACHE_ENTRY *ppCacheEntry,
    IN OUT  PULONG pEntryLength
    );

NTSTATUS
NlpOpenCache( VOID );

VOID
NlpCloseCache( VOID );

NTSTATUS
NlpOpenSecret(
    IN  ULONG   Index
    );

VOID
NlpCloseSecret( VOID );

NTSTATUS
NlpWriteSecret(
    IN  PLSAPR_CR_CIPHER_VALUE NewSecret,
    IN  PLSAPR_CR_CIPHER_VALUE OldSecret
    );

NTSTATUS
NlpReadSecret(
    OUT PLSAPR_CR_CIPHER_VALUE * NewSecret,
    OUT PLSAPR_CR_CIPHER_VALUE * OldSecret
    );

NTSTATUS
NlpMakeSecretPassword(
    OUT PLSAPR_CR_CIPHER_VALUE Passwords,
    IN  PUNICODE_STRING UserName,
    IN  PNT_OWF_PASSWORD NtOwfPassword OPTIONAL,
    IN  PLM_OWF_PASSWORD LmOwfPassword OPTIONAL
    );

NTSTATUS
NlpMakeSecretPasswordNT5(
    IN OUT PCACHE_PASSWORDS Passwords,
    IN  PUNICODE_STRING UserName,
    IN  PNT_OWF_PASSWORD NtOwfPassword OPTIONAL,
    IN  PLM_OWF_PASSWORD LmOwfPassword OPTIONAL
    );


BOOLEAN
NlpCheckMitCacheEntry(
    IN PLOGON_CACHE_ENTRY CacheEntry,
    IN PUNICODE_STRING DomainName,
    IN PUNICODE_STRING UserName
    );   


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_INFO4* 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_INFO4 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_RESOURCE    NlpLogonCacheCritSec;



HANDLE          NlpCacheHandle  = (HANDLE)     INVALID_HANDLE_VALUE;
LSAPR_HANDLE    NlpSecretHandle = (LSAPR_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;


//
// global, per-machine key used for encrypting NT_5_0 version cache
// entries.
//

CHAR                NlpCacheEncryptionKey[ NLP_CACHE_ENCRYPTION_KEY_LEN ];



#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

--*/

{
    RtlInitializeResource(&NlpLogonCacheCritSec);
    return STATUS_SUCCESS;
}


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(NlpCacheHandle)) {
            NtClose( NlpCacheHandle );
        }

        FreeToHeap( NlpCteTable );
    }

    RtlDeleteResource(&NlpLogonCacheCritSec);
    return STATUS_SUCCESS;
}


NTSTATUS
NlpGetCacheEntry(
    IN  PNETLOGON_LOGON_IDENTITY_INFO LogonInfo,
    OUT PNETLOGON_VALIDATION_SAM_INFO4* AccountInfo,
    OUT PCACHE_PASSWORDS Passwords,
    OUT PVOID *ppSupplementalCacheData OPTIONAL ,
    OUT PULONG SupplementalCacheDataLength OPTIONAL
    )

/*++

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_INFO4 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_INFO4
                    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_INFO4
        SamInfo = NULL;

    PLOGON_CACHE_ENTRY
        CacheEntry = NULL;

    ULONG
        EntrySize,
        Index;

    PLSAPR_CR_CIPHER_VALUE
        CurrentSecret = NULL,
        OldSecret = NULL;


    BOOLEAN fCacheLocked = FALSE;


    *AccountInfo = NULL;

    if( ppSupplementalCacheData )
        *ppSupplementalCacheData = 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);
    }

    //
    // TODO: consider comparing LogonDomainName to NlpSamDomainName
    // and failing cached logon attempts at local machine.
    //

    READ_CACHE();
    fCacheLocked = TRUE;

    //
    // Find the cache entry and open its secret (if found)
    //

    NtStatus = NlpReadCacheEntry(&LogonInfo->LogonDomainName,
                                 &LogonInfo->UserName,
                                 &Index,
                                 &CacheEntry,
                                 &EntrySize);

    if(!NT_SUCCESS(NtStatus)) {
        LEAVE_CACHE();
        return (NtStatus);
    }

    if( CacheEntry->Revision >= NLP_CACHE_REVISION_NT_5_0 ) {

        //
        // for NT5, we can release the cache lock now, since all data
        // stored in one place.
        //

        LEAVE_CACHE();
        fCacheLocked = FALSE;

        //
        // if caller wanted supplemental data, give it to them.
        //

        if( ppSupplementalCacheData && SupplementalCacheDataLength )
        {
            LPBYTE Source;

            *SupplementalCacheDataLength = CacheEntry->SupplementalCacheDataLength;

            *ppSupplementalCacheData = MIDL_user_allocate( *SupplementalCacheDataLength );

            if( *ppSupplementalCacheData == NULL ) {
                NtStatus = STATUS_NO_MEMORY;
                goto Cleanup;
            }

            //
            // note: the decrypt operation that occurred during the
            // ReadCacheEntry validates any data and pointers through
            // integrity checking via HMAC.  Having said that, we can be
            // lazy and not do boundry checking.
            //

            Source = ((LPBYTE)CacheEntry + CacheEntry->SupplementalCacheDataOffset);

            CopyMemory( *ppSupplementalCacheData,
                        Source,
                        *SupplementalCacheDataLength
                        );
        }

    }


    NtStatus = NlpBuildAccountInfo(CacheEntry, EntrySize, &SamInfo);
    if (!NT_SUCCESS(NtStatus))
    {
        goto Cleanup;
    }


    if( CacheEntry->Revision >= NLP_CACHE_REVISION_NT_5_0 ) {

        //
        // for NT5, the Passwords are stored in the CacheEntry.
        // note: passwords are assumed to be salted.
        //

        RtlCopyMemory( Passwords, &(CacheEntry->CachePasswords), sizeof(*Passwords) );


    } else {

        //
        // prior to NT5, the Passwords are stored separately in their
        // own LSA secret.
        //

        NtStatus = NlpReadSecret(&CurrentSecret, &OldSecret);

        if(!NT_SUCCESS(NtStatus))
        {
            goto Cleanup;
        }

        if ( CurrentSecret == NULL )
        {
            NtStatus = STATUS_LOGON_FAILURE;
            goto Cleanup;
        }

        //
        // can release the cache lock now, since second data item fetched.
        //

        LEAVE_CACHE();
        fCacheLocked = FALSE;


        //
        // Check to see which version of the passwords are stored
        // here - the normal or the salted.
        //

        RtlCopyMemory((PVOID)Passwords,
            (PVOID)CurrentSecret->Buffer,
            (ULONG)CurrentSecret->Length
            );

        if ( CacheEntry->Revision < NLP_CACHE_REVISION_NT_4_SP4 )
        {
            if (Passwords->SecretPasswords.NtPasswordPresent)
            {
                NtStatus = NlpComputeSaltedHashedPassword(
                                &Passwords->SecretPasswords.NtOwfPassword,
                                &Passwords->SecretPasswords.NtOwfPassword,
                                &SamInfo->EffectiveName
                                );
                if(!NT_SUCCESS(NtStatus))
                {
                    goto Cleanup;
                }
            }

            if (Passwords->SecretPasswords.LmPasswordPresent)
            {
                NtStatus = NlpComputeSaltedHashedPassword(
                                &Passwords->SecretPasswords.LmOwfPassword,
                                &Passwords->SecretPasswords.LmOwfPassword,
                                &SamInfo->EffectiveName
                                );

                if(!NT_SUCCESS(NtStatus))
                {
                    goto Cleanup;
                }
            }
        }
    }


Cleanup:

    if( fCacheLocked ) {
        LEAVE_CACHE();
    }


    //
    // free structure allocated by NlpReadCacheEntry
    //

    if( CacheEntry ) {
        ZeroMemory( CacheEntry, EntrySize );
        FreeToHeap(CacheEntry);
    }


    //
    // free structures allocated by NlpReadSecret
    //

    if (CurrentSecret) {
        MIDL_user_free(CurrentSecret);
    }
    if (OldSecret) {
        MIDL_user_free(OldSecret);
    }


    if( NT_SUCCESS( NtStatus ) ) {
        *AccountInfo = SamInfo;
    } else {

        if ( SamInfo != NULL ) {
            MIDL_user_free( SamInfo );
        }

        if( ppSupplementalCacheData && *ppSupplementalCacheData ) {
            MIDL_user_free( *ppSupplementalCacheData );
            *ppSupplementalCacheData = NULL;
        }
    }

    return(NtStatus);
}


NTSTATUS
NlpAddCacheEntry(
    IN  PNETLOGON_INTERACTIVE_INFO LogonInfo,
    IN  PNETLOGON_VALIDATION_SAM_INFO4 AccountInfo,
    IN  PVOID SupplementalCacheData,
    IN  ULONG SupplementalCacheDataLength,
    IN  ULONG CacheFlags
    )

/*++

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_INFO4 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 = NULL;

    PLOGON_CACHE_ENTRY
        CacheEntryExisting = NULL;

    ULONG
        EntrySize,
        EntrySizeExisting,
        Index;


    PUNICODE_STRING LogonDomainName;
    UNICODE_STRING NullString = {0,0,NULL};

    BOOLEAN fCacheLocked = FALSE;

#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);
    }


    //
    // LogonUser() allows for a NULL domain name to be supplied, which
    // causes netlogon search logic to kick in.  this can result to logon
    // packages requesting to cache local account information.
    // In this case, use the LogonDomainName that SAM provides to make
    // a decision about whether to allow caching.  In the same scenario,
    // if we decide caching is allowed, the cache entry target domain
    // is also set based on what SAM returned.
    //
    // For MIT logons, don't use logon info.   
    //
    // 
    if ((CacheFlags & MSV1_0_CACHE_LOGON_REQUEST_MIT_LOGON) != 0) {
        LogonDomainName = &(NullString);      
    }                                   
    else if( LogonInfo->Identity.LogonDomainName.Length != 0 )  {    
        LogonDomainName = &(LogonInfo->Identity.LogonDomainName);
    } else {
        LogonDomainName = &(AccountInfo->LogonDomainName);
    }

    if( NlpSamDomainName.Buffer &&
        RtlEqualDomainName(LogonDomainName, &NlpSamDomainName)
        )
    {

#if DBG
        if (DumpCacheInfo) {
            DbgPrint("NlpAddCacheEntry: attempt to cache against local account skipped.\n");
        }
#endif
        return STATUS_SUCCESS;
    }

    //
    // build base cache entry.
    //

    NtStatus = NlpBuildCacheEntry(
                LogonInfo, 
                AccountInfo, 
                CacheFlags, 
                &CacheEntry, 
                &EntrySize
                );

    if(!NT_SUCCESS(NtStatus) )
    {
        return (NtStatus);
    }



    //
    // add any supplemental data to the cache entry.
    // (this is new for NT5).
    //

    NtStatus = NlpAddSupplementalCacheData(
                            SupplementalCacheData,
                            SupplementalCacheDataLength,
                            &CacheEntry,
                            &EntrySize
                            );


    if(!NT_SUCCESS(NtStatus)) {
        goto Cleanup;
    }

    //
    // add in salted OWFs.
    //

    NtStatus = NlpMakeSecretPasswordNT5(
                            &CacheEntry->CachePasswords,
                            &AccountInfo->EffectiveName,
                            &LogonInfo->NtOwfPassword,
                            &LogonInfo->LmOwfPassword
                            );

    if(!NT_SUCCESS(NtStatus)) {
        goto Cleanup;
    }




    READ_CACHE();
    fCacheLocked = TRUE;

    //
    // See if this entry already exists in the cache.
    // If so, use the same index.
    //

    NtStatus = NlpReadCacheEntry( LogonDomainName,
                                  &LogonInfo->Identity.UserName,
                                  &Index,
                                  &CacheEntryExisting,
                                  &EntrySizeExisting
                                  );

    //
    // If we didn't find an entry, then we need to allocate an
    // entry.
    //

    if (!NT_SUCCESS(NtStatus)) {

        NlpGetFreeEntryIndex( &Index );

        CacheEntryExisting = NULL;

    } else {

        //
        // We already have an entry for this user.
        // Discard the structure we got back but
        // use the same index.
        //

        // TODO: check if existing entry matches new built entry.
        // if so, avoid write.

        BOOLEAN fMatchesExisting;

        fMatchesExisting = NlpCompareCacheEntry(
                                    CacheEntry,
                                    EntrySize,
                                    CacheEntryExisting,
                                    EntrySizeExisting
                                    );

        if( fMatchesExisting )
        {
            goto Cleanup;
        }
    }


    //
    // encrypt sensitive portions of the cache entry.
    // note: this was done prior to locking the cache, but, in the interest
    // of allowing for cache compare above, the encryption is deferred until
    // now.
    //

    NtStatus = NlpEncryptCacheEntry(CacheEntry, EntrySize);

    if(!NT_SUCCESS(NtStatus)) {
        goto Cleanup;
    }


    //
    // we already have the read lock, convert it to write-lock.
    //

    READ_TO_WRITE_CACHE();


    //
    // now, write the entry out...
    //

    NtStatus = NlpWriteCacheEntry(Index, CacheEntry, EntrySize);


    if (NT_SUCCESS(NtStatus)) {
        NlpCteTable[Index].Time = CacheEntry->Time;
        NlpAddEntryToActiveList( Index );
    }



Cleanup:

    if( fCacheLocked )
    {
        LEAVE_CACHE();
    }


    if( CacheEntry ) {
        ZeroMemory( CacheEntry, EntrySize );
        FreeCacheEntry( CacheEntry );
    }

    if( CacheEntryExisting ) {
        ZeroMemory( CacheEntryExisting, EntrySizeExisting );
        FreeCacheEntry( CacheEntryExisting );
    }

    return(NtStatus);
}



NTSTATUS
NlpAddSupplementalCacheData(
    IN      PVOID SupplementalCacheData,
    IN      ULONG SupplementalCacheDataLength,
    IN OUT  PLOGON_CACHE_ENTRY *ppCacheEntry,
    IN OUT  PULONG pEntryLength
    )

/*++

Routine Description:

    Extends the supplied LOGON_CACHE_ENTRY with opaque authentication package
    SupplementalCacheData (eg: smart-card logon cache info).

Return Value:

    NTSTATUS
        Success = STATUS_SUCCESS

        Failure =

--*/

{
    PLOGON_CACHE_ENTRY NewCacheEntry = NULL;

    if( (*ppCacheEntry)->Revision < NLP_CACHE_REVISION_NT_5_0 ) {
        return STATUS_SUCCESS;
    }

    (*ppCacheEntry)->SupplementalCacheDataLength = SupplementalCacheDataLength;
    (*ppCacheEntry)->SupplementalCacheDataOffset = *pEntryLength;


    if( SupplementalCacheData == NULL || SupplementalCacheDataLength == 0 ) {
        return STATUS_SUCCESS;
    }


    //
    // allocate new entry, and copy existing entry + supplemental data to end.
    //

    NewCacheEntry = AllocateCacheEntry( *pEntryLength + SupplementalCacheDataLength );

    if( NewCacheEntry == NULL ) {
        return STATUS_NO_MEMORY;
    }

    CopyMemory( NewCacheEntry, *ppCacheEntry, *pEntryLength );
    CopyMemory( ((PBYTE)(NewCacheEntry) + *pEntryLength),
                SupplementalCacheData,
                SupplementalCacheDataLength
                );

    ZeroMemory( *ppCacheEntry, *pEntryLength );
    FreeCacheEntry( *ppCacheEntry );

    *ppCacheEntry = NewCacheEntry;
    *pEntryLength += SupplementalCacheDataLength;

    return STATUS_SUCCESS;
}


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 = NULL;

    ULONG
        EntrySize,
        Index;


    if (NlpInitializationNotYetPerformed) {
        NtStatus = NlpInternalCacheInitialize();
        if (!NT_SUCCESS(NtStatus)) {
            return(NtStatus);
        }
    }


    if (NlpCacheControl.Entries == 0) {
        return(STATUS_SUCCESS);
    }

    WRITE_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 );
    }


    LEAVE_CACHE();


    //
    // Free the structure returned from NlpReadCacheEntry()
    //

    if( CacheEntry ) {
        ZeroMemory( CacheEntry, EntrySize );
        FreeToHeap( CacheEntry );
    }


    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 = NULL;

    ULONG
        EntrySize,
        Index;

    PLSAPR_CR_CIPHER_VALUE
        CurrentSecret = NULL,
        OldSecret = NULL;

    LSAPR_CR_CIPHER_VALUE
        Passwords;


#if DBG
    if (DumpCacheInfo) {
        DbgPrint("NlpChangeCachePassword\n");
    }
#endif

    if (NlpInitializationNotYetPerformed) {
        NtStatus = NlpInternalCacheInitialize();
        if (!NT_SUCCESS(NtStatus)) {
            return;
        }
    }


    if (NlpCacheControl.Entries == 0) {
        return;
    }

    WRITE_CACHE();


    NtStatus = NlpReadCacheEntry( DomainName,
                                  UserName,
                                  &Index,
                                  &CacheEntry,
                                  &EntrySize);

    if(!NT_SUCCESS( NtStatus) ) {
        LEAVE_CACHE();
        return ;
    }


    if( CacheEntry->Revision >= NLP_CACHE_REVISION_NT_5_0 ) {
        UNICODE_STRING CachedUser;

        CachedUser.Length =
            CachedUser.MaximumLength = CacheEntry->UserNameLength;
        CachedUser.Buffer = (PWSTR) ((PBYTE) CacheEntry + sizeof(LOGON_CACHE_ENTRY));

        NtStatus = NlpMakeSecretPasswordNT5( &CacheEntry->CachePasswords,
                                          &CachedUser,
                                          NtOwfPassword,
                                          LmOwfPassword );


        if(NT_SUCCESS(NtStatus)) {

            //
            // encrypt the entry...
            //

            NtStatus = NlpEncryptCacheEntry( CacheEntry, EntrySize );
        }

        if(NT_SUCCESS( NtStatus )) {

            //
            // now, write the entry out...
            //

            NtStatus = NlpWriteCacheEntry(Index, CacheEntry, EntrySize);

#ifdef DBG
            if(DumpCacheInfo) {
                if( NT_SUCCESS( NtStatus ) ) {
                    DbgPrint("NlpChangeCachePassword: SUCCEED write NT5 version cache entry.\n");
                } else {
                    DbgPrint("NlpChangeCachePassword: FAIL write NT5 version cache entry.\n");
                }
            }
#endif

        }

    } else {

        NtStatus = NlpOpenSecret( Index );
        if (NT_SUCCESS(NtStatus)) {

            NtStatus = NlpReadSecret(&CurrentSecret, &OldSecret);
            if (NT_SUCCESS(NtStatus)) {
                UNICODE_STRING CachedUser;

                //
                // Grab the various strings from the cache entry.
                //
                ASSERT( CacheEntry->Revision >= NLP_CACHE_REVISION_NT_1_0B );

                CachedUser.Length =
                    CachedUser.MaximumLength = CacheEntry->UserNameLength;
                CachedUser.Buffer = (PWSTR) ((PBYTE) CacheEntry + sizeof(LOGON_CACHE_ENTRY_NT_4_SP4));

                NtStatus = NlpMakeSecretPassword( &Passwords,
                                                  &CachedUser,
                                                  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);
                }

            }
        }

    }

    LEAVE_CACHE();


    //
    // free structure allocated by NlpReadCacheEntry
    //

    if( CacheEntry ) {
        ZeroMemory( CacheEntry, EntrySize );
        FreeToHeap(CacheEntry);
    }

    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;

// 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 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).
    //
    //      A global cache encryption key will be initialized.
    //



    WRITE_CACHE();

    //
    // Check again if the cache is initialized now that the crit sect is locked.
    //

    if (NlpInitializationNotYetPerformed) {

        //
        // Open the local system's policy object
        //


        //
        // 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();

            //
            // Initialize the per-machine cache encryption key.
            //

            if(NT_SUCCESS( NtStatus) ) {
                NtStatus = NlpCacheKeyInitialize();
            }

            //
            // 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 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
NlpCacheKeyInitialize(
    VOID
    )
/*++

Routine Description:

    Initializes the Global variable NlpCacheEncryptionKey with a per-machine
    cache encryption key.  If the per-machine key does not exist as an LSA
    secret, it is created.

--*/
{
    LSAPR_HANDLE SecretHandle;
    UNICODE_STRING ValueName;
    BOOLEAN SecretCreationNeeded = FALSE;
    NTSTATUS NtStatus;

    RtlInitUnicodeString( &ValueName, NLP_CACHE_ENCRYPTION_KEY_NAME );

    NtStatus = I_LsarOpenSecret(NtLmGlobalPolicyHandle,
                             (PLSAPR_UNICODE_STRING) &ValueName,
                             SECRET_QUERY_VALUE | SECRET_SET_VALUE,
                             &SecretHandle
                             );

    if (!NT_SUCCESS(NtStatus)) {

        //
        // create new key, if not present.
        //

        if (NtStatus != STATUS_OBJECT_NAME_NOT_FOUND) {
            return (NtStatus);
        }

        NtStatus = I_LsarCreateSecret(NtLmGlobalPolicyHandle,
                                   (PLSAPR_UNICODE_STRING) &ValueName,
                                   SECRET_SET_VALUE,
                                   &SecretHandle
                                   );

        if (!NT_SUCCESS(NtStatus)) {
            return (NtStatus);
        }

        SecretCreationNeeded = TRUE;

    } else {

        //
        // query current value...
        //

        LARGE_INTEGER
            CurrentTime;

        PLSAPR_CR_CIPHER_VALUE CurrentSecret = NULL;

        NtStatus = I_LsarQuerySecret(SecretHandle,
                                  &CurrentSecret,
                                  &CurrentTime,
                                  NULL,
                                  NULL
                                  );

        if(NT_SUCCESS( NtStatus ) ) {
            if( CurrentSecret == NULL ) {

                //
                // non existing data, create it.
                //

                SecretCreationNeeded = TRUE;
            } else {

                //
                // size of data is wrong, bail now and leave things as-is.
                //

                if( CurrentSecret->Length != sizeof( NlpCacheEncryptionKey ) ) {
                    NtStatus = STATUS_SECRET_TOO_LONG;
                } else {

                    //
                    // capture existing data into global.
                    //

                    CopyMemory( NlpCacheEncryptionKey, CurrentSecret->Buffer, CurrentSecret->Length );
                }

                MIDL_user_free(CurrentSecret);
            }
        }
    }


    if( SecretCreationNeeded ) {
        LSAPR_CR_CIPHER_VALUE SecretValue;

        SspGenerateRandomBits( NlpCacheEncryptionKey, sizeof(NlpCacheEncryptionKey) );

        //
        // write out secret...
        //

        SecretValue.Length = sizeof(NlpCacheEncryptionKey);
        SecretValue.MaximumLength = SecretValue.Length;
        SecretValue.Buffer = (PBYTE)NlpCacheEncryptionKey;

        NtStatus = I_LsarSetSecret(SecretHandle,
                                &SecretValue,
                                NULL
                                );

    }


    I_LsarClose( &SecretHandle );

    return (NtStatus);
}

BOOLEAN
NlpCompareCacheEntry(
    IN  PLOGON_CACHE_ENTRY CacheEntry1,
    IN  ULONG EntrySize1,
    IN  PLOGON_CACHE_ENTRY CacheEntry2,
    IN  ULONG EntrySize2
    )
/*++

Routine Description:

    Compare two in-memory cache entries, for the purpose of avoiding
    un-necessary cache updates.

    Certain fields are not taken into account during the compare,
    ie: the Random encryption key.

--*/
{
    LARGE_INTEGER Time1;
    LARGE_INTEGER Time2;
    CHAR RandomKey1[16];
    CHAR RandomKey2[16];
    CHAR MAC1[16];
    CHAR MAC2[16];

    BOOLEAN fEqual = FALSE;


    if( EntrySize1 != EntrySize2 )
    {
        return FALSE;
    }


    if( CacheEntry1->Revision != CacheEntry2->Revision )
    {
        return FALSE;
    }

    //
    // scoop up the current values of the 'volatile' fields,
    // whack them to zero,
    // do the memory compare,
    // put the saved values back.
    //

    ASSERT(( sizeof(RandomKey1) == sizeof(CacheEntry1->RandomKey) ));
    ASSERT(( sizeof(MAC1) == sizeof(CacheEntry1->MAC) ));
    ASSERT(( sizeof(Time1) == sizeof(CacheEntry1->Time) ));

    Time1 = CacheEntry1->Time;
    Time2 = CacheEntry2->Time;
    RtlZeroMemory(&CacheEntry1->Time, sizeof(CacheEntry1->Time));
    RtlZeroMemory(&CacheEntry2->Time, sizeof(CacheEntry2->Time));

    RtlCopyMemory(RandomKey1, CacheEntry1->RandomKey, sizeof(RandomKey1));
    RtlCopyMemory(RandomKey2, CacheEntry2->RandomKey, sizeof(RandomKey2));
    ZeroMemory(CacheEntry1->RandomKey, sizeof(CacheEntry1->RandomKey));
    ZeroMemory(CacheEntry2->RandomKey, sizeof(CacheEntry2->RandomKey));

    RtlCopyMemory(MAC1, CacheEntry1->MAC, sizeof(MAC1));
    RtlCopyMemory(MAC2, CacheEntry2->MAC, sizeof(MAC2));
    ZeroMemory(CacheEntry1->MAC, sizeof(CacheEntry1->MAC));
    ZeroMemory(CacheEntry2->MAC, sizeof(CacheEntry2->MAC));

    if( memcmp(CacheEntry1, CacheEntry2, EntrySize1) == 0 )
    {
        fEqual = TRUE;
    }

    CacheEntry1->Time = Time1;
    CacheEntry2->Time = Time2;

    RtlCopyMemory(CacheEntry1->RandomKey, RandomKey1, sizeof(RandomKey1));
    RtlCopyMemory(CacheEntry2->RandomKey, RandomKey2, sizeof(RandomKey2));

    RtlCopyMemory(CacheEntry1->MAC, MAC1, sizeof(MAC1));
    RtlCopyMemory(CacheEntry2->MAC, MAC2, sizeof(MAC2));

    return fEqual;
}


NTSTATUS
NlpEncryptCacheEntry(
    IN  PLOGON_CACHE_ENTRY CacheEntry,
    IN  ULONG EntrySize
    )
/*++

Routine Description:

    Encrypts the sensitive portions of the input CacheEntry.

--*/
{
    HMACMD5_CTX hmacCtx;
    RC4_KEYSTRUCT rc4key;
    CHAR DerivedKey[ MD5DIGESTLEN ];

    PBYTE pbData;
    ULONG cbData;

    if( CacheEntry->Revision < NLP_CACHE_REVISION_NT_5_0 ) {
        return STATUS_SUCCESS;
    }


    //
    // derive encryption key from global machine LSA secret, and random
    // cache entry key.
    //

    HMACMD5Init(&hmacCtx, NlpCacheEncryptionKey, sizeof(NlpCacheEncryptionKey));
    HMACMD5Update(&hmacCtx, CacheEntry->RandomKey, sizeof(CacheEntry->RandomKey));
    HMACMD5Final(&hmacCtx, DerivedKey);


    //
    // begin encrypting at the cachepasswords field.
    //

    pbData = (PBYTE)&(CacheEntry->CachePasswords);

    //
    // data length is EntrySize - header up to CachePasswords.
    //

    cbData = EntrySize - (ULONG)( pbData - (PBYTE)CacheEntry );


    //
    // MAC the data for integrity checking.
    //

    HMACMD5Init(&hmacCtx, DerivedKey, sizeof(DerivedKey));
    HMACMD5Update(&hmacCtx, pbData, cbData);
    HMACMD5Final(&hmacCtx, CacheEntry->MAC);

    //
    // now encrypt it...
    //

    rc4_key( &rc4key, sizeof(DerivedKey), DerivedKey );
    rc4( &rc4key, cbData, pbData );

    ZeroMemory( DerivedKey, sizeof(DerivedKey) );

    return STATUS_SUCCESS;
}


NTSTATUS
NlpDecryptCacheEntry(
    IN  PLOGON_CACHE_ENTRY CacheEntry,
    IN  ULONG EntrySize
    )
/*++

Routine Description:

    Decrypts the sensitive portions of the input CacheEntry, and verified
    integrity of decrypted data.

--*/
{
    HMACMD5_CTX hmacCtx;
    RC4_KEYSTRUCT rc4key;
    CHAR DerivedKey[ MD5DIGESTLEN ];

    CHAR MAC[ MD5DIGESTLEN ];

    PBYTE pbData;
    ULONG cbData;

    if( CacheEntry->Revision < NLP_CACHE_REVISION_NT_5_0 ) {
        return STATUS_SUCCESS;
    }


    //
    // derive encryption key from global machine LSA secret, and random
    // cache entry key.
    //

    HMACMD5Init(&hmacCtx, NlpCacheEncryptionKey, sizeof(NlpCacheEncryptionKey));
    HMACMD5Update(&hmacCtx, CacheEntry->RandomKey, sizeof(CacheEntry->RandomKey));
    HMACMD5Final(&hmacCtx, DerivedKey);


    //
    // begin decrypting at the cachepasswords field.
    //

    pbData = (PBYTE)&(CacheEntry->CachePasswords);

    //
    // data length is EntrySize - header up to CachePasswords.
    //

    cbData = EntrySize - (ULONG)( pbData - (PBYTE)CacheEntry );

    //
    // now decrypt it...
    //

    rc4_key( &rc4key, sizeof(DerivedKey), DerivedKey );
    rc4( &rc4key, cbData, pbData );


    //
    // compute MAC on decrypted data for integrity checking.
    //

    HMACMD5Init(&hmacCtx, DerivedKey, sizeof(DerivedKey));
    HMACMD5Update(&hmacCtx, pbData, cbData);
    HMACMD5Final(&hmacCtx, MAC);

    ZeroMemory( DerivedKey, sizeof(DerivedKey) );


    //
    // verify MAC.
    //

    if( memcmp( MAC, CacheEntry->MAC, sizeof(MAC) ) != 0 ) {
        return STATUS_LOGON_FAILURE;
    }


    return STATUS_SUCCESS;
}




NTSTATUS
NlpBuildCacheEntry(
    IN  PNETLOGON_INTERACTIVE_INFO LogonInfo,
    IN  PNETLOGON_VALIDATION_SAM_INFO4 AccountInfo,
    IN  ULONG CacheFlags,
    OUT PLOGON_CACHE_ENTRY* ppCacheEntry,
    OUT PULONG pEntryLength
    )

/*++

Routine Description:

    Builds a LOGON_CACHE_ENTRY from a NETLOGON_VALIDATION_SAM_INFO4 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_INFO4 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;

    UNICODE_STRING SamAccountName;
    UNICODE_STRING NetbiosDomainName;
    UNICODE_STRING DnsDomainName;
    UNICODE_STRING Upn;

    NTSTATUS NtStatus;


    //
    // Grab the various forms of the account name
    //

    NlpGetAccountNames( &LogonInfo->Identity,
                        AccountInfo,
                        &SamAccountName,
                        &NetbiosDomainName,
                        &DnsDomainName,
                        &Upn );

    //
    // assumes GROUP_MEMBERSHIP is integral multiple of DWORDs
    //

    length = ROUND_UP_COUNT(sizeof(LOGON_CACHE_ENTRY), sizeof(ULONG))
                + ROUND_UP_COUNT(NetbiosDomainName.Length, sizeof(ULONG))
                + ROUND_UP_COUNT(SamAccountName.Length, sizeof(ULONG))
                + ROUND_UP_COUNT(DnsDomainName.Length, sizeof(ULONG))
                + ROUND_UP_COUNT(Upn.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))
                + ROUND_UP_COUNT(AccountInfo->LogonServer.Length, 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->LogonPackage = LogonInfo->Identity.ParameterControl;


    dataptr = (PCHAR)(pEntry + 1);
    *pEntryLength = length;

    ASSERT(!((ULONG_PTR)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 = SamAccountName.Length;
    RtlCopyMemory(dataptr, SamAccountName.Buffer, length);
    dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));

    length = pEntry->DomainNameLength = NetbiosDomainName.Length;
    if (length) {
        RtlCopyMemory(dataptr, NetbiosDomainName.Buffer, length);
        dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
    }

    length = pEntry->DnsDomainNameLength = DnsDomainName.Length;
    if (length) {
        RtlCopyMemory(dataptr, DnsDomainName.Buffer, length);
        dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
    }

    length = pEntry->UpnLength = Upn.Length;
    if (length) {
        RtlCopyMemory(dataptr, Upn.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;
    }

    length = pEntry->LogonDomainIdLength = (USHORT) RtlLengthSid(AccountInfo->LogonDomainId);

    NtStatus = RtlCopySid(pEntry->LogonDomainIdLength,
                          (PSID)dataptr,
                          AccountInfo->LogonDomainId
                          );
    ASSERT(NT_SUCCESS(NtStatus));
    dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));


    //
    // copy in the LogonServer
    //

    length = pEntry->LogonServerLength = AccountInfo->LogonServer.Length;
    if (length) {
        RtlCopyMemory(dataptr, AccountInfo->LogonServer.Buffer, length);
        dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));
    }


    //
    // fill in randomkey for this cache entry.
    //

    SspGenerateRandomBits( pEntry->RandomKey, sizeof(pEntry->RandomKey) );

    pEntry->CacheFlags = CacheFlags;

    *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    // default is reasonable from SYSTEM context
                                );
    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)) {
        I_LsarClose( &NlpSecretHandle );
    }


    ValueName.Buffer = &NameBuffer[0];
    ValueName.MaximumLength = 32;
    ValueName.Length = 0;
    NlpMakeCacheEntryName( Index, &ValueName );




    NtStatus = I_LsarOpenSecret(NtLmGlobalPolicyHandle,
                             (PLSAPR_UNICODE_STRING) &ValueName,
                             SECRET_QUERY_VALUE | SECRET_SET_VALUE,
                             &NlpSecretHandle
                             );

    if (!NT_SUCCESS(NtStatus)) {
        if (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
            NtStatus = I_LsarCreateSecret(NtLmGlobalPolicyHandle,
                                       (PLSAPR_UNICODE_STRING) &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 = I_LsarClose(&NlpSecretHandle);
#if DBG
        if (DumpCacheInfo) {
            DbgPrint("CloseSecret: LsaClose returns %#08x\n", NtStatus);
        }
#endif
        ASSERT(NT_SUCCESS(NtStatus));
        INVALIDATE_HANDLE(NlpSecretHandle);
    }
}


NTSTATUS
NlpWriteSecret(
    IN  PLSAPR_CR_CIPHER_VALUE NewSecret,
    IN  PLSAPR_CR_CIPHER_VALUE 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 I_LsarSetSecret(NlpSecretHandle, NewSecret, OldSecret);
}


NTSTATUS
NlpReadSecret(
    OUT PLSAPR_CR_CIPHER_VALUE * NewSecret,
    OUT PLSAPR_CR_CIPHER_VALUE * 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 = I_LsarQuerySecret(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
NlpComputeSaltedHashedPassword(
    OUT PNT_OWF_PASSWORD SaltedOwfPassword,
    IN PNT_OWF_PASSWORD OwfPassword,
    IN PUNICODE_STRING UserName
    )

/*++

Routine Description:

    Computes the salted hash of a password by concatenating the user name
    with the OWF and computing the OWF of the combination.

Arguments:

    SaltedOwfPassword - receives the LM or NT salted password/
    OwfPassword - Contains the NT or LM owf password.
    UserName - Contains the name of the user, used for salt.

Return Value:

    NTSTATUS
        Success = STATUS_SUCCESS
                    Passwords created OK

        Failure = STATUS_NO_MEMORY
                    Not enough storage to create Passwords

--*/
{
    NTSTATUS Status;
    UNICODE_STRING TempString;
    UNICODE_STRING LowerUserName;

    //
    // Compute the lower case user name.
    //

    Status = RtlDowncaseUnicodeString( &LowerUserName,
                                       UserName,
                                       TRUE );

    if ( !NT_SUCCESS(Status)) {
        return Status;
    }


    //
    // Build a string that is a concatenation of the OWF and LowerCase username.
    //

    TempString.Length = TempString.MaximumLength = LowerUserName.Length + sizeof(NT_OWF_PASSWORD);
    TempString.Buffer = AllocateFromHeap( TempString.Length );
    if (TempString.Buffer == NULL) {
        RtlFreeUnicodeString( &LowerUserName );
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    RtlCopyMemory(
        TempString.Buffer,
        OwfPassword,
        sizeof(NT_OWF_PASSWORD) );

    RtlCopyMemory(
        (PUCHAR) TempString.Buffer + sizeof(NT_OWF_PASSWORD),
        LowerUserName.Buffer,
        LowerUserName.Length );


    //
    // The Salted hash is the OWF of that.
    //
    Status = RtlCalculateNtOwfPassword(
                &TempString,
                SaltedOwfPassword
                );

    FreeToHeap(TempString.Buffer);
    RtlFreeUnicodeString( &LowerUserName );

    return(Status);

}


NTSTATUS
NlpMakeSecretPassword(
    OUT PLSAPR_CR_CIPHER_VALUE Passwords,
    IN  PUNICODE_STRING UserName,
    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

--*/

{
    NTSTATUS Status = STATUS_SUCCESS;
    PCACHE_PASSWORDS pwd;

    Passwords->Buffer = NULL;

    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)) {
        Status = NlpComputeSaltedHashedPassword(
                    &pwd->SecretPasswords.NtOwfPassword,
                    NtOwfPassword,
                    UserName
                    );
        if (!NT_SUCCESS(Status)) {
            goto Cleanup;
        }

        pwd->SecretPasswords.NtPasswordPresent = TRUE;
    } else {
        RtlZeroMemory((PVOID)&pwd->SecretPasswords.NtOwfPassword,
                        sizeof(pwd->SecretPasswords.NtOwfPassword)
                        );
        pwd->SecretPasswords.NtPasswordPresent = FALSE;
    }

    if (ARGUMENT_PRESENT(LmOwfPassword)) {
        Status = NlpComputeSaltedHashedPassword(
                    &pwd->SecretPasswords.LmOwfPassword,
                    LmOwfPassword,
                    UserName
                    );
        if (!NT_SUCCESS(Status)) {
            goto Cleanup;
        }

        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 = (PUCHAR)pwd;

Cleanup:

    if( !NT_SUCCESS( Status ) ) {
        if( pwd != NULL )
            FreeToHeap( pwd );
    }

    return Status;
}


NTSTATUS
NlpMakeSecretPasswordNT5(
    IN OUT PCACHE_PASSWORDS Passwords,
    IN  PUNICODE_STRING UserName,
    IN  PNT_OWF_PASSWORD NtOwfPassword OPTIONAL,
    IN  PLM_OWF_PASSWORD LmOwfPassword OPTIONAL
    )

/*++

Routine Description:

    Populates CACHE_PASSWORDS structure with salted forms of NtOwfPassword
    and LmOwfPassword.

Arguments:

    Passwords       - populated 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

--*/

{
    NTSTATUS Status = STATUS_SUCCESS;
    PCACHE_PASSWORDS pwd;


    pwd = Passwords;

    //
    // 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)) {
        Status = NlpComputeSaltedHashedPassword(
                    &pwd->SecretPasswords.NtOwfPassword,
                    NtOwfPassword,
                    UserName
                    );
        if (!NT_SUCCESS(Status)) {
            goto Cleanup;
        }

        pwd->SecretPasswords.NtPasswordPresent = TRUE;
    } else {
        RtlZeroMemory((PVOID)&pwd->SecretPasswords.NtOwfPassword,
                        sizeof(pwd->SecretPasswords.NtOwfPassword)
                        );
        pwd->SecretPasswords.NtPasswordPresent = FALSE;
    }

    //
    // Windows2000:
    // never store LMOWF -- since we never need it, and, this would
    // be the first thing attacked once a cache entry is unwrapped.
    //

#if 0

    if (ARGUMENT_PRESENT(LmOwfPassword)) {
        Status = NlpComputeSaltedHashedPassword(
                    &pwd->SecretPasswords.LmOwfPassword,
                    LmOwfPassword,
                    UserName
                    );
        if (!NT_SUCCESS(Status)) {
            goto Cleanup;
        }

        pwd->SecretPasswords.LmPasswordPresent = TRUE;
    } else
#else
    UNREFERENCED_PARAMETER( LmOwfPassword );
#endif
    {
        RtlZeroMemory((PVOID)&pwd->SecretPasswords.LmOwfPassword,
                        sizeof(pwd->SecretPasswords.LmOwfPassword)
                        );
        pwd->SecretPasswords.LmPasswordPresent = FALSE;
    }


Cleanup:


    return Status;
}




BOOLEAN
NlpCheckMitCacheEntry(
    IN PLOGON_CACHE_ENTRY CacheEntry,
    IN PUNICODE_STRING DomainName,
    IN PUNICODE_STRING UserName
    )

/*++

Routine Description:

    Uses the supplemental data found in a cached MIT logon to 
    determine whether this is the correct entry

Arguments:

    DomainName - The name of the domain in which the account exists.
        This can be the Netbios or Dns Domain Name.

    UserName - The name of the account whose password is to be changed.
        This can be the Sam Account Name.
        If DomainName is empty, this is the UPN of the account


    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:

    BOOLEAN
        TRUE -  The user is doing a cached MIT logon, and this is the correct
                entry.
        
        FALSE - Doh!  Not it.

--*/

{
    UNICODE_STRING MitRealm;
    UNICODE_STRING MitUser;
    PBYTE Tmp, Start;  


    if (CacheEntry->SupplementalCacheDataLength < (2 * sizeof(UNICODE_STRING)) ||
        CacheEntry->SupplementalCacheDataOffset < sizeof(LOGON_CACHE_ENTRY))
    {   
        return FALSE;
    }



    // Supplemental data will contain 2 UNICODE_STRINGs & buffers, in format
    // MIT User <buffer> MIT Realm <buffer>.  All buffers are offset from 
    // beginning of supplemental data.

    Tmp = Start = (PBYTE) RtlOffsetToPointer(CacheEntry, CacheEntry->SupplementalCacheDataOffset);
    RtlCopyMemory(
        &MitUser,
        Tmp,
        sizeof(UNICODE_STRING)
        );

    Tmp += sizeof(UNICODE_STRING);
    MitUser.Buffer = (PWSTR) RtlOffsetToPointer(Start, MitUser.Buffer);
    Tmp += ROUND_UP_COUNT(MitUser.Length, ALIGN_LONG);


    RtlCopyMemory(
        &MitRealm,
        Tmp,
        sizeof(UNICODE_STRING)
        );

    MitRealm.Buffer = (PWSTR) RtlOffsetToPointer(Start, MitRealm.Buffer);

    
    // This must be a UPN
    if (DomainName->Length == 0)
    {
        // obviously, it isn't this cache entry.
        if (MitRealm.Length != 0)
        {   
            return FALSE;
        }

        return (RtlEqualUnicodeString(
                        &MitUser,
                        UserName,
                        TRUE
                        ));

    }


    return (RtlEqualUnicodeString(
                        &MitUser,
                        UserName,
                        TRUE
                        ) &&
            RtlEqualUnicodeString(
                        &MitRealm,
                        DomainName,
                        TRUE
                        ));

}
    




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.
        This can be the Netbios or Dns Domain Name.

    UserName - The name of the account whose password is to be changed.
        This can be the Sam Account Name.
        If DomainName is empty, this is the UPN of the account


    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 = STATUS_SUCCESS;

    UNICODE_STRING CachedUser;
    UNICODE_STRING CachedDomain;
    UNICODE_STRING CachedDnsDomain;
    UNICODE_STRING CachedUpn;

    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
        }


        //
        // Grab the various strings from the cache entry.
        //
        ASSERT((*CacheEntry)->Revision >= NLP_CACHE_REVISION_NT_1_0B );

        //
        // decrypt the cache entry...
        //

        NtStatus = NlpDecryptCacheEntry( *CacheEntry, *EntrySize );

        if(!NT_SUCCESS(NtStatus)) {

            //
            // for failed decrypt, continue the search.
            // the reason for this is because the decrypt does an integrity
            // check.  We don't want one corrupt cache entry to cause (possibly)
            // the whole cache to be invalidated.
            //

            FreeToHeap( (*CacheEntry) );
            Next = (PNLP_CTE)(Next->Link.Flink);

            continue;
        }

 
        CachedUser.Length =
            CachedUser.MaximumLength = (*CacheEntry)->UserNameLength;
        if( (*CacheEntry)->Revision >= NLP_CACHE_REVISION_NT_5_0 ) {
            CachedUser.Buffer = (PWSTR) ((PBYTE) *CacheEntry + sizeof(LOGON_CACHE_ENTRY));
        } else {
            CachedUser.Buffer = (PWSTR) ((PBYTE) *CacheEntry + sizeof(LOGON_CACHE_ENTRY_NT_4_SP4));
        }

        CachedDomain.Length =
            CachedDomain.MaximumLength = (*CacheEntry)->DomainNameLength;
        CachedDomain.Buffer = (PWSTR)((LPBYTE)CachedUser.Buffer +
            ROUND_UP_COUNT((*CacheEntry)->UserNameLength, sizeof(ULONG)));

        CachedDnsDomain.Length =
            CachedDnsDomain.MaximumLength = (*CacheEntry)->DnsDomainNameLength;
        CachedDnsDomain.Buffer = (PWSTR)((LPBYTE)CachedDomain.Buffer +
                ROUND_UP_COUNT((*CacheEntry)->DomainNameLength, sizeof(ULONG)));

        CachedUpn.Length =
            CachedUpn.MaximumLength = (*CacheEntry)->UpnLength;
        CachedUpn.Buffer = (PWSTR)((LPBYTE)CachedDnsDomain.Buffer +
                ROUND_UP_COUNT((*CacheEntry)->DnsDomainNameLength, sizeof(ULONG)));



        //
        // If this cache entry has the MIT flag set, then the supplemental
        // data field contains a UNICODE_STRING MITUserName, followed by a
        // UNICODE_STRING MITRealmName, followed by buffers.
        //
        if ((*CacheEntry)->CacheFlags == MSV1_0_CACHE_LOGON_REQUEST_MIT_LOGON)
        {

           if (NlpCheckMitCacheEntry(
                *CacheEntry,
                DomainName,
                UserName
                ))
           {

               //
               // found it!
               //

               break; // out of while loop

           }   

           
        

        //
        // If the caller passed in a domain name,
        //  the user name is the SamAccountName,
        //  and the domain name is either the Netbios or Dns Domain Name.
        //

        } else if ( DomainName->Length != 0 ) {
            if (RtlEqualUnicodeString(UserName, &CachedUser, (BOOLEAN) TRUE ) ) { 

                if ( RtlEqualDomainName(DomainName, &CachedDomain ) ||
                     RtlEqualUnicodeString(DomainName, &CachedDnsDomain, (BOOLEAN) TRUE ) ) {


                    //
                    // found it !
                    //

                    break; // out of while-loop
                }

            }

        //
        // If no domain name was passed in,
        //  the user name is the UPN.
        //
        } else {
            if (RtlEqualUnicodeString(UserName, &CachedUpn, (BOOLEAN) TRUE ) ) {

                //
                // 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;

        if( (*CacheEntry)->Revision < NLP_CACHE_REVISION_NT_5_0 ) {

            //
            // versions prior to NT5 require us open the corresponding LSA secret.
            //

            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
                             );
    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_INFO4 *AccountInfo
    )

/*++

Routine Description:

    Performs the reverse of NlpBuildCacheEntry - creates a NETLOGON_VALIDATION_SAM_INFO4
    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_INFO4

Return Value:

    NTSTATUS
        Success = STATUS_SUCCESS

        Failure = STATUS_NO_MEMORY

--*/

{
    PNETLOGON_VALIDATION_SAM_INFO4 pSamInfo;
    PUCHAR source;
    PUCHAR dest;
    ULONG length;
    ULONG sidLength;
    ULONG commonBits;
    LPWSTR computerName;
    ULONG computerNameLength = 0;


    //
    // commonBits is the size of the variable data area common to both the
    // LOGON_CACHE_ENTRY and NETLOGON_VALIDATION_SAM_INFO4 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_NT_5_0 )
    {
        commonBits  += ROUND_UP_COUNT(pCacheEntry->DnsDomainNameLength, sizeof(ULONG))
                    + ROUND_UP_COUNT(pCacheEntry->UpnLength, sizeof(ULONG))
                    ;

        if( pCacheEntry->LogonServerLength != 0 )
        {
            computerNameLength = pCacheEntry->LogonServerLength;
            computerName = NULL;
        }
    }

    if( computerNameLength == 0 )
    {
        //
        // 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
        //

        computerName = NlpComputerName.Buffer;
        computerNameLength = NlpComputerName.Length / sizeof(WCHAR);

        ASSERT( computerName );
        ASSERT( computerNameLength );

#if DBG
        if (DumpCacheInfo) {
            DbgPrint("ComputerName = %ws, length = %d\n", computerName, computerNameLength);
        }
#endif
    }



    ASSERT(pCacheEntry->Revision >= NLP_CACHE_REVISION_NT_1_0B);

    //
    // Account for possible roundup for NETLOGON_SID_AND_ATTRIBUTES structure
    //
    commonBits += sizeof(PVOID);

    commonBits += ROUND_UP_COUNT(sizeof(NETLOGON_SID_AND_ATTRIBUTES) * pCacheEntry->SidCount, sizeof(ULONG))
                + ROUND_UP_COUNT(pCacheEntry->SidLength, sizeof(ULONG))
                ;
    sidLength = pCacheEntry->LogonDomainIdLength;



    //
    // length is the required amount of buffer in which to build a working
    // NETLOGON_VALIDATION_SAM_INFO4 structure
    //

    length = ROUND_UP_COUNT(sizeof(NETLOGON_VALIDATION_SAM_INFO4), sizeof(ULONG))
                + commonBits
                + sidLength
                + computerNameLength * sizeof(WCHAR)
                ;
#if DBG
    if (DumpCacheInfo) {
        DbgPrint("NlpBuildAccountInfo: %d bytes required\n", length);
    }
#endif

    // MIDL_user_allocate zeros the buffer.  This routine depends on that.
    pSamInfo = (PNETLOGON_VALIDATION_SAM_INFO4)MIDL_user_allocate(length);
    if (pSamInfo == NULL) {
        return STATUS_NO_MEMORY;
    }

    //
    // 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);


    //
    // point source at the first string to be copied out of the variable length
    // data area at the end of the cache entry
    //

    ASSERT(pCacheEntry->Revision >= NLP_CACHE_REVISION_NT_1_0B );

    if( pCacheEntry->Revision >= NLP_CACHE_REVISION_NT_5_0 ) {
        source = (PUCHAR)(pCacheEntry + 1);
    } else {
        source = (PUCHAR)( (PLOGON_CACHE_ENTRY_NT_4_SP4)pCacheEntry + 1 );
    }

    source +=   ROUND_UP_COUNT(pCacheEntry->UserNameLength, sizeof(ULONG))
              + ROUND_UP_COUNT(pCacheEntry->DomainNameLength, sizeof(ULONG)) ;


    if( pCacheEntry->Revision >= NLP_CACHE_REVISION_NT_5_0 )
    {

        NlpCopyAndUpdateAccountInfo(pCacheEntry->DnsDomainNameLength,
                                    &pSamInfo->DnsLogonDomainName,
                                    &source,
                                    &dest
                                    );

        NlpCopyAndUpdateAccountInfo(pCacheEntry->UpnLength,
                                    &pSamInfo->Upn,
                                    &source,
                                    &dest
                                    );

    } else {
        //
        // Fill in the new field for the PNETLOGON_VALIDATION_SAM_INFO4 structure
        //

        RtlInitUnicodeString( &pSamInfo->DnsLogonDomainName, NULL );
        RtlInitUnicodeString( &pSamInfo->Upn, NULL );

        source += ROUND_UP_COUNT(pCacheEntry->DnsDomainNameLength, sizeof(ULONG))
                + ROUND_UP_COUNT(pCacheEntry->UpnLength, sizeof(ULONG));

    }



    //
    // 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_INFO4 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_INFO4
    // 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_NT_1_0B ) {
        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));

            //
            // Structures containing pointers must start on 8-byte boundries
            //
            dest = ROUND_UP_POINTER(dest, sizeof(PVOID));
            
            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));
    source = ROUND_UP_POINTER(source + sidLength, sizeof(ULONG));

    if( computerName != NULL )
    {
        //
        // 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
                                    );
    } else {

        //
        // Sanity check that we have a proper cache revision.
        //

        if( pCacheEntry->Revision >= NLP_CACHE_REVISION_NT_5_0 )
        {
            //
            // final UNICODE_STRING from LOGON_CACHE_ENTRY.
            //

            NlpCopyAndUpdateAccountInfo((USHORT)pCacheEntry->LogonServerLength,
                                        &pSamInfo->LogonServer,
                                        &source,
                                        &dest
                                        );
        }
    }


    //
    // 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;
    if (pCacheEntry->LogonPackage != 0) {
        pSamInfo->UserFlags |= pCacheEntry->LogonPackage << PRIMARY_CRED_LOGON_PACKAGE_SHIFT;
    }

    // RtlZeroMemory(&pSamInfo->UserSessionKey, sizeof(pSamInfo->UserSessionKey));



#if DBG
    if (DumpCacheInfo) {
        DbgPrint("NlpBuildAccountInfo:\n");
        DumpAccountInfo(pSamInfo);
    }
#endif

    *AccountInfo = pSamInfo;
    return STATUS_SUCCESS;
    UNREFERENCED_PARAMETER( EntryLength );
}


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 = NULL;


    //
    // 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) {
            NtStatus = STATUS_NO_MEMORY;
            goto Cleanup;
        }

        NtStatus = NtQueryValueKey(NlpCacheHandle,
                                   &CacheControlValueName,
                                   KeyValuePartialInformation,
                                   (PVOID)RegInfo,
                                   RequiredSize,
                                   &RequiredSize
                                   );

        if (!NT_SUCCESS(NtStatus)) {
            goto Cleanup;
        }

        //
        // check the revision - we can't deal with up-level revisions.
        //

        if (RegInfo->DataLength < sizeof(NLP_CACHE_CONTROL)) {
            NtStatus = STATUS_UNKNOWN_REVISION;
            goto Cleanup;
        }

        RtlCopyMemory( &NlpCacheControl, &(RegInfo->Data[0]), sizeof(NLP_CACHE_CONTROL) );
        if (NlpCacheControl.Revision > NLP_CACHE_REVISION) {
            NtStatus = STATUS_UNKNOWN_REVISION;
            goto Cleanup;
        }


        //
        // If this is an older cache, update it with the latest revision
        //

        if (NlpCacheControl.Revision != NLP_CACHE_REVISION) {

            // There is no conversion. All the version of cache control have
            //  been the same.
            NlpCacheControl.Revision = NLP_CACHE_REVISION;
            NtStatus = NlpWriteCacheControl();

            if (!NT_SUCCESS(NtStatus)) {
                goto Cleanup;
            }
        }

        NtStatus = STATUS_SUCCESS;
    }

Cleanup:

    if (!NT_SUCCESS(NtStatus)) {
        NlpCacheControl.Entries = 0;    // Disable logon cache
    }

    if( RegInfo ) {
        FreeToHeap( RegInfo );
    }

    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 = STATUS_SUCCESS;

    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_NT_4_SP4)) {

            //
            // 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,
                                                     &CacheAndSecrets[i].EntrySize
                                                     // &EntrySize
                                                     );
                if (NT_SUCCESS(NtStatus)) {

                    //
                    // for pre-Win2000 cache entries, read the associated secret.
                    //

                    if( CacheAndSecrets[i].CacheEntry->Revision < NLP_CACHE_REVISION_NT_5_0 ) {

                        NtStatus = NlpOpenSecret( Next->Index );
                        if (NT_SUCCESS(NtStatus)) {
                            NtStatus = NlpReadSecret( &CacheAndSecrets[i].NewSecret,
                                                      &CacheAndSecrets[i].OldSecret);

                            NlpCloseSecret();
                        }
                    }

                    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

                    }
                }

                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)) {

                    if( CacheAndSecrets[j].CacheEntry->Revision < NLP_CACHE_REVISION_NT_5_0 ) {

                        //
                        // for pre-Win2000 cache entries, write the secret back out.
                        // note: we don't bother to try to migrate pre-win2000 -> Win2000
                        // here, because this will happen later, as a side-effect
                        // of updating cache entry during successful DC validated logon.
                        //

                        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
                              );
    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];

    LSAPR_HANDLE
        SecretHandle;

    ValueName.Length = 0;
    ValueName.MaximumLength = 32;
    ValueName.Buffer = &NameBuffer[0];

    NlpMakeCacheEntryName( Index, &ValueName );

    NtStatus = I_LsarOpenSecret( NtLmGlobalPolicyHandle,
                                    (PLSAPR_UNICODE_STRING) &ValueName,
                                    DELETE,
                                    &SecretHandle
                                    );

    if( NT_SUCCESS( NtStatus ) ) {

        //
        // for Windows2000, we remove old style cache entry related
        // LSA secrets.
        //


        //
        // Deleting and object causes its handle to be closed
        //

        I_LsarDelete( SecretHandle );

//        I_LsarClose( &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
                              );

    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];

    LSAPR_HANDLE
        SecretHandle;


    ValueName.Buffer = &NameBuffer[0];
    ValueName.MaximumLength = 32;
    ValueName.Length = 0;
    NlpMakeCacheEntryName( Index, &ValueName );

    NtStatus = I_LsarOpenSecret(NtLmGlobalPolicyHandle,
                             (PLSAPR_UNICODE_STRING) &ValueName,
                             DELETE,
                             &SecretHandle
                             );

    if (NT_SUCCESS(NtStatus)) {

        //
        // Deleting and object causes its handle to be closed
        //

        NtStatus = I_LsarDelete( SecretHandle );
    }

    //
    // Now delete the registry value
    //

    NtStatus = NtDeleteValueKey( NlpCacheHandle, &ValueName );

    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

    BYTE FastBuffer[ 512 ];
    PBYTE SlowBuffer = NULL;

    ValueName.Buffer = &NameBuffer[0];
    ValueName.MaximumLength = 32;
    ValueName.Length = 0;
    NlpMakeCacheEntryName( Index, &ValueName );


    RegInfo = (PKEY_VALUE_FULL_INFORMATION)FastBuffer;
    RequiredSize = sizeof(FastBuffer);

    //
    // perform first query to find out how much buffer to allocate
    //

    NtStatus = NtQueryValueKey(NlpCacheHandle,
                               &ValueName,
                               KeyValueFullInformation,
                               (PVOID)RegInfo,
                               RequiredSize,
                               &RequiredSize
                               );

    if( (NtStatus == STATUS_BUFFER_TOO_SMALL) ||
        (NtStatus == STATUS_BUFFER_OVERFLOW) ) {

        //
        // allocate buffer then do query again, this time receiving data
        //

        SlowBuffer = (PBYTE)AllocateFromHeap(RequiredSize);
        if (SlowBuffer == NULL) {
            return(STATUS_NO_MEMORY);
        }

        RegInfo = (PKEY_VALUE_FULL_INFORMATION)SlowBuffer;

        NtStatus = NtQueryValueKey(NlpCacheHandle,
                                   &ValueName,
                                   KeyValueFullInformation,
                                   (PVOID)RegInfo,
                                   RequiredSize,
                                   &RequiredSize
                                   );
    }


    if (NT_SUCCESS(NtStatus)) {

#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( RegInfo->DataLength == 0 ) {
            NtStatus = STATUS_INTERNAL_DB_CORRUPTION;
            *CacheEntry = NULL;
            *EntrySize = 0;
        } else {

            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 {
                RtlCopyMemory( (*CacheEntry),
                                RCacheEntry,
                                (*EntrySize) );

            }

        }
    }

    if( SlowBuffer )
        FreeToHeap( SlowBuffer );

    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.Blink, &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
    )
{

    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
    )
{
    DbgPrint(String);

    if ( Sid == NULL ) {
        DbgPrint(0, "(null)\n");
    } else {
        UNICODE_STRING SidString;
        NTSTATUS Status;

        Status = RtlConvertSidToUnicodeString( &SidString, Sid, TRUE );

        if ( !NT_SUCCESS(Status) ) {
            DbgPrint("Invalid 0x%lX\n", Status );
        } else {
            DbgPrint( "%wZ\n", &SidString );
            RtlFreeUnicodeString( &SidString );
        }
    }
}


VOID
DumpAccountInfo(
    IN  PNETLOGON_VALIDATION_SAM_INFO4 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"
                "Upn                : \"%*.*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->Upn.Length/sizeof(WCHAR),
                AccountInfo->Upn.Length/sizeof(WCHAR),
                AccountInfo->Upn.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"
                "DnsLogonDomainName : \"%*.*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,
                AccountInfo->DnsLogonDomainName.Length/sizeof(WCHAR),
                AccountInfo->DnsLogonDomainName.Length/sizeof(WCHAR),
                AccountInfo->DnsLogonDomainName.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->DnsDomainNameLength;
    DbgPrint(   "DnsDomainname      : \"%*.*ws\"\n", length/2, length/2, dataptr);
    dataptr = ROUND_UP_POINTER(dataptr+length, sizeof(ULONG));

    length = pEntry->UpnLength;
    DbgPrint(   "Upn                : \"%*.*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
