CACHE.C

/*++ 

Copyright (c) 1996 Microsoft Corporation

This program is released into the public domain for any purpose.


Module Name:

cache.c

Abstract:

This module implements a simple user cache. The cached users are kept
in an LRU sorted list. If there will be a large number of simultaneous
users, then a sorted array would be more appropriate.

--*/

#include <windows.h>
#include <httpfilt.h>
#include "authfilt.h"

//
// Constants
//

//
// The maximum number of users we will cache. If there will be a large number
// of simultaneous users, bump this value
//

#define MAX_CACHED_USERS 100

//
// The position after which we'll move a cache entry to the front of the list
//

#define LIST_REORDER_THRESHOLD 6

//
// Cached user structure
//

typedef struct _USER_INFO
{
LIST_ENTRY ListEntry; // Double linked list entry

CHAR achUserName[SF_MAX_USERNAME]; // External username and password
CHAR achPassword[SF_MAX_PASSWORD];

CHAR achNTUserName[SF_MAX_USERNAME]; // NT account and password to map user to
CHAR achNTUserPassword[SF_MAX_PASSWORD];

} USER_INFO, *PUSER_INFO;


//
// Globals
//

//
// Circular double linked list of cached users
//

LIST_ENTRY CacheListHead;

//
// Critical section protects cache list
//

CRITICAL_SECTION csCacheLock;

//
// Indicates whether we are initialized
//

BOOL fCacheInitialized = FALSE;

//
// Number of items in the cache
//

DWORD cCacheItems = 0;


BOOL
InitializeCache(
VOID
)
/*++

Routine Description:

Initializes the cache module

Return Value:

TRUE if initialized successfully, FALSE on error

--*/
{
if ( fCacheInitialized )
return TRUE;

InitializeCriticalSection( &csCacheLock );
CacheListHead.Blink = CacheListHead.Flink = &CacheListHead;

fCacheInitialized = TRUE;

return TRUE;
}


BOOL
LookupUserInCache(
CHAR * pszUserName,
BOOL * pfFound,
CHAR * pszPassword,
CHAR * pszNTUser,
CHAR * pszNTUserPassword
)
/*++

Routine Description:

Checks to see if a user is in the cache and returns the user properties
if found

Arguments:

pszUserName - Case insensitive username to find
pfFound - Set to TRUE if the specified user was found
pszPassword - Receives password for specified user if found
pszNTUser - Receives the NT Username to map this user to
pszNTUserPassword - Receives the NT Password for pszNTUser

Note: pszPassword and pszNTUserPassword must be at least SF_MAX_PASSWORD
characters. pszNTUser must be at least SF_MAX_USERNAME characters.

Return Value:

TRUE if no errors occurred.

--*/
{
LIST_ENTRY * pEntry;
USER_INFO * pUser;
DWORD cPosition = 0;

//
// Search the cache for the specified user
//

EnterCriticalSection( &csCacheLock );

for ( pEntry = CacheListHead.Flink;
pEntry != &CacheListHead;
pEntry = pEntry->Flink )
{
pUser = CONTAINING_RECORD( pEntry, USER_INFO, ListEntry );

if ( !stricmp( pszUserName, pUser->achUserName ))
{
goto Found;
}

cPosition++;
}

LeaveCriticalSection( &csCacheLock );

//
// Not Found
//

*pfFound = FALSE;
return TRUE;

Found:

//
// Copy out the user properties
//

strcpy( pszPassword, pUser->achPassword );
strcpy( pszNTUser, pUser->achNTUserName );
strcpy( pszNTUserPassword, pUser->achNTUserPassword );

//
// Move this user entry to the front of the list as we're probably going
// to get subsequent requests for this user. Note we only move it
// if it's not already near the front
//

if ( cPosition > LIST_REORDER_THRESHOLD )
{
//
// Remove from the old position...
//

pEntry->Blink->Flink = pEntry->Flink;
pEntry->Flink->Blink = pEntry->Blink;

//
// ...and insert it at the beginning of the list
//

pEntry->Blink = &CacheListHead;
pEntry->Flink = CacheListHead.Flink;

CacheListHead.Flink->Blink = pEntry;
CacheListHead.Flink = pEntry;
}

LeaveCriticalSection( &csCacheLock );

*pfFound = TRUE;

return TRUE;
}

BOOL
AddUserToCache(
CHAR * pszUserName,
CHAR * pszPassword,
CHAR * pszNTUser,
CHAR * pszNTUserPassword
)
/*++

Routine Description:

Adds the specified user to the cache

Arguments:

pszUserName - Username to add
pszPassword - Contains the external password for this user
pszNTUser - Contains the NT user name to use for this user
pszNTUserPassword - Contains the password for NTUser

Return Value:

TRUE if no errors occurred.

--*/
{
LIST_ENTRY * pEntry;
USER_INFO * pUser;

//
// Check our parameters before adding them to the cache
//

if ( strlen( pszUserName ) > SF_MAX_USERNAME ||
strlen( pszPassword ) > SF_MAX_PASSWORD ||
strlen( pszNTUser ) > SF_MAX_USERNAME ||
strlen( pszNTUserPassword ) > SF_MAX_PASSWORD )
{
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}

//
// Search the cache for the specified user to make sure there are no
// duplicates
//

EnterCriticalSection( &csCacheLock );

for ( pEntry = CacheListHead.Flink;
pEntry != &CacheListHead;
pEntry = pEntry->Flink )
{
pUser = CONTAINING_RECORD( pEntry, USER_INFO, ListEntry );

if ( !stricmp( pszUserName, pUser->achUserName ))
{
goto Found;
}
}

//
// Allocate a new cache item and put it at the head of the list
//

pUser = (USER_INFO *) LocalAlloc( LPTR, sizeof( USER_INFO ));

if ( !pUser )
{
LeaveCriticalSection( &csCacheLock );

SetLastError( ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}

pUser->ListEntry.Flink = CacheListHead.Flink;
pUser->ListEntry.Blink = &CacheListHead;

CacheListHead.Flink->Blink = &pUser->ListEntry;
CacheListHead.Flink = &pUser->ListEntry;

Found:

//
// Set the various fields
//

strcpy( pUser->achUserName, pszUserName );
strcpy( pUser->achPassword, pszPassword );
strcpy( pUser->achNTUserName, pszNTUser );
strcpy( pUser->achNTUserPassword, pszNTUserPassword );

cCacheItems++;

//
// If there are too many cached users, remove the least recently
// used one now
//

if ( cCacheItems > MAX_CACHED_USERS )
{
pEntry = CacheListHead.Blink;

pEntry->Blink->Flink = &CacheListHead;
CacheListHead.Blink = pEntry->Blink;

LocalFree( CONTAINING_RECORD( pEntry, USER_INFO, ListEntry ));

cCacheItems--;
}

LeaveCriticalSection( &csCacheLock );

return TRUE;
}

VOID
TerminateCache(
VOID
)
/*++

Routine Description:

Terminates the cache module and frees any allocated memory

--*/
{
LIST_ENTRY * pEntry;
LIST_ENTRY * pEntryNext;
USER_INFO * pUser;

if ( !fCacheInitialized )
return;

EnterCriticalSection( &csCacheLock );

//
// Free all of the cache entries
//

for ( pEntry = CacheListHead.Flink;
pEntry != &CacheListHead;
pEntry = pEntryNext )

{
pUser = CONTAINING_RECORD( pEntry, USER_INFO, ListEntry );

pEntryNext = pEntry->Flink;

//
// Remove this entry from the list and free it
//

pEntry->Blink->Flink = pEntry->Flink;
pEntry->Flink->Blink = pEntry->Blink;

LocalFree( pUser );
}

cCacheItems = 0;
LeaveCriticalSection( &csCacheLock );

DeleteCriticalSection( &csCacheLock );

fCacheInitialized = FALSE;
}