// // rmigrate.cpp // // Implementation of CTscRegMigrate // // CTscRegMigrate migrates Tsc settings from the registry // to .RDP files // // Copyright(C) Microsoft Corporation 2000 // Author: Nadim Abdo (nadima) // // #include "stdafx.h" #define TRC_GROUP TRC_GROUP_UI #define TRC_FILE "rmigrate.cpp" #include #include "rmigrate.h" #include "autreg.h" #include "rdpfstore.h" #include "sh.h" #ifdef OS_WINCE #include #endif #define TSC_SETTINGS_REG_ROOT TEXT("Software\\Microsoft\\Terminal Server Client\\") #ifdef OS_WINCE #define WBT_SETTINGS TEXT("WBT\\Settings") #endif CTscRegMigrate::CTscRegMigrate() { } CTscRegMigrate::~CTscRegMigrate() { } // // Migrates all tsc settings to files in szRootDirectory // BOOL CTscRegMigrate::MigrateAll(LPTSTR szRootDirectory) { DC_BEGIN_FN("MigrateAll"); TRC_ASSERT(szRootDirectory, (TB,_T("szRootDirectory is NULL"))); TCHAR szFileName[MAX_PATH*2]; TCHAR szKeyName[MAX_PATH+1]; BOOL fCreatedRootDir = FALSE; if(szRootDirectory) { // // Enumerate and migrate all the TS sessions under HKCU // HKEY hRootKey; LONG rc = RegOpenKeyEx(HKEY_CURRENT_USER, TSC_SETTINGS_REG_ROOT, 0, KEY_READ, &hRootKey); if(ERROR_SUCCESS == rc && hRootKey) { DWORD dwIndex = 0; for(;;) { DWORD cName = sizeof(szKeyName)/sizeof(TCHAR) - 1; FILETIME ft; rc = RegEnumKeyEx(hRootKey, dwIndex, szKeyName, &cName, NULL, NULL, NULL, &ft); if(ERROR_SUCCESS == rc) { // // Ughh..hackalicious, don't migrate // the Trace subkey. or the 'Default' // or LocalDevices // subkey as default connectoids // always use new settings // if(_tcscmp(szKeyName, TEXT("Trace")) && _tcscmp(szKeyName, SH_DEFAULT_REG_SESSION) && _tcsicmp(szKeyName, REG_SECURITY_FILTER_SECTION)) { _tcscpy(szFileName, szRootDirectory); _tcscat(szFileName, szKeyName); _tcscat(szFileName, RDP_FILE_EXTENSION); if (!fCreatedRootDir) { // // Only create RD dir if there are keys to migrate // if(CSH::SH_CreateDirectory(szRootDirectory)) { fCreatedRootDir = TRUE; } else { TRC_ERR((TB, _T("Error creating directory %s"),szRootDirectory)); RegCloseKey(hRootKey); return FALSE; } } //Carry on migrating whether this migrate //fails or not CRdpFileStore rdpf; if(rdpf.OpenStore( szFileName ) ) { if(!MigrateSession(szKeyName, &rdpf, TRUE)) { TRC_ERR((TB, _T("Migrate failed session %s - file %s"), szKeyName, szFileName)); } if(rdpf.CommitStore()) { rdpf.CloseStore(); } } } //next dwIndex++; } else { //done enum break; } } rc = RegCloseKey(hRootKey); if(ERROR_SUCCESS == rc) { return TRUE; } else { TRC_ERR((TB,_T("RegCloseKey failed - err:%d"), GetLastError())); } } else { TRC_ERR((TB,_T("Error opening tsc reg key"))); return FALSE; } } DC_END_FN(); return FALSE; } // // Migrates settings in registry to a settings store // params: // szSessionName - session to migrate // pSetStore - settings store to dump the new settings in // fDeleteUnsafeRegKeys - set to TRUE to delete old regkeys after migration // BOOL CTscRegMigrate::MigrateSession(LPTSTR szSessionName, ISettingsStore* pStore, BOOL fDeleteUnsafeRegKeys) { DC_BEGIN_FN("MigrateSession"); TCHAR szRegSection[MAX_PATH]; // // Sessions are migrated by pulling all the registry settings // for a session and flattening them into a settings store // // In the registry case we used to look for settings under HKCU // first, if they were not there we'd try HKLM. // // The migrate code enumerates all values in the registry under // a named session, first for HKLM then for HKCU. By writing the HKCU // settings last, they overwrite any exising HKLM settings, giving // the correct precedence. // // Certain subfolders such as HOTKEYS subfolders and ADDINS are not migrated // as those values always come from the registry. // TRC_ASSERT(szSessionName && pStore, (TB,_T("Invalid params to MigrateSession"))); TRC_ASSERT(pStore->IsOpenForWrite(), (TB,_T("Settings store not open for write"))); if(pStore && pStore->IsOpenForWrite() && szSessionName) { if(!_tcsicmp(szSessionName,SH_DEFAULT_REG_SESSION)) { TRC_ALT((TB,_T("Never migrate 'Default' session"))); return FALSE; } _tcscpy(szRegSection, TSC_SETTINGS_REG_ROOT); _tcsncat(szRegSection, szSessionName, SIZECHAR(szRegSection) - SIZECHAR(TSC_SETTINGS_REG_ROOT)); // // Doesn't matter if HKLM migrate fails // In face it is pretty common because it is usually // not even present // MigrateHiveSettings(HKEY_LOCAL_MACHINE, szRegSection, pStore); if(MigrateHiveSettings(HKEY_CURRENT_USER, szRegSection, pStore)) { #ifdef OS_WINCE // For a WBT configuration, read some additional registry entries // which are common for all the sessions. if (g_CEConfig == CE_CONFIG_WBT) { _tcscpy(szRegSection, TSC_SETTINGS_REG_ROOT); _tcsncat(szRegSection, WBT_SETTINGS, SIZECHAR(szRegSection) - SIZECHAR(TSC_SETTINGS_REG_ROOT)); // // Doesn't matter if HKLM migrate fails // In face it is pretty common because it is usually // not even present /// if (!MigrateHiveSettings(HKEY_LOCAL_MACHINE, szRegSection, pStore)) { TRC_ERR((TB,_T("Unable to read the common settings for WBT"))); } } #endif #ifndef OS_WINCE if (!ConvertPasswordFormat( pStore )) { TRC_ERR((TB,_T("ConvertPasswordFormat failed"))); return FALSE; } #endif // // Flag controlled as we only want to do this when migrating all settings // not necessarily when auto-migrating single settings // if (fDeleteUnsafeRegKeys) { // // After all the migration, delete unsafe entries in the registry // RemoveUnsafeRegEntries(HKEY_LOCAL_MACHINE, szRegSection); RemoveUnsafeRegEntries(HKEY_CURRENT_USER, szRegSection); } return MungeForWin2kDefaults(pStore); } else { return FALSE; } } else { return FALSE; } DC_END_FN(); } // // Migrate settings from hKey\szRootName to the settings store (pSto) // It would have been cool if this function could have been completely // generic, but it has to have specific knowledge of tsc registry layout // because of how mstsc5.0 bogusly special cased so many things. E.g // user name is stored as unicode in a binary blob. // BOOL CTscRegMigrate::MigrateHiveSettings(HKEY hKey, LPCTSTR szRootName, ISettingsStore* pSto) { DC_BEGIN_FN("MigrateHiveSettings"); USES_CONVERSION; HKEY rootKey; LONG rc; BOOL fRet = FALSE; rc = RegOpenKeyEx( hKey, szRootName, 0, KEY_READ | KEY_QUERY_VALUE, &rootKey); if(ERROR_SUCCESS == rc) { // // Enumerate all the values under this key // DWORD dwIndex = 0; for(;;) { TCHAR szValueName[MAX_PATH]; DWORD dwValueLen = MAX_PATH; DWORD dwType; BYTE buf[MAX_PATH]; DWORD dwBufLen = MAX_PATH; // // It is important to zero the buf // because we read some REG_BINARY's that // are really 0 encoded unicode strings // but tsc4 and 5 had no trailing 0's. // memset(buf, 0, sizeof(buf)); rc =RegEnumValue( rootKey, dwIndex, szValueName, &dwValueLen, NULL, //reserved &dwType, (PBYTE)&buf, //data buffer &dwBufLen); if(ERROR_SUCCESS == rc) { switch(dwType) { case REG_DWORD: { // Store as int UINT value = (UINT)(*((LPDWORD)buf)); fRet = pSto->WriteInt(szValueName, -1, //default ignored value, TRUE); //always write if(!fRet) { DC_QUIT; } } break; case REG_SZ: { if(FilterStringMigrate(szValueName)) { fRet = pSto->WriteString(szValueName, NULL, //no default (LPTSTR)buf, TRUE); //always write if(!fRet) { DC_QUIT; } } } break; case REG_BINARY: { // // This is where things get yucky // some settings e.g UserName are stored // as 'binary' when they are really unicode strings // No choice but to look those up. // fRet = FALSE; if(MigrateAsRealBinary(szValueName)) { fRet = pSto->WriteBinary(szValueName, (PBYTE)buf, dwBufLen); } else { // // The binary blob is really a unicode string // LPTSTR szString = W2T((LPWSTR)buf); if( szString) { // // If things weren't yucky enough... // strip out the " 50" suffix if it is // present // LPTSTR szSuffix = _tcsstr(szValueName, TEXT(" 50")); if(szSuffix) { *szSuffix = 0; } fRet = pSto->WriteString(szValueName, NULL, //no default szString, TRUE); //always write } } if(!fRet) { DC_QUIT; } } break; } //Keep enumerating dwIndex++; } else if(ERROR_NO_MORE_ITEMS == rc) { fRet = TRUE; break; } else { TRC_ERR((TB,_T("RegEnumValue failed - err:%d"), GetLastError())); fRet = FALSE; break; } } //for(;;) } else { TRC_ERR((TB,_T("Failed to open reg key - err:%d"), GetLastError())); return FALSE; } DC_EXIT_POINT: if(ERROR_SUCCESS != RegCloseKey(rootKey)) { TRC_ERR((TB,_T("RegCloseKey failed - err:%d"), GetLastError())); } DC_END_FN(); return fRet; } // // Return true if the name (szName) should be migrated as // a real binary blob. // BOOL CTscRegMigrate::MigrateAsRealBinary(LPCTSTR szName) { DC_BEGIN_FN("MigrateAsRealBinary"); // // In Tsc4 and Tsc5, only the password/salt fields // were real binary blobs, all other REG_BINARY blobs // are really just unicode strings. // if(!_tcscmp(szName, UTREG_UI_PASSWORD50)) { return TRUE; } else if(!_tcscmp(szName, UTREG_UI_PASSWORD)) { return TRUE; } else if(!_tcscmp(szName, UTREG_UI_SALT50)) { return TRUE; } #ifdef OS_WINCE else if (!_tcscmp(szName, UI_SETTING_PASSWORD51)) { return TRUE; } #endif else { // // Don't migrate as binary // return FALSE; } DC_END_FN(); } // // Return true if it is ok to migrate the value // in name // BOOL CTscRegMigrate::FilterStringMigrate(LPTSTR szName) { if(szName) { if(_tcsstr(szName, TEXT("MRU"))) { if(!_tcscmp(szName, UTREG_UI_SERVER_MRU0)) { //Translate MRU0 to fulladdress _tcscpy(szName, UTREG_UI_FULL_ADDRESS); return TRUE; } //Don't migrate any other MRU strings. //those stay in the registry return FALSE; } else { //everything else is OK return TRUE; } } else { return FALSE; } } // // Munges the settings in the settings store to be consisten // with win2k's defaults. Win2k's default values were usually // deleted on write, this means we would instead use whistler // defaults but we don't want that. Instead the behavior we // want is that a migrated connectoid has _exactly_ the same // settings it used to have for those options that had UI in win2k. // // Params - // pSto - settings store to munge for defaults // Returns - // Success flag // // #define TSC_WIN2K_DEFAULT_DESKTOPSIZE 0 //640x480 #define TSC_WIN2K_DEFAULT_FULLSCREENMODE 1 //windowed #define TSC_WIN2K_DEFAULT_BITMAPCACHE 0 //off #define TSC_WIN2K_DEFAULT_COMPRESSION 1 //on BOOL CTscRegMigrate::MungeForWin2kDefaults(ISettingsStore* pSto) { DC_BEGIN_FN("MungeForWin2kDefaults"); TRC_ASSERT(pSto, (TB,_T("pSto is null"))); TRC_ASSERT(pSto->IsOpenForRead() && pSto->IsOpenForWrite(), (TB,_T("pSto is null"))); // // Munge settings that had conman UI // to ensure that we use the user's previous settings (Even // if they are win2k defaults i.e could be value not specified). // // Resolution if(!pSto->IsValuePresent(UTREG_UI_DESKTOP_SIZEID)) { //Write out win2k's default desktop size if(!pSto->WriteInt(UTREG_UI_DESKTOP_SIZEID, -1, //default ignored TSC_WIN2K_DEFAULT_DESKTOPSIZE, TRUE)) //always write { TRC_ERR((TB,_T("WriteInt UTREG_UI_DESKTOP_SIZEID failed"))); return FALSE; } } // Screen mode if(!pSto->IsValuePresent(UTREG_UI_SCREEN_MODE)) { //Write out win2k's default screen mode if(!pSto->WriteInt(UTREG_UI_SCREEN_MODE, -1, //default ignored TSC_WIN2K_DEFAULT_FULLSCREENMODE, TRUE)) //always write { TRC_ERR((TB,_T("WriteInt UTREG_UI_SCREEN_MODE failed"))); return FALSE; } } // Bitmap caching if(!pSto->IsValuePresent(UTREG_UI_BITMAP_PERSISTENCE)) { //write out win2k's default bmp persistence option if(!pSto->WriteInt(UTREG_UI_BITMAP_PERSISTENCE, -1, //default ignored TSC_WIN2K_DEFAULT_BITMAPCACHE, TRUE)) { TRC_ERR((TB,_T("WriteInt TSC_WIN2K_DEFAULT_BITMAPCACHE failed"))); return FALSE; } } // Compression // Compression is special..We've made a decision that it should // always be on for perf reasons (and there are no drawbacks) so // change the win2k // if(!pSto->IsValuePresent(UTREG_UI_COMPRESS)) { //write out win2k's default compression option if(!pSto->WriteInt(UTREG_UI_COMPRESS, -1, //default ignored TSC_WIN2K_DEFAULT_COMPRESSION, TRUE)) { TRC_ERR((TB,_T("WriteInt TSC_WIN2K_DEFAULT_BITMAPCACHE failed"))); return FALSE; } } DC_END_FN(); return TRUE; } #ifndef OS_WINCE // // Convert the password format (if password present) // i.e if the old style TS5 passwords are present then decrypt // to plain text and then re-encrypt and save out using CryptoAPI's // // On Platforms that don't support Crypto-API we just nuke // the existing password format as we don't support migrating it // to the RDP files since it's not a secure format (just a hash). // // Start fields in pSto - 'Password 50' + 'Salt 50' // After conversion - (win2k+) 'Password 51' - binary crypto api password // After conversion - (less than win2k) = nothing // // BOOL CTscRegMigrate::ConvertPasswordFormat(ISettingsStore* pSto) { BOOL bRet = TRUE; DC_BEGIN_FN("ConvertPasswordFormat"); //Nuke TS4 format pSto->DeleteValueIfPresent( UTREG_UI_PASSWORD ); if ( CSH::IsCryptoAPIPresent() && pSto->IsValuePresent( UTREG_UI_PASSWORD50 ) && pSto->IsValuePresent( UTREG_UI_SALT50 ) ) { BOOL fHavePass = FALSE; BYTE Password[TSC_MAX_PASSWORD_LENGTH_BYTES]; BYTE Salt[TSC_SALT_LENGTH]; memset( Password, 0, TSC_MAX_PASSWORD_LENGTH_BYTES); if (pSto->ReadBinary(UTREG_UI_PASSWORD50, (PBYTE)Password, sizeof(Password))) //size in bytes { fHavePass = TRUE; } else { TRC_NRM((TB, _T("ReadBinary for password failed. Maybe password not present"))); } // // Salt // if (!pSto->ReadBinary(UTREG_UI_SALT50, (PBYTE)Salt, sizeof(Salt))) { fHavePass = FALSE; TRC_NRM((TB,_T("ReadBinary for salt failed."))); } if (fHavePass && EncryptDecryptLocalData50( Password, TSC_WIN2K_PASSWORD_LENGTH_BYTES, Salt, sizeof(Salt))) { //Now we have the clear text pass in Password //encrypt it with the crypto API and save that back //out to the store DATA_BLOB din; DATA_BLOB dout; din.cbData = sizeof(Password); din.pbData = (PBYTE)&Password; dout.pbData = NULL; if (CSH::DataProtect( &din, &dout)) { if (!pSto->WriteBinary(UI_SETTING_PASSWORD51, dout.pbData, dout.cbData)) { bRet = FALSE; } LocalFree( dout.pbData ); } else { bRet = FALSE; } // Wipe from stack SecureZeroMemory( Password, TSC_MAX_PASSWORD_LENGTH_BYTES); } } // No longer need the old format so delete them pSto->DeleteValueIfPresent( UTREG_UI_PASSWORD50 ); pSto->DeleteValueIfPresent( UTREG_UI_SALT50 ); DC_END_FN(); return bRet; } #endif BOOL CTscRegMigrate::DeleteRegValue(HKEY hKeyRoot, LPCTSTR szRootName, LPCTSTR szValueName) { HKEY hKey; LONG rc; BOOL fRet = FALSE; DC_BEGIN_FN("DeleteRegValue"); rc = RegOpenKeyEx( hKeyRoot, szRootName, 0, KEY_SET_VALUE, //needed for delete access &hKey); if(ERROR_SUCCESS == rc) { rc = RegDeleteValue(hKey, szValueName); if (ERROR_SUCCESS == rc) { fRet = TRUE; } RegCloseKey(hKey); } DC_END_FN(); return fRet; } // // Remove entries we don't want to keep lying around in the registry // primarily these are passwords in the old 'insecure' obfuscated formats // BOOL CTscRegMigrate::RemoveUnsafeRegEntries(HKEY hKeyRoot, LPCTSTR szRootName) { BOOL fRet = FALSE; DC_BEGIN_FN("RemoveUnsafeRegEntries"); if (!DeleteRegValue(hKeyRoot, szRootName, UTREG_UI_PASSWORD50)) { TRC_ALT((TB,_T("Failed to delete: %s\\%s"), szRootName, UTREG_UI_PASSWORD50)); } if (!DeleteRegValue(hKeyRoot, szRootName, UTREG_UI_PASSWORD)) { TRC_ALT((TB,_T("Failed to delete: %s\\%s"), szRootName, UTREG_UI_PASSWORD)); } if (!DeleteRegValue(hKeyRoot, szRootName, UTREG_UI_SALT50)) { TRC_ALT((TB,_T("Failed to delete: %s\\%s"), szRootName, UTREG_UI_SALT50)); } fRet = TRUE; DC_END_FN(); return fRet; }