/*++ Copyright (c) 1999-2000 Microsoft Corporation Module Name: TSRDPRemoteDesktopClient Abstract: This is the TS/RDP implementation of the Remote Desktop Client class. The Remote Desktop Client class hierarchy provides a pluggable C++ interface for remote desktop access, by abstracting the implementation specific details of remote desktop access for the client-side The TSRDPRemoteDesktopClass implements remote-desktopping with the help of an instance of the MSTSC ActiveX client control. Author: Tad Brockway 02/00 Revision History: --*/ #include "stdafx.h" #ifdef TRC_FILE #undef TRC_FILE #endif #define TRC_FILE "_tsrdpc" #include "RDCHost.h" #include "TSRDPRemoteDesktopClient.h" #include #include #include #include "pchannel.h" #include #include #include #include #include "parseaddr.h" #include "icshelpapi.h" #include #include "base64.h" #define ISRCSTATUSCODE(code) ((code) > SAFERROR_SHADOWEND_BASE) // // Variable to manage WinSock and ICS library startup/shutdown // LONG CTSRDPRemoteDesktopClient::gm_ListeningLibraryRefCount = 0; // Number of time that WinSock is intialized HRESULT CTSRDPRemoteDesktopClient::InitListeningLibrary() /*++ Description: Function to initialize WinSock and ICS library for StartListen(), function add reference count to library if WinSock/ICS library already initialized. Parameters: None. Returns: S_OK or error code. --*/ { WSADATA wsaData; WORD versionRequested; INT intRC; DWORD dwStatus; HRESULT hr = S_OK; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::InitListeningLibrary"); // Our COM object is apartment-threaded model, need a critical section if // we switch to multi-threaded if( gm_ListeningLibraryRefCount == 0 ) { // // Initialize WinSock. // versionRequested = MAKEWORD(1, 1); intRC = WSAStartup(versionRequested, &wsaData); if( intRC != 0 ) { intRC = WSAGetLastError(); TRC_ERR((TB, _T("WSAStartup failed %d"), intRC)); TRC_ASSERT( (intRC == 0), (TB, _T("WSAStartup failed...\n")) ); hr = HRESULT_FROM_WIN32( intRC ); goto CLEANUPANDEXIT; } /************************************************************************/ /* Now confirm that this WinSock supports version 1.1. Note that if */ /* the DLL supports versions greater than 1.1 in addition to 1.1 then */ /* it will still return 1.1 in the version information as that is the */ /* version requested. */ /************************************************************************/ if ((LOBYTE(wsaData.wVersion) != 1) || (HIBYTE(wsaData.wVersion) != 1)) { /********************************************************************/ /* Oops - this WinSock doesn't support version 1.1. */ /********************************************************************/ TRC_ERR((TB, _T("WinSock doesn't support version 1.1"))); WSACleanup(); hr = HRESULT_FROM_WIN32( WSAVERNOTSUPPORTED ); goto CLEANUPANDEXIT; } // // Initialize ICS library. // dwStatus = StartICSLib(); if( ERROR_SUCCESS != dwStatus ) { // Shutdown WinSock so that we have a matching WSAStatup() and StartICSLib(). WSACleanup(); hr = HRESULT_FROM_WIN32( dwStatus ); TRC_ERR((TB, _T("StartICSLib() failed with %d"), dwStatus)); TRC_ASSERT( (ERROR_SUCCESS == dwStatus), (TB, _T("StartICSLib() failed...\n")) ); goto CLEANUPANDEXIT; } } InterlockedIncrement( &gm_ListeningLibraryRefCount ); CLEANUPANDEXIT: DC_END_FN(); return hr; } HRESULT CTSRDPRemoteDesktopClient::TerminateListeningLibrary() /*++ Description: Function to shutdown ICS libaray and WinSock, decrement reference count if more than one object is referencing WinSock/ICS library. Parameters: None. Returns: S_OK or error code Note: Not multi-thread safe, need CRITICAL_SECTION if we switch to multi-threaded model. --*/ { HRESULT hr = S_OK; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::TerminateListeningLibrary"); ASSERT( gm_ListeningLibraryRefCount > 0 ); if( gm_ListeningLibraryRefCount <= 0 ) { TRC_ERR((TB, _T("TerminateListeningLibrary() called before InitListeningLibrary()"))); hr = HRESULT_FROM_WIN32(WSANOTINITIALISED); goto CLEANUPANDEXIT; } if( 0 == InterlockedDecrement( &gm_ListeningLibraryRefCount ) ) { // Stop ICS libray. StopICSLib(); // Shutdown WinSock WSACleanup(); } CLEANUPANDEXIT: DC_END_FN(); return hr; } /////////////////////////////////////////////////////// // // CMSTSCClientEventSink Methods // CMSTSCClientEventSink::~CMSTSCClientEventSink() { DC_BEGIN_FN("CMSTSCClientEventSink::~CMSTSCClientEventSink"); if (m_Obj) { ASSERT(m_Obj->IsValid()); } DC_END_FN(); } // // Event Sinks // HRESULT __stdcall CMSTSCClientEventSink::OnRDPConnected() { m_Obj->OnRDPConnected(); return S_OK; } HRESULT __stdcall CMSTSCClientEventSink::OnLoginComplete() { m_Obj->OnLoginComplete(); return S_OK; } HRESULT __stdcall CMSTSCClientEventSink::OnDisconnected( long disconReason ) { m_Obj->OnDisconnected(disconReason); return S_OK; } void __stdcall CMSTSCClientEventSink::OnReceiveData( BSTR chanName, BSTR data ) { m_Obj->OnMSTSCReceiveData(data); } void __stdcall CMSTSCClientEventSink::OnReceivedTSPublicKey( BSTR publicKey, VARIANT_BOOL* pfbContinueLogon ) { m_Obj->OnReceivedTSPublicKey(publicKey, pfbContinueLogon); } /////////////////////////////////////////////////////// // // CCtlChannelEventSink Methods // CCtlChannelEventSink::~CCtlChannelEventSink() { DC_BEGIN_FN("CCtlChannelEventSink::~CCtlChannelEventSink"); if (m_Obj) { ASSERT(m_Obj->IsValid()); } DC_END_FN(); } // // Event Sinks // void __stdcall CCtlChannelEventSink::DataReady(BSTR channelName) { m_Obj->HandleControlChannelMsg(); } /////////////////////////////////////////////////////// // // CTSRDPRemoteDesktopClient Methods // HRESULT CTSRDPRemoteDesktopClient::FinalConstruct() { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::FinalConstruct"); HRESULT hr = S_OK; if (!AtlAxWinInit()) { TRC_ERR((TB, L"AtlAxWinInit failed.")); hr = E_FAIL; } DC_END_FN(); return hr; } CTSRDPRemoteDesktopClient::~CTSRDPRemoteDesktopClient() /*++ Routine Description: The Destructor Arguments: Return Value: --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::~CTSRDPRemoteDesktopClient"); if (m_ChannelMgr) { m_CtlChannelEventSink.DispEventUnadvise(m_CtlChannel); } if (m_TSClient != NULL) { m_TSClient->Release(); m_TSClient = NULL; } if( m_TimerId > 0 ) { KillTimer( m_TimerId ); } ListenConnectCleanup(); if( m_InitListeningLibrary ) { // Dereference listening library. TerminateListeningLibrary(); } DC_END_FN(); } HRESULT CTSRDPRemoteDesktopClient::Initialize( LPCREATESTRUCT pCreateStruct ) /*++ Routine Description: Final Initialization Arguments: pCreateStruct - WM_CREATE, create struct. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::Initialize"); RECT rcClient = { 0, 0, pCreateStruct->cx, pCreateStruct->cy }; HRESULT hr; IUnknown *pUnk = NULL; DWORD result; IMsRdpClientAdvancedSettings2 *advancedSettings; CComBSTR bstr; HKEY hKey = NULL; HRESULT hrIgnore; ASSERT(!m_Initialized); // // Create the client Window. // m_TSClientWnd = m_TSClientAxView.Create( m_hWnd, rcClient, MSTSCAX_TEXTGUID, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0 ); if (m_TSClientWnd == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); TRC_ERR((TB, L"Window Create: %08X", GetLastError())); goto CLEANUPANDEXIT; } // // Get IUnknown // hr = AtlAxGetControl(m_TSClientWnd, &pUnk); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"AtlAxGetControl: %08X", hr)); pUnk = NULL; goto CLEANUPANDEXIT; } // // Initialize the event sink. // m_TSClientEventSink.m_Obj = this; // // Add the event sink. // hr = m_TSClientEventSink.DispEventAdvise(pUnk); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"DispEventAdvise: %08X", hr)); goto CLEANUPANDEXIT; } // // Get the control. // hr = pUnk->QueryInterface(__uuidof(IMsRdpClient2), (void**)&m_TSClient); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"QueryInterface: %08X", hr)); goto CLEANUPANDEXIT; } // // Specify that the MSTSC input handler window should accept background // events. // hr = m_TSClient->get_AdvancedSettings3(&advancedSettings); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"IMsTscAdvancedSettings: %08X", hr)); goto CLEANUPANDEXIT; } hr = advancedSettings->put_allowBackgroundInput(1); // // Disable autoreconnect it doesn't apply to Salem // hr = advancedSettings->put_EnableAutoReconnect(VARIANT_FALSE); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"put_EnableAutoReconnect: %08X", hr)); result = E_FAIL; goto CLEANUPANDEXIT; } // // Disable advanced desktop features for the help session. // An error here is not critical, so we ignore it. // LONG flags = TS_PERF_DISABLE_WALLPAPER | TS_PERF_DISABLE_THEMING; hrIgnore = advancedSettings->put_PerformanceFlags(flags); if (!SUCCEEDED(hrIgnore)) { TRC_ERR((TB, L"put_PerformanceFlags: %08X", hrIgnore)); } // // Disable CTRL_ALT_BREAK, ignore error // hrIgnore = advancedSettings->put_HotKeyFullScreen(0); if (!SUCCEEDED(hrIgnore)) { TRC_ERR((TB, L"put_HotKeyFullScreen: %08X", hrIgnore)); } // // Don't allow mstscax to grab input focus on connect. Ignore error // on failure. // hrIgnore = advancedSettings->put_GrabFocusOnConnect(FALSE); if (!SUCCEEDED(hrIgnore)) { TRC_ERR((TB, L"put_HotKeyFullScreen: %08X", hrIgnore)); } advancedSettings->Release(); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"put_allowBackgroundInput: %08X", hr)); goto CLEANUPANDEXIT; } // // Create the "remote desktop" virtual channel with the TS Client. // bstr = TSRDPREMOTEDESKTOP_VC_CHANNEL; hr = m_TSClient->CreateVirtualChannels(bstr); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"CreateVirtualChannels: %08X", hr)); result = E_FAIL; goto CLEANUPANDEXIT; } // // Set the Shadow Persistent option // hr = m_TSClient->SetVirtualChannelOptions(bstr, CHANNEL_OPTION_REMOTE_CONTROL_PERSISTENT); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"SetVirtualChannelOptions: %08X", hr)); result = E_FAIL; goto CLEANUPANDEXIT; } //initialize timer-related stuff m_PrevTimer = GetTickCount(); // //get the time interval for pings from the registry // if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_CONTROL_SALEM, 0, KEY_READ, &hKey ) == ERROR_SUCCESS ) { DWORD dwSize = sizeof(DWORD); DWORD dwType; if((RegQueryValueEx(hKey, RDC_CONNCHECK_ENTRY, NULL, &dwType, (PBYTE) &m_RdcConnCheckTimeInterval, &dwSize ) == ERROR_SUCCESS) && dwType == REG_DWORD ) { m_RdcConnCheckTimeInterval *= 1000; //we need this in millisecs } else { // //fall back to default, if reg lookup failed // m_RdcConnCheckTimeInterval = RDC_CHECKCONN_TIMEOUT; } } CLEANUPANDEXIT: if(NULL != hKey ) RegCloseKey(hKey); // // m_TSClient keeps our reference to the client object until // the destructor is called. // if (pUnk != NULL) { pUnk->Release(); } SetValid(SUCCEEDED(hr)); DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::SendData( BSTR data ) /*++ Routine Description: IDataChannelIO Data Channel Send Method Arguments: data - Data to send. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::SendData"); CComBSTR channelName; HRESULT hr; ASSERT(IsValid()); channelName = TSRDPREMOTEDESKTOP_VC_CHANNEL; hr = m_TSClient->SendOnVirtualChannel( channelName, (BSTR)data ); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"SendOnVirtualChannel: %08X", hr)); } // //update timer // m_PrevTimer = GetTickCount(); DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::put_EnableSmartSizing( BOOL val ) /*++ Routine Description: Enable/Disable Smart Sizing Arguments: val - TRUE for enable. FALSE, otherwise. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { HRESULT hr; IMsRdpClientAdvancedSettings *pAdvSettings = NULL; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::put_EnableSmartSizing"); if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } hr = m_TSClient->get_AdvancedSettings2(&pAdvSettings); if (hr != S_OK) { TRC_ERR((TB, L"get_AdvancedSettings2: %08X", hr)); goto CLEANUPANDEXIT; } hr = pAdvSettings->put_SmartSizing(val ? VARIANT_TRUE : VARIANT_FALSE); pAdvSettings->Release(); CLEANUPANDEXIT: DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::get_EnableSmartSizing( BOOL *pVal ) /*++ Routine Description: Enable/Disable Smart Sizing Arguments: val - TRUE for enable. FALSE, otherwise. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { HRESULT hr; VARIANT_BOOL vb; IMsRdpClientAdvancedSettings *pAdvSettings = NULL; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::put_EnableSmartSizing"); if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } hr = m_TSClient->get_AdvancedSettings2(&pAdvSettings); if (hr != S_OK) { TRC_ERR((TB, L"get_AdvancedSettings2: %08X", hr)); goto CLEANUPANDEXIT; } hr = pAdvSettings->get_SmartSizing(&vb); *pVal = (vb != 0); pAdvSettings->Release(); CLEANUPANDEXIT: DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::put_ChannelMgr( ISAFRemoteDesktopChannelMgr *newVal ) /*++ Routine Description: Assign the data channel manager interface. Arguments: newVal - Data Channel Manager Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::put_ChannelMgr"); HRESULT hr = S_OK; // // We should get called one time. // ASSERT(m_ChannelMgr == NULL); m_ChannelMgr = newVal; // // Register the Remote Desktop control channel // hr = m_ChannelMgr->OpenDataChannel( REMOTEDESKTOP_RC_CONTROL_CHANNEL, &m_CtlChannel ); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } // // Register an event sink with the channel manager. // m_CtlChannelEventSink.m_Obj = this; // // Add the event sink. // hr = m_CtlChannelEventSink.DispEventAdvise(m_CtlChannel); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"DispEventAdvise: %08X", hr)); } CLEANUPANDEXIT: return hr; } HRESULT CTSRDPRemoteDesktopClient::ConnectServerWithOpenedSocket() /*++ Routine Description: Connects the client component to the server-side Remote Desktop Host COM Object with already opened socket. Arguments: None. Returns: S_OK or error code --*/ { HRESULT hr = S_OK; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::ConnectServerWithSocket"); IMsRdpClientAdvancedSettings* ptsAdvSettings = NULL; TRC_NRM((TB, L"ConnectServerWithOpenedSocket")); ASSERT( INVALID_SOCKET != m_TSConnectSocket ); // // Direct the MSTSCAX control to connect. // hr = m_TSClient->put_Server( m_ConnectedServer ); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"put_Server: %ld", hr)); goto CLEANUPANDEXIT; } hr = m_TSClient->get_AdvancedSettings2( &ptsAdvSettings ); if( SUCCEEDED(hr) && ptsAdvSettings ) { VARIANT var; VariantClear(&var); var.vt = VT_BYREF; var.byref = (PVOID)m_TSConnectSocket; hr = ptsAdvSettings->put_ConnectWithEndpoint( &var ); if( FAILED(hr) ) { TRC_ERR((TB, _T("put_ConnectWithEndpoint failed - GLE:%x"), hr)); } VariantClear(&var); ptsAdvSettings->Release(); } if( FAILED(hr) ) { goto CLEANUPANDEXIT; } // // mstscax owns this socket and will close it // m_TSConnectSocket = INVALID_SOCKET; hr = m_TSClient->Connect(); if( FAILED(hr) ) { TRC_ERR((TB, L"Connect: 0x%08x", hr)); goto CLEANUPANDEXIT; } CLEANUPANDEXIT: DC_END_FN(); return hr; } HRESULT CTSRDPRemoteDesktopClient::ConnectServerPort( BSTR bstrServer, LONG portNumber ) /*++ Routine Description: Connects the client component to the server-side Remote Desktop Host COM Object with specific port number Arguments: bstrServer : Name or IP address of server. portNumber : optional port number. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::ConnectServerPort"); HRESULT hr; IMsRdpClientAdvancedSettings* ptsAdvSettings = NULL; TRC_NRM((TB, L"ConnectServerPort %s %d", bstrServer, portNumber)); // // Direct the MSTSCAX control to connect. // hr = m_TSClient->put_Server( bstrServer ); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"put_Server: %ld", hr)); goto CLEANUPANDEXIT; } hr = m_TSClient->get_AdvancedSettings2( &ptsAdvSettings ); if( SUCCEEDED(hr) && ptsAdvSettings ) { // // Previous ConnectServerPort() might have set this port number // other than 3389 // hr = ptsAdvSettings->put_RDPPort( (0 != portNumber) ? portNumber : TERMSRV_TCPPORT ); if (FAILED(hr) ) { TRC_ERR((TB, L"put_RDPPort failed: 0x%08x", hr)); } ptsAdvSettings->Release(); } else { TRC_ERR((TB, L"get_AdvancedSettings2 failed: 0x%08x", hr)); } // // Failed the connection if we can't set the port number // if( FAILED(hr) ) { goto CLEANUPANDEXIT; } m_ConnectedServer = bstrServer; m_ConnectedPort = (0 != portNumber) ? portNumber : TERMSRV_TCPPORT; hr = m_TSClient->Connect(); if( FAILED(hr) ) { TRC_ERR((TB, L"Connect: 0x%08x", hr)); } CLEANUPANDEXIT: DC_END_FN(); return hr; } HRESULT CTSRDPRemoteDesktopClient::SetupConnectionInfo( BOOL bListenConnectInfo, BSTR bstrExpertBlob ) /*++ Routine Description: Connects the client component to the server-side Remote Desktop Host COM Object. Arguments: bstrExpertBlob : Optional parameter to be transmitted to SAF resolver. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::SetupConnectionInfo"); HRESULT hr = S_OK; DWORD result; DWORD protocolType; IMsTscNonScriptable* ptsns = NULL; IMsRdpClientAdvancedSettings* ptsAdvSettings = NULL; IMsRdpClientSecuredSettings* ptsSecuredSettings = NULL; CComBSTR bstrAssistantAccount; CComBSTR bstrAccountDomainName; CComBSTR machineAddressList; VARIANT_BOOL bNotifyTSPublicKey; // // Parse the connection parameters. // result = ParseConnectParmsString( m_ConnectParms, &m_ConnectParmVersion, &protocolType, machineAddressList, bstrAssistantAccount, m_AssistantAccountPwd, m_HelpSessionID, m_HelpSessionName, m_HelpSessionPwd, m_TSSecurityBlob ); if (result != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(result); goto CLEANUPANDEXIT; } // // If the protocol type doesn't match, then fail. // if (protocolType != REMOTEDESKTOP_TSRDP_PROTOCOL) { TRC_ERR((TB, L"Invalid connection protocol %ld", protocolType)); hr = HRESULT_FROM_WIN32(ERROR_INVALID_USER_BUFFER); goto CLEANUPANDEXIT; } if (bListenConnectInfo) { m_ServerAddressList.clear(); } else { // // Parse address list in connect parm. // result = ParseAddressList( machineAddressList, m_ServerAddressList ); if( ERROR_SUCCESS != result ) { TRC_ERR((TB, L"Invalid address list 0x%08x", result)); hr = HRESULT_FROM_WIN32(result); goto CLEANUPANDEXIT; } if( 0 == m_ServerAddressList.size() ) { TRC_ERR((TB, L"Invalid connection address list")); hr = HRESULT_FROM_WIN32(ERROR_INVALID_USER_BUFFER); goto CLEANUPANDEXIT; } } hr = m_TSClient->put_UserName(SALEMHELPASSISTANTACCOUNT_NAME); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"put_UserName: %ld", hr)); goto CLEANUPANDEXIT; } hr = m_TSClient->get_AdvancedSettings2( &ptsAdvSettings ); if( SUCCEEDED(hr) && ptsAdvSettings ) { hr = ptsAdvSettings->put_DisableRdpdr( TRUE ); if (FAILED(hr) ) { TRC_ERR((TB, L"put_DisableRdpdr failed: 0x%08x", hr)); } // older version does not have security blob so don't notify us about // TS public key. bNotifyTSPublicKey = (VARIANT_BOOL)(m_ConnectParmVersion >= SALEM_CONNECTPARM_SECURITYBLOB_VERSION); // tell activeX control to notify us TS public key hr = ptsAdvSettings->put_NotifyTSPublicKey(bNotifyTSPublicKey); if (FAILED(hr) ) { TRC_ERR((TB, L"put_NotifyTSPublicKey failed: 0x%08x", hr)); goto CLEANUPANDEXIT; } ptsAdvSettings->Release(); } else { TRC_ERR((TB, L"QueryInterface IID_IMsRdpClientAdvancedSettings: %ld", hr)); } // // Setting connection timeout, ICS might take sometime to routine // opened port to actual TS server, neither is critical error. // hr = ptsAdvSettings->put_singleConnectionTimeout( 60 * 2 ); // try two mins timeout if( FAILED(hr) ) { TRC_ERR((TB, L"put_singleConnectionTimeout : 0x%x", hr)); } hr = ptsAdvSettings->put_overallConnectionTimeout( 60 * 2 ); if( FAILED(hr) ) { TRC_ERR((TB, L"put_overallConnectionTimeout : 0x%x", hr)); } // Password encryption is based on encyption cycle key + help session ID hr = m_TSClient->get_SecuredSettings2( &ptsSecuredSettings ); if( FAILED(hr) || !ptsSecuredSettings ) { TRC_ERR((TB, L"get_IMsTscSecuredSettings : 0x%08x", hr)); goto CLEANUPANDEXIT; } // // TermSrv invoke sessmgr to check if help session is valid // before kicking off rdsaddin.exe, we need to send over // help session ID and password, only place available and big // enough is on WorkDir and StartProgram property, TermSrv will // ignore these and fill appropriate value for it // hr = ptsSecuredSettings->put_WorkDir( m_HelpSessionID ); if( FAILED(hr) ) { TRC_ERR((TB, L"put_WorkDir: 0x%08x", hr)); goto CLEANUPANDEXIT; } hr = ptsSecuredSettings->put_StartProgram( m_HelpSessionPwd ); if( FAILED(hr) ) { TRC_ERR((TB, L"put_StartProgram: 0x%08x", hr)); goto CLEANUPANDEXIT; } ptsSecuredSettings->Release(); // we only use this to disable redirection, not a critical // error, just ugly hr = m_TSClient->QueryInterface(IID_IMsTscNonScriptable, (void**)&ptsns); if(!SUCCEEDED(hr) || !ptsns){ TRC_ERR((TB, L"QueryInterface IID_IMsTscNonScriptable: %ld", hr)); goto CLEANUPANDEXIT; } // password in workdir, stuff something into password field hr = ptsns->put_ClearTextPassword( m_AssistantAccountPwd ); if (!SUCCEEDED(hr)) { TRC_ERR((TB, L"put_ClearTextPassword: 0x%08x", hr)); goto CLEANUPANDEXIT; } m_ExpertBlob = bstrExpertBlob; // // Instruct mstscax to connect with certain screen resolution, // mstscax will default to 200x20 (???), min. is VGA size. // { RECT rect; LONG cx; LONG cy; GetClientRect(&rect); cx = rect.right - rect.left; cy = rect.bottom - rect.top; if( cx < 640 || cy < 480 ) { cx = 640; cy = 480; } m_TSClient->put_DesktopWidth(cx); m_TSClient->put_DesktopHeight(cy); } CLEANUPANDEXIT: if(ptsns) { ptsns->Release(); ptsns = NULL; } DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::AcceptListenConnection( BSTR bstrExpertBlob ) /*++ Routine Description: Establish reverse connection with TS server, TS server must be connected wia reverse connection. Parameters: bstrExpertBlob : Same as ConnectToServer(). Returns: S_OK or error code. --*/ { HRESULT hr = S_OK; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::AcceptListenConnection"); // // If we are already connected or not valid, then just // return. // if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } if (m_ConnectedToServer || m_ConnectionInProgress) { TRC_ERR((TB, L"Connection active")); hr = HRESULT_FROM_WIN32(ERROR_CONNECTION_ACTIVE); goto CLEANUPANDEXIT; } if( !ListenConnectInProgress() ) { TRC_ERR((TB, L"Connection in-active")); hr = HRESULT_FROM_WIN32(ERROR_CONNECTION_INVALID); goto CLEANUPANDEXIT; } if( INVALID_SOCKET == m_TSConnectSocket ) { TRC_ERR((TB, L"Socket is not connected")); hr = HRESULT_FROM_WIN32(ERROR_CONNECTION_INVALID); goto CLEANUPANDEXIT; } hr = SetupConnectionInfo(TRUE, bstrExpertBlob); if( FAILED(hr) ) { TRC_ERR((TB, L"SetupConnectionInfo() failed with 0x%08x", hr)); goto CLEANUPANDEXIT; } hr = ConnectServerWithOpenedSocket(); CLEANUPANDEXIT: m_ConnectionInProgress = SUCCEEDED(hr); DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::ConnectToServer(BSTR bstrExpertBlob) /*++ Routine Description: Connects the client component to the server-side Remote Desktop Host COM Object. Arguments: bstrExpertBlob : Optional parameter to be transmitted to SAF resolver. Return Value: S_OK on success. Otherwise, an error code is returned. Params--*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::ConnectToServer"); HRESULT hr = S_OK; ServerAddress address; // // If we are already connected or not valid, then just // return. // if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } if (m_ConnectedToServer || m_ConnectionInProgress) { TRC_ERR((TB, L"Connection active")); hr = HRESULT_FROM_WIN32(ERROR_CONNECTION_ACTIVE); goto CLEANUPANDEXIT; } hr = SetupConnectionInfo(FALSE, bstrExpertBlob); address = m_ServerAddressList.front(); m_ServerAddressList.pop_front(); hr = ConnectServerPort(address.ServerName, address.portNumber); if (FAILED(hr)) { TRC_ERR((TB, L"ConnectServerPort: %08X", hr)); } CLEANUPANDEXIT: // // If we succeeded, remember that we are in a state of connecting. // m_ConnectionInProgress = SUCCEEDED(hr); DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::DisconnectFromServer() /*++ Routine Description: Disconnects the client from the server to which we are currently connected. Arguments: Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { return DisconnectFromServerInternal( SAFERROR_LOCALNOTERROR ); } STDMETHODIMP CTSRDPRemoteDesktopClient::DisconnectFromServerInternal( LONG errorCode ) /*++ Routine Description: Disconnects the client from the server to which we are currently connected. Arguments: reason - Reason for disconnect. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::DisconnectFromServerInternal"); HRESULT hr; // // Make sure our window is hidden. // //ShowWindow(SW_HIDE); ListenConnectCleanup(); if (m_ConnectedToServer || m_ConnectionInProgress) { hr = m_TSClient->Disconnect(); if (SUCCEEDED(hr)) { m_ConnectionInProgress = FALSE; m_ConnectedToServer = FALSE; if (m_RemoteControlRequestInProgress) { m_RemoteControlRequestInProgress = FALSE; Fire_RemoteControlRequestComplete(SAFERROR_SHADOWEND_UNKNOWN); } // // Fire the server disconnect event. // Fire_Disconnected(errorCode); } } else { TRC_NRM((TB, L"Not connected.")); hr = S_OK; } DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::ConnectRemoteDesktop() /*++ Routine Description: Once "remote desktop mode" has been enabled for the server-side Remote Desktop Host COM Object and we are connected to the server, the ConnectRemoteDesktop method can be invoked to take control of the remote user's desktop. Arguments: Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::ConnectRemoteDesktop"); HRESULT hr = S_OK; DWORD result; BSTR rcRequest = NULL; // // Fail if we are not valid or not connected to the server. // if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } if (!m_ConnectedToServer) { hr = HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_CONNECTED); goto CLEANUPANDEXIT; } // // Succeed if a remote control request is already in progress. // if (m_RemoteControlRequestInProgress) { hr = S_OK; goto CLEANUPANDEXIT; } // // Generate the remote control connect request message. // hr = GenerateRCRequest(&rcRequest); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } // // Send it. // hr = m_CtlChannel->SendChannelData(rcRequest); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } // // A request is in progress, if we successfully sent the request. // m_RemoteControlRequestInProgress = TRUE; CLEANUPANDEXIT: if (rcRequest != NULL) { SysFreeString(rcRequest); } DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::DisconnectRemoteDesktop() /*++ Routine Description: Once "remote desktop mode" has been enabled for the server-side Remote Desktop Host COM Object and we are connected to the server, the ConnectRemoteDesktop method can be invoked to take control of the remote user's desktop. Arguments: Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::DisconnectRemoteDesktop"); HRESULT hr = S_OK; CComBSTR rcRequest; // // Fail if we are not valid or not connected. // if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } if (!m_ConnectedToServer) { hr = HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_CONNECTED); goto CLEANUPANDEXIT; } // // Generate the terminate remote control key sequence and sent it to the // server. // if (m_RemoteControlRequestInProgress) { hr = SendTerminateRCKeysToServer(); } CLEANUPANDEXIT: DC_END_FN(); return hr; } // // ISAFRemoteDesktopTestExtension // STDMETHODIMP CTSRDPRemoteDesktopClient::put_TestExtDllName(/*[in]*/ BSTR newVal) { HRESULT hr = E_NOTIMPL; IMsTscAdvancedSettings *pMstscAdvSettings = NULL; IMsTscDebug *pMstscDebug = NULL; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::put_TestExtDllName" ); if ( NULL == m_TSClient ) { TRC_ERR((TB, L"m_TSClient is NULL" )); hr = E_NOINTERFACE; goto CLEANUPANDEXIT; } hr = m_TSClient->get_AdvancedSettings( &pMstscAdvSettings ); if (FAILED( hr )) { TRC_ERR((TB, L"m_TSClient->get_AdvancedSettings failed %08X", hr )); goto CLEANUPANDEXIT; } hr = m_TSClient->get_Debugger( &pMstscDebug ); if ( FAILED( hr )) { TRC_ERR((TB, L"m_TSClient->get_Debugger failed %08X", hr )); goto CLEANUPANDEXIT; } hr = pMstscAdvSettings->put_allowBackgroundInput( 1 ); if (FAILED( hr )) { TRC_ERR((TB, L"put_allowBackgroundInput failed %08X", hr )); } pMstscDebug->put_CLXDll( newVal ); CLEANUPANDEXIT: if ( NULL != pMstscAdvSettings ) pMstscAdvSettings->Release(); if ( NULL != pMstscDebug ) pMstscDebug->Release(); DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::put_TestExtParams(/*[in]*/ BSTR newVal) { HRESULT hr = E_NOTIMPL; IMsTscDebug *pMstscDebug = NULL; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::put_TestExtParams" ); if ( NULL == m_TSClient ) { TRC_ERR((TB, L"m_TSClient is NULL" )); hr = E_NOINTERFACE; goto CLEANUPANDEXIT; } hr = m_TSClient->get_Debugger( &pMstscDebug ); if (FAILED( hr )) { TRC_ERR((TB, L"m_TSClient->get_Debugger failed %08X", hr )); goto CLEANUPANDEXIT; } hr = pMstscDebug->put_CLXCmdLine( newVal ); CLEANUPANDEXIT: if ( NULL != pMstscDebug ) pMstscDebug->Release(); DC_END_FN(); return hr; } VOID CTSRDPRemoteDesktopClient::OnMSTSCReceiveData( BSTR data ) /*++ Routine Description: Handle Remote Control Control Channel messages. Arguments: Return Value: --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::OnMSTSCReceiveData"); // //we got some data, so we must be connected, update timer // m_PrevTimer = GetTickCount(); // // Fire the data ready event. // Fire_DataReady(data); DC_END_FN(); } VOID CTSRDPRemoteDesktopClient::HandleControlChannelMsg() /*++ Routine Description: Handle Remote Control Control Channel messages. Arguments: Return Value: --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::HandleControlChannelMsg"); PREMOTEDESKTOP_CTL_BUFHEADER msgHeader; BSTR msg = NULL; LONG *pResult; BSTR authenticateReq = NULL; BSTR versionInfoPacket = NULL; HRESULT hr; DWORD result; ASSERT(IsValid()); // // Read the next message. // result = m_CtlChannel->ReceiveChannelData(&msg); if (result != ERROR_SUCCESS) { goto CLEANUPANDEXIT; } // // Dispatch, based on the message type. // msgHeader = (PREMOTEDESKTOP_CTL_BUFHEADER)msg; // // If the server-side of the VC link is alive. // // if ((msgHeader->msgType == REMOTEDESKTOP_CTL_SERVER_ANNOUNCE) && m_ConnectionInProgress) { // // Send version information to the server. // hr = GenerateVersionInfoPacket( &versionInfoPacket ); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } hr = m_CtlChannel->SendChannelData(versionInfoPacket); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } // // Request client authentication. // hr = GenerateClientAuthenticateRequest( &authenticateReq ); if (!SUCCEEDED(hr)) { goto CLEANUPANDEXIT; } hr = m_CtlChannel->SendChannelData(authenticateReq); } // // If the message is from the server, indicating that it is // disconnecting. This can happen if the RDSRemoteDesktopServer // is directed to exit listening mode. // else if (msgHeader->msgType == REMOTEDESKTOP_CTL_DISCONNECT) { TRC_NRM((TB, L"Server indicated a disconnect.")); DisconnectFromServerInternal(SAFERROR_BYSERVER); } // // If the message is a message result. // else if (msgHeader->msgType == REMOTEDESKTOP_CTL_RESULT) { pResult = (LONG *)(msgHeader+1); // // If a remote control request is in progress, then we should check // for a remote control complete status. // if (m_RemoteControlRequestInProgress && ISRCSTATUSCODE(*pResult)) { TRC_ERR((TB, L"Received RC terminate status code.")); m_RemoteControlRequestInProgress = FALSE; Fire_RemoteControlRequestComplete(*pResult); } // // Otherwise, if a connection is in progress, then the client // authentication request must have succeeded. // else if (m_ConnectionInProgress) { // // Should not be getting a remote control status here. // ASSERT(!ISRCSTATUSCODE(*pResult)); // // Fire connect request succeeded message. // if (*pResult == SAFERROR_NOERROR ) { m_ConnectedToServer = TRUE; m_ConnectionInProgress = FALSE; // //set the timer to check if the user is still connected //ignore errors, worst case - the ui is up even after the user disconnects // if( m_RdcConnCheckTimeInterval ) m_TimerId = SetTimer(WM_CONNECTCHECK_TIMER, m_RdcConnCheckTimeInterval); // // Not in progress once connected // m_ListenConnectInProgress = FALSE; m_TSConnectSocket = INVALID_SOCKET; Fire_Connected(); } // // Otherwise, fire a disconnected event. // else { DisconnectFromServerInternal(*pResult); m_ConnectionInProgress = FALSE; } } } // // We will ignore other packets to support forward compatibility. // CLEANUPANDEXIT: // // Release the message. // if (msg != NULL) { SysFreeString(msg); } if (versionInfoPacket != NULL) { SysFreeString(versionInfoPacket); } if (authenticateReq != NULL) { SysFreeString(authenticateReq); } DC_END_FN(); } HRESULT CTSRDPRemoteDesktopClient::GenerateRCRequest( BSTR *rcRequest ) /*++ Routine Description: Generate a remote control request message for the server. TODO: We might need to be able to push this up to the parent class, if it makes sense for NetMeeting. Arguments: rcRequest - Returned request message. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::GenerateRCRequest"); PREMOTEDESKTOP_CTL_BUFHEADER msgHeader; PBYTE ptr; HRESULT hr; DWORD len; len = sizeof(REMOTEDESKTOP_CTL_BUFHEADER) + ((m_ConnectParms.Length()+1) * sizeof(WCHAR)); msgHeader = (PREMOTEDESKTOP_CTL_BUFHEADER)SysAllocStringByteLen(NULL, len); if (msgHeader != NULL) { msgHeader->msgType = REMOTEDESKTOP_CTL_REMOTE_CONTROL_DESKTOP; ptr = (PBYTE)(msgHeader + 1); memcpy(ptr, (BSTR)m_ConnectParms, ((m_ConnectParms.Length()+1) * sizeof(WCHAR))); *rcRequest = (BSTR)msgHeader; hr = S_OK; } else { TRC_ERR((TB, L"SysAllocStringByteLen failed for %ld bytes", len)); hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } DC_END_FN(); return hr; } HRESULT CTSRDPRemoteDesktopClient::GenerateClientAuthenticateRequest( BSTR *authenticateReq ) /*++ Routine Description: Generate a 'client authenticate' request. TODO: We might need to be able to push this up to the parent class, if it makes sense for NetMeeting. Arguments: rcRequest - Returned request message. Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::GenerateClientAuthenticateRequest"); PREMOTEDESKTOP_CTL_BUFHEADER msgHeader; PBYTE ptr; HRESULT hr; DWORD len; len = sizeof(REMOTEDESKTOP_CTL_BUFHEADER) + ((m_ConnectParms.Length()+1) * sizeof(WCHAR)); #if FEATURE_USERBLOBS if( m_ExpertBlob.Length() > 0 ) { len += ((m_ExpertBlob.Length() + 1) * sizeof(WCHAR)); } #endif msgHeader = (PREMOTEDESKTOP_CTL_BUFHEADER)SysAllocStringByteLen(NULL, len); if (msgHeader != NULL) { msgHeader->msgType = REMOTEDESKTOP_CTL_AUTHENTICATE; ptr = (PBYTE)(msgHeader + 1); memcpy(ptr, (BSTR)m_ConnectParms, ((m_ConnectParms.Length()+1) * sizeof(WCHAR))); #if FEATURE_USERBLOBS if( m_ExpertBlob.Length() > 0 ) { ptr += ((m_ConnectParms.Length()+1) * sizeof(WCHAR)); memcpy(ptr, (BSTR)m_ExpertBlob, ((m_ExpertBlob.Length()+1) * sizeof(WCHAR))); } #endif *authenticateReq = (BSTR)msgHeader; hr = S_OK; } else { TRC_ERR((TB, L"SysAllocStringByteLen failed for %ld bytes", len)); hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } DC_END_FN(); return hr; } HRESULT CTSRDPRemoteDesktopClient::GenerateVersionInfoPacket( BSTR *versionInfoPacket ) /*++ Routine Description: Generate a version information packet. Arguments: versionInfoPacket - Version Information Returned Packet Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::GenerateVersionInfoPacket"); PREMOTEDESKTOP_CTL_BUFHEADER msgHeader; PDWORD ptr; HRESULT hr; DWORD len; len = sizeof(REMOTEDESKTOP_CTL_BUFHEADER) + (sizeof(DWORD) * 2); msgHeader = (PREMOTEDESKTOP_CTL_BUFHEADER)SysAllocStringByteLen(NULL, len); if (msgHeader != NULL) { msgHeader->msgType = REMOTEDESKTOP_CTL_VERSIONINFO; ptr = (PDWORD)(msgHeader + 1); *ptr = REMOTEDESKTOP_VERSION_MAJOR; ptr++; *ptr = REMOTEDESKTOP_VERSION_MINOR; *versionInfoPacket = (BSTR)msgHeader; hr = S_OK; } else { TRC_ERR((TB, L"SysAllocStringByteLen failed for %ld bytes", len)); hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } DC_END_FN(); return hr; } VOID CTSRDPRemoteDesktopClient::OnReceivedTSPublicKey(BSTR bstrPublicKey, VARIANT_BOOL* pfContinue) { DWORD dwStatus; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::OnReceivedPublicKey"); CComBSTR bstrTSPublicKey; if( m_ConnectParmVersion >= SALEM_CONNECTPARM_SECURITYBLOB_VERSION ) { // // hash TS public key send from client activeX control, reverse // hashing from what we got in connect parm might not give us // back the original value. // dwStatus = HashSecurityData( (PBYTE) bstrPublicKey, ::SysStringByteLen(bstrPublicKey), bstrTSPublicKey ); if( ERROR_SUCCESS != dwStatus ) { TRC_ERR((TB, L"Hashed Public Key Send from TS %s", bstrPublicKey)); TRC_ERR((TB, L"Hashed public Key in parm %s", m_TSSecurityBlob)); TRC_ERR((TB, L"HashSecurityData() failed with %d", dwStatus)); *pfContinue = FALSE; } else if( !(bstrTSPublicKey == m_TSSecurityBlob) ) { TRC_ERR((TB, L"Hashed Public Key Send from TS %s", bstrPublicKey)); TRC_ERR((TB, L"Hashed public Key in parm %s", m_TSSecurityBlob)); *pfContinue = FALSE; } else { *pfContinue = TRUE; } } else { *pfContinue = TRUE; } DC_END_FN(); } VOID CTSRDPRemoteDesktopClient::OnRDPConnected() { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::OnRDPConnected"); Fire_BeginConnect(); DC_END_FN(); } VOID CTSRDPRemoteDesktopClient::OnLoginComplete() /*++ Routine Description: Arguments: Return Value: --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::OnLoginComplete"); // // Clear server address list // m_ServerAddressList.clear(); // // We got some event from the mstsc, so we must be connected, update timer // m_PrevTimer = GetTickCount(); CLEANUPANDEXIT: DC_END_FN(); } LONG CTSRDPRemoteDesktopClient::TranslateMSTSCDisconnectCode( DisconnectReasonCode disconReason, ExtendedDisconnectReasonCode extendedReasonCode ) /*++ Routine Description: Translate an MSTSC disconnect code into a Salem disconnect code. Arguments: disconReason - Disconnect Reason extendedReasonCode - MSTSCAX Extended Reason Code Return Value: Salem disconnect code. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::TranslateMSTSCDisconnectCode"); LONG ret; BOOL handled; // // First check the extended error information. // TODO: Need to keep track of additional values added by NadimA // and company, here, before we ship. // if (extendedReasonCode != exDiscReasonNoInfo) { // // Record the extended error code, if given. Note that this may be // overridden below if we have better information. // m_LastExtendedErrorInfo = extendedReasonCode; // // Check for a protocol error. // if ((extendedReasonCode >= exDiscReasonProtocolRangeStart) && (extendedReasonCode <= exDiscReasonProtocolRangeEnd)) { ret = SAFERROR_RCPROTOCOLERROR; goto CLEANUPANDEXIT; } } // // If the extended error information didn't help us. // switch(disconReason) { case disconnectReasonNoInfo : ret = SAFERROR_NOINFO; break; case 0xb08: // UI_ERR_NORMAL_DISCONNECT not defined in mstsax.idl case disconnectReasonLocalNotError : ret = SAFERROR_LOCALNOTERROR; break; case disconnectReasonRemoteByUser : ret = SAFERROR_REMOTEBYUSER; break; case disconnectReasonByServer : ret = SAFERROR_BYSERVER; break; case disconnectReasonDNSLookupFailed2 : m_LastExtendedErrorInfo = disconReason; case disconnectReasonDNSLookupFailed : ret = SAFERROR_DNSLOOKUPFAILED; break; case disconnectReasonOutOfMemory3 : case disconnectReasonOutOfMemory2 : m_LastExtendedErrorInfo = disconReason; case disconnectReasonOutOfMemory : ret = SAFERROR_OUTOFMEMORY; break; case disconnectReasonConnectionTimedOut : ret = SAFERROR_CONNECTIONTIMEDOUT; break; case disconnectReasonSocketConnectFailed : case 0x904 : ret = SAFERROR_SOCKETCONNECTFAILED; // NadimA is adding to // MSTSCAX IDL. This is a // NL_ERR_TDFDCLOSE error. break; case disconnectReasonHostNotFound : ret = SAFERROR_HOSTNOTFOUND; break; case disconnectReasonWinsockSendFailed : ret = SAFERROR_WINSOCKSENDFAILED; break; case disconnectReasonInvalidIP : m_LastExtendedErrorInfo = disconReason; case disconnectReasonInvalidIPAddr : ret = SAFERROR_INVALIDIPADDR; break; case disconnectReasonSocketRecvFailed : ret = SAFERROR_SOCKETRECVFAILED; break; case disconnectReasonInvalidEncryption : ret = SAFERROR_INVALIDENCRYPTION; break; case disconnectReasonGetHostByNameFailed : ret = SAFERROR_GETHOSTBYNAMEFAILED; break; case disconnectReasonLicensingFailed : m_LastExtendedErrorInfo = disconReason; case disconnectReasonLicensingTimeout : ret = SAFERROR_LICENSINGFAILED; break; case disconnectReasonDecryptionError : ret = SAFERROR_DECRYPTIONERROR; break; case disconnectReasonServerCertificateUnpackErr : ret = SAFERROR_MISMATCHPARMS; break; default: ret = SAFERROR_RCUNKNOWNERROR; m_LastExtendedErrorInfo = disconReason; ASSERT(FALSE); } CLEANUPANDEXIT: DC_END_FN(); return ret; } VOID CTSRDPRemoteDesktopClient::OnDisconnected( long disconReason ) /*++ Routine Description: Arguments: Return Value: --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::OnDisconnected"); HRESULT hr = E_HANDLE; // initialize an error code. long clientReturnCode; ExtendedDisconnectReasonCode extendedClientCode; TRC_ERR((TB, L"Disconnected because %ld", disconReason)); m_TSClient->get_ExtendedDisconnectReason(&extendedClientCode); clientReturnCode = TranslateMSTSCDisconnectCode( (DisconnectReasonCode)disconReason, extendedClientCode ); // Go thru all remaining server:port, mstscax might return some // error code that we don't understand. if( m_ServerAddressList.size() > 0 ) { ServerAddress address; address = m_ServerAddressList.front(); m_ServerAddressList.pop_front(); hr = ConnectServerPort( address.ServerName, address.portNumber ); if (FAILED(hr)) { TRC_ERR((TB, L"ConnectServerPort: %08X", hr)); } } // // Return the error code from connecting to 'last' server to client // if( FAILED(hr) ) { m_ServerAddressList.clear(); // // Fire the server disconnect event, if we are really connected or // we have a connection in progress. // if (m_ConnectedToServer || m_ConnectionInProgress) { Fire_Disconnected(clientReturnCode); } m_ConnectedToServer = FALSE; m_ConnectionInProgress = FALSE; ListenConnectCleanup(); // // Fire the remote control request failure event, if appropriate. // if (m_RemoteControlRequestInProgress) { ASSERT(clientReturnCode != SAFERROR_NOERROR); Fire_RemoteControlRequestComplete(SAFERROR_SHADOWEND_UNKNOWN); m_RemoteControlRequestInProgress = FALSE; } } DC_END_FN(); } HRESULT CTSRDPRemoteDesktopClient::SendTerminateRCKeysToServer() /*++ Routine Description: Send the terminate shadowing key sequence to the server. Arguments: Return Value: S_OK on success. Otherwise, an error status is returned. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::SendTerminateRCKeysToServer"); HRESULT hr = S_OK; IMsRdpClientNonScriptable* pTscNonScript = NULL; VARIANT_BOOL keyUp[] = { VARIANT_FALSE, VARIANT_FALSE, VARIANT_TRUE, VARIANT_TRUE }; LONG keyData[] = { MapVirtualKey(VK_CONTROL, 0), // these are SCANCODES. MapVirtualKey(VK_MULTIPLY, 0), MapVirtualKey(VK_MULTIPLY, 0), MapVirtualKey(VK_CONTROL, 0), }; // // Send the terminate keys to the server. // hr = m_TSClient->QueryInterface( IID_IMsRdpClientNonScriptable, (void**)&pTscNonScript ); if (hr != S_OK) { TRC_ERR((TB, L"QI: %08X", hr)); goto CLEANUPANDEXIT; } pTscNonScript->NotifyRedirectDeviceChange(0, 0); pTscNonScript->SendKeys(4, keyUp, keyData); if (hr != S_OK) { TRC_ERR((TB, L"SendKeys, QI: %08X", hr)); } pTscNonScript->Release(); CLEANUPANDEXIT: DC_END_FN(); return hr; } HWND CTSRDPRemoteDesktopClient::SearchForWindow( HWND hwndParent, LPTSTR srchCaption, LPTSTR srchClass ) /*++ Routine Description: Search for a child window of the specified parent window. Arguments: srchCaption - Window caption for which to search. NULL is considered a wildcard. srchClass - Window class for which to search. NULL is considred a wildcard. Return Value: S_OK on success. Otherwise, an error status is returned. --*/ { WINSEARCH srch; srch.foundWindow = NULL; srch.srchCaption = srchCaption; srch.srchClass = srchClass; BOOL result = EnumChildWindows( hwndParent, (WNDENUMPROC)_WindowSrchProc, (LPARAM)&srch ); return srch.foundWindow; } BOOL CALLBACK CTSRDPRemoteDesktopClient::_WindowSrchProc(HWND hwnd, PWINSEARCH srch) { TCHAR classname[128]; TCHAR caption[128]; if (srch->srchClass && !GetClassName(hwnd, classname, sizeof(classname) / sizeof(TCHAR))) { return TRUE; } if (srch->srchCaption && !::GetWindowText(hwnd, caption, sizeof(caption)/sizeof(TCHAR))) { return TRUE; } if ((!srch->srchClass || !_tcscmp(classname, srch->srchClass) && (!srch->srchCaption || !_tcscmp(caption, srch->srchCaption))) ) { srch->foundWindow = hwnd; return FALSE; } return TRUE; } HRESULT CTSRDPRemoteDesktopClient::GenerateNullData( BSTR* pbstrData ) { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::GenerateNullData"); HRESULT hr; DWORD len; PREMOTEDESKTOP_CTL_BUFHEADER msgHeader = NULL; len = sizeof( REMOTEDESKTOP_CTL_BUFHEADER ); msgHeader = (PREMOTEDESKTOP_CTL_BUFHEADER)SysAllocStringByteLen(NULL, len); if (msgHeader != NULL) { msgHeader->msgType = REMOTEDESKTOP_CTL_ISCONNECTED; //nothing else other than the message *pbstrData = (BSTR)msgHeader; hr = S_OK; } else { TRC_ERR((TB, L"SysAllocStringByteLen failed for %ld bytes", len)); hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } DC_END_FN(); return hr; } LRESULT CTSRDPRemoteDesktopClient::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::OnCheckConnectTimer"); BSTR bstrMsg = NULL; if( WM_LISTENTIMEOUT_TIMER == wParam ) { bHandled = TRUE; if( TRUE == ListenConnectInProgress() ) { StopListen(); Fire_ListenConnect( SAFERROR_CONNECTIONTIMEDOUT ); } } else if ( WM_CONNECTCHECK_TIMER == wParam ) { DWORD dwCurTimer = GetTickCount(); bHandled = TRUE; if(m_ConnectedToServer) { // //see if the timer wrapped around to zero (does so if the system was up 49.7 days or something) //if so reset it // if( dwCurTimer > m_PrevTimer && ( dwCurTimer - m_PrevTimer >= m_RdcConnCheckTimeInterval )) { // //time to send a null data // if(SUCCEEDED(GenerateNullData(&bstrMsg))) { if(!SUCCEEDED(m_CtlChannel->SendChannelData(bstrMsg))) { // //could not send data, assume disconnected // DisconnectFromServer(); // //don't need the timer anymore, kill it // KillTimer( m_TimerId ); m_TimerId = 0; } } } } //m_ConnectedToServer // //update the timer // m_PrevTimer = dwCurTimer; if( NULL != bstrMsg ) { SysFreeString(bstrMsg); } } DC_END_FN(); return 0; } STDMETHODIMP CTSRDPRemoteDesktopClient::CreateListenEndpoint( IN LONG port, OUT BSTR* pConnectParm ) /*++ Description: Routine to create a listening socket and return connection parameter to this 'listen' socket. Parameters: port : Port that socket should listen on. pConnectParm : Return connection parameter to this listening socket. returns: S_OK or error code. Notes: Function is async, return code, if error, is for listening thread set up, caller is notified of successful or error in network connection via ListenConnect event. --*/ { HRESULT hr = S_OK; SOCKET hListenSocket = INVALID_SOCKET; IMsRdpClientAdvancedSettings* pAdvSettings; LONG rdpPort = 0; int intRC; int lastError; SOCKADDR_IN sockAddr; int sockAddrSize; int optvalue; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::CreateListenEndpoint"); if( NULL == pConnectParm ) { hr = E_POINTER; return hr; } if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; return hr; } // // Return error if we are in progress of connect or connected. // if( TRUE == ListenConnectInProgress() || // Listen already started. TRUE == m_ConnectionInProgress || // Connection already in progress TRUE == m_ConnectedToServer ) { // Already connected to server hr = HRESULT_FROM_WIN32( ERROR_SHARING_VIOLATION ); TRC_ERR((TB, L"StartListen() already in listen")); goto CLEANUPANDEXIT; } // // Initialize Winsock and ICS library if not yet initialized. // InitListeningLibrary() will only add ref. count // if library already initialized by other instance. // if( FALSE == m_InitListeningLibrary ) { hr = InitListeningLibrary(); if( FAILED(hr) ) { TRC_ERR((TB, L"InitListeningLibrary() failed : %08X", hr)); goto CLEANUPANDEXIT; } m_InitListeningLibrary = TRUE; } // // mstscax will close the socket once connection is ended. // m_TSConnectSocket = INVALID_SOCKET; // // Create a listening socket // m_ListenSocket = socket(AF_INET, SOCK_STREAM, 0); if( INVALID_SOCKET == m_ListenSocket ) { intRC = WSAGetLastError(); TRC_ERR((TB, _T("socket failed %d"), intRC)); hr = HRESULT_FROM_WIN32(intRC); goto CLEANUPANDEXIT; } // // Disable NAGLE algorithm and enable don't linger option. // optvalue = 1; setsockopt( m_ListenSocket, IPPROTO_TCP, TCP_NODELAY, (char *)&optvalue, sizeof(optvalue) ); optvalue = 1; setsockopt( m_ListenSocket, SOL_SOCKET, SO_DONTLINGER, (char *)&optvalue, sizeof(optvalue) ); // // Request async notifications to send to our window // intRC = WSAAsyncSelect( m_ListenSocket, m_hWnd, WM_TSCONNECT, FD_ACCEPT ); if(SOCKET_ERROR == intRC) { intRC = WSAGetLastError(); TRC_ERR((TB, _T("WSAAsyncSelect failed %d"), intRC)); hr = HRESULT_FROM_WIN32(intRC); goto CLEANUPANDEXIT; } sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(port); sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);; intRC = bind( m_ListenSocket, (struct sockaddr *) &sockAddr, sizeof(sockAddr) ); if( SOCKET_ERROR == intRC ) { lastError = WSAGetLastError(); TRC_ERR((TB, _T("bind failed - %d"), lastError)); hr = HRESULT_FROM_WIN32( lastError ); goto CLEANUPANDEXIT; } if( 0 == port ) { // // Retrieve which port we are listening // sockAddrSize = sizeof( sockAddr ); intRC = getsockname( m_ListenSocket, (struct sockaddr *)&sockAddr, &sockAddrSize ); if( SOCKET_ERROR == intRC ) { lastError = WSAGetLastError(); TRC_ERR((TB, _T("getsockname failed - GLE:%d"),lastError)); hr = HRESULT_FROM_WIN32( lastError ); goto CLEANUPANDEXIT; } m_ConnectedPort = ntohs(sockAddr.sin_port); } else { m_ConnectedPort = port; } TRC_ERR((TB, _T("Listenin on port %d"),m_ConnectedPort)); // // Tell ICS library to punch a hole thru ICS, no-op // if not ICS configuration. // m_ICSPort = OpenPort( m_ConnectedPort ); // // Retrieve connection parameters for this client (expert). // hr = RetrieveUserConnectParm( pConnectParm ); if( FAILED(hr) ) { TRC_ERR((TB, _T("RetrieveUserConnectParm failed - 0x%08x"),hr)); } CLEANUPANDEXIT: if( FAILED(hr) ) { StopListen(); } DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::StopListen() /*++ Description: Stop listening waiting for TS server (helpee, user) to connect. Parameters: None. Returns: S_OK or error code. --*/ { HRESULT hr = S_OK; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::StopListen"); if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; return hr; } // End listening, either we are actually issued a listen() to socket // or we just created the listen socket but not yet start listening if( TRUE == ListenConnectInProgress() || INVALID_SOCKET != m_ListenSocket ) { ListenConnectCleanup(); Fire_ListenConnect( SAFERROR_STOPLISTENBYUSER ); } else { TRC_ERR((TB, _T("StopListen called while not in listen mode"))); hr = HRESULT_FROM_WIN32( WSANOTINITIALISED ); } DC_END_FN(); return hr; } LRESULT CTSRDPRemoteDesktopClient::OnTSConnect( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) /*++ Routine Description: Window Message Handler FD_ACCEPT from async. winsock. Parameters: Refer to async. winsock FD_ACCEPT. Returns: --*/ { WORD eventWSA; WORD errorWSA; HRESULT hr = S_OK; SOCKADDR_IN inSockAddr; int inSockAddrSize; SOCKET s; DWORD dwStatus; DWORD SafErrorCode = SAFERROR_NOERROR; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::OnTSConnect"); eventWSA = WSAGETSELECTEVENT(lParam); errorWSA = WSAGETSELECTERROR(lParam); // // MSDN : Message might already in our queue before we stop listen. // if( INVALID_SOCKET == m_ListenSocket || FALSE == ListenConnectInProgress() ) { bHandled = TRUE; return 0; } // // we are not expecting event other than FD_CONNECT // if( eventWSA != FD_ACCEPT ) { TRC_ERR((TB, _T("Expecting event %d got got %d"), FD_CONNECT, eventWSA)); return 0; } // // Make sure we don't do anything other than our own socket // if( (SOCKET)wParam != m_ListenSocket ) { TRC_ERR((TB, _T("Expecting listening socket %d got %d"), m_ListenSocket, wParam)); return 0; } // // We handle the message // bHandled = TRUE; // // Error occurred, fire a error event. // if( 0 != errorWSA ) { TRC_ERR((TB, _T("WSA socket listen failed : %d"), errorWSA)); hr = HRESULT_FROM_WIN32( errorWSA ); SafErrorCode = SAFERROR_SOCKETCONNECTFAILED; goto CLEANUPANDEXIT; } inSockAddrSize = sizeof(inSockAddr); m_TSConnectSocket = accept( m_ListenSocket, (struct sockaddr DCPTR)&inSockAddr, &inSockAddrSize ); if( INVALID_SOCKET == m_TSConnectSocket ) { dwStatus = WSAGetLastError(); hr = HRESULT_FROM_WIN32(dwStatus); TRC_ERR((TB, _T("accept failed : %d"), dwStatus)); SafErrorCode = SAFERROR_SOCKETCONNECTFAILED; goto CLEANUPANDEXIT; } // // Cached connecting TS server IP address. // m_ConnectPort is set at the time we bind socket // m_ConnectedServer = inet_ntoa(inSockAddr.sin_addr); // // Stop async. event notification now, accepted socket // has same properties as original listening socket. // dwStatus = WSAAsyncSelect( m_TSConnectSocket, m_hWnd, 0, 0 ); // // Not critical, // listening socket. // if((DWORD)SOCKET_ERROR == dwStatus) { TRC_ERR((TB, _T("WSAAsyncSelect resetting notification failed : %d"), dwStatus)); } CLEANUPANDEXIT: // // Close listening socket and kill timer. // if( (UINT_PTR)0 != m_ListenTimeoutTimerID ) { KillTimer( m_ListenTimeoutTimerID ); m_ListenTimeoutTimerID = (UINT_PTR)0; } if( INVALID_SOCKET != m_ListenSocket ) { closesocket( m_ListenSocket ); m_ListenSocket = INVALID_SOCKET; } // // Successfully established connection, terminate listening socket // Fire_ListenConnect( SafErrorCode ); DC_END_FN(); return 0; } STDMETHODIMP CTSRDPRemoteDesktopClient::StartListen( /*[in]*/ LONG timeout ) /*++ Routine Description: Put client into listen mode with optionally timeout. Parameters: timeout : Listen wait timeout, 0 for infinite. Returns: S_OK or error code. --*/ { DC_BEGIN_FN("CTSRDPRemoteDesktopClient::OnTSConnect"); HRESULT hr = S_OK; int intRC; int lastError; if( FALSE == IsValid() ) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } if( INVALID_SOCKET == m_ListenSocket ) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } // // Start listening on the port // intRC = listen( m_ListenSocket, SOMAXCONN ); if( SOCKET_ERROR == intRC ) { lastError = WSAGetLastError(); TRC_ERR((TB, _T("listen failed - GLE:%d"), lastError)); hr = HRESULT_FROM_WIN32( lastError ); goto CLEANUPANDEXIT; } // // we are in listening now. // m_ListenConnectInProgress = TRUE; // // Start listening timer // if( 0 != timeout ) { m_ListenTimeoutTimerID = SetTimer( (UINT_PTR)WM_LISTENTIMEOUT_TIMER, (UINT)(timeout * 1000) ); if( (UINT_PTR)0 == m_ListenTimeoutTimerID ) { DWORD dwStatus; // Failed to create a timer dwStatus = GetLastError(); TRC_ERR((TB, _T("SetTimer failed - %d"),dwStatus)); hr = HRESULT_FROM_WIN32( dwStatus ); } } else { m_ListenTimeoutTimerID = (UINT_PTR)0; } CLEANUPANDEXIT: if( FAILED(hr) ) { StopListen(); } DC_END_FN(); return hr; } HRESULT CTSRDPRemoteDesktopClient::RetrieveUserConnectParm( BSTR* pConnectParm ) /*++ Routine Description: Retrieve Salem connection parameter to this expert. Parameters: pConnectParm : Pointer to BSTR to receive connect parm. Returns: S_OK or error code. --*/ { LPTSTR pszAddress = NULL; int BufSize = 0; CComBSTR bstrConnParm; DWORD dwRetry; HRESULT hRes; DWORD dwBufferRequire; DWORD dwNumChars; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::RetrieveUserConnectParm"); if( NULL == pConnectParm ) { hRes = E_POINTER; goto CLEANUPANDEXIT; } // // Address might have change which might require bigger buffer, retry // // for(dwRetry=0; dwRetry < MAX_FETCHIPADDRESSRETRY; dwRetry++) { if( NULL != pszAddress ) { LocalFree( pszAddress ); } // // Fetch all address on local machine. // dwBufferRequire = FetchAllAddresses( NULL, 0 ); if( 0 == dwBufferRequire ) { hRes = E_UNEXPECTED; ASSERT(FALSE); goto CLEANUPANDEXIT; } pszAddress = (LPTSTR) LocalAlloc( LPTR, sizeof(TCHAR)*(dwBufferRequire+1) ); if( NULL == pszAddress ) { hRes = E_OUTOFMEMORY; goto CLEANUPANDEXIT; } dwNumChars = FetchAllAddresses( pszAddress, dwBufferRequire ); ASSERT( dwNumChars <= dwBufferRequire ); if( dwNumChars <= dwBufferRequire ) { break; } } if( NULL == pszAddress ) { hRes = E_UNEXPECTED; goto CLEANUPANDEXIT; } bstrConnParm = pszAddress; *pConnectParm = bstrConnParm.Copy(); if( NULL == *pConnectParm ) { hRes = E_OUTOFMEMORY; } CLEANUPANDEXIT: if( NULL != pszAddress ) { LocalFree(pszAddress); } DC_END_FN(); return hRes; } STDMETHODIMP CTSRDPRemoteDesktopClient::put_ColorDepth( LONG val ) /*++ Routine Description: Set Color depth Arguments: val - Value in bits perpel to set Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { HRESULT hr; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::put_ColorDepth"); if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } hr = m_TSClient->put_ColorDepth(val); if (hr != S_OK) { TRC_ERR((TB, L"put_ColorDepth: %08X", hr)); goto CLEANUPANDEXIT; } CLEANUPANDEXIT: DC_END_FN(); return hr; } STDMETHODIMP CTSRDPRemoteDesktopClient::get_ColorDepth( LONG *pVal ) /*++ Routine Description: Get Color depth Arguments: pVal - address to place the colordepth value in Return Value: S_OK on success. Otherwise, an error code is returned. --*/ { HRESULT hr; IMsRdpClientAdvancedSettings *pAdvSettings = NULL; DC_BEGIN_FN("CTSRDPRemoteDesktopClient::get_ColorDepth"); if (!IsValid()) { ASSERT(FALSE); hr = E_FAIL; goto CLEANUPANDEXIT; } hr = m_TSClient->get_ColorDepth(pVal); if (hr != S_OK) { TRC_ERR((TB, L"get_ColorDepth: %08X", hr)); goto CLEANUPANDEXIT; } CLEANUPANDEXIT: DC_END_FN(); return hr; }