|
|
/*************************************************************************
* * shadow.c * * Citrix routines for supporting shadowing * * Copyright Microsoft Corporation, 1998 * * *************************************************************************/
/*
* Includes */ #include "precomp.h"
#pragma hdrstop
#include <rpc.h>
#include <winsock.h>
// the highest required size for RDP is 431
#define MODULE_SIZE 512
typedef struct _SHADOW_PARMS { BOOLEAN ShadowerIsHelpSession; // True if the shadow target is being
// shadowed in a Remote Assistance
// scenario.
ULONG ClientLogonId; ULONG ClientShadowId; PWSTR pTargetServerName; ULONG TargetLogonId; WINSTATIONCONFIG2 Config; ICA_STACK_ADDRESS Address; PVOID pModuleData; ULONG ModuleDataLength; PVOID pThinwireData; ULONG ThinwireDataLength; HANDLE ImpersonationToken; WCHAR ClientName[DOMAIN_LENGTH+USERNAME_LENGTH+4]; BOOL fResetShadowMode; } SHADOW_PARMS, *PSHADOW_PARMS;
/*
* External procedures defined */ NTSTATUS WinStationShadowWorker( ULONG, PWSTR, ULONG, BYTE, USHORT ); NTSTATUS WinStationShadowTargetSetupWorker( ULONG ); NTSTATUS WinStationShadowTargetWorker( BOOLEAN, BOOL, ULONG, PWINSTATIONCONFIG2, PICA_STACK_ADDRESS, PVOID, ULONG, PVOID, ULONG, PVOID); NTSTATUS WinStationStopAllShadows( PWINSTATION );
BOOLEAN WINAPI _WinStationShadowTargetSetup( HANDLE hServer, ULONG LogonId );
NTSTATUS WINAPI _WinStationShadowTarget( HANDLE hServer, ULONG LogonId, PWINSTATIONCONFIG2 pConfig, PICA_STACK_ADDRESS pAddress, PVOID pModuleData, ULONG ModuleDataLength, PVOID pThinwireData, ULONG ThinwireDataLength, PVOID pClientName, ULONG ClientNameLength );
NTSTATUS WinStationWinerrorToNtStatus(ULONG ulWinError);
/*
* Internal procedures defined */ NTSTATUS _CreateShadowAddress( ULONG, PWINSTATIONCONFIG2, PWSTR, PICA_STACK_ADDRESS, PICA_STACK_ADDRESS ); NTSTATUS _WinStationShadowTargetThread( PVOID );
NTSTATUS _CheckShadowLoop( IN ULONG ClientLogonId, IN PWSTR pTargetServerName, IN ULONG TargetLogonId );
/*
* External procedures used. */ NTSTATUS RpcCheckClientAccess( PWINSTATION, ACCESS_MASK, BOOLEAN );
NTSTATUS WinStationDoDisconnect( PWINSTATION, PRECONNECT_INFO, BOOLEAN bSyncNotify );
NTSTATUS xxxWinStationQueryInformation(ULONG, WINSTATIONINFOCLASS, PVOID, ULONG, PULONG);
BOOL GetSalemOutbufCount(PDWORD pdwValue);
ULONG UniqueShadowId = 0;
extern WCHAR g_DigProductId[CLIENT_PRODUCT_ID_LENGTH];
/*****************************************************************************
* * WinStationShadowWorker * * Start a Winstation shadow operation * * ENTRY: * ClientLogonId (input) * client of the shadow * pTargetServerName (input) * target server name * TargetLogonId (input) * target login id (where the app is running) * HotkeyVk (input) * virtual key to press to stop shadow * HotkeyModifiers (input) * virtual modifer to press to stop shadow (i.e. shift, control) * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/
NTSTATUS WinStationShadowWorker( IN ULONG ClientLogonId, IN PWSTR pTargetServerName, IN ULONG TargetLogonId, IN BYTE HotkeyVk, IN USHORT HotkeyModifiers ) { PWINSTATION pWinStation; ULONG Length; LONG rc; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; OBJECT_ATTRIBUTES ObjA; HANDLE ClientToken; HANDLE ImpersonationToken; PVOID pModuleData; ULONG ModuleDataLength; PVOID pThinwireData; ULONG ThinwireDataLength; PWINSTATIONCONFIG2 pShadowConfig = NULL; ICA_STACK_ADDRESS ShadowAddress; ICA_STACK_ADDRESS RemoteShadowAddress; ICA_STACK_BROKEN Broken; ICA_STACK_HOTKEY Hotkey; WINSTATION_APIMSG msg; HANDLE hShadowThread; PSHADOW_PARMS pShadowParms; HANDLE hTargetThread; DWORD ThreadId; PVOID pEndpoint; ULONG EndpointLength; LARGE_INTEGER Timeout; LONG retry; NTSTATUS WaitStatus; NTSTATUS TargetStatus; NTSTATUS Status; int nFormattedlength; BOOL bShadowerHelpSession = FALSE;
/*
* Allocate memory */ pShadowConfig = MemAlloc(sizeof(WINSTATIONCONFIG2)); if (pShadowConfig == NULL) { Status = STATUS_NO_MEMORY; return Status; }
/*
* If target server name is ourself, then clear the target name */
if ( pTargetServerName ) { if ( *pTargetServerName ) { WCHAR ServerName[MAX_COMPUTERNAME_LENGTH+1];
Length = MAX_COMPUTERNAME_LENGTH+1; GetComputerName( ServerName, &Length ); if ( !_wcsicmp( ServerName, pTargetServerName ) ) pTargetServerName = NULL; } else { pTargetServerName = NULL; } }
/*
* Verify the target logonid is valid, is currently shadowable, * and that the caller (client) has shadow access. */ if ( pTargetServerName == NULL ) {
Status = WinStationShadowTargetSetupWorker( TargetLogonId );
/*
* Otherwise, open the remote targer server and call the shadow target API. */ } else { HANDLE hServer;
hServer = WinStationOpenServer( pTargetServerName ); if ( hServer == NULL ) { Status = STATUS_OBJECT_NAME_NOT_FOUND; } else { if (_WinStationShadowTargetSetup( hServer, TargetLogonId ) == FALSE) { Status = WinStationWinerrorToNtStatus(GetLastError()); } else { Status = STATUS_SUCCESS; } WinStationCloseServer( hServer ); } }
/*
* Check the status of the setup call. */ if ( !NT_SUCCESS( Status ) ) goto badsetup;
/*
* Find and lock client WinStation */ pWinStation = FindWinStationById( ClientLogonId, FALSE ); if ( pWinStation == NULL ) { Status = STATUS_ACCESS_DENIED; goto badsetup; }
/*
* If WinStation is not in the active state (connected and * a user is logged on), or there is no stack handle, * then deny the shadow request. */ if ( pWinStation->State != State_Active || pWinStation->hStack == NULL ) { Status = STATUS_CTX_SHADOW_INVALID; goto badstate; }
/*
* If shadower is help session, we already disable screen saver on logon notify */ bShadowerHelpSession = TSIsSessionHelpSession(pWinStation, NULL); /*
* Allocate a unique shadow id for this request. * (This is used by the shadow target thread in order * to synchronize the return status.) */ pWinStation->ShadowId = InterlockedIncrement( &UniqueShadowId );
/*
* Set up shadow config structure to use Named Pipe transport driver */ RtlZeroMemory( pShadowConfig, sizeof(WINSTATIONCONFIG2) ); wcscpy( pShadowConfig->Pd[0].Create.PdName, L"namedpipe" ); pShadowConfig->Pd[0].Create.SdClass = SdNetwork; wcscpy( pShadowConfig->Pd[0].Create.PdDLL, L"tdpipe" ); pShadowConfig->Pd[0].Create.PdFlag = PD_TRANSPORT | PD_CONNECTION | PD_FRAME | PD_RELIABLE;
pShadowConfig->Pd[0].Create.OutBufLength = 530; pShadowConfig->Pd[0].Create.OutBufCount = 6; //
//344175 Mouse buffer size needs to be increased
//check if this is a help session, if it is read OutBufCount from registry
//
if (bShadowerHelpSession) { if (!GetSalemOutbufCount((PDWORD)&pShadowConfig->Pd[0].Create.OutBufCount)) { //
//set the default outbuf count to 25
//we don't want any low water mark for help sessions
//
pShadowConfig->Pd[0].Create.OutBufCount = 25; } pShadowConfig->Pd[0].Create.PdFlag |= PD_NOLOW_WATERMARK; //no low water mark
}
pShadowConfig->Pd[0].Create.OutBufDelay = 0; pShadowConfig->Pd[0].Params.SdClass = SdNetwork; pShadowConfig->Pd[1].Create.SdClass = SdNone;
/*
* Use same WD as shadowing WinStation */ pShadowConfig->Wd = pWinStation->Config.Wd;
/*
* Create a shadow address based on the config Pd[0] type. */ Status = _CreateShadowAddress( pWinStation->ShadowId, pShadowConfig, pTargetServerName, &ShadowAddress, &RemoteShadowAddress );
if (!NT_SUCCESS(Status)) { goto badAddress; }
/*
* Now impersonate the client and duplicate the impersonation token * so we can hand it off to the thread doing the target side work. */
/*
* Duplicate our impersonation token to allow the shadow * target thread to use it. */ Status = NtOpenThreadToken( NtCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &ClientToken );
if (!NT_SUCCESS(Status)) { goto badtoken; }
InitializeObjectAttributes( &ObjA, NULL, 0L, NULL, NULL );
SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE;
ObjA.SecurityQualityOfService = &SecurityQualityOfService; Status = NtDuplicateToken( ClientToken, TOKEN_IMPERSONATE, &ObjA, FALSE, TokenImpersonation, &ImpersonationToken );
NtClose( ClientToken );
if (!NT_SUCCESS(Status)) { goto badtoken; }
/*
* Query client module data */
pModuleData = MemAlloc( MODULE_SIZE ); if ( !pModuleData ) { Status = STATUS_NO_MEMORY; goto badwddata; }
// Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hStack, IOCTL_ICA_STACK_QUERY_MODULE_DATA, NULL, 0, pModuleData, MODULE_SIZE, &ModuleDataLength ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
if ( Status == STATUS_BUFFER_TOO_SMALL ) {
MemFree( pModuleData ); pModuleData = MemAlloc( ModuleDataLength ); if ( !pModuleData ) { Status = STATUS_NO_MEMORY; goto badwddata; }
// Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hStack, IOCTL_ICA_STACK_QUERY_MODULE_DATA, NULL, 0, pModuleData, ModuleDataLength, &ModuleDataLength ); } else { Status = STATUS_CTX_SHADOW_INVALID; } }
if ( !NT_SUCCESS( Status ) ) { goto badwddata; }
/*
* Query thinwire module data */ pThinwireData = MemAlloc( MODULE_SIZE ); if ( !pThinwireData ) { Status = STATUS_NO_MEMORY; goto badthinwiredata; }
Status = IcaChannelIoControl( pWinStation->hIcaThinwireChannel, IOCTL_ICA_VIRTUAL_QUERY_MODULE_DATA, NULL, 0, pThinwireData, MODULE_SIZE, &ThinwireDataLength ); if ( Status == STATUS_BUFFER_TOO_SMALL ) {
MemFree( pThinwireData ); pThinwireData = MemAlloc( ThinwireDataLength ); if ( !pThinwireData ) { Status = STATUS_NO_MEMORY; goto badthinwiredata; }
Status = IcaChannelIoControl( pWinStation->hIcaThinwireChannel, IOCTL_ICA_VIRTUAL_QUERY_MODULE_DATA, NULL, 0, pThinwireData, ThinwireDataLength, &ThinwireDataLength ); }
if ( !NT_SUCCESS( Status ) ) { goto badthinwiredata; }
/*
* Create the local passthru stack */ Status = IcaStackOpen( pWinStation->hIca, Stack_Passthru, (PROC)WsxStackIoControl, pWinStation, &pWinStation->hPassthruStack ); if ( !NT_SUCCESS( Status ) ) goto badstackopen;
#ifdef notdef
/*
* Create the client endpoint. * This call will return the ICA_STACK_ADDRESS we bound to, * so we can pass it on to the shadow target routine. */ Status = IcaStackCreateShadowEndpoint( pWinStation->hPassthruStack, pWinStation->ListenName, pShadowConfig, &ShadowAddress, NULL ); if ( !NT_SUCCESS( Status ) ) goto badshadowendpoint; #endif
/*
* Create stack broken event and register it */ Status = NtCreateEvent( &pWinStation->ShadowBrokenEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE ); if ( !NT_SUCCESS( Status ) ) goto badevent; Broken.BrokenEvent = pWinStation->ShadowBrokenEvent;
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: BrokenEvent(%ld) = %p\n", pWinStation->LogonId, pWinStation->ShadowBrokenEvent));
// Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hPassthruStack, IOCTL_ICA_STACK_REGISTER_BROKEN, &Broken, sizeof(Broken), NULL, 0, NULL ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
if ( !NT_SUCCESS(Status) ) goto badbroken;
/*
* Register hotkey */ Hotkey.HotkeyVk = HotkeyVk; Hotkey.HotkeyModifiers = HotkeyModifiers;
// Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hStack, IOCTL_ICA_STACK_REGISTER_HOTKEY, &Hotkey, sizeof(Hotkey), NULL, 0, NULL ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
if ( !NT_SUCCESS(Status) ) goto badhotkey;
/*
* Before we enable passthru mode, change the WinStation state */ pWinStation->State = State_Shadow; NotifySystemEvent( WEVENT_STATECHANGE );
/*
* Tell win32k about passthru mode being enabled */ msg.ApiNumber = SMWinStationPassthruEnable; Status = SendWinStationCommand( pWinStation, &msg, 60 ); if ( !NT_SUCCESS( Status ) ) goto badpassthru;
/*
* Allocate a SHADOW_PARMS struct to pass to the target thread */ pShadowParms = MemAlloc( sizeof(SHADOW_PARMS) ); if ( !pShadowParms ) { Status = STATUS_NO_MEMORY; goto badshadowparms; }
/*
* Create a thread to load the target shadow stack */ pShadowParms->fResetShadowMode = bShadowerHelpSession; // Only reset if client is HelpAssistant session
pShadowParms->ShadowerIsHelpSession = bShadowerHelpSession ? TRUE : FALSE; pShadowParms->ClientLogonId = ClientLogonId; pShadowParms->ClientShadowId = pWinStation->ShadowId; pShadowParms->pTargetServerName = pTargetServerName; pShadowParms->TargetLogonId = TargetLogonId; pShadowParms->Config = *pShadowConfig; pShadowParms->Address = ShadowAddress; pShadowParms->pModuleData = pModuleData; pShadowParms->ModuleDataLength = ModuleDataLength; pShadowParms->pThinwireData = pThinwireData; pShadowParms->ThinwireDataLength = ThinwireDataLength; pShadowParms->ImpersonationToken = ImpersonationToken;
nFormattedlength = _snwprintf(pShadowParms->ClientName, sizeof(pShadowParms->ClientName) / sizeof(WCHAR), L"%s\\%s", pWinStation->Domain, pWinStation->UserName); if (nFormattedlength < 0 || nFormattedlength == sizeof(pShadowParms->ClientName) / sizeof(WCHAR)) { Status = STATUS_INVALID_PARAMETER; goto badClientName; }
pWinStation->ShadowTargetStatus = 0; hTargetThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)_WinStationShadowTargetThread, pShadowParms, THREAD_SET_INFORMATION, &ThreadId ); if ( hTargetThread == NULL ){ Status = STATUS_NO_MEMORY; goto badthread; } pModuleData = NULL; // Target thread will free
pThinwireData = NULL; // Target thread will free
ImpersonationToken = NULL; // Target thread will close
pShadowParms = NULL; // Target thread will free
/*
* Allocate an endpoint buffer */ EndpointLength = MODULE_SIZE; pEndpoint = MemAlloc( MODULE_SIZE ); if ( !pEndpoint ) { Status = STATUS_NO_MEMORY; goto badmalloc; }
/*
* Unlock WinStation while we try to connect to the shadow target */ UnlockWinStation( pWinStation );
/*
* Wait for connection from the shadow target * * We must do this in a loop since we don't know how long it * will take the target side thread to get to the corresponding * IcaStackConnectionWait() call. In between calls, we delay for * 1 second, but break out if the ShadowBrokenEvent gets triggered. */ for ( retry = 0; retry < 35; retry++ ) { ULONG ReturnedLength;
Status = IcaStackConnectionRequest( pWinStation->hPassthruStack, pWinStation->ListenName, pShadowConfig, &RemoteShadowAddress, pEndpoint, EndpointLength, &ReturnedLength ); if ( Status == STATUS_BUFFER_TOO_SMALL ) { MemFree( pEndpoint ); pEndpoint = MemAlloc( ReturnedLength ); if ( !pEndpoint ) { Status = STATUS_NO_MEMORY; break; } EndpointLength = ReturnedLength; Status = IcaStackConnectionRequest( pWinStation->hPassthruStack, pWinStation->ListenName, pShadowConfig, &RemoteShadowAddress, pEndpoint, EndpointLength, &ReturnedLength ); } if ( Status != STATUS_OBJECT_NAME_NOT_FOUND ) break; Timeout = RtlEnlargedIntegerMultiply( 1000, -10000 ); WaitStatus = NtWaitForSingleObject( pWinStation->ShadowBrokenEvent, FALSE, &Timeout ); if ( WaitStatus != STATUS_TIMEOUT ) break; /*
* If the shadow has already completed, we don't need to continue * trying to initiate it */ if (pWinStation->ShadowTargetStatus) { break; } }
/*
* Now relock the WinStation */ RelockWinStation( pWinStation );
/*
* Check the status from the wait for connection */ if ( !NT_SUCCESS( Status ) ) { // The pipe disconnected before the worker thread can set an error
// code. Wait for worker thread to set error code.
if ( Status == STATUS_PIPE_DISCONNECTED ) { UnlockWinStation( pWinStation ); Timeout = RtlEnlargedIntegerMultiply( 10000, -10000 ); WaitStatus = NtWaitForSingleObject( hTargetThread, FALSE, &Timeout ); RelockWinStation( pWinStation ); } if ( pWinStation->ShadowTargetStatus ) { Status = pWinStation->ShadowTargetStatus; } goto badconnect; }
#ifdef notdef
/*
* Now accept the shadow target connection */ // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hPassthruStack, IOCTL_ICA_STACK_OPEN_ENDPOINT, pEndpoint, EndpointLength, NULL, 0, NULL ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
if ( !NT_SUCCESS( Status ) ) goto badaccept; #endif
/*
* Enable I/O for the passthru stack */ // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hPassthruStack, IOCTL_ICA_STACK_ENABLE_IO, NULL, 0, NULL, 0, NULL ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
if ( !NT_SUCCESS(Status) ) goto badenableio;
/*
* Since we don't do the stack query for a shadow stack, * simply call an ioctl to mark the stack as connected now. */ // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hPassthruStack, IOCTL_ICA_STACK_SET_CONNECTED, NULL, 0, NULL, 0, NULL ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
if ( !NT_SUCCESS( Status ) ) goto badsetconnect;
/*
* Wait for shadow broken event to be triggered */ TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Waiting for BrokenEvent(%ld) = %p\n", pWinStation->LogonId, pWinStation->ShadowBrokenEvent));
if( !bShadowerHelpSession ) { /*
* Notify WinLogon shadow Started. */ msg.ApiNumber = SMWinStationNotify; msg.WaitForReply = FALSE; msg.u.DoNotify.NotifyEvent = WinStation_Notify_DisableScrnSaver; Status = SendWinStationCommand( pWinStation, &msg, 0 );
//
// Not critical, just performance issue
//
ASSERT( NT_SUCCESS( Status ) ); }
UnlockWinStation( pWinStation );
Status = NtWaitForSingleObject( pWinStation->ShadowBrokenEvent, FALSE, NULL );
RelockWinStation( pWinStation );
if( !bShadowerHelpSession ) {
/*
* Notify WinLogon shadow Ended. */ msg.ApiNumber = SMWinStationNotify; msg.WaitForReply = FALSE; msg.u.DoNotify.NotifyEvent = WinStation_Notify_EnableScrnSaver; Status = SendWinStationCommand( pWinStation, &msg, 0 );
//
// Not critical, just performance issue
//
ASSERT( NT_SUCCESS( Status ) ); }
/*
* Disable I/O for the passthru stack */ // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hPassthruStack, IOCTL_ICA_STACK_DISABLE_IO, NULL, 0, NULL, 0, NULL ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
ASSERT( NT_SUCCESS( Status ) );
/*
* Tell win32k about passthru mode being disabled */ msg.ApiNumber = SMWinStationPassthruDisable; Status = SendWinStationCommand( pWinStation, &msg, 60 ); TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Passthru mode disabled\n"));
//ASSERT( NT_SUCCESS( Status ) );
/*
* Restore WinStation state */ if ( pWinStation->State == State_Shadow ) { pWinStation->State = State_Active; NotifySystemEvent( WEVENT_STATECHANGE ); }
/*
* Turn off hotkey registration */ RtlZeroMemory( &Hotkey, sizeof(Hotkey) ); if ( pWinStation->hStack ) { // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hStack, IOCTL_ICA_STACK_REGISTER_HOTKEY, &Hotkey, sizeof(Hotkey), NULL, 0, NULL ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
ASSERT( NT_SUCCESS( Status ) ); }
/*
* Close broken event and passthru stack */ NtClose( pWinStation->ShadowBrokenEvent ); pWinStation->ShadowBrokenEvent = NULL;
if ( pWinStation->hPassthruStack ) { IcaStackConnectionClose( pWinStation->hPassthruStack, pShadowConfig, pEndpoint, EndpointLength );
IcaStackClose( pWinStation->hPassthruStack ); pWinStation->hPassthruStack = NULL; }
MemFree( pEndpoint );
/*
* Now give target thread a chance to exit. If it fails to exit within the * allotted time period we just allow it to orphan and close its handle so * it will be destroyed when it finally does exit. This can occur in * highly loaded stress situations and is not part of normal execution. */ TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Waiting for target thread to exit\n")); UnlockWinStation( pWinStation ); Timeout = RtlEnlargedIntegerMultiply( 5000, -10000 ); WaitStatus = NtWaitForSingleObject( hTargetThread, FALSE, &Timeout ); NtClose( hTargetThread ); TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Target thread exit status: %lx\n", WaitStatus));
/*
* Relock WinStation and get target thread exit status */ RelockWinStation( pWinStation ); TargetStatus = pWinStation->ShadowTargetStatus;
/*
* If there is a shadow done event, then signal the waiter now */ if ( pWinStation->ShadowDoneEvent ) SetEvent( pWinStation->ShadowDoneEvent );
/*
* Release winstation */ ReleaseWinStation( pWinStation );
if (pShadowConfig != NULL) { MemFree(pShadowConfig); pShadowConfig = NULL; } return( TargetStatus );
/*=============================================================================
== Error returns =============================================================================*/
badsetconnect: if ( pWinStation->hPassthruStack ) { // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
(void) pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hPassthruStack, IOCTL_ICA_STACK_DISABLE_IO, NULL, 0, NULL, 0, NULL ); } }
badenableio:
#ifdef notdef
badaccept: #endif
if ( pWinStation->hPassthruStack ) { IcaStackConnectionClose( pWinStation->hPassthruStack, pShadowConfig, pEndpoint, EndpointLength ); }
badconnect: if ( pEndpoint ) MemFree( pEndpoint );
badmalloc: UnlockWinStation( pWinStation ); //Timeout = RtlEnlargedIntegerMultiply( 5000, -10000 );
//WaitStatus = NtWaitForSingleObject( hTargetThread, FALSE, &Timeout );
//ASSERT( WaitStatus == STATUS_SUCCESS );
NtClose( hTargetThread );
/*
* Relock WinStation and get target thread exit status */ RelockWinStation( pWinStation ); if ( pWinStation->ShadowTargetStatus ) Status = pWinStation->ShadowTargetStatus;
badthread: badClientName: if ( pShadowParms ) MemFree( pShadowParms );
badshadowparms: msg.ApiNumber = SMWinStationPassthruDisable; SendWinStationCommand( pWinStation, &msg, 60 );
badpassthru: if ( pWinStation->State == State_Shadow ) { pWinStation->State = State_Active; NotifySystemEvent( WEVENT_STATECHANGE ); } RtlZeroMemory( &Hotkey, sizeof(Hotkey) ); // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl && pWinStation->hStack) {
(void) pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pWinStation->hStack, IOCTL_ICA_STACK_REGISTER_HOTKEY, &Hotkey, sizeof(Hotkey), NULL, 0, NULL ); }
badhotkey: badbroken: NtClose( pWinStation->ShadowBrokenEvent ); pWinStation->ShadowBrokenEvent = NULL;
badevent:
#ifdef notdef
badshadowendpoint: #endif
if ( pWinStation->hPassthruStack ) { IcaStackClose( pWinStation->hPassthruStack ); pWinStation->hPassthruStack = NULL; }
badstackopen: badthinwiredata: if ( pThinwireData ) MemFree( pThinwireData ); badwddata: if ( pModuleData ) MemFree( pModuleData ); if ( ImpersonationToken ) NtClose( ImpersonationToken ); badAddress: badtoken: badstate: ReleaseWinStation( pWinStation );
badsetup:
if (pShadowConfig != NULL) { MemFree(pShadowConfig); pShadowConfig = NULL; }
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationShadowWorker, Status=0x%x\n", Status )); return( Status ); }
/*****************************************************************************
* * WinStationShadowTargetSetupWorker * * Setup the target side of a Winstation shadow operation * * ENTRY: * LogonId (input) * client of the shadow * * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/
NTSTATUS WinStationShadowTargetSetupWorker( IN ULONG TargetLogonId ) { PWINSTATION pWinStation; NTSTATUS Status;
/*
* Find and lock target WinStation */ pWinStation = FindWinStationById( TargetLogonId, FALSE ); if ( pWinStation == NULL ) { return( STATUS_ACCESS_DENIED ); }
/*
* Check the target WinStation state. We only allow shadow of * active (connected, logged on) WinStations. */ if ( pWinStation->State != State_Active ) { Status = STATUS_CTX_SHADOW_INVALID; goto shadowinvalid; }
/*
* Stop attempts to shadow an RDP session that is already shadowed. * RDP stacks don't support that yet. * TODO: Add support for multiple RDP shadows. */ if ((pWinStation->Config).Wd.WdFlag & WDF_TSHARE) { if ( !IsListEmpty( &pWinStation->ShadowHead ) ) { Status = STATUS_CTX_SHADOW_DENIED; goto shadowdenied; } }
/*
* Verify that client has WINSTATION_SHADOW access to the target WINSTATION */ Status = RpcCheckClientAccess( pWinStation, WINSTATION_SHADOW, TRUE ); if ( !NT_SUCCESS( Status ) ) goto shadowinvalid;
ReleaseWinStation( pWinStation );
return( STATUS_SUCCESS );
/*=============================================================================
== Error returns =============================================================================*/
shadowinvalid: shadowdenied: ReleaseWinStation( pWinStation );
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationShadowTargetSetupWorker, Status=0x%x\n", Status )); return( Status ); }
/*****************************************************************************
* * WinStationShadowTargetWorker * * Start the target side of a Winstation shadow operation * * ENTRY: * fResetShadowSetting(input) * Reset session shadow class back to original value * ShadowerIsHelpSession * true if the shadowing session is logged in as help assistant. * LogonId (input) * client of the shadow * pConfig (input) * pointer to WinStation config data (for shadow stack) * pAddress (input) * address of shadow client * pModuleData (input) * pointer to client module data * ModuleDataLength (input) * length of client module data * pThinwireData (input) * pointer to thinwire module data * ThinwireDataLength (input) * length of thinwire module data * pClientName (input) * pointer to client name string (domain/username) * * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/
NTSTATUS WinStationShadowTargetWorker( IN BOOLEAN ShadowerIsHelpSession, IN BOOL fResetShadowSetting, IN ULONG TargetLogonId, IN PWINSTATIONCONFIG2 pConfig, IN PICA_STACK_ADDRESS pAddress, IN PVOID pModuleData, IN ULONG ModuleDataLength, IN PVOID pThinwireData, IN ULONG ThinwireDataLength, IN PVOID pClientName)
{ PWINSTATION pWinStation; WINSTATION_APIMSG msg; ULONG ShadowResponse; OBJECT_ATTRIBUTES ObjA; PSHADOW_INFO pShadow; ICA_STACK_BROKEN Broken; NTSTATUS Status, Status2, EndStatus = STATUS_SUCCESS; BOOLEAN fConcurrentLicense = FALSE; DWORD ProtocolMask; BOOLEAN fChainedDD = FALSE; int cchTitle, cchMessage;
ULONG shadowIoctlCode;
HANDLE hIca; HANDLE hStack; PWSEXTENSION pWsx; PVOID pWsxContext;
BOOL bResetStateFlags = FALSE;
HANDLE ChannelHandle;
/*
* Find and lock target WinStation */ pWinStation = FindWinStationById( TargetLogonId, FALSE ); if ( pWinStation == NULL ) {
Status = STATUS_ACCESS_DENIED; goto done; }
//
// save current the console winstation parameters and
// set them to the global values.
//
if (pWinStation->fOwnsConsoleTerminal) { hIca = pWinStation->hIca; hStack = pWinStation->hStack; pWsx = pWinStation->pWsx; pWsxContext = pWinStation->pWsxContext; }
/*
* Check the target WinStation state. We only allow shadow of * active (connected, logged on) WinStations. */ if ( pWinStation->State != State_Active ) {
// the line below is the fix for bug #230870
Status = STATUS_CTX_SHADOW_INVALID; goto shadowinvalid; }
/*
* Check if we are shadowing the same protocol winstation or not. * But let any shadow happen if it's the console and it isn't being shadowed. */ if (!(pWinStation->fOwnsConsoleTerminal && IsListEmpty( &pWinStation->ShadowHead ))) {
ProtocolMask=WDF_ICA|WDF_TSHARE;
if (((pConfig->Wd).WdFlag & ProtocolMask) != ((pWinStation->Config).Wd.WdFlag & ProtocolMask)) { Status=STATUS_CTX_SHADOW_INVALID; goto shadowinvalid; } }
//
// Stop attempts to shadow an RDP session that is already shadowed.
// RDP stacks don't support that yet.
//
if( pWinStation->fOwnsConsoleTerminal || ((pWinStation->Config).Wd.WdFlag & WDF_TSHARE )) { if ( pWinStation->StateFlags & WSF_ST_SHADOW ) { //
// Bug 195616, we release winstation lock when
// waiting for user to accept/deny shadow request,
// another thread can come in and weird thing can
// happen
Status = STATUS_CTX_SHADOW_DENIED; goto shadowdenied; }
pWinStation->StateFlags |= WSF_ST_SHADOW; bResetStateFlags = TRUE; }
/*
* Check shadowing options */ switch ( pWinStation->Config.Config.User.Shadow ) { WCHAR szTitle[32]; WCHAR szMsg2[256]; WCHAR ShadowMsg[256];
/*
* If shadowing is disabled, then deny this request */ case Shadow_Disable :
Status = STATUS_CTX_SHADOW_DISABLED; goto shadowinvalid; break;
/*
* If one of the Notify shadow options is set, * then ask for permission from session being shadowed. * But deny the shadow if this WinStation is currently * disconnected (i.e. there is no user to answer the request). */ case Shadow_EnableInputNotify : case Shadow_EnableNoInputNotify :
if ( pWinStation->State == State_Disconnected ) { Status = STATUS_CTX_SHADOW_INVALID; goto shadowinvalid; }
cchTitle = LoadString( hModuleWin, STR_CITRIX_SHADOW_TITLE, szTitle, sizeof(szTitle)/sizeof(WCHAR));
cchMessage = LoadString( hModuleWin, STR_CITRIX_SHADOW_MSG_2, szMsg2, sizeof(szMsg2)/sizeof(WCHAR));
if ((cchMessage == 0) || (cchMessage == sizeof(szMsg2)/sizeof(WCHAR))) { Status = STATUS_CTX_SHADOW_INVALID; goto shadowinvalid; }
cchMessage = _snwprintf( ShadowMsg, sizeof(ShadowMsg)/sizeof(WCHAR), L" %s %s", pClientName, szMsg2 );
if ((cchMessage <= 0) || (cchMessage == sizeof(ShadowMsg)/sizeof(WCHAR))) { Status = STATUS_CTX_SHADOW_INVALID; goto shadowinvalid; }
/*
* Send message and wait for reply */ msg.u.SendMessage.pTitle = szTitle; msg.u.SendMessage.TitleLength = (cchTitle+1) * sizeof(WCHAR); msg.u.SendMessage.pMessage = ShadowMsg; msg.u.SendMessage.MessageLength = (cchMessage+1) * sizeof(WCHAR); msg.u.SendMessage.Style = MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION; msg.u.SendMessage.Timeout = 30; msg.u.SendMessage.DoNotWait = FALSE; msg.u.SendMessage.pResponse = &ShadowResponse;
msg.ApiNumber = SMWinStationDoMessage;
/*
* Create wait event */ InitializeObjectAttributes( &ObjA, NULL, 0, NULL, NULL ); Status = NtCreateEvent( &msg.u.SendMessage.hEvent, EVENT_ALL_ACCESS, &ObjA, NotificationEvent, FALSE ); if ( !NT_SUCCESS(Status) ) { goto shadowinvalid; }
/*
* Initialize response to IDTIMEOUT */ ShadowResponse = IDTIMEOUT;
/*
* Tell the WinStation to display the message box */ Status = SendWinStationCommand( pWinStation, &msg, 0 );
/*
* Wait for response */ if ( Status == STATUS_SUCCESS ) { TRACE((hTrace,TC_ICASRV,TT_API1, "WinStationSendMessage: wait for response\n" )); UnlockWinStation( pWinStation ); Status = NtWaitForSingleObject( msg.u.SendMessage.hEvent, FALSE, NULL ); if ( !RelockWinStation( pWinStation ) ) { Status = STATUS_CTX_CLOSE_PENDING; } TRACE((hTrace,TC_ICASRV,TT_API1, "WinStationSendMessage: got response %u\n", ShadowResponse )); NtClose( msg.u.SendMessage.hEvent ); } else { /* makarp; close the event in case of SendWinStationCommand failure as well. #182792 */ NtClose( msg.u.SendMessage.hEvent ); }
if ( Status == STATUS_SUCCESS && ShadowResponse != IDYES ) Status = STATUS_CTX_SHADOW_DENIED;
/*
* Check again the target WinStation state as the user could logoff. * We only allow shadow of active (connected, logged on) WinStations. */ if ( Status == STATUS_SUCCESS && pWinStation->State != State_Active ) { Status = STATUS_CTX_SHADOW_INVALID; }
if ( Status != STATUS_SUCCESS ) { goto shadowinvalid; }
break; }
/*
* The shadow request is accepted: for the console session, we now need * to chain in the DD or there won't be much output to shadow */ TRACE((hTrace,TC_ICASRV,TT_API3, "TERMSRV: Logon ID %ld\n", pWinStation->LogonId ));
/*
* If the session is connected to the local console, we need to load * the chained shadow display driver before starting the shadoe sequence */
if (pWinStation->fOwnsConsoleTerminal) {
Status = ConsoleShadowStart( pWinStation, pConfig, pModuleData, ModuleDataLength ); if (NT_SUCCESS(Status)) { fChainedDD = TRUE; TRACE((hTrace,TC_ICASRV,TT_API3, "TERMSRV: success\n")); } else { TRACE((hTrace,TC_ICASRV,TT_API3, "TERMSRV: ConsoleConnect failed 0x%x\n", Status)); goto shadowinvalid; }
}
/*
* Allocate shadow data structure */ pShadow = MemAlloc( sizeof(*pShadow) ); if ( pShadow == NULL ) { Status = STATUS_NO_MEMORY; goto shadowinvalid; }
/*
* Create shadow stack */ Status = IcaStackOpen( pWinStation->hIca, Stack_Shadow, (PROC)WsxStackIoControl, pWinStation, &pShadow->hStack ); if ( !NT_SUCCESS(Status) ) goto badopen;
/*
* Create stack broken event and register it */ Status = NtCreateEvent( &pShadow->hBrokenEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE ); if ( !NT_SUCCESS( Status ) ) goto badevent; Broken.BrokenEvent = pShadow->hBrokenEvent;
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: BrokenEvent(%ld) = %p\n", pWinStation->LogonId, pShadow->hBrokenEvent));
// Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pShadow->hStack, IOCTL_ICA_STACK_REGISTER_BROKEN, &Broken, sizeof(Broken), NULL, 0, NULL ); } else { Status = STATUS_CTX_SHADOW_INVALID; }
if ( !NT_SUCCESS(Status) ) goto badbroken;
/*
* Add the shadow structure to the shadow list for the WinStation */ InsertTailList( &pWinStation->ShadowHead, &pShadow->Links );
/*
* Allocate an endpoint buffer */ pShadow->pEndpoint = MemAlloc( MODULE_SIZE ); if ( pShadow->pEndpoint == NULL ) { Status = STATUS_NO_MEMORY; goto badendpoint; }
/*
* Unlock WinStation while we attempt to connect to the client * side of the shadow. */ UnlockWinStation( pWinStation );
/*
* Connect to client side of shadow */
Status = IcaStackConnectionWait ( pShadow->hStack, pWinStation->ListenName, pConfig, pAddress, pShadow->pEndpoint, MODULE_SIZE, &pShadow->EndpointLength ); if ( Status == STATUS_BUFFER_TOO_SMALL ) { MemFree( pShadow->pEndpoint ); pShadow->pEndpoint = MemAlloc( pShadow->EndpointLength ); if ( pShadow->pEndpoint == NULL ) { Status = STATUS_NO_MEMORY; RelockWinStation( pWinStation ); goto badendpoint; } Status = IcaStackConnectionWait ( pShadow->hStack, pWinStation->ListenName, pConfig, pAddress, pShadow->pEndpoint, pShadow->EndpointLength, &pShadow->EndpointLength ); } if ( !NT_SUCCESS(Status) ) { RelockWinStation( pWinStation ); goto badconnect; }
/*
* Relock the WinStation. * If the WinStation is going away, then bail out. */ if ( !RelockWinStation( pWinStation ) ) { Status = STATUS_CTX_CLOSE_PENDING; goto closing; }
/*
* Now accept the shadow target connection */ // Check for availability
if (pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl) { Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pShadow->hStack, IOCTL_ICA_STACK_OPEN_ENDPOINT, pShadow->pEndpoint, pShadow->EndpointLength, NULL, 0, NULL); } else { Status = STATUS_CTX_SHADOW_INVALID; } if (!NT_SUCCESS(Status)) goto PostCreateConnection;
/*
* If user is configured to permit shadow input, * then enable shadow input on the keyboard/mouse channels, * if not permitting shadow input, disable keyboard/mouse * channels. */ switch ( pWinStation->Config.Config.User.Shadow ) {
case Shadow_EnableInputNotify : case Shadow_EnableInputNoNotify :
shadowIoctlCode = IOCTL_ICA_CHANNEL_ENABLE_SHADOW; break;
case Shadow_EnableNoInputNotify : case Shadow_EnableNoInputNoNotify :
shadowIoctlCode = IOCTL_ICA_CHANNEL_DISABLE_SHADOW; break;
default:
Status = STATUS_INVALID_PARAMETER; } if( !NT_SUCCESS(Status) ) goto PostCreateConnection; Status = IcaChannelOpen( pWinStation->hIca, Channel_Keyboard, NULL, &ChannelHandle );
if( !NT_SUCCESS( Status ) ) goto PostCreateConnection;
Status = IcaChannelIoControl( ChannelHandle, shadowIoctlCode, NULL, 0, NULL, 0, NULL ); ASSERT( NT_SUCCESS( Status ) ); IcaChannelClose( ChannelHandle );
if( !NT_SUCCESS( Status ) ) goto PostCreateConnection;
Status = IcaChannelOpen( pWinStation->hIca, Channel_Mouse, NULL, &ChannelHandle ); if ( !NT_SUCCESS( Status ) ) goto PostCreateConnection;
Status = IcaChannelIoControl( ChannelHandle, shadowIoctlCode, NULL, 0, NULL, 0, NULL ); ASSERT( NT_SUCCESS( Status ) ); IcaChannelClose( ChannelHandle );
if( !NT_SUCCESS( Status ) ) goto PostCreateConnection;
/*
* Tell win32k about the pending shadow operation */
msg.ApiNumber = SMWinStationShadowSetup; Status = SendWinStationCommand( pWinStation, &msg, 60 ); if ( !NT_SUCCESS( Status ) ) goto badsetup;
/*
* Since we don't do the stack query for a shadow stack, * simply call an ioctl to mark the stack as connected now. */ // Check for availability
if (pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl) { Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pShadow->hStack, IOCTL_ICA_STACK_SET_CONNECTED, pModuleData, ModuleDataLength, NULL, 0, NULL); } else { Status = STATUS_CTX_SHADOW_INVALID; }
if (!NT_SUCCESS(Status)) goto badsetconnect;
/*
* Enable I/O for the shadow stack */ // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
Status = pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pShadow->hStack, IOCTL_ICA_STACK_ENABLE_IO, NULL, 0, NULL, 0, NULL );
} else {
Status = STATUS_CTX_SHADOW_INVALID; }
if ( !NT_SUCCESS(Status) ) goto badenableio;
/*
* If this is a help assistant scenario then notify the target winlogon (via Win32k) * that HA shadow is about to commence. */ if (ShadowerIsHelpSession) { msg.ApiNumber = SMWinStationNotify; msg.WaitForReply = TRUE; msg.u.DoNotify.NotifyEvent = WinStation_Notify_HelpAssistantShadowStart; SendWinStationCommand( pWinStation, &msg, 60); }
/*
* Start shadowing */ msg.ApiNumber = SMWinStationShadowStart; msg.u.ShadowStart.pThinwireData = pThinwireData; msg.u.ShadowStart.ThinwireDataLength = ThinwireDataLength; Status = SendWinStationCommand( pWinStation, &msg, 60 ); if ( NT_SUCCESS( Status ) ) {
/*
* Wait for the shadow to be terminated */ UnlockWinStation( pWinStation );
if ( fChainedDD ) { HANDLE hEvents[2];
hEvents[0] = pShadow->hBrokenEvent; hEvents[1] = pWinStation->ShadowDisplayChangeEvent;
Status = NtWaitForMultipleObjects( 2, hEvents, WaitAny, FALSE, NULL ); } else { NtWaitForSingleObject( pShadow->hBrokenEvent, FALSE, NULL ); } RelockWinStation( pWinStation );
if ( fChainedDD && (Status == WAIT_OBJECT_0 + 1) ) {
// valid only if there's one shadower?
NtResetEvent(pWinStation->ShadowDisplayChangeEvent, NULL); EndStatus = STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE; }
/*
* Stop shadowing */ msg.ApiNumber = SMWinStationShadowStop;
Status = SendWinStationCommand( pWinStation, &msg, 60 );
ASSERT( NT_SUCCESS( Status ) ); }
/*
* If this is a help assistant scenario then notify the target winlogon (via Win32k) * that HA shadow is done. */ if (ShadowerIsHelpSession) { msg.ApiNumber = SMWinStationNotify; msg.WaitForReply = FALSE; msg.u.DoNotify.NotifyEvent = WinStation_Notify_HelpAssistantShadowFinish; SendWinStationCommand( pWinStation, &msg, 0); }
/*
* Disable I/O for the shadow stack */ // Check for availability
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxIcaStackIoControl ) {
(void) pWinStation->pWsx->pWsxIcaStackIoControl( pWinStation->pWsxContext, pWinStation->hIca, pShadow->hStack, IOCTL_ICA_STACK_DISABLE_IO, NULL, 0, NULL, 0, NULL ); }
/*
* Do final shadow cleanup */ msg.ApiNumber = SMWinStationShadowCleanup; msg.u.ShadowCleanup.pThinwireData = pThinwireData; msg.u.ShadowCleanup.ThinwireDataLength = ThinwireDataLength; Status2 = SendWinStationCommand( pWinStation, &msg, 60 ); ASSERT( NT_SUCCESS( Status2 ) );
RemoveEntryList( &pShadow->Links );
IcaStackConnectionClose( pShadow->hStack, pConfig, pShadow->pEndpoint, pShadow->EndpointLength );
MemFree( pShadow->pEndpoint );
NtClose( pShadow->hBrokenEvent ); IcaStackClose( pShadow->hStack );
MemFree( pShadow );
/*
* If there is a shadow done event and this was the last shadow, * then signal the waiter now. */ if ( pWinStation->ShadowDoneEvent && IsListEmpty( &pWinStation->ShadowHead ) ) SetEvent( pWinStation->ShadowDoneEvent );
/*
* For the console session, we now need to unchain the DD for * performance reasons. Ignore this return code -- we don't need it and * we also don't want to overwrite the value in Status. */ if (fChainedDD == TRUE) { TRACE((hTrace,TC_ICASRV,TT_API3, "TERMSRV: unchain console DD\n")); pWinStation->Flags |= WSF_DISCONNECT; ConsoleShadowStop( pWinStation );
pWinStation->Flags &= ~WSF_DISCONNECT; fChainedDD = FALSE; }
//
// reset the console winstation parameters.
//
if (pWinStation->fOwnsConsoleTerminal) { pWinStation->hIca = hIca; pWinStation->hStack = hStack; pWinStation->pWsx = pWsx; pWinStation->pWsxContext = pWsxContext; } else if( fResetShadowSetting ) { // Console shadow already reset back to original value
// can't do this in WinStationShadowWorker(), might run into
// some timing problem.
pWinStation->Config.Config.User.Shadow = pWinStation->OriginalShadowClass; }
if( bResetStateFlags ) { pWinStation->StateFlags &= ~WSF_ST_SHADOW; }
/*
* Unlock winstation */ ReleaseWinStation( pWinStation );
if ( NT_SUCCESS(Status) && (EndStatus != STATUS_SUCCESS) ) { Status = EndStatus; }
return( Status );
/*=============================================================================
== Error returns =============================================================================*/
badenableio: badsetconnect: msg.ApiNumber = SMWinStationShadowCleanup; msg.u.ShadowCleanup.pThinwireData = pThinwireData; msg.u.ShadowCleanup.ThinwireDataLength = ThinwireDataLength; SendWinStationCommand( pWinStation, &msg, 60 );
badsetup: PostCreateConnection: closing: IcaStackConnectionClose( pShadow->hStack, pConfig, pShadow->pEndpoint, pShadow->EndpointLength );
badconnect: MemFree( pShadow->pEndpoint );
badendpoint: RemoveEntryList( &pShadow->Links );
badbroken: NtClose( pShadow->hBrokenEvent );
badevent: IcaStackClose( pShadow->hStack );
badopen: MemFree( pShadow );
shadowinvalid: shadowdenied: /*
* For the console session, we now need to unchain the DD for * performance reasons */ if (fChainedDD == TRUE) { TRACE((hTrace,TC_ICASRV,TT_API3, "TERMSRV: unchain console DD\n")); pWinStation->Flags |= WSF_DISCONNECT; /*
* Ignore this return code -- we don't need it and we also don't want * to overwrite the value in Status. */ (void)ConsoleShadowStop( pWinStation ); pWinStation->Flags &= ~WSF_DISCONNECT; fChainedDD = FALSE; }
//
// reset the console winstation parameters.
//
if (pWinStation->fOwnsConsoleTerminal) { pWinStation->hIca = hIca; pWinStation->hStack = hStack; pWinStation->pWsx = pWsx; pWinStation->pWsxContext = pWsxContext; } else if( fResetShadowSetting ) { // Console shadow already reset back to original value
// can't do this in WinStationShadowWorker(), might run into
// some timing problem.
pWinStation->Config.Config.User.Shadow = pWinStation->OriginalShadowClass; }
if( bResetStateFlags ) { pWinStation->StateFlags &= ~WSF_ST_SHADOW; }
ReleaseWinStation( pWinStation );
done: TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: WinStationShadowTarget, Status=0x%x\n", Status )); return Status; }
/*****************************************************************************
* * WinStationStopAllShadows * * Stop all shadow activity for this Winstation * * ENTRY: * pWinStation (input) * pointer to WinStation * * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/
NTSTATUS WinStationStopAllShadows( PWINSTATION pWinStation ) { PLIST_ENTRY Head, Next; NTSTATUS Status;
/*
* If this WinStation is a shadow client, then set the shadow broken * event and create an event to wait on for it the shadow to terminate. */ if ( pWinStation->hPassthruStack ) {
pWinStation->ShadowDoneEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); ASSERT( pWinStation->ShadowDoneEvent );
if ( pWinStation->ShadowBrokenEvent ) { SetEvent( pWinStation->ShadowBrokenEvent ); } }
/*
* If this WinStation is a shadow target, then loop through the * shadow structures and signal the broken event for each one. */ if ( !IsListEmpty( &pWinStation->ShadowHead ) ) {
pWinStation->ShadowDoneEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); ASSERT( pWinStation->ShadowDoneEvent );
Head = &pWinStation->ShadowHead; for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { PSHADOW_INFO pShadow;
pShadow = CONTAINING_RECORD( Next, SHADOW_INFO, Links ); NtSetEvent( pShadow->hBrokenEvent, NULL ); } }
/*
* If a shadow done event was created above, then we'll wait on it * now (for either the shadow client or shadow target to complete). */ if ( pWinStation->ShadowDoneEvent ) {
UnlockWinStation( pWinStation ); Status = WaitForSingleObject( pWinStation->ShadowDoneEvent, 60*1000 ); RelockWinStation( pWinStation );
CloseHandle( pWinStation->ShadowDoneEvent ); pWinStation->ShadowDoneEvent = NULL; }
return( STATUS_SUCCESS ); }
NTSTATUS _CreateShadowAddress( ULONG ShadowId, PWINSTATIONCONFIG2 pConfig, PWSTR pTargetServerName, PICA_STACK_ADDRESS pAddress, PICA_STACK_ADDRESS pRemoteAddress ) { WCHAR ServerName[MAX_COMPUTERNAME_LENGTH+1]; ULONG Length; int nFormattedLength; NTSTATUS Status = STATUS_INVALID_PARAMETER;
RtlZeroMemory( pAddress, sizeof(*pAddress) );
if ( !_wcsicmp( pConfig->Pd[0].Create.PdDLL, L"tdpipe" ) ) { Length = MAX_COMPUTERNAME_LENGTH+1; ServerName[0] = (WCHAR)0; GetComputerName( ServerName, &Length ); *((PWCHAR)pAddress) = (WCHAR)0; nFormattedLength = _snwprintf( (PWSTR)pAddress, sizeof(ICA_STACK_ADDRESS)/sizeof(WCHAR), L"\\??\\Pipe\\Shadowpipe\\%d", ShadowId ); if (nFormattedLength < 0 || nFormattedLength == sizeof(ICA_STACK_ADDRESS)/sizeof(WCHAR)) { return STATUS_INVALID_PARAMETER; } if ( pTargetServerName ) { *((PWCHAR)pRemoteAddress) = (WCHAR)0; nFormattedLength = _snwprintf( (PWSTR)pRemoteAddress, sizeof(ICA_STACK_ADDRESS)/sizeof(WCHAR), L"\\??\\UNC\\%ws\\Pipe\\Shadowpipe\\%d", pTargetServerName, ShadowId ); if (nFormattedLength < 0 || nFormattedLength == sizeof(ICA_STACK_ADDRESS)/sizeof(WCHAR)) { return STATUS_INVALID_PARAMETER; } } else { *pRemoteAddress = *pAddress; } } else if ( !_wcsicmp( pConfig->Pd[0].Create.PdDLL, L"tdnetb" ) ) { *(PUSHORT)pAddress = AF_NETBIOS; GetSystemTimeAsFileTime( (LPFILETIME)((PUSHORT)(pAddress)+1) ); pConfig->Pd[0].Params.Network.LanAdapter = 1; *pRemoteAddress = *pAddress; } else if ( !_wcsicmp( pConfig->Pd[0].Create.PdDLL, L"tdtcp" ) ) { *(PUSHORT)pAddress = AF_INET; *pRemoteAddress = *pAddress; } else if ( !_wcsicmp( pConfig->Pd[0].Create.PdDLL, L"tdipx" ) || !_wcsicmp( pConfig->Pd[0].Create.PdDLL, L"tdspx" ) ) { *(PUSHORT)pAddress = AF_IPX; *pRemoteAddress = *pAddress; } else { return STATUS_INVALID_PARAMETER; }
return( STATUS_SUCCESS ); }
NTSTATUS _WinStationShadowTargetThread( PVOID p ) { PSHADOW_PARMS pShadowParms; HANDLE NullToken; PWINSTATION pWinStation; //DWORD WNetRc;
NTSTATUS Status; NTSTATUS ShadowStatus;
pShadowParms = (PSHADOW_PARMS)p;
/*
* Impersonate the client using the token handle passed to us. */ ShadowStatus = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, (PVOID)&pShadowParms->ImpersonationToken, (ULONG)sizeof(HANDLE) ); ASSERT( NT_SUCCESS( ShadowStatus ) ); if ( !NT_SUCCESS( ShadowStatus ) ) goto impersonatefailed;
/*
* If target server name was not specified, then call the * target worker function directly and avoid the RPC overhead. */ if ( pShadowParms->pTargetServerName == NULL ) {
ShadowStatus = WinStationShadowTargetWorker( pShadowParms->ShadowerIsHelpSession, pShadowParms->fResetShadowMode, pShadowParms->TargetLogonId, &pShadowParms->Config, &pShadowParms->Address, pShadowParms->pModuleData, pShadowParms->ModuleDataLength, pShadowParms->pThinwireData, pShadowParms->ThinwireDataLength, pShadowParms->ClientName); SetLastError(RtlNtStatusToDosError(ShadowStatus));
/*
* Otherwise, open the remote targer server and call the shadow target API. */ } else { HANDLE hServer;
hServer = WinStationOpenServer( pShadowParms->pTargetServerName ); if ( hServer == NULL ) { ShadowStatus = STATUS_OBJECT_NAME_NOT_FOUND; } else { ShadowStatus = _WinStationShadowTarget( hServer, pShadowParms->TargetLogonId, &pShadowParms->Config, &pShadowParms->Address, pShadowParms->pModuleData, pShadowParms->ModuleDataLength, pShadowParms->pThinwireData, pShadowParms->ThinwireDataLength, pShadowParms->ClientName, sizeof(pShadowParms->ClientName) );
if (ShadowStatus != STATUS_SUCCESS) { ShadowStatus = WinStationWinerrorToNtStatus(GetLastError()); }
WinStationCloseServer( hServer ); } }
/*
* Revert back to our threads default token. */ NullToken = NULL; Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, (PVOID)&NullToken, (ULONG)sizeof(HANDLE) ); ASSERT( NT_SUCCESS( Status ) );
impersonatefailed:
/*
* Now find and lock the client WinStation and return the status * from the above call to the client WinStation. */ pWinStation = FindWinStationById( pShadowParms->ClientLogonId, FALSE ); if ( pWinStation != NULL ) { if ( pWinStation->ShadowId == pShadowParms->ClientShadowId ) { pWinStation->ShadowTargetStatus = ShadowStatus; } ReleaseWinStation( pWinStation ); }
NtClose( pShadowParms->ImpersonationToken ); MemFree( pShadowParms->pModuleData ); MemFree( pShadowParms->pThinwireData ); MemFree( pShadowParms );
TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: ShadowTargetThread got: Status=0x%x\n", ShadowStatus ));
return( ShadowStatus ); }
/*****************************************************************************
* * WinStationShadowChangeMode * * Change the mode of the current shadow: interactive/non interactive * * ENTRY: * pWinStation (input/output) * pointer to WinStation * pWinStationShadow (input) * pointer to WINSTATIONSHADOW struct * ulLength (input) * length of the input buffer * * * EXIT: * STATUS_xxx error * ****************************************************************************/
NTSTATUS WinStationShadowChangeMode( PWINSTATION pWinStation, PWINSTATIONSHADOW pWinStationShadow, ULONG ulLength ) { NTSTATUS Status = STATUS_SUCCESS; //assume success
if (ulLength >= sizeof(WINSTATIONSHADOW)) {
//
// If the session is being shadowed then check the new shadow mode
//
if ( pWinStation->State == State_Active && !IsListEmpty(&pWinStation->ShadowHead) ) {
HANDLE ChannelHandle; ULONG IoCtlCode = 0;
switch ( pWinStationShadow->ShadowClass ) {
case Shadow_EnableInputNotify : case Shadow_EnableInputNoNotify : //
// we want input : enable it regardless of current state!
//
IoCtlCode = IOCTL_ICA_CHANNEL_ENABLE_SHADOW; break;
case Shadow_EnableNoInputNotify : case Shadow_EnableNoInputNoNotify : //
// We want no input, disable it.
//
IoCtlCode = IOCTL_ICA_CHANNEL_DISABLE_SHADOW; break;
case Shadow_Disable : Status = STATUS_INVALID_PARAMETER; break;
default: Status = STATUS_INVALID_PARAMETER; break;
}
if ( IoCtlCode != 0 ) { KEYBOARD_INDICATOR_PARAMETERS KbdLeds; NTSTATUS Status2;
Status = IcaChannelOpen( pWinStation->hIca, Channel_Keyboard, NULL, &ChannelHandle );
if ( NT_SUCCESS( Status ) ) {
// if we're re-enabling input, get the leds state on the primary stack...
if ( IoCtlCode == IOCTL_ICA_CHANNEL_ENABLE_SHADOW ) { Status2 = IcaChannelIoControl( ChannelHandle, IOCTL_KEYBOARD_QUERY_INDICATORS, NULL, 0, &KbdLeds, sizeof(KbdLeds), NULL); }
Status = IcaChannelIoControl( ChannelHandle, IoCtlCode, NULL, 0, NULL, 0, NULL );
// and update all stacks with this state.
if ( IoCtlCode == IOCTL_ICA_CHANNEL_ENABLE_SHADOW && NT_SUCCESS( Status ) && NT_SUCCESS( Status2 ) ) {
Status2 = IcaChannelIoControl( ChannelHandle, IOCTL_KEYBOARD_SET_INDICATORS, &KbdLeds, sizeof(KbdLeds), NULL, 0, NULL); }
IcaChannelClose( ChannelHandle ); }
if ( NT_SUCCESS( Status ) ) {
Status = IcaChannelOpen( pWinStation->hIca, Channel_Mouse, NULL, &ChannelHandle );
if ( NT_SUCCESS( Status ) ) {
Status = IcaChannelIoControl( ChannelHandle, IoCtlCode, NULL, 0, NULL, 0, NULL ); IcaChannelClose( ChannelHandle ); } }
}
// Do not update WinStation shadow config, user should not
// be able to bypass what's defined in Group Policy.
}
} else { Status = STATUS_BUFFER_TOO_SMALL; }
return Status; }
/*****************************************************************************
* * _DetectLoop * * Detects a loop by walking the chain of containers. * * ENTRY: * RemoteSessionId (input) * id of the session from where we start the search * pRemoteServerDigProductId (input) * product id of the machine from where we start the search * TargetSessionId (input) * id of the session looked up * pTargetServerDigProductId (input) * product id of the machine looked up * pLocalServerProductId (input) * product id of the local machine * pbLoop (output) * pointer to the result of the search * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/
NTSTATUS _DetectLoop( IN ULONG RemoteSessionId, IN PWSTR pRemoteServerDigProductId, IN ULONG TargetSessionId, IN PWSTR pTargetServerDigProductId, IN PWSTR pLocalServerProductId, OUT BOOL* pbLoop ) { NTSTATUS Status = STATUS_SUCCESS; PWINSTATION pWinStation; WCHAR TmpDigProductId[CLIENT_PRODUCT_ID_LENGTH];
if ( pbLoop == NULL ) return STATUS_INVALID_PARAMETER; else *pbLoop = FALSE;
do {
if ( _wcsicmp( pLocalServerProductId, pRemoteServerDigProductId ) != 0 ) {
// For now limit the search to the local cases.
// Later we can add a RPC call or any other
// mechanism (by instance through the client)
// to get this info from the distant machine.
Status = STATUS_UNSUCCESSFUL;
// The solution could be to RPC the remote machine to get
// the client data for the session id. Then from these data
// we can get the client computer name and the client session id.
// RPC to use: WinStationQueryInformation with information
// class set to WinStationClient.
// No need to add a new RPC call.
} else {
// we're sure that the remote session is on the same server
pWinStation = FindWinStationById( RemoteSessionId, FALSE );
if ( pWinStation != NULL ) { // set the new remote info
RemoteSessionId = pWinStation->Client.ClientSessionId;
memcpy(TmpDigProductId, pWinStation->Client.clientDigProductId, sizeof( TmpDigProductId )); pRemoteServerDigProductId = TmpDigProductId; ReleaseWinStation( pWinStation ); } else { Status = STATUS_ACCESS_DENIED; } }
if( !*pRemoteServerDigProductId ) //older client, can't do anything, allow shadow
break;
if ( Status == STATUS_SUCCESS ) {
if ( (RemoteSessionId == TargetSessionId) && (_wcsicmp( pRemoteServerDigProductId, pTargetServerDigProductId ) == 0) ) {
*pbLoop = TRUE;
} else if ( RemoteSessionId == LOGONID_NONE ) {
// no loop, return success.
break; } } } while ( (*pbLoop == FALSE) && (Status == STATUS_SUCCESS) );
return Status; }
/*****************************************************************************
* * _CheckShadowLoop * * Detects a loop in the shadow. * * ENTRY: pWinStation pointer to the current Winstation * ClientLogonId (input) * client of the shadow * pTargetServerName (input) * target server name * TargetLogonId (input) * target login id (where the app is running) * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ NTSTATUS _CheckShadowLoop( IN ULONG ClientLogonId, IN PWSTR pTargetServerName, IN ULONG TargetLogonId ) { NTSTATUS Status = STATUS_SUCCESS; BOOL bLoop;
WCHAR LocalDigProductId [ CLIENT_PRODUCT_ID_LENGTH ]; WCHAR* pTargetServerDigProductId = NULL; WINSTATIONPRODID WinStationProdId; ULONG len;
memcpy( LocalDigProductId, g_DigProductId, sizeof( LocalDigProductId ));
//get the target's sessionid and digital product id
if ( pTargetServerName == NULL ) { pTargetServerDigProductId = LocalDigProductId; /*
* Otherwise, open the remote targer server and call the shadow target API. */ } else { HANDLE hServer; ZeroMemory( &WinStationProdId, sizeof( WINSTATIONPRODID ));
hServer = WinStationOpenServer( pTargetServerName ); if ( hServer == NULL ) { //ignore errors, we allow shadowing
goto done; } else { //ignore errors
WinStationQueryInformation( hServer, TargetLogonId, WinStationDigProductId, &WinStationProdId, sizeof(WinStationProdId), &len); WinStationCloseServer( hServer ); } pTargetServerDigProductId = WinStationProdId.DigProductId; }
//
// First pass: start from the local session (i.e. the shadow client)
// and walk the chain of containers up to the outtermost session in case
// we reach the target session in the chain.
//
if( *LocalDigProductId && *pTargetServerDigProductId ) {
Status = _DetectLoop( ClientLogonId, LocalDigProductId, TargetLogonId, pTargetServerDigProductId, LocalDigProductId, &bLoop);
if ( Status == STATUS_SUCCESS ) { if (bLoop) { // Status = STATUS_CTX_SHADOW_CIRCULAR;
Status = STATUS_ACCESS_DENIED; goto done; } } //else ignore errors and do the second pass
//
// Second pass: start from the target session (i.e. the shadow target)
// and walk the chain of containers up to the outtermost session in case
// we reach the client session in the chain.
//
Status = _DetectLoop( TargetLogonId, pTargetServerDigProductId, ClientLogonId, LocalDigProductId, LocalDigProductId, &bLoop);
if ( Status == STATUS_SUCCESS ) { if (bLoop) { //Status = STATUS_CTX_SHADOW_CIRCULAR;
Status = STATUS_ACCESS_DENIED; } } else { //else ignore errors and grant shadow
Status = STATUS_SUCCESS; } }
done: return Status; }
/*****************************************************************************
* * GetSalemOutbufCount * * Gets the outbufcount from the registry for the help assistant * * ENTRY: * pdwValue * output where the value is stored * EXIT: * TRUE - no error * ****************************************************************************/
BOOL GetSalemOutbufCount(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, WIN_OUTBUFCOUNT, NULL, &dwType, (PBYTE) pdwValue, &dwSize ) == ERROR_SUCCESS) && dwType == REG_DWORD && *pdwValue > 0) { fSuccess = TRUE; } }
if(NULL != hKey ) RegCloseKey(hKey); return fSuccess; }
|