/*++ 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; }