|
|
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
cache.cxx
Abstract:
Credential cache object for digest sspi package.
Author:
Adriaan Canter (adriaanc) 01-Aug-1998
--*/
#include "include.hxx"
//-----------------CCredCache Private Functions --------------------------------
//--------------------------------------------------------------------
// CCredCache::Lock
//--------------------------------------------------------------------
BOOL CCredCache::Lock() { BOOL bRet; DWORD dwError;
dwError = WaitForSingleObject(_hMutex, INFINITE);
switch (dwError) { // Mutex is signalled. We own the mutex. Fall through.
case WAIT_OBJECT_0:
// The thread owning the mutex failed to release it
// before terminating. We still own the mutex.
case WAIT_ABANDONED: bRet = TRUE; break;
// Fall through.
case WAIT_FAILED:
// Fail.
default: bRet = FALSE; } return bRet; }
//--------------------------------------------------------------------
// CCredCache::Unlock
//--------------------------------------------------------------------
BOOL CCredCache::Unlock() { BOOL bRet;
bRet = ReleaseMutex(_hMutex);
return bRet; }
//--------------------------------------------------------------------
// CCredCache::GetPtrToObject
//--------------------------------------------------------------------
LPDWORD CCredCache::GetPtrToObject(DWORD dwObject) { return _pMMFile->GetHeaderData(dwObject); }
//--------------------------------------------------------------------
// CCredCache::SearchCredList
//--------------------------------------------------------------------
CCred* CCredCache::SearchCredList(CSess *pSess, LPSTR szHost, LPSTR szRealm, LPSTR szUser, BOOL fMatchHost) { CList CredList; CCred *pMatch = NULL;
if (!pSess->dwCred) goto exit;
CredList.Init(&pSess->dwCred); while (pMatch = (CCred*) CredList.GetNext()) { if ((!szRealm || !lstrcmpi(szRealm, CCred::GetRealm(pMatch))) && (!szUser || !lstrcmpi(szUser, CCred::GetUser(pMatch)))) { if (!fMatchHost) break;
CNonce *pNonce; CList NonceList; NonceList.Init(&pMatch->dwNonce); while (pNonce = (CNonce*) NonceList.GetNext()) { if (CNonce::IsHostMatch(pNonce, szHost)) goto exit; }
pMatch = NULL; break; } } exit: return pMatch; }
//--------------------------------------------------------------------
// CCredCache::UpdateInfoList
//--------------------------------------------------------------------
CCredInfo* CCredCache::UpdateInfoList(CCredInfo *pInfo, CCredInfo *pHead) { CCredInfo *pList, *pCur; BOOL fUpdate = TRUE;
if (!pHead) return (pInfo);
pList = pCur = pHead;
while (pCur) { // Do entry usernames match ?
if (!strcmp(pInfo->szUser, pCur->szUser)) { // Is the new entry timestamp greater?
if (pInfo->tStamp > pCur->tStamp) { // De-link existing entry.
if (pCur->pPrev) pCur->pPrev->pNext = pCur->pNext; else pList = pCur->pNext;
if (pCur->pNext) pCur->pNext->pPrev = pCur->pPrev;
// Delete existing entry.
delete pCur; } else { // Found a match but time stamp
// of existing entry was greater.
fUpdate = FALSE; } break; } pCur = pCur->pNext; }
// If we superceded an existing matching entry
// or found no matching entries, prepend to list.
if (fUpdate) { pInfo->pNext = pList; if (pList) pList->pPrev = pInfo; pList = pInfo; }
return pList; }
//-----------------CCredCache Public Functions --------------------------------
//--------------------------------------------------------------------
// CCredCache::GetHeapPtr
//--------------------------------------------------------------------
DWORD_PTR CCredCache::GetHeapPtr() { return _pMMFile->GetMapPtr(); }
//--------------------------------------------------------------------
// CCredCache::IsTrustedHost
// BUGBUG - no limits on szCtx
//--------------------------------------------------------------------
BOOL CCredCache::IsTrustedHost(LPSTR szCtx, LPSTR szHost) { CHAR szBuf[MAX_PATH]; CHAR szRegPath[MAX_PATH];
DWORD dwType, dwError, cbBuf = MAX_PATH; BOOL fRet = FALSE; HKEY hHosts = (HKEY) INVALID_HANDLE_VALUE;
memcpy(szRegPath, DIGEST_HOSTS_REG_KEY, sizeof(DIGEST_HOSTS_REG_KEY) - 1); memcpy(szRegPath + sizeof(DIGEST_HOSTS_REG_KEY) - 1, szCtx, strlen(szCtx) + 1);
if ((dwError = RegCreateKey(HKEY_CURRENT_USER, szRegPath, &hHosts)) == ERROR_SUCCESS) { if ((dwError = RegQueryValueEx(hHosts, szHost, NULL, &dwType, (LPBYTE) szBuf, &cbBuf)) == ERROR_SUCCESS) { fRet = TRUE; } }
if (hHosts != INVALID_HANDLE_VALUE) RegCloseKey(hHosts);
return fRet; }
//--------------------------------------------------------------------
// CCredCache::SetTrustedHostInfo
//--------------------------------------------------------------------
BOOL CCredCache::SetTrustedHostInfo(LPSTR szCtx, CParams *pParams) { CHAR szRegPath[MAX_PATH], *szUrlBuf = NULL, *szHostBuf = NULL; DWORD dwZero = 0, dwError = ERROR_SUCCESS, cbUrlBuf, cbHostBuf; BOOL fRet = FALSE; HKEY hHosts = (HKEY) INVALID_HANDLE_VALUE;
// Form path to trusted host reg key.
memcpy(szRegPath, DIGEST_HOSTS_REG_KEY, sizeof(DIGEST_HOSTS_REG_KEY) - 1); memcpy(szRegPath + sizeof(DIGEST_HOSTS_REG_KEY) - 1, szCtx, strlen(szCtx) + 1);
// Open top-level reg key.
if ((dwError = RegCreateKey(HKEY_CURRENT_USER, szRegPath, &hHosts)) != ERROR_SUCCESS) goto exit;
// First set authenticating host in registry.
LPSTR szHost; szHost = pParams->GetParam(CParams::HOST); DIGEST_ASSERT(szHost);
if ((dwError = RegSetValueEx(hHosts, szHost, NULL, REG_DWORD, (LPBYTE) &dwZero, sizeof(DWORD))) != ERROR_SUCCESS) goto exit;
// Now check the domain header for any additional trusted hosts.
LPSTR szDomain, pszUrl; DWORD cbDomain, cbUrl; pszUrl = NULL; pParams->GetParam(CParams::DOMAIN, &szDomain, &cbDomain); if (!szDomain) { fRet = TRUE; goto exit; }
// Parse the domain header for urls. Crack each url to get the
// host and set the host value in the registry.
// First attempt to load shlwapi. If this fails then we simply do not have
// domain header support.
if (!g_hShlwapi) { g_hShlwapi = LoadLibrary(SHLWAPI_DLL_SZ); if (!g_hShlwapi) { dwError = ERROR_DLL_INIT_FAILED; goto exit; } }
// Attempt to get addresses of UrlUnescape and UrlGetPart
PFNURLUNESCAPE pfnUrlUnescape; PFNURLGETPART pfnUrlGetPart; pfnUrlUnescape = (PFNURLUNESCAPE) GetProcAddress(g_hShlwapi, "UrlUnescapeA"); pfnUrlGetPart = (PFNURLGETPART) GetProcAddress(g_hShlwapi, "UrlGetPartA"); if (!(pfnUrlUnescape && pfnUrlGetPart)) { dwError = ERROR_INVALID_FUNCTION; goto exit; }
// Strtok through string to get each url (ws and tab delimiters)
pszUrl = NULL; while (pszUrl = strtok((pszUrl ? NULL : szDomain), " \t")) { // Allocate a buffer for the url since we will first unescape it.
// Also allocate buffer for host which will be returned from
// call to shlwapi. Unescaped url and host buffer sizes are
// bounded by length of original url.
cbUrl = strlen(pszUrl) + 1; cbUrlBuf = cbHostBuf = cbUrl; szUrlBuf = new CHAR[cbUrlBuf]; szHostBuf = new CHAR[cbHostBuf]; if (!(szUrlBuf && szHostBuf)) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto exit; }
// Copy strtoked url to buffer.
memcpy(szUrlBuf, pszUrl, cbUrl);
// Unescape the url
if (S_OK == pfnUrlUnescape(szUrlBuf, NULL, NULL, URL_UNESCAPE_INPLACE)) { // If unescape is successful, parse host from url.
if (S_OK == pfnUrlGetPart(szUrlBuf, szHostBuf, &cbHostBuf, URL_PART_HOSTNAME, 0)) { // If parse is successful, set host in registry.
if ((dwError = RegSetValueEx(hHosts, szHostBuf, NULL, REG_DWORD, (LPBYTE) &dwZero, sizeof(DWORD))) != ERROR_SUCCESS) goto exit; } } delete [] szUrlBuf; delete [] szHostBuf; szUrlBuf = szHostBuf = NULL; }
fRet = TRUE;
// Cleanup.
exit:
DIGEST_ASSERT(dwError == ERROR_SUCCESS);
if (hHosts != INVALID_HANDLE_VALUE) RegCloseKey(hHosts);
if (szUrlBuf) delete [] szUrlBuf;
if (szHostBuf) delete [] szHostBuf;
return fRet; }
//--------------------------------------------------------------------
// CCredCache::MapHandleToSession
//--------------------------------------------------------------------
// BUGBUG - don't walk the sessionlist, just obfuscate the ptr in handle.
CSess *CCredCache::MapHandleToSession(DWORD_PTR dwSess) { // BUGBUG - if locking fails, return error directly,
// no last error.
CSess *pSess = NULL; if (!Lock()) { DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); goto exit; }
_pSessList->Seek();
while (pSess = (CSess*) _pSessList->GetNext()) { if ((CSess*) (dwSess + (DWORD_PTR) _pMMFile->GetMapPtr()) == pSess) break; }
Unlock(); exit: return pSess; }
//--------------------------------------------------------------------
// CCredCache::MapSessionToHandle
//--------------------------------------------------------------------
DWORD CCredCache::MapSessionToHandle(CSess* pSess) { DWORD dwSess = 0;
if (!Lock()) { DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); goto exit; }
dwSess = (DWORD) ((DWORD_PTR) pSess - _pMMFile->GetMapPtr()); Unlock();
exit: return dwSess; }
// BUGBUG - init mutex issues.
//--------------------------------------------------------------------
// CCredCache::CCredCache
//--------------------------------------------------------------------
CCredCache::CCredCache() { Init(); }
//--------------------------------------------------------------------
// CCredCache::~CCredCache
//--------------------------------------------------------------------
CCredCache::~CCredCache() { DeInit(); }
//--------------------------------------------------------------------
// CCredCache::Init
//--------------------------------------------------------------------
DWORD CCredCache::Init() { BOOL fFirstProc; CHAR szMutexName[MAX_PATH]; DWORD cbMutexName = MAX_PATH;
_dwSig = SIG_CACH;
// IE5# 89288
// Get mutex name based on user
if ((_dwStatus = CMMFile::MakeUserObjectName(szMutexName, &cbMutexName, MAKE_MUTEX_NAME)) != ERROR_SUCCESS) return _dwStatus; // Create/Open mutex.
_hMutex = CreateMutex(NULL, FALSE, szMutexName);
// BUGBUG - this goes at a higher level.
// BUGBUG - also watch out for failure to create mutex
// and then unlocking it.
if (_hMutex) { // Created/opened mutex. Flag if we're first process.
fFirstProc = (GetLastError() != ERROR_ALREADY_EXISTS); } else { // Failed to create/open mutex.
DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); goto exit; }
// Acquire mutex.
if (!Lock()) { DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); return _dwStatus; }
// Open or create memory map.
_pMMFile = new CMMFile(CRED_CACHE_HEAP_SIZE, CRED_CACHE_ENTRY_SIZE);
if (!_pMMFile) { DIGEST_ASSERT(FALSE); _dwStatus = ERROR_NOT_ENOUGH_MEMORY; goto exit; }
_dwStatus = _pMMFile->Init(); if (_dwStatus != ERROR_SUCCESS) { DIGEST_ASSERT(FALSE); goto exit; }
g_pHeap = GetHeapPtr();
// Initialize session list.
// BUGBUG - check return codes on failure.
_pSessList = new CList();
DIGEST_ASSERT(_pSessList); _pSessList->Init(GetPtrToObject(CRED_CACHE_SESSION_LIST));
exit:
// Relase mutex.
Unlock();
return _dwStatus; }
//--------------------------------------------------------------------
// CCredCache::DeInit
//--------------------------------------------------------------------
DWORD CCredCache::DeInit() { // bugbug - assert session list is null and destroy.
// bugbug - release lock before closing handle.
if (!Lock()) { DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); goto exit; }
delete _pMMFile; _dwStatus = CloseHandle(_hMutex); Unlock();
exit: return _dwStatus; }
//--------------------------------------------------------------------
// CCredCache::LogOnToCache
//--------------------------------------------------------------------
CSess *CCredCache::LogOnToCache(LPSTR szAppCtx, LPSTR szUserCtx, BOOL fHTTP) { CSess *pSessNew = NULL; BOOL fLocked = FALSE;
// Obtain mutex.
if (!Lock()) { DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); goto exit; }
fLocked = TRUE;
// For non-http clients, find or create the single
// global session which all non-http clients use.
if (!fHTTP) { CSess * pSess; pSess = NULL; _pSessList->Seek(); while (pSess = (CSess*) _pSessList->GetNext()) { if (!pSess->fHTTP) { // Found the session.
pSessNew = pSess; _dwStatus = ERROR_SUCCESS; goto exit; } } if (!pSessNew) { // Create the non-http gobal session.
pSessNew = CSess::Create(_pMMFile, NULL, NULL, FALSE); } } else { // Create a session context; add to list.
pSessNew = CSess::Create(_pMMFile, szAppCtx, szUserCtx, TRUE); }
if (!pSessNew) { // This reflects running out of space in the memmap
// file. Shouldn't happen in practice.
DIGEST_ASSERT(FALSE); _dwStatus = ERROR_NOT_ENOUGH_MEMORY; goto exit; }
// Push this session on to the session list.
_pSessList->Insert(pSessNew);
_dwStatus = ERROR_SUCCESS;
exit:
// Release mutex.
if(fLocked) Unlock(); return pSessNew; }
//--------------------------------------------------------------------
// CCredCache::LogOffFromCache
//--------------------------------------------------------------------
DWORD CCredCache::LogOffFromCache(CSess *pSess) { CList CredList; CCred *pCred;
// Obtain mutex.
if (!Lock()) { DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); goto exit; }
if (pSess->fHTTP) { // Purge all credentials for this session.
// BUGBUG - not needed.
// CredList.Init((CEntry **) &pSess->pCred);
// Flush all credentials for this session
// This will also delete nonces.
FlushCreds(pSess, NULL);
// Finally, free the session.
_pSessList->DeLink(pSess); CEntry::Free(_pMMFile, pSess); }
_dwStatus = ERROR_SUCCESS; // Release mutex.
Unlock();
exit: return _dwStatus; }
//--------------------------------------------------------------------
// CCredCache::CreateCred
//--------------------------------------------------------------------
CCred* CCredCache::CreateCred(CSess *pSess, CCredInfo *pInfo) { CCred* pCred = NULL, *pCredList; CList CredList;
// Obtain mutex.
if (!Lock()) { _dwStatus = GetLastError(); goto exit; }
// First check to see if any credential in this session matches realm.
pCred = SearchCredList(pSess, NULL, pInfo->szRealm, NULL, FALSE); if (pCred) { CredList.Init(&pSess->dwCred); CredList.DeLink(pCred); CCred::Free(_pMMFile, pSess, pCred); }
// Create a credential.
// BUGBUG - this could fail, transact any cred update.
pCred = CCred::Create(_pMMFile, pInfo->szHost, pInfo->szRealm, pInfo->szUser, pInfo->szPass, pInfo->szNonce, pInfo->szCNonce);
DIGEST_ASSERT(pCred);
// Insert into head of session's credential list.
if (!CSess::GetCred(pSess)) CSess::SetCred(pSess, pCred); else { CredList.Init(&pSess->dwCred); CredList.Insert(pCred); }
_dwStatus = ERROR_SUCCESS;
// Relase mutex.
Unlock();
exit: return pCred; }
//--------------------------------------------------------------------
// CCredCache::FindCred
//--------------------------------------------------------------------
CCredInfo* CCredCache::FindCred(CSess *pSessIn, LPSTR szHost, LPSTR szRealm, LPSTR szUser, LPSTR szNonce, LPSTR szCNonce, DWORD dwFlags) { CCred *pCred; CCredInfo *pInfo = NULL; BOOL fLocked = FALSE;
// Obtain mutex.
if (!Lock()) { DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); goto exit; }
fLocked = TRUE;
// If finding a credential for preauthentication.
if (dwFlags & FIND_CRED_PREAUTH) { // First search this session's credential list for a match,
// filtering on the host field in available nonces.
pCred = SearchCredList(pSessIn, szHost, szRealm, szUser, TRUE);
// If a credential is found the nonce is also required.
// We do not attempt to search other sessions for a nonce
// because nonce counts must remain in sync. See note below.
if (pCred) { // Increment this credential's nonce count.
CNonce *pNonce; pNonce = CCred::GetNonce(pCred, szHost, SERVER_NONCE); if (pNonce) { pNonce->cCount++; pInfo = new CCredInfo(pCred, szHost); if (!pInfo || pInfo->dwStatus != ERROR_SUCCESS) { DIGEST_ASSERT(FALSE); _dwStatus = ERROR_NOT_ENOUGH_MEMORY; goto exit; } } } }
// Otherwise if finding a credential for response to challenge.
else if (dwFlags & FIND_CRED_AUTH) { // First search this session's credential list for a match,
// ignoring the host field in available nonces.
pCred = SearchCredList(pSessIn, NULL, szRealm, szUser, FALSE);
// If a credential was found.
if (pCred) { // Update the credential's nonce value if extant.
// SetNonce will update any existing nonce entry
// or create a new one if necessary.
CCred::SetNonce(_pMMFile, pCred, szHost, szNonce, SERVER_NONCE);
// BUGBUG - if credential contains a client nonce for a host,
// (for MD5-sess) and is challenged for MD5, we don't revert
// the credential's client nonce to null, so that on subsequent
// auths we will default to MD5. Fix is to delete client nonce
// in this case. Not serious problem though since we don't expect this.
if (szCNonce) CCred::SetNonce(_pMMFile, pCred, szHost, szCNonce, CLIENT_NONCE);
// Increment this credential's nonce count.
CNonce *pNonce; pNonce = CCred::GetNonce(pCred, szHost, SERVER_NONCE); pNonce->cCount++;
// Create and return the found credential.
pInfo = new CCredInfo(pCred, szHost); if (!pInfo || pInfo->dwStatus != ERROR_SUCCESS) { DIGEST_ASSERT(FALSE); _dwStatus = ERROR_NOT_ENOUGH_MEMORY; goto exit; } }
// If no credential was found and the username has been specified
// also search other sessions for the latest matching credential.
else if (szUser) { CSess* pSessCur; _pSessList->Seek(); CCred* pMatch; while (pSessCur = (CSess*) _pSessList->GetNext()) { // We've already searched the session passed in.
if (pSessIn == pSessCur) continue;
// Are this session's credentials shareable?
if (CSess::CtxMatch(pSessIn, pSessCur)) { // Find latest credential based on time stamp.
CCred *pCredList; pMatch = SearchCredList(pSessCur, NULL, szRealm, szUser, FALSE); if (pMatch && ((!pCred || (pMatch->tStamp > pCred->tStamp)))) { pCred = pMatch; } } }
// If we found a credential in another session, duplicate it
// and add it to the passed in session's credential list.
// NOTE : WHEN CREATING THE CREDENTIAL DO NOT DUPLICATE
// THE NONCE, OTHERWISE NONCE COUNTS WILL BE INCORRECT.
// USE THE NONCE SUPPLIED IN THE CHALLENGE.
if (pCred) { // Create a cred info from the found credential
// and the nonce received from the challenge.
pInfo = new CCredInfo(szHost, CCred::GetRealm(pCred), CCred::GetUser(pCred), CCred::GetPass(pCred), szNonce, szCNonce);
if (!pInfo || pInfo->dwStatus != ERROR_SUCCESS) { DIGEST_ASSERT(FALSE); _dwStatus = ERROR_NOT_ENOUGH_MEMORY; goto exit; }
// Create the credential in the session list.
pCred = CreateCred(pSessIn, pInfo);
// Increment this credential's nonce count
CNonce *pNonce; pNonce = CCred::GetNonce(pCred, szHost, SERVER_NONCE); pNonce->cCount++; } } }
// Otherwise we are prompting for UI.
else if (dwFlags & FIND_CRED_UI) { // First search this session's credential list for a match,
// ignoring the host field in available nonces.
pCred = SearchCredList(pSessIn, NULL, szRealm, szUser, FALSE);
if (pCred) { // Create and return the found credential.
pInfo = new CCredInfo(szHost, CCred::GetRealm(pCred), CCred::GetUser(pCred), CCred::GetPass(pCred), szNonce, szCNonce);
if (!pInfo || pInfo->dwStatus != ERROR_SUCCESS) { DIGEST_ASSERT(FALSE); _dwStatus = ERROR_NOT_ENOUGH_MEMORY; goto exit; } } else { // No credential found in this session's list. Search
// the credentials in other sessions and assemble a list
// of available credentials. If multiple credentials
// are found for a user, select the latest based on
// time stamp.
CSess* pSessCur; _pSessList->Seek(); while (pSessCur = (CSess*) _pSessList->GetNext()) { // We've already searched the session passed in.
if (pSessIn == pSessCur) continue;
// Are this session's credentials shareable?
if (CSess::CtxMatch(pSessIn, pSessCur)) { pCred = SearchCredList(pSessCur, NULL, szRealm, szUser, FALSE);
if (pCred) { // Found a valid credential.
CCredInfo *pNew; pNew = new CCredInfo(szHost, CCred::GetRealm(pCred), CCred::GetUser(pCred), CCred::GetPass(pCred), szNonce, szCNonce);
if (!pNew || pNew->dwStatus != ERROR_SUCCESS) { DIGEST_ASSERT(FALSE); _dwStatus = ERROR_NOT_ENOUGH_MEMORY; goto exit; }
// Update list based on timestamps.
pInfo = UpdateInfoList(pNew, pInfo);
} } } } }
_dwStatus = ERROR_SUCCESS;
exit:
// Clean up allocated cred infos if
// we failed for some reason.
// bugbug - clean this up.
if (_dwStatus != ERROR_SUCCESS) { CCredInfo *pNext; while (pInfo) { pNext = pInfo->pNext; delete pInfo; pInfo = pNext; } pInfo = NULL; }
// Relase mutex.
if(fLocked) Unlock();
// Return any CCredInfo found, possibly a list or NULL.
return pInfo; }
//--------------------------------------------------------------------
// CCredCache::FlushCreds
//--------------------------------------------------------------------
VOID CCredCache::FlushCreds(CSess *pSess, LPSTR szRealm) { CSess *pSessCur; CCred *pCred; CList CredList;
// Obtain mutex.
if (!Lock()) { DIGEST_ASSERT(FALSE); _dwStatus = GetLastError(); return; }
// BUGBUG - don't scan through all sessions.
// BUGBUG - abstract cred deletion.
// Flush all credentials if no session specified
// or only the credentials of the indicated session.
_pSessList->Seek(); while (pSessCur = (CSess*) _pSessList->GetNext()) { if (pSess && (pSessCur != pSess)) continue;
CredList.Init(&pSessCur->dwCred); while (pCred = (CCred*) CredList.GetNext()) { // If a realm is specified, only delete
// credentials with that realm.
if (!szRealm || (!strcmp(szRealm, CCred::GetRealm(pCred)))) CCred::Free(_pMMFile, pSessCur, pCred); } }
// Release mutex.
Unlock(); }
//--------------------------------------------------------------------
// CCredCache::GetStatus
//--------------------------------------------------------------------
DWORD CCredCache::GetStatus() { return _dwStatus; }
|