Windows2003-3790/termsrv/newclient/clshell/rmigrate.cpp
2020-09-30 16:53:55 +02:00

789 lines
25 KiB
C++

//
// 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 <atrcapi.h>
#include "rmigrate.h"
#include "autreg.h"
#include "rdpfstore.h"
#include "sh.h"
#ifdef OS_WINCE
#include <ceconfig.h>
#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;
}