//============================================================ // // UserHive.cpp - Class to load/unload specified user's profile // hive from registry // // Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved // // 01/03/97 a-jmoon created // //============================================================ #include "precomp.h" #include #include #include "sid.h" #include "UserHive.h" #include #include #pragma warning(disable : 4995) // we introduced as including strsafe.h gives error for all unsafe string functions CThreadBase CUserHive::m_criticalSection; /***************************************************************************** * * FUNCTION : CUserHive::CUserHive * * DESCRIPTION : Constructor * * INPUTS : none * * OUTPUTS : none * * RETURNS : nothing * * COMMENTS : * *****************************************************************************/ CUserHive::CUserHive() { OSInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO) ; GetVersionEx(&OSInfo) ; m_pOriginalPriv = NULL; m_dwSize = NULL; m_hKey = NULL; } /***************************************************************************** * * FUNCTION : CUserHive::~CUserHive * * DESCRIPTION : Destructor * * INPUTS : none * * OUTPUTS : none * * RETURNS : nothing * * COMMENTS : * *****************************************************************************/ CUserHive::~CUserHive() { #ifdef NTONLY if (m_pOriginalPriv) RestorePrivilege(); #endif // NOTE: The destructor does not unload the key. Nor does doing a load unload // a previously loaded key; ASSERT_BREAK(m_hKey == NULL); if (m_hKey != NULL) { RegCloseKey(m_hKey); } } /***************************************************************************** * * FUNCTION : CUserHive::AcquirePrivilege * * DESCRIPTION : Acquires SeRestorePrivilege for calling thread * * INPUTS : none * * OUTPUTS : none * * RETURNS : nothing * * COMMENTS : * *****************************************************************************/ #ifdef NTONLY DWORD CUserHive::AcquirePrivilege() { // are you calling in twice? Shouldn't. // at worst, it would cause a leak, so I'm going with it anyway. ASSERT_BREAK(m_pOriginalPriv == NULL); BOOL bRetCode = FALSE; SmartCloseHandle hToken; TOKEN_PRIVILEGES TPriv ; LUID LUID ; // Validate the platform //====================== // Try getting the thread token. if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &hToken)) { GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &m_dwSize); if (m_dwSize > 0) { // This is cleaned in the destructor, so no try/catch required m_pOriginalPriv = (TOKEN_PRIVILEGES*) new BYTE[m_dwSize]; if (m_pOriginalPriv == NULL) { throw CHeap_Exception ( CHeap_Exception :: E_ALLOCATION_ERROR ) ; } } if (m_pOriginalPriv && GetTokenInformation(hToken, TokenPrivileges, m_pOriginalPriv, m_dwSize, &m_dwSize)) { bRetCode = LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &LUID) ; if(bRetCode) { TPriv.PrivilegeCount = 1 ; TPriv.Privileges[0].Luid = LUID ; TPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED ; bRetCode = AdjustTokenPrivileges(hToken, FALSE, &TPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL) ; } bRetCode = LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &LUID) ; if(bRetCode) { TPriv.PrivilegeCount = 1 ; TPriv.Privileges[0].Luid = LUID ; TPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED ; bRetCode = AdjustTokenPrivileges(hToken, FALSE, &TPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL) ; } } } if(!bRetCode) { return GetLastError() ; } return ERROR_SUCCESS ; } #endif /***************************************************************************** * * FUNCTION : CUserHive::RestorePrivilege * * DESCRIPTION : Restores original status of SeRestorePrivilege * * INPUTS : none * * OUTPUTS : none * * RETURNS : nothing * * COMMENTS : * *****************************************************************************/ #ifdef NTONLY void CUserHive::RestorePrivilege() { ASSERT_BREAK(m_pOriginalPriv != NULL); if (m_pOriginalPriv != NULL) { SmartCloseHandle hToken; try { if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, TRUE, &hToken)) { AdjustTokenPrivileges(hToken, FALSE, m_pOriginalPriv, m_dwSize, NULL, NULL); } } catch ( ... ) { delete m_pOriginalPriv; m_pOriginalPriv = NULL; m_dwSize = 0; throw; } delete m_pOriginalPriv; m_pOriginalPriv = NULL; m_dwSize = 0; } } #endif DWORD CUserHive::Load(LPCWSTR pszUserName, LPWSTR pszKeyName, size_t PATHSIZE) { // NOTE: The destructor does not unload the key. Nor does doing a load unload // a previously loaded key; ASSERT_BREAK(m_hKey == NULL); #ifdef NTONLY return LoadNT(pszUserName, pszKeyName, PATHSIZE); #endif #ifdef WIN9XONLY return Load95(pszUserName, pszKeyName); #endif } /***************************************************************************** * * FUNCTION : CUserHive::LoadNT * * DESCRIPTION : Locates user's hive & loads into registry if not already * present * * INPUTS : none * * OUTPUTS : pszKeyName receives the expanded SID of the user's * registry key under HKEY_USERS * * RETURNS : nothing * * COMMENTS : Hive will remain in registry unless unloaded * *****************************************************************************/ #ifdef NTONLY DWORD CUserHive::LoadNT(LPCTSTR pszUserName, LPTSTR pszKeyName, size_t PATHSIZE) { DWORD i, dwSIDSize, dwRetCode, dwDomainNameSize, dwSubAuthorities ; TCHAR szDomainName[_MAX_PATH], szSID[_MAX_PATH], szTemp[_MAX_PATH] ; PSID pSID = NULL ; PSID_IDENTIFIER_AUTHORITY pSIA ; SID_NAME_USE AccountType ; CHString sTemp ; CRegistry Reg ; // Set the necessary privs //======================== dwRetCode = AcquirePrivilege() ; if(dwRetCode != ERROR_SUCCESS) { return dwRetCode ; } // Look up the user's account info //================================ dwSIDSize = 0L ; dwDomainNameSize = sizeof(szDomainName) ; BOOL bLookup = FALSE; { bLookup = LookupAccountName(NULL, pszUserName, pSID, &dwSIDSize, szDomainName, &dwDomainNameSize, &AccountType); ASSERT_BREAK ( bLookup == FALSE ); if ( ERROR_INSUFFICIENT_BUFFER == ::GetLastError () ) { pSID = (PSID) malloc( dwSIDSize ); if ( NULL != pSID ) { bLookup = LookupAccountName(NULL, pszUserName, pSID, &dwSIDSize, szDomainName, &dwDomainNameSize, &AccountType); } else { RestorePrivilege() ; return ERROR_NOT_ENOUGH_MEMORY ; } } } if(!bLookup) { if ( pSID ) { free ( pSID ) ; pSID = NULL ; } RestorePrivilege() ; return ERROR_BAD_USERNAME ; } // Translate the SID into text (a la PSS article Q131320) //======================================================= pSIA = GetSidIdentifierAuthority(pSID) ; dwSubAuthorities = *GetSidSubAuthorityCount(pSID) ; dwSIDSize = _stprintf(szSID, _T("S-%lu-"), (DWORD) SID_REVISION) ; if((pSIA->Value[0] != 0) || (pSIA->Value[1] != 0) ) { dwSIDSize += _stprintf(szSID + _tcslen(szSID), _T("0x%02hx%02hx%02hx%02hx%02hx%02hx"), (USHORT) pSIA->Value[0], (USHORT) pSIA->Value[1], (USHORT) pSIA->Value[2], (USHORT) pSIA->Value[3], (USHORT) pSIA->Value[4], (USHORT) pSIA->Value[5]) ; } else { dwSIDSize += _stprintf(szSID + _tcslen(szSID), _T("%lu"), (ULONG)(pSIA->Value[5] ) + (ULONG)(pSIA->Value[4] << 8) + (ULONG)(pSIA->Value[3] << 16) + (ULONG)(pSIA->Value[2] << 24)); } for(i = 0 ; i < dwSubAuthorities ; i++) { dwSIDSize += _stprintf(szSID + dwSIDSize, _T("-%lu"), *GetSidSubAuthority(pSID, i)) ; } free ( pSID ) ; pSID = NULL ; // See if the key already exists //============================== dwRetCode = Reg.Open(HKEY_USERS, szSID, KEY_READ) ; // We need to keep a handle open. See m_hKey below, so we'll let the destructor close this. // Reg.Close(); if(dwRetCode != ERROR_SUCCESS) { // Try to locate user's registry hive //=================================== _stprintf(szTemp, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%s"), szSID) ; dwRetCode = Reg.Open(HKEY_LOCAL_MACHINE, szTemp, KEY_READ) ; if(dwRetCode == ERROR_SUCCESS) { dwRetCode = Reg.GetCurrentKeyValue(_T("ProfileImagePath"), sTemp) ; Reg.Close() ; if(dwRetCode == ERROR_SUCCESS) { // NT 4 doesn't include the file name in the registry //=================================================== if(OSInfo.dwMajorVersion >= 4) { sTemp += _T("\\NTUSER.DAT") ; } ExpandEnvironmentStrings(LPCTSTR(sTemp), szTemp, sizeof(szTemp) / sizeof(TCHAR)) ; // Try it three times, another process may have the file open bool bTryTryAgain = false; int nTries = 0; do { // need to serialize access, using "write" because RegLoadKey wants exclusive access // even though it is a read operation m_criticalSection.BeginWrite(); try { dwRetCode = (DWORD) RegLoadKey(HKEY_USERS, szSID, szTemp) ; } catch ( ... ) { m_criticalSection.EndWrite(); throw; } m_criticalSection.EndWrite(); if ((dwRetCode == ERROR_SHARING_VIOLATION) && (++nTries < 11)) { Sleep(20 * nTries); bTryTryAgain = true; } else { bTryTryAgain = false; } } while (bTryTryAgain); // if we still can't get in, tell somebody. if (dwRetCode == ERROR_SHARING_VIOLATION) LogErrorMessage(_T("Sharing violation on NTUSER.DAT (Load)")); } } } if(dwRetCode == ERROR_SUCCESS) { HRESULT hr = StringCchCopy(pszKeyName, PATHSIZE, szSID) ; ASSERT_BREAK(SUCCEEDED(hr)); LONG lRetVal; CHString sKey(szSID); sKey += _T("\\Software"); lRetVal = RegOpenKeyEx(HKEY_USERS, sKey, 0, KEY_QUERY_VALUE, &m_hKey); ASSERT_BREAK(lRetVal == ERROR_SUCCESS); } // Restore original privilege level & end self-impersonation //========================================================== RestorePrivilege() ; return dwRetCode ; } #endif /***************************************************************************** * * FUNCTION : CUserHive::Load95 * * DESCRIPTION : Locates user's hive & loads into registry if not already * present * * INPUTS : none * * OUTPUTS : pszKeyName receives the expanded SID of the user's * registry key under HKEY_USERS * * RETURNS : nothing * * COMMENTS : Hive will remain in registry unless unloaded * *****************************************************************************/ #ifdef WIN9XONLY DWORD CUserHive::Load95(LPCWSTR pszUserName, LPWSTR pszKeyName) { DWORD dwRetCode; WCHAR wszTemp[_MAX_PATH]; TCHAR szTemp[_MAX_PATH]; CHString sTemp ; CRegistry Reg ; wcscpy(pszKeyName, pszUserName); // See if the key already exists //============================== dwRetCode = Reg.Open(HKEY_USERS, pszKeyName, KEY_READ) ; // We need to keep a handle open. See m_hKey below, so we'll let the destructor close this. // Reg.Close() ; if(dwRetCode == ERROR_SUCCESS) { // We need to keep a handle open. See m_hKey below, so we'll let the destructor close this. // Reg.Close() ; } else { // Try to locate user's registry hive //=================================== swprintf(wszTemp, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\ProfileList\\%s", pszUserName) ; dwRetCode = Reg.Open(HKEY_LOCAL_MACHINE, wszTemp, KEY_READ); if(dwRetCode == ERROR_SUCCESS) { dwRetCode = Reg.GetCurrentKeyValue(L"ProfileImagePath", sTemp) ; Reg.Close() ; if(dwRetCode == ERROR_SUCCESS) { sTemp += _T("\\USER.DAT") ; ExpandEnvironmentStrings(TOBSTRT(sTemp), szTemp, sizeof(szTemp) / sizeof(TCHAR)) ; // Try it three times, another process may have the file open bool bTryTryAgain = false; int nTries = 0; do { // need to serialize access, using "write" because RegLoadKey wants exclusive access // even though it is a read operation m_criticalSection.BeginWrite(); try { dwRetCode = (DWORD) RegLoadKey(HKEY_USERS, TOBSTRT(pszUserName), szTemp) ; } catch ( ... ) { m_criticalSection.EndWrite(); throw; } m_criticalSection.EndWrite(); if ((dwRetCode == ERROR_SHARING_VIOLATION) && (++nTries < 11)) { LogErrorMessage(L"Sharing violation on USER.DAT (Load)"); Sleep(15 * nTries); bTryTryAgain = true; } else bTryTryAgain = false; } while (bTryTryAgain); } } } if (dwRetCode == ERROR_SUCCESS) { LONG lRetVal; CHString sKey(pszUserName); sKey += L"\\Software"; lRetVal = RegOpenKeyEx(HKEY_USERS, TOBSTRT(sKey), 0, KEY_QUERY_VALUE, &m_hKey); ASSERT_BREAK(lRetVal == ERROR_SUCCESS); } return dwRetCode ; } #endif /***************************************************************************** * * FUNCTION : CUserHive::LoadProfile * * DESCRIPTION : Locates user's hive & loads into registry if not already * present * * INPUTS : none * * OUTPUTS : none. * * RETURNS : nothing * * COMMENTS : Hive will remain in registry unless unloaded * NT Only. * *****************************************************************************/ DWORD CUserHive::LoadProfile( LPCWSTR pszSID, CHString& strUserName ) { // NOTE: The destructor does not unload the key. Nor does doing a load unload // a previously loaded key; ASSERT_BREAK(m_hKey == NULL); DWORD dwRetCode = ERROR_SUCCESS; WCHAR szTemp[_MAX_PATH] ; CHString sTemp ; CRegistry Reg ; strUserName = L""; // Set the necessary privs //======================== #ifdef NTONLY dwRetCode = AcquirePrivilege() ; #endif if(dwRetCode != ERROR_SUCCESS) { return dwRetCode ; } // See if the key already exists //============================== dwRetCode = Reg.Open(HKEY_USERS, pszSID, KEY_READ) ; // We need to keep a handle open. See m_hKey below, so we'll let the destructor close this. // Reg.Close() ; // If we got the profile, make sure we can get account information regarding // the SID. if(dwRetCode == ERROR_SUCCESS) { CRegistry Reg2 ; StringCbPrintf(szTemp, _MAX_PATH, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%s", pszSID) ; dwRetCode = Reg2.Open(HKEY_LOCAL_MACHINE, szTemp, KEY_READ) ; if(dwRetCode == ERROR_SUCCESS) { // Load the user account information dwRetCode = UserAccountFromProfile( Reg2, strUserName ); // We need to keep a handle open. See m_hKey below, so we'll let the destructor close this. Reg2.Close() ; } } else { // Try to locate user's registry hive //=================================== StringCbPrintf(szTemp,_MAX_PATH, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%s", pszSID) ; dwRetCode = Reg.Open(HKEY_LOCAL_MACHINE, szTemp, KEY_READ) ; if(dwRetCode == ERROR_SUCCESS) { UserAccountFromProfile( Reg, strUserName ); dwRetCode = Reg.GetCurrentKeyValue(L"ProfileImagePath", sTemp) ; Reg.Close() ; if(dwRetCode == ERROR_SUCCESS) { // NT 4 doesn't include the file name in the registry //=================================================== if(OSInfo.dwMajorVersion >= 4) { sTemp += _T("\\NTUSER.DAT") ; } TCHAR szTemp[MAX_PATH]; ExpandEnvironmentStrings(TOBSTRT(sTemp), szTemp, sizeof(szTemp) / sizeof(TCHAR)) ; // Try it three times, another process may have the file open bool bTryTryAgain = false; int nTries = 0; do { // need to serialize access, using "write" because RegLoadKey wants exclusive access // even though it is a read operation m_criticalSection.BeginWrite(); try { dwRetCode = (DWORD) RegLoadKey(HKEY_USERS, TOBSTRT(pszSID), szTemp); } catch ( ... ) { m_criticalSection.EndWrite(); throw; } m_criticalSection.EndWrite(); if ((dwRetCode == ERROR_SHARING_VIOLATION) && (++nTries < 11)) { LogErrorMessage(L"Sharing violation on NTUSER.DAT (LoadProfile)"); Sleep(20 * nTries); bTryTryAgain = true; } else { bTryTryAgain = false; } } while (bTryTryAgain); } } } if(dwRetCode == ERROR_SUCCESS) { LONG lRetVal; CHString sKey(pszSID); sKey += _T("\\Software"); lRetVal = RegOpenKeyEx(HKEY_USERS, TOBSTRT(sKey), 0, KEY_QUERY_VALUE, &m_hKey); ASSERT_BREAK(lRetVal == ERROR_SUCCESS); } // Restore original privilege level & end self-impersonation //========================================================== #ifdef NTONLY RestorePrivilege() ; #endif return dwRetCode ; } /***************************************************************************** * * FUNCTION : CUserHive::UserAccountFromProfile * * DESCRIPTION : Pulls the PSID out of the registry object, and creates * a DOMAIN\UserName value. * * INPUTS : none * * OUTPUTS : none. * * RETURNS : nothing * * COMMENTS : Registry Object must be preloaded to the correct profile * key. * *****************************************************************************/ DWORD CUserHive::UserAccountFromProfile( CRegistry& reg, CHString& strUserName ) { DWORD dwReturn = ERROR_SUCCESS, dwSidSize = 0; if ( ( dwReturn = reg.GetCurrentBinaryKeyValue( L"Sid", NULL, &dwSidSize ) ) == ERROR_SUCCESS ) { PSID psid = new byte [ dwSidSize ]; if ( NULL != psid ) { try { if ( ( dwReturn = reg.GetCurrentBinaryKeyValue( L"Sid", (LPBYTE) psid, &dwSidSize ) ) == ERROR_SUCCESS ) { CSid sid( psid ); // The sid account type must be valid and the lookup must have been // successful. if ( sid.IsOK() && sid.IsAccountTypeValid() ) { sid.GetDomainAccountName( strUserName ); } else { dwReturn = ERROR_NO_SUCH_USER; } } } catch ( ... ) { delete [] psid; throw ; } delete [] psid; } else { throw CHeap_Exception ( CHeap_Exception :: E_ALLOCATION_ERROR ) ; } } return dwReturn; } /***************************************************************************** * * FUNCTION : CUserHive::Unload * * DESCRIPTION : Unloads key from HKEY_USERS if present * * INPUTS : none * * OUTPUTS : none * * RETURNS : Windows error code * * COMMENTS : * *****************************************************************************/ DWORD CUserHive::Unload(LPCWSTR pszKeyName) { DWORD dwRetCode = ERROR_SUCCESS; if (m_hKey != NULL) { RegCloseKey(m_hKey); m_hKey = NULL; } #ifdef NTONLY dwRetCode = AcquirePrivilege(); #endif if(dwRetCode == ERROR_SUCCESS) { m_criticalSection.BeginWrite(); try { dwRetCode = RegUnLoadKey(HKEY_USERS, TOBSTRT(pszKeyName)) ; } catch ( ... ) { m_criticalSection.EndWrite(); throw; } m_criticalSection.EndWrite(); #ifdef NTONLY RestorePrivilege() ; #endif } return dwRetCode ; }