Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2476 lines
66 KiB

/*++
Copyright (c) 1999-2000 Microsoft Corporation
Module Name:
rdsaddin.cpp
Abstract:
The TSRDP Assistant Session VC Add-In is an executable that is
loaded in the session that is created when the TSRDP client plug-in
first logs in to the server machine. It acts, primarily, as a
proxy between the client VC interface and the Remote Desktop Host
COM Object. Channel data is routed from the TSRDP Assistant Session
VC Add-In to the Remote Desktop Host COM Object using a named pipe
that is created by the Remote Desktop Host COM Object when it enters
"listen" mode.
In addition to its duties as a proxy, the Add-In also manages a control
channel between the client and the server. This control channel is
used by the client-side to direct the server side to initiate remote
control of the end user's TS session.
TODO: We should make the pipe IO synchronous since we now have two
IO threads.
Author:
Tad Brockway 02/00
Revision History:
--*/
#ifdef TRC_FILE
#undef TRC_FILE
#endif
#define TRC_FILE "_sesa"
#include <windows.h>
#include <process.h>
#include <RemoteDesktop.h>
#include <RemoteDesktopDBG.h>
#include <RemoteDesktopChannels.h>
#include <TSRDPRemoteDesktop.h>
#include <wtblobj.h>
#include <wtsapi32.h>
#include <sessmgr.h>
#include <winsta.h>
#include <atlbase.h>
#include <RemoteDesktopUtils.h>
#include <sessmgr_i.c>
#include <pchannel.h>
#include <RDCHost.h>
#include <regapi.h>
///////////////////////////////////////////////////////
//
// Defines
//
#define CLIENTPIPE_CONNECTTIMEOUT (20 * 1000) // 20 seconds.
#define VCBUFFER_RESIZE_DELTA CHANNEL_CHUNK_LENGTH
#define RDS_CHECKCONN_TIMEOUT (30 * 1000) //millisec. default value to ping is 30 seconds
#define RDC_CONNCHECK_ENTRY L"ConnectionCheck"
#define THREADSHUTDOWN_WAITTIMEOUT 30 * 1000
///////////////////////////////////////////////////////
//
// Typedefs
//
typedef struct _IOBuffer {
PREMOTEDESKTOP_CHANNELBUFHEADER buffer;
DWORD bufSize;
DWORD offset;
} IOBUFFER;
///////////////////////////////////////////////////////
//
// Internal Prototypes
//
DWORD ReturnResultToClient(
LONG result
);
VOID RemoteControlDesktop(
BSTR parms
);
BOOL ClientVersionCompatible(
DWORD dwMajor,
DWORD dwMinor
);
VOID ClientAuthenticate(
BSTR parms,
BSTR blob
);
DWORD ProcessControlChannelRequest(
IOBUFFER &msg
);
DWORD SendMsgToClient(
PREMOTEDESKTOP_CHANNELBUFHEADER msg
);
VOID HandleVCReadComplete(
HANDLE waitableObject,
PVOID clientData
);
DWORD HandleReceivedVCMsg(
IOBUFFER &msg
);
VOID HandleVCClientConnect(
HANDLE waitableObject,
PVOID clientData
);
VOID HandleVCClientDisconnect(
HANDLE waitableObject,
PVOID clientData
);
VOID HandleNamedPipeReadComplete(
OVERLAPPED &incomingPipeOL,
IOBUFFER &incomingPipeBuf
);
VOID HandleReceivedPipeMsg(
IOBUFFER &msg
);
DWORD ConnectVC();
DWORD ConnectClientSessionPipe();
DWORD IssueVCOverlappedRead(
IOBUFFER &msg,
OVERLAPPED &ol
);
DWORD IssueNamedPipeOverlappedRead(
IOBUFFER &msg,
OVERLAPPED &ol
);
unsigned __stdcall
NamedPipeReadThread(
void* ptr
);
VOID WakeUpFunc(
HANDLE waitableObject,
PVOID clientData
);
VOID HandleHelpCenterExit(
HANDLE waitableObject,
PVOID clientData
);
DWORD
SendNullDataToClient(
);
BOOL GetDwordFromRegistry(PDWORD pdwValue);
///////////////////////////////////////////////////////
//
// Globals to this Module
//
CComBSTR g_bstrCmdLineHelpSessionId;
WTBLOBJMGR g_WaitObjMgr = NULL;
BOOL g_Shutdown = FALSE;
HANDLE g_VCHandle = NULL;
HANDLE g_ProcHandle = NULL;
DWORD g_SessionID = 0;
HANDLE g_ProcToken = NULL;
HANDLE g_WakeUpForegroundThreadEvent = NULL;
DWORD g_PrevTimer = 0;
DWORD g_dwTimeOutInterval = 0;
HANDLE g_ShutdownEvent = NULL;
HANDLE g_RemoteControlDesktopThread = NULL;
HANDLE g_NamedPipeReadThread = NULL;
HANDLE g_NamedPipeWriteEvent = NULL;
//
// VC Globals
//
HANDLE g_ClientIsconnectEvent = NULL;
HANDLE g_VCFileHandle = NULL;
OVERLAPPED g_VCReadOverlapped = { 0, 0, 0, 0, NULL };
BOOL g_ClientConnected = FALSE;
//
// Client Session Information
//
LONG g_ClientSessionID = -1;
HANDLE g_ClientSessionPipe = NULL;
//
// True if the client has been successfully authenticated.
//
BOOL g_ClientAuthenticated = FALSE;
//
// Incoming Virtual Channel Buf.
//
IOBUFFER g_IncomingVCBuf = { NULL, 0, 0 };
//
// Global help session manager object, this need to be
// global so that when process exit, object destructor
// can inform resolver about the disconnect
//
CComPtr<IRemoteDesktopHelpSessionMgr> g_HelpSessionManager;
//
// Help Session Identifier for the Current Client Connection
//
CComBSTR g_HelpSessionID;
//
// Client (expert side) rdchost major version
//
DWORD g_ClientMajor;
DWORD g_ClientMinor;
//
// Handle to Help Center : B2 blocker workaround for BUG:342742
//
HANDLE g_hHelpCenterProcess = NULL;
CRITICAL_SECTION g_cs;
//------------------------------------------------------------------
BOOL WINAPI
ControlHandler(
IN DWORD dwCtrlType
)
/*++
Abstract:
Parameter:
IN dwCtrlType : control type
Return:
++*/
{
switch( dwCtrlType )
{
case CTRL_BREAK_EVENT: // use Ctrl+C or Ctrl+Break to simulate
case CTRL_C_EVENT: // SERVICE_CONTROL_STOP in debug mode
case CTRL_CLOSE_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT:
SetEvent( g_ShutdownEvent );
g_Shutdown = TRUE;
return TRUE;
}
return FALSE;
}
DWORD
IsZeroterminateString(
LPTSTR pszString,
int length
)
/*++
Routine Description;
Check is string is NULL terminated, code modified from TermSrv's IsZeroterminateStringW()
Parameters:
pszString : Pointer to string.
dwLength : Length of string.
Returns:
ERROR_SUCCESS or ERROR_INVALID_PARAMETER
--*/
{
if (pszString == NULL || length <= 0) {
return ERROR_INVALID_PARAMETER;
}
for (; 0 < length; ++pszString, --length ) {
if (*pszString == (TCHAR)0) {
return ERROR_SUCCESS;
}
}
return ERROR_INVALID_PARAMETER;
}
DWORD
ReturnResultToClient(
LONG clientResult
)
/*++
Routine Description:
Return a result code to the client in the form of a
REMOTEDESKTOP_RC_CONTROL_CHANNEL channel REMOTEDESKTOP_CTL_RESULT message.
Arguments:
Return Value:
ERROR_SUCCESS on success. Otherwise, an error code is returned.
--*/
{
DC_BEGIN_FN("ReturnResultToClient");
DWORD result;
REMOTEDESKTOP_CTL_RESULT_PACKET msg;
memcpy(msg.packetHeader.channelName, REMOTEDESKTOP_RC_CONTROL_CHANNEL,
sizeof(REMOTEDESKTOP_RC_CONTROL_CHANNEL));
msg.packetHeader.channelBufHeader.channelNameLen = REMOTEDESKTOP_RC_CHANNELNAME_LENGTH;
#ifdef USE_MAGICNO
msg.packetHeader.channelBufHeader.magicNo = CHANNELBUF_MAGICNO;
#endif
msg.packetHeader.channelBufHeader.dataLen = sizeof(REMOTEDESKTOP_CTL_RESULT_PACKET) -
sizeof(REMOTEDESKTOP_CTL_PACKETHEADER);
msg.msgHeader.msgType = REMOTEDESKTOP_CTL_RESULT;
msg.result = clientResult;
result = SendMsgToClient((PREMOTEDESKTOP_CHANNELBUFHEADER )&msg);
DC_END_FN();
return result;
}
unsigned __stdcall
RemoteControlDesktopThread(
void* ptr
)
/*++
Routine Description:
Thread func for Remote Control
Arguments:
Return Value:
This function returns a status back to the Salem client when shadow
terminates. It is only allowed to return error codes that are prefixed by
SAFERROR_SHADOWEND
--*/
{
BSTR parms = (BSTR) ptr;
DC_BEGIN_FN("RemoteControlDesktopThread");
CComPtr<IRemoteDesktopHelpSessionMgr> helpSessionManager;
CComPtr<IRemoteDesktopHelpSession> helpSession;
HRESULT hr;
DWORD result;
LONG errReturnCode = SAFERROR_SHADOWEND_UNKNOWN;
//
// If we have not resolve the right user session ID
//
if( g_ClientSessionID == -1 ) {
TRC_ALT((TB, L"Invalid user session ID %ld",
g_ClientSessionID));
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PASSWORD);
errReturnCode = SAFERROR_SHADOWEND_UNKNOWN;
ASSERT(FALSE);
goto CLEANUPANDEXIT;
}
CoInitialize(NULL);
//
// Create a new instance of helpmgr object to get around threading issue
// in COM
//
hr = helpSessionManager.CoCreateInstance(CLSID_RemoteDesktopHelpSessionMgr, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_DISABLE_AAA);
if (!SUCCEEDED(hr)) {
TRC_ERR((TB, TEXT("Can't create help session manager: %08X"), hr));
// Setup issue
errReturnCode = SAFERROR_SHADOWEND_UNKNOWN;
ASSERT(FALSE);
goto CLEANUPANDEXIT;
}
//
// Set the security level to impersonate. This is required by
// the session manager.
//
hr = CoSetProxyBlanket(
(IUnknown *)helpSessionManager,
RPC_C_AUTHN_DEFAULT,
RPC_C_AUTHZ_DEFAULT,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IDENTIFY,
NULL,
EOAC_NONE
);
if (!SUCCEEDED(hr)) {
TRC_ERR((TB, TEXT("CoSetProxyBlanket: %08X"), hr));
ASSERT(FALSE);
errReturnCode = SAFERROR_SHADOWEND_UNKNOWN;
goto CLEANUPANDEXIT;
}
//
// Retrieve help session object for the incident
//
hr = helpSessionManager->RetrieveHelpSession(
g_HelpSessionID,
&helpSession
);
if (!SUCCEEDED(hr)) {
TRC_ERR((TB, L"RetrieveHelpSession: %08X", hr));
errReturnCode = SAFERROR_SHADOWEND_UNKNOWN;
goto CLEANUPANDEXIT;
}
//
// Set shadow configuration to help session RDS setting
// Console shadow always reset shadow class back to
// original value.
//
hr = helpSession->EnableUserSessionRdsSetting(TRUE);
if( FAILED(hr) ) {
TRC_ERR((TB, L"Can't set shadow setting on %ld : %08X.", hr));
errReturnCode = SAFERROR_SHADOWEND_UNKNOWN;
goto CLEANUPANDEXIT;
}
//
// Shadow the desktop.
//
if (!WinStationShadow(
SERVERNAME_CURRENT,
NULL, //machineName,
g_ClientSessionID,
TSRDPREMOTEDESKTOP_SHADOWVKEY,
TSRDPREMOTEDESKTOP_SHADOWVKEYMODIFIER
)) {
result = GetLastError();
hr = HRESULT_FROM_WIN32(result);
//
// Map the error code to a SAF error code.
//
if( result == ERROR_CTX_SHADOW_ENDED_BY_MODE_CHANGE ) {
errReturnCode = SAFERROR_SHADOWEND_CONFIGCHANGE;
}
else {
errReturnCode = SAFERROR_SHADOWEND_UNKNOWN;
}
}
//
// No need to reset g_ClientSessionID, we don't support multiple instance.
//
//
// Inform help session object that shadow has completed, NotifyRemoteControl()
// internally invoke EnableUserSessionRdsSetting(TRUE) to change
// TS shadow class
// No need to reset g_ClientSessionID, we don't support multiple instance.
//
//
// Inform help session object that shadow has completed
//
hr = helpSession->EnableUserSessionRdsSetting( FALSE );
if (FAILED(hr)) {
TRC_ERR((TB, L"Can't reset shadow setting on %ld : %08X.",
g_ClientSessionID, hr));
//
// not a critical error.
//
}
CLEANUPANDEXIT:
//
// Send the result to the client on failure to shadow.
//
ReturnResultToClient(errReturnCode);
CoUninitialize();
DC_END_FN();
_endthreadex(errReturnCode);
return errReturnCode;
}
VOID
RemoteControlDesktop(
BSTR parms
)
/*++
Routine Description:
Arguments:
Connection Parameters
Return Value:
--*/
{
unsigned dump;
DC_BEGIN_FN("RemoteControlDesktop");
//
// RDCHOST.DLL will not send any control message so there is no checking on
// second remote control command.
//
g_RemoteControlDesktopThread = (HANDLE)_beginthreadex( NULL, 0, RemoteControlDesktopThread, (void *)parms, 0, &dump );
if ((uintptr_t)g_RemoteControlDesktopThread == -1 ) {
g_RemoteControlDesktopThread = NULL;
TRC_ERR((TB, L"Failed to create RemoteControlDesktopThread for session %d - %ld",
g_ClientSessionID, GetLastError()));
// return error code only when
// failed to spawn another thread
ReturnResultToClient(SAFERROR_SHADOWEND_UNKNOWN);
}
DC_END_FN();
}
BOOL
ClientVersionCompatible(
DWORD dwMajor,
DWORD dwMinor
)
/*++
Routine Description:
Verify client (expert) version is compatible with our version.
Parameters:
dwMajor : Client major version.
dwMinor : Client minor version.
Returns:
None.
--*/
{
//
// Build 2409 or earlier (including B1 release has major version of 1 and minor version of 1
// rdchost/rdsaddin need to deal with versioning, for build 2409 or earlier, we
// just make it in-compatible since we need some expert identity from rdchost.dll
//
#if FEATURE_USERBLOBS
if( dwMajor == 1 && dwMinor == 1 ) {
return FALSE;
}
#endif
return TRUE;
}
VOID
ClientAuthenticate(
BSTR parms,
BSTR blob
)
/*++
Routine Description:
Handle a REMOTEDESKTOP_CTL_AUTHENTICATE request from the client.
Arguments:
Return Value:
This function will return the following results back to the client,
based on the following
--*/
{
DC_BEGIN_FN("ClientAuthenticate");
HRESULT hr;
DWORD result = ERROR_NOT_AUTHENTICATED;
CComBSTR machineName;
CComBSTR assistantAccount;
CComBSTR assistantAccountPwd;
CComBSTR helpSessionPwd;
CComBSTR helpSessionName;
CComBSTR protocolSpecificParms;
BOOL match = FALSE;
DWORD protocolType;
long userTSSessionID;
DWORD dwVersion;
LONG clientReturnCode = SAFERROR_NOERROR;
if( FALSE == ClientVersionCompatible( g_ClientMajor, g_ClientMinor ) ) {
clientReturnCode = SAFERROR_INCOMPATIBLEVERSION;
goto CLEANUPANDEXIT;
}
//
// Parse the parms.
//
result = ParseConnectParmsString(
parms,
&dwVersion,
&protocolType,
machineName,
assistantAccount,
assistantAccountPwd,
g_HelpSessionID,
helpSessionName,
helpSessionPwd,
protocolSpecificParms
);
if (result != ERROR_SUCCESS) {
clientReturnCode = SAFERROR_INVALIDPARAMETERSTRING;
goto CLEANUPANDEXIT;
}
//
// Verify HelpSession ID and password match with our command line
// parameter
//
if( !(g_bstrCmdLineHelpSessionId == g_HelpSessionID) ) {
clientReturnCode = SAFERROR_MISMATCHPARMS;
TRC_ERR((TB, TEXT("Parameter mismatched")));
goto CLEANUPANDEXIT;
}
//
// Open an instance of the Remote Desktop Help Session Manager service.
//
hr = g_HelpSessionManager.CoCreateInstance(CLSID_RemoteDesktopHelpSessionMgr, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_DISABLE_AAA);
if (!SUCCEEDED(hr)) {
clientReturnCode = SAFERROR_INTERNALERROR;
goto CLEANUPANDEXIT;
}
//
// Set the security level to impersonate. This is required by
// the session manager.
//
hr = CoSetProxyBlanket(
(IUnknown *)g_HelpSessionManager,
RPC_C_AUTHN_DEFAULT,
RPC_C_AUTHZ_DEFAULT,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IDENTIFY,
NULL,
EOAC_NONE
);
if (!SUCCEEDED(hr)) {
TRC_ERR((TB, TEXT("CoSetProxyBlanket: %08X"), hr));
ASSERT(FALSE);
clientReturnCode = SAFERROR_INTERNALERROR;
goto CLEANUPANDEXIT;
}
//
// Resolve the Terminal Services session with help from the session
// manager. This gives the help application the opportunity to "find
// the user" and to start the TS-session named pipe component,
// by opening the relevant Remote Desktopping Session Object.
//
hr = g_HelpSessionManager->VerifyUserHelpSession(
g_HelpSessionID,
helpSessionPwd,
CComBSTR(parms),
blob,
GetCurrentProcessId(),
(ULONG_PTR*)&g_hHelpCenterProcess,
&clientReturnCode,
&userTSSessionID
);
if (SUCCEEDED(hr)) {
if( userTSSessionID != -1 ) {
//
// Cache the session ID so we don't have to make extra call
// to get the actual session ID, note, one instance of RDSADDIN
// per help assistant connection.
//
g_ClientSessionID = userTSSessionID;
match = TRUE;
}
if (match) {
TRC_NRM((TB, L"Successful password authentication for %ld",
g_ClientSessionID));
}
else {
TRC_ALT((TB, L"Can't authenticate pasword %s for %s",
helpSessionPwd, g_HelpSessionID));
clientReturnCode = SAFERROR_INVALIDPASSWORD;
goto CLEANUPANDEXIT;
}
}
else {
TRC_ERR((TB, L"Can't verify user help session %s: %08X.",
g_HelpSessionID, hr));
if( SAFERROR_NOERROR == clientReturnCode ) {
ASSERT(FALSE);
TRC_ERR((TB, L"Sessmgr did not return correct error code for VerifyUserHelpSession."));
clientReturnCode = SAFERROR_UNKNOWNSESSMGRERROR;
}
goto CLEANUPANDEXIT;
}
#ifndef DISABLESECURITYCHECKS
//
// Wait on Help Center to terminate as a fix for B2 Stopper: 342742.
//
if (g_hHelpCenterProcess == NULL) {
TRC_ERR((TB, L"Invalid g_HelpCenterProcess."));
ASSERT(FALSE);
clientReturnCode = SAFERROR_INTERNALERROR;
goto CLEANUPANDEXIT;
}
result = WTBLOBJ_AddWaitableObject(
g_WaitObjMgr, NULL,
g_hHelpCenterProcess,
HandleHelpCenterExit
);
if (result != ERROR_SUCCESS) {
clientReturnCode = SAFERROR_INTERNALERROR;
goto CLEANUPANDEXIT;
}
#endif
//
// Connect to the client session's named pipe.
//
result = ConnectClientSessionPipe();
if (result != ERROR_SUCCESS) {
clientReturnCode = SAFERROR_CANTFORMLINKTOUSERSESSION;
}
else {
g_ClientAuthenticated = TRUE;
}
CLEANUPANDEXIT:
//
// Send the result to the client.
//
ReturnResultToClient(clientReturnCode);
DC_END_FN();
}
DWORD
ProcessControlChannelRequest(
IOBUFFER &msg
)
/*++
Routine Description:
Arguments:
Return Value:
ERROR_SUCCESS on success. Otherwise, an error status is returned.
--*/
{
DC_BEGIN_FN("ProcessControlChannelRequest");
PREMOTEDESKTOP_CTL_BUFHEADER ctlHdr;
PBYTE ptr;
PBYTE end_ptr;
//
// Sanity check the message size.
//
DWORD minSize = sizeof(REMOTEDESKTOP_CHANNELBUFHEADER) + sizeof(REMOTEDESKTOP_CTL_BUFHEADER);
if (msg.bufSize < minSize) {
TRC_ERR((TB, L"minSize == %ld", minSize));
ASSERT(FALSE);
DC_END_FN();
return E_FAIL;
}
//
// Switch on the request type.
//
ptr = (PBYTE)(msg.buffer + 1);
ptr += msg.buffer->channelNameLen;
ctlHdr = (PREMOTEDESKTOP_CTL_BUFHEADER)ptr;
end_ptr = ptr + msg.buffer->dataLen;
switch(ctlHdr->msgType)
{
case REMOTEDESKTOP_CTL_AUTHENTICATE:
{
CComBSTR bstrConnectParm;
#if FEATURE_USERBLOBS
CComBSTR bstrExpertBlob;
#endif
// check to see if connectParm even exist.
if( end_ptr <= (ptr+sizeof(REMOTEDESKTOP_CTL_BUFHEADER)) )
{
ReturnResultToClient( SAFERROR_INVALIDPARAMETERSTRING );
return ERROR_INVALID_DATA;
}
//
// advance pointer to start of connect parm.
//
ptr += sizeof(REMOTEDESKTOP_CTL_BUFHEADER);
if( 0 != ((PtrToLong(end_ptr) - PtrToLong(ptr)) % 2) )
{
// connect parm and expert blob is BSTR so remaining data should be even bytes
ReturnResultToClient( SAFERROR_INVALIDPARAMETERSTRING );
return ERROR_INVALID_DATA;
}
if( ERROR_SUCCESS != IsZeroterminateString( (LPTSTR)ptr, PtrToLong(end_ptr) - PtrToLong(ptr) ) )
{
ReturnResultToClient( SAFERROR_INVALIDPARAMETERSTRING );
return ERROR_INVALID_DATA;
}
bstrConnectParm = (BSTR)ptr;
#if FEATURE_USERBLOBS
ptr += (bstrConnectParm.Length()+1)*sizeof(WCHAR);
// check bound of connectParm
if( end_ptr < ptr )
{
ReturnResultToClient( SAFERROR_INVALIDPARAMETERSTRING );
return ERROR_INVALID_DATA;
}
else if( end_ptr > ptr )
{
// check to see if we have an expert blob
if( ERROR_SUCCESS != IsZeroterminateString( (LPTSTR)ptr, PtrToLong(end_ptr) - PtrToLong(ptr) ) )
{
ReturnResultToClient( SAFERROR_INVALIDPARAMETERSTRING );
return ERROR_INVALID_DATA;
}
bstrExpertBlob = (BSTR)ptr;
ptr += ( bstrExpertBlob.Length() + 1 ) * sizeof( WCHAR );
if( ptr != end_ptr )
{
ReturnResultToClient( SAFERROR_INVALIDPARAMETERSTRING );
return ERROR_INVALID_DATA;
}
}
else
{
// Authentication packet does not contain expert specific blob,
// pass empty string over or RPC call will fail.
bstrExpertBlob = L"";
}
#endif
ClientAuthenticate(
bstrConnectParm,
#if FEATURE_USERBLOBS
bstrExpertBlob
#else
CComBSTR(L"")
#endif
);
}
break;
case REMOTEDESKTOP_CTL_REMOTE_CONTROL_DESKTOP :
// RemoteControlDesktop((BSTR)(ptr+sizeof(REMOTEDESKTOP_CTL_BUFHEADER)));
// thread makes no use of bstrparm leave it for null in case we need to
// cbange this for later
RemoteControlDesktop( ( BSTR )NULL );
break;
case REMOTEDESKTOP_CTL_VERSIONINFO:
g_ClientMajor = *(DWORD *)(ptr + sizeof(REMOTEDESKTOP_CTL_BUFHEADER));
g_ClientMinor = *(DWORD *)(ptr + sizeof(REMOTEDESKTOP_CTL_BUFHEADER) + sizeof(DWORD));
TRC_NRM((TB, L"dwMajor = %ld, dwMinor = %d", g_ClientMajor, g_ClientMinor));
//
// We only store version number and let ClientAuthenticate() disconnect client,
// rdchost.dll send two packets, version and AUTHENTICATE in sequence.
//
break;
default:
//
// We will ignore unknown control messages for forward compatibility
//
TRC_NRM((TB, L"Unknown ctl message from client: %ld", ctlHdr->msgType));
}
DC_END_FN();
return ERROR_SUCCESS;
}
DWORD
SendMsgToClient(
PREMOTEDESKTOP_CHANNELBUFHEADER msg
)
/*++
Routine Description:
Arguments:
msg - Message to send.
Return Value:
ERROR_SUCCESS on success. Otherwise, an error status is returned.
--*/
{
DC_BEGIN_FN("SendMsgToClient");
OVERLAPPED overlapped;
PBYTE ptr;
DWORD bytesToWrite;
DWORD bytesWritten;
DWORD result = ERROR_SUCCESS;
#ifdef USE_MAGICNO
ASSERT(msg->magicNo == CHANNELBUF_MAGICNO);
#endif
//
// Send the data out the virtual channel interface.
//
// TODO: Figure out why this flag is not getting set ... and
// if it really matters. Likely, remove the flag.
//
//if (g_ClientConnected) {
EnterCriticalSection( &g_cs );
ptr = (PBYTE)msg;
bytesToWrite = msg->dataLen + msg->channelNameLen +
sizeof(REMOTEDESKTOP_CHANNELBUFHEADER);
while (bytesToWrite > 0) {
//
// Write
//
memset(&overlapped, 0, sizeof(overlapped));
if (!WriteFile(g_VCFileHandle, ptr, bytesToWrite,
&bytesWritten, &overlapped)) {
if (GetLastError() == ERROR_IO_PENDING) {
if (!GetOverlappedResult(
g_VCFileHandle,
&overlapped,
&bytesWritten,
TRUE)) {
result = GetLastError();
TRC_ERR((TB, L"GetOverlappedResult: %08X", result));
break;
}
}
else {
result = GetLastError();
TRC_ERR((TB, L"WriteFile: %08X", result));
// ASSERT(FALSE); overactive assert after disconnect
break;
}
}
//
// Increment the ptr and decrement the bytes remaining.
//
bytesToWrite -= bytesWritten;
ptr += bytesWritten;
}
LeaveCriticalSection( &g_cs );
/*
else {
result = ERROR_NOT_CONNECTED;
}
*/
//
//update the timer
//
g_PrevTimer = GetTickCount();
DC_END_FN();
return result;
}
VOID
HandleVCReadComplete(
HANDLE waitableObject,
PVOID clientData
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
DC_BEGIN_FN("HandleVCReadComplete");
DWORD bytesRead;
DWORD result = ERROR_SUCCESS;
BOOL resizeBuf = FALSE;
//
// Get the results of the read.
//
if (!GetOverlappedResult(
g_VCFileHandle,
&g_VCReadOverlapped,
&bytesRead,
FALSE)) {
//
// If we are too small, then reissue the read with a larger buffer.
//
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
resizeBuf = TRUE;
}
else {
result = GetLastError();
TRC_ERR((TB, L"GetOverlappedResult: %08X", result));
goto CLEANUPANDEXIT;
}
}
else {
g_IncomingVCBuf.offset += bytesRead;
}
//
// See if we have a complete packet from the client.
//
if (g_IncomingVCBuf.offset >= sizeof(REMOTEDESKTOP_CHANNELBUFHEADER)) {
DWORD packetSize = g_IncomingVCBuf.buffer->dataLen +
g_IncomingVCBuf.buffer->channelNameLen +
sizeof(REMOTEDESKTOP_CHANNELBUFHEADER);
//
// If we have a complete packet, then handle the read and reset the offset.
//
if (g_IncomingVCBuf.offset >= packetSize) {
result = HandleReceivedVCMsg(g_IncomingVCBuf);
if (result == ERROR_SUCCESS) {
g_IncomingVCBuf.offset = 0;
}
else {
goto CLEANUPANDEXIT;
}
}
//
// Otherwise, resize the incoming buf if we are exactly at the incoming
// buffer boundary.
//
else if (g_IncomingVCBuf.offset == g_IncomingVCBuf.bufSize) {
resizeBuf = TRUE;
}
}
//
// Resize, if necessary.
//
if (resizeBuf) {
PREMOTEDESKTOP_CHANNELBUFHEADER pBuffer = NULL;
pBuffer = (PREMOTEDESKTOP_CHANNELBUFHEADER )REALLOCMEM(
g_IncomingVCBuf.buffer,
g_IncomingVCBuf.bufSize + VCBUFFER_RESIZE_DELTA
);
if (pBuffer != NULL) {
g_IncomingVCBuf.buffer = pBuffer;
result = ERROR_SUCCESS;
g_IncomingVCBuf.bufSize = g_IncomingVCBuf.bufSize +
VCBUFFER_RESIZE_DELTA;
}
else {
result = ERROR_NOT_ENOUGH_MEMORY;
TRC_ERR((TB, L"Couldn't allocate incoming VC buf."));
goto CLEANUPANDEXIT;
}
}
//
//update the timer
//
g_PrevTimer = GetTickCount();
//
// Issue the next read request.
//
result = IssueVCOverlappedRead(g_IncomingVCBuf, g_VCReadOverlapped) ;
CLEANUPANDEXIT:
//
// Any failure is fatal. The client will need to reconnect to get things
// started again.
//
if (result != ERROR_SUCCESS) {
TRC_ERR((TB, L"Client considered disconnected. Shutting down."));
g_Shutdown = TRUE;
}
DC_END_FN();
}
DWORD
IssueVCOverlappedRead(
IOBUFFER &msg,
OVERLAPPED &ol
)
/*++
Routine Description:
Issue an overlapped read for the next VC buffer.
Arguments:
msg - Incoming VC buffer.
ol - Corresponding overlapped IO struct.
Return Value:
Returns ERROR_SUCCESS on success. Otherwise, an error code is returned.
--*/
{
DC_BEGIN_FN("IssueVCOverlappedRead");
DWORD result = ERROR_SUCCESS;
ol.Internal = 0;
ol.InternalHigh = 0;
ol.Offset = 0;
ol.OffsetHigh = 0;
ResetEvent(ol.hEvent);
if (!ReadFile(g_VCFileHandle, ((PBYTE)msg.buffer)+msg.offset,
msg.bufSize - msg.offset, NULL, &ol)) {
if (GetLastError() != ERROR_IO_PENDING) {
result = GetLastError();
TRC_ERR((TB, L"ReadFile failed: %08X", result));
}
}
DC_END_FN();
return result;
}
DWORD
IssueNamedPipeOverlappedRead(
IOBUFFER &msg,
OVERLAPPED &ol,
DWORD len
)
/*++
Routine Description:
Issue an overlapped read for the next named pipe buffer.
Arguments:
msg - Incoming Named Pipe buffer.
ol - Corresponding overlapped IO struct.
Return Value:
Returns ERROR_SUCCESS on success. Otherwise, an error code is returned.
--*/
{
DC_BEGIN_FN("IssueNamedPipeOverlappedRead");
DWORD result = ERROR_SUCCESS;
ol.Internal = 0;
ol.InternalHigh = 0;
ol.Offset = 0;
ol.OffsetHigh = 0;
ResetEvent(ol.hEvent);
if (!ReadFile(g_ClientSessionPipe, ((PBYTE)msg.buffer), len, NULL, &ol)) {
if (GetLastError() != ERROR_IO_PENDING) {
result = GetLastError();
TRC_ERR((TB, L"ReadFile failed: %08X", result));
}
}
DC_END_FN();
return result;
}
DWORD
HandleReceivedVCMsg(
IOBUFFER &msg
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
DC_BEGIN_FN("HandleReceivedVCMsg");
OVERLAPPED overlapped;
PBYTE ptr;
DWORD bytesToWrite;
DWORD bytesWritten;
DWORD result = ERROR_SUCCESS;
BSTR channelName;
BSTREqual isBSTREqual;
CComBSTR tmpStr;
#ifdef USE_MAGICNO
ASSERT(msg.buffer->magicNo == CHANNELBUF_MAGICNO);
#endif
//
// Get the channel name.
// TODO: We could actually be smarter about this by checking the
// length for a match, first.
//
channelName = SysAllocStringByteLen(NULL, msg.buffer->channelNameLen);
if (channelName == NULL) {
TRC_ERR((TB, TEXT("Can't allocate channel name.")));
goto CLEANUPANDEXIT;
}
ptr = (PBYTE)(msg.buffer + 1);
memcpy(channelName, ptr, msg.buffer->channelNameLen);
//
// Filter control channel data.
//
tmpStr = REMOTEDESKTOP_RC_CONTROL_CHANNEL;
if (isBSTREqual(channelName, tmpStr)) {
result = ProcessControlChannelRequest(msg);
goto CLEANUPANDEXIT;
}
//
// If the client is not yet authenticated.
//
if (!g_ClientAuthenticated) {
result = E_FAIL;
goto CLEANUPANDEXIT;
}
if( g_ClientSessionPipe == INVALID_HANDLE_VALUE ||
g_ClientSessionPipe == NULL ) {
//
// when client is authenticated, g_ClientSessionPipe must
// have valid value.
ASSERT(FALSE);
result = E_FAIL;
goto CLEANUPANDEXIT;
}
//
// Send the message header.
//
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = g_NamedPipeWriteEvent;
ResetEvent(g_NamedPipeWriteEvent);
if (!WriteFile(g_ClientSessionPipe,
msg.buffer, sizeof(REMOTEDESKTOP_CHANNELBUFHEADER),
&bytesWritten, &overlapped)) {
if (GetLastError() == ERROR_IO_PENDING) {
if (WaitForSingleObject(
g_NamedPipeWriteEvent,
INFINITE
) != WAIT_OBJECT_0) {
result = GetLastError();
TRC_ERR((TB, L"WaitForSingleObject: %08X", result));
goto CLEANUPANDEXIT;
}
if (!GetOverlappedResult(
g_ClientSessionPipe,
&overlapped,
&bytesWritten,
FALSE)) {
result = GetLastError();
TRC_ERR((TB, L"GetOverlappedResult: %08X", result));
goto CLEANUPANDEXIT;
}
}
else {
result = GetLastError();
TRC_ERR((TB, L"WriteFile: %08X", result));
goto CLEANUPANDEXIT;
}
}
ASSERT(bytesWritten == sizeof(REMOTEDESKTOP_CHANNELBUFHEADER));
//
// Send the message data.
//
ptr = ((PBYTE)msg.buffer) + sizeof(REMOTEDESKTOP_CHANNELBUFHEADER);
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = g_NamedPipeWriteEvent;
ResetEvent(g_NamedPipeWriteEvent);
if (!WriteFile(g_ClientSessionPipe,
ptr, msg.buffer->dataLen +
msg.buffer->channelNameLen,
&bytesWritten, &overlapped)) {
if (GetLastError() == ERROR_IO_PENDING) {
if (WaitForSingleObject(
g_NamedPipeWriteEvent,
INFINITE
) != WAIT_OBJECT_0) {
result = GetLastError();
TRC_ERR((TB, L"WaitForSingleObject: %08X", result));
goto CLEANUPANDEXIT;
}
if (!GetOverlappedResult(
g_ClientSessionPipe,
&overlapped,
&bytesWritten,
FALSE)) {
result = GetLastError();
TRC_ERR((TB, L"GetOverlappedResult: %08X", result));
goto CLEANUPANDEXIT;
}
}
else {
result = GetLastError();
TRC_ERR((TB, L"WriteFile: %08X", result));
goto CLEANUPANDEXIT;
}
}
ASSERT(bytesWritten == msg.buffer->dataLen +
msg.buffer->channelNameLen);
CLEANUPANDEXIT:
if (channelName != NULL) {
SysFreeString(channelName);
}
DC_END_FN();
return result;
}
VOID
HandleVCClientConnect(
HANDLE waitableObject,
PVOID clientData
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
DC_BEGIN_FN("HandleVCClientConnect");
g_ClientConnected = TRUE;
DC_END_FN();
}
VOID
HandleVCClientDisconnect(
HANDLE waitableObject,
PVOID clientData
)
/*++
Routine Description:
Arguments:
Return Value:
ERROR_SUCCESS on success. Otherwise, an error status is returned.
--*/
{
DC_BEGIN_FN("HandleVCClientDisconnect");
DWORD dwCurTimer = GetTickCount();
//
//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 > g_PrevTimer && ( dwCurTimer - g_PrevTimer >= g_dwTimeOutInterval)) {
//
//enough time passed since the last check. send data to client
if( SendNullDataToClient() != ERROR_SUCCESS ) {
//
//set the shutdown flag
//
g_Shutdown = TRUE;
g_ClientConnected = FALSE;
}
}
g_PrevTimer = dwCurTimer;
DC_END_FN();
}
VOID
HandleNamedPipeReadComplete(
OVERLAPPED &incomingPipeOL,
IOBUFFER &incomingPipeBuf
)
/*++
Routine Description:
Handle a read complete event on the session's named pipe.
Arguments:
incomingPipeOL - Overlapped Read Struct
incomingPipeBuf - Incoming Data Buffer.
Return Value:
--*/
{
DC_BEGIN_FN("HandleNamedPipeReadComplete");
DWORD bytesRead;
DWORD requiredSize;
BOOL disconnectClientPipe = FALSE;
DWORD result;
DWORD bytesToRead;
HANDLE waitableObjects[2];
DWORD waitResult;
//
// Get the results of the read on the buffer header.
//
if (!GetOverlappedResult(
g_ClientSessionPipe,
&incomingPipeOL,
&bytesRead,
FALSE)
|| (bytesRead != sizeof(REMOTEDESKTOP_CHANNELBUFHEADER))) {
disconnectClientPipe = TRUE;
goto CLEANUPANDEXIT;
}
//
// Make sure the incoming buffer is large enough.
//
requiredSize = incomingPipeBuf.buffer->dataLen +
incomingPipeBuf.buffer->channelNameLen +
sizeof(REMOTEDESKTOP_CHANNELBUFHEADER);
if (incomingPipeBuf.bufSize < requiredSize) {
PREMOTEDESKTOP_CHANNELBUFHEADER pBuffer = NULL;
pBuffer = (PREMOTEDESKTOP_CHANNELBUFHEADER )REALLOCMEM(
incomingPipeBuf.buffer,
requiredSize
);
if (pBuffer != NULL) {
incomingPipeBuf.buffer = pBuffer;
incomingPipeBuf.bufSize = requiredSize;
}
else {
TRC_ERR((TB, L"Shutting down because of memory allocation failure."));
g_Shutdown = TRUE;
goto CLEANUPANDEXIT;
}
}
//
// Now read the buffer data.
//
incomingPipeOL.Internal = 0;
incomingPipeOL.InternalHigh = 0;
incomingPipeOL.Offset = 0;
incomingPipeOL.OffsetHigh = 0;
ResetEvent(incomingPipeOL.hEvent);
if (!ReadFile(
g_ClientSessionPipe,
incomingPipeBuf.buffer + 1,
incomingPipeBuf.buffer->channelNameLen +
incomingPipeBuf.buffer->dataLen,
&bytesRead, &incomingPipeOL)
) {
if (GetLastError() == ERROR_IO_PENDING) {
waitableObjects[0] = incomingPipeOL.hEvent;
waitableObjects[1] = g_ShutdownEvent;
waitResult = WaitForMultipleObjects(
2, waitableObjects,
FALSE,
INFINITE
);
if ((waitResult != WAIT_OBJECT_0) || g_Shutdown) {
disconnectClientPipe = TRUE;
goto CLEANUPANDEXIT;
}
if (!GetOverlappedResult(
g_ClientSessionPipe,
&incomingPipeOL,
&bytesRead,
FALSE)) {
disconnectClientPipe = TRUE;
goto CLEANUPANDEXIT;
}
}
else {
disconnectClientPipe = TRUE;
goto CLEANUPANDEXIT;
}
}
//
// Make sure we got all the data.
//
bytesToRead = incomingPipeBuf.buffer->channelNameLen +
incomingPipeBuf.buffer->dataLen;
if (bytesRead != bytesToRead) {
TRC_ERR((TB, L"Bytes read: %ld != bytes requested: %ld",
bytesRead, bytesToRead));
ASSERT(FALSE);
disconnectClientPipe = TRUE;
goto CLEANUPANDEXIT;
}
//
// Handle the read data.
//
HandleReceivedPipeMsg(incomingPipeBuf);
//
// Issue the read for the next message header.
//
result = IssueNamedPipeOverlappedRead(
incomingPipeBuf,
incomingPipeOL,
sizeof(REMOTEDESKTOP_CHANNELBUFHEADER)
);
disconnectClientPipe = (result != ERROR_SUCCESS);
CLEANUPANDEXIT:
//
// This is considered a fatal error because the client session must
// no longer be in "listen" mode.
//
if (disconnectClientPipe) {
TRC_ERR((TB, L"Connection to client pipe lost: %08X",
GetLastError()));
g_Shutdown = TRUE;
}
DC_END_FN();
}
VOID
HandleReceivedPipeMsg(
IOBUFFER &msg
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
DC_BEGIN_FN("HandleReceivedPipeMsg");
DWORD result;
//
// Forward the message to the client.
//
result = SendMsgToClient(msg.buffer);
//
// This is considered a fatal error. The client will need to reconnect
// to get things started again.
//
if (result != ERROR_SUCCESS) {
TRC_ERR((TB, L"Shutting down because of VC IO error."));
g_Shutdown = TRUE;
}
DC_END_FN();
}
DWORD
ConnectVC()
/*++
Routine Description:
Arguments:
Return Value:
ERROR_SUCCESS on success. Otherwise, an error status is returned.
--*/
{
DC_BEGIN_FN("ConnectVC");
WCHAR buf[256];
DWORD len;
PVOID vcFileHandlePtr;
REMOTEDESKTOP_CTL_SERVERANNOUNCE_PACKET msg;
REMOTEDESKTOP_CTL_VERSIONINFO_PACKET versionInfoMsg;
DWORD result = ERROR_SUCCESS;
//
// Open the virtual channel.
//
g_VCHandle = WTSVirtualChannelOpen(
WTS_CURRENT_SERVER_HANDLE,
WTS_CURRENT_SESSION,
TSRDPREMOTEDESKTOP_VC_CHANNEL_A
);
if (g_VCHandle == NULL) {
result = GetLastError();
if (result == ERROR_SUCCESS) { result = E_FAIL; }
TRC_ERR((TB, L"WTSVirtualChannelOpen: %08X", result));
goto CLEANUPANDEXIT;
}
//
// Get access to the underlying file handle for async IO.
//
if (!WTSVirtualChannelQuery(
g_VCHandle,
WTSVirtualFileHandle,
&vcFileHandlePtr,
&len
)) {
result = GetLastError();
TRC_ERR((TB, L"WTSQuerySessionInformation: %08X", result));
goto CLEANUPANDEXIT;
}
ASSERT(len == sizeof(g_VCFileHandle));
//
// WTSVirtualChannelQuery allocates the returned buffer.
//
memcpy(&g_VCFileHandle, vcFileHandlePtr, sizeof(g_VCFileHandle));
LocalFree(vcFileHandlePtr);
//
//create the timer event, we will start it later
//it will be signaled when the time is up
//
g_ClientIsconnectEvent = CreateWaitableTimer( NULL, FALSE, NULL);
if (g_ClientIsconnectEvent == NULL) {
result = GetLastError();
TRC_ERR((TB, L"CreateEvent: %08X", result));
goto CLEANUPANDEXIT;
}
//
// Create the read finish event.
//
g_VCReadOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (g_VCReadOverlapped.hEvent == NULL) {
result = GetLastError();
TRC_ERR((TB, L"CreateEvent: %08X", result));
goto CLEANUPANDEXIT;
}
//
// Register the read finish event.
//
result = WTBLOBJ_AddWaitableObject(
g_WaitObjMgr, NULL,
g_VCReadOverlapped.hEvent,
HandleVCReadComplete
);
if (result != ERROR_SUCCESS) {
goto CLEANUPANDEXIT;
}
// register the disconnect event
//NOTE : order IS important
//waitformultipleobjects returns the lowest index
//when more than one are signaled
//we want to use the read event, not the disconnect event
//in case both are signaled
result = WTBLOBJ_AddWaitableObject(
g_WaitObjMgr, NULL,
g_ClientIsconnectEvent,
HandleVCClientDisconnect
);
if (result != ERROR_SUCCESS) {
goto CLEANUPANDEXIT;
}
//
// Allocate space for the first VC read.
//
g_IncomingVCBuf.buffer = (PREMOTEDESKTOP_CHANNELBUFHEADER )ALLOCMEM(
VCBUFFER_RESIZE_DELTA
);
if (g_IncomingVCBuf.buffer != NULL) {
g_IncomingVCBuf.bufSize = VCBUFFER_RESIZE_DELTA;
g_IncomingVCBuf.offset = 0;
}
else {
TRC_ERR((TB, L"Can't allocate VC read buffer."));
result = ERROR_NOT_ENOUGH_MEMORY;
goto CLEANUPANDEXIT;
}
//
// Issue the first overlapped read on the VC.
//
result = IssueVCOverlappedRead(g_IncomingVCBuf, g_VCReadOverlapped);
if (result != ERROR_SUCCESS) {
goto CLEANUPANDEXIT;
}
//
// Notify the client that we are alive.
//
memcpy(msg.packetHeader.channelName, REMOTEDESKTOP_RC_CONTROL_CHANNEL,
sizeof(REMOTEDESKTOP_RC_CONTROL_CHANNEL));
msg.packetHeader.channelBufHeader.channelNameLen = REMOTEDESKTOP_RC_CHANNELNAME_LENGTH;
#ifdef USE_MAGICNO
msg.packetHeader.channelBufHeader.magicNo = CHANNELBUF_MAGICNO;
#endif
msg.packetHeader.channelBufHeader.dataLen =
sizeof(REMOTEDESKTOP_CTL_SERVERANNOUNCE_PACKET) -
sizeof(REMOTEDESKTOP_CTL_PACKETHEADER);
msg.msgHeader.msgType = REMOTEDESKTOP_CTL_SERVER_ANNOUNCE;
result = SendMsgToClient((PREMOTEDESKTOP_CHANNELBUFHEADER)&msg);
if (result != ERROR_SUCCESS) {
goto CLEANUPANDEXIT;
}
//
// Send the server protocol version information.
//
memcpy(versionInfoMsg.packetHeader.channelName, REMOTEDESKTOP_RC_CONTROL_CHANNEL,
sizeof(REMOTEDESKTOP_RC_CONTROL_CHANNEL));
versionInfoMsg.packetHeader.channelBufHeader.channelNameLen = REMOTEDESKTOP_RC_CHANNELNAME_LENGTH;
#ifdef USE_MAGICNO
versionInfoMsg.packetHeader.channelBufHeader.magicNo = CHANNELBUF_MAGICNO;
#endif
versionInfoMsg.packetHeader.channelBufHeader.dataLen =
sizeof(REMOTEDESKTOP_CTL_VERSIONINFO_PACKET) -
sizeof(REMOTEDESKTOP_CTL_PACKETHEADER);
versionInfoMsg.msgHeader.msgType = REMOTEDESKTOP_CTL_VERSIONINFO;
versionInfoMsg.versionMajor = REMOTEDESKTOP_VERSION_MAJOR;
versionInfoMsg.versionMinor = REMOTEDESKTOP_VERSION_MINOR;
result = SendMsgToClient((PREMOTEDESKTOP_CHANNELBUFHEADER)&versionInfoMsg);
if (result != ERROR_SUCCESS) {
goto CLEANUPANDEXIT;
}
CLEANUPANDEXIT:
DC_END_FN();
return result;
}
DWORD
ConnectClientSessionPipe()
/*++
Routine Description:
Connect to the client session TSRDP plug-in named pipe.
Arguments:
Return Value:
ERROR_SUCCESS on success. Otherwise, an error status is returned.
--*/
{
DC_BEGIN_FN("ConnectClientSessionPipe");
unsigned dump;
WCHAR pipePath[MAX_PATH+1];
DWORD result;
DWORD pipeMode = PIPE_READMODE_MESSAGE | PIPE_WAIT;
//
// Loop until we are connected or time out.
//
ASSERT(g_ClientSessionPipe == NULL);
while(g_ClientSessionPipe == NULL) {
wsprintf(pipePath, L"\\\\.\\pipe\\%s-%s",
TSRDPREMOTEDESKTOP_PIPENAME, g_HelpSessionID);
g_ClientSessionPipe = CreateFile(
pipePath,
GENERIC_READ |
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, NULL
);
if (g_ClientSessionPipe != INVALID_HANDLE_VALUE) {
TRC_NRM((TB, L"Pipe successfully connected."));
result = ERROR_SUCCESS;
break;
}
else {
TRC_ALT((TB, L"Waiting for pipe availability: %08X.",
GetLastError()));
WaitNamedPipe(pipePath, CLIENTPIPE_CONNECTTIMEOUT);
result = GetLastError();
if (result != ERROR_SUCCESS) {
TRC_ERR((TB, L"WaitNamedPipe: %08X", result));
break;
}
}
}
//
// If we didn't get a valid connection, then bail out of
// this function and shut down.
//
if (g_ClientSessionPipe == INVALID_HANDLE_VALUE) {
ASSERT(result != ERROR_SUCCESS);
TRC_ERR((TB, L"Shutting down because of named pipe error."));
g_Shutdown = TRUE;
goto CLEANUPANDEXIT;
}
//
//set the options on the pipe to be the same as that of the server end to avoid problems
//fatal if we could not set it
//
if(!SetNamedPipeHandleState(g_ClientSessionPipe,
&pipeMode, // new pipe mode
NULL,
NULL
)) {
result = GetLastError();
TRC_ERR((TB, L"Shutting down, SetNamedPipeHandleState: %08X", result));
g_Shutdown = TRUE;
goto CLEANUPANDEXIT;
}
//
// Spin off the pipe read background thread.
//
g_NamedPipeReadThread = (HANDLE)_beginthreadex(NULL, 0, NamedPipeReadThread, NULL, 0, &dump);
if ((uintptr_t)g_NamedPipeReadThread == -1) {
g_NamedPipeReadThread = NULL;
TRC_ERR((TB, L"Failed to create NamedPipeReadThread: %08X", GetLastError()));
g_Shutdown = TRUE;
result = errno;
goto CLEANUPANDEXIT;
}
CLEANUPANDEXIT:
DC_END_FN();
return result;
}
unsigned __stdcall
NamedPipeReadThread(
void* ptr
)
/*++
Routine Description:
Named Pipe Input Thread
Arguments:
ptr - Ignored
Return Value:
NA
--*/
{
DC_BEGIN_FN("NamedPipeReadThread");
IOBUFFER incomingPipeBuf = { NULL, 0, 0 };
OVERLAPPED overlapped = { 0, 0, 0, 0, NULL };
DWORD waitResult;
DWORD ret;
HANDLE waitableObjects[2];
//
// Allocate the initial buffer for incoming named pipe data.
//
incomingPipeBuf.buffer = (PREMOTEDESKTOP_CHANNELBUFHEADER )
ALLOCMEM(sizeof(REMOTEDESKTOP_CHANNELBUFHEADER));
if (incomingPipeBuf.buffer != NULL) {
incomingPipeBuf.bufSize = sizeof(REMOTEDESKTOP_CHANNELBUFHEADER);
}
else {
TRC_ERR((TB, L"Can't allocate named pipe buf."));
g_Shutdown = TRUE;
goto CLEANUPANDEXIT;
}
//
// Create the overlapped pipe read event.
//
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped.hEvent == NULL) {
TRC_ERR((TB, L"CreateEvent: %08X", GetLastError()));
g_Shutdown = TRUE;
goto CLEANUPANDEXIT;
}
//
// Issue the read for the first message header.
//
ret = IssueNamedPipeOverlappedRead(
incomingPipeBuf,
overlapped,
sizeof(REMOTEDESKTOP_CHANNELBUFHEADER)
);
//
// If we can't connect, that's considered a fatal error because
// the client must no longer be in "listen" mode.
//
if (ret != ERROR_SUCCESS) {
TRC_ERR((TB, L"Shutting down because of named pipe error."));
g_Shutdown = TRUE;
}
//
// Loop until shut down.
//
waitableObjects[0] = overlapped.hEvent;
waitableObjects[1] = g_ShutdownEvent;
while (!g_Shutdown) {
//
// We will be signalled when the pipe closes or the read completes.
//
waitResult = WaitForMultipleObjects(
2, waitableObjects,
FALSE,
INFINITE
);
if ((waitResult == WAIT_OBJECT_0) && !g_Shutdown) {
HandleNamedPipeReadComplete(overlapped, incomingPipeBuf);
}
else {
TRC_ERR((TB, L"WaitForMultipleObjects: %08X", GetLastError()));
g_Shutdown = TRUE;
}
}
CLEANUPANDEXIT:
//
// Make sure the foreground thread knows that we are shutting down.
//
if (g_WakeUpForegroundThreadEvent != NULL) {
SetEvent(g_WakeUpForegroundThreadEvent);
}
if (overlapped.hEvent != NULL) {
CloseHandle(overlapped.hEvent);
}
if (incomingPipeBuf.buffer != NULL) {
FREEMEM(incomingPipeBuf.buffer);
}
DC_END_FN();
_endthreadex(0);
return 0;
}
VOID WakeUpFunc(
HANDLE waitableObject,
PVOID clientData
)
/*++
Routine Description:
Stub function, called when the background thread wants the foreground
thread to wake up because of a state change.
Arguments:
Return Value:
--*/
{
DC_BEGIN_FN("WakeUpFunc");
DC_END_FN();
}
VOID HandleHelpCenterExit(
HANDLE waitableObject,
PVOID clientData
)
/*++
Routine Description:
Woken up when Help Center exits as a fix for B2 Stopper: 342742
Arguments:
Return Value:
--*/
{
DC_BEGIN_FN("HandleHelpCenterExit");
g_Shutdown = TRUE;
DC_END_FN();
}
extern "C"
int
__cdecl
wmain( int argc, wchar_t *argv[])
{
DC_BEGIN_FN("Main");
DWORD result = ERROR_SUCCESS;
DWORD sz;
HRESULT hr;
LARGE_INTEGER liDueTime;
BOOL backgroundThreadFailedToExit = FALSE;
DWORD waitResult;
SetConsoleCtrlHandler( ControlHandler, TRUE );
//
// Expecting two parameters, first is HelpSession ID and second is
// HelpSession Password, we don't want to failed here just because
// number of argument mismatched, we will let authentication fail and
// return error code.
//
ASSERT( argc == 2 );
if( argc >= 2 ) {
g_bstrCmdLineHelpSessionId = argv[1];
TRC_ALT((TB, L"Input Parameters 1 : %ws ", argv[1]));
}
//
// Initialize Critical Section
//
__try
{
InitializeCriticalSection( &g_cs );
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
TRC_ERR( ( TB , L"InitializeCriticalSection failed" ) );
return E_OUTOFMEMORY;
}
//
// Initialize COM.
//
hr = CoInitialize(NULL);
if (!SUCCEEDED(hr)) {
result = E_FAIL;
TRC_ERR((TB, L"CoInitialize: %08X", hr));
goto CLEANUPANDEXIT;
}
//
// Get our process.
//
g_ProcHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE,
GetCurrentProcessId());
if (g_ProcHandle == NULL) {
result = GetLastError();
TRC_ERR((TB, L"OpenProcess: %08X", result));
goto CLEANUPANDEXIT;
}
//
// Get our process token.
//
if (!OpenProcessToken(g_ProcHandle, TOKEN_READ, &g_ProcToken)) {
result = GetLastError();
TRC_ERR((TB, L"OpenProcessToken: %08X", result));
goto CLEANUPANDEXIT;
}
//
// Get our session ID.
//
if (!GetTokenInformation(g_ProcToken, TokenSessionId,
&g_SessionID, sizeof(g_SessionID), &sz)) {
result = GetLastError();
TRC_ERR((TB, L"GetTokenInformation: %08X", result));
goto CLEANUPANDEXIT;
}
//
// Initialize the waitable object manager.
//
g_WaitObjMgr = WTBLOBJ_CreateWaitableObjectMgr();
if (g_WaitObjMgr == NULL) {
result = E_FAIL;
goto CLEANUPANDEXIT;
}
//
//initialize the timer, get the timer interval from registry or use default
//used for finding if the client (expert) is still connected
//
g_PrevTimer = GetTickCount();
if(!GetDwordFromRegistry(&g_dwTimeOutInterval))
g_dwTimeOutInterval = RDS_CHECKCONN_TIMEOUT;
else
g_dwTimeOutInterval *= 1000; //we need this in millisec
liDueTime.QuadPart = -1 * g_dwTimeOutInterval * 1000 * 100; //in one hundred nanoseconds
//
// Initiate the VC channel connection.
//
result = ConnectVC();
if (result != ERROR_SUCCESS) {
goto CLEANUPANDEXIT;
}
//
// This is an event the background thread can use to wake up the
// foreground thread in order to check state.
//
g_WakeUpForegroundThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_WakeUpForegroundThreadEvent == NULL) {
TRC_ERR((TB, L"CreateEvent: %08X", GetLastError()));
result = E_FAIL;
goto CLEANUPANDEXIT;
}
result = WTBLOBJ_AddWaitableObject(
g_WaitObjMgr, NULL,
g_WakeUpForegroundThreadEvent,
WakeUpFunc
);
if (result != ERROR_SUCCESS) {
goto CLEANUPANDEXIT;
}
//
// Create the named pipe write complete event.
//
g_NamedPipeWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (g_NamedPipeWriteEvent == NULL) {
result = GetLastError();
TRC_ERR((TB, L"CreateEvent: %08X", result));
goto CLEANUPANDEXIT;
}
//
//start the timer event, ignore error. 0 in the registry means don't send any pings
//worst case, we don't get disconnected which is fine
//
if(g_dwTimeOutInterval)
SetWaitableTimer( g_ClientIsconnectEvent, &liDueTime, g_dwTimeOutInterval, NULL, NULL, FALSE );
//
// Create the shutdown event.
//
g_ShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (g_ShutdownEvent == NULL) {
result = GetLastError();
TRC_ERR((TB, L"CreateEvent: %08X", result));
goto CLEANUPANDEXIT;
}
//
// Handle IO events until the shut down flag is set.
//
while (!g_Shutdown) {
result = WTBLOBJ_PollWaitableObjects(g_WaitObjMgr);
if (result != ERROR_SUCCESS) {
g_Shutdown = TRUE;
}
}
//
// Notify the client that we have disconnected, in case it hasn't
// figured it out yet.
//
if (g_VCFileHandle != NULL) {
REMOTEDESKTOP_CTL_DISCONNECT_PACKET msg;
memcpy(msg.packetHeader.channelName, REMOTEDESKTOP_RC_CONTROL_CHANNEL,
sizeof(REMOTEDESKTOP_RC_CONTROL_CHANNEL));
msg.packetHeader.channelBufHeader.channelNameLen =
REMOTEDESKTOP_RC_CHANNELNAME_LENGTH;
#ifdef USE_MAGICNO
msg.packetHeader.channelBufHeader.magicNo = CHANNELBUF_MAGICNO;
#endif
msg.packetHeader.channelBufHeader.dataLen =
sizeof(REMOTEDESKTOP_CTL_DISCONNECT_PACKET) -
sizeof(REMOTEDESKTOP_CTL_PACKETHEADER);
msg.msgHeader.msgType = REMOTEDESKTOP_CTL_DISCONNECT;
SendMsgToClient((PREMOTEDESKTOP_CHANNELBUFHEADER)&msg);
}
CLEANUPANDEXIT:
//
// Signal the shutdown event.
//
if (g_ShutdownEvent != NULL) {
SetEvent(g_ShutdownEvent);
}
//
// Wait for the background threads to exit.
//
if (g_RemoteControlDesktopThread != NULL) {
// if we can get here, force a shadow stop
WinStationShadowStop(
SERVERNAME_CURRENT,
g_ClientSessionID,
TRUE
);
waitResult = WaitForSingleObject(
g_RemoteControlDesktopThread,
THREADSHUTDOWN_WAITTIMEOUT
);
if (waitResult != WAIT_OBJECT_0) {
backgroundThreadFailedToExit = TRUE;
TRC_ERR((TB, L"WaitForSingleObject g_RemoteControlDesktopThread: %ld",
waitResult));
}
CloseHandle( g_RemoteControlDesktopThread );
}
if (g_NamedPipeReadThread != NULL) {
waitResult = WaitForSingleObject(
g_NamedPipeReadThread,
THREADSHUTDOWN_WAITTIMEOUT
);
if (waitResult != WAIT_OBJECT_0) {
backgroundThreadFailedToExit = TRUE;
TRC_ERR((TB, L"WaitForSingleObject g_NamedPipeReadThread: %ld", waitResult));
}
CloseHandle( g_NamedPipeReadThread );
}
if (g_hHelpCenterProcess) {
CloseHandle(g_hHelpCenterProcess);
}
if( g_HelpSessionManager != NULL ) {
g_HelpSessionManager.Release();
}
if (g_WaitObjMgr != NULL) {
WTBLOBJ_DeleteWaitableObjectMgr(g_WaitObjMgr);
}
if (g_ProcHandle != NULL) {
CloseHandle(g_ProcHandle);
}
if (g_ClientIsconnectEvent != NULL) {
CloseHandle(g_ClientIsconnectEvent);
}
if (g_VCReadOverlapped.hEvent != NULL) {
CloseHandle(g_VCReadOverlapped.hEvent);
}
if (g_ClientSessionPipe != NULL) {
CloseHandle(g_ClientSessionPipe);
}
if (g_IncomingVCBuf.buffer != NULL) {
FREEMEM(g_IncomingVCBuf.buffer);
}
if (g_ShutdownEvent != NULL) {
CloseHandle(g_ShutdownEvent);
g_ShutdownEvent = NULL;
}
if (g_NamedPipeWriteEvent != NULL) {
CloseHandle(g_NamedPipeWriteEvent);
}
CoUninitialize();
DeleteCriticalSection( &g_cs );
DC_END_FN();
//
// If any of the background threads failed to exit then terminate
// the process.
//
if (backgroundThreadFailedToExit) {
ExitProcess(0);
}
return result;
}
DWORD
SendNullDataToClient(
)
/*++
Routine Description:
sends a null data packet to client.
Only purpose is to find out if the client is still connected; if not we exit the process
REMOTEDESKTOP_RC_CONTROL_CHANNEL channel REMOTEDESKTOP_CTL_RESULT message.
Arguments:
Return Value:
ERROR_SUCCESS on success. Otherwise, an error code is returned.
--*/
{
DC_BEGIN_FN("SendNullDataToClient");
DWORD result;
DWORD bytesWritten = 0;
REMOTEDESKTOP_CTL_ISCONNECTED_PACKET msg;
memcpy(msg.packetHeader.channelName, REMOTEDESKTOP_RC_CONTROL_CHANNEL,
sizeof(REMOTEDESKTOP_RC_CONTROL_CHANNEL));
msg.packetHeader.channelBufHeader.channelNameLen = REMOTEDESKTOP_RC_CHANNELNAME_LENGTH;
#ifdef USE_MAGICNO
msg.packetHeader.channelBufHeader.magicNo = CHANNELBUF_MAGICNO;
#endif
msg.packetHeader.channelBufHeader.dataLen = sizeof(REMOTEDESKTOP_CTL_ISCONNECTED_PACKET) -
sizeof(REMOTEDESKTOP_CTL_PACKETHEADER);
msg.msgHeader.msgType = REMOTEDESKTOP_CTL_ISCONNECTED;
result = SendMsgToClient((PREMOTEDESKTOP_CHANNELBUFHEADER )&msg);
//if we couldn't write all data to the client
//if we could write some data, assume it is still connected
//client probably disconnected
if(result != ERROR_SUCCESS)
result = SAFERROR_SESSIONNOTCONNECTED;
DC_END_FN();
return result;
}
BOOL GetDwordFromRegistry(PDWORD pdwValue)
{
BOOL fSuccess = FALSE;
HKEY hKey = NULL;
if( NULL == pdwValue )
return FALSE;
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) pdwValue,
&dwSize
) == ERROR_SUCCESS) && dwType == REG_DWORD ) {
//
//fall back to default
//
fSuccess = TRUE;
}
}
CLEANUPANDEXIT:
if(NULL != hKey )
RegCloseKey(hKey);
return fSuccess;
}