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
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;
|
|
}
|