/************************************************************************* * * shadow.c * * Citrix routines for supporting shadowing * * Copyright Microsoft Corporation, 1998 * * *************************************************************************/ /* * Includes */ #include "precomp.h" #pragma hdrstop #include #include // 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; }