//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1993. // // File: prcache.cxx // // Contents: Principal cache handling code // // Classes: CPrincipalHandler // // History: 4-02-93 WadeR Created // 04-Nov-93 WadeR Updated to hold CLogonAccounts // 08-Nov-93 WadeR Removed altogether in favour of Account.dll caching. Sigh. // // Notes: This is the principal cache code. // // When you first call GetASTicket, it passes in a flag indicating a logon is // in progress. This flag migrates to the created cache entry, and is passed // out in the TicketInfo when you call GetTicketInfo(). // // When you call TGSTicket with a PAC, it checks the flag. If it's set, it // calls LogonComplete(success). // // LogonComplete will clear the flag, and delete the entry from the cache. // // When the cache is full and another entry is needed the first pass looks for // an entry that is not in the middle of a login. If it finds one, then that // is released, and used. If it can't find one, it finds the oldest logon // pending, and calls LogonComplete(fail) on it, and uses it. // // If ClearCache() attempts to remove an entry with a non-zero use count // (indicating that someone has called GetLogonInfo and not ReleaseLogonInfo), // that entry is tagged as invalid and left alone. Once the use count goes // down to zero, it will be discarded. // // BUGBUG: Need some form of notification from the Account code to // tell me that an account changed. Also, need to defer // re-loading it if it's in use. // // BUGBUG: Should change Win4Assert to SafeAssert or ASSERT(). // // BUGBUG: Should remove PrintSizes(); // //---------------------------------------------------------------------------- // Get the common, global header files. #include #pragma hdrstop // Place any local #includes files here. #include "secdata.hxx" #include #include /////////////////////////////////////////////////////////////// // // // Global data // CPrincipalHandler PrincipalCache; const PWCHAR pwcKdcPrincipal = L"\\KDC"; const PWCHAR pwcPSPrincipal = L"\\PrivSvr"; // Constants to control cache size. The cache will start out being // CACHE_INITIALSIZE bytes (rounded down to a multiple of sizeof(CacheEntry)). // // When the cache needs to grow, it will grow by CACHE_GROW bytes (also // rounded down). When it is CACHE_SHRINK_THRESHOLD bytes larger than needed, // it will shrink to become the minumum size needed plus CACHE_SHRINK bytes. // // Caveats: // // If CACHE_GROW > CACHE_SHRINK_THRESHOLD, every time the cache grows it will // shrink again. If CACHE_SHRINK >= CACHE_SHRINK_THRESHOLD, the cache will // shrink over and over again, without changing size. Finally, If // CACHE_SHRINK_THRESHOLD < CACHE_INITIALSIZE then the cache will shrink on // startup (which is not a smart thing to do). // // I suggest that CACHE_SHRINK_THRESHOLD >= 2 * CACHE_SHRINK // CACHE_SHRINK_THRESHOLD >= 2 * CACHE_GROW // CACHE_SHRINK_THRESHOLD > CACHE_INITIALSIZE // CACHE_SHRINK ~= CACHE_GROW // // Currently, sizeof( CacheEntry ) == 24 bytes. #if 0 #define CACHE_INITIALSIZE 1024 #define CACHE_GROW 512 #define CACHE_SHRINK_THRESHOLD 1536 #define CACHE_SHRINK 512 #else #pragma MEMO( Really tiny cache sizes for testing ) #define CACHE_INITIALSIZE 50 #define CACHE_GROW 25 #define CACHE_SHRINK_THRESHOLD 75 #define CACHE_SHRINK 25 #endif // // Flags for fCacheFlags. // // Note that CACHE_DOING_LOGON (flag for fCacheFlags) has the same value as // DOING_LOGON (flag for TicketInfo::LogonSteps). // #define CACHE_DOING_LOGON DOING_LOGON #define CACHE_STICKY 0x00010000 #define CACHE_INVALID 0x00080000 // // Helper functinons (only used by this file). // #define PrintSizes() KdcDebug(( DEB_T_CACHE, "Line %d: cCache=%d, cMaxCacheSize=%d\n", \ __LINE__, _cCache, _cCacheMaxSize )) //+--------------------------------------------------------------------------- // // Function: MapNameDRN // // Synopsis: Maps a name to a domain relative name // // Effects: may orphan memory (indicated by an error message) // // Arguments: [pwzInName] -- Name to map // // Returns: pointer into string passed in, or new memory. // // History: 14-Sep-93 WadeR Created // // Notes: // // BUGBUG: The client should be fixed to NEVER pass in a bogus name, // so this should be able to map in place or return an error. // //---------------------------------------------------------------------------- static PWCHAR MapNameDRN( PWCHAR pwzInName ) { if (*pwzInName == L'\\') return(pwzInName); if (wcsnicmp(pwzInName, SecData.KdcRealm()->Buffer, SecData.KdcRealm()->Length / sizeof(WCHAR) ) == 0 ) { // Starts with this domain. return(pwzInName + SecData.KdcRealm()->Length / sizeof(WCHAR) ); } ULONG cch = wcslen( awcDOMAIN ) - 1; // - 1 because don't want the '\' if (wcsnicmp( pwzInName, awcDOMAIN, cch ) == 0) { // Starts with "domain:" return(pwzInName + cch); } KdcDebug(( DEB_WARN, "MapNameDRN(%ws): Don't understand (prepending '\\').\n", pwzInName )); KdcDebug(( DEB_WARN, "MapNameDRN: Memory leak.\n" )); PWCHAR pwNew = new WCHAR [wcslen(pwzInName) + 2]; pwNew[0] = '\\'; pwNew[1] = '\0'; wcscat( pwNew, pwzInName ); return(pwNew); } // // Private member functions. // //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::ReleaseCacheEntry // // Synopsis: Removes a cache slot, releasing if needed. // // Effects: May call CLogonAccount::Release() // // Arguments: [i] -- Slot to release // // Requires: Caller must have write access to _Monitor // // Returns: void // // History: 04-Nov-93 WadeR Created // // Notes: This moves the empty slot to the end. // //---------------------------------------------------------------------------- void CPrincipalHandler::ReleaseCacheEntry( ULONG i ) { KdcDebug(( DEB_T_CACHE, "Releasing slot %d.\n", i )); if (_Cache[i].plga != NULL) { _Cache[i].plga->Release(); } if (_Cache[i].pwzName) { delete _Cache[i].pwzName; } // Move the empty spot to the end of the array. _cCache--; Win4Assert( _cCache >= 0 ); _Cache[i] = _Cache[_cCache]; // Zero out the new empty spot _Cache[_cCache].plga = 0; _Cache[_cCache].pwzName = 0; PrintSizes(); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::Discard // // Synopsis: Removes a cache entry, completing it's logon if needed. // // Effects: may call CLogonAccount::LogonComplete() // // Arguments: [i] -- Slot to discard // // Requires: Caller must have write access to _Monitor // // Returns: void // // History: 04-Nov-93 WadeR Created // // Notes: // //---------------------------------------------------------------------------- void CPrincipalHandler::Discard( int i ) { KdcDebug(( DEB_T_CACHE, "Discarding %ws from cache slot %d%s.\n", _Cache[i].pwzName, i, (_Cache[i].fCacheFlags & CACHE_DOING_LOGON)? " (logon interupted)": "" )); // If it's a logon, then mark it as failed. // ReleaseCacheEntry will remove the entry, and move the // empty spot to the end. if (_Cache[i].fCacheFlags & CACHE_DOING_LOGON) { _Cache[i].plga->LogonComplete( FALSE, (FILETIME*)&tsZero ); // failed logon, no lockout _Cache[i].plga->Save(NULL, FALSE); } ReleaseCacheEntry( i ); PrintSizes(); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::GrowCache // // Synopsis: Grows the principal cache. // // Effects: Allocates memory // // Arguments: (none) // // Requires: Caller must have a write lock on the cache. // // Returns: HRESULT (S_OK or E_OUTOFMEMORY) // // Signals: none // // Algorithm: Adds GROW_SIZE bytes to the cache, rounded down to // sizeof(CacheElement). // // History: 05-Nov-93 WadeR Created // // Notes: // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::GrowCache() { SafeAssert( _cCacheMaxSize >= _cCache ); ULONG cNewSize = (_cCacheMaxSize * sizeof( CacheEntry ) + CACHE_GROW) / sizeof( CacheEntry ); KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::GrowCache() %d -> %d\n", _cCacheMaxSize, cNewSize )); PrintSizes(); SafeAssert( cNewSize > _cCache ); CacheEntry * pNew = new (NullOnFail) CacheEntry [cNewSize]; if (pNew == NULL) { KdcDebug(( DEB_ERROR, "Out of memory.\n" )); return(E_OUTOFMEMORY); } RtlCopyMemory( (PBYTE) pNew, (PBYTE) _Cache, _cCache * sizeof( CacheEntry ) ); delete _Cache; _Cache = pNew; _cCacheMaxSize = cNewSize; PrintSizes(); return(S_OK); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::ShrinkCache // // Synopsis: Shrinks the principal cache, if needed. // // Effects: Allocates memory // // Arguments: (none) // // Requires: Caller must have a write lock on the cache. // // Returns: HRESULT (S_OK or E_OUTOFMEMORY) // // Signals: none // // Algorithm: If the cache is SHRINK_THRESHOLD bytes too large, resizes // it to be just SHRINK_SIZE bytes bigger than needed, rounded // down to sizeof(CacheElement). // // History: 05-Nov-93 WadeR Created // // Notes: // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::ShrinkCache() { KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::ShrinkCache(%d)\n", _cCacheMaxSize )); PrintSizes(); SafeAssert( _cCacheMaxSize >= _cCache ); if ( (_cCacheMaxSize - _cCache) > CACHE_SHRINK_THRESHOLD / sizeof( CacheEntry ) ) { // Need to shrink the cache. ULONG cNewSize = (_cCache * sizeof( CacheEntry ) + CACHE_SHRINK) / sizeof( CacheEntry ); KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::ShrinkCache() %d -> %d\n", _cCacheMaxSize, cNewSize )); SafeAssert( cNewSize > _cCache ); CacheEntry * pNew = new (NullOnFail) CacheEntry [cNewSize]; if (pNew == NULL) { KdcDebug(( DEB_ERROR, "Out of memory.\n" )); return(E_OUTOFMEMORY); } RtlCopyMemory( (PBYTE) pNew, (PBYTE) _Cache, _cCache * sizeof( CacheEntry ) ); delete _Cache; _Cache = pNew; _cCacheMaxSize = cNewSize; } PrintSizes(); return(S_OK); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::PurgeSomething // // Synopsis: Makes at least one entry in the cache free. // // Effects: May release some cache entries, may grow the cache. // // Arguments: (none) // // Requires: _Monitor to be acquired for writing. // // Algorithm: Deletes the oldest cache element. // // History: 04-Nov-93 WadeR Created // // Notes: This function guarentees that on return, there is at least // one empty cache entry for the caller to use. To do this, // the caller must know that no other thread will think it can // use that slot. Therefore the caller must have write access // to the cache. // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::PurgeSomething() { KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::PurgeSomething()\n" )); PrintSizes(); ULONG i; TimeStamp tsOldest = tsInfinity; ULONG iOldest = 0; // // First pass, only consider entries that are not part of // a logon in progress. // for (i=0; i<_cCache; i++) { if (!(_Cache[i].fCacheFlags & (CACHE_DOING_LOGON | CACHE_STICKY)) && (_Cache[i].cUseCount == 0) && (_Cache[i].tsLastUsed < tsOldest)) { iOldest = i; tsOldest = _Cache[i].tsLastUsed; } } if (tsOldest == tsInfinity) { // // Didn't find anything that could be removed. // return GrowCache(); } // // We have found the one to boot out. // // Convert the lock to write before discarding it. Since we like to leave // things the way we found them, convert it back when we're done. // Discard( iOldest ); PrintSizes(); return(S_OK); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::GetCacheEntry // // Synopsis: Finds something in the cache, adding it if needed and if // requested. // // Effects: May call GetLogonAccount to insert in the cache. // May remove something from the cache to make room. // // Arguments: [pwzName] -- [in] name of principal // [piIndex] -- [out] cache index // [fCacheFlags] -- [in] Flags to put in created cache entry // [fLoad] -- [in] if true, load missing entry. // // Signals: nothing (unless GetLogonAccount throws something). // // Requires: Caller must have read access to _Monitor // // Returns: S_OK if in cache, else result of GetLogonAccount() // KDC_E_C_PRINCIPAL_UNKNOWN if it isn't in the cache and // fLoad == FALSE // // History: 04-Nov-93 WadeR Created // // Notes: This code is not exception-safe. If GetLogonAccount throws // an exception, it will leak resources. // // This routine returns an index into the cache. This index must remain // valid, so you can't allow anyone to change the cache while it's in use. // Therefore the caller must hold a read lock on the cache, and release it // once the caller is done with the index returned. // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::GetCacheEntry( PWCHAR pwzName, PULONG piIndex, ULONG fCacheFlags, BOOL fLoad ) { KdcDebug(( DEB_TRACE, "Looking for account %ws (DRN)\n", pwzName )); // Scan through the cache entries looking for this name. for (ULONG i=0; i<_cCache; i++ ) { if (!wcsicmp(_Cache[i].pwzName, pwzName )) { // // We found the cache entry we are interested in. // *piIndex = i; // // Convert the monitor to write access, so we can modify // the last access time. // _Monitor.ReadToWrite(); GetCurrentTimeStamp( &_Cache[i].tsLastUsed ); // Return the monitor and leave. _Monitor.WriteToRead(); return(S_OK); } } // Didn't find it in the cache. KdcDebug(( DEB_T_CACHE, "Didn't find %ws in the cache.\n", pwzName )); if (!fLoad) { return(KDC_E_C_PRINCIPAL_UNKNOWN); } // // Build the information that we are going to write to the // Cache now, before upgrading to a write lock on the monitor. // This way, we keep an exclusive lock for as little time as // possible. // // // Copy the name, so it stays valid regardless of what the caller // does with it. // PWCHAR pwzNameCopy = new (NullOnFail) WCHAR [wcslen( pwzName ) + 1]; if (pwzNameCopy == 0) { return(E_OUTOFMEMORY); } wcscpy( pwzNameCopy, pwzName ); CLogonAccount* plga; // // Get the account object // HRESULT hr = GetLogonAccount( pwzName, TRUE, // Domain namespace &plga ); if (FAILED(hr)) { KdcDebug(( DEB_WARN, "Error finding principal '%ws' (0x%X)\n", pwzName, hr )); delete pwzNameCopy; return(hr); } TimeStamp tsNow; GetCurrentTimeStamp( &tsNow ); _Monitor.ReadToWrite(); if (_cCache == _cCacheMaxSize) { // Cache is full, so must purge something. PurgeSomething(); } // // The last entry is free now. // // // BUGBUG: PurgeSomething could run out of memory. // SafeAssert( _cCache < _cCacheMaxSize ); // // Insert the data we constructed before. // _Cache[_cCache].pwzName = pwzNameCopy; _Cache[_cCache].plga = plga; _Cache[_cCache].tsLastUsed = tsNow; _Cache[_cCache].fCacheFlags = fCacheFlags; *piIndex = _cCache; _cCache++; _Monitor.WriteToRead(); // Subtract 1 because we've already incremented the count (inside the // monitor). KdcDebug(( DEB_T_CACHE, "Added %ws in slot %d (flags:%x)\n", _Cache[_cCache-1].pwzName, _cCache-1, _Cache[_cCache-1].fCacheFlags )); PrintSizes(); return(S_OK); } // // // Public methods // // //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::GetTicketInfo // // Synopsis: Gets the ticket-granting info for a principal // // Effects: Cache lookup. // // Arguments: [pwzName] -- [in] Name of principal // [pti] -- [out] ticket info // // Requires: // // Returns: // // Signals: // // Modifies: // // Derivation: // // Algorithm: // // History: 04-Nov-93 WadeR Created // // Notes: This routine is exception-safe. If any of the routines it // calls (GetCacheEntry, CLogonAccount::GetTicketInfo) throw, // it will pass the exception up and not leak resources. // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::GetTicketInfo( PWCHAR pwzName, TicketInfo* pti ) { KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::GetTicketInfo(%ws)\n", pwzName )); SafeAssert( _fInitialized ); ULONG iIndex; PWCHAR pwzDRName = MapNameDRN( pwzName ); CReadLock lock( _Monitor ); RET_IF_ERROR( DEB_WARN, GetCacheEntry(pwzDRName, &iIndex, 0) ); RET_IF_ERROR( DEB_TRACE, _Cache[iIndex].plga->GetTicketInfo( &(pti->gGuid), &(pti->kKey), &(pti->fTicketOpts) )); pti->fLogonSteps = _Cache[iIndex].fCacheFlags & CACHE_DOING_LOGON; return(S_OK); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::GetLogonInfo // // Synopsis: Gets the logon info (and ticket info) for a principal // // Effects: Cache lookup, increments the use count of returned principal // // Arguments: [pwzName] -- [in] name of principal // [pli] -- [out] logon info // [pti] -- [out] ticket info // [phHandle]-- [out] hint for Release, below. // [fLogon] -- [in] true if it's a logon attempt // // Requires: // // Returns: // // Signals: // // Modifies: // // Derivation: // // Algorithm: // // History: 04-Nov-93 WadeR Created // // Notes: This probes the memory returned from // CLogonAccount::GetLogonInfo(). // // This routine will catch any exceptions thrown by it's callees, and return // and error code without leaking resources. // // The caller MUST call ReleaseLogonInfo with the handle returned from this // call when the logon info is no longer needed. The cache will not delete // the logon hours or the valid workstations until ReleaseLogonInfo is called. // //---------------------------------------------------------------------------- #define PROBE_R_DWORD( _x_ ) ((void) (*((volatile long *)(_x_)))) #define PROBE_R_CHAR( _x_ ) ((void) (*((volatile char *)(_x_)))) HRESULT CPrincipalHandler::GetLogonInfo(PWCHAR pwzName, LogonInfo * pli, TicketInfo* pti, PULONG phHandle, BOOL fLogon ) { KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::GetLogonInfo(%ws)\n", pwzName )); SafeAssert( _fInitialized ); HRESULT hr; TRY { CReadLock lock (_Monitor); RtlZeroMemory( pli, sizeof( LogonInfo ) ); RtlZeroMemory( pti, sizeof( TicketInfo ) ); PWCHAR pwzDRName = MapNameDRN( pwzName ); ULONG iIndex; hr = GetCacheEntry( pwzDRName, &iIndex, fLogon? CACHE_DOING_LOGON : 0); if (FAILED(hr)) { KdcDebug(( DEB_TRACE, "GetCacheEntry(%ws)==%x\n", pwzDRName, hr )); goto Error; } *phHandle = iIndex; hr = _Cache[iIndex].plga->GetTicketInfo( &(pti->gGuid), &(pti->kKey), &(pti->fTicketOpts) ); if (FAILED(hr)) { KdcDebug(( DEB_TRACE, "GetTicketInfo(%ws)==%x\n", pwzDRName, hr )); goto Error; } pti->fLogonSteps = _Cache[iIndex].fCacheFlags & CACHE_DOING_LOGON; hr = _Cache[iIndex].plga->GetLogonInfo( &(pli->pbValidLogonHours), &(pli->prpwszValidWorkstations), &(pli->fInteractive), &(pli->fAttributes), &(pli->ftAccountExpiry), &(pli->ftPasswordChange), &(pli->ftLockoutTime) ); if (FAILED(hr)) { KdcDebug(( DEB_TRACE, "GetLogonInfo(%ws)==%x\n", pwzDRName, hr )); goto Error; } // // Check that the pointers, etc, returned are valid. // if (pli->pbValidLogonHours) { PROBE_R_DWORD( pli->pbValidLogonHours ); PROBE_R_DWORD( pli->pbValidLogonHours->pBlobData ); PROBE_R_DWORD( pli->pbValidLogonHours->pBlobData + pli->pbValidLogonHours->cbSize ); #if DBG if (KDCInfoLevel & DEB_T_CACHE) { SECURITY_STRING ss; ss = FormatBytes(pli->pbValidLogonHours->pBlobData, (BYTE) pli->pbValidLogonHours->cbSize ); KdcDebug(( DEB_T_CACHE, "Valid logon hours (%d): %wZ\n", pli->pbValidLogonHours->cbSize, &ss )); SRtlFreeString( &ss ); } #endif } if (pli->prpwszValidWorkstations) { PROBE_R_DWORD( pli->prpwszValidWorkstations ); KdcDebug(( DEB_T_CACHE, "There are %d valid workstations\n", pli->prpwszValidWorkstations->cElems )); for (ULONG i=0; iprpwszValidWorkstations->cElems; i++ ) { (void) wcslen( pli->prpwszValidWorkstations->pElems[i] ); KdcDebug(( DEB_T_CACHE, "Valid workstation[%d] = %ws\n", i, pli->prpwszValidWorkstations->pElems[i] )); } } // // Bump my read lock up to a write lock, then increment the count. // Note that the lock is released at the end of this scope // automatically. // _Monitor.ReadToWrite(); _Cache[iIndex].cUseCount++; _Monitor.WriteToRead(); hr = S_OK; Error: // Fall out of the TRY/CATCH block. ; } CATCH( CException, e ) { hr = e.GetErrorCode(); KdcDebug(( DEB_ERROR, "Exception 0x%X getting and checking logon info for %ws\n", hr, pwzName )); } END_CATCH return(hr); } //+--------------------------------------------------------------------------- // // Function: ReleaseLogonInfo // // Synopsis: Allows the cache entry to be deleted. // // Effects: Decrements the use count // // Arguments: [pwzName] -- Name of principal // [iHandle] -- handle returned from GetLogonInfo // // Returns: void // // History: 04-Nov-93 WadeR Created // // Notes: iHandle is just a hint. If it has been moved, // this will look through the cache for it. // //---------------------------------------------------------------------------- void CPrincipalHandler::ReleaseLogonInfo( PWCHAR pwzName, ULONG iHandle ) { SafeAssert( _fInitialized ); _Monitor.GetRead(); if ( (iHandle >= _cCache) || wcsicmp( _Cache[iHandle].pwzName, pwzName ) != 0) { // // The cache entry must have been moved. // // It still has to be in the cache, because it's tagged as in-use. // This will be fast, and it must succeed, because it won't have to // hit the disk. // #if DBG HRESULT hr = #else (void) #endif GetCacheEntry( pwzName, &iHandle, 0, FALSE ); // No flags, don't load #if DBG Win4Assert( SUCCEEDED( hr ) ); #endif } Win4Assert( _Cache[iHandle].cUseCount > 0 ); _Monitor.ReadToWrite(); _Cache[iHandle].cUseCount--; // If the CACHE_INVALID bit is set, and the CACHE_DOING_LOGON bit is NOT // set, and the use count is zero, then discard it. if (((_Cache[iHandle].fCacheFlags & (CACHE_INVALID | CACHE_DOING_LOGON)) == CACHE_INVALID ) && (_Cache[iHandle].cUseCount == 0) ) { Discard( iHandle ); } _Monitor.Release(); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::GetPAC // // Synopsis: Gets the PAC for a principal. // // Effects: Allocates memory, creates a CPAC // // Arguments: [pwzName] -- [in] principal to get pac for // [ppPAC] -- [out] pac // // Requires: // // Returns: // // Signals: // // Modifies: // // Derivation: // // Algorithm: // // History: 04-Nov-93 WadeR Created // // Notes: // // BUGBUG: Need to merge MikeSe's changes, comment better. // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::GetPAC( PWCHAR pwzName, CPAC ** ppPAC ) { KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::GetPAC(%ws)\n", pwzName )); SafeAssert( _fInitialized ); PWCHAR pwzDRName = MapNameDRN( pwzName ); SECID gSID; KerbKey kGarbage; ULONG dwGarbage; ULONG iIndex; CReadLock lock(_Monitor); RET_IF_ERROR( DEB_WARN, GetCacheEntry(pwzDRName, &iIndex, 0) ); // 0 -> no flags RET_IF_ERROR( DEB_WARN, _Cache[iIndex].plga->GetTicketInfo( &gSID, &kGarbage, &dwGarbage ) ); *ppPAC = new CPAC; (*ppPAC)->Init( _Cache[iIndex].plga->GetGroupInfo(), gSID, pwzName, NULL ); // NT Sid. #if DBG // Why would anyone want to get thier PAC if they aren't doing // a login? if ((_Cache[iIndex].fCacheFlags & CACHE_DOING_LOGON) == 0) { KdcDebug(( DEB_WARN, "Strange, \"%ws\" fetching a PAC outside login.\n", pwzName )); } #endif return(S_OK); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::LogonComplete // // Synopsis: Calls LogonComplete on the CLogonAccount, saves it, releases it. // // Effects: releases memory. // // Arguments: [pwzName] -- [in] Name of principal who's finished logging on. // [fGood] -- [in] True if it's a successful logon. // [ftLock] -- [in] Account locked out until this time. // // Requires: // // Returns: // // Signals: // // Modifies: // // Derivation: // // Algorithm: // // History: 04-Nov-93 WadeR Created // // Notes: This catches any exceptions, and passes back error codes. // This should check the CACHE_INVALID bit and, if set, discard // the entry. But, since this always discards the entry it can // safely ignore the bit. // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::LogonComplete( PWCHAR pwzName, BOOL fGood, FILETIME ftLock ) { SafeAssert( _fInitialized ); HRESULT hr; KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::LogonComplete(%ws)\n", pwzName )); TRY { ULONG iIndex; PWCHAR pwzDRName = MapNameDRN( pwzName ); CReadLock lock(_Monitor); hr = GetCacheEntry(pwzDRName, &iIndex, 0); // no special flags if (SUCCEEDED(hr)) { _Monitor.ReadToWrite(); if ((_Cache[iIndex].fCacheFlags & CACHE_DOING_LOGON) == 0) { // Oops! We aged this principal out of the cache already. // This means that the principal took so long to log on, we // decided that it was a failed logon attempt and we've // already marked it as such. So we'll just ignore this call // to LogonComplete. KdcDebug(( DEB_ERROR, "Someone called LogonComplete when " "they weren't logging on!\n" )); } else { _Cache[iIndex].plga->LogonComplete( fGood, &ftLock ); _Cache[iIndex].plga->Save(NULL, FALSE); } ReleaseCacheEntry( iIndex ); hr = ShrinkCache(); } else { KdcDebug(( DEB_WARN, "GetCacheEntry(%ws) failed 0x%x.\n", pwzDRName, hr )); } } CATCH( CException, e ) { hr = e.GetErrorCode(); KdcDebug(( DEB_ERROR, "Exception 0x%X in LogonComplete( %ws )\n", hr, pwzName )); } END_CATCH return(hr); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::ClearCache // // Synopsis: Removes one or more cache entries // // Effects: May call LogonComplete on some entries. // // Arguments: [pwzName] -- Name of principal. If null, clears everything. // [fForce] -- If true, remove everything, even pending logons. // // Requires: // // Returns: // // Signals: // // Modifies: // // Derivation: // // Algorithm: // // History: 05-Nov-93 WadeR Created // // Notes: // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::ClearCache(PWCHAR pwzName, BOOL fForce) { KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::ClearCache(%ws)\n", pwzName )); SafeAssert( _fInitialized ); // Flag to indicate the principal should be reloaded, because it was // in the middle of a logon. BOOL fReload = FALSE; if (pwzName) { ULONG iIndex; // Get a read lock so we can call GetCacheEntry _Monitor.GetRead(); // Get the cache entry, but don't load it if it isn't there. if (GetCacheEntry(pwzName, &iIndex, 0, FALSE) == S_OK) { // It was found in the cache. // We are going to change it, either to mark it as invalid or to // delete it, so upgrade our lock. _Monitor.ReadToWrite(); // If fForce is true, or the cache entry is in use, we can discard // it. If it's in use, and fForce isn't set, simply mark it as // invalid. if (fForce || (_Cache[iIndex].cUseCount == 0 )) { if (_Cache[iIndex].fCacheFlags & CACHE_DOING_LOGON) { // This principal was in the process of logging on, so // reload the account (so LogonComplete can succeed), // unless fForce is true (because we don't want it to // reappear). fReload = !fForce; } ReleaseCacheEntry( iIndex ); } else { _Cache[iIndex].fCacheFlags |= CACHE_INVALID; } // // If we flushed an account that was in the process of logging on, // we should re-load it. // if (fReload) { ULONG iFoo; _Monitor.WriteToRead(); (void) GetCacheEntry( pwzName, &iFoo, CACHE_DOING_LOGON ); } } _Monitor.Release(); } else { // Some of the cache entries will be in the middle of a logon. We // must save the names away, so they can be re-loaded later. // // Since the cache is fixed size, we know the maximim number of names. PWCHAR * apwzNames = new PWCHAR [_cCacheMaxSize]; ULONG cNamesUsed = 0; // Going to delete everything, so get the lock once up front. _Monitor.GetWrite(); // // This loop is a little strange in that both ends move. The // Discard() method will move the empty slot to the end of the cache, // and decrement _cCache. Since it moves a new cache entry to the // vacated spot, we don't want to increment the index if we discard // something. // // On the other hand, if we don't discard the entry, we must increment // the index to look at the next entry. // // Every iteration of the loop gets one step closer to finishing, // either by raising the index or lowering the max. ULONG iIndex = 0; while (iIndex < _cCache) { if (fForce || (_Cache[iIndex].cUseCount == 0 )) { if (!fForce && _Cache[iIndex].fCacheFlags & CACHE_DOING_LOGON) { apwzNames[cNamesUsed++] = _Cache[iIndex].pwzName; _Cache[iIndex].pwzName = 0; // so it isn't deleted by Discard. } // This will decrement _cCache ReleaseCacheEntry( iIndex ); } else { _Cache[iIndex].fCacheFlags |= CACHE_INVALID; iIndex++; } } // // Finished clearing the cache, so re-load the principals that were in // the middle of a logon. // _Monitor.WriteToRead(); for (iIndex=0; iIndex < cNamesUsed; iIndex++) { ULONG iFoo; (void) GetCacheEntry( apwzNames[iIndex], &iFoo, CACHE_DOING_LOGON); delete apwzNames[iIndex]; } delete apwzNames; _Monitor.Release(); } if ( !((pwzName == NULL) && fForce) ) { // // If we are forcing everything out of the cache, we shouldn't be // loading new stuff again. Otherwise we can count on these being // useful. // // Load the frequently used accounts. // // ULONG iFoo; _Monitor.GetRead(); RET_IF_ERROR( DEB_ERROR, GetCacheEntry( pwcKdcPrincipal, &iFoo, CACHE_STICKY )); RET_IF_ERROR( DEB_ERROR, GetCacheEntry( pwcPSPrincipal, &iFoo, CACHE_STICKY )); _Monitor.Release(); // // Load the ticket info for the KDC and PS // TicketInfo tiKDC; TicketInfo tiPS; RET_IF_ERROR( DEB_ERROR, GetTicketInfo( pwcKdcPrincipal, &tiKDC ) ); RET_IF_ERROR( DEB_ERROR, GetTicketInfo( pwcPSPrincipal, &tiPS ) ); _Monitor.GetWrite(); _tiKdc = tiKDC; _tiPS = tiPS; _Monitor.Release(); } _Monitor.GetWrite(); HRESULT hr = ShrinkCache(); _Monitor.Release(); #if DBG if (FAILED(hr)) { KdcDebug(( DEB_ERROR, "ShrinkCache() == 0x%x\n", hr )); } #endif return(hr); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::AgeCache // // Synopsis: Removes old entries from the cache. // // Effects: May release lots of cache slots. // // Arguments: (none) // // Returns: S_OK // // History: 04-Nov-93 WadeR Created // // Notes: // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::AgeCache() { KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::AgeCache()\n" )); SafeAssert( _fInitialized ); ULONG i; int cRemoved = 0; TimeStamp tsCutoff; GetCurrentTimeStamp( &tsCutoff ); tsCutoff = tsCutoff - _tsMaxAge; CWriteLock lock( _Monitor ); for (i=0; i<_cCache; i++) { if (!(_Cache[i].fCacheFlags & CACHE_STICKY) && (_Cache[i].cUseCount == 0) && (_Cache[i].tsLastUsed < tsCutoff)) { Discard(i); cRemoved++; } } HRESULT hr = ShrinkCache(); KdcDebug(( DEB_T_CACHE, "AgeCache() removed %d\n", cRemoved )); return(hr); } //+--------------------------------------------------------------------------- // // Member: CPrincipalHandler::Init // // Synopsis: Initializes the principal handler // // Effects: Calls CoInitialize, loads PS and KDC info. // // Arguments: (none) // // Requires: // // Returns: HRESULT // // History: 04-Nov-93 WadeR Created // // Notes: // // BUGBUG: If this fails, it could leak resources. // //---------------------------------------------------------------------------- HRESULT CPrincipalHandler::Init() { KdcDebug(( DEB_T_CACHE, "CPrincipalHandler::Init()\n" )); if (_fInitialized) { Win4Assert( !"CPrincipalHandler::Init() called twice!" ); return(E_UNEXPECTED); } RET_IF_ERROR(DEB_ERROR, _sci.Init() ); _tsMaxAge = tsZero; AddSecondsToTimeStamp( &_tsMaxAge, 60 * 60 ); // BUGBUG: magic numbers. // // Initialize the cache // _cCache = 0; _cCacheMaxSize = CACHE_INITIALSIZE / sizeof( CacheEntry ); _Cache = new (NullOnFail) CacheEntry [ _cCacheMaxSize ]; if (_Cache == NULL) { return(E_OUTOFMEMORY); } _fInitialized = TRUE; // This will load the KDC and PS info. RET_IF_ERROR( DEB_ERROR, ClearCache() ); // // Load the ticket info for the KDC and PS // TicketInfo tiKDC; TicketInfo tiPS; RET_IF_ERROR( DEB_ERROR, GetTicketInfo( pwcKdcPrincipal, &tiKDC ) ); RET_IF_ERROR( DEB_ERROR, GetTicketInfo( pwcPSPrincipal, &tiPS ) ); _Monitor.GetWrite(); _tiKdc = tiKDC; _tiPS = tiPS; _Monitor.Release(); return(S_OK); }