|
|
//****************************************************************************/
// winsta.c
//
// TermSrv session and session stack related code.
//
// Copyright (C) 1997-2000 Microsoft Corporation
/****************************************************************************/
#include "precomp.h"
#pragma hdrstop
#include "icaevent.h"
#include "tsappcmp.h" // for TermsrvAppInstallMode
#include <msaudite.h>
#include "sessdir.h"
#include <allproc.h>
#include <userenv.h>
#include <winsock2.h>
#include "conntfy.h"
#include "tsremdsk.h"
#include <ws2tcpip.h>
#include <Accctrl.h>
#include <Aclapi.h>
#include "tssec.h"
//
// Autoreconnect security headers
//
#include <md5.h>
#include <hmac.h>
// performance flags
#include "tsperf.h"
// DoS attack Filters
#include "filters.h"
#ifndef MAX_WORD
#define MAX_WORD 0xffff
#endif
//
// SIGN_BYPASS_OPTION #define should be removed before WIN64 SHIPS!!!!!
//
#ifdef _WIN64
#define SIGN_BYPASS_OPTION
#endif
/*
* Local defines */ #define SETUP_REG_PATH L"\\Registry\\Machine\\System\\Setup"
#define REG_WINDOWS_KEY TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion")
#define MAXIMUM_WAIT_WINSTATIONS ((MAXIMUM_WAIT_OBJECTS >> 1) - 1)
#define MAX_STRING_BYTES 512
#define MAX_ALLOWED_PASSWORD_LEN 126
BOOL gbFirtsConnectionThread = TRUE; WINSTATIONCONFIG2 gConsoleConfig; WCHAR g_DigProductId[CLIENT_PRODUCT_ID_LENGTH];
RECONNECT_INFO ConsoleReconnectInfo;
ULONG gLogoffTimeout = 90; /*90 seconds default value for logoff timeout*/
/*
* Globals to support load balancing. Since this is queried frequently we can't * afford to lock the winstation list and count them up. Note that they are * modified only when we have the WinStationListLock to avoid mutual exclusion * issues. */ ULONG WinStationTotalCount = 0; ULONG WinStationDiscCount = 0; LOAD_BALANCING_METRICS gLB; BOOL g_fGetLocalIP = FALSE;
/*
* External procedures defined */
VOID StartAllWinStations(HKEY); NTSTATUS QueueWinStationCreate( PWINSTATIONNAME ); NTSTATUS WinStationCreateWorker(PWINSTATIONNAME pWinStationName, PULONG pLogonId, BOOLEAN fStartWinStation ); VOID WinStationTerminate( PWINSTATION ); VOID WinStationDeleteWorker( PWINSTATION ); NTSTATUS WinStationDoDisconnect( PWINSTATION, PRECONNECT_INFO, BOOLEAN ); NTSTATUS WinStationDoReconnect( PWINSTATION, PRECONNECT_INFO ); BOOL CopyReconnectInfo(PWINSTATION, PRECONNECT_INFO); VOID CleanupReconnect( PRECONNECT_INFO ); NTSTATUS WinStationExceptionFilter( PWSTR, PEXCEPTION_POINTERS ); NTSTATUS IcaWinStationNameFromLogonId( ULONG, PWINSTATIONNAME ); VOID WriteErrorLogEntry( IN NTSTATUS NtStatusCode, IN PVOID pRawData, IN ULONG RawDataLength );
NTSTATUS CheckIdleWinstation(VOID); BOOL IsKernelDebuggerAttached();
/*
* Internal procedures defined */ NTSTATUS WinStationTerminateThread( PVOID ); NTSTATUS WinStationIdleControlThread( PVOID ); NTSTATUS WinStationConnectThread( ULONG ); NTSTATUS WinStationTransferThread( PVOID ); NTSTATUS ConnectSmWinStationApiPort( VOID ); NTSTATUS IcaRegWinStationEnumerate( PULONG, PWINSTATIONNAME, PULONG ); NTSTATUS WinStationStart( PWINSTATION ); NTSTATUS StartWinStationDeviceAndStack(PWINSTATION pWinStation); NTSTATUS WinStationCreateComplete(PWINSTATION pWinStation); BOOL WinStationTerminateProcesses( PWINSTATION, ULONG *pNumTerminated ); VOID WinStationDeleteProc( PREFLOCK ); VOID WinStationZombieProc( PREFLOCK ); NTSTATUS SetRefLockDeleteProc( PREFLOCK, PREFLOCKDELETEPROCEDURE );
VOID WsxBrokenConnection( PWINSTATION ); NTSTATUS TerminateProcessAndWait( HANDLE, HANDLE, ULONG ); VOID ResetAutoReconnectInfo( PWINSTATION ); ULONG WinStationShutdownReset( PVOID ); ULONG WinStationLogoff( PVOID ); NTSTATUS DoForWinStationGroup( PULONG, ULONG, LPTHREAD_START_ROUTINE ); NTSTATUS LogoffWinStation( PWINSTATION, ULONG ); PWINSTATION FindIdleWinStation( VOID );
ULONG CountWinStationType( PWINSTATIONNAME pListenName, BOOLEAN bActiveOnly, BOOLEAN bLockHeld);
NTSTATUS _CloseEndpoint( IN PWINSTATIONCONFIG2 pWinStationConfig, IN PVOID pEndpoint, IN ULONG EndpointLength, IN PWINSTATION pWinStation, IN BOOLEAN bNeedStack );
NTSTATUS _VerifyStackModules(PWINSTATION);
NTSTATUS _ImpersonateClient(HANDLE, HANDLE *);
WinstationRegUnLoadKey(HKEY hKey, LPWSTR lpSubKey);
ULONG WinstationCountUserSessions(PSID, ULONG);
BOOLEAN WinStationCheckConsoleSession(VOID);
NTSTATUS WinStationWinerrorToNtStatus(ULONG ulWinError);
VOID WinStationSetMaxOustandingConnections();
VOID ReadDoSParametersFromRegistry( HKEY hKeyTermSrv );
NTSTATUS GetProductIdFromRegistry( WCHAR* DigProductId, DWORD dwSize );
/*
* External procedures used */ NTSTATUS WinStationInitRPC( VOID ); NTSTATUS WinStationInitLPC( VOID ); RPC_STATUS RegisterRPCInterface( BOOL bReregister ); NTSTATUS WinStationStopAllShadows( PWINSTATION ); VOID NotifySystemEvent( ULONG ); NTSTATUS SendWinStationCommand( PWINSTATION, PWINSTATION_APIMSG, ULONG ); NTSTATUS RpcCheckClientAccess( PWINSTATION, ACCESS_MASK, BOOLEAN ); NTSTATUS WinStationSecurityInit( VOID ); VOID DisconnectTimeout( ULONG LogonId ); PWSEXTENSION FindWinStationExtensionDll( PWSTR, ULONG );
PSECURITY_DESCRIPTOR WinStationGetSecurityDescriptor( PWINSTATION pWinStation );
VOID WinStationFreeSecurityDescriptor( PWINSTATION pWinStation );
NTSTATUS WinStationInheritSecurityDescriptor( PVOID pSecurityDescriptor, PWINSTATION pTargetWinStation );
NTSTATUS ReadWinStationSecurityDescriptor( PWINSTATION pWinStation );
NTSTATUS WinStationKeepAlive();
NTSTATUS WinStationReadRegistryWorker();
void PostErrorValueEvent( unsigned EventCode, DWORD ErrVal);
BOOL Filter_AddOutstandingConnection( IN HANDLE pContext, IN PVOID pEndpoint, IN ULONG EndpointLength, OUT PBYTE pin_addr, OUT PUINT puAddrSize, OUT BOOLEAN *pbBlocked );
BOOL Filter_RemoveOutstandingConnection( IN PBYTE pin_addr, IN UINT uAddrSize );
RTL_GENERIC_COMPARE_RESULTS NTAPI Filter_CompareConnectionEntry( IN struct _RTL_GENERIC_TABLE *Table, IN PVOID FirstInstance, IN PVOID SecondInstance );
RTL_GENERIC_COMPARE_RESULTS NTAPI Filter_CompareFailedConnectionEntry( IN struct _RTL_GENERIC_TABLE *Table, IN PVOID FirstInstance, IN PVOID SecondInstance );
PVOID Filter_AllocateConnectionEntry( IN struct _RTL_GENERIC_TABLE *Table, IN CLONG ByteSize );
PVOID Filter_AllocateConnectionEntry( IN struct _RTL_GENERIC_TABLE *Table, IN CLONG ByteSize );
VOID Filter_FreeConnectionEntry ( IN struct _RTL_GENERIC_TABLE *Table, IN PVOID Buffer );
BOOL FindFirstListeningWinStationName( PWINSTATIONNAMEW pListenName, PWINSTATIONCONFIG2 pConfig );
typedef struct _TRANSFER_INFO { ULONG LogonId; PVOID pEndpoint; ULONG EndpointLength; } TRANSFER_INFO, *PTRANSFER_INFO;
VOID AuditEvent( PWINSTATION pWinstation, ULONG EventId ); VOID AuditShutdownEvent(VOID);
NTSTATUS IsZeroterminateStringW( PWCHAR pwString, DWORD dwLength );
//
//From regapi.dll
//
BOOLEAN RegIsTimeZoneRedirectionEnabled();
/*
* Local variables */ RTL_CRITICAL_SECTION WinStationListLock; RTL_CRITICAL_SECTION WinStationListenersLock; RTL_CRITICAL_SECTION WinStationStartCsrLock; RTL_CRITICAL_SECTION TimerCritSec; RTL_CRITICAL_SECTION WinStationZombieLock; RTL_CRITICAL_SECTION UserProfileLock; RTL_CRITICAL_SECTION ConsoleLock; RTL_RESOURCE WinStationSecurityLock;
// This synchronization counter prevents WinStationIdleControlThread from
// Trying to create a console session when there is not one. There are two
// Situations where we do no want it to create such session:
// - At initialization time before we create session Zero which is the initial
// console session.
// - During reconnect in the window were we just disconnected the console session
// (so there is no console session) but we know we are we going to reconnect
// an other session to the console.
ULONG gConsoleCreationDisable = 1;
LIST_ENTRY WinStationListHead; // protected by WinStationListLock
LIST_ENTRY SystemEventHead; // protected by WinStationListLock
LIST_ENTRY ZombieListHead; ULONG LogonId; LARGE_INTEGER TimeoutZero; HANDLE WinStationEvent = NULL; HANDLE WinStationIdleControlEvent = NULL; HANDLE ConsoleLogoffEvent = NULL; HANDLE g_hMachineGPEvent=NULL; static HANDLE WinStationApiPort = NULL; BOOLEAN StopOnDown = FALSE; HANDLE hTrace = NULL; //BOOLEAN ShutdownTerminateNoWait = FALSE;
ULONG ShutDownFromSessionID = 0;
// IdleWinStationPoolCount made 0 -- change necessiated by DoS attack prevention logic added to TS
ULONG IdleWinStationPoolCount = 0;
ULONG_PTR gMinPerSessionPageCommitMB = 20; #define REG_MIN_PERSESSION_PAGECOMMIT L"MinPerSessionPageCommit"
PVOID glpAddress;
ULONG_PTR gMinPerSessionPageCommit;
typedef struct _TS_OUTSTANDINGCONNECTION { ULONGLONG blockUntilTime; ULONG NumOutStandingConnect; UINT uAddrSize; BYTE addr[16]; struct _TS_OUTSTANDINGCONNECTION *pNext; } TS_OUTSTANDINGCONNECTION, *PTS_OUTSTANDINGCONNECTION;
PTS_OUTSTANDINGCONNECTION g_pBlockedConnections = NULL;
// Table used to keep track of Outstanding connections (DoS)
RTL_GENERIC_TABLE gOutStandingConnections;
RTL_CRITICAL_SECTION FilterLock; ULONG MaxOutStandingConnect; ULONG NumOutStandingConnect; ULONG MaxSingleOutStandingConnect; // maximum number of outstanding connections from a single IP
ULONG DelayConnectionTime = 30*1000;
//
// DoS
// If a bad IP (attacker) has 'MaxFailedConnect' # of failed Pre Auth in 'TimeLimitForFailedConnections' ms, block the IP for 'DoSBlockTime' ms
//
ULONG MaxFailedConnect; // max number of PreAuth failed connections from a single IP
ULONG DoSBlockTime; // Block IP for this much time, if they fail PreAuth simulate 5 mins block for now
ULONG TimeLimitForFailedConnections; ULONG CleanupTimeout; // Timeout to fire a routine to cleanup our Bad IP addr table for DoS
SYSTEMTIME LastLoggedDelayConnection; ULONGLONG LastLoggedBlockedConnection = 0; BOOLEAN gbNeverLoggedDelayConnection = TRUE;
HANDLE hConnectEvent;
BOOLEAN gbWinSockInitialized = FALSE;
/*
* Global data */ extern BOOL g_fAppCompat; extern BOOL g_SafeBootWithNetwork;
RTL_CRITICAL_SECTION g_AuthzCritSection;
extern HANDLE gReadyEventHandle;
extern BOOLEAN RegDenyTSConnectionsPolicy(); //extern BOOLEAN IsPreAuthEnabled(POLICY_TS_MACHINE *p );
extern DWORD WaitForTSConnectionsPolicyChanges( BOOLEAN bWaitForAccept, HANDLE hEvent ); extern void InitializeConsoleClientData( PWINSTATIONCLIENTW pWC );
// defines in REGAPI
extern BOOLEAN RegGetMachinePolicyEx( BOOLEAN forcePolicyRead, FILETIME *pTime , PPOLICY_TS_MACHINE pPolicy );
extern BOOLEAN RegIsMachineInHelpMode();
// Global TermSrv counter values
DWORD g_TermSrvTotalSessions; DWORD g_TermSrvDiscSessions; DWORD g_TermSrvReconSessions;
DWORD g_TermSrvSuccTotalLogons; DWORD g_TermSrvSuccRemoteLogons; DWORD g_TermSrvSuccLocalLogons; DWORD g_TermSrvSuccSession0Logons;
// Global system SID
PSID gSystemSid = NULL; PSID gAdminSid = NULL; PSID gAnonymousSid = NULL;
BOOLEAN g_fDenyTSConnectionsPolicy = 0;
POLICY_TS_MACHINE g_MachinePolicy;
/****************************************************************************/ // IsEmbedded
//
// Service-load-time initialization.
/****************************************************************************/ BOOL IsEmbedded() { static int fResult = -1; if(fResult == -1) { OSVERSIONINFOEX ovix; BOOL b; fResult = 0;
ovix.dwOSVersionInfoSize = sizeof(ovix); b = GetVersionEx((LPOSVERSIONINFO) &ovix); ASSERT(b); if(b && (ovix.wSuiteMask & VER_SUITE_EMBEDDEDNT)) { fResult = 1; } } return (fResult == 1); }
/****************************************************************************/ // InitTermSrv
//
// Service-load-time initialization.
/****************************************************************************/ NTSTATUS InitTermSrv(HKEY hKeyTermSrv) { NTSTATUS Status; DWORD dwLen; DWORD dwType; ULONG szBuffer[MAX_PATH/sizeof(ULONG)]; FILETIME policyTime; WSADATA wsaData;
#define MAX_DEFAULT_CONNECTIONS 50
#define MAX_CONNECT_LOW_THRESHOLD 5
#define MAX_SINGLE_CONNECT_THRESHOLD_DIFF 5
#define MAX_DEFAULT_CONNECTIONS_PRO 3
#define MAX_DEFAULT_SINGLE_CONNECTIONS_PRO 2
ASSERT(hKeyTermSrv != NULL);
g_TermSrvTotalSessions = 0; g_TermSrvDiscSessions = 0; g_TermSrvReconSessions = 0;
g_TermSrvSuccTotalLogons = 0; g_TermSrvSuccRemoteLogons = 0; g_TermSrvSuccLocalLogons = 0; g_TermSrvSuccSession0Logons = 0;
// Set default value for maximum simultaneous connection attempts
WinStationSetMaxOustandingConnections();
NumOutStandingConnect = 0; hConnectEvent = NULL;
ShutdownInProgress = FALSE; //ShutdownTerminateNoWait = FALSE;
ShutDownFromSessionID = 0;
// don't bother saving the policy time, the thread that waits for policy update will save it's own copy at the
// cost of running the 1st time around. Alternatively, I need to use another global var for the policy update value.
RegGetMachinePolicyEx( TRUE, &policyTime, &g_MachinePolicy ); // see if keep alive is required, then IOCTL it to TermDD
WinStationKeepAlive();
Status = RtlInitializeCriticalSection( &FilterLock ); ASSERT( NT_SUCCESS( Status )); if (!NT_SUCCESS(Status)) { goto badFilterLock; }
// Table to keep track of OutStanding Sessions
RtlInitializeGenericTable( &gOutStandingConnections, Filter_CompareConnectionEntry, Filter_AllocateConnectionEntry, Filter_FreeConnectionEntry, NULL );
//
// Following code is for PreAuthenticating client + DoS attack - commented out for now
//
#if 0
// Lock to serialize access to list of sessions which failed PreAuthentication (DoS attack)
Status = RtlInitializeCriticalSection( &DoSLock ); if (!NT_SUCCESS(Status)) { goto badFilterLock; } // Table to keep track of sessions which failed PreAuthentication
RtlInitializeGenericTable( &gFailedConnections, Filter_CompareFailedConnectionEntry, Filter_AllocateConnectionEntry, Filter_FreeConnectionEntry, NULL );
#endif
Status = RtlInitializeCriticalSection( &ConsoleLock ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badConsoleLock; }
Status = RtlInitializeCriticalSection( &UserProfileLock ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badUserProfileLock; }
Status = RtlInitializeCriticalSection( &WinStationListLock ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badWinstationListLock; }
if (gbListenerOff) { Status = RtlInitializeCriticalSection( &WinStationListenersLock ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badWinStationListenersLock; } }
Status = RtlInitializeCriticalSection( &WinStationZombieLock ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badWinStationZombieLock; }
Status = RtlInitializeCriticalSection( &TimerCritSec ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badTimerCritsec; }
Status = RtlInitializeCriticalSection( &g_AuthzCritSection ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badAuthzCritSection; }
InitializeListHead( &WinStationListHead ); InitializeListHead( &SystemEventHead ); InitializeListHead( &ZombieListHead );
Status = InitializeConsoleNotification (); if (!NT_SUCCESS(Status)) { goto badinitStartCsrLock; }
Status = RtlInitializeCriticalSection( &WinStationStartCsrLock ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badinitStartCsrLock; }
Status = LCInitialize( g_bPersonalTS ? LC_INIT_LIMITED : LC_INIT_ALL, g_fAppCompat );
if (!NT_SUCCESS(Status)) { goto badLcInit; }
//
// Listener winstations always get LogonId above 65536 and are
// assigned by Terminal Server. LogonId's for sessions are
// generated by mm in the range 0-65535
//
LogonId = MAX_WORD + 1;
TimeoutZero = RtlConvertLongToLargeInteger( 0 ); Status = NtCreateEvent( &WinStationEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE ); Status = NtCreateEvent( &WinStationIdleControlEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badEvent; }
Status = NtCreateEvent( &ConsoleLogoffEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, TRUE ); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badEvent; }
/*
* Initialize WinStation security */
RtlAcquireResourceExclusive(&WinStationSecurityLock, TRUE); Status = WinStationSecurityInit(); RtlReleaseResource(&WinStationSecurityLock); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badInitSecurity; }
Status = WinStationInitLPC(); ASSERT( NT_SUCCESS( Status ) ); if (!NT_SUCCESS(Status)) { goto badInitLPC; }
//
// Read the registry to determine if maximum outstanding connections
// policy is turned on and the value for it
//
//
// Get MaxOutstandingCon string value
//
dwLen = sizeof(MaxOutStandingConnect); if (RegQueryValueEx(hKeyTermSrv, MAX_OUTSTD_CONNECT, NULL, &dwType, (PCHAR)&szBuffer, &dwLen) == ERROR_SUCCESS) { if (*(PULONG)szBuffer > 0) { MaxOutStandingConnect = *(PULONG)szBuffer; } }
dwLen = sizeof(MaxSingleOutStandingConnect); if (RegQueryValueEx(hKeyTermSrv, MAX_SINGLE_OUTSTD_CONNECT, NULL, &dwType, (PCHAR)&szBuffer, &dwLen) == ERROR_SUCCESS) { if (*(PULONG)szBuffer > 0) { MaxSingleOutStandingConnect = *(PULONG)szBuffer; } }
//ReadDoSParametersFromRegistry(hKeyTermSrv);
dwLen = sizeof(gLogoffTimeout); if (RegQueryValueEx(hKeyTermSrv, LOGOFF_TIMEOUT, NULL, &dwType, (PCHAR)&szBuffer, &dwLen) == ERROR_SUCCESS) { gLogoffTimeout = *(PULONG)szBuffer; }
//
// Read the logoff timeout value. This timeout is used by termsrv to force terminate
// winlogon, if winlogon does not complete logoff after ExitWindows message was sent to him
//
//
// set max number of outstanding connection from single IP
//
if ( MaxOutStandingConnect < MAX_SINGLE_CONNECT_THRESHOLD_DIFF*5) { MaxSingleOutStandingConnect = MaxOutStandingConnect - 1; } else { MaxSingleOutStandingConnect = MaxOutStandingConnect - MAX_SINGLE_CONNECT_THRESHOLD_DIFF; }
//
// Create the connect Event
//
if (MaxOutStandingConnect != 0) { hConnectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (hConnectEvent == NULL) { MaxOutStandingConnect = 0; } }
//
// Initialize winsock
//
// Ask for Winsock version 2.2.
if (WSAStartup(MAKEWORD(2, 2), &wsaData) == 0) { gbWinSockInitialized = TRUE; }
//
// Initialize the Cleanup Timer used to cleanup the Bad IP addr for DoS attacks
// Just create the timer, do not start it now - it will be started when there is a entry in the table
// Not used now but may be turned on at a later state
// Status = IcaTimerCreate( 0, &hCleanupTimer );
return(Status);
/*
* Clean up on failure. Clean up is not implemented for * all cases of failure. However most of it will be done implicitly * by the exit from termsrv process. Failure at this point means anyway * there will be no multi-user feature. */
badInitLPC: // Cleanup code not implemented
badInitSecurity: badEvent: if (WinStationEvent != NULL) NtClose(WinStationEvent); if (WinStationIdleControlEvent != NULL) NtClose(WinStationIdleControlEvent); if (ConsoleLogoffEvent != NULL) NtClose(ConsoleLogoffEvent); badLcInit: RtlDeleteCriticalSection( &WinStationStartCsrLock ); badinitStartCsrLock: RtlDeleteCriticalSection( &TimerCritSec ); badTimerCritsec: badWinStationZombieLock: if (gbListenerOff) { RtlDeleteCriticalSection( &WinStationListenersLock ); } badWinStationListenersLock: RtlDeleteCriticalSection( &WinStationListLock ); badWinstationListLock: RtlDeleteCriticalSection( &UserProfileLock ); badUserProfileLock: RtlDeleteCriticalSection( &ConsoleLock ); badAuthzCritSection: RtlDeleteCriticalSection( &g_AuthzCritSection ); badConsoleLock: RtlDeleteCriticalSection( &FilterLock ); badFilterLock: return Status; }
/*******************************************************************************
* * GroupPOlicyChangeStartSalem() * * This routine is called by timer to start Salem. * * Entry: * None, not using any input parameter, refer to WAITORTIMERCALLBACK * for function declaration. * * *******************************************************************************/ VOID CALLBACK GroupPOlicyChangeStartSalem( PVOID lParm, BOOLEAN TimerOrWait ) { // Policy might change during wait, check if policy still allow
// to get help from local machine, if so, startup Salem.
if( TSIsMachinePolicyAllowHelp() && RegIsMachineInHelpMode() ) { TSStartupSalem(); }
return; }
/*******************************************************************************
* * GroupPolicyNotifyThread * Entry: * nothing * * *******************************************************************************/ DWORD GroupPolicyNotifyThread(DWORD notUsed ) { DWORD dwError; BOOL rc; HANDLE hEvent; BOOLEAN bWaitForAccept; BOOLEAN bSystemStartup;
HANDLE hStartupSalemTimer = NULL; HANDLE hStartupSalemTimerQueue = NULL; BOOL bTimerStatus;
static FILETIME timeOfLastPolicyRead = { 0 , 0 } ;
rc = RegisterGPNotification( g_hMachineGPEvent, TRUE);
if (rc) { hEvent = g_hMachineGPEvent; } else { // TS can still run with the default set of config data, besides
// if there were any machine group policy data, TS got them on the
// last reboot cycle.
//
hEvent = NULL; }
hStartupSalemTimerQueue = CreateTimerQueue(); if( NULL == hStartupSalemTimerQueue ) { // non-critical, we just can't startup Salem to punch firewall or
// ICS port.
DBGPRINT(("TERMSRV: Error %d in CreateTimerQueue\n", GetLastError())); }
//
// At the beginning the listeners are not started.
// So wait (or test) for the connections to be accepted.
//
bWaitForAccept = TRUE; bSystemStartup = TRUE;
//
// Query and set the global flag before entering any wait.
//
g_fDenyTSConnectionsPolicy = RegDenyTSConnectionsPolicy();
while (TRUE) {
dwError = WaitForTSConnectionsPolicyChanges( bWaitForAccept, hEvent );
if( hStartupSalemTimerQueue && (dwError == WAIT_OBJECT_0 || dwError == WAIT_OBJECT_0 + 1) ) { // refer to WaitForTSConnectionsPolicyChanges() for return code.
if( hStartupSalemTimer != NULL ) { // Delete the timer-queue timer and wait for callback to complete
DeleteTimerQueueTimer( hStartupSalemTimerQueue, hStartupSalemTimer, INVALID_HANDLE_VALUE ); hStartupSalemTimer = NULL; }
//
// Delay startup Salem, user might change mind or policy might change especially
// domain policy.
//
bTimerStatus = CreateTimerQueueTimer( &hStartupSalemTimer, hStartupSalemTimerQueue, GroupPOlicyChangeStartSalem, NULL, DELAY_STARTUP_SALEM_TIME, 0, WT_EXECUTEONLYONCE );
if( FALSE == bTimerStatus ) { // non-critical, we just can't startup Salem to punch firewall or
// ICS port.
DBGPRINT(("TERMSRV: Error %d in CreateTimerQueueTimer\n", GetLastError())); } }
//
// Both GP changes and reg changes can affect this one.
//
g_fDenyTSConnectionsPolicy = RegDenyTSConnectionsPolicy();
if (dwError == WAIT_OBJECT_0) {
//
// A change in the TS connections policy has occurred.
//
if (bWaitForAccept) {
// are the connections really accepted?
if (!(g_fDenyTSConnectionsPolicy && !(TSIsMachinePolicyAllowHelp() && RegIsMachineInHelpMode()))) {
// Start the listeners.
if ( bSystemStartup ) { // the first time, start all listeners
StartStopListeners(NULL, TRUE); } else { // after the first time, use this function to start
// listeners only as needed.
WinStationReadRegistryWorker(); }
// Switch to a wait for denied connections.
bWaitForAccept = FALSE;
bSystemStartup = FALSE; }
} else {
// are the connections really denied?
if (g_fDenyTSConnectionsPolicy && !(TSIsMachinePolicyAllowHelp() && RegIsMachineInHelpMode())) { // Stop the listeners.
StartStopListeners(NULL, FALSE);
// Switch to a wait for accepted connections.
bWaitForAccept = TRUE; }
}
} else if (dwError == WAIT_OBJECT_0 + 1) {
//
// We got notified that the GP has changed.
//
if ( RegGetMachinePolicyEx( FALSE, & timeOfLastPolicyRead, &g_MachinePolicy ) ) { // there has been a change, go ahead with the actual updates
WinStationReadRegistryWorker();
// Also update the session directory settings if on an app
// server.
if (!g_bPersonalTS && g_fAppCompat && g_bAdvancedServer) { UpdateSessionDirectory(0); }
RegisterRPCInterface( TRUE ); }
} else {
// should we not do something else?
Sleep( 5000 ); continue; }
}
if (rc) { UnregisterGPNotification(g_hMachineGPEvent); }
if( hStartupSalemTimerQueue ) { if( hStartupSalemTimer != NULL ) { // Delete the timer-queue timer and wait for callback to complete
DeleteTimerQueueTimer( hStartupSalemTimerQueue, hStartupSalemTimer, INVALID_HANDLE_VALUE ); }
DeleteTimerQueueEx( hStartupSalemTimerQueue, NULL ); }
return 0; }
/*******************************************************************************
* * StartAllWinStations * * Get list of configured WinStations from the registry, * start the Console, and then start all remaining WinStations. * * ENTRY: * nothing * * EXIT: * nothing * ******************************************************************************/
void StartAllWinStations(HKEY hKeyTermSrv) { OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING KeyPath; HANDLE KeyHandle; UNICODE_STRING ValueName; #define VALUE_BUFFER_SIZE (sizeof(KEY_VALUE_PARTIAL_INFORMATION) + 256 * sizeof(WCHAR))
CHAR ValueBuffer[VALUE_BUFFER_SIZE]; PKEY_VALUE_PARTIAL_INFORMATION KeyValueInfo; ULONG ValueLength; DWORD ThreadId; NTSTATUS Status; DWORD ValueType; DWORD ValueSize; #define AUTOSTARTTIME 600000
ASSERT(hKeyTermSrv != NULL);
/*
* Initialize the number of idle winstations and gMinPerSessionPageCommitMB, * if this value is in the registry. */ ValueSize = sizeof(IdleWinStationPoolCount); Status = RegQueryValueEx(hKeyTermSrv, REG_CITRIX_IDLEWINSTATIONPOOLCOUNT, NULL, &ValueType, (LPBYTE) &ValueBuffer, &ValueSize ); if ( Status == ERROR_SUCCESS ) { IdleWinStationPoolCount = *(PULONG)ValueBuffer; }
// IdleWinStationPoolCount made 0 -- change necessiated by DoS attack prevention logic added to TS
// Also Idle WinStations are not very useful anymore because CSR and Winlogon is started much later than before
// So set it to Zero here, irrespective of what its value is in the Registry
IdleWinStationPoolCount = 0; //get the product id from registry for use in detecting shadow loop
GetProductIdFromRegistry( g_DigProductId, sizeof( g_DigProductId ) );
//Terminal Service needs to skip Memory check in Embedded images
//when the TS service starts
//bug #246972
if(!IsEmbedded()) { ValueSize = sizeof(gMinPerSessionPageCommitMB); Status = RegQueryValueEx(hKeyTermSrv, REG_MIN_PERSESSION_PAGECOMMIT, NULL, &ValueType, (LPBYTE) &ValueBuffer, &ValueSize ); if ( Status == ERROR_SUCCESS ) { gMinPerSessionPageCommitMB = *(PULONG)ValueBuffer; }
gMinPerSessionPageCommit = gMinPerSessionPageCommitMB * 1024 * 1024; Status = NtAllocateVirtualMemory( NtCurrentProcess(), &glpAddress, 0, &gMinPerSessionPageCommit, MEM_RESERVE, PAGE_READWRITE ); ASSERT( NT_SUCCESS( Status ) );
}
/*
* Open connection to our WinStationApiPort. This will be used to * queue requests to our API thread instead of doing them inline. */ Status = ConnectSmWinStationApiPort(); ASSERT( NT_SUCCESS( Status ) );
/*
* Create Console WinStation first */ Status = WinStationCreateWorker( L"Console", NULL, TRUE ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(("INIT: Failed to create Console WinStation, status=0x%08x\n", Status)); } else { /*
* From now on,WinStationIdleControlThread can create console sessions if needed */ InterlockedDecrement(&gConsoleCreationDisable);
}
/*
* Open the Setup key and look for the valuename "SystemSetupInProgress". * If found and it has a value of TRUE (non-zero), then setup is in * progress and we skip starting WinStations other than the Console. */ RtlInitUnicodeString( &KeyPath, SETUP_REG_PATH ); InitializeObjectAttributes( &ObjectAttributes, &KeyPath, OBJ_CASE_INSENSITIVE, NULL, NULL ); Status = NtOpenKey( &KeyHandle, GENERIC_READ, &ObjectAttributes ); if ( NT_SUCCESS( Status ) ) { RtlInitUnicodeString( &ValueName, L"SystemSetupInProgress" ); KeyValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer; Status = NtQueryValueKey( KeyHandle, &ValueName, KeyValuePartialInformation, (PVOID)KeyValueInfo, VALUE_BUFFER_SIZE, &ValueLength ); NtClose( KeyHandle ); if ( NT_SUCCESS( Status ) ) { ASSERT( ValueLength < VALUE_BUFFER_SIZE ); if ( KeyValueInfo->Type == REG_DWORD && KeyValueInfo->DataLength == sizeof(ULONG) && *(PULONG)(KeyValueInfo->Data) != 0 ) { return; } } }
/*
* Start a policy notfiy thread. * */ { HANDLE hGroupPolicyNotifyThread; DWORD dwID; g_hMachineGPEvent = CreateEvent (NULL, FALSE, FALSE, TEXT("TermSrv: machine GP event"));
if (g_hMachineGPEvent) { hGroupPolicyNotifyThread = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE) GroupPolicyNotifyThread, 0, 0, &dwID); if ( hGroupPolicyNotifyThread ) { NtClose( hGroupPolicyNotifyThread ); } } }
/*
* If necessary, create the thread in charge of the regulation of the idle sessions */ { HANDLE hIdleControlThread = CreateThread( NULL, 0, // use Default stack size of the svchost process
(LPTHREAD_START_ROUTINE)WinStationIdleControlThread, NULL, THREAD_SET_INFORMATION, &ThreadId ); if (hIdleControlThread) { NtClose(hIdleControlThread); } }
/*
* Finally, create the terminate thread */ { HANDLE hTerminateThread = CreateThread( NULL, 0, // use Default stack size of the svchost process
(LPTHREAD_START_ROUTINE)WinStationTerminateThread, NULL, THREAD_SET_INFORMATION, &ThreadId ); if ( hTerminateThread ) NtClose( hTerminateThread ); } }
/*******************************************************************************
* * StartStopListeners * * Get list of configured WinStations from the registry, * and start the WinStations. * * ENTRY: * bStart * if TRUE start the listeners. * if FALSE stop the listeners if no connections related to them exist * anymore. However we do this only on PRO and PER as on a server we * don't mind keeping the listeners. * * EXIT: * nothing * ******************************************************************************/ BOOL StartStopListeners(LPWSTR WinStationName, BOOLEAN bStart) { ULONG i; ULONG WinStationCount, ByteCount; PWINSTATIONNAME pBuffer; PWINSTATIONNAME pWinStationName; PLIST_ENTRY Head, Next; PWINSTATION pWinStation; NTSTATUS Status; BOOL bReturn = FALSE;
if (bStart) {
/*
* Get list of WinStations from registry */ pBuffer = NULL; WinStationCount = 0; Status = IcaRegWinStationEnumerate( &WinStationCount, NULL, &ByteCount ); if ( NT_SUCCESS(Status) ) { pBuffer = pWinStationName = MemAlloc( ByteCount ); WinStationCount = (ULONG) -1; if (pBuffer) { IcaRegWinStationEnumerate( &WinStationCount, pWinStationName, &ByteCount ); } }
/*
* Now create all remaining WinStations defined in registry * Note that every 4th WinStation we do the WinStationCreate inline * instead of queueing it. This is so we don't create an excess * number of API threads right off the bat. */ if ( pBuffer ) { for ( i = 0; i < WinStationCount; i++ ) {
if ( _wcsicmp( pWinStationName, L"Console" ) ) {
if ( i % 4 ) QueueWinStationCreate( pWinStationName ); else { // Don't queue more than 4 at a time
(void) WinStationCreateWorker( pWinStationName, NULL, TRUE ); } } (char *)pWinStationName += sizeof(WINSTATIONNAME); }
MemFree( pBuffer ); }
bReturn = TRUE;
} else {
if ( !gbListenerOff ) { return FALSE; }
ENTERCRIT( &WinStationListenersLock );
// Test if TS connections are denied or not in case we are called from a
// terminate or a disconnect.
if ( g_fDenyTSConnectionsPolicy && // Performance, we only want to check if policy enable help when connection is denied
(!TSIsMachineInHelpMode() || !TSIsMachinePolicyAllowHelp()) ) {
ULONG ulLogonId;
if( WinStationName ) {
// note that this function doesn't handle renamed listeners
WinStationCount = CountWinStationType( WinStationName, TRUE, FALSE );
if ( WinStationCount == 0 ) {
pWinStation = FindWinStationByName( WinStationName, FALSE );
if ( pWinStation ) {
ulLogonId = pWinStation->LogonId;
ReleaseWinStation( pWinStation );
// reset it and don't recreate it
WinStationResetWorker( ulLogonId, TRUE, FALSE, FALSE ); } }
} else {
// terminate all listeners
searchagain: Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) {
pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links );
//
// Only check listening winstations
//
if ( (pWinStation->Flags & WSF_LISTEN) && !(pWinStation->Flags & (WSF_RESET | WSF_DELETE)) ) {
ulLogonId = pWinStation->LogonId;
// note that this function doesn't handle renamed listeners
WinStationCount = CountWinStationType( pWinStation->WinStationName, TRUE, TRUE );
if ( WinStationCount == 0 ) {
LEAVECRIT( &WinStationListLock );
// reset it and don't recreate it
WinStationResetWorker( ulLogonId, TRUE, FALSE, FALSE ); goto searchagain; }
} } LEAVECRIT( &WinStationListLock ); }
bReturn = TRUE;
} LEAVECRIT( &WinStationListenersLock );
}
return bReturn; }
/*******************************************************************************
* WinStationIdleControlThread * * This routine will control the number of idle sessions. ******************************************************************************/ NTSTATUS WinStationIdleControlThread(PVOID Parameter) { ULONG i; NTSTATUS Status = STATUS_SUCCESS; PLIST_ENTRY Head, Next; PWINSTATION pWinStation; ULONG IdleCount = 0; ULONG j; LARGE_INTEGER Timeout; PLARGE_INTEGER pTimeout; ULONG ulSleepDuration; ULONG ulRetries = 0;
ulSleepDuration = 60*1000; // 1 minute
pTimeout = NULL;
/*
* Now create the pool of idle WinStations waiting for a connection. * we need to wait for termsrv to be fully up, otherwise Winlogon will * fail its RPC call to termsrv (WaitForConnectWorker). */
if (gReadyEventHandle != NULL) { WaitForSingleObject(gReadyEventHandle, (DWORD)-1); }
for ( i = 0; i < IdleWinStationPoolCount; i++ ) { (void) WinStationCreateWorker( NULL, NULL, FALSE ); }
Timeout = RtlEnlargedIntegerMultiply( ulSleepDuration, -10000);
if (WinStationIdleControlEvent != NULL) { while (TRUE) {
Status = NtWaitForSingleObject(WinStationIdleControlEvent,FALSE, pTimeout);
if ( !NT_SUCCESS(Status) && (Status != STATUS_TIMEOUT)) { Sleep(1000); // don't eat too much CPU
continue; } pTimeout = &Timeout; /*
* See if we need to create a console session * If if we fail creating a console session, we'll set a timeout so that we will * retry . */ ENTERCRIT( &ConsoleLock );
if (gConsoleCreationDisable == 0) { if (WinStationCheckConsoleSession()) { pTimeout = NULL; } }
LEAVECRIT( &ConsoleLock );
/*
* Now count the number of IDLE WinStations and ensure there * are enough available. */ if (IdleWinStationPoolCount != 0) { ENTERCRIT( &WinStationListLock ); IdleCount = 0; Head = &WinStationListHead; for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( pWinStation->Flags & WSF_IDLE ) IdleCount++; } LEAVECRIT( &WinStationListLock );
for ( j = IdleCount; j < IdleWinStationPoolCount; j++ ) { WinStationCreateWorker( NULL, NULL, FALSE ); } } } } return STATUS_SUCCESS; }
#if _MSC_FULL_VER >= 13008827
#pragma warning(push)
#pragma warning(disable:4715) // Not all control paths return (due to infinite loop)
#endif
/*******************************************************************************
* WinStationTerminateThread * * This routine will wait for WinStation processes to terminate, * and will then reset the corresponding WinStation. ******************************************************************************/ NTSTATUS WinStationTerminateThread(PVOID Parameter) { LONG ThreadIndex = (LONG)(INT_PTR)Parameter; LONG WinStationIndex; PLIST_ENTRY Head, Next; PWINSTATION pWinStation; LONG EventCount; LONG EventIndex, i; int WaitCount; int HandleCount; int HandleArraySize = 0; PHANDLE pHandleArray = NULL; PULONG pIdArray = NULL; ULONG ThreadsNeeded; ULONG ThreadsRunning = 1; ULONG j; NTSTATUS Status; LARGE_INTEGER Timeout; PLARGE_INTEGER pTimeout; ULONG ulSleepDuration; HANDLE DupHandle;
/*
* We need some timer values for the diferent cases of failure in * the thread's loop: * - If we fail creating a new WinstationterminateThread, * then we will do a WaitFormulpipleObjects with a timer instead of a wait without * time out. This will give a new chance to create the thread when timout is over. * if we fail allocating a new buffer to extend handle array, we will wait a timeout * duration before we retry. */
ulSleepDuration = 3*60*1000; Timeout = RtlEnlargedIntegerMultiply( ulSleepDuration, -10000);
/*
* Loop forever waiting for WinStation processes to terminate */ for ( ; ; ) {
/*
* Determine number of WinStations */ pTimeout = NULL; WaitCount = 0; Head = &WinStationListHead; ENTERCRIT( &WinStationListLock ); for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) WaitCount++;
/*
* If there are more than the maximum number of objects that * can be specified to NtWaitForMultipleObjects, then determine * if we must start up additional thread(s). */ if ( WaitCount > MAXIMUM_WAIT_WINSTATIONS ) { ThreadsNeeded = (WaitCount + MAXIMUM_WAIT_WINSTATIONS - 1) / MAXIMUM_WAIT_WINSTATIONS; WaitCount = MAXIMUM_WAIT_WINSTATIONS; if ( ThreadIndex == 0 && ThreadsNeeded > ThreadsRunning ) { LEAVECRIT( &WinStationListLock ); for ( j = ThreadsRunning; j < ThreadsNeeded; j++ ) { DWORD ThreadId; HANDLE Handle;
Handle = CreateThread( NULL, 0, // use Default stack size of the svchost process
(LPTHREAD_START_ROUTINE) WinStationTerminateThread, ULongToPtr( j * MAXIMUM_WAIT_WINSTATIONS ), THREAD_SET_INFORMATION, &ThreadId ); if ( !Handle ) { pTimeout = &Timeout; break; }
// makarp: 182597 - close handle to the thread.
CloseHandle(Handle);
ThreadsRunning++; } ENTERCRIT( &WinStationListLock ); } }
/*
* If we need a larger handle array, then release the * WinStationList lock, allocate the new handle array, * and go start the loop again. */ HandleCount = (WaitCount << 1) + 1; ASSERT( HandleCount < MAXIMUM_WAIT_OBJECTS ); if ( HandleCount > HandleArraySize || HandleCount < HandleArraySize - 10 ) { LEAVECRIT( &WinStationListLock ); if ( pHandleArray ){ MemFree( pHandleArray ); }
pHandleArray = MemAlloc( HandleCount * sizeof(HANDLE) ); if ( pIdArray ) { MemFree( pIdArray ); }
pIdArray = MemAlloc( HandleCount * sizeof(ULONG) );
/* makarp: check for allocation failures #182597 */ if (!pIdArray || !pHandleArray) {
if (pIdArray) { MemFree(pIdArray); pIdArray = NULL; } if (pHandleArray){ MemFree(pHandleArray); pHandleArray = NULL; }
HandleArraySize = 0;
Sleep(ulSleepDuration); continue; }
HandleArraySize = HandleCount; continue; }
/*
* Build list of handles to wait on */ EventCount = 0; pIdArray[EventCount] = 0; pHandleArray[EventCount++] = WinStationEvent; WinStationIndex = 0; for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { if ( WinStationIndex++ < ThreadIndex ) continue; pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( !pWinStation->LogonId ) // no waiting on console
continue; if ( pWinStation->Starting ) continue; if (pWinStation->Terminating) { continue; }
/*
* Do not use handles to subsystem and initial command. These handles may get closed in Winstation * Terminate routine and there is no easy way to synchronize this with Terminate routine using those * handles. So, duplicated those handles. * * NOTE: If the duplication of the handles fails below, the winstation remains unmonitored. */
// Duplicate the subsystem process handle.
DupHandle = NULL; if ( pWinStation->WindowsSubSysProcess ) { Status = NtDuplicateObject( NtCurrentProcess(), pWinStation->WindowsSubSysProcess, NtCurrentProcess(), &DupHandle, 0, 0, DUPLICATE_SAME_ACCESS ); if (NT_SUCCESS(Status)) { pIdArray[EventCount] = pWinStation->LogonId; pHandleArray[EventCount++] = DupHandle; } }
// Duplicate the initial command process handle.
DupHandle = NULL; if ( pWinStation->InitialCommandProcess ) { Status = NtDuplicateObject( NtCurrentProcess(), pWinStation->InitialCommandProcess, NtCurrentProcess(), &DupHandle, 0, 0, DUPLICATE_SAME_ACCESS ); if (NT_SUCCESS(Status)) { pIdArray[EventCount] = pWinStation->LogonId; pHandleArray[EventCount++] = DupHandle; } }
if ( WinStationIndex - ThreadIndex >= WaitCount ) break; }
/*
* Reset WinStationEvent and release the WinStationList lock */ NtResetEvent( WinStationEvent, NULL ); LEAVECRIT( &WinStationListLock );
/*
* Wait for WinStationEvent to trigger (meaning that the * WinStationList has changed), or for one of the existing * Win32 subsystems or initial commands to terminate. */ TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: TerminateThread, Waiting for initial command exit (ArraySize=%d)\n", EventCount ));
Status = NtWaitForMultipleObjects( EventCount, pHandleArray, WaitAny, FALSE, pTimeout );
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: TerminateThread, WaitForMultipleObjects, rc=%x\n", Status ));
/*
* Cleanup the handles array immediately. Close the duplicated handles. Just make sure we are closing a valid handle. * Do not close the handle[0]. It's WinstationEvent handle. */ for ( i = 1; i < EventCount; i++) { if (pHandleArray[i]) { NtClose(pHandleArray[i]); pHandleArray[i] = NULL; } }
if ( !NT_SUCCESS(Status) || Status >= EventCount ) { //WinStationVerifyHandles();
continue; }
/*
* If WinStationEvent triggered, then just go recompute handle list */ if ( (EventIndex = Status) == STATUS_WAIT_0 ) continue;
/*
* Find the WinStation for the process that terminated and * mark it as terminating. This prevents us from waiting * on that WinStation's processes next time through the loop. * (NOTE: The 'Terminating' field is protected by the global * WinStationListLock instead of the WinStation mutex.) */ Head = &WinStationListHead; ENTERCRIT( &WinStationListLock ); for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( pWinStation->LogonId == pIdArray[EventIndex] ) { pWinStation->Terminating = TRUE; break; } }
LEAVECRIT( &WinStationListLock );
/*
* Wake up the WinStationIdleControlThread */ NtSetEvent(WinStationIdleControlEvent, NULL);
/*
* If there are multiple terminate threads, cause the other * threads to wakeup in order to recompute their wait lists. */ NtSetEvent( WinStationEvent, NULL );
/*
* One of the initial command processes has terminated, * queue a request to reset the WinStation. */ QueueWinStationReset( pIdArray[EventIndex]); }
// make the compiler happy
return STATUS_SUCCESS; } #if _MSC_FULL_VER >= 13008827
#pragma warning(pop)
#endif
/*******************************************************************************
* InvalidateTerminateWaitList * * Wakes up WinStationTerminateThread to force it to reinitialize its * wait list. Used when we detect that the initial process was ntsd, * and we change the initial process to WinLogon. * * ENTRY: * The WinStationListLock must not be held. ******************************************************************************/ VOID InvalidateTerminateWaitList(void) { ENTERCRIT( &WinStationListLock ); NtSetEvent( WinStationEvent, NULL ); LEAVECRIT( &WinStationListLock ); }
/*******************************************************************************
* WinStationConnectThread * * This routine will wait for and process incoming connections * for a specified WinStation. ******************************************************************************/ NTSTATUS WinStationConnectThread(ULONG Parameter) { typedef struct _TRANSFERTHREAD { LIST_ENTRY Entry; HANDLE hThread; } TRANSFERTHREAD, *PTRANSFERTHREAD;
LIST_ENTRY TransferThreadList; PTRANSFERTHREAD pTransferThread; PLIST_ENTRY Next;
PWINSTATION pListenWinStation; PVOID pEndpoint = NULL; ULONG EndpointLength; ULONG WinStationCount; ULONG TransferThreadCount; BOOLEAN rc; BOOLEAN bConnectSuccess = FALSE; BOOLEAN bTerminate = FALSE; NTSTATUS Status; SYSTEMTIME currentSystemTime;
#define MODULE_SIZE 1024
#define _WAIT_ERROR_LIMIT 10
ULONG WaitErrorLimit = _WAIT_ERROR_LIMIT; // # of consecutive errors allowed
/*
* Initialize list of transfer threads */ InitializeListHead( &TransferThreadList );
/*
* Find and lock the WinStation */ pListenWinStation = FindWinStationById( Parameter, FALSE ); if ( pListenWinStation == NULL ) { return( STATUS_ACCESS_DENIED ); }
/*
* Ensure only authorized Session Driver and Video Driver stack * modules will be loaded as a result of this connection thread. * * If any module fails verification, mark the WinStation in the * DOWN state and exit WITHOUT error. * * NOTE: * The silent exit is very much intentional so as not to aid in * a third party's attempt to circumvent this security measure. */
Status = _VerifyStackModules( pListenWinStation ); if ( Status != STATUS_SUCCESS ) { pListenWinStation->State = State_Down; ReleaseWinStation( pListenWinStation ); return( STATUS_SUCCESS ); }
/*
* Indicate we got this far successfully. */ pListenWinStation->CreateStatus = STATUS_SUCCESS;
/*
* Load the WinStation extension dll for this WinStation. * Note that we don't save the result in pListenWinStation->pWsx * since we don't want to make callouts to it for the * listen WinStation. */ (VOID) FindWinStationExtensionDll( pListenWinStation->Config.Wd.WsxDLL, pListenWinStation->Config.Wd.WdFlag );
/*
* Do not start accepting client connections before termsrv is totaly UP */ if (gReadyEventHandle != NULL) { WaitForSingleObject(gReadyEventHandle, (DWORD)-1); }
/*
* for perf reason termsrv startup is delayed. We the need to also delay * accepting connections so that if a console logon hapened before termsrv * was up, we get the delayed logon notification before we accept a * client connection. */
if (gbFirtsConnectionThread) { Sleep(5*1000); gbFirtsConnectionThread = FALSE; }
// Get the local IP address enabled for rdp for Session Directory Use
if (!g_bPersonalTS && g_fAppCompat && g_bAdvancedServer && !g_fGetLocalIP) { ULONG LocalIPAddress, LocalIPAddressLength; struct sockaddr_in6 addr6;
Status = IcaStackQueryLocalAddress( pListenWinStation->hStack, pListenWinStation->WinStationName, &pListenWinStation->Config, NULL, (PVOID)&addr6, sizeof(addr6), &LocalIPAddressLength );
if( NT_SUCCESS(Status) ) { g_fGetLocalIP = TRUE; if( AF_INET == addr6.sin6_family ) { struct sockaddr_in* pAddr = (struct sockaddr_in *)&addr6;
LocalIPAddress = pAddr->sin_addr.s_addr; } else { // Support of IPV6 is for next release.
; } if (g_LocalIPAddress != LocalIPAddress) { g_LocalIPAddress = LocalIPAddress; UpdateSessionDirectory(0); } }
}
/*
* Loop waiting for connection requests and passing them off * to an idle WinStation. */ for ( ; ; ) {
/*
* Abort retries if this listener has been terminated */ if ( pListenWinStation->Terminating ) { break; }
/*
* Allocate an endpoint buffer */ pEndpoint = MemAlloc( MODULE_SIZE ); if ( !pEndpoint ) { Status = STATUS_NO_MEMORY;
// Sleep for 30 seconds and try again. Listener thread should not exit
// simply just in low memory condition
UnlockWinStation(pListenWinStation);
Sleep(30000);
if (!RelockWinStation(pListenWinStation)) break;
continue; }
/*
* Unlock listen WinStation while we wait for a connection */ UnlockWinStation( pListenWinStation );
/*
* Check if # outstanding connections reaches max value * If so, wait for the event when the connection # drops * below the max. There is a timeout value of 30 seconds * for the wait */ if (hConnectEvent != NULL) { if (NumOutStandingConnect > MaxOutStandingConnect) { DWORD rc;
// Event log we have exceeded max outstanding connections. but not more than
// once in a day.
GetSystemTime(¤tSystemTime); if ( currentSystemTime.wYear != LastLoggedDelayConnection.wYear || currentSystemTime.wMonth != LastLoggedDelayConnection.wMonth || currentSystemTime.wDay != LastLoggedDelayConnection.wDay || gbNeverLoggedDelayConnection ) { gbNeverLoggedDelayConnection = FALSE; LastLoggedDelayConnection = currentSystemTime; WriteErrorLogEntry(EVENT_TOO_MANY_CONNECTIONS, pListenWinStation->WinStationName, sizeof(pListenWinStation->WinStationName)); }
// manual reset the ConnectEvent before wait
ResetEvent(hConnectEvent); rc = WAIT_TIMEOUT;
// wait for Connect Event for 30 secs
while (rc == WAIT_TIMEOUT) { rc = WaitForSingleObject(hConnectEvent, DelayConnectionTime); if (NumOutStandingConnect <= MaxOutStandingConnect) { break; } if (rc == WAIT_TIMEOUT) { KdPrint(("TermSrv: Reached 30 secs timeout\n")); } else { KdPrint(("TermSrv: WaitForSingleObject return status=%x\n", rc)); } }
} }
/*
* Wait for connection */ Status = IcaStackConnectionWait( pListenWinStation->hStack, pListenWinStation->WinStationName, &pListenWinStation->Config, NULL, pEndpoint, MODULE_SIZE, &EndpointLength );
if ( Status == STATUS_BUFFER_TOO_SMALL ) { MemFree( pEndpoint ); pEndpoint = MemAlloc( EndpointLength ); if ( !pEndpoint ) { Status = STATUS_NO_MEMORY;
// Sleep for 30 seconds and try again. Listener thread should not exit
// simply just in low memory condition
Sleep(30000);
if (!RelockWinStation( pListenWinStation )) break;
continue;
}
Status = IcaStackConnectionWait( pListenWinStation->hStack, pListenWinStation->WinStationName, &pListenWinStation->Config, NULL, pEndpoint, EndpointLength, &EndpointLength ); }
/*
* If ConnectionWait was not successful, * check to see if the consecutive error limit has been reached. */ if ( !NT_SUCCESS( Status ) ) { MemFree( pEndpoint ); pEndpoint = NULL; if (Status == STATUS_SHARING_VIOLATION && _wcsicmp(pListenWinStation->Config.Pd[0].Create.PdName, L"tcp") == 0) { /*
* This status means that our port is opened by some other process. * Lets log an event about this. */ static BOOL bPortTakenEventlogged = FALSE; if (!bPortTakenEventlogged) { PostErrorValueEvent(EVENT_TS_WINSTATION_START_FAILED, WSAEADDRINUSE); bPortTakenEventlogged = TRUE; } }
/*
* If status is DEVICE_DOES_NOT_EXIST, then we want to wait before retrying * otherwise, this prioritary thread will take all the CPU trying 10 times * lo load the listener stack. Such an error takes time to be fixed (either * changing the NIC or going into tscc to have the NIC GUID table updated. */ if ((Status == STATUS_DEVICE_DOES_NOT_EXIST) || (!bConnectSuccess) || (Status == STATUS_INVALID_ADDRESS_COMPONENT) ) {
Sleep(30000); }
if ( WaitErrorLimit--) { if (!RelockWinStation( pListenWinStation )) break;
/*
* If we have had a successful connection, * then skip the stack close/reopen since this would * terminate any existing connections. */ if ( !bConnectSuccess ) { /*
* What we really need is a function to unload the * stack drivers but leave the stack handle open. */ Status = IcaStackClose( pListenWinStation->hStack ); ASSERT( NT_SUCCESS( Status ) ); pListenWinStation->hStack = NULL; Status = IcaStackOpen( pListenWinStation->hIca, Stack_Primary, (PROC)WsxStackIoControl, pListenWinStation, &pListenWinStation->hStack ); if ( !NT_SUCCESS( Status ) ) { pListenWinStation->State = State_Down; break; } }
continue; } else {
// Sleep for 30 seconds and try again. Listener thread should not exit
Sleep(30000);
if (!RelockWinStation( pListenWinStation )) break;
// Reset the error count
WaitErrorLimit = _WAIT_ERROR_LIMIT;
continue; }
} else { bConnectSuccess = TRUE; WaitErrorLimit = _WAIT_ERROR_LIMIT; }
/*
* Check for Shutdown and MaxInstance */ rc = RelockWinStation( pListenWinStation ); if ( !rc ) { Status = _CloseEndpoint( &pListenWinStation->Config, pEndpoint, EndpointLength, pListenWinStation, TRUE ); // Use a temporary stack
MemFree( pEndpoint ); pEndpoint = NULL; break; }
/*
* Reject all connections if a shutdown is in progress */ if ( ShutdownInProgress ) { Status = _CloseEndpoint( &pListenWinStation->Config, pEndpoint, EndpointLength, pListenWinStation, TRUE ); // Use a temporary stack
MemFree( pEndpoint ); pEndpoint = NULL;
continue; }
/*
* Reject all connections if user or the Group-Policy has disabled accepting connections */ if ( g_fDenyTSConnectionsPolicy ) {
//
// Performance, we only want to check if policy enable help when connection is denied
//
if( !TSIsMachineInHelpMode() || !TSIsMachinePolicyAllowHelp() ) { Status = _CloseEndpoint( &pListenWinStation->Config, pEndpoint, EndpointLength, pListenWinStation, TRUE ); // Use a temporary stack
MemFree( pEndpoint ); pEndpoint = NULL;
if ( gbListenerOff ) { //
// if there are no more connections associated
// to this listener, then terminate it.
//
// note that this function doesn't handle renamed listeners
WinStationCount = CountWinStationType( pListenWinStation->WinStationName, TRUE, FALSE );
if ( WinStationCount == 0 ) { bTerminate = TRUE; break; } }
Sleep( 5000 ); // sleep for 5 seconds, defense against
// denial of service attacks.
continue; } }
/*
* Check to see how many transfer threads we have active. * If more than the MaxInstance count, we won't start any more. */ TransferThreadCount = 0; Next = TransferThreadList.Flink; while ( Next != &TransferThreadList ) { pTransferThread = CONTAINING_RECORD( Next, TRANSFERTHREAD, Entry ); Next = Next->Flink;
/*
* If thread is still active, bump the thread count */ if ( WaitForSingleObject( pTransferThread->hThread, 0 ) != 0 ) { TransferThreadCount++;
/*
* Thread has exited, so close the thread handle and free memory */ } else { RemoveEntryList( &pTransferThread->Entry ); CloseHandle( pTransferThread->hThread ); MemFree( pTransferThread ); } }
/*
* If this is not a single-instance connection * and there is a MaxInstance count specified, * then check whether the MaxInstance limit will be exceeded. */ if ( !(pListenWinStation->Config.Pd[0].Create.PdFlag & PD_SINGLE_INST) && pListenWinStation->Config.Create.MaxInstanceCount != (ULONG)-1 ) { ULONG Count;
/*
* Count number of currently active WinStations */ WinStationCount = CountWinStationType( pListenWinStation->WinStationName, FALSE, FALSE );
/*
* Get larger of WinStation and TransferThread count */ Count = max( WinStationCount, TransferThreadCount );
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Count %d\n", Count )); TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: MaxInstanceCount %d\n", pListenWinStation->Config.Create.MaxInstanceCount ));
if ( pListenWinStation->Config.Create.MaxInstanceCount <= Count ) { Status = _CloseEndpoint( &pListenWinStation->Config, pEndpoint, EndpointLength, pListenWinStation, TRUE ); // Use a temporary stack
MemFree( pEndpoint ); pEndpoint = NULL;
continue; } } UnlockWinStation( pListenWinStation );
/*
* Increment the counter of pending connections. */
InterlockedIncrement( &NumOutStandingConnect );
/*
* If this is a single instance connection, * then handle the transfer of the connection endpoint to * an idle WinStation directly. */ if ( pListenWinStation->Config.Pd[0].Create.PdFlag & PD_SINGLE_INST ) { Status = TransferConnectionToIdleWinStation( pListenWinStation, pEndpoint, EndpointLength, NULL ); pEndpoint = NULL;
/*
* If the transfer was successful, then for a single instance * connection, we must now exit the wait for connect loop. */ if ( NT_SUCCESS( Status ) ) { RelockWinStation( pListenWinStation ); break; } else { bConnectSuccess = FALSE; }
/*
* For non-single instance WinStations, let the worker thread * handle the connection handoff so this thread can go back * and listen for another connection immediately. */ } else { PTRANSFER_INFO pInfo; DWORD ThreadId; HANDLE hTransferThread; BOOLEAN bTransferThreadCreated = FALSE;
pInfo = MemAlloc( sizeof(*pInfo) ); pTransferThread = MemAlloc( sizeof(*pTransferThread) );
if (pInfo && pTransferThread) {
pInfo->LogonId = pListenWinStation->LogonId; pInfo->pEndpoint = pEndpoint; pInfo->EndpointLength = EndpointLength;
pTransferThread->hThread = CreateThread( NULL, 0, // use Default stack size of the svchost process
(LPTHREAD_START_ROUTINE)WinStationTransferThread, (PVOID)pInfo, 0, &ThreadId );
if ( pTransferThread->hThread ) { bTransferThreadCreated = TRUE; InsertTailList( &TransferThreadList, &pTransferThread->Entry ); }
}
if (!bTransferThreadCreated) { if (pInfo) { MemFree( pInfo ); }
if (pTransferThread) { MemFree( pTransferThread ); }
TransferConnectionToIdleWinStation( pListenWinStation, pEndpoint, EndpointLength, NULL ); }
pEndpoint = NULL; }
/*
* Relock the listen WinStation */ if (!RelockWinStation( pListenWinStation ) ) break;
} // for - wait for connection
/*
* Clean up the transfer thread list. * (There's no need to wait for them to exit.) */ Next = TransferThreadList.Flink; while ( Next != &TransferThreadList ) { pTransferThread = CONTAINING_RECORD( Next, TRANSFERTHREAD, Entry ); Next = Next->Flink; RemoveEntryList( &pTransferThread->Entry ); CloseHandle( pTransferThread->hThread ); MemFree( pTransferThread ); }
/*
* If after exiting the connect loop above, the WinStation is marked down, * then write the error status to the event log. */ if ( pListenWinStation->State == State_Down ) { ReleaseWinStation( pListenWinStation );
if ( Status != STATUS_CTX_CLOSE_PENDING ) { PostErrorValueEvent(EVENT_TS_LISTNER_WINSTATION_ISDOWN, Status); } } else { /*
* If not a single-instance transport release the WinStation; * otherwise, delete the listener WinStation. */ if (!(pListenWinStation->Config.Pd[0].Create.PdFlag & PD_SINGLE_INST) && !bTerminate) { ReleaseWinStation( pListenWinStation );
} else { /*
* Mark the listen winstation as being deleted. * If a reset/delete operation is already in progress * on this winstation, then don't proceed with the delete. */ if ( pListenWinStation->Flags & (WSF_RESET | WSF_DELETE) ) { ReleaseWinStation( pListenWinStation ); Status = STATUS_CTX_WINSTATION_BUSY; } else { pListenWinStation->Flags |= WSF_DELETE;
/*
* Make sure this WinStation is ready to delete */ WinStationTerminate( pListenWinStation );
/*
* Call the WinStationDelete worker */ WinStationDeleteWorker( pListenWinStation ); } } }
return Status; }
/*******************************************************************************
* WinStationTransferThread ******************************************************************************/ NTSTATUS WinStationTransferThread(PVOID Parameter) { PTRANSFER_INFO pInfo; PWINSTATION pListenWinStation; NTSTATUS Status;
/*
* Find and lock the listen WinStation * (We MUST do this so that it doesn't get deleted while * we are attempting to transfer the new connection.) */ pInfo = (PTRANSFER_INFO)Parameter; pListenWinStation = FindWinStationById( pInfo->LogonId, FALSE ); if ( pListenWinStation == NULL ) { MemFree( pInfo );
if( InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) { if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } }
return( STATUS_ACCESS_DENIED );
}
/*
* Unlock the listen WinStation but hold a reference to it. */ UnlockWinStation( pListenWinStation );
/*
* Transfer the connection to an idle WinStation */ Status = TransferConnectionToIdleWinStation( pListenWinStation, pInfo->pEndpoint, pInfo->EndpointLength, NULL );
/*
* Relock and release the listen WinStation */ RelockWinStation( pListenWinStation ); ReleaseWinStation( pListenWinStation );
MemFree(pInfo);
return Status; }
NTSTATUS TransferConnectionToIdleWinStation( PWINSTATION pListenWinStation, PVOID pEndpoint, ULONG EndpointLength, PICA_STACK_ADDRESS pStackAddress) { PWINSTATION pTargetWinStation = NULL; ULONG ReturnLength; BOOLEAN rc; BOOLEAN CreatedIdle = FALSE; BOOLEAN ConnectionAccepted = FALSE; NTSTATUS Status; ICA_TRACE Trace; LS_STATUS_CODE LlsStatus; NT_LS_DATA LsData;
BOOLEAN bBlockThis;
PWCHAR pListenName; PWINSTATIONCONFIG2 pConfig; BOOL bPolicyAllowHelp;
BYTE in_addr[16]; UINT uAddrSize, index; BOOL bSuccessAdded = FALSE; WINSTATIONNAME szDefaultConfigWinstationName; BOOL bCanCallout;
// error code we need to pass back to client
NTSTATUS StatusCallback = STATUS_SUCCESS;
// Flag to detemine if session is a RA login
BOOL bSessionIsHelpSession; BOOL bValidRAConnect; // Flag to determine if we can Queue the winstation for Reset - used in Error paths
BOOL bQueueForReset = FALSE; BOOL bBlocked = FALSE;
//
// Check AllowGetHelp policy is enabled and salem has pending help session
//
bPolicyAllowHelp = TSIsMachinePolicyAllowHelp() & TSIsMachineInHelpMode();
if( g_fDenyTSConnectionsPolicy && !bPolicyAllowHelp ) { //
// Close the connection if TS policy deny connection and
// help is disabled.
//
TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Denying TS connection due to GP\n")); if ( pListenWinStation && pEndpoint ) { Status = _CloseEndpoint( &pListenWinStation->Config, pEndpoint, EndpointLength, pListenWinStation, TRUE ); // Use a temporary stack
MemFree(pEndpoint); pEndpoint = NULL;
if( InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) { if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } } } return STATUS_CTX_WINSTATION_ACCESS_DENIED; }
//
// check for rejected connections
// This includes many pending Sessions as well as sessions failing PreAuthentication
//
if( pListenWinStation ) { uAddrSize = sizeof( in_addr );
bSuccessAdded = Filter_AddOutstandingConnection( pListenWinStation->hStack, pEndpoint, EndpointLength, in_addr, &uAddrSize, &bBlockThis );
//
// connection blocked, close and exit
//
if ( bBlockThis ) { TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Excessive number of pending connections\n"));
if ( bSuccessAdded ) { Filter_RemoveOutstandingConnection( in_addr, uAddrSize ); } Status = _CloseEndpoint( &pListenWinStation->Config, pEndpoint, EndpointLength, pListenWinStation, TRUE ); // Use a temporary stack
MemFree( pEndpoint ); pEndpoint = NULL;
if( InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) { if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } } return STATUS_CTX_WINSTATION_ACCESS_DENIED; }
// Look to see if this connection is to be blocked because of repeated Failure of PreAuthentication
// No PreAuthentication for now - this code will be needed later when we add PreAuthentication feature
#if 0
ENTERCRIT( &DoSLock ); bBlocked = Filter_CheckIfBlocked( in_addr, uAddrSize ) ; LEAVECRIT( &DoSLock ); if (bBlocked) {
TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Excessive number of connections which failed PreAuthentication. \n")); if ( bSuccessAdded ) { Filter_RemoveOutstandingConnection( in_addr, uAddrSize ); } Status = _CloseEndpoint( &pListenWinStation->Config, pEndpoint, EndpointLength, pListenWinStation, TRUE ); // Use a temporary stack
MemFree( pEndpoint ); pEndpoint = NULL; if( InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) {
if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } } return STATUS_CTX_WINSTATION_ACCESS_DENIED; }
#endif
} else { // Make sure variable
bBlockThis = FALSE; bSuccessAdded = FALSE; }
//
//
/*
* Now find an idle WinStation to transfer this connection to. * If there is not one available, then we attempt to create one. * if this also fails, then we have no choice but to close * the connection endpoint and wait again. */ pTargetWinStation = FindIdleWinStation(); if ( pTargetWinStation == NULL ) { /*
* Create another idle WinStation but do not start it as yet */ Status = WinStationCreateWorker( NULL, NULL, FALSE ); if ( NT_SUCCESS( Status ) ) CreatedIdle = TRUE;
pTargetWinStation = FindIdleWinStation(); if ( pTargetWinStation == NULL ) { TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Could not get an idle WinStation!\n")); goto releaseresources; } }
ASSERT( pTargetWinStation->Flags & WSF_IDLE ); ASSERT( pTargetWinStation->WinStationName[0] == UNICODE_NULL );
if ( pListenWinStation ) { pConfig = &(pListenWinStation->Config); pListenName = pListenWinStation->WinStationName; } else { //
// For Whistler, callback is only for Salem, we can pick
// configuration from any listening winstation as
// 1) All we need is HelpAssistant logon/shadow right, which
// is already in default.
// 2) No listening winstation, system is either no pending help
// or not allow to get help, so we need to bail out.
// 3) Additional check at the bottom to make sure login
// from callback is HelpAssistant only.
//
// If we going support this for the general case, we need
// to take a default configuration, make connection and issue
// a new IOCTL call into tdtcp.sys to determine NIC card/IP address
// that establish connection and from there, map to right winstation
// configuration.
//
// Setup initial callback configuration, this is only
// heruristic, we will reset the configuration after
// determine which NIC is used to connect to TS client
//
bCanCallout = FindFirstListeningWinStationName( szDefaultConfigWinstationName, &pTargetWinStation->Config );
if( FALSE == bCanCallout ) { // If no listening thread, connection is not active, don't allow
// callback
Status = STATUS_ACCESS_DENIED; // It's ok to go to releaseresources even if pConfig is not set
// because in this case pListenWinStation and pEndpoint are NULL.
goto releaseresources; } pListenName = szDefaultConfigWinstationName; pConfig = &(pTargetWinStation->Config); }
/*
* Check for MaxInstance */ if ( !(pConfig->Pd[0].Create.PdFlag & PD_SINGLE_INST) ) { ULONG Count;
Count = CountWinStationType( pListenName, FALSE, FALSE );
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Count %d\n", Count )); TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: MaxInstanceCount %d\n", pConfig->Create.MaxInstanceCount));
if ( pConfig->Create.MaxInstanceCount <= Count ) { TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Exceeded maximum instance count [%ld >= %ld]\n", Count, pConfig->Create.MaxInstanceCount)); goto releaseresources; } }
/*
* Copy the listen name to the target WinStation. * This is done to enable tracing on the new stack. * * Also, this is done BEFORE the connection accept so that if the * listen WinStation is reset before the accept completes, the * target WinStation will also be reset. */ RtlCopyMemory( pTargetWinStation->ListenName, pListenName, sizeof(pTargetWinStation->ListenName) );
/*
* Enable trace */ RtlZeroMemory( &Trace , sizeof( ICA_TRACE ) ); InitializeTrace( pTargetWinStation, FALSE, &Trace );
/*
* Hook extensions for this target */ pTargetWinStation->pWsx = FindWinStationExtensionDll( pConfig->Wd.WsxDLL, pConfig->Wd.WdFlag );
/*
* Initialize winstation extension context structure */ if (pTargetWinStation->pWsx && pTargetWinStation->pWsx->pWsxWinStationInitialize) { Status = pTargetWinStation->pWsx->pWsxWinStationInitialize( &pTargetWinStation->pWsxContext); if (Status != STATUS_SUCCESS) { TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: WsxWinStationInitialize failed [%lx]\n", Status)); goto badconnect; } }
/*
* Terminate Listen stack of single-instance transports now, so that * the underlying CancelIo doesn't disturb the Accept stack. */ // this one can prevent from generalizing efficiently the transfer function
if (pListenWinStation && (pListenWinStation->Config.Pd[0].Create.PdFlag & PD_SINGLE_INST)) { IcaStackTerminate(pListenWinStation->hStack); }
/*
* Change state to ConnectQuery while we try to accept the connection */ pTargetWinStation->State = State_ConnectQuery; NotifySystemEvent(WEVENT_STATECHANGE);
/*
* Since the ConnectionAccept may take a while, we have to unlock * the target WinStation before the call. However, we set the * WSF_IDLEBUSY flag so that the WinStation does not appear idle. */ pTargetWinStation->Flags |= WSF_IDLEBUSY; UnlockWinStation( pTargetWinStation );
if ( !pListenWinStation && pStackAddress) {
// must have extension DLL loaded
if( !pTargetWinStation->pWsx || !pTargetWinStation->pWsx->pWsxIcaStackIoControl ) { Status = STATUS_UNSUCCESSFUL; goto badconnect; }
//
// Allocate an endpoint buffer
//
EndpointLength = MODULE_SIZE; pEndpoint = MemAlloc( MODULE_SIZE ); if ( !pEndpoint ) { Status = STATUS_NO_MEMORY; goto badconnect; }
Status = IcaStackConnectionRequest( pTargetWinStation->hStack, pTargetWinStation->ListenName, pConfig, pStackAddress, pEndpoint, EndpointLength, &ReturnLength );
if ( Status == STATUS_BUFFER_TOO_SMALL ) { MemFree( pEndpoint ); pEndpoint = MemAlloc( ReturnLength ); if ( !pEndpoint ) { Status = STATUS_NO_MEMORY; goto badconnect; } EndpointLength = ReturnLength;
Status = IcaStackConnectionRequest( pTargetWinStation->hStack, pTargetWinStation->ListenName, pConfig, pStackAddress, pEndpoint, EndpointLength, &ReturnLength ); }
if ( !NT_SUCCESS(Status) ) { // special error code to pass back to client
StatusCallback = Status; goto badconnect; }
}
/*
* Now accept the connection for the target WinStation * using the new endpoint. */ Status = IcaStackConnectionAccept(pTargetWinStation->hIca, pTargetWinStation->hStack, pListenName, pConfig, pEndpoint, EndpointLength, NULL, 0, &Trace);
ConnectionAccepted = (Status == STATUS_SUCCESS);
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: IcaStackConnectionAccept, Status=0x%x\n", Status));
if (NT_SUCCESS(Status)) { // In post-logon SD work, quering load balancing info has been moved
// to WinStationNotifyLogonWorker
// while this part of getting load balancing info is still here
// because we rely on it to connect to the console
TS_LOAD_BALANCE_INFO LBInfo; ULONG ReturnLength; // Get the client load balance capability info.
// Note we use _IcaStackIoControl() instead of IcaStackIoControl() or
// WsxIcaStackIoControl() since we still have the stack lock.
memset(&LBInfo, 0, sizeof(LBInfo)); Status = _IcaStackIoControl(pTargetWinStation->hStack, IOCTL_TS_STACK_QUERY_LOAD_BALANCE_INFO, NULL, 0, &LBInfo, sizeof(LBInfo), &ReturnLength);
// On non-success, we'll have FALSE for all of our entries, on
// success valid values. So, save off the cluster info into the
// WinStation struct now.
pTargetWinStation->bClientSupportsRedirection = LBInfo.bClientSupportsRedirection; pTargetWinStation->bRequestedSessionIDFieldValid = LBInfo.bRequestedSessionIDFieldValid; pTargetWinStation->bClientRequireServerAddr = LBInfo.bClientRequireServerAddr; pTargetWinStation->RequestedSessionID = LBInfo.RequestedSessionID;
/*
* Attempt to license the client. Failure is fatal, do not let the * connection continue. */
LCAssignPolicy(pTargetWinStation);
Status = LCProcessConnectionProtocol(pTargetWinStation);
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: LCProcessConnectionProtocol, LogonId=%d, Status=0x%x\n", pTargetWinStation->LogonId, Status));
// The stack was locked from successful IcaStackConnectionAccept(),
// unlock it now.
IcaStackUnlock(pTargetWinStation->hStack); }
/*
* Now relock the target WinStation */ rc = RelockWinStation(pTargetWinStation);
/*
* If the connection accept was not successful, * then we have no choice but to close the connection endpoint * and go back and wait for another connection. */ if (!NT_SUCCESS(Status) || !rc) { TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Connection attempt failed, Status [%lx], rc [%lx]\n", Status, rc)); goto badconnect; }
/*
* Initialize client data */ pTargetWinStation->Client.ClientSessionId = LOGONID_NONE; ZeroMemory( pTargetWinStation->Client.clientDigProductId, sizeof( pTargetWinStation->Client.clientDigProductId )); pTargetWinStation->Client.PerformanceFlags = TS_PERF_DISABLE_NOTHING;
// Reset the client ActiveInputLocale info
pTargetWinStation->Client.ActiveInputLocale = 0;
if ( pTargetWinStation->pWsx && pTargetWinStation->pWsx->pWsxIcaStackIoControl ) { (void) pTargetWinStation->pWsx->pWsxIcaStackIoControl( pTargetWinStation->pWsxContext, pTargetWinStation->hIca, pTargetWinStation->hStack, IOCTL_ICA_STACK_QUERY_CLIENT, NULL, 0, &pTargetWinStation->Client, sizeof(pTargetWinStation->Client), &ReturnLength ); }
//
// Clear helpassistant specific bits to indicate we still not sure
// login user is a help assistant
//
pTargetWinStation->StateFlags &= ~WSF_ST_HELPSESSION_FLAGS; bSessionIsHelpSession = TSIsSessionHelpSession( pTargetWinStation, &bValidRAConnect );
/*
* We have to start this new WinStation. * Before doing so, lets see if GP or WMI requires us to pre-authenticate this client connection * No Pre Auth if this is a RA Session */
// Note : g_PreAuthenticateClient is hard-coded to FALSE now -- we will call IsPreAuthEnabled later on when Preauth feature is included
// So the block below will not be entered for now
// g_PreAuthenticateClient = IsPreAuthEnabled( &g_MachinePolicy );
if ( (bSessionIsHelpSession == FALSE) && g_PreAuthenticateClient ) {
pExtendedClientCredentials pPreAuthenticationCredentials = NULL; ULONG BytesGot ;
pPreAuthenticationCredentials = MemAlloc( sizeof(ExtendedClientCredentials) ); if ( pPreAuthenticationCredentials == NULL ) { Status = STATUS_NO_MEMORY; goto badconnect; } RtlZeroMemory(pPreAuthenticationCredentials,sizeof(ExtendedClientCredentials));
// Try WsxEscape first to get long credentials
if ( pTargetWinStation->pWsx && pTargetWinStation->pWsx->pWsxEscape ) {
Status = pTargetWinStation->pWsx->pWsxEscape( pTargetWinStation->pWsxContext, GET_LONG_USERNAME, NULL, 0, pPreAuthenticationCredentials, sizeof(ExtendedClientCredentials), &BytesGot) ;
} else if ( pTargetWinStation->pWsx && pTargetWinStation->pWsx->pWsxIcaStackIoControl ) {
// We must never be here for RDP client as we support WsxEscape
RtlCopyMemory(pPreAuthenticationCredentials->Domain, pTargetWinStation->Client.Domain, sizeof(pTargetWinStation->Client.Domain)); RtlCopyMemory(pPreAuthenticationCredentials->UserName, pTargetWinStation->Client.UserName, sizeof(pTargetWinStation->Client.UserName)); RtlCopyMemory(pPreAuthenticationCredentials->Password, pTargetWinStation->Client.Password, sizeof(pTargetWinStation->Client.Password)); } if (!NT_SUCCESS(Status)) { // WsxEscape for GET_LONG_USERNAME failed
RtlSecureZeroMemory(pPreAuthenticationCredentials->Password, sizeof(pPreAuthenticationCredentials->Password)); MemFree(pPreAuthenticationCredentials); pPreAuthenticationCredentials = NULL; goto badconnect; } else { HANDLE hToken; BOOL Result;
// If the UserName is a UPN name, then explicitly set the Domain name to NULL
if ( wcschr(pPreAuthenticationCredentials->UserName, '@') != NULL ) { pPreAuthenticationCredentials->Domain[0] = L'\0'; }
// If the password is bigger than max allowed password len, make it NULL
if (wcslen(pPreAuthenticationCredentials->Password) > MAX_ALLOWED_PASSWORD_LEN) { pPreAuthenticationCredentials->Password[0] = L'\0'; }
Result = LogonUser( pPreAuthenticationCredentials->UserName, pPreAuthenticationCredentials->Domain, pPreAuthenticationCredentials->Password, LOGON32_LOGON_INTERACTIVE, // Logon Type
LOGON32_PROVIDER_WINNT50, // Logon Provider
&hToken // Token that represents the account
); RtlSecureZeroMemory(pPreAuthenticationCredentials->Password, sizeof(pPreAuthenticationCredentials->Password)); /*
* check for account restriction which indicates a blank password * on the account that is correct though - allow this thru on console */
if ( (!Result) && (GetLastError() == ERROR_ACCOUNT_RESTRICTION) ) { Result = TRUE; }
if( !Result) { BOOL bSuccessfulAdded = TRUE ;
if (g_BlackListPolicy) { bSuccessfulAdded = Filter_AddFailedConnection( in_addr, uAddrSize ); }
MemFree(pPreAuthenticationCredentials); pPreAuthenticationCredentials = NULL; goto badconnect; }
// LogonUser Successful !
/*
* Close the token handle since we only needed to determine * if the account and password is still valid. */ CloseHandle( hToken ); MemFree(pPreAuthenticationCredentials); pPreAuthenticationCredentials = NULL; }
} // if (bPreAuthenticateClient)
//Since we don't need the password anymore, clean it up
RtlSecureZeroMemory(pTargetWinStation->Client.Password, sizeof(pTargetWinStation->Client.Password));
// Start the WinStation now if its not already started
if ( (pTargetWinStation->InitialCommandProcess == NULL) && (pTargetWinStation->WindowsSubSysProcess == NULL) ) { Status = WinStationStart( pTargetWinStation ) ; if (Status != STATUS_SUCCESS) { // Starting the winstation failed - close connection endpoint and go and wait for another connection
TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: TransferConnectionToIdleWinStation: WinstationStart Failed : Connection attempt failed, Status [%lx]\n",Status )); goto badconnect; }
Status = WinStationCreateComplete( pTargetWinStation) ; if (Status != STATUS_SUCCESS) { // WinstationCreateComplete failed - close connection endpoint and go and wait for another connection
TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: TransferConnectionToIdleWinStation: WinstationCreateComplete Failed : Connection attempt failed, Status [%lx]\n",Status )); goto badconnect;
} }
ASSERT( pTargetWinStation->LogonId != -1 );
pTargetWinStation->Flags &= ~WSF_IDLEBUSY;
// From this point on, it is ok to Queue the WinStation for Reset in case of error
bQueueForReset = TRUE;
/*
* The connection accept was successful, save the * new endpoint in the target WinStation, copy the config * parameters to the target WinStation, and reset the WSF_IDLE flag. */ pTargetWinStation->pEndpoint = pEndpoint; pTargetWinStation->EndpointLength = EndpointLength; if ( pListenWinStation ) pTargetWinStation->Config = pListenWinStation->Config; pTargetWinStation->Flags &= ~WSF_IDLE;
/*
* Copy real name of Single-Instance transports */ if ( pConfig->Pd[0].Create.PdFlag & PD_SINGLE_INST ) { RtlCopyMemory( pTargetWinStation->WinStationName, pTargetWinStation->ListenName, sizeof(pTargetWinStation->WinStationName) ); /*
* Otherwise, build dynamic name from Listen name and target LogonId. */ } else { int CopyCount; WINSTATIONNAME TempName;
// swprintf( TempName, L"#%d", pTargetWinStation->LogonId );
ASSERT(pTargetWinStation->LogonId > 0 && pTargetWinStation->LogonId < 65536); swprintf( TempName, L"#%d", pTargetWinStation->SessionSerialNumber );
CopyCount = min( wcslen( pTargetWinStation->ListenName ), sizeof( pTargetWinStation->WinStationName ) / sizeof( pTargetWinStation->WinStationName[0] ) - wcslen( TempName ) - 1 ); wcsncpy( pTargetWinStation->WinStationName, pTargetWinStation->ListenName, CopyCount ); wcscpy( &pTargetWinStation->WinStationName[CopyCount], TempName ); }
/*
* Inherit the security descriptor from the listen WINSTATION to the * connected WINSTATION. */ if ( pListenWinStation ) { RtlAcquireResourceShared(&WinStationSecurityLock, TRUE); Status = WinStationInheritSecurityDescriptor( pListenWinStation->pSecurityDescriptor, pTargetWinStation ); RtlReleaseResource(&WinStationSecurityLock); if (Status != STATUS_SUCCESS) { // badconnect free pEndpoint, WinStationTerminate() will try to free this
// end point again causing double free.
pTargetWinStation->pEndpoint = NULL; goto badconnect; } } else { ReadWinStationSecurityDescriptor( pTargetWinStation ); } //
// For non-experience-aware clients give as close an experience
// as win2k.
//
if (pTargetWinStation->Client.PerformanceFlags & TS_PERF_DEFAULT_NONPERFCLIENT_SETTING) { pTargetWinStation->Client.PerformanceFlags = TS_PERF_DISABLE_MENUANIMATIONS | TS_PERF_DISABLE_THEMING | TS_PERF_DISABLE_CURSOR_SHADOW; }
if ( pTargetWinStation->Config.Config.User.fWallPaperDisabled ) { pTargetWinStation->Client.PerformanceFlags |= TS_PERF_DISABLE_WALLPAPER; }
if ( pTargetWinStation->Config.Config.User.fCursorBlinkDisabled ) { pTargetWinStation->Client.PerformanceFlags |= TS_PERF_DISABLE_CURSORSETTINGS; } //
// If TS policy denies connection, only way to comes to
// here is policy allow help, reject connection if logon
// user is not salem help assistant.
//
if( TRUE == bSessionIsHelpSession ) { //
// If invalid ticket or policy deny help, we immediately disconnect
//
if( FALSE == bValidRAConnect || FALSE == bPolicyAllowHelp ) { // Invalid ticket, disconnect immediately
TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Invalid RA login\n")); goto invalid_ra_connection; } } else if( !pListenWinStation && pStackAddress ) { //
// Reverse Connect, parameter passed in pListenWinStation = NULL
// and pStackAddress is not NULL, for normal connection,
// pListenWinStation is not NULL but pStackAddress is NULL
//
//
// Handle non-RA Reverse connection, Whistler revert connection
// only allow RA login.
//
TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Not/invalid Help Assistant logon\n")); goto invalid_ra_connection; }
//
// Connecting client must be either non-RA or valid RA connection.
//
//
// Handle Safeboot with networking and TS deny non-RA connection,
// safeboot with networking only allow RA connection.
//
// Disconnect client on following
//
// 1) Safeboot with networking if not RA connect.
// 2) Reverse connection if not RA connect.
// 3) TS not accepting connection via policy setting if not RA connect.
// 4) Not allowing help if RA connect.
// 5) Invalid RA connection if RA connect.
// 6) if Home edition and not RA commection
//
if( g_SafeBootWithNetwork || g_fDenyTSConnectionsPolicy || g_bPersonalWks) { if( FALSE == bSessionIsHelpSession ) { TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Policy or safeboot denied connection\n"));
goto invalid_ra_connection; } }
//
// Only log Salem event for reverse connection
//
if( !pListenWinStation && pStackAddress ) { ASSERT( TRUE == bSessionIsHelpSession ); TSLogSalemReverseConnection(pTargetWinStation, pStackAddress); }
/*
* Set the connect event to wake up the target WinStation. */ if (pTargetWinStation->ConnectEvent != NULL) { NtSetEvent( pTargetWinStation->ConnectEvent, NULL ); }
/*
* Release target WinStation */
if( pListenWinStation ) {
if (bSuccessAdded) { // If we could add this IP address to the per IP list then remember it.
PREMEMBERED_CLIENT_ADDRESS pAddress; if ((uAddrSize != 0) && (pAddress = (PREMEMBERED_CLIENT_ADDRESS) MemAlloc( sizeof(REMEMBERED_CLIENT_ADDRESS) + uAddrSize -1 ))!= NULL ) { pAddress->length = uAddrSize; RtlCopyMemory( &pAddress->addr[0] , in_addr,uAddrSize ); pTargetWinStation->pRememberedAddress = pAddress;
// at this point we have a valid remote address. We'll cache this address.
pTargetWinStation->pLastClientAddress = ( PREMEMBERED_CLIENT_ADDRESS )MemAlloc( sizeof( REMEMBERED_CLIENT_ADDRESS ) + uAddrSize - 1 );
if( pTargetWinStation->pLastClientAddress != NULL ) { RtlCopyMemory( &pTargetWinStation->pLastClientAddress->addr[0] , &pTargetWinStation->pRememberedAddress->addr[0] , uAddrSize );
pTargetWinStation->pLastClientAddress->length = uAddrSize;
}
} else { Filter_RemoveOutstandingConnection( in_addr, uAddrSize ); if( (ULONG)InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) { if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } }
} } else{ // We could not add this IP address to the pr IP list
if( (ULONG)InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) { if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } } }
}
ReleaseWinStation( pTargetWinStation );
/*
* If necessary, create another idle WinStation to replace the one being connected */ NtSetEvent(WinStationIdleControlEvent, NULL);
return STATUS_SUCCESS;
/*=============================================================================
== Error returns =============================================================================*/ invalid_ra_connection:
// badconnect free pEndpoint, WinStationTerminate() will try to free this
// end point again causing double free.
pTargetWinStation->pEndpoint = NULL; StatusCallback = STATUS_CTX_WINSTATION_ACCESS_DENIED;
/*
* Error during ConnectionAccept */ badconnect: /*
* Clear the Listen name */ if (pTargetWinStation) { RtlZeroMemory( pTargetWinStation->ListenName, sizeof(pTargetWinStation->ListenName) ); /*
* Call WinStation rundown function before killing the stack */ if (pTargetWinStation->pWsxContext) { if ( pTargetWinStation->pWsx && pTargetWinStation->pWsx->pWsxWinStationRundown ) { pTargetWinStation->pWsx->pWsxWinStationRundown( pTargetWinStation->pWsxContext ); } pTargetWinStation->pWsxContext = NULL; } pTargetWinStation->pWsx = NULL; }
/*
* Release system resources. This happens in two phases: * * a.) The connection endpoint - since endpoints are not reference counted * care must be taken to destroy the endpoint with the stack in which it * was loaded. In the event it was not loaded, we create a temporary * stack to do the dirty work. * * b.) The Winstation inself - if we had to create an idle winstation to * handle this connection, it is destroyed. Otherwise we just return * it back to the idle pool. * */ releaseresources: /*
* If we created a target WinStation, then use its stack to close the * endpoint since the stack may have a reference to it. */ TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: Closing Endpoint [0x%p], winsta = 0x%p, Accepted = %ld\n", pEndpoint, pTargetWinStation, ConnectionAccepted));
if ((pTargetWinStation != NULL) && (ConnectionAccepted)) { Status = _CloseEndpoint( pConfig, pEndpoint, EndpointLength, pTargetWinStation, FALSE ); // Use the stack which already has
// the endpoint loaded
}
/*
* Otherwise, we failed before we got the endpoint loaded so close the * endpoint using a temporary stack. */ else if ( pListenWinStation ) { // note that:
// 1. if pListenWinStation is NULL then pEndpoint is NULL, so nothing to close;
// 2. use the config of pListenWinStation in case pConfig is not set yet.
Status = _CloseEndpoint( &pListenWinStation->Config, pEndpoint, EndpointLength, pListenWinStation, TRUE ); // Use a temporary stack
}
if ( pEndpoint ) MemFree( pEndpoint );
pEndpoint = NULL;
/*
* Return the winstation if we got that far in the protocol sequence */ if (pTargetWinStation != NULL) { /*
* If we created a WinStation above because there were no IDLE * WinStations available, then we will now have an extra IDLE * WinStation. In this case, reset the current IDLE WinStation. */ if ( CreatedIdle ) { // clear this so it doesn't get selected as idle when unlocked
pTargetWinStation->Flags &= ~WSF_IDLE; if ( bQueueForReset == TRUE ) { QueueWinStationReset( pTargetWinStation->LogonId ); ReleaseWinStation( pTargetWinStation ); } else { if ( !(pTargetWinStation->Flags & (WSF_RESET | WSF_DELETE)) ) { pTargetWinStation->Flags |= WSF_DELETE; WinStationTerminate( pTargetWinStation ); pTargetWinStation->State = State_Down; //PostErrorValueEvent(EVENT_TS_WINSTATION_START_FAILED, Status);
WinStationDeleteWorker(pTargetWinStation); } else { ReleaseWinStation(pTargetWinStation); } } } /*
* Else return this WinStation to the idle pool after cleaning up the * stack. */ else {
//
// The licensing context needs to be freed and recreated to
// ensure it is cleaned up properly.
//
LCDestroyContext(pTargetWinStation);
Status = LCCreateContext(pTargetWinStation);
if (NT_SUCCESS(Status)) { Status = IcaStackClose( pTargetWinStation->hStack ); ASSERT( NT_SUCCESS( Status ) ); pTargetWinStation->hStack = NULL; Status = IcaStackOpen( pTargetWinStation->hIca, Stack_Primary, (PROC)WsxStackIoControl, pTargetWinStation, &pTargetWinStation->hStack ); }
if (NT_SUCCESS(Status)) { pTargetWinStation->Flags |= WSF_IDLE; pTargetWinStation->Flags &= ~WSF_IDLEBUSY; RtlZeroMemory(pTargetWinStation->WinStationName, sizeof(pTargetWinStation->WinStationName)); pTargetWinStation->State = State_Idle; NotifySystemEvent( WEVENT_STATECHANGE ); ReleaseWinStation( pTargetWinStation ); } else { pTargetWinStation->Flags &= ~WSF_IDLE; QueueWinStationReset( pTargetWinStation->LogonId); ReleaseWinStation( pTargetWinStation ); } } }
if ( pListenWinStation ) {
if (bSuccessAdded) { Filter_RemoveOutstandingConnection( in_addr, uAddrSize ); } if( (ULONG)InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) { if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } }
}
// If error is due to call back, return meaningful error
// code.
if( STATUS_SUCCESS != StatusCallback ) { return StatusCallback; }
return -1 /*STATUS_CTX_UNACCEPTED_CONNECTION*/; }
/*******************************************************************************
* ConnectSmWinStationApiPort * * Open a connection to the WinStationApiPort. This will be used * to queue requests to the Api Request thread instead of processing * them in line. ******************************************************************************/ NTSTATUS ConnectSmWinStationApiPort() { UNICODE_STRING PortName; SECURITY_QUALITY_OF_SERVICE DynamicQos; WINSTATIONAPI_CONNECT_INFO info; ULONG ConnectInfoLength; NTSTATUS Status;
/*
* Set up the security quality of service parameters to use over the * port. Use the most efficient (least overhead) - which is dynamic * rather than static tracking. */ DynamicQos.ImpersonationLevel = SecurityImpersonation; DynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; DynamicQos.EffectiveOnly = TRUE;
RtlInitUnicodeString( &PortName, L"\\SmSsWinStationApiPort" );
// Fill in the ConnectInfo structure with our access request mask
info.Version = CITRIX_WINSTATIONAPI_VERSION; info.RequestedAccess = 0; ConnectInfoLength = sizeof(WINSTATIONAPI_CONNECT_INFO);
Status = NtConnectPort( &WinStationApiPort, &PortName, &DynamicQos, NULL, NULL, NULL, // Max message length [select default]
(PVOID)&info, &ConnectInfoLength );
if ( !NT_SUCCESS( Status ) ) { // Look at the returned INFO to see why if desired
if ( ConnectInfoLength == sizeof(WINSTATIONAPI_CONNECT_INFO) ) { DBGPRINT(( "TERMSRV: Sm connect failed, Reason 0x%x\n", info.AcceptStatus)); } DBGPRINT(( "TERMSRV: Connect to SM failed %lx\n", Status )); }
return Status; }
/*******************************************************************************
* QueueWinStationCreate * * Send a create message to the WinStationApiPort. * * ENTRY: * pWinStationName (input) * Pointer to WinStationName to be created ******************************************************************************/ NTSTATUS QueueWinStationCreate(PWINSTATIONNAME pWinStationName) { WINSTATION_APIMSG msg; NTSTATUS Status;
TRACE((hTrace,TC_ICASRV,TT_API1,"TERMSRV: QueueWinStationCreate: %S\n", pWinStationName ));
/*
* Initialize msg */ msg.h.u1.s1.DataLength = sizeof(msg) - sizeof(PORT_MESSAGE); msg.h.u1.s1.TotalLength = sizeof(msg); msg.h.u2.s2.Type = 0; // Kernel will fill in message type
msg.h.u2.s2.DataInfoOffset = 0; msg.ApiNumber = SMWinStationCreate; msg.WaitForReply = FALSE; if ( pWinStationName ) { RtlCopyMemory( msg.u.Create.WinStationName, pWinStationName, sizeof(msg.u.Create.WinStationName) ); } else { RtlZeroMemory( msg.u.Create.WinStationName, sizeof(msg.u.Create.WinStationName) ); }
/*
* Send create message to our API request thread * but don't wait for a reply. */ Status = NtRequestPort( WinStationApiPort, (PPORT_MESSAGE) &msg ); ASSERT( NT_SUCCESS( Status ) );
return Status; }
/*******************************************************************************
* QueueWinStationReset * * Send a reset message to the WinStationApiPort. * * ENTRY: * LogonId (input) * LogonId of WinStationName to reset ******************************************************************************/ NTSTATUS QueueWinStationReset(ULONG LogonId) {
WINSTATION_APIMSG msg; NTSTATUS Status;
TRACE((hTrace,TC_ICASRV,TT_API1,"TERMSRV: QueueWinStationReset: %u\n", LogonId ));
/*
* Initialize msg */ msg.h.u1.s1.DataLength = sizeof(msg) - sizeof(PORT_MESSAGE); msg.h.u1.s1.TotalLength = sizeof(msg); msg.h.u2.s2.Type = 0; // Kernel will fill in message type
msg.h.u2.s2.DataInfoOffset = 0; msg.ApiNumber = SMWinStationReset; msg.WaitForReply = FALSE; msg.u.Reset.LogonId = LogonId;
/*
* Send reset message to our API request thread * but don't wait for a reply. */ Status = NtRequestPort( WinStationApiPort, (PPORT_MESSAGE) &msg ); ASSERT( NT_SUCCESS( Status ) );
return( Status ); }
/*******************************************************************************
* QueueWinStationDisconnect * * Send a disconnect message to the WinStationApiPort. * * ENTRY: * LogonId (input) * LogonId of WinStationName to disconnect ******************************************************************************/ NTSTATUS QueueWinStationDisconnect(ULONG LogonId) { WINSTATION_APIMSG msg; NTSTATUS Status;
TRACE((hTrace,TC_ICASRV,TT_API1,"TERMSRV: QueueWinStationDisconnect: %u\n", LogonId ));
/*
* Initialize msg */ msg.h.u1.s1.DataLength = sizeof(msg) - sizeof(PORT_MESSAGE); msg.h.u1.s1.TotalLength = sizeof(msg); msg.h.u2.s2.Type = 0; // Kernel will fill in message type
msg.h.u2.s2.DataInfoOffset = 0; msg.ApiNumber = SMWinStationDisconnect; msg.WaitForReply = FALSE; msg.u.Reset.LogonId = LogonId;
/*
* Send disconnect message to our API request thread * but don't wait for a reply. */ Status = NtRequestPort( WinStationApiPort, (PPORT_MESSAGE) &msg ); ASSERT( NT_SUCCESS( Status ) );
return( Status ); }
/*******************************************************************************
* IcaRegWinStationEnumerate * * Enumerate all WinStations configured in the registry. * * ENTRY: * pWinStationCount (input/output) * count of WinStation names to return, on return the number of * WinStation names actually returned in name buffer * pWinStationName (output) * pointer to buffer to return WinStation names * pByteCount (input/output) * size of WinStation name buffer, on return the number of * bytes actually returned in the name buffer ******************************************************************************/ NTSTATUS IcaRegWinStationEnumerate( PULONG pWinStationCount, PWINSTATIONNAME pWinStationName, PULONG pByteCount) { NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; WCHAR PathBuffer[ 260 ]; UNICODE_STRING KeyPath; HANDLE Handle; ULONG i; ULONG Count;
wcscpy( PathBuffer, REG_NTAPI_CONTROL_TSERVER L"\\" REG_WINSTATIONS ); RtlInitUnicodeString( &KeyPath, PathBuffer );
InitializeObjectAttributes( &ObjectAttributes, &KeyPath, OBJ_CASE_INSENSITIVE, NULL, NULL );
Status = NtOpenKey( &Handle, GENERIC_READ, &ObjectAttributes ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: NtOpenKey failed, rc=%x\n", Status )); return( Status ); }
Count = pWinStationName ? min( *pByteCount / sizeof(WINSTATIONNAME), *pWinStationCount ) : (ULONG) -1; *pWinStationCount = *pByteCount = 0; for ( i = 0; i < Count; i++ ) { WINSTATIONNAME WinStationName; UNICODE_STRING WinStationString;
WinStationString.Length = 0; WinStationString.MaximumLength = sizeof(WinStationName); WinStationString.Buffer = WinStationName; Status = RtlpNtEnumerateSubKey( Handle, &WinStationString, i, NULL ); if ( !NT_SUCCESS( Status ) ) { if ( Status != STATUS_NO_MORE_ENTRIES ) { DBGPRINT(( "TERMSRV: RtlpNtEnumerateSubKey failed, rc=%x\n", Status )); } break; } if ( pWinStationName ) { RtlCopyMemory( pWinStationName, WinStationName, WinStationString.Length ); pWinStationName[WinStationString.Length>>1] = UNICODE_NULL; (char*)pWinStationName += sizeof(WINSTATIONNAME); } (*pWinStationCount)++; *pByteCount += sizeof(WINSTATIONNAME); }
NtClose( Handle );
return STATUS_SUCCESS; }
/*******************************************************************************
* WinStationCreateWorker * * Worker routine to create/start a WinStation. * * ENTRY: * pWinStationName (input) (optional) * Pointer to WinStationName to be created * pLogonId (output) * location to return LogonId of new WinStation * fStartWinStation (input) * Tells if we need to Start the winstation after creating it * * NOTE: If a WinStation name is specified, then this will become the * "listening" WinStation for the specified name. * If a WinStation name is not specified, then this will become * part of the free pool of idle/disconnected WinStations. ******************************************************************************/ NTSTATUS WinStationCreateWorker( PWINSTATIONNAME pWinStationName, PULONG pLogonId, BOOLEAN fStartWinStation ) { BOOL fConsole; PWINSTATION pWinStation = NULL; PWINSTATION pCurWinStation; NTSTATUS Status; UNICODE_STRING WinStationString; ULONG ReturnLength; /*
* If system shutdown is in progress, then don't allow create */ if ( ShutdownInProgress ) { Status = STATUS_ACCESS_DENIED; goto shutdown; }
if (pWinStationName == NULL) { fConsole = FALSE; } else { fConsole = (_wcsicmp(pWinStationName, L"Console") == 0); }
/*
* If not the Console, then verify the WinStation name is defined * in the registry and that it is enabled. */ if ( pWinStationName && !fConsole ) { Status = CheckWinStationEnable( pWinStationName ); if ( Status != STATUS_SUCCESS ) { DBGPRINT(( "TERMSRV: WinStation '%ws' is disabled\n", pWinStationName )); goto disabled; } }
/*
* Allocate and initialize WinStation struct */ if ( (pWinStation = MemAlloc( sizeof(WINSTATION) )) == NULL ) { Status = STATUS_NO_MEMORY; goto nomem; } RtlZeroMemory( pWinStation, sizeof(WINSTATION) ); pWinStation->Starting = TRUE; pWinStation->NeverConnected = TRUE; pWinStation->State = State_Init; pWinStation->pNewNotificationCredentials = NULL; pWinStation->fReconnectPending = FALSE; pWinStation->fReconnectingToConsole = FALSE; pWinStation->LastReconnectType = NeverReconnected; pWinStation->fDisallowAutoReconnect = FALSE; pWinStation->fSmartCardLogon = FALSE; pWinStation->fSDRedirectedSmartCardLogon = FALSE; memset( pWinStation->ExecSrvSystemPipe, 0, EXECSRVPIPENAMELEN*sizeof(WCHAR) );
// Create the Session Initialized event which will be set when Winlogon calls after creating a Desktop
pWinStation->SessionInitializedEvent = CreateEvent(NULL, TRUE, // Manual Reset
FALSE, // Initial State is non signaled
NULL);
if (pWinStation->SessionInitializedEvent == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto nomem; }
InitializeListHead( &pWinStation->ShadowHead ); InitializeListHead( &pWinStation->Win32CommandHead );
// Create the licensing context
Status = LCCreateContext(pWinStation); if ( !NT_SUCCESS( Status ) ) goto nolicensecontext;
// Create and lock winstation mutex
Status = InitRefLock( &pWinStation->Lock, WinStationDeleteProc ); if ( !NT_SUCCESS( Status ) ) goto nolock;
/*
* If a WinStation name was specified, see if it already exists * (on return, WinStationListLock will be locked). */ if ( pWinStationName ) { if ( pCurWinStation = FindWinStationByName( pWinStationName, TRUE ) ) { ReleaseWinStation( pCurWinStation ); LEAVECRIT( &WinStationListLock ); Status = STATUS_CTX_WINSTATION_NAME_COLLISION; goto alreadyexists; }
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Creating WinStation %ws\n", pWinStationName )); LEAVECRIT( &WinStationListLock ); wcscpy( pWinStation->WinStationName, pWinStationName );
/*
* If not the console, then this will become a "listen" WinStation */ if ( !fConsole ) {
pWinStation->Flags |= WSF_LISTEN;
//
// Listener winstations always get LogonId above 65536 and are
// assigned by Terminal Server. LogonId's for sessions are
// generated by mm in the range 0-65535
//
pWinStation->LogonId = LogonId++; ASSERT(pWinStation->LogonId >= 65536);
} else {
//
// Console always get 0
//
pWinStation->LogonId = 0; pWinStation->fOwnsConsoleTerminal = TRUE;
}
/*
* No WinStation name was specified. * This will be an idle WinStation waiting for a connection. */ } else { TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Creating IDLE WinStation\n" )); pWinStation->Flags |= WSF_IDLE; pWinStation->LogonId = -1; // MM will asssign session IDs
}
/*
* Initialize WinStation configuration data */ #ifdef NO_CONSOLE_REGISTRY
if ( pWinStation->LogonId ) { #endif
/*
* Read winstation configuration data from registry */ if ( pWinStationName ) { Status = RegWinStationQueryEx( SERVERNAME_CURRENT, &g_MachinePolicy, pWinStationName, &pWinStation->Config, sizeof(WINSTATIONCONFIG2), &ReturnLength, TRUE ); if ( !NT_SUCCESS(Status) ) { goto badregdata; }
if (pWinStation->Config.Wd.WdFlag & WDF_TSHARE) { pWinStation->Client.ProtocolType = PROTOCOL_RDP; } else if (pWinStation->Config.Wd.WdFlag & WDF_ICA) { pWinStation->Client.ProtocolType = PROTOCOL_ICA; } else { pWinStation->Client.ProtocolType = PROTOCOL_CONSOLE; }
/*
* Save console config for console sessions. */
if (pWinStation->LogonId == 0) { gConsoleConfig = pWinStation->Config;
// initalize client data, since there isn't any real rdp client sending anythhing to us
InitializeConsoleClientData( & pWinStation->Client );
} } #ifdef NO_CONSOLE_REGISTRY
} else {
/*
* Hand craft the console configuration data */ PWDCONFIG pWdConfig = &pWinStation->Config.Wd; PPDCONFIGW pPdConfig = &pWinStation->Config.Pd[0];
wcscpy( pWdConfig->WdName, L"Console" ); pWdConfig->WdFlag = WDF_NOT_IN_LIST; wcscpy( pPdConfig->Create.PdName, L"Console" ); pPdConfig->Create.PdClass = PdConsole; pPdConfig->Create.PdFlag = PD_USE_WD | PD_RELIABLE | PD_FRAME | PD_CONNECTION | PD_CONSOLE;
RegQueryOEMId( (PBYTE) &pWinStation->Config.Config.OEMId, sizeof(pWinStation->Config.Config.OEMId) );
} #endif
/*
* Allocate LogonId and insert in WinStation list */ ENTERCRIT( &WinStationListLock ); InsertTailList( &WinStationListHead, &pWinStation->Links ); LEAVECRIT( &WinStationListLock );
if (pWinStation->LogonId == 0 || g_bPersonalTS) { // Create a named event for console session so that winmm can check if we
// are remoting audio on the console itself. Use a global event is for
// fast check
{ BYTE bSA[SECURITY_DESCRIPTOR_MIN_LENGTH]; PSECURITY_DESCRIPTOR pSD = &bSA; SECURITY_ATTRIBUTES SA; EXPLICIT_ACCESS ea[2]; SID_IDENTIFIER_AUTHORITY siaWorld = SECURITY_WORLD_SID_AUTHORITY; SID_IDENTIFIER_AUTHORITY siaNT = SECURITY_NT_AUTHORITY; PSID pSidWorld; PSID pSidNT; PACL pNewDAcl; DWORD dwres;
if ( AllocateAndInitializeSid( &siaWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pSidWorld)) { if ( AllocateAndInitializeSid( &siaNT, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pSidNT)) { ZeroMemory(ea, sizeof(ea)); ea[0].grfAccessPermissions = SYNCHRONIZE; ea[0].grfAccessMode = GRANT_ACCESS; ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[0].Trustee.ptstrName = (LPTSTR)pSidWorld; ea[1].grfAccessPermissions = GENERIC_ALL; ea[1].grfAccessMode = GRANT_ACCESS; ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[1].Trustee.ptstrName = (LPTSTR)pSidNT;
dwres = SetEntriesInAcl(2, ea, NULL, &pNewDAcl ); if ( ERROR_SUCCESS == dwres ) { if (InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) { if (SetSecurityDescriptorDacl(pSD, TRUE, pNewDAcl, FALSE )) { SA.nLength = sizeof( SA ); SA.lpSecurityDescriptor = pSD; SA.bInheritHandle = FALSE; pWinStation->hWinmmConsoleAudioEvent = CreateEvent( &SA, TRUE, FALSE, L"Global\\WinMMConsoleAudioEvent"); if ( pWinStation->LogonId == 0 ) { pWinStation->hRDPAudioDisabledEvent = CreateEvent( &SA, TRUE, FALSE, L"Global\\RDPAudioDisabledEvent"); } else { pWinStation->hRDPAudioDisabledEvent = NULL; } } } LocalFree( pNewDAcl ); } LocalFree( pSidNT ); } LocalFree( pSidWorld ); } } } else { pWinStation->hWinmmConsoleAudioEvent = NULL; pWinStation->hRDPAudioDisabledEvent = NULL; }
/*
* Start the WinStation's primary device and stack */
Status = StartWinStationDeviceAndStack( pWinStation ) ;
/*
* Ignore errors from console, otherwise keep going */ if ( ( pWinStation->LogonId ) && ( Status != STATUS_SUCCESS ) ) { goto starterror; }
/*
* Start the WinStation - do this only for the Listener and Console Sessions or if last param is TRUE * For idle winstations, we start the Csr and Winlogon later on when Transferring connection */
if (( pWinStation->Flags & WSF_LISTEN ) || (pWinStation->LogonId == 0) || (fStartWinStation)) {
Status = WinStationStart( pWinStation );
/*
* Ignore errors from console, otherwise keep going */ if ( ( pWinStation->LogonId ) && ( Status != STATUS_SUCCESS ) ) goto starterror;
Status = WinStationCreateComplete( pWinStation ) ;
if (Status != STATUS_SUCCESS) { TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: WinstationCreateComplete Failed : Connection attempt failed, Status [%lx]\n",Status )); goto starterror; } }
/*
* Return LogonId to caller * At this point LogonId is really -1 for idle sessions because we delay the starting of Csr and winlogon */ if ( pLogonId ) *pLogonId = pWinStation->LogonId;
pWinStation->Starting = FALSE;
/*
* Release WinStation now */ ReleaseWinStation( pWinStation );
return( STATUS_SUCCESS );
/*=============================================================================
== Error returns =============================================================================*/
/*
* WinStationStart returned error * WinStation kernel object could not be created */ starterror: if ( !(pWinStation->Flags & (WSF_RESET | WSF_DELETE)) ) { if ( StopOnDown ) DbgBreakPoint(); pWinStation->Flags |= WSF_DELETE; WinStationTerminate( pWinStation ); pWinStation->State = State_Down; PostErrorValueEvent(EVENT_TS_WINSTATION_START_FAILED, Status); WinStationDeleteWorker(pWinStation); } else { ReleaseWinStation( pWinStation ); }
return Status;
/*
* Error reading registry data */ badregdata:
/*
* WinStation name already exists */ alreadyexists: NtClose( pWinStation->Lock.Mutex );
/*
* Could not create WinStation lock */ nolock: LCDestroyContext(pWinStation);
/*
* Could not allocate licensing context */ nolicensecontext:
/*
* Close the session initialization event if created successfully. */ if (pWinStation->SessionInitializedEvent) { CloseHandle(pWinStation->SessionInitializedEvent); pWinStation->SessionInitializedEvent = NULL; }
/*
* Could not allocate WinStation */ nomem: PostErrorValueEvent(EVENT_TS_WINSTATION_START_FAILED, Status);
if (pWinStation) { MemFree(pWinStation); }
/*
* WinStation is disabled * System shutdown is in progress */ disabled: shutdown:
return Status; }
/*******************************************************************************
* StartWinStationDeviceAndStack * Open the primary Device and primary Stack of the WinStation. * * ENTRY: * pWinStation (input) * Pointer to WinStation to start ******************************************************************************/
NTSTATUS StartWinStationDeviceAndStack(PWINSTATION pWinStation) { NTSTATUS Status = STATUS_SUCCESS; ICA_TRACE Trace;
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: StartWinStationDeviceAndStack, %S (LogonId=%d)\n", pWinStation->WinStationName, pWinStation->LogonId ));
/*
* If its a WSF_LISTEN WinStation, see if a specific ACL has * been set for it. * * This ACL will be inherited by WinStations that are * connected to from this thread. * * If not specific ACL has been set, the system default * will be used. */
if (( pWinStation->Flags & WSF_LISTEN ) || (pWinStation->LogonId == 0)){
ReadWinStationSecurityDescriptor( pWinStation ); }
/*
* Open an instance of the TermDD device driver */ Status = IcaOpen( &pWinStation->hIca ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV StartWinStationDeviceAndStack : IcaOpen: Error 0x%x from IcaOpen, last error %d\n", Status, GetLastError() )); goto done; }
/*
* Open a stack instance */ Status = IcaStackOpen( pWinStation->hIca, Stack_Primary, (PROC)WsxStackIoControl, pWinStation, &pWinStation->hStack);
if ( !NT_SUCCESS( Status ) ) { IcaClose( pWinStation->hIca ); pWinStation->hIca = NULL; DBGPRINT(( "TERMSRV StartWinStationDeviceAndStack : IcaStackOpen: Error 0x%x from IcaStackOpen, last error %d\n", Status, GetLastError() )); goto done; }
/*
* Enable trace */ RtlZeroMemory( &Trace , sizeof( ICA_TRACE ) ); InitializeTrace( pWinStation, FALSE, &Trace );
done: TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: StartWinStationDeviceAndStack, Status = 0x%x\n", Status));
return Status; }
/*******************************************************************************
* WinStationStart * * Start a WinStation. This involves reading the * execute list from the registry, loading the WinStation * subsystems, and starting the initial program. * * ENTRY: * pWinStation (input) * Pointer to WinStation to start ******************************************************************************/ NTSTATUS WinStationStart(PWINSTATION pWinStation) { OBJECT_ATTRIBUTES ObjA; LARGE_INTEGER Timeout; NTSTATUS Status; UNICODE_STRING InitialCommand; PUNICODE_STRING pInitialCommand; PWCHAR pExecuteBuffer = NULL; ULONG CommandSize; PWCHAR pszCsrStartEvent = NULL; PWCHAR pszReconEvent = NULL; UNICODE_STRING EventName;
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationStart, %S (LogonId=%d)\n", pWinStation->WinStationName, pWinStation->LogonId ));
// allocate memory
pExecuteBuffer = MemAlloc( MAX_STRING_BYTES * sizeof(WCHAR) ); if (pExecuteBuffer == NULL) { Status = STATUS_NO_MEMORY; goto done; }
pszCsrStartEvent = MemAlloc( MAX_PATH * sizeof(WCHAR) ); if (pszCsrStartEvent == NULL) { Status = STATUS_NO_MEMORY; goto done; }
pszReconEvent = MemAlloc( MAX_PATH * sizeof(WCHAR) ); if (pszReconEvent == NULL) { Status = STATUS_NO_MEMORY; goto done; }
/*
* If this is a "listening" WinStation, then we don't load the * subsystems and initial command. Instead we create a service * thread to wait for new connections and service them. */ if ( pWinStation->Flags & WSF_LISTEN ) { DWORD ThreadId;
pWinStation->hConnectThread = CreateThread( NULL, 0, // use Default stack size of the svchost process
(LPTHREAD_START_ROUTINE)WinStationConnectThread, LongToPtr( pWinStation->LogonId ), 0, &ThreadId );
pWinStation->CreateStatus = STATUS_SUCCESS; Status = pWinStation->CreateStatus; pWinStation->NeverConnected = FALSE; pWinStation->State = State_Listen; NotifySystemEvent( WEVENT_STATECHANGE );
/*
* Load subsystems and initial command * * Session Manager starts the console itself, but returns the * process ID's. For all others, this actually starts CSR * and winlogon. */ } else { /*
* Create event we will wait on below */ if ( pWinStation->LogonId ) {
InitializeObjectAttributes( &ObjA, NULL, 0, NULL, NULL ); Status = NtCreateEvent( &pWinStation->CreateEvent, EVENT_ALL_ACCESS, &ObjA, NotificationEvent, FALSE ); if ( !NT_SUCCESS( Status ) ) goto done; }
UnlockWinStation( pWinStation );
/*
* Check debugging options */ Status = RegWinStationQueryValueW( SERVERNAME_CURRENT, pWinStation->WinStationName, L"Execute", pExecuteBuffer, MAX_STRING_BYTES * sizeof(WCHAR), &CommandSize );
if ( !Status && CommandSize ) { RtlInitUnicodeString( &InitialCommand, pExecuteBuffer ); pInitialCommand = &InitialCommand; } else { pInitialCommand = NULL; }
/*
* For now only do one winstation start at a time. This is because of * WinStation space problems. The Session manager maps it's self into * the WinStation space of the CSR it wants to start so the CSR inherits * the space. That means only one CSR can be started at a time. */ ENTERCRIT( &WinStationStartCsrLock ); //Terminal Service needs to skip Memory check in Embedded images
//when the TS service starts
//bug #246972
if(!IsEmbedded()) { /*
* Make sure we have enough resources to start a new session */
Status = NtAllocateVirtualMemory( NtCurrentProcess(), &glpAddress, 0, &gMinPerSessionPageCommit, MEM_COMMIT, PAGE_READWRITE );
if (!NT_SUCCESS(Status)) { DBGPRINT(( "TERMSRV: NtAllocateVirtualMemory failed with Status %lx for Size %lx(MB)\n",Status,gMinPerSessionPageCommitMB)); LEAVECRIT( &WinStationStartCsrLock ); goto done; } else {
Status = NtFreeVirtualMemory( NtCurrentProcess(), &glpAddress, &gMinPerSessionPageCommit, MEM_DECOMMIT ); if (!NT_SUCCESS(Status)) { DBGPRINT(( "TERMSRV: NtFreeVirtualMemory failed with Status %lx \n",Status)); ASSERT(NT_SUCCESS(Status)); } } }
Status = SmStartCsr( IcaSmApiPort, &pWinStation->LogonId, pInitialCommand, (PULONG_PTR)&pWinStation->InitialCommandProcessId, (PULONG_PTR)&pWinStation->WindowsSubSysProcessId );
LEAVECRIT( &WinStationStartCsrLock );
if ( !RelockWinStation( pWinStation ) ) Status = STATUS_CTX_CLOSE_PENDING;
if ( Status != STATUS_SUCCESS) { DBGPRINT(("TERMSRV: SmStartCsr failed\n")); goto done; }
/*
* Close handle to initial command process, if already opened */ if ( pWinStation->InitialCommandProcess ) { NtClose( pWinStation->InitialCommandProcess ); pWinStation->InitialCommandProcess = NULL; }
/*
* Open handle to initial command process */ pWinStation->InitialCommandProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, (DWORD)(ULONG_PTR)(pWinStation->InitialCommandProcessId) );
if ( pWinStation->InitialCommandProcess == NULL ) { TRACE((hTrace,TC_ICASRV,TT_ERROR,"TERMSRV: Logon %d cannot open Initial command process\n", pWinStation->LogonId)); Status = STATUS_ACCESS_DENIED; goto done; }
/*
* Open handle to WIN32 subsystem process */ pWinStation->WindowsSubSysProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, (DWORD)(ULONG_PTR)(pWinStation->WindowsSubSysProcessId) );
if ( pWinStation->WindowsSubSysProcess == NULL ) { TRACE((hTrace,TC_ICASRV,TT_ERROR,"TERMSRV: Logon %d cannot open windows subsystem process\n", pWinStation->LogonId)); Status = STATUS_ACCESS_DENIED; goto done; }
//
// Terminal Server calls into Session Manager to create a new Hydra session.
// The session manager creates and resume a new session and returns to Terminal
// server the session id of the new session. There is a race condition where
// CSR can resume and call into terminal server before terminal server can
// store the session id in its internal structure. To prevent this CSR will
// wait here on a named event which will be set by Terminal server once it
// gets the sessionid for the newly created session
// Create CsrStartEvent
//
if ( NT_SUCCESS( Status ) && pWinStation->LogonId ) {
wsprintf(pszCsrStartEvent, L"\\Sessions\\%d\\BaseNamedObjects\\CsrStartEvent",pWinStation->LogonId); RtlInitUnicodeString( &EventName,pszCsrStartEvent); InitializeObjectAttributes( &ObjA, &EventName, OBJ_OPENIF, NULL, NULL );
Status = NtCreateEvent( &(pWinStation->CsrStartEventHandle), EVENT_ALL_ACCESS, &ObjA, NotificationEvent, FALSE );
if ( !NT_SUCCESS( Status ) ) { DBGPRINT(("TERMSRV: NtCreateEvent (%ws) failed (%lx)\n",pszCsrStartEvent, Status)); ASSERT(FALSE); pWinStation->CsrStartEventHandle = NULL; goto done; }
//
// Now that we have the sessionId(LogonId), set the CsrStartEvent so
// CSR can connect to TerminalServer
//
NtSetEvent(pWinStation->CsrStartEventHandle, NULL); }
{ //
// Create ReconnectReadyEvent
//
if ( pWinStation->LogonId == 0 ) { wsprintf(pszReconEvent, L"\\BaseNamedObjects\\ReconEvent"); } else { wsprintf(pszReconEvent, L"\\Sessions\\%d\\BaseNamedObjects\\ReconEvent",pWinStation->LogonId); } RtlInitUnicodeString( &EventName,pszReconEvent); InitializeObjectAttributes( &ObjA, &EventName, OBJ_OPENIF, NULL, NULL );
Status = NtCreateEvent( &(pWinStation->hReconnectReadyEvent), EVENT_ALL_ACCESS, &ObjA, NotificationEvent, TRUE );
if ( !NT_SUCCESS( Status ) ) { DBGPRINT(("TERMSRV: NtCreateEvent (%ws) failed (%lx)\n",pszReconEvent, Status)); ASSERT(FALSE); pWinStation->hReconnectReadyEvent = NULL; goto done; }
}
/*
* For console, create is always successful - but do we need to * crank up the stack for the console session? */ if ( pWinStation->LogonId == 0 ) { pWinStation->CreateStatus = STATUS_SUCCESS; Status = pWinStation->CreateStatus; pWinStation->NeverConnected = FALSE; pWinStation->State = State_Connected;
/*
* Wait for create event to be triggered and get create status */ } else { Timeout = RtlEnlargedIntegerMultiply( 30000, -10000 ); UnlockWinStation( pWinStation ); Status = NtWaitForSingleObject( pWinStation->CreateEvent, FALSE, &Timeout ); if ( !RelockWinStation( pWinStation ) ) Status = STATUS_CTX_CLOSE_PENDING; if ( Status == STATUS_SUCCESS ) Status = pWinStation->CreateStatus;
NtClose( pWinStation->CreateEvent ); pWinStation->CreateEvent = NULL; } }
done: if (pExecuteBuffer != NULL) { MemFree(pExecuteBuffer); pExecuteBuffer = NULL; }
if (pszCsrStartEvent != NULL) { MemFree(pszCsrStartEvent); pszCsrStartEvent = NULL; }
if (pszReconEvent != NULL) { MemFree(pszReconEvent); pszReconEvent = NULL; }
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationStart Subsys PID=%d InitialProg PID=%d, Status=0x%x\n", pWinStation->WindowsSubSysProcessId, pWinStation->InitialCommandProcessId, Status ));
return Status; }
NTSTATUS WinStationCreateComplete(PWINSTATION pWinStation) {
NTSTATUS Status = STATUS_SUCCESS;
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationCreateComplete, %S (LogonId=%d)\n", pWinStation->WinStationName, pWinStation->LogonId ));
// Increment the total number of sessions created since TermSrv started.
// we don't count the console and listener sessions
if (pWinStation->LogonId > 0 && pWinStation->LogonId < 65536) { pWinStation->SessionSerialNumber = (ULONG) InterlockedIncrement(&g_TermSrvTotalSessions); }
if (!(pWinStation->Flags & WSF_LISTEN)) { Status = InitializeSessionNotification(pWinStation); if ( !NT_SUCCESS( Status ) ) { goto done; } }
/*
* Set WinStationEvent to indicate another WinStation has been created */ ENTERCRIT( &WinStationListLock ); NtSetEvent( WinStationEvent, NULL ); // Keep track of total session count for Load Balancing Indicator but
// don't count listen winstations
if (!(pWinStation->Flags & WSF_LISTEN)) WinStationTotalCount++;
LEAVECRIT( &WinStationListLock );
/*
* Notify clients of WinStation create */
NotifySystemEvent( WEVENT_CREATE );
done :
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationCreateComplete, %S (LogonId=%d) Status = 0x%x \n", pWinStation->WinStationName, pWinStation->LogonId, Status ));
return Status ; }
/*******************************************************************************
* WinStationRenameWorker * * Worker routine to rename a WinStation. * * ENTRY: * pWinStationNameOld (input) * Pointer to old WinStationName * pWinStationNameNew (input) * Pointer to new WinStationName ******************************************************************************/ NTSTATUS WinStationRenameWorker( PWINSTATIONNAME pWinStationNameOld, ULONG NameOldSize, PWINSTATIONNAME pWinStationNameNew, ULONG NameNewSize) { PWINSTATION pWinStation; PLIST_ENTRY Head, Next; NTSTATUS Status; ULONG ulNewNameLength;
/*
* Ensure new WinStation name is non-zero length */
//
// The new WinStationName is allocated by the RPC server stub and the
// size is sent over by the client (this is part of the existing interface.
// Therefore, it is sufficient to assert for the pWinStationNameNew to be
// non-null AND the New size to be non-zero. If it asserts, this is a serious
// problem with the rpc stub that should never happen in a released build.
//
// The old WinStationName also poses a problem. It is assumed in the code
// that follows that the old WinStationName is NULL terminated. The RPC
// interface does not say that. Which means the call to FindWinStation by
// name can potentially AV.
if (!( (pWinStationNameNew != 0 ) && (NameNewSize != 0 ) && !IsBadWritePtr( pWinStationNameNew, NameNewSize ) ) ) {
return( STATUS_CTX_WINSTATION_NAME_INVALID ); }
if (!( (pWinStationNameOld != 0 ) && (NameOldSize != 0 ) && !IsBadReadPtr( pWinStationNameOld, NameOldSize ) && !IsBadWritePtr( pWinStationNameOld, NameOldSize))) {
return( STATUS_CTX_WINSTATION_NAME_INVALID ); }
/*
* Find and lock the WinStation * (Note that we hold the WinStationList lock while changing the name.) */ // We will add a NULL Terminator to the end of the old winstation name
pWinStationNameOld[ NameOldSize - 1 ] = 0; pWinStationNameNew[ NameNewSize - 1 ] = 0;
/*
* Ensure new WinStation name is non-zero length and not too long */ ulNewNameLength = wcslen( pWinStationNameNew ); if ( ( ulNewNameLength == 0 ) || ( ulNewNameLength > WINSTATIONNAME_LENGTH ) ) return( STATUS_CTX_WINSTATION_NAME_INVALID );
pWinStation = FindWinStationByName( pWinStationNameOld, TRUE );
if ( pWinStation == NULL ) { LEAVECRIT( &WinStationListLock ); return( STATUS_CTX_WINSTATION_NOT_FOUND ); }
/*
* Verify that client has DELETE access */ Status = RpcCheckClientAccess( pWinStation, DELETE, FALSE ); if ( !NT_SUCCESS( Status ) ) { LEAVECRIT( &WinStationListLock ); ReleaseWinStation( pWinStation ); return( Status ); }
/*
* Now search the WinStation list to see if the new WinStation name * is already used. If so then this is an error. */ Head = &WinStationListHead; for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { PWINSTATION pWinStationTemp;
pWinStationTemp = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( !_wcsicmp( pWinStationTemp->WinStationName, pWinStationNameNew ) ) { LEAVECRIT( &WinStationListLock ); ReleaseWinStation( pWinStation ); return( STATUS_CTX_WINSTATION_NAME_COLLISION ); } }
/*
* Free the old name and set the new one, then release * the WinStationList lock and the WinStation mutex. */ wcsncpy( pWinStation->WinStationName, pWinStationNameNew, WINSTATIONNAME_LENGTH ); pWinStation->WinStationName[ WINSTATIONNAME_LENGTH ] = 0;
LEAVECRIT( &WinStationListLock ); ReleaseWinStation( pWinStation );
/*
* Notify clients of WinStation rename */ NotifySystemEvent( WEVENT_RENAME );
return STATUS_SUCCESS; }
/*******************************************************************************
* WinStationTerminate * * Terminate a WinStation. This involves causing the WinStation initial * program to logoff, terminating the initial program, and terminating * all subsystems. * * ENTRY: * pWinStation (input) * Pointer to WinStation to terminate ******************************************************************************/ VOID WinStationTerminate(PWINSTATION pWinStation) { WINSTATION_APIMSG msg; LARGE_INTEGER Timeout; NTSTATUS Status = 0; BOOL AllExited = FALSE; BOOL bDoDisconnectFailed = FALSE; int i, iLoop;
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationTerminate, %S (LogonId=%d)\n", pWinStation->WinStationName, pWinStation->LogonId ));
//
// Release filtered address
//
/*
if (pWinStation->pRememberedAddress != NULL) { Filter_RemoveOutstandingConnection( &pWinStation->pRememberedAddress->addr[0], pWinStation->pRememberedAddress->length ); MemFree(pWinStation->pRememberedAddress); pWinStation->pRememberedAddress = NULL; if( (ULONG)InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) { if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } }
} */
if (pWinStation->fOwnsConsoleTerminal) { CopyReconnectInfo(pWinStation, &ConsoleReconnectInfo);
}
/*
* If not already set, mark the WinStation as terminating. * This prevents our WinStation Terminate thread from waiting on * the initial program or Win32 subsystem processes. */ ENTERCRIT( &WinStationListLock ); if ( !pWinStation->Terminating ) { pWinStation->Terminating = TRUE; NtSetEvent( WinStationEvent, NULL ); }
if (!(pWinStation->StateFlags & WSF_ST_WINSTATIONTERMINATE)) { pWinStation->StateFlags |= WSF_ST_WINSTATIONTERMINATE; } else { DBGPRINT(("Termsrv: WinstationTerminate: Session %ld has already been terminated \n",pWinStation->LogonId)); LEAVECRIT( &WinStationListLock );
return; } LEAVECRIT( &WinStationListLock );
/*
* If WinStation is idle waiting for a connection, signal connect event * - this will return an error back to winlogon */ if ( pWinStation->ConnectEvent ) { NtSetEvent( pWinStation->ConnectEvent, NULL ); }
/*
* Stop any shadowing for this WinStation */ WinStationStopAllShadows( pWinStation );
/*
* Tell Win32 to disconnect. * This puts up the disconnected desktop among other things. */ if ( ( pWinStation->WinStationName[0] ) && ( !pWinStation->NeverConnected ) && ( !(pWinStation->Flags & WSF_LISTEN) ) && ( !(pWinStation->Flags & WSF_DISCONNECT) ) && ( !(pWinStation->StateFlags & WSF_ST_IN_DISCONNECT )) && ( (pWinStation->StateFlags & WSF_ST_CONNECTED_TO_CSRSS) ) ) {
msg.ApiNumber = SMWinStationDoDisconnect; msg.u.DoDisconnect.ConsoleShadowFlag = FALSE;
/*
* Insignia really wants the video driver to be notified before * the transport is closed. */ pWinStation->StateFlags |= WSF_ST_IN_DISCONNECT;
Status = SendWinStationCommand( pWinStation, &msg, 600 );
if (!NT_SUCCESS(Status)) { bDoDisconnectFailed = TRUE; }else { /*
* Tell csrss to notify winlogon for the disconnect. */
msg.ApiNumber = SMWinStationNotify; msg.WaitForReply = FALSE; msg.u.DoNotify.NotifyEvent = WinStation_Notify_Disconnect; Status = SendWinStationCommand( pWinStation, &msg, 0 );
pWinStation->StateFlags &= ~WSF_ST_CONNECTED_TO_CSRSS;
} }
/*
* In case, the winstation termination is called without logoff notification, we will have to * carry out the post logoff licensing. */ if (pWinStation->StateFlags & WSF_ST_LICENSING) { (VOID)LCProcessConnectionLogoff(pWinStation); pWinStation->StateFlags &= ~WSF_ST_LICENSING; }
/*
* Free Timers */ if ( pWinStation->fIdleTimer ) { IcaTimerClose( pWinStation->hIdleTimer ); pWinStation->fIdleTimer = FALSE; } if ( pWinStation->fLogonTimer ) { IcaTimerClose( pWinStation->hLogonTimer ); pWinStation->fLogonTimer = FALSE; } if ( pWinStation->fDisconnectTimer ) { IcaTimerClose( pWinStation->hDisconnectTimer ); pWinStation->fDisconnectTimer = FALSE; }
/*
* Free events */ if ((pWinStation->LogonId == 0 || g_bPersonalTS)) { if ( pWinStation->hWinmmConsoleAudioEvent) { CloseHandle(pWinStation->hWinmmConsoleAudioEvent); } if ( pWinStation->hRDPAudioDisabledEvent) { CloseHandle(pWinStation->hRDPAudioDisabledEvent); } }
/*
* Notify clients of WinStation delete * * This mimics what happened in 1.6, but the state of the winstation hasn't changed * yet and it's still in the list, so it's not "deleted". Maybe we should add * a State_Exiting. Right now, it's marked down when it loses the LPC connection * with the CSR. Later, it's removed from the list and another WEVENT_DELETE is sent. */ NotifySystemEvent( WEVENT_DELETE ); if (!(pWinStation->Flags & WSF_LISTEN)) { UnlockWinStation(pWinStation); RemoveSessionNotification( pWinStation->LogonId, pWinStation->SessionSerialNumber );
/*
* WinStationDeleteWorker, deletes the lock, which is always called after WinStationTerminate. * therefore we should always succeed in Relock here. */ RTL_VERIFY(RelockWinStation(pWinStation)); }
/*
* Terminate ICA stack */
if ( pWinStation->hStack && (!bDoDisconnectFailed) ) { /*
* Close the connection endpoint, if any */ if ( pWinStation->pEndpoint ) {
/*
* First notify Wsx that connection is going away */ WsxBrokenConnection( pWinStation );
IcaStackConnectionClose( pWinStation->hStack, &pWinStation->Config, pWinStation->pEndpoint, pWinStation->EndpointLength ); MemFree( pWinStation->pEndpoint ); pWinStation->pEndpoint = NULL; pWinStation->EndpointLength = 0; }
IcaStackTerminate( pWinStation->hStack );
} else{ pWinStation->StateFlags |= WSF_ST_DELAYED_STACK_TERMINATE; }
/*
* Flush the Win32 command queue. * If the Win32 command list is not empty, then loop through each * entry on the list and unlink it and trigger the wait event. */ while ( !IsListEmpty( &pWinStation->Win32CommandHead ) ) { PLIST_ENTRY Head; PCOMMAND_ENTRY pCommand;
Head = pWinStation->Win32CommandHead.Flink; pCommand = CONTAINING_RECORD( Head, COMMAND_ENTRY, Links ); RemoveEntryList( &pCommand->Links ); if ( !pCommand->pMsg->WaitForReply ) { ASSERT( pCommand->Event == NULL ); MemFree( pCommand ); } else { pCommand->Links.Flink = NULL; pCommand->pMsg->ReturnedStatus = STATUS_CTX_WINSTATION_BUSY; NtSetEvent( pCommand->Event, NULL ); } }
//
// close CsrStartEvent
//
if (pWinStation->CsrStartEventHandle != NULL) { NtClose(pWinStation->CsrStartEventHandle); }
//
// close hReconnectReadyEvent
//
if (pWinStation->hReconnectReadyEvent != NULL) { NtClose(pWinStation->hReconnectReadyEvent); }
/*
* Force initial program to exit if it hasn't already */ if ( pWinStation->InitialCommandProcess ) { DWORD WaitStatus;
/*
* If initial program has already exited, then we can skip this */ WaitStatus = WaitForSingleObject( pWinStation->InitialCommandProcess, 0 ); if ( WaitStatus != 0 ) {
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Terminating initial command, LogonId=%d\n", pWinStation->LogonId ));
//
// if we are asked to terminate winstation that initiated the shutdown
// there is no point in sending SMWinStationExitWindows to this window, as its
// winlogons main thread is already busy waiting for this (RpcWinStationShutdownSystem) lpc
//.
if (!ShutDownFromSessionID || ShutDownFromSessionID != pWinStation->LogonId) { /*
* Tell the WinStation to logoff */ msg.ApiNumber = SMWinStationExitWindows; msg.u.ExitWindows.Flags = EWX_LOGOFF | EWX_FORCE; Status = SendWinStationCommand( pWinStation, &msg, 10 ); if ( NT_SUCCESS( Status ) && ( pWinStation->InitialCommandProcess != NULL ) ) { ULONG i;
if ( ShutDownFromSessionID ) Timeout = RtlEnlargedIntegerMultiply( 1, -10000 ); else Timeout = RtlEnlargedIntegerMultiply( 2000, -10000 );
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Waiting for InitialCommand (ID=0x%x) to exit\n", pWinStation->InitialCommandProcessId ));
for ( i = 0; i < gLogoffTimeout; i++ ) { HANDLE CommandHandle = pWinStation->InitialCommandProcess;
UnlockWinStation( pWinStation ); Status = NtWaitForSingleObject( CommandHandle, FALSE, &Timeout ); RelockWinStation( pWinStation ); if ( Status == STATUS_SUCCESS ) break;
TRACE((hTrace,TC_ICASRV,TT_API1, "." ));
}
TRACE((hTrace,TC_ICASRV,TT_API1, "\nTERMSRV: Wait for InitialCommand to exit, Status=0x%x\n", Status )); } } else { // we are not going to have to terminate winlogon for the session that initiated shutdown.
Status = STATUS_UNSUCCESSFUL; }
/*
* If unable to connect to the WinStation, then we must use * the brute force method - just terminate the initial command. */ if ( ( Status != STATUS_SUCCESS ) && ( pWinStation->InitialCommandProcess != NULL ) ) {
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Waiting for InitialCommand to terminate\n" ));
Status = TerminateProcessAndWait( pWinStation->InitialCommandProcessId, pWinStation->InitialCommandProcess, 120 ); if ( Status != STATUS_SUCCESS ) { DBGPRINT(( "TERMSRV: InitialCommand failed to terminate, Status=%x\n", Status ));
/*
* We can fail terminating initial process if it is waiting * for a user validation in the Hard Error popup. In this case it is * Ok to proceceed as sending SMWinStationTerminate message bellow * will trigger Win32k cleanup code that will dismiss the popup. */ ASSERT(pWinStation->WindowsSubSysProcess); } } } }
/*
* Now check to see if there are any remaining processes in * the system other than CSRSS with this SessionId. If so, terminate them now. */
for (i = 0 ; i < 45; i++) {
ULONG NumTerminated = 0; AllExited = WinStationTerminateProcesses( pWinStation, &NumTerminated );
/*
* If we found any processes other than CSRSS that had to be terminated, we * have to re-enumerate all the process and make sure that no new processes * in this session were created in the windows between the call to NtQuerySystemInformation * and terminating all the found processes. If we only find CSRSS we don't have to * re-enumerate since CSRSS does not create any processes. */ if (AllExited && (NumTerminated == 0)) { break; }
/*
* This is a hack to give enough time to processess to terminate */ Sleep(2*1000); }
if (pWinStation->WindowsSubSysProcess) { /*
* Send terminate message to this subsystem */ msg.ApiNumber = SMWinStationTerminate; /*
* We used to not wait for this. However, if the reverse LPC is * hung, the CSR is not going to exit anyway and we don't want * to silently forget about the WinStation. (It takes up memory.) * * Also, if we kill the thread prematurely W32 is never going to exit. */ Status = SendWinStationCommand( pWinStation, &msg, -1 );
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Call to SMWinStationTerminate returned Status=0x%x\n", Status));
if ((Status != STATUS_SUCCESS) && (Status != STATUS_CTX_CLOSE_PENDING) ) { SetRefLockDeleteProc(&pWinStation->Lock, WinStationZombieProc); }
/*
* Now check to see if there are any remaining processes in * the system other than CSRSS with this SessionId. If so, terminate them now. */
for (i = 0 ; i < 45; i++) {
ULONG NumTerminated = 0; AllExited = WinStationTerminateProcesses( pWinStation, &NumTerminated );
/*
* If we found any processes other than CSRSS that had to be terminated, we * have to re-enumerate all the process and make sure that no new processes * in this session were created in the windows between the call to NtQuerySystemInformation * and terminating all the found processes. If we only find CSRSS we don't have to * re-enumerate since CSRSS does not create any processes. */ if (AllExited && (NumTerminated == 0)) { break; }
/*
* This is a hack to give enough time to processess to terminate */ Sleep(2*1000); }
/*
* Force the windows subsystem to exit. Only terminate CSRSS it all other processes * have terminated */ if ( AllExited ) {
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: All process exited in Session %d\n",pWinStation->LogonId ));
/*
* Wait for the subsystem to exit */ if ( NT_SUCCESS(Status) || ( Status == STATUS_CTX_WINSTATION_BUSY ) || (Status == STATUS_CTX_CLOSE_PENDING ) ) {
ASSERT(!(pWinStation->Flags & WSF_LISTEN)); // ENTERCRIT( &WinStationStartCsrLock );
// Status = SmStopCsr( IcaSmApiPort,
// pWinStation->LogonId );
// LEAVECRIT( &WinStationStartCsrLock );
// DBGPRINT(( "TERMSRV: SmStopCsr on CSRSS for Session=%d returned Status=%x\n",
// pWinStation->LogonId, Status ));
//
// ASSERT(NT_SUCCESS(Status));
// if (!NT_SUCCESS(Status)) {
// DBGPRINT(( "TERMSRV: SmStopCsr Failed for Session=%d returned Status=%x\n",
// pWinStation->LogonId, Status ));
// DbgBreakPoint();
//}
} } else {
DBGPRINT(("TERMSRV: Did not terminate all the session processes\n")); SetRefLockDeleteProc(&pWinStation->Lock, WinStationZombieProc);
// DbgBreakPoint();
} }
}
/*******************************************************************************
* WinStationTerminateProcesses * * Terminate all processes executing on the specified WinStation ******************************************************************************/ BOOL WinStationTerminateProcesses( PWINSTATION pWinStation, ULONG *pNumTerminated) { PCHAR pBuffer; ULONG ByteCount; NTSTATUS Status; PSYSTEM_PROCESS_INFORMATION ProcessInfo; UNICODE_STRING CsrssName; UNICODE_STRING NtsdName; BOOL retval = TRUE; WCHAR ProcessName[MAX_PATH]; SYSTEM_SESSION_PROCESS_INFORMATION SessionProcessInfo; ULONG retlen = 0;
ByteCount = 32 * 1024;
*pNumTerminated = 0;
SessionProcessInfo.SessionId = pWinStation->LogonId; for ( ; ; ) { if ( (pBuffer = MemAlloc( ByteCount )) == NULL ) return (FALSE);
SessionProcessInfo.Buffer = pBuffer; SessionProcessInfo.SizeOfBuf = ByteCount;
/*
* get process info */
Status = NtQuerySystemInformation( SystemSessionProcessInformation, &SessionProcessInfo, sizeof(SessionProcessInfo), &retlen );
if ( NT_SUCCESS( Status ) ) break;
/*
* Make sure buffer is big enough */ MemFree( pBuffer ); if ( Status != STATUS_INFO_LENGTH_MISMATCH ) return (FALSE); ByteCount *= 2; }
if (retlen == 0) { MemFree(pBuffer); return TRUE; }
RtlInitUnicodeString(&CsrssName,L"CSRSS");
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer; for ( ; ; ) { HANDLE ProcessHandle; CLIENT_ID ClientId; OBJECT_ATTRIBUTES ObjA;
if (RtlPrefixUnicodeString(&CsrssName,&(ProcessInfo->ImageName),TRUE)) { if (ProcessInfo->NextEntryOffset == 0) break; ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) ((ULONG_PTR)ProcessInfo + ProcessInfo->NextEntryOffset); continue; }
RtlInitUnicodeString(&NtsdName,L"ntsd"); if (! RtlPrefixUnicodeString(&NtsdName,&(ProcessInfo->ImageName),TRUE) ) { // If we found any process other than CSRSS and ntsd.exe, bump the
// the count
(*pNumTerminated) += 1; }
/*
* Found a process with a matching LogonId. * Attempt to open the process and terminate it. */ TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: TerminateProcesses, found processid 0x%x for LogonId %d\n", ProcessInfo->UniqueProcessId, ProcessInfo->SessionId )); TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Process Name %ws for LogonId %d\n", ProcessInfo->ImageName.Buffer, ProcessInfo->SessionId ));
ClientId.UniqueThread = 0; ClientId.UniqueProcess = (HANDLE)ProcessInfo->UniqueProcessId;
InitializeObjectAttributes( &ObjA, NULL, 0, NULL, NULL ); Status = NtOpenProcess( &ProcessHandle, PROCESS_ALL_ACCESS, &ObjA, &ClientId ); if (!NT_SUCCESS(Status)) { DBGPRINT(("TERMSRV: Unable to open processid 0x%x, status=0x%x\n", ProcessInfo->UniqueProcessId, Status )); retval = FALSE; } else { Status = TerminateProcessAndWait( ProcessInfo->UniqueProcessId, ProcessHandle, 60 ); NtClose( ProcessHandle ); if ( Status != STATUS_SUCCESS ) { DBGPRINT(("TERMSRV: Unable to terminate processid 0x%x, status=0x%x\n", ProcessInfo->UniqueProcessId, Status ));
retval = FALSE; } }
if ( ProcessInfo->NextEntryOffset == 0 ) break; ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) ((ULONG_PTR)ProcessInfo + ProcessInfo->NextEntryOffset); }
/*
* free buffer */ MemFree( pBuffer );
return retval; }
/*******************************************************************************
* WinStationDeleteWorker * * Delete a WinStation. ******************************************************************************/ VOID WinStationDeleteWorker(PWINSTATION pWinStation) { TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationDeleteWorker, %S (LogonId=%d)\n", pWinStation->WinStationName, pWinStation->LogonId ));
/*
* If this is the last reference, then * Initial program and all subsystems should be terminated by now. */ ENTERCRIT( &WinStationListLock ); ASSERT( (pWinStation->Links.Flink != NULL) && (pWinStation->Links.Blink != NULL)); RemoveEntryList( &pWinStation->Links ); #if DBG
pWinStation->Links.Flink = pWinStation->Links.Blink = NULL; #endif
// Keep track of total session count for Load Balancing Indicator but don't
// track listen winstations
if (!(pWinStation->Flags & WSF_LISTEN)) WinStationTotalCount--;
// If we're resetting a disconnected session then adjust LB counter
if (pWinStation->State == State_Disconnected) { WinStationDiscCount--; }
LEAVECRIT( &WinStationListLock );
/*
* Unlock WinStation and delete it */ DeleteRefLock( &pWinStation->Lock );
/*
* Notify clients of deletion */ NotifySystemEvent( WEVENT_DELETE ); }
/*******************************************************************************
* WinStationDeleteProc * * Delete the WinStation containing the specified RefLock. * * ENTRY: * pLock (input) * Pointer to RefLock of WinStation to delete ******************************************************************************/ VOID WinStationDeleteProc(PREFLOCK pLock) { PWINSTATION pWinStation; ICA_TRACE IcaTrace; NTSTATUS Status = STATUS_SUCCESS;
/*
* See if we need to wakeup IdleControlThread to maintain Console session */
if ((USER_SHARED_DATA->ActiveConsoleId == -1) && (gConsoleCreationDisable == 0) ) { NtSetEvent(WinStationIdleControlEvent, NULL); }
/*
* Get a pointer to the containing WinStation */ pWinStation = CONTAINING_RECORD( pLock, WINSTATION, Lock );
/*
* Release filtered address */
if (pWinStation->pRememberedAddress != NULL) { Filter_RemoveOutstandingConnection( &pWinStation->pRememberedAddress->addr[0], pWinStation->pRememberedAddress->length ); MemFree(pWinStation->pRememberedAddress); pWinStation->pRememberedAddress = NULL; if( (ULONG)InterlockedDecrement( &NumOutStandingConnect ) == MaxOutStandingConnect ) { if (hConnectEvent != NULL) { SetEvent(hConnectEvent); } }
}
/*
* Release last remote ip address */
if( pWinStation->pLastClientAddress != NULL ) { MemFree( pWinStation->pLastClientAddress ); pWinStation->pLastClientAddress = NULL; }
/*
* If this hasn't yet been cleaned up do it now. */ if (pWinStation->ConnectEvent) { NtClose( pWinStation->ConnectEvent ); pWinStation->ConnectEvent = NULL; } if (pWinStation->CreateEvent) { NtClose( pWinStation->CreateEvent ); pWinStation->CreateEvent = NULL; }
if (pWinStation->SessionInitializedEvent) { CloseHandle(pWinStation->SessionInitializedEvent); pWinStation->SessionInitializedEvent = NULL; }
/*
* In the case where we timed out disconnecting the session we had * to delay the stack unload till here to avoid situation where Win32k * Display driver believe the session is still connected while the WD * is already unloaded. */ if ( pWinStation->hStack && (pWinStation->StateFlags & WSF_ST_DELAYED_STACK_TERMINATE) ) { pWinStation->StateFlags &= ~WSF_ST_DELAYED_STACK_TERMINATE;
/*
* Close the connection endpoint, if any */ if ( pWinStation->pEndpoint ) { /*
* First notify Wsx that connection is going away */ WsxBrokenConnection( pWinStation );
IcaStackConnectionClose( pWinStation->hStack, &pWinStation->Config, pWinStation->pEndpoint, pWinStation->EndpointLength ); MemFree( pWinStation->pEndpoint ); pWinStation->pEndpoint = NULL; pWinStation->EndpointLength = 0; }
IcaStackTerminate( pWinStation->hStack ); } /* close cdm */ if ( pWinStation->pWsx && pWinStation->pWsx->pWsxCdmDisconnect ) { pWinStation->pWsx->pWsxCdmDisconnect( pWinStation->pWsxContext, pWinStation->LogonId, pWinStation->hIca ); }
/*
* Call WinStation rundown function before killing the stack */ if ( pWinStation->pWsxContext ) { if ( pWinStation->pWsx && pWinStation->pWsx->pWsxWinStationRundown ) { pWinStation->pWsx->pWsxWinStationRundown( pWinStation->pWsxContext ); } pWinStation->pWsxContext = NULL; }
/*
* Close ICA stack and device handles */ if ( pWinStation->hStack ) { IcaStackClose( pWinStation->hStack ); pWinStation->hStack = NULL; }
if ( pWinStation->hIca ) { /* close trace */ memset( &IcaTrace, 0, sizeof(IcaTrace) ); (void) IcaIoControl( pWinStation->hIca, IOCTL_ICA_SET_TRACE, &IcaTrace, sizeof(IcaTrace), NULL, 0, NULL );
/* close handle */ IcaClose( pWinStation->hIca ); pWinStation->hIca = NULL; }
/*
* Close various ICA channel handles */ if ( pWinStation->hIcaBeepChannel ) { (void) IcaChannelClose( pWinStation->hIcaBeepChannel ); pWinStation->hIcaBeepChannel = NULL; }
if ( pWinStation->hIcaThinwireChannel ) { (void) IcaChannelClose( pWinStation->hIcaThinwireChannel ); pWinStation->hIcaThinwireChannel = NULL; }
if ( pWinStation->hConnectThread ) { NtClose( pWinStation->hConnectThread ); pWinStation->hConnectThread = NULL; }
/*
* Free security structures */ WinStationFreeSecurityDescriptor( pWinStation );
if ( pWinStation->pUserSid ) { pWinStation->pProfileSid = pWinStation->pUserSid; pWinStation->pUserSid = NULL; }
if (pWinStation->pProfileSid) { WinstationUnloadProfile(pWinStation); MemFree( pWinStation->pProfileSid ); pWinStation->pProfileSid = NULL; }
/*
* Cleanup UserToken */ if ( pWinStation->UserToken ) { NtClose( pWinStation->UserToken ); pWinStation->UserToken = NULL; }
if (pWinStation->LogonId > 0) { ENTERCRIT( &WinStationStartCsrLock ); Status = SmStopCsr( IcaSmApiPort, pWinStation->LogonId ); LEAVECRIT( &WinStationStartCsrLock ); } // Clean up the New Client Credentials struct for Long UserName
if (pWinStation->pNewClientCredentials != NULL) { MemFree(pWinStation->pNewClientCredentials); pWinStation->pNewClientCredentials = NULL; }
// Clean up the updated Notification Credentials
if (pWinStation->pNewNotificationCredentials != NULL) { MemFree(pWinStation->pNewNotificationCredentials); pWinStation->pNewNotificationCredentials = NULL; }
/*
* Close the handles to the initial command process and sub-system process here. */ if (pWinStation->WindowsSubSysProcess) { NtClose( pWinStation->WindowsSubSysProcess ); pWinStation->WindowsSubSysProcess = NULL; }
// Close the InitialCommandProcess
if (pWinStation->InitialCommandProcess) { NtClose( pWinStation->InitialCommandProcess ); pWinStation->InitialCommandProcess = NULL; }
/*
* Cleanup licensing context */ LCDestroyContext(pWinStation);
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: SmStopCsr on CSRSS for Session=%d returned Status=%x\n", pWinStation->LogonId, Status )); ASSERT(NT_SUCCESS(Status));
if (!NT_SUCCESS(Status)) { DBGPRINT(( "TERMSRV: SmStopCsr Failed for Session=%d returned Status=%x\n", pWinStation->LogonId, Status )); // DbgBreakPoint();
ENTERCRIT( &WinStationZombieLock ); InsertTailList( &ZombieListHead, &pWinStation->Links ); LEAVECRIT( &WinStationZombieLock ); return; }
/*
* Zero WinStation name buffer */ RtlZeroMemory( pWinStation->WinStationName, sizeof(pWinStation->WinStationName) );
MemFree( pWinStation ); }
/*******************************************************************************
* WinStationZombieProc * * Puts WinStation containing the specified RefLock in the zombie list. * * ENTRY: * pLock (input) * Pointer to RefLock of WinStation to delete ******************************************************************************/ VOID WinStationZombieProc(PREFLOCK pLock) { PWINSTATION pWinStation;
pWinStation = CONTAINING_RECORD( pLock, WINSTATION, Lock ); ENTERCRIT( &WinStationZombieLock ); InsertTailList( &ZombieListHead, &pWinStation->Links ); LEAVECRIT( &WinStationZombieLock ); }
/*******************************************************************************
* CopyReconnectInfo * * * ENTRY: ******************************************************************************/ BOOL CopyReconnectInfo(PWINSTATION pWinStation, PRECONNECT_INFO pReconnectInfo) { NTSTATUS Status;
RtlZeroMemory( pReconnectInfo, sizeof(*pReconnectInfo) );
/*
* Save WinStation name and configuration data. */ RtlCopyMemory( pReconnectInfo->WinStationName, pWinStation->WinStationName, sizeof(WINSTATIONNAME) ); RtlCopyMemory( pReconnectInfo->ListenName, pWinStation->ListenName, sizeof(WINSTATIONNAME) ); RtlCopyMemory( pReconnectInfo->ProtocolName, pWinStation->ProtocolName, sizeof(pWinStation->ProtocolName) ); RtlCopyMemory( pReconnectInfo->DisplayDriverName, pWinStation->DisplayDriverName, sizeof(pWinStation->DisplayDriverName) ); pReconnectInfo->Config = pWinStation->Config; pReconnectInfo->Client = pWinStation->Client;
/*
* Open a new TS connection to temporarily attach the stack to. */ Status = IcaOpen( &pReconnectInfo->hIca ); if (Status != STATUS_SUCCESS ) { return FALSE; }
Status = IcaStackDisconnect( pWinStation->hStack, pReconnectInfo->hIca, NULL ); if ( !NT_SUCCESS( Status ) ){ IcaClose( pReconnectInfo->hIca ); pReconnectInfo->hIca = NULL; return FALSE; }
/*
* Save stack and endpoint data */ pReconnectInfo->hStack = pWinStation->hStack; pReconnectInfo->pEndpoint = pWinStation->pEndpoint; pReconnectInfo->EndpointLength = pWinStation->EndpointLength;
/*
* Indicate no stack or connection endpoint for this WinStation */ pWinStation->hStack = NULL; pWinStation->pEndpoint = NULL; pWinStation->EndpointLength = 0;
/*
* Reopen a stack for this WinStation */ Status = IcaStackOpen( pWinStation->hIca, Stack_Primary, (PROC)WsxStackIoControl, pWinStation, &pWinStation->hStack );
/*
* Save the licensing stuff to move to other winstation */ if ( pWinStation->pWsx && pWinStation->pWsx->pWsxDuplicateContext ) { pReconnectInfo->pWsx = pWinStation->pWsx; pWinStation->pWsx->pWsxDuplicateContext( pWinStation->pWsxContext, &pReconnectInfo->pWsxContext ); }
/*
* Copy console owner info */ pReconnectInfo->fOwnsConsoleTerminal = pWinStation->fOwnsConsoleTerminal;
/*
* Copy the notification Credentials to move to other winstation */ if (pWinStation->pNewNotificationCredentials) { pReconnectInfo->pNotificationCredentials = pWinStation->pNewNotificationCredentials; } else { pReconnectInfo->pNotificationCredentials = NULL; }
/*
* Copy the remote client ip address to move to the other winstation */
if( pReconnectInfo->pRememberedAddress != NULL ) { MemFree( pReconnectInfo->pRememberedAddress ); pReconnectInfo->pRememberedAddress = NULL; }
if( pWinStation->pLastClientAddress != NULL ) { pReconnectInfo->pRememberedAddress = ( PREMEMBERED_CLIENT_ADDRESS )MemAlloc( sizeof( REMEMBERED_CLIENT_ADDRESS ) + pWinStation->pLastClientAddress->length - 1);
if( pReconnectInfo->pRememberedAddress != NULL ) { RtlCopyMemory( &pReconnectInfo->pRememberedAddress->addr[0] , &pWinStation->pLastClientAddress->addr[0] , pWinStation->pLastClientAddress->length );
pReconnectInfo->pRememberedAddress->length = pWinStation->pLastClientAddress->length; } } return TRUE;
}
/*******************************************************************************
* WinStationDoDisconnect * * Send disconnect message to a WinStation and optionally close connection * * ENTRY: * pWinStation (input) * Pointer to WinStation to disconnect * pReconnectInfo (input) OPTIONAL * Pointer to RECONNECT_INFO buffer * If NULL, this is a terminate disconnect. * * EXIT: * STATUS_SUCCESS - if successful * STATUS_CTX_WINSTATION_BUSY - if session is already disconnected, or busy ******************************************************************************/ NTSTATUS WinStationDoDisconnect( PWINSTATION pWinStation, PRECONNECT_INFO pReconnectInfo, BOOLEAN bSyncNotify) { WINSTATION_APIMSG DisconnectMsg; NTSTATUS Status; ULONG ulTimeout; BOOLEAN fOwnsConsoleTerminal = pWinStation->fOwnsConsoleTerminal; FILETIME DiscTime; DWORD SessionID; BOOLEAN bInformSessionDirectory = FALSE; TS_AUTORECONNECTINFO SCAutoReconnectInfo; ULONG BytesGot; BOOLEAN noTimeOutForConsoleOrSession0;
// We need to prevent from WinStationDoDisconnect being called twice
if ( pWinStation->State == State_Disconnected || pWinStation->StateFlags & WSF_ST_IN_DISCONNECT) { // The session is already disconnected.
// BUBUG a specific error code STATUS_CTX_SESSION_DICONNECTED would be better.
return (STATUS_CTX_WINSTATION_BUSY); } pWinStation->StateFlags |= WSF_ST_IN_DISCONNECT;
// console session or session0 behave like the physical console, they are special, no time out.
noTimeOutForConsoleOrSession0 = ( pWinStation->LogonId == 0 ) || (pWinStation->LogonId == (USER_SHARED_DATA->ActiveConsoleId) );
if (! noTimeOutForConsoleOrSession0 ) { /*
* Start disconnect timer if enabled */ if ( ulTimeout = pWinStation->Config.Config.User.MaxDisconnectionTime ) { if ( !pWinStation->fDisconnectTimer ) { Status = IcaTimerCreate( 0, &pWinStation->hDisconnectTimer ); if ( NT_SUCCESS( Status ) ) pWinStation->fDisconnectTimer = TRUE; else DBGPRINT(("xxxWinStationDisconnect - failed to create timer \n")); } if ( pWinStation->fDisconnectTimer ) IcaTimerStart( pWinStation->hDisconnectTimer, DisconnectTimeout, LongToPtr( pWinStation->LogonId ), ulTimeout ); } }
/*
* Stop any shadowing for this WinStation */ WinStationStopAllShadows( pWinStation );
/*
* Tell Win32k about the disconnect */ if (pWinStation->StateFlags & WSF_ST_CONNECTED_TO_CSRSS) { DisconnectMsg.ApiNumber = SMWinStationDoDisconnect; DisconnectMsg.u.DoDisconnect.ConsoleShadowFlag = FALSE;
Status = SendWinStationCommand( pWinStation, &DisconnectMsg, 600 ); if ( !NT_SUCCESS(Status) ) { TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: CSR DoDisconnect failed LogonId=%d Status=0x%x\n", pWinStation->LogonId, Status )); goto badwin32disconnect; } else {
ULONG WaitTime = 0;
/*
* Tell csrss to notify winlogon for the disconnect. */ if (pWinStation->UserName[0] != L'\0') { DisconnectMsg.WaitForReply = TRUE; WaitTime = 10; } else { DisconnectMsg.WaitForReply = FALSE; } DisconnectMsg.ApiNumber = SMWinStationNotify; if (bSyncNotify) { DisconnectMsg.u.DoNotify.NotifyEvent = WinStation_Notify_SyncDisconnect; } else { DisconnectMsg.u.DoNotify.NotifyEvent = WinStation_Notify_Disconnect; } Status = SendWinStationCommand( pWinStation, &DisconnectMsg, WaitTime );
pWinStation->StateFlags &= ~WSF_ST_CONNECTED_TO_CSRSS; pWinStation->fOwnsConsoleTerminal = FALSE; } }
/*
* close cdm */ if ( pWinStation->pWsx && pWinStation->pWsx->pWsxCdmDisconnect ) { pWinStation->pWsx->pWsxCdmDisconnect( pWinStation->pWsxContext, pWinStation->LogonId, pWinStation->hIca ); }
/*
* If a reconnect info struct has been specified, then this is NOT * a terminate disconnect. Save the current WinStation name, * WinStation and client configuration info, and license data. * Also disconnect the current stack and save the stack handle * and connection endpoint data. */ if ( pReconnectInfo || fOwnsConsoleTerminal) {
if ((pReconnectInfo == NULL) && fOwnsConsoleTerminal) { pReconnectInfo = &ConsoleReconnectInfo; if (ConsoleReconnectInfo.hIca) { CleanupReconnect(&ConsoleReconnectInfo); RtlZeroMemory(&ConsoleReconnectInfo,sizeof(RECONNECT_INFO)); } }
if (!CopyReconnectInfo(pWinStation, pReconnectInfo)) { Status = STATUS_UNSUCCESSFUL; goto badstackopen;
} /*
* Copy console owner info */ pReconnectInfo->fOwnsConsoleTerminal = fOwnsConsoleTerminal;
/*
* This is a terminate disconnect. * If there is a connection endpoint, then close it now. */ } else if (pWinStation->pEndpoint ) {
/*
* First grab any autoreconnect info state and save it * in the winstation */
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Disconnecting - grabbing SC autoreconnect from stack\n"));
if (pWinStation->pWsx && pWinStation->pWsx->pWsxEscape) { Status = pWinStation->pWsx->pWsxEscape( pWinStation->pWsxContext, GET_SC_AUTORECONNECT_INFO, NULL, 0, &SCAutoReconnectInfo, sizeof(SCAutoReconnectInfo), &BytesGot);
if (NT_SUCCESS(Status)) {
//
// Valid the length of the SC info and save it into the winstation
// this will be used later on. We need to grab the info now
// before the stack handle is closed as we won't be able to IOCTL
// down to the stack at autoreconnect time.
//
if (SCAutoReconnectInfo.cbAutoReconnectInfo == sizeof(pWinStation->AutoReconnectInfo.ArcRandomBits)) {
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Disconnecting - got SC ARC from stack\n"));
pWinStation->AutoReconnectInfo.Valid = TRUE; memcpy(&pWinStation->AutoReconnectInfo.ArcRandomBits, &SCAutoReconnectInfo.AutoReconnectInfo, sizeof(pWinStation->AutoReconnectInfo.ArcRandomBits)); } else { TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: Disconnecting - got invalid len SC ARC from stack\n")); ResetAutoReconnectInfo(pWinStation); } } else {
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: Disconnecting - did not get SC ARC from stack\n")); ResetAutoReconnectInfo(pWinStation); } }
/*
* First notify Wsx that connection is going away */ WsxBrokenConnection( pWinStation );
if (pWinStation->hStack != NULL) { Status = IcaStackConnectionClose( pWinStation->hStack, &pWinStation->Config, pWinStation->pEndpoint, pWinStation->EndpointLength ); ASSERT( NT_SUCCESS(Status) ); if ( !NT_SUCCESS(Status) ) { TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: StackConnectionClose failed LogonId=%d Status=0x%x\n", pWinStation->LogonId, Status )); } }
MemFree( pWinStation->pEndpoint ); pWinStation->pEndpoint = NULL; pWinStation->EndpointLength = 0;
/*
* Close the stack and reopen it. * What we really need is a function to unload the stack drivers * but leave the stack handle open. */
if (pWinStation->hStack != NULL) { Status = IcaStackClose( pWinStation->hStack ); ASSERT( NT_SUCCESS( Status ) ); pWinStation->hStack = NULL; }
Status = IcaStackOpen( pWinStation->hIca, Stack_Primary, (PROC)WsxStackIoControl, pWinStation, &pWinStation->hStack );
/*
* Since this is a terminate disconnect, clear all client * license data and indicate it no longer holds a license. */ if ( pWinStation->pWsx && pWinStation->pWsx->pWsxClearContext ) { pWinStation->pWsx->pWsxClearContext( pWinStation->pWsxContext ); } /*
* Session 0, we want to get rid of any protocol extension so that next remote * connection could happen with a different protocol. */ if (pWinStation->LogonId == 0 ) { if ( pWinStation->pWsxContext ) { if ( pWinStation->pWsx && pWinStation->pWsx->pWsxWinStationRundown ) { pWinStation->pWsx->pWsxWinStationRundown( pWinStation->pWsxContext ); } pWinStation->pWsxContext = NULL; } pWinStation->pWsx = NULL; pWinStation->Client.ProtocolType = PROTOCOL_CONSOLE; } }
/*
* Cancel timers */ if ( pWinStation->fIdleTimer ) { pWinStation->fIdleTimer = FALSE; IcaTimerClose( pWinStation->hIdleTimer ); } if ( pWinStation->fLogonTimer ) { pWinStation->fLogonTimer = FALSE; IcaTimerClose( pWinStation->hLogonTimer ); }
// Send Audit Information only for actual disconnects
if (pWinStation->UserName && (wcslen(pWinStation->UserName) > 0)) { AuditEvent( pWinStation, SE_AUDITID_SESSION_DISCONNECTED ); }
// before we change state to disconnect, we want to give repopulating thread
// enough time to pick up winstation/report to SD, then this thread
// can notify SD to change state to disconnect.
// wait here before we ENTERCRIT() because repopulate thread
// also doing the same sequence.
if (!g_bPersonalTS && g_fAppCompat && g_bAdvancedServer) { SessDirWaitForRepopulate(); }
{ ENTERCRIT( &WinStationListLock ); (VOID) NtQuerySystemTime( &pWinStation->DisconnectTime );
if ((pWinStation->State == State_Active) || (pWinStation->State == State_Shadow)) { // If the session was active or in a shadow state and is being
// disconnected...
//
// Copy off the session ID and disconnection FileTime for the
// session directory call below. We do not want to hold locks when
// calling the directory interface.
memcpy(&DiscTime, &pWinStation->DisconnectTime, sizeof(DiscTime)); SessionID = pWinStation->LogonId;
// Set flag that we need to notify the session directory.
bInformSessionDirectory = TRUE; }
pWinStation->State = State_Disconnected; RtlZeroMemory( pWinStation->WinStationName, sizeof(pWinStation->WinStationName) ); RtlZeroMemory( pWinStation->ListenName, sizeof(pWinStation->ListenName) );
// Keep track of disconnected session count for Load Balancing
// Indicator.
WinStationDiscCount++;
LEAVECRIT( &WinStationListLock );
NotifySystemEvent( WEVENT_DISCONNECT | WEVENT_STATECHANGE ); }
// Call the session directory to inform of the disconnection.
if (!g_bPersonalTS && g_fAppCompat && g_bAdvancedServer && bInformSessionDirectory) SessDirNotifyDisconnection(SessionID, DiscTime);
TRACE((hTrace, TC_ICASRV, TT_API1, "TERMSRV: WinStationDoDisconnect, rc=0x0\n" ));
Status = NotifyDisconnect(pWinStation, fOwnsConsoleTerminal); if ( !NT_SUCCESS(Status) ) { DBGPRINT(("NotifyConsoleDisconnect failed, SessionId = %d, Status = " "%d", pWinStation->LogonId, Status)); } pWinStation->StateFlags &= ~WSF_ST_IN_DISCONNECT;
return STATUS_SUCCESS;
/*=============================================================================
== Error returns =============================================================================*/
badstackopen: badwin32disconnect: TRACE((hTrace, TC_ICASRV, TT_API1, "TERMSRV: WinStationDoDisconnect, rc=0x%x\n", Status )); pWinStation->StateFlags &= ~WSF_ST_IN_DISCONNECT; return Status; }
/*******************************************************************************
* ImperonateClient * * Giving a client primary, make the calling thread impersonate the client * * ENTRY: * ClientToken (input) * A client primary token * pImpersonationToken (output) * Pointer to an impersonation token ******************************************************************************/ NTSTATUS _ImpersonateClient(HANDLE ClientToken, HANDLE *pImpersonationToken) { SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; OBJECT_ATTRIBUTES ObjA; NTSTATUS Status;
//
// ClientToken is a primary token - create an impersonation token
// version of it so we can set it on our thread
//
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, pImpersonationToken ); if ( !NT_SUCCESS( Status ) ) { TRACE( ( hTrace, TC_ICASRV, TT_ERROR, "ImpersonateClient: cannot get impersonation token: 0x%x\n", Status ) ); return( Status ); }
//
// Impersonate the client
//
Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, ( PVOID )pImpersonationToken, ( ULONG )sizeof( HANDLE ) ); if ( !NT_SUCCESS( Status ) ) { TRACE( ( hTrace, TC_ICASRV, TT_ERROR, "ImpersonateClient: cannot impersonate client: 0x%x\n", Status ) ); }
return Status; }
/*******************************************************************************
* WinStationDoReconnect * * Send connect Api message to a WinStation. * * ENTRY: * pWinStation (input) * Pointer to WinStation to connect * pReconnectInfo (input) * Pointer to RECONNECT_INFO buffer ******************************************************************************/ NTSTATUS WinStationDoReconnect( PWINSTATION pWinStation, PRECONNECT_INFO pReconnectInfo) { WINSTATION_APIMSG ReconnectMsg; NTSTATUS Status; BOOLEAN fDisableCdm; BOOLEAN fDisableCpm; BOOLEAN fDisableLPT; BOOLEAN fDisableCcm; BOOLEAN fDisableClip; SHADOWCLASS Shadow; NTSTATUS TempStatus; PWINSTATIONCONFIG2 pCurConfig = NULL; PWINSTATIONCLIENT pCurClient = NULL;
// WinStation should not currently be connected
ASSERT( pWinStation->pEndpoint == NULL );
// Mark that Reconnect is still pending for this winstation
pWinStation->fReconnectPending = TRUE;
// Check if we r going to reconnect a session to the Console - this Flag is used in SendWinStationCommand
pWinStation->fReconnectingToConsole = pReconnectInfo->fOwnsConsoleTerminal ;
// Save the shadow state
Shadow = pWinStation->Config.Config.User.Shadow;
//
// Allocate and initialize CurConfig struct
//
if ( (pCurConfig = MemAlloc( sizeof(WINSTATIONCONFIG2) )) == NULL ) { Status = STATUS_NO_MEMORY; goto nomem; } RtlZeroMemory( pCurConfig, sizeof(WINSTATIONCONFIG2) );
//
// Allocate and initialize CurClient struct
//
if ( (pCurClient = MemAlloc( sizeof(WINSTATIONCLIENT) )) == NULL ) { Status = STATUS_NO_MEMORY; goto nomem; } RtlZeroMemory( pCurClient, sizeof(WINSTATIONCLIENT) );
//
// Config info has to be set prior to calling CSRSS. CSRSS notifies winlogon
// which in turn sends reconnect messages to notification dlls. We query
// protocol info from termsrv notification dll which is stored in config
// data
//
*pCurConfig = pWinStation->Config; pWinStation->Config = pReconnectInfo->Config;
*pCurClient = pWinStation->Client; pWinStation->Client = pReconnectInfo->Client;
if ((pWinStation->LogonId == 0) && (pWinStation->UserName[0] == L'\0')) { ReconnectMsg.ApiNumber = SMWinStationNotify; ReconnectMsg.WaitForReply = TRUE; ReconnectMsg.u.DoNotify.NotifyEvent = WinStation_Notify_PreReconnect; Status = SendWinStationCommand( pWinStation, &ReconnectMsg, 60 ); }
/*
* Unconditionally, we will send the PreReconnectDesktopSwitch event. */ ReconnectMsg.ApiNumber = SMWinStationNotify; ReconnectMsg.WaitForReply = TRUE; ReconnectMsg.u.DoNotify.NotifyEvent = WinStation_Notify_PreReconnectDesktopSwitch; Status = SendWinStationCommand( pWinStation, &ReconnectMsg, 60 );
/*
* Close the current stack and reconnect the saved stack to this WinStation */ if (pWinStation->hStack != NULL) { IcaStackClose( pWinStation->hStack ); pWinStation->hStack = NULL; }
Status = IcaStackReconnect( pReconnectInfo->hStack, pWinStation->hIca, pWinStation, pWinStation->LogonId ); if ( !NT_SUCCESS( Status ) ){ pWinStation->Config = *pCurConfig; pWinStation->Client = *pCurClient; goto badstackreconnect; }
/*
* Save stack and endpoint data */ pWinStation->hStack = pReconnectInfo->hStack; pWinStation->pEndpoint = pReconnectInfo->pEndpoint; pWinStation->EndpointLength = pReconnectInfo->EndpointLength;
/*
* Save the notification Credentials in the new winstation */ if (pReconnectInfo->pNotificationCredentials) {
if (pWinStation->pNewNotificationCredentials == NULL) { pWinStation->pNewNotificationCredentials = MemAlloc(sizeof(CLIENTNOTIFICATIONCREDENTIALS)); if (pWinStation->pNewNotificationCredentials == NULL) { Status = STATUS_NO_MEMORY ; goto nomem ; } }
RtlCopyMemory( pWinStation->pNewNotificationCredentials->Domain, pReconnectInfo->pNotificationCredentials->Domain, sizeof(pReconnectInfo->pNotificationCredentials->Domain) );
RtlCopyMemory( pWinStation->pNewNotificationCredentials->UserName, pReconnectInfo->pNotificationCredentials->UserName, sizeof(pReconnectInfo->pNotificationCredentials->UserName) );
} else { pWinStation->pNewNotificationCredentials = NULL; }
pReconnectInfo->hStack = NULL; pReconnectInfo->pEndpoint = NULL; pReconnectInfo->EndpointLength = 0; pReconnectInfo->pNotificationCredentials = NULL;
/*
* Copy remote ip to winstation */
if( pWinStation->pLastClientAddress != NULL ) { MemFree( pWinStation->pLastClientAddress ); pWinStation->pLastClientAddress = NULL; }
if( pReconnectInfo->pRememberedAddress != NULL ) { pWinStation->pLastClientAddress = pReconnectInfo->pRememberedAddress; pReconnectInfo->pRememberedAddress = NULL; }
/*
* Tell Win32k about the reconnect */ ReconnectMsg.ApiNumber = SMWinStationDoReconnect; ReconnectMsg.u.DoReconnect.fMouse = (BOOLEAN)pReconnectInfo->Client.fMouse; ReconnectMsg.u.DoReconnect.fClientDoubleClickSupport = (BOOLEAN)pReconnectInfo->Client.fDoubleClickDetect; ReconnectMsg.u.DoReconnect.fEnableWindowsKey = (BOOLEAN)pReconnectInfo->Client.fEnableWindowsKey; RtlCopyMemory( ReconnectMsg.u.DoReconnect.WinStationName, pReconnectInfo->WinStationName, sizeof(WINSTATIONNAME) ); RtlCopyMemory( ReconnectMsg.u.DoReconnect.AudioDriverName, pReconnectInfo->Client.AudioDriverName, sizeof( ReconnectMsg.u.DoReconnect.AudioDriverName ) ); RtlCopyMemory( ReconnectMsg.u.DoReconnect.DisplayDriverName, pReconnectInfo->DisplayDriverName, sizeof( ReconnectMsg.u.DoReconnect.DisplayDriverName ) ); RtlCopyMemory( ReconnectMsg.u.DoReconnect.ProtocolName, pReconnectInfo->ProtocolName, sizeof( ReconnectMsg.u.DoReconnect.ProtocolName ) );
/*
* Set the display resolution information in the message for reconnection */ ReconnectMsg.u.DoReconnect.HRes = pReconnectInfo->Client.HRes; ReconnectMsg.u.DoReconnect.VRes = pReconnectInfo->Client.VRes; ReconnectMsg.u.DoReconnect.ProtocolType = pReconnectInfo->Client.ProtocolType; ReconnectMsg.u.DoReconnect.fDynamicReconnect = (BOOLEAN)(pWinStation->Config.Wd.WdFlag & WDF_DYNAMIC_RECONNECT );
/*
* Translate the color to the format excpected in winsrv */ switch (pReconnectInfo->Client.ColorDepth) { case 1: ReconnectMsg.u.DoReconnect.ColorDepth=4 ; // 16 colors
break; case 2: ReconnectMsg.u.DoReconnect.ColorDepth=8 ; // 256
break; case 4: ReconnectMsg.u.DoReconnect.ColorDepth= 16;// 64K
break; case 8: ReconnectMsg.u.DoReconnect.ColorDepth= 24;// 16M
break; #define DC_HICOLOR
#ifdef DC_HICOLOR
case 16: ReconnectMsg.u.DoReconnect.ColorDepth= 15;// 32K
break; #endif
default: ReconnectMsg.u.DoReconnect.ColorDepth=8 ; break; }
ReconnectMsg.u.DoReconnect.KeyboardType = pWinStation->Client.KeyboardType; ReconnectMsg.u.DoReconnect.KeyboardSubType = pWinStation->Client.KeyboardSubType; ReconnectMsg.u.DoReconnect.KeyboardFunctionKey = pWinStation->Client.KeyboardFunctionKey;
if (pWinStation->LogonId == 0 || g_bPersonalTS) { if (pWinStation->hWinmmConsoleAudioEvent) { if (pWinStation->Client.fRemoteConsoleAudio) { // set the remoting audio on console flag
SetEvent(pWinStation->hWinmmConsoleAudioEvent);
} else { // don't set the remoting audio on console flag
ResetEvent(pWinStation->hWinmmConsoleAudioEvent); } } if ( pWinStation->hRDPAudioDisabledEvent ) { if ( pWinStation->Config.Config.User.fDisableCam ) { SetEvent( pWinStation->hRDPAudioDisabledEvent ); } else { ResetEvent( pWinStation->hRDPAudioDisabledEvent ); } } }
if (WaitForSingleObject(pWinStation->hReconnectReadyEvent, 45*1000) != WAIT_OBJECT_0) { DbgPrint("Wait Failed for hReconnectReadyEvent for Session %d\n", pWinStation->LogonId); SetEvent(pWinStation->hReconnectReadyEvent); }
Status = SendWinStationCommand( pWinStation, &ReconnectMsg, 600 ); if ( !NT_SUCCESS(Status) ) {
TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: CSR DoReconnect failed LogonId=%d Status=0x%x\n", pWinStation->LogonId, Status )); pWinStation->Config = *pCurConfig; pWinStation->Client = *pCurClient; goto badreconnect; } else { pWinStation->StateFlags |= WSF_ST_CONNECTED_TO_CSRSS; }
//
// Update protocol and display driver names.
//
RtlCopyMemory( pWinStation->ProtocolName, pReconnectInfo->ProtocolName, sizeof(pWinStation->ProtocolName) ); RtlCopyMemory( pWinStation->DisplayDriverName, pReconnectInfo->DisplayDriverName, sizeof(pWinStation->DisplayDriverName) ); /*
* Copy console owner info */ pWinStation->fOwnsConsoleTerminal = pReconnectInfo->fOwnsConsoleTerminal;
//
// Set session time zone information.
//
if(pWinStation->LogonId != 0 && !pWinStation->fOwnsConsoleTerminal && RegIsTimeZoneRedirectionEnabled()) { WINSTATION_APIMSG TimezoneMsg;
memset( &TimezoneMsg, 0, sizeof(TimezoneMsg) ); TimezoneMsg.ApiNumber = SMWinStationSetTimeZone; memcpy(&(TimezoneMsg.u.SetTimeZone.TimeZone),&(pReconnectInfo->Client.ClientTimeZone), sizeof(TS_TIME_ZONE_INFORMATION)); SendWinStationCommand( pWinStation, &TimezoneMsg, 600 ); } /*
* Close temporary ICA connection that was opened for the reconnect */ IcaClose( pReconnectInfo->hIca ); pReconnectInfo->hIca = NULL;
/*
* Move all of the licensing stuff to the new WinStation */
/*
* we may not have pWsx if the ReconnectInfo is from a session * that was connected to the local console */ if ( pReconnectInfo->pWsxContext ) { if ( pWinStation->pWsx == NULL ) { //
// This means that we are reconnecting remotely to a session
// that comes from the console. So create a new extension.
//
pWinStation->pWsx = FindWinStationExtensionDll( pWinStation->Config.Wd.WsxDLL, pWinStation->Config.Wd.WdFlag );
//
// Initialize winstation extension context structure
//
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxWinStationInitialize ) { Status = pWinStation->pWsx->pWsxWinStationInitialize( &pWinStation->pWsxContext);
if (!NT_SUCCESS(Status)) { pWinStation->pWsx = NULL; } }
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxWinStationReInitialize ) { WSX_INFO WsxInfo;
WsxInfo.Version = WSX_INFO_VERSION_1; WsxInfo.hIca = pWinStation->hIca; WsxInfo.hStack = pWinStation->hStack; WsxInfo.SessionId = pWinStation->LogonId; WsxInfo.pDomain = pWinStation->Domain; WsxInfo.pUserName = pWinStation->UserName;
Status = pWinStation->pWsx->pWsxWinStationReInitialize( pWinStation->pWsxContext, &WsxInfo );
if (!NT_SUCCESS(Status)) { pWinStation->pWsx = NULL; } } }
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxCopyContext ) { pWinStation->pWsx->pWsxCopyContext( pWinStation->pWsxContext, pReconnectInfo->pWsxContext ); } if ( pReconnectInfo->pWsx && pReconnectInfo->pWsx->pWsxWinStationRundown ) { pReconnectInfo->pWsx->pWsxWinStationRundown( pReconnectInfo->pWsxContext ); } pReconnectInfo->pWsxContext = NULL;
} else { // pReconnectInfo->pWsxContext == NULL
//
// This means that we are reconnecting to the console.
//
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxWinStationRundown ) { //
// Reconnecting a remote session to the console.
// Delete the extension.
//
pWinStation->pWsx->pWsxWinStationRundown( pWinStation->pWsxContext ); } pWinStation->pWsxContext = NULL; pWinStation->pWsx = NULL;
//
// In the case where both are NULL, we are reconnecting
// to the console a session that comes from the console.
//
}
//
// Anytime we change state, we need to make sure session directory
// does not pick up wrong state.
// We need this because Repopulate release the lock before calling
// SD so there might be small timing that we might send this reconnect
// first because repopulate come back.
//
if (!g_bPersonalTS && g_fAppCompat && g_bAdvancedServer) { SessDirWaitForRepopulate(); }
RtlEnterCriticalSection( &WinStationListLock ); if (pWinStation->UserName[0] != (WCHAR) 0) { pWinStation->State = State_Active; } else { pWinStation->State = State_Connected; } RtlCopyMemory( pWinStation->WinStationName, pReconnectInfo->WinStationName, sizeof(WINSTATIONNAME) );
/*
* Copy the original listen name for instance checking. */ RtlCopyMemory( pWinStation->ListenName, pReconnectInfo->ListenName, sizeof(WINSTATIONNAME) );
// Keep track of disconnected session count for Load Balancing Indicator
WinStationDiscCount--;
RtlLeaveCriticalSection( &WinStationListLock );
/*
* Disable virtual channel flags are from the transport setup. * Do not overwrite them. */ fDisableCdm = (BOOLEAN) pWinStation->Config.Config.User.fDisableCdm; fDisableCpm = (BOOLEAN) pWinStation->Config.Config.User.fDisableCpm; fDisableLPT = (BOOLEAN) pWinStation->Config.Config.User.fDisableLPT; fDisableCcm = (BOOLEAN) pWinStation->Config.Config.User.fDisableCcm; fDisableClip = (BOOLEAN) pWinStation->Config.Config.User.fDisableClip;
pWinStation->Config = pReconnectInfo->Config;
pWinStation->Config.Config.User.fDisableCdm = fDisableCdm; pWinStation->Config.Config.User.fDisableCpm = fDisableCpm; pWinStation->Config.Config.User.fDisableLPT = fDisableLPT; pWinStation->Config.Config.User.fDisableCcm = fDisableCcm; pWinStation->Config.Config.User.fDisableClip = fDisableClip; pWinStation->Config.Config.User.Shadow = Shadow;
/*
* Disable virtual channels if needed. */ VirtualChannelSecurity( pWinStation ); /*
* Notify the CDM channel of reconnection. */ if ( pWinStation->pWsx && pWinStation->pWsx->pWsxCdmConnect ) { (VOID) pWinStation->pWsx->pWsxCdmConnect( pWinStation->pWsxContext, pWinStation->LogonId, pWinStation->hIca ); }
/*
* Reset any autoreconnect information prior to reconnection * as it is stale. New information will be generated by the stack * when login completes. */ ResetAutoReconnectInfo(pWinStation);
if ( pWinStation->pWsx && pWinStation->pWsx->pWsxLogonNotify ) {
PWCHAR pUserNameToSend, pDomainToSend ; // Use the New notification credentials sent from Gina for the call below if they are available
if (pWinStation->pNewNotificationCredentials) { pUserNameToSend = pWinStation->pNewNotificationCredentials->UserName; pDomainToSend = pWinStation->pNewNotificationCredentials->Domain; } else { pUserNameToSend = pWinStation->UserName; pDomainToSend = pWinStation->Domain ; }
Status = pWinStation->pWsx->pWsxLogonNotify( pWinStation->pWsxContext, pWinStation->LogonId, NULL, pDomainToSend, pUserNameToSend );
if (pWinStation->pNewNotificationCredentials != NULL) { MemFree(pWinStation->pNewNotificationCredentials); pWinStation->pNewNotificationCredentials = NULL; }
if(!NT_SUCCESS(Status)) { TRACE((hTrace, TC_ICASRV, TT_API1, "TERMSRV: WinStationDoReconnect: LogonNotify rc=0x%x\n", Status )); } }
NotifySystemEvent( WEVENT_CONNECT | WEVENT_STATECHANGE );
/*
* Cleanup any allocated buffers. * The endpoint buffer was transfered to the WinStation above. */ pReconnectInfo->pEndpoint = NULL; pReconnectInfo->EndpointLength = 0;
/*
* Set connect time and stop disconnect timer */ NtQuerySystemTime(&pWinStation->ConnectTime); if (pWinStation->fDisconnectTimer) { pWinStation->fDisconnectTimer = FALSE; IcaTimerClose( pWinStation->hDisconnectTimer ); }
/*
* Start logon timers */ StartLogonTimers(pWinStation);
// Notify the session directory of the reconnection.
if (!g_bPersonalTS && g_fAppCompat && g_bAdvancedServer) { TSSD_ReconnectSessionInfo ReconnInfo;
ReconnInfo.SessionID = pWinStation->LogonId; ReconnInfo.TSProtocol = pWinStation->Client.ProtocolType; ReconnInfo.ResolutionWidth = pWinStation->Client.HRes; ReconnInfo.ResolutionHeight = pWinStation->Client.VRes; ReconnInfo.ColorDepth = pWinStation->Client.ColorDepth; SessDirNotifyReconnection(pWinStation, &ReconnInfo); }
TRACE((hTrace, TC_ICASRV, TT_API1, "TERMSRV: WinStationDoReconnect, rc=0x0\n" )); AuditEvent( pWinStation, SE_AUDITID_SESSION_RECONNECTED );
/*
* Tell csrss to notify winlogon for the reconnect then notify any process * that registred for notification. */
ReconnectMsg.ApiNumber = SMWinStationNotify; ReconnectMsg.WaitForReply = FALSE; ReconnectMsg.u.DoNotify.NotifyEvent = WinStation_Notify_Reconnect; Status = SendWinStationCommand( pWinStation, &ReconnectMsg, 0 );
Status = NotifyConnect(pWinStation, pWinStation->fOwnsConsoleTerminal); if ( !NT_SUCCESS(Status) ) { DBGPRINT(("NotifyConsoleConnect failed, SessionId = %d, Status = %d", pWinStation->LogonId, Status)); }
// Free up allocated Memory
if (pCurConfig != NULL) { MemFree( pCurConfig ); pCurConfig = NULL; } if (pCurClient != NULL) { MemFree( pCurClient ); pCurClient = NULL; }
if (pWinStation->pNewNotificationCredentials != NULL) { MemFree(pWinStation->pNewNotificationCredentials); pWinStation->pNewNotificationCredentials = NULL; }
// Since the winstation has reconnected, we can allow further autoreconnects
pWinStation->fDisallowAutoReconnect = FALSE;
// Reset ReconnectPending Flag
pWinStation->fReconnectPending = FALSE; pWinStation->fReconnectingToConsole = FALSE;
return STATUS_SUCCESS;
/*=============================================================================
== Error returns =============================================================================*/
/*
* Failure from Win32 reconnect call. * Disconnect the stack again, and indicate the WinStation * does not have a stack or endpoint connection. */ badreconnect: TempStatus = IcaStackDisconnect( pWinStation->hStack, pReconnectInfo->hIca, NULL ); //ASSERT( NT_SUCCESS( TempStatus ) );
pReconnectInfo->hStack = pWinStation->hStack; pReconnectInfo->pEndpoint = pWinStation->pEndpoint; pReconnectInfo->EndpointLength = pWinStation->EndpointLength; pWinStation->hStack = NULL; pWinStation->pEndpoint = NULL; pWinStation->EndpointLength = 0;
badstackreconnect: TempStatus = IcaStackOpen( pWinStation->hIca, Stack_Primary, (PROC)WsxStackIoControl, pWinStation, &pWinStation->hStack ); //ASSERT( NT_SUCCESS( TempStatus ) ); // don't know how to handle any error here
nomem: // Free up allocated Memory
if (pCurConfig != NULL) { MemFree( pCurConfig ); pCurConfig = NULL; } if (pCurClient != NULL) { MemFree( pCurClient ); pCurClient = NULL; }
if (pWinStation->pNewNotificationCredentials != NULL) { MemFree(pWinStation->pNewNotificationCredentials); pWinStation->pNewNotificationCredentials = NULL; }
TRACE((hTrace, TC_ICASRV, TT_API1, "TERMSRV: WinStationDoReconnect, rc=0x%x\n", Status ));
// Reset ReconnectPending Flag
pWinStation->fReconnectPending = FALSE; pWinStation->fReconnectingToConsole = FALSE;
return( Status ); }
/*******************************************************************************
* WsxBrokenConnection * * Send broken connection notification to the WinStation extension DLL ******************************************************************************/ VOID WsxBrokenConnection(PWINSTATION pWinStation) { /*
* Only send notification if there is a reason */ if ( pWinStation->BrokenReason ) { if ( pWinStation->pWsx && pWinStation->pWsx->pWsxBrokenConnection ) { ICA_BROKEN_CONNECTION Broken;
Broken.Reason = pWinStation->BrokenReason; Broken.Source = pWinStation->BrokenSource; pWinStation->pWsx->pWsxBrokenConnection( pWinStation->pWsxContext, pWinStation->hStack, &Broken ); }
/*
* Clear these once we have tried to send them */ pWinStation->BrokenReason = 0; pWinStation->BrokenSource = 0; } }
/*******************************************************************************
* CleanupReconnect * * Cleanup the specified RECONNECT_INFO structure * * ENTRY: * pReconnectInfo (input) * Pointer to RECONNECT_INFO buffer ******************************************************************************/ VOID CleanupReconnect(PRECONNECT_INFO pReconnectInfo) { NTSTATUS Status;
/*
* If there is a connection endpoint, then close it now. * When done, we also free the endpoint structure. */ if ( (pReconnectInfo->pEndpoint != NULL) && (pReconnectInfo->hStack != NULL)) { Status = IcaStackConnectionClose( pReconnectInfo->hStack, &pReconnectInfo->Config, pReconnectInfo->pEndpoint, pReconnectInfo->EndpointLength );
ASSERT( Status == STATUS_SUCCESS ); MemFree( pReconnectInfo->pEndpoint ); pReconnectInfo->pEndpoint = NULL; }
if ( pReconnectInfo->pWsxContext ) { if ( pReconnectInfo->pWsx && pReconnectInfo->pWsx->pWsxWinStationRundown ) { pReconnectInfo->pWsx->pWsxWinStationRundown( pReconnectInfo->pWsxContext ); } pReconnectInfo->pWsxContext = NULL; }
if ( pReconnectInfo->hStack ) { IcaStackClose( pReconnectInfo->hStack ); pReconnectInfo->hStack = NULL; }
if ( pReconnectInfo->hIca ) { IcaClose( pReconnectInfo->hIca ); pReconnectInfo->hIca = NULL; }
if( pReconnectInfo->pRememberedAddress != NULL ) { MemFree( pReconnectInfo->pRememberedAddress ); pReconnectInfo->pRememberedAddress = NULL; } }
NTSTATUS _CloseEndpoint( IN PWINSTATIONCONFIG2 pWinStationConfig, IN PVOID pEndpoint, IN ULONG EndpointLength, IN PWINSTATION pWinStation, IN BOOLEAN bNeedStack) { HANDLE hIca; HANDLE hStack; NTSTATUS Status;
/*
* Open a stack handle that we can use to close the specified endpoint. */
TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: _CloseEndpoint [%p] on %s stack\n", pEndpoint, bNeedStack ? "Temporary" : "Primary"));
if (bNeedStack) { Status = IcaOpen( &hIca ); if ( NT_SUCCESS( Status ) ) { Status = IcaStackOpen( hIca, Stack_Primary, NULL, NULL, &hStack ); if ( NT_SUCCESS( Status ) ) { Status = IcaStackConnectionClose( hStack, pWinStationConfig, pEndpoint, EndpointLength ); IcaStackClose( hStack ); } IcaClose( hIca ); } }
else { Status = IcaStackConnectionClose( pWinStation->hStack, pWinStationConfig, pEndpoint, EndpointLength ); } if ( !NT_SUCCESS( Status ) ) { TRACE((hTrace, TC_ICASRV, TT_ERROR, "TERMSRV: _CloseEndpoint failed [%s], Status=%x\n", bNeedStack ? "Temporary" : "Primary", Status )); }
return Status; }
/*******************************************************************************
* WinStationExceptionFilter * * Handle exception from a WinStation thread * * ENTRY: * pExceptionInfo (input) * pointer to EXCEPTION_POINTERS struct * * EXIT: * EXCEPTION_EXECUTE_HANDLER -- always ******************************************************************************/ NTSTATUS WinStationExceptionFilter( PWSTR OutputString, PEXCEPTION_POINTERS pexi) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; MUTANT_BASIC_INFORMATION MutexInfo; NTSTATUS Status;
DbgPrint( "TERMSRV: %S\n", OutputString ); DbgPrint( "TERMSRV: ExceptionRecord=%p ContextRecord=%p\n", pexi->ExceptionRecord, pexi->ContextRecord ); DbgPrint( "TERMSRV: Exception code=%08x, flags=%08x, addr=%p, IP=%p\n", pexi->ExceptionRecord->ExceptionCode, pexi->ExceptionRecord->ExceptionFlags, pexi->ExceptionRecord->ExceptionAddress, CONTEXT_TO_PROGRAM_COUNTER(pexi->ContextRecord) ); #ifdef i386
DbgPrint( "TERMSRV: esp=%p ebp=%p\n", pexi->ContextRecord->Esp, pexi->ContextRecord->Ebp ); #endif
DbgBreakPoint();
/*
* Lock the global WinStation critsec if we don't already own it */ if ( NtCurrentTeb()->ClientId.UniqueThread != WinStationListLock.OwningThread ) ENTERCRIT( &WinStationListLock );
/*
* Search the WinStation list to see if we had any locked */ Head = &WinStationListHead; for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); Status = NtQueryMutant( pWinStation->Lock.Mutex, MutantBasicInformation, &MutexInfo, sizeof(MutexInfo), NULL ); if ( NT_SUCCESS( Status ) && MutexInfo.OwnedByCaller ) { ReleaseWinStation( pWinStation ); break; // OK to quit now, we should never lock more than one
} }
LEAVECRIT( &WinStationListLock );
return EXCEPTION_EXECUTE_HANDLER; }
/*******************************************************************************
* GetProcessLogonId * * Get LogonId for a process * * ENTRY: * ProcessHandle (input) * handle of process to get LogonId for * pLogonId (output) * location to return LogonId of process ******************************************************************************/ NTSTATUS GetProcessLogonId(HANDLE Process, PULONG pLogonId) { NTSTATUS Status; PROCESS_SESSION_INFORMATION ProcessInfo;
/*
* Get the LogonId for the process */ *pLogonId = 0; Status = NtQueryInformationProcess( Process, ProcessSessionInformation, &ProcessInfo, sizeof( ProcessInfo ), NULL ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: GetProcessLogonId, Process=%x, Status=%x\n", Process, Status )); return( Status ); }
*pLogonId = ProcessInfo.SessionId; return Status; }
/*******************************************************************************
* SetProcessLogonId * * Set LogonId for a process * * ENTRY: * ProcessHandle (input) * handle of process to set LogonId for * LogonId (output) * LogonId to set for process ******************************************************************************/ NTSTATUS SetProcessLogonId(HANDLE Process, ULONG LogonId) { NTSTATUS Status; PROCESS_SESSION_INFORMATION ProcessInfo;
/*
* Set the LogonId for the process */ ProcessInfo.SessionId = LogonId; Status = NtSetInformationProcess( Process, ProcessSessionInformation, &ProcessInfo, sizeof( ProcessInfo ) ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: SetProcessLogonId, Process=%x, Status=%x\n", Process, Status )); return Status; }
return Status; }
/*******************************************************************************
* FindWinStationById * * Find and lock a WinStation given its LogonId * * ENTRY: * LogonId (input) * LogonId of WinStation to find * LockList (input) * BOOLEAN indicating whether WinStationListLock should be * left locked on return * * EXIT: * On success - Pointer to WinStation * On failure - NULL ******************************************************************************/ PWINSTATION FindWinStationById(ULONG LogonId, BOOLEAN LockList) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; PWINSTATION pFoundWinStation = NULL; ULONG uCount;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
/*
* Search the list for a WinStation with the given logonid. */ searchagain: uCount = 0; for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( pWinStation->LogonId == LogonId ) { uCount++;
/*
* Now try to lock the WinStation. */ if (pFoundWinStation == NULL){ if ( !LockRefLock( &pWinStation->Lock ) ) goto searchagain; pFoundWinStation = pWinStation; } #if DBG
#else
break; #endif
} }
ASSERT((uCount <= 1) || (LogonId== -1) );
/*
* If the WinStationList lock should not be held, then release it now. */ if ( !LockList ) LEAVECRIT( &WinStationListLock );
if (pFoundWinStation == NULL) { TRACE((hTrace,TC_ICASRV,TT_API2,"TERMSRV: FindWinStationById: %d (not found)\n", LogonId )); }
return pFoundWinStation; }
BOOL FindFirstListeningWinStationName( PWINSTATIONNAMEW pListenName, PWINSTATIONCONFIG2 pConfig ) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; BOOL bFound = FALSE;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
searchagain: /*
* Search the list for a WinStation with the given name. */ for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( pWinStation->Flags & WSF_LISTEN && pWinStation->Client.ProtocolType == PROTOCOL_RDP) {
// try to lock winstation.
if ( !LockRefLock( &pWinStation->Lock ) ) goto searchagain;
CopyMemory( pConfig, &(pWinStation->Config), sizeof(WINSTATIONCONFIG2) ); lstrcpy( pListenName, pWinStation->WinStationName ); ReleaseWinStation( pWinStation ); bFound = TRUE; } }
LEAVECRIT( &WinStationListLock );
TRACE((hTrace,TC_ICASRV,TT_API3,"TERMSRV: FindFirstListeningWinStationName: %ws\n", (bFound) ? pListenName : L"Not Found" ));
return bFound; }
/*******************************************************************************
* FindWinStationByName * * Find and lock a WinStation given its Name * * ENTRY: * WinStationName (input) * Name of WinStation to find * LockList (input) * BOOLEAN indicating whether WinStationListLock should be * left locked on return * * EXIT: * On success - Pointer to WinStation * On failure - NULL ******************************************************************************/ PWINSTATION FindWinStationByName(LPWSTR WinStationName, BOOLEAN LockList) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
/*
* Search the list for a WinStation with the given name. */ searchagain: for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( !_wcsicmp( pWinStation->WinStationName, WinStationName ) ) {
/*
* Now try to lock the WinStation. If this succeeds, * then ensure it still has the name we're searching for. */ if ( !LockRefLock( &pWinStation->Lock ) ) goto searchagain; if ( _wcsicmp( pWinStation->WinStationName, WinStationName ) ) { ReleaseWinStation( pWinStation ); goto searchagain; }
/*
* If the WinStationList lock should not be held, then release it now. */ if ( !LockList ) LEAVECRIT( &WinStationListLock );
TRACE((hTrace,TC_ICASRV,TT_API3,"TERMSRV: FindWinStationByName: %S, LogonId %u\n", WinStationName, pWinStation->LogonId )); return( pWinStation ); } }
/*
* If the WinStationList lock should not be held, then release it now. */ if ( !LockList ) LEAVECRIT( &WinStationListLock );
TRACE((hTrace,TC_ICASRV,TT_API3,"TERMSRV: FindWinStationByName: %S, (not found)\n", WinStationName )); return NULL; }
/*******************************************************************************
* FindIdleWinStation * * Find and lock an idle WinStation * * EXIT: * On success - Pointer to WinStation * On failure - NULL ******************************************************************************/ PWINSTATION FindIdleWinStation() { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; BOOLEAN bFirstTime = TRUE;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
/*
* Search the list for an idle WinStation */ searchagain: for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( (pWinStation->Flags & WSF_IDLE) && !(pWinStation->Flags & WSF_IDLEBUSY) && !pWinStation->Starting ) {
/*
* Now try to lock the WinStation. If this succeeds, * then ensure it is still marked as idle. */ if ( !LockRefLock( &pWinStation->Lock ) ) { goto searchagain; } if ( !(pWinStation->Flags & WSF_IDLE) || (pWinStation->Flags & WSF_IDLEBUSY) || pWinStation->Starting ) { ReleaseWinStation( pWinStation ); goto searchagain; }
LEAVECRIT( &WinStationListLock ); return( pWinStation ); } }
LEAVECRIT( &WinStationListLock );
TRACE((hTrace,TC_ICASRV,TT_API2,"TERMSRV: FindIdleWinStation: (none found)\n" )); return NULL; }
/*******************************************************************************
* CountWinStationType * * Count the number of matching Winstation Listen Names * * ENTRY: * Listen Name * * bActiveOnly if TRUE, count only active WinStations * * EXIT: * Number ******************************************************************************/ ULONG CountWinStationType( PWINSTATIONNAME pListenName, BOOLEAN bActiveOnly, BOOLEAN bLockHeld) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; ULONG Count = 0;
Head = &WinStationListHead;
if ( !bLockHeld ) { ENTERCRIT( &WinStationListLock ); }
/*
* Search the list for an idle WinStation */ for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( !wcscmp( pWinStation->ListenName, pListenName ) ) { if ( !bActiveOnly ) Count++; else if ( pWinStation->State == State_Active || pWinStation->State == State_Shadow ) Count++; } }
if ( !bLockHeld ) { LEAVECRIT( &WinStationListLock ); }
TRACE((hTrace,TC_ICASRV,TT_API2,"TERMSRV: CountWinstationType %d\n", Count )); return Count; }
/*******************************************************************************
* IncrementReference * * Increments refcount on a WinStation given a pointer * * NOTE: * WinStationListLock must be locked on entry and will be locked on return. * * ENTRY: * pWinStation (input) * Pointer to WinStation to lock * ******************************************************************************/ void IncrementReference(PWINSTATION pWinStation) { /*
* increment the ref count on winstation. */
InterlockedIncrement( &pWinStation->Lock.RefCount ); }
/*******************************************************************************
* InitRefLock * * Initialize a RefLock and lock it. * * ENTRY: * pLock (input) * Pointer to RefLock to init * pDeleteProcedure (input) * Pointer to delete procedure for object ******************************************************************************/ NTSTATUS InitRefLock(PREFLOCK pLock, PREFLOCKDELETEPROCEDURE pDeleteProcedure) { NTSTATUS Status;
// Create and lock winstation mutex
Status = NtCreateMutant( &pLock->Mutex, MUTANT_ALL_ACCESS, NULL, TRUE ); if ( !NT_SUCCESS( Status ) ) return( Status );
pLock->RefCount = 1; pLock->Invalid = FALSE; pLock->pDeleteProcedure = pDeleteProcedure;
return STATUS_SUCCESS; }
/*******************************************************************************
* SetRefLockDeleteProc * * Cahnge a RefLock DeleteProc. * * ENTRY: * pLock (input) * Pointer to RefLock to init * pDeleteProcedure (input) * Pointer to delete procedure for object ******************************************************************************/ NTSTATUS SetRefLockDeleteProc( PREFLOCK pLock, PREFLOCKDELETEPROCEDURE pDeleteProcedure) { pLock->pDeleteProcedure = pDeleteProcedure; return STATUS_SUCCESS; }
/*******************************************************************************
* LockRefLock * * Increment the reference count for a RefLock and lock it. * * NOTE: * WinStationListLock must be locked on entry and will be locked on return. * * ENTRY: * pLock (input) * Pointer to RefLock to lock * * EXIT: * TRUE - if object was locked successfully * FALSE - otherwise ******************************************************************************/ BOOLEAN LockRefLock(PREFLOCK pLock) { /*
* Increment reference count for this RefLock. */ InterlockedIncrement( &pLock->RefCount );
/*
* If mutex cannot be locked without blocking, * then unlock the WinStation list lock, wait for the mutex, * and relock the WinStation list lock. */ if ( NtWaitForSingleObject( pLock->Mutex, FALSE, &TimeoutZero ) != STATUS_SUCCESS ) { LEAVECRIT( &WinStationListLock ); NtWaitForSingleObject( pLock->Mutex, FALSE, NULL ); ENTERCRIT( &WinStationListLock );
/*
* If the object is marked as invalid, it was removed while * we waited for the lock. Release our lock and return FALSE, * indicating we were unable to lock it. */ if ( pLock->Invalid ) { /*
* Release the winstationlist lock because the Winstation * migth go away as a result of releasing its lock, */ LEAVECRIT( &WinStationListLock ); ReleaseRefLock( pLock ); ENTERCRIT( &WinStationListLock ); return FALSE; } }
return TRUE; }
/*******************************************************************************
* RelockRefLock * * Relock a RefLock which has been unlocked but still has a reference. * * NOTE: * Object must have been previously unlocked by calling UnlockRefLock. * * EXIT: * TRUE - if object is still valid * FALSE - if object was marked invalid while unlocked ******************************************************************************/ BOOLEAN RelockRefLock(PREFLOCK pLock) { /*
* Lock the mutex */ NtWaitForSingleObject( pLock->Mutex, FALSE, NULL );
/*
* If the object is marked as invalid, * it was removed while it was unlocked so we return FALSE. */ return !pLock->Invalid; }
/*******************************************************************************
* UnlockRefLock * * Unlock a RefLock but keep a reference to it (don't decrement * the reference count). Caller must use RelockWinRefLock * to relock the object. * * ENTRY: * pLock (input) * Pointer to RefLock to unlock ******************************************************************************/ VOID UnlockRefLock(PREFLOCK pLock) { NtReleaseMutant(pLock->Mutex, NULL); }
/*******************************************************************************
* ReleaseRefLock * * Unlock and dereference a RefLock. * * ENTRY: * pLock (input) * Pointer to RefLock to release ******************************************************************************/ VOID ReleaseRefLock(PREFLOCK pLock) { ASSERT( pLock->RefCount > 0 );
/*
* If object has been marked invalid and we are the * last reference, then finish deleting it now. */ if ( pLock->Invalid ) { ULONG RefCount;
RefCount = InterlockedDecrement( &pLock->RefCount ); NtReleaseMutant( pLock->Mutex, NULL ); if ( RefCount == 0 ) { NtClose( pLock->Mutex ); (*pLock->pDeleteProcedure)( pLock ); }
} else { InterlockedDecrement( &pLock->RefCount ); NtReleaseMutant( pLock->Mutex, NULL ); } }
/*******************************************************************************
* DeleteRefLock * * Unlock, dereference, and delete a RefLock. * * ENTRY: * pLock (input) * Pointer to RefLock to delete ******************************************************************************/ VOID DeleteRefLock(PREFLOCK pLock) { ASSERT( pLock->RefCount > 0 );
/*
* If we are the last reference, then delete the object now */ if ( InterlockedDecrement( &pLock->RefCount ) == 0 ) { NtReleaseMutant( pLock->Mutex, NULL ); NtClose( pLock->Mutex ); (*pLock->pDeleteProcedure)( pLock );
/*
* Otherwise, just mark the object invalid */ } else { pLock->Invalid = TRUE; NtReleaseMutant( pLock->Mutex, NULL ); } }
BOOLEAN IsWinStationLockedByCaller(PWINSTATION pWinStation) { MUTANT_BASIC_INFORMATION MutantInfo; NTSTATUS Status;
Status = NtQueryMutant( pWinStation->Lock.Mutex, MutantBasicInformation, &MutantInfo, sizeof(MutantInfo), NULL ); if ( NT_SUCCESS( Status ) ) return MutantInfo.OwnedByCaller;
return FALSE; }
/*******************************************************************************
* WinStationEnumerateWorker * * Enumerate the WinStation list and return LogonIds and WinStation * names to the caller. * * NOTE: * This version only returns one entry at a time. There is no guarantee * across calls that the list will not change, causing the users Index * to miss an entry or get the same entry twice. * * ENTRY: * pEntries (input/output) * Pointer to number of entries to return/number actually returned * pWin (output) * Pointer to buffer to return entries * pByteCount (input/output) * Pointer to size of buffer/length of data returned in buffer * pIndex (input/output) * Pointer to WinStation index to return/next index ******************************************************************************/ NTSTATUS WinStationEnumerateWorker( PULONG pEntries, PLOGONID pWin, PULONG pByteCount, PULONG pIndex) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; ULONG WinStationIndex; ULONG MaxEntries, MaxByteCount; NTSTATUS Status; NTSTATUS Error = STATUS_NO_MORE_ENTRIES;
WinStationIndex = 0; MaxEntries = *pEntries; MaxByteCount = *pByteCount; *pEntries = 0; *pByteCount = 0; Head = &WinStationListHead; ENTERCRIT( &WinStationListLock ); for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) {
if ( *pEntries >= MaxEntries || *pByteCount + sizeof(LOGONID) > MaxByteCount ) { break; }
pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links );
//
// If Session has not yet been fully Started, skip this session during Enumeration
//
if (pWinStation->LogonId == -1) { continue; }
if ( *pIndex == WinStationIndex ) { (*pIndex)++; // set Index to next entry
/*
* Verify that client has QUERY access before * returning it in the enumerate list. * (Note that RpcCheckClientAccess only references the WinStation * to get the LogonId, so it is safe to call this routine without * locking the WinStation since we hold the WinStationListLock * which prevents the WinStation from being deleted.) */ Status = RpcCheckClientAccess( pWinStation, WINSTATION_QUERY, FALSE ); if ( NT_SUCCESS( Status ) ) { Error = STATUS_SUCCESS;
/*
* It's possible that the LPC client can go away while we * are processing this call. Its also possible that another * server thread handles the LPC_PORT_CLOSED message and closes * the port, which deletes the view memory, which is what * pWin points to. In this case the pWin references below * will trap. We catch this and just break out of the loop. */ try { pWin->LogonId = pWinStation->LogonId; if ( pWinStation->Terminating ) pWin->State = State_Down; else pWin->State = pWinStation->State; wcscpy( pWin->WinStationName, pWinStation->WinStationName ); } except( EXCEPTION_EXECUTE_HANDLER ) { break; } pWin++; (*pEntries)++; *pByteCount += sizeof(LOGONID); } } WinStationIndex++; }
LEAVECRIT( &WinStationListLock ); return Error; }
/*******************************************************************************
* LogonIdFromWinStationNameWorker * * Return the LogonId for a given WinStation name. * * ENTRY: * WinStationName (input) * name of WinStation to query * pLogonId (output) * Pointer to location to return LogonId ******************************************************************************/ NTSTATUS LogonIdFromWinStationNameWorker( PWINSTATIONNAME WinStationName, ULONG NameSize, PULONG pLogonId) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; NTSTATUS Status; UINT uiLength;
// make sure we don't go beyond the end of one of the two strings
// (and work around bug #229753 : NameSize is in bytes, not a characters count)
if (NameSize > sizeof(WINSTATIONNAME)) { uiLength = sizeof(WINSTATIONNAME)/sizeof(WCHAR); } else { uiLength = NameSize/sizeof(WCHAR); }
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock ); for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links );
if ( !_wcsnicmp( pWinStation->WinStationName, WinStationName, uiLength ) ) {
/*
* If client doesn't have QUERY access, return NOT_FOUND error */ Status = RpcCheckClientAccess( pWinStation, WINSTATION_QUERY, FALSE ); if ( !NT_SUCCESS( Status ) ) break; *pLogonId = pWinStation->LogonId; LEAVECRIT( &WinStationListLock ); return( STATUS_SUCCESS ); } }
LEAVECRIT( &WinStationListLock ); return STATUS_CTX_WINSTATION_NOT_FOUND; }
/*******************************************************************************
* IcaWinStationNameFromLogonId * * Return the WinStation name for a given LogonId. * * ENTRY: * LogonId (output) * LogonId to query * pWinStationName (input) * pointer to location to return WinStation name ******************************************************************************/ NTSTATUS IcaWinStationNameFromLogonId( ULONG LogonId, PWINSTATIONNAME pWinStationName) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; NTSTATUS Status;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock ); for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links );
if ( pWinStation->LogonId == LogonId ) { /*
* If client doesn't have QUERY access, return NOT_FOUND error */ Status = RpcCheckClientAccess( pWinStation, WINSTATION_QUERY, FALSE ); if ( !NT_SUCCESS( Status ) ) break; wcscpy( pWinStationName, pWinStation->WinStationName ); LEAVECRIT( &WinStationListLock ); return( STATUS_SUCCESS ); } }
LEAVECRIT( &WinStationListLock ); return STATUS_CTX_WINSTATION_NOT_FOUND; }
NTSTATUS TerminateProcessAndWait( HANDLE ProcessId, HANDLE Process, ULONG Seconds) { NTSTATUS Status; ULONG mSecs; LARGE_INTEGER Timeout;
/*
* Try to terminate the process */ TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: TerminateProcessAndWait, process=0x%x, ", ProcessId ));
Status = NtTerminateProcess( Process, STATUS_SUCCESS ); if ( !NT_SUCCESS( Status ) && Status != STATUS_PROCESS_IS_TERMINATING ) { DBGPRINT(("Terminate=0x%x\n", Status )); return( Status ); } TRACE((hTrace,TC_ICASRV,TT_API1, "Terminate=0x%x, ", Status ));
/*
* Wait for the process to die */ mSecs = Seconds * 1000; Timeout = RtlEnlargedIntegerMultiply( mSecs, -10000 ); Status = NtWaitForSingleObject( Process, FALSE, &Timeout );
TRACE((hTrace,TC_ICASRV,TT_API1, "Wait=0x%x\n", Status ));
return Status; }
/*****************************************************************************
* ShutdownLogoff * * Worker function to handle logoff notify of WinStations when * the system is being shutdown. * * It is built from the code in WinStationReset * * ENTRY: * Client LogonId (input) * LogonId of the client Winstation doing the shutdown. This is so * that he does not get reset. * Flags (input) * The shutdown flags. ****************************************************************************/ NTSTATUS ShutdownLogoff(ULONG ClientLogonId, ULONG Flags) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation, pConsole = NULL; PULONG Tmp; PULONG Ids = NULL; ULONG IdCount = 0; ULONG IdAllocCount = 0; NTSTATUS Status = STATUS_SUCCESS;
TRACE((hTrace,TC_ICASRV,TT_API1, "ShutdownLogoff: Called from WinStation %d Flags %x\n", ClientLogonId, Flags ));
/*
* Loop through all the WinStations getting the LogonId's * of active Winstations */ Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links );
//
// take a reference on the console
//
if ( pWinStation->fOwnsConsoleTerminal ) { if (!pConsole) { IncrementReference( pWinStation ); pConsole = pWinStation; } }
//
// just skip :
// - the caller's session
// - the console (because winsrv!W32WinStationExitWindows would fail for the console)
// - the listener
//
if ( ( pWinStation->LogonId == ClientLogonId ) || ( pWinStation->LogonId == 0) || ( pWinStation->Flags & WSF_LISTEN ) ) { // Skip this one, or it's a listen
continue; }
if ( IdCount >= IdAllocCount ) { // Reallocate the array
IdAllocCount += 16; Tmp = RtlAllocateHeap( RtlProcessHeap(), 0, IdAllocCount * sizeof(ULONG) ); if ( Tmp == NULL ) { Status = STATUS_NO_MEMORY; if ( Ids ) RtlFreeHeap( RtlProcessHeap(), 0, Ids ); IdCount = 0; break; } if ( Ids ) { RtlCopyMemory( Tmp, Ids, IdCount*sizeof(ULONG) ); RtlFreeHeap( RtlProcessHeap(), 0, Ids ); } Ids = Tmp; } // Copy the LogonId into our array
Ids[IdCount++] = pWinStation->LogonId; }
//
// We are protected by new winstations starting up by the shutdown
// global flags.
//
// The actual WinStation reset routine will validate that the LogonId
// is still valid
//
LEAVECRIT( &WinStationListLock );
//
// see if the console is being shadowed
//
if ( pConsole ) { RelockWinStation( pConsole ); WinStationStopAllShadows( pConsole ); ReleaseWinStation( pConsole ); }
if (IdCount !=0) { //
// Ids[] holds the LogonId's of valid Winstations, IdCount is the number
//
/*
* Now do the actual logout and/or reset of the WinStations. */ if (Flags & WSD_LOGOFF) { Status = DoForWinStationGroup( Ids, IdCount, (LPTHREAD_START_ROUTINE) WinStationLogoff); }
if (Flags & WSD_SHUTDOWN) { Status = DoForWinStationGroup( Ids, IdCount, (LPTHREAD_START_ROUTINE) WinStationShutdownReset); } }
return Status; }
/*****************************************************************************
* DoForWinStationGroup * * Executes a function for each WinStation in the group. * The group is passed as an array of LogonId's. * * ENTRY: * Ids (input) * Array of LogonId's of WinStations to reset * * IdCount (input) * Count of LogonId's in array * * ThreadProc (input) * The thread routine executed for each WinStation. ****************************************************************************/ NTSTATUS DoForWinStationGroup( PULONG Ids, ULONG IdCount, LPTHREAD_START_ROUTINE ThreadProc) { ULONG Index; NTSTATUS Status; LARGE_INTEGER Timeout; PHANDLE ThreadHandles = NULL;
ThreadHandles = RtlAllocateHeap( RtlProcessHeap(), 0, IdCount * sizeof(HANDLE) ); if( ThreadHandles == NULL ) { return( STATUS_NO_MEMORY ); }
/*
* Wait a max of 60 seconds for thread to exit */ Timeout = RtlEnlargedIntegerMultiply( 60000, -10000 );
for( Index=0; Index < IdCount; Index++ ) {
//
// Here we create a thread to run the actual reset function.
// Since we are holding the lists crit sect, the threads will
// wait until we are done, then wake up when we release it
//
DWORD ThreadId;
ThreadHandles[Index] = CreateThread( NULL, 0, // use Default stack size of the svchost process
ThreadProc, LongToPtr( Ids[Index] ), // LogonId
THREAD_SET_INFORMATION, &ThreadId ); if ( !ThreadHandles[Index] ) { ThreadHandles[Index] = (HANDLE)(-1); TRACE((hTrace,TC_ICASRV,TT_ERROR,"TERMSRV: Shutdown: Could not create thread for WinStation %d Shutdown\n", Ids[Index])); } }
//
// Now wait for the threads to exit. Each will reset their
// WinStation and be signal by the kernel when the thread is
// exited.
//
for (Index=0; Index < IdCount; Index++) { if ( ThreadHandles[Index] != (HANDLE)(-1) ) { Status = NtWaitForSingleObject( ThreadHandles[Index], FALSE, // Not alertable
&Timeout );
if( Status == STATUS_TIMEOUT ) { TRACE((hTrace,TC_ICASRV,TT_ERROR,"TERMSRV: DoForWinStationGroup: Timeout Waiting for Thread\n")); } else if (!NT_SUCCESS( Status ) ) { TRACE((hTrace,TC_ICASRV,TT_ERROR,"TERMSRV: DoForWinStationGroup: Error waiting for Thread Status 0x%x\n", Status)); } NtClose( ThreadHandles[Index] ); } }
/* makarp:free the ThreadHandles. // #182609 */ RtlFreeHeap( RtlProcessHeap(), 0, ThreadHandles );
return STATUS_SUCCESS; }
/*****************************************************************************
* WinStationShutdownReset * * Reset a WinStation due to a system shutdown. Does not re-create * it. * * ENTRY: * ThreadArg (input) * WinStation logonId ****************************************************************************/ ULONG WinStationShutdownReset(PVOID ThreadArg) { ULONG LogonId = (ULONG)(INT_PTR)ThreadArg; PWINSTATION pWinStation; NTSTATUS Status; ULONG ulIndex; BOOL bConnectDisconnectPending = TRUE;
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: ShutdownReset, LogonId=%d\n", LogonId ));
/*
* Find and lock the WinStation struct for the specified LogonId */ pWinStation = FindWinStationById( LogonId, FALSE ); if ( pWinStation == NULL ) { Status = STATUS_CTX_WINSTATION_NOT_FOUND; goto done; }
/*
* Console is a special case since it only logs off */ if ( LogonId == 0 ) { Status = LogoffWinStation( pWinStation, (EWX_FORCE | EWX_LOGOFF) ); ReleaseWinStation( pWinStation ); goto done; }
/*
* Mark the winstation as being deleted. * If a reset/delete operation is already in progress * on this winstation, then don't proceed with the delete. * Also if there is a Connect/disconnect pending, give it * a chance to complete. */ for (ulIndex=0; ulIndex < WINSTATION_WAIT_COMPLETE_RETRIES; ulIndex++) {
if ( pWinStation->Flags & (WSF_RESET | WSF_DELETE) ) { ReleaseWinStation( pWinStation ); Status = STATUS_CTX_WINSTATION_BUSY; goto done; }
if ( pWinStation->Flags & (WSF_CONNECT | WSF_DISCONNECT) ) { LARGE_INTEGER Timeout; Timeout = RtlEnlargedIntegerMultiply( WINSTATION_WAIT_COMPLETE_DURATION, -10000 ); UnlockWinStation( pWinStation ); NtDelayExecution( FALSE, &Timeout ); if ( !RelockWinStation( pWinStation ) ) { ReleaseWinStation( pWinStation ); Status = STATUS_SUCCESS; goto done; } } else { bConnectDisconnectPending = FALSE; break; } }
if ( bConnectDisconnectPending ) { ReleaseWinStation( pWinStation ); Status = STATUS_CTX_WINSTATION_BUSY; goto done; }
pWinStation->Flags |= WSF_DELETE;
/*
* If no broken reason/source have been set, then set them here. */ if ( pWinStation->BrokenReason == 0 ) { pWinStation->BrokenReason = Broken_Terminate; pWinStation->BrokenSource = BrokenSource_Server; }
/*
* Make sure this WinStation is ready to delete */ WinStationTerminate( pWinStation );
/*
* Call the WinStationDelete worker */ WinStationDeleteWorker( pWinStation ); Status = STATUS_SUCCESS;
done: TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: ShutdownReset, Status=0x%x\n", Status )); ExitThread( 0 ); return Status; }
/*****************************************************************************
* WinStationLogoff * * Logoff the WinStation via ExitWindows. * * ENTRY: * ThreadArg (input) * WinStation logonId ****************************************************************************/ ULONG WinStationLogoff(PVOID ThreadArg) { ULONG LogonId = (ULONG)(INT_PTR)ThreadArg; PWINSTATION pWinStation; NTSTATUS Status; LARGE_INTEGER Timeout;
/*
* Wait a maximum of 1 min for the session to logoff */ Timeout = RtlEnlargedIntegerMultiply( 60000, -10000 );
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationLogoff, LogonId=%d\n", LogonId ));
/*
* Find and lock the WinStation struct for the specified LogonId */ pWinStation = FindWinStationById( LogonId, FALSE ); if ( pWinStation == NULL ) { Status = STATUS_CTX_WINSTATION_NOT_FOUND; } else { Status = LogoffWinStation( pWinStation, EWX_LOGOFF);
if (ShutdownInProgress && NT_SUCCESS(Status) && ((pWinStation->State == State_Active) || (pWinStation->State == State_Disconnected))) {
UnlockWinStation( pWinStation ); Status = NtWaitForSingleObject( pWinStation->InitialCommandProcess, FALSE, &Timeout ); RelockWinStation( pWinStation ); }
ReleaseWinStation( pWinStation ); }
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStationLogoff, Status=0x%x\n", Status )); ExitThread( 0 ); return Status; }
/*******************************************************************************
* ResetGroupByListener * * Resets all active winstations on the supplied listen name. * * ENTRY: * pListenName (input) * Type of Winstation (e.g. tcp, ipx) ******************************************************************************/ VOID ResetGroupByListener(PWINSTATIONNAME pListenName) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
/*
* Search the list for all active WinStation with the given ListenName. */ for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if (!wcscmp(pWinStation->ListenName, pListenName) && (!(pWinStation->Flags & (WSF_RESET | WSF_LISTEN)))) { QueueWinStationReset(pWinStation->LogonId); } }
LEAVECRIT( &WinStationListLock ); }
NTSTATUS LogoffWinStation(PWINSTATION pWinStation, ULONG ExitWindowsFlags) { WINSTATION_APIMSG msg; NTSTATUS Status = 0;
/*
* Tell the WinStation to logoff */ msg.ApiNumber = SMWinStationExitWindows; msg.u.ExitWindows.Flags = ExitWindowsFlags; Status = SendWinStationCommand( pWinStation, &msg, 0 ); return Status; }
/*****************************************************************************
* * This section of the file contains the impementation of the digital * certification mechanism for the Stack and WinStation Extension DLLs. This * code is not in a separate file so that external symbols are not visible. * All routines are declared static. * ****************************************************************************/
//
// For security reasons, the TRACE statements in the following routine are
// normally not included. If you want to include them, uncomment the
// SIGN_DEBUG_WINSTA #define below.
//
// #define SIGN_DEBUG_WINSTA
#include <wincrypt.h>
#include <imagehlp.h>
#include <stddef.h>
#include "../../tscert/inc/pubblob.h" // needed by certvfy.inc
#include "../../tscert/inc/certvfy.inc" // VerifyFile()
//
// The following are initialized by VfyInit.
//
static RTL_CRITICAL_SECTION VfyLock; static WCHAR szSystemDir[ MAX_PATH + 1 ]; static WCHAR szDriverDir[ MAX_PATH + 1 ];
/*******************************************************************************
* ReportStackLoadFailure * * Send a StackFailed message to the WinStationApiPort. * * ENTRY: * Module (input) * Name of Module to Log Error Against ******************************************************************************/ static NTSTATUS ReportStackLoadFailure(PWCHAR Module) { HANDLE h; extern WCHAR gpszServiceName[];
h = RegisterEventSource(NULL, gpszServiceName); if (h != NULL) { if (!ReportEventW(h, // event log handle
EVENTLOG_ERROR_TYPE, // event type
0, // category zero
EVENT_BAD_STACK_MODULE,// event identifier
NULL, // no user security identifier
1, // one substitution string
0, // no data
&Module, // pointer to string array
NULL) // pointer to data
) { DBGPRINT(("ReportEvent Failed %ld. Event ID=%lx module=%ws\n",GetLastError(), EVENT_BAD_STACK_MODULE, Module)); }
DeregisterEventSource(h); } else { DBGPRINT(("Cannot RegisterEvent Source %ld Event ID=%lx module=%ws\n",GetLastError(), EVENT_BAD_STACK_MODULE, Module)); }
return STATUS_SUCCESS; }
/******************************************************************************
* _VerifyStackModules * Verifies the integrity of the stack modules and the authenticity * of the digital signature. * * ENTRY: * pWinStation (input) * Pointer to a Listen Winstation. * * EXIT: * STATUS_SUCCESS - no error * STATUS_UNSUCCESSFUL - DLL integrity check, authenticity check failed * or registry stucture invalid *****************************************************************************/ static NTSTATUS _VerifyStackModules(IN PWINSTATION pWinStation) { PWCHAR pszModulePath = NULL; NTSTATUS Status = STATUS_SUCCESS; DWORD KeyIndex; DWORD Error; #ifdef SIGN_BYPASS_OPTION
HKEY hKey; #endif SIGN_BYPASS_OPTION
HKEY hVidKey; HKEY hVidDriverKey; UNICODE_STRING KeyPath; UNICODE_STRING ValueName; OBJECT_ATTRIBUTES ObjectAttributes; HANDLE hServiceKey; PKEY_VALUE_PARTIAL_INFORMATION KeyValueInfo; ULONG ValueLength; #define VALUE_BUFFER_SZ (sizeof(KEY_VALUE_PARTIAL_INFORMATION) + \
256 * sizeof( WCHAR)) PCHAR pValueBuffer = NULL; INT Entries; DWORD dwByteCount; PPDNAME pPdNames, p; INT i; DLLNAME WdDLL;
#ifdef SIGN_BYPASS_OPTION
//
// Check if Verification is to be bypassed
//
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, REG_CONTROL_TSERVER L"\\BypassVerification", 0, KEY_READ, &hKey ) == ERROR_SUCCESS ) { RegCloseKey( hKey ); Status = STATUS_SUCCESS; goto exit; }
#endif //SIGN_BYPASS_OPTION
#ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "System Dir: %ws\n", szSystemDir )); #endif // SIGN_DEBUG_WINSTA
// allocate memory
pszModulePath = MemAlloc( (MAX_PATH + 1) * sizeof(WCHAR) ) ; if (pszModulePath == NULL) { Status = STATUS_NO_MEMORY; goto exit; }
pValueBuffer = MemAlloc( VALUE_BUFFER_SZ ); if (pValueBuffer == NULL) { Status = STATUS_NO_MEMORY; goto exit; }
//
// Verify the WSX DLL if defined
//
if ( pWinStation->Config.Wd.WsxDLL[0] != L'\0' ) { wcscpy( pszModulePath, szSystemDir ); wcscat( pszModulePath, pWinStation->Config.Wd.WsxDLL ); wcscat( pszModulePath, L".DLL" ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "==> WSX Path: %ws\n", pszModulePath )); #endif // SIGN_DEBUG_WINSTA
if ( !VerifyFile( pszModulePath, &VfyLock ) ) { ReportStackLoadFailure(pszModulePath); Status = STATUS_UNSUCCESSFUL; goto exit; } } //
// Verify the WD
//
wcscpy( WdDLL, pWinStation->Config.Wd.WdDLL ); wcscpy( pszModulePath, szDriverDir ); wcscat( pszModulePath, WdDLL ); wcscat( pszModulePath, L".SYS" ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "==> WD Path: %ws\n", pszModulePath )); #endif // SIGN_DEBUG_WINSTA
if ( !VerifyFile( pszModulePath, &VfyLock ) ) { ReportStackLoadFailure(pszModulePath); Status = STATUS_UNSUCCESSFUL; goto exit; } //
// Verify the TD which is in Pd[0]. Always defined for Listen Stack.
//
wcscpy( pszModulePath, szDriverDir ); wcscat( pszModulePath, pWinStation->Config.Pd[0].Create.PdDLL ); wcscat( pszModulePath, L".SYS" ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "==> WD Path: %ws\n", pszModulePath )); #endif // SIGN_DEBUG_WINSTA
if ( !VerifyFile( pszModulePath, &VfyLock ) ) { ReportStackLoadFailure(pszModulePath); Status = STATUS_UNSUCCESSFUL; goto exit; } //
// Enumerate the PDs for this WD and verify all the PDs.
// Can't depend on Pd[i] for this since optional PDs won't
// be present during Listen.
//
Entries = -1; dwByteCount = 0; i = 0; Error = RegPdEnumerate( NULL, WdDLL, FALSE, &i, &Entries, NULL, &dwByteCount ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "RegPdEnumerate 1 complete., Entries %d, Error %d\n", Entries, Error )); #endif // SIGN_DEBUG_WINSTA
if ( Error != ERROR_NO_MORE_ITEMS && Error != ERROR_CANTOPEN ) { Status = STATUS_UNSUCCESSFUL; goto exit; } //
// T.Share doesn't have PDs, so check if none
//
if ( Entries ) { dwByteCount = sizeof(PDNAME) * Entries; pPdNames = MemAlloc( dwByteCount ); if ( !pPdNames ) { Status = STATUS_UNSUCCESSFUL; goto exit; } i = 0; Error = RegPdEnumerate( NULL, WdDLL, FALSE, &i, &Entries, pPdNames, &dwByteCount ); if ( Error != ERROR_SUCCESS ) {
/* makarp #182610 */ MemFree( pPdNames );
Status = STATUS_UNSUCCESSFUL; goto exit; } //
// Open up the Registry entry for each named PD and pull out the value
// of the PdDLL. This is the name of the DLL to verify.
//
for ( i = 0, p = pPdNames; i < Entries; i++, (char*)p += sizeof(PDNAME) ) { HKEY hPdKey; PWCHAR pszPdDLL = NULL; PWCHAR pszRegPath = NULL; DWORD dwLen; DWORD dwType;
// allocate memory
pszPdDLL = MemAlloc( (MAX_PATH+1) * sizeof(WCHAR) ); if (pszPdDLL == NULL) { MemFree( pPdNames ); Status = STATUS_NO_MEMORY; goto exit; }
pszRegPath = MemAlloc( (MAX_PATH+1) * sizeof(WCHAR) ); if (pszRegPath == NULL) { MemFree( pszPdDLL ); MemFree( pPdNames ); Status = STATUS_NO_MEMORY; goto exit; } //
// Build up the Registry Path to open the PD's key
//
wcscpy( pszRegPath, WD_REG_NAME ); wcscat( pszRegPath, L"\\" ); wcscat( pszRegPath, WdDLL ); wcscat( pszRegPath, PD_REG_NAME L"\\" ); wcscat( pszRegPath, p ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "PdKeyPath: %ws\n", pszRegPath )); #endif // SIGN_DEBUG_WINSTA
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, pszRegPath, 0, KEY_READ, &hPdKey ) != ERROR_SUCCESS ) { MemFree( pPdNames ); MemFree( pszPdDLL ); MemFree( pszRegPath ); Status = STATUS_UNSUCCESSFUL; goto exit; } //
// Get the name of the Pd DLL.
//
dwLen = (MAX_PATH + 1) * sizeof(WCHAR) ; if ( RegQueryValueEx( hPdKey, WIN_PDDLL, NULL, &dwType, (PCHAR) pszPdDLL, &dwLen ) != ERROR_SUCCESS ) { MemFree( pPdNames ); MemFree( pszPdDLL ); MemFree( pszRegPath );
// makarp:182610
RegCloseKey(hPdKey);
Status = STATUS_UNSUCCESSFUL; goto exit; }
// makarp:182610
RegCloseKey(hPdKey);
//
// Build path to DLL and attempt verification
//
wcscpy( pszModulePath, szDriverDir ); wcscat( pszModulePath, pszPdDLL ); wcscat( pszModulePath, L".SYS" ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "==> PD Path: %ws\n", pszModulePath )); #endif // SIGN_DEBUG_WINSTA
if ( !VerifyFile( pszModulePath, &VfyLock ) && GetLastError() != ERROR_CANTOPEN ) { MemFree( pPdNames ); MemFree( pszPdDLL ); MemFree( pszRegPath ); ReportStackLoadFailure(pszModulePath); Status = STATUS_UNSUCCESSFUL; goto exit; } MemFree( pszPdDLL ); MemFree( pszRegPath ); } MemFree( pPdNames ); }
//
// for all keys under HKLM\System\CCS\Control\Terminal Server\VIDEO
// open the subkey \Device\Video0 and use that value as
// a string to open
// \REGISTRY\Machine\System\CCS\Services\vdtw30\Device0
// DLL name is in Value "Installed Display Drivers"
//
// Open registry (LOCAL_MACHINE\System\CCS\Control\Terminal Server\VIDEO)
//
// NOTE: All video driver DLLs are verified since there isn't any simple
// method to determine which one is used for this stack.
//
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, VIDEO_REG_NAME, 0, KEY_ENUMERATE_SUB_KEYS, &hVidKey ) != ERROR_SUCCESS ) { Status = STATUS_UNSUCCESSFUL; goto exit; }
for ( KeyIndex = 0 ;; KeyIndex++ ) { // For all VIDEO subkeys
PWCHAR pszVidDriverName = NULL; PWCHAR pszRegPath = NULL; PWCHAR pszDeviceKey = NULL; PWCHAR pszServiceKey = NULL; DWORD dwLen; DWORD dwType;
// allocate memory
pszVidDriverName = MemAlloc( (MAX_PATH + 1) * sizeof(WCHAR) ); if (pszVidDriverName == NULL) { Status = STATUS_NO_MEMORY; goto exit; }
pszRegPath = MemAlloc( (MAX_PATH + 1) * sizeof(WCHAR) ); if (pszRegPath == NULL) { MemFree(pszVidDriverName); Status = STATUS_NO_MEMORY; goto exit; }
pszDeviceKey = MemAlloc( (MAX_PATH + 1) * sizeof(WCHAR) ); if (pszDeviceKey == NULL) { MemFree(pszVidDriverName); MemFree(pszRegPath); Status = STATUS_NO_MEMORY; goto exit; }
pszServiceKey = MemAlloc( (MAX_PATH + 1) * sizeof(WCHAR) ); if (pszServiceKey == NULL) { MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); Status = STATUS_NO_MEMORY; goto exit; }
//
// Get name of VIDEO driver subkey. If end of subkeys, exit loop.
//
if ((Error = RegEnumKey( hVidKey, KeyIndex, pszVidDriverName, MAX_PATH+1))!= ERROR_SUCCESS ){ MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); MemFree(pszServiceKey);
break; // exit for loop
}
//
// Build up the Registry Path to open the VgaCompatible Value
//
wcscpy( pszRegPath, VIDEO_REG_NAME L"\\" ); wcscat( pszRegPath, pszVidDriverName ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "VidDriverKeyPath: %ws\n", pszRegPath )); #endif // SIGN_DEBUG_WINSTA
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, pszRegPath, 0, KEY_READ, &hVidDriverKey ) != ERROR_SUCCESS ) { Status = STATUS_UNSUCCESSFUL; MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); MemFree(pszServiceKey); goto closevidkey; }
//
// Don't like to use constant strings, but this is the way
// WINSRV does it...
//
dwLen = (MAX_PATH + 1) * sizeof(WCHAR) ;
if ( RegQueryValueEx( hVidDriverKey, L"VgaCompatible", NULL, &dwType, (PCHAR) pszDeviceKey, &dwLen ) != ERROR_SUCCESS ) { RegCloseKey( hVidDriverKey ); Status = STATUS_UNSUCCESSFUL; MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); MemFree(pszServiceKey); goto closevidkey; } #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "DeviceKey: %ws\n", pszDeviceKey )); #endif // SIGN_DEBUG_WINSTA
dwLen = (MAX_PATH + 1) * sizeof(WCHAR); if ( RegQueryValueEx( hVidDriverKey, pszDeviceKey, NULL, &dwType, (PCHAR) pszServiceKey, &dwLen ) != ERROR_SUCCESS ) { RegCloseKey( hVidDriverKey ); Status = STATUS_UNSUCCESSFUL; MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); MemFree(pszServiceKey); goto closevidkey; } RegCloseKey( hVidDriverKey ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "ServiceKey: %ws\n", pszServiceKey )); #endif // SIGN_DEBUG_WINSTA
RtlInitUnicodeString( &KeyPath, pszServiceKey ); InitializeObjectAttributes( &ObjectAttributes, &KeyPath, OBJ_CASE_INSENSITIVE, NULL, NULL ); //
// Must use NT Registry APIs since the ServiceKey key name from
// the registry is in the form used by these APIs.
//
Status = NtOpenKey( &hServiceKey, GENERIC_READ, &ObjectAttributes ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: NtOpenKey failed, rc=%x\n", Status )); Status = STATUS_UNSUCCESSFUL; MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); MemFree(pszServiceKey); goto closevidkey; }
//
// Don't like to use constant strings, but this is the way
// WINSRV does it...
//
RtlInitUnicodeString( &ValueName, L"InstalledDisplayDrivers" ); KeyValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)pValueBuffer; Status = NtQueryValueKey( hServiceKey, &ValueName, KeyValuePartialInformation, (PVOID)KeyValueInfo, VALUE_BUFFER_SZ, &ValueLength ); NtClose( hServiceKey ); if ( !NT_SUCCESS( Status ) ) { Status = STATUS_UNSUCCESSFUL; MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); MemFree(pszServiceKey); goto closevidkey; }
wcscpy( pszModulePath, szSystemDir ); wcscat( pszModulePath, (PWCHAR)&KeyValueInfo->Data ); wcscat( pszModulePath, L".DLL" ); #ifdef SIGN_DEBUG_WINSTA
TRACE((hTrace,TC_ICASRV,TT_API1, "==> VidDriverDLLPath: %ws\n", pszModulePath )); #endif // SIGN_DEBUG_WINSTA
if ( !VerifyFile( pszModulePath, &VfyLock ) ) { ReportStackLoadFailure(pszModulePath); Status = STATUS_UNSUCCESSFUL; MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); MemFree(pszServiceKey); goto closevidkey; }
MemFree(pszVidDriverName); MemFree(pszRegPath); MemFree(pszDeviceKey); MemFree(pszServiceKey);
} // for all VIDEO subkeys
closevidkey: RegCloseKey( hVidKey );
exit: if (pszModulePath != NULL) { MemFree(pszModulePath); pszModulePath = NULL; } if (pValueBuffer != NULL) { MemFree(pValueBuffer); pValueBuffer = NULL; } return Status; }
/*******************************************************************************
* VfyInit * Sets up environment for Stack DLL verification. ******************************************************************************/ NTSTATUS VfyInit() { GetSystemDirectory( szSystemDir, sizeof( szSystemDir )/ sizeof(WCHAR)); wcscat( szSystemDir, L"\\" ); wcscpy( szDriverDir, szSystemDir ); wcscat( szDriverDir, L"Drivers\\" );
return RtlInitializeCriticalSection(&VfyLock); }
VOID WinstationUnloadProfile(PWINSTATION pWinStation) { #if 0
NTSTATUS NtStatus; UNICODE_STRING UnicodeString; BOOL bResult;
// if this is not the last session for this user, then we do nothing.
if (WinstationCountUserSessions(pWinStation->pProfileSid, pWinStation->LogonId) != 0) { return; }
// Get the user hive name from user Sid.
NtStatus = RtlConvertSidToUnicodeString( &UnicodeString, pWinStation->pProfileSid, (BOOLEAN)TRUE ); if (!NT_SUCCESS(NtStatus)) { DBGPRINT(("TERMSRV: WinstationUnloadProfile couldn't convert Sid to string. \n")); return; }
// Unload the user's hive.
bResult = WinstationRegUnLoadKey(HKEY_USERS, UnicodeString.Buffer); if (!bResult) { DBGPRINT(("TERMSRV: WinstationUnloadProfile failed. \n")); }
// free allocated string.
RtlFreeUnicodeString(&UnicodeString); #endif
}
BOOL WinstationRegUnLoadKey(HKEY hKey, LPWSTR lpSubKey) { BOOL bResult = TRUE; LONG error; NTSTATUS Status; BOOLEAN WasEnabled;
ENTERCRIT(&UserProfileLock); //
// Enable the restore privilege
//
Status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &WasEnabled);
if (NT_SUCCESS(Status)) {
error = RegUnLoadKey(hKey, lpSubKey);
if ( error != ERROR_SUCCESS) { DBGPRINT(("TERMSRV: WinstationRegUnLoadKey RegUnLoadKey failed. \n")); bResult = FALSE; }
//
// Restore the privilege to its previous state
//
Status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, WasEnabled, FALSE, &WasEnabled); } else { DBGPRINT(("TERMSRV: WinstationRegUnLoadKey adjust privilege failed. \n")); bResult = FALSE; }
LEAVECRIT(&UserProfileLock); return bResult; }
ULONG WinstationCountUserSessions(PSID pUserSid, ULONG CurrentLogonId) { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; ULONG Count = 0; PSID pSid;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
// Search the list for WinStations with a matching ListenName
for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if (pWinStation->LogonId == CurrentLogonId) { continue; } if (pWinStation->pUserSid != NULL) { pSid = pWinStation->pUserSid; } else { pSid = pWinStation->pProfileSid; } if ( (pSid != NULL) && RtlEqualSid( pSid, pUserSid ) ) { Count++; } }
LEAVECRIT( &WinStationListLock ); return Count; }
PWINSTATION FindConsoleSession() { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; PWINSTATION pFoundWinStation = NULL; ULONG uCount;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
/*
* Search the list for a WinStation with the Console Session. */ searchagain: uCount = 0; for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) { pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if ( pWinStation->fOwnsConsoleTerminal) { uCount++;
/*
* Now try to lock the WinStation. */ if (pFoundWinStation == NULL){ if ( !LockRefLock( &pWinStation->Lock ) ) goto searchagain; pFoundWinStation = pWinStation; } #if DBG
#else
break; #endif
} }
ASSERT((uCount <= 1));
/*
* If the WinStationList lock should not be held, then release it now. */ LEAVECRIT( &WinStationListLock );
return pFoundWinStation; }
PWINSTATION FindIdleSessionZero() { PLIST_ENTRY Head, Next; PWINSTATION pWinStation; PWINSTATION pFoundWinStation = NULL; ULONG uCount;
Head = &WinStationListHead; ENTERCRIT( &WinStationListLock );
/*
* Search the list for a WinStation with the Console Session. */ searchagain: uCount = 0; for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) {
pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links ); if (pWinStation->LogonId == 0) { uCount++;
/*
* Now try to lock the WinStation. */ if (pFoundWinStation == NULL){ if ( !LockRefLock( &pWinStation->Lock ) ) goto searchagain; pFoundWinStation = pWinStation; } #if DBG
#else
break; #endif
} }
ASSERT((uCount <= 1));
/*
* If the WinStationList lock should not be held, then release it now. */ LEAVECRIT( &WinStationListLock );
if (pFoundWinStation != NULL) { if ((pFoundWinStation->State == State_Disconnected) && (!pFoundWinStation->Flags) && (pFoundWinStation->UserName[0] == L'\0') ) { return pFoundWinStation; } else { ReleaseWinStation(pFoundWinStation); } } return NULL; }
BOOLEAN WinStationCheckConsoleSession(VOID) { PWINSTATION pWinStation; NTSTATUS Status;
// Check if there already is a console session
pWinStation = FindConsoleSession(); if (pWinStation != NULL) { ReleaseWinStation(pWinStation); return TRUE; } else { if (gConsoleCreationDisable > 0) { return FALSE; } }
//
// See if we can use a disconnected session zero that is not in use
//
if (ConsoleReconnectInfo.hStack != NULL) { pWinStation = FindIdleSessionZero();
if (gConsoleCreationDisable > 0) { if (pWinStation != NULL) { ReleaseWinStation(pWinStation); } return FALSE; }
if (pWinStation != NULL) {
NTSTATUS Status;
pWinStation->Flags |= WSF_CONNECT; Status = WinStationDoReconnect(pWinStation, &ConsoleReconnectInfo); pWinStation->Flags &= ~WSF_CONNECT;
ReleaseWinStation(pWinStation);
if (NT_SUCCESS(Status)) { RtlZeroMemory(&ConsoleReconnectInfo,sizeof(RECONNECT_INFO)); return TRUE; }else{ CleanupReconnect(&ConsoleReconnectInfo); RtlZeroMemory(&ConsoleReconnectInfo,sizeof(RECONNECT_INFO)); } }
}
// We nead to create a new session to connect to the Console
pWinStation = FindIdleWinStation(); if (pWinStation == NULL) { WinStationCreateWorker( NULL, NULL, TRUE); pWinStation = FindIdleWinStation(); if (pWinStation == NULL) { return FALSE; } }
// It is possible that we may have found a idle winstation which is not yet started
// We shud Start the Winstation now in that case
// To check this, check the Subsystem/Initial program is non null
if ( (pWinStation->InitialCommandProcess == NULL) && (pWinStation->WindowsSubSysProcess == NULL) ) {
// Protect this WinStation with WSF_IDLEBUSY flag because we want to use it (WinStationStart unlocks the winstation)
pWinStation->Flags |= WSF_IDLEBUSY;
Status = WinStationStart( pWinStation ) ; if (Status != STATUS_SUCCESS) { // Starting the winstation failed - close connection endpoint and go and wait for another connection
pWinStation->Flags &= ~WSF_IDLEBUSY; goto StartError; } pWinStation->Flags &= ~WSF_IDLEBUSY;
Status = WinStationCreateComplete( pWinStation) ; if (Status != STATUS_SUCCESS) { // WinstationCreateComplete failed - close connection endpoint and go and wait for another connection
goto StartError; } }
if (gConsoleCreationDisable > 0) { ReleaseWinStation(pWinStation); return FALSE; }
// Set the session as owning the Console and wakeup the WaitForConnectWorker
// Actually there is more to do than that and I will need to process LLS licensing here.
pWinStation->fOwnsConsoleTerminal = TRUE; pWinStation->State = State_ConnectQuery; pWinStation->Flags &= ~WSF_IDLE; wcscpy(pWinStation->WinStationName, L"Console");
CleanupReconnect(&ConsoleReconnectInfo); RtlZeroMemory(&ConsoleReconnectInfo,sizeof(RECONNECT_INFO)); NtSetEvent( pWinStation->ConnectEvent, NULL ); ReleaseWinStation(pWinStation);
// If necessary, create another idle WinStation to replace the one being connected
NtSetEvent(WinStationIdleControlEvent, NULL);
return TRUE;
StartError:
if ( !(pWinStation->Flags & (WSF_RESET | WSF_DELETE)) ) { pWinStation->Flags |= WSF_DELETE; WinStationTerminate( pWinStation ); pWinStation->State = State_Down; //PostErrorValueEvent(EVENT_TS_WINSTATION_START_FAILED, Status);
WinStationDeleteWorker(pWinStation); } else { ReleaseWinStation(pWinStation); }
return FALSE; }
/******************************************************************************
* Tells win32k to load the console shadow mirroring driver * * ENTRY: * pWinStation (input) * Pointer to the console Winstation. * pClientConfig (input) * Pointer to the configuration of the shadow client. * * EXIT: * STATUS_SUCCESS - no error * STATUS_xxx - error *****************************************************************************/ NTSTATUS ConsoleShadowStart( IN PWINSTATION pWinStation, IN PWINSTATIONCONFIG2 pClientConfig, IN PVOID pModuleData, IN ULONG ModuleDataLength) { NTSTATUS Status; WINSTATION_APIMSG WMsg; ULONG ReturnLength;
TRACE((hTrace, TC_ICASRV, TT_API1, "CONSOLE REMOTING: LOAD DD\n"));
Status = NtCreateEvent( &pWinStation->ShadowDisplayChangeEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE ); if ( !NT_SUCCESS( Status) ) { goto badevent; }
Status = NtDuplicateObject( NtCurrentProcess(), pWinStation->ShadowDisplayChangeEvent, pWinStation->WindowsSubSysProcess, &WMsg.u.DoConnect.hDisplayChangeEvent, 0, 0, DUPLICATE_SAME_ACCESS ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, NtDuplicateObject 0x%x\n", pWinStation->LogonId, Status )); goto badevent; }
/*
* Read Wd, Cd and Pd configuration data from registry */ Status = RegConsoleShadowQuery( SERVERNAME_CURRENT, pWinStation->WinStationName, pClientConfig->Wd.WdPrefix, &pWinStation->Config, sizeof(WINSTATIONCONFIG2), &ReturnLength ); if ( !NT_SUCCESS(Status) ) { goto badconfig; }
/*
* Build the Console Stack. * We need this special stack for the Console Shadow. */ Status = IcaOpen( &pWinStation->hIca ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV IcaOpen for console stack : Error 0x%x from IcaOpen, last error %d\n", Status, GetLastError() )); goto badopen; }
Status = IcaStackOpen( pWinStation->hIca, Stack_Console, (PROC)WsxStackIoControl, pWinStation, &pWinStation->hStack ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV IcaOpen for console stack : Error 0x%x from IcaOpen, last error %d\n", Status, GetLastError() )); goto badstackopen; }
DBGPRINT(("WinStationStart: pushing stack for console...\n"));
/*
* Load and initialize the WinStation extensions */ pWinStation->pWsx = FindWinStationExtensionDll( pWinStation->Config.Wd.WsxDLL, pWinStation->Config.Wd.WdFlag ); if ( pWinStation->pWsx && pWinStation->pWsx->pWsxWinStationInitialize ) { Status = pWinStation->pWsx->pWsxWinStationInitialize( &pWinStation->pWsxContext ); }
if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV IcaOpen for console stack : Error 0x%x from IcaOpen, last error %d\n", Status, GetLastError() )); goto badextension; }
/*
* Load the stack */ Status = IcaPushConsoleStack( (HANDLE)(pWinStation->hStack), pWinStation->WinStationName, &pWinStation->Config, pModuleData, ModuleDataLength);
if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV IcaOpen for console stack : Error 0x%x from IcaOpen, last error %d\n", Status, GetLastError() )); goto badpushstack; }
DBGPRINT(("WinStationStart: pushed stack for console\n"));
/*
* This code is based on that in WaitForConnectWorker (see wait.c) */ if ( !(pWinStation->pWsx) || !(pWinStation->pWsx->pWsxInitializeClientData) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, No pWsxInitializeClientData\n" )); Status = STATUS_CTX_SHADOW_INVALID; goto done; }
pWinStation->State = State_Idle;
/*
* Open the beep channel (if not already) and duplicate it. * This is one channel that both CSR and ICASRV have open. */ Status = IcaChannelOpen( pWinStation->hIca, Channel_Beep, NULL, &pWinStation->hIcaBeepChannel ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, IcaChannelOpen 0x%x\n", pWinStation->LogonId, Status )); goto done; }
Status = NtDuplicateObject( NtCurrentProcess(), pWinStation->hIcaBeepChannel, pWinStation->WindowsSubSysProcess, &WMsg.u.DoConnect.hIcaBeepChannel, 0, 0, DUPLICATE_SAME_ACCESS ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, NtDuplicateObject 0x%x\n", pWinStation->LogonId, Status )); goto done; }
/*
* Open the thinwire channel (if not already) and duplicate it. * This is one channel that both CSR and ICASRV have open. */ Status = IcaChannelOpen( pWinStation->hIca, Channel_Virtual, VIRTUAL_THINWIRE, &pWinStation->hIcaThinwireChannel ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, IcaChannelOpen 0x%x\n", pWinStation->LogonId, Status )); goto done; }
Status = NtDuplicateObject( NtCurrentProcess(), pWinStation->hIcaThinwireChannel, pWinStation->WindowsSubSysProcess, &WMsg.u.DoConnect.hIcaThinwireChannel, 0, 0, DUPLICATE_SAME_ACCESS ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, NtDuplicateObject 0x%x\n", pWinStation->LogonId, Status )); goto done; }
Status = IcaChannelIoControl( pWinStation->hIcaThinwireChannel, IOCTL_ICA_CHANNEL_ENABLE_SHADOW, NULL, 0, NULL, 0, NULL ); ASSERT( NT_SUCCESS( Status ) );
/*
* Video channel */ Status = WinStationOpenChannel( pWinStation->hIca, pWinStation->WindowsSubSysProcess, Channel_Video, NULL, &WMsg.u.DoConnect.hIcaVideoChannel ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, NtDuplicateObject 0x%x\n", pWinStation->LogonId, Status )); goto done; }
/*
* Keyboard channel */ Status = WinStationOpenChannel( pWinStation->hIca, pWinStation->WindowsSubSysProcess, Channel_Keyboard, NULL, &WMsg.u.DoConnect.hIcaKeyboardChannel ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, NtDuplicateObject 0x%x\n", pWinStation->LogonId, Status )); goto done; }
/*
* Mouse channel */ Status = WinStationOpenChannel( pWinStation->hIca, pWinStation->WindowsSubSysProcess, Channel_Mouse, NULL, &WMsg.u.DoConnect.hIcaMouseChannel ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, NtDuplicateObject 0x%x\n", pWinStation->LogonId, Status )); goto done; }
/*
* Command channel */ Status = WinStationOpenChannel( pWinStation->hIca, pWinStation->WindowsSubSysProcess, Channel_Command, NULL, &WMsg.u.DoConnect.hIcaCommandChannel ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, NtDuplicateObject 0x%x\n", pWinStation->LogonId, Status )); goto done; }
/*
* Secure any virtual channels */ VirtualChannelSecurity( pWinStation );
/*
* Get the client data */ Status = pWinStation->pWsx->pWsxInitializeClientData( pWinStation->pWsxContext, pWinStation->hStack, pWinStation->hIca, pWinStation->hIcaThinwireChannel, pWinStation->VideoModuleName, sizeof(pWinStation->VideoModuleName), &pWinStation->Config.Config.User, &pWinStation->Client.HRes, &pWinStation->Client.VRes, &pWinStation->Client.ColorDepth, &WMsg.u.DoConnect ); if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, InitializeClientData failed 0x%x\n", pWinStation->LogonId, Status )); goto done; }
/*
* Store WinStation name in connect msg */ RtlCopyMemory( WMsg.u.DoConnect.WinStationName, pWinStation->WinStationName, sizeof(WINSTATIONNAME) );
/*
* Save screen resolution, and color depth */ WMsg.u.DoConnect.HRes = pWinStation->Client.HRes; WMsg.u.DoConnect.VRes = pWinStation->Client.VRes;
/*
* Translate the color to the format excpected in winsrv */
switch(pWinStation->Client.ColorDepth){ case 1: WMsg.u.DoConnect.ColorDepth=4 ; // 16 colors
break; case 2: WMsg.u.DoConnect.ColorDepth=8 ; // 256
break; case 4: WMsg.u.DoConnect.ColorDepth= 16;// 64K
break; case 8: WMsg.u.DoConnect.ColorDepth= 24;// 16M
break; #define DC_HICOLOR
#ifdef DC_HICOLOR
case 16: WMsg.u.DoConnect.ColorDepth= 15;// 32K
break; #endif
default: WMsg.u.DoConnect.ColorDepth=8 ; break; }
/*
* Tell Win32 about the connection */ WMsg.ApiNumber = SMWinStationDoConnect; WMsg.u.DoConnect.ConsoleShadowFlag = TRUE;
Status = SendWinStationCommand( pWinStation, &WMsg, 60 );
TRACE((hTrace,TC_ICASRV,TT_API1,"TERMSRV: SMWinStationDoConnect %d Status=0x%x\n", pWinStation->LogonId, Status));
if ( !NT_SUCCESS( Status ) ) { DBGPRINT(( "TERMSRV: ConsoleShadowStart, LogonId=%d, SendWinStationCommand failed 0x%x\n", pWinStation->LogonId, Status )); goto done; }
/*
* This flag is important: without it, WinStationDoDisconnect won't let * Win32k know about the disconnection, so it can't unload the chained DD. */ pWinStation->StateFlags |= WSF_ST_CONNECTED_TO_CSRSS;
/*
* Set connect time */ NtQuerySystemTime( &pWinStation->ConnectTime );
/*
* no need for logon timers here - we don't want to * stop the console session! */
TRACE((hTrace, TC_ICASRV, TT_API1, "CONSOLE REMOTING: LOADED DD\n")); pWinStation->State = State_Active;
return Status;
/*
* Error paths: */ done: // to undo the push stack, does the IcaStackClose below suffice?
pWinStation->State = State_Active;
badpushstack: if (pWinStation->pWsxContext) { if ( pWinStation->pWsx && pWinStation->pWsx->pWsxWinStationRundown ) { pWinStation->pWsx->pWsxWinStationRundown( pWinStation->pWsxContext ); } pWinStation->pWsxContext = NULL; }
badextension: pWinStation->pWsx = NULL;
IcaStackClose( pWinStation->hStack );
badstackopen: IcaClose( pWinStation->hIca );
badopen: pWinStation->Config = gConsoleConfig;
badconfig: NtClose(pWinStation->ShadowDisplayChangeEvent); pWinStation->ShadowDisplayChangeEvent = NULL;
badevent: return Status; }
/******************************************************************************
* Tells win32k to unload the console shadow mirroring driver * * ENTRY: * pWinStation (input) * Pointer to the console Winstation. * * EXIT: * STATUS_SUCCESS - no error * STATUS_xxx - error *****************************************************************************/ NTSTATUS ConsoleShadowStop(PWINSTATION pWinStation) { WINSTATION_APIMSG ConsoleShadowStopMsg; NTSTATUS Status;
/*
* Tell Win32k to unload the chained DD */ ConsoleShadowStopMsg.ApiNumber = SMWinStationDoDisconnect; ConsoleShadowStopMsg.u.DoDisconnect.ConsoleShadowFlag = TRUE; Status = SendWinStationCommand( pWinStation, &ConsoleShadowStopMsg, 600 ); if ( !NT_SUCCESS(Status) ) { TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: CSR ConsoleShadowStop failed LogonId=%d Status=0x%x\n", pWinStation->LogonId, Status )); }
/*
* No matter what happened, everything must be undone. */ if (pWinStation->pWsxContext) { if ( pWinStation->pWsx && pWinStation->pWsx->pWsxWinStationRundown ) { pWinStation->pWsx->pWsxWinStationRundown( pWinStation->pWsxContext ); } pWinStation->pWsxContext = NULL; }
pWinStation->pWsx = NULL;
IcaStackClose( pWinStation->hStack );
IcaClose( pWinStation->hIca );
/*
* Close various ICA channel handles */ if ( pWinStation->hIcaBeepChannel ) { (void) IcaChannelClose( pWinStation->hIcaBeepChannel ); pWinStation->hIcaBeepChannel = NULL; }
if ( pWinStation->hIcaThinwireChannel ) { (void) IcaChannelClose( pWinStation->hIcaThinwireChannel ); pWinStation->hIcaThinwireChannel = NULL; }
/*
* Restore console config. */ pWinStation->Config = gConsoleConfig;
NtClose(pWinStation->ShadowDisplayChangeEvent); pWinStation->ShadowDisplayChangeEvent = NULL;
return Status; }
ULONG CodePairs[] = {
// Very general NT Status
STATUS_SUCCESS, NO_ERROR, STATUS_NO_MEMORY, ERROR_NOT_ENOUGH_MEMORY, STATUS_ACCESS_DENIED, ERROR_ACCESS_DENIED, STATUS_INSUFFICIENT_RESOURCES, ERROR_NO_SYSTEM_RESOURCES, STATUS_BUFFER_TOO_SMALL, ERROR_INSUFFICIENT_BUFFER, STATUS_OBJECT_NAME_NOT_FOUND, ERROR_FILE_NOT_FOUND, STATUS_NOT_SUPPORTED, ERROR_NOT_SUPPORTED, // RPC specific Status
RPC_NT_SERVER_UNAVAILABLE, RPC_S_SERVER_UNAVAILABLE, RPC_NT_INVALID_STRING_BINDING, RPC_S_INVALID_STRING_BINDING, RPC_NT_WRONG_KIND_OF_BINDING, RPC_S_WRONG_KIND_OF_BINDING, RPC_NT_PROTSEQ_NOT_SUPPORTED, RPC_S_PROTSEQ_NOT_SUPPORTED, RPC_NT_INVALID_RPC_PROTSEQ, RPC_S_INVALID_RPC_PROTSEQ, RPC_NT_INVALID_STRING_UUID, RPC_S_INVALID_STRING_UUID, RPC_NT_INVALID_ENDPOINT_FORMAT, RPC_S_INVALID_ENDPOINT_FORMAT, RPC_NT_INVALID_NET_ADDR, RPC_S_INVALID_NET_ADDR, RPC_NT_NO_ENDPOINT_FOUND, RPC_S_NO_ENDPOINT_FOUND, RPC_NT_INVALID_TIMEOUT, RPC_S_INVALID_TIMEOUT, RPC_NT_OBJECT_NOT_FOUND, RPC_S_OBJECT_NOT_FOUND, RPC_NT_ALREADY_REGISTERED, RPC_S_ALREADY_REGISTERED, RPC_NT_TYPE_ALREADY_REGISTERED, RPC_S_TYPE_ALREADY_REGISTERED, RPC_NT_ALREADY_LISTENING, RPC_S_ALREADY_LISTENING, RPC_NT_NO_PROTSEQS_REGISTERED, RPC_S_NO_PROTSEQS_REGISTERED, RPC_NT_NOT_LISTENING, RPC_S_NOT_LISTENING, RPC_NT_UNKNOWN_MGR_TYPE, RPC_S_UNKNOWN_MGR_TYPE, RPC_NT_UNKNOWN_IF, RPC_S_UNKNOWN_IF, RPC_NT_NO_BINDINGS, RPC_S_NO_BINDINGS, RPC_NT_NO_MORE_BINDINGS, RPC_S_NO_MORE_BINDINGS, RPC_NT_NO_PROTSEQS, RPC_S_NO_PROTSEQS, RPC_NT_CANT_CREATE_ENDPOINT, RPC_S_CANT_CREATE_ENDPOINT, RPC_NT_OUT_OF_RESOURCES, RPC_S_OUT_OF_RESOURCES, RPC_NT_SERVER_TOO_BUSY, RPC_S_SERVER_TOO_BUSY, RPC_NT_INVALID_NETWORK_OPTIONS, RPC_S_INVALID_NETWORK_OPTIONS, RPC_NT_NO_CALL_ACTIVE, RPC_S_NO_CALL_ACTIVE, RPC_NT_CALL_FAILED, RPC_S_CALL_FAILED, RPC_NT_CALL_FAILED_DNE, RPC_S_CALL_FAILED_DNE, RPC_NT_PROTOCOL_ERROR, RPC_S_PROTOCOL_ERROR, RPC_NT_UNSUPPORTED_TRANS_SYN, RPC_S_UNSUPPORTED_TRANS_SYN, RPC_NT_UNSUPPORTED_TYPE, RPC_S_UNSUPPORTED_TYPE, RPC_NT_INVALID_TAG, RPC_S_INVALID_TAG, RPC_NT_INVALID_BOUND, RPC_S_INVALID_BOUND, RPC_NT_NO_ENTRY_NAME, RPC_S_NO_ENTRY_NAME, RPC_NT_INVALID_NAME_SYNTAX, RPC_S_INVALID_NAME_SYNTAX, RPC_NT_UNSUPPORTED_NAME_SYNTAX, RPC_S_UNSUPPORTED_NAME_SYNTAX, RPC_NT_UUID_NO_ADDRESS, RPC_S_UUID_NO_ADDRESS, RPC_NT_DUPLICATE_ENDPOINT, RPC_S_DUPLICATE_ENDPOINT, RPC_NT_UNKNOWN_AUTHN_TYPE, RPC_S_UNKNOWN_AUTHN_TYPE, RPC_NT_MAX_CALLS_TOO_SMALL, RPC_S_MAX_CALLS_TOO_SMALL, RPC_NT_STRING_TOO_LONG, RPC_S_STRING_TOO_LONG, RPC_NT_PROTSEQ_NOT_FOUND, RPC_S_PROTSEQ_NOT_FOUND, RPC_NT_PROCNUM_OUT_OF_RANGE, RPC_S_PROCNUM_OUT_OF_RANGE, RPC_NT_BINDING_HAS_NO_AUTH, RPC_S_BINDING_HAS_NO_AUTH, RPC_NT_UNKNOWN_AUTHN_SERVICE, RPC_S_UNKNOWN_AUTHN_SERVICE, RPC_NT_UNKNOWN_AUTHN_LEVEL, RPC_S_UNKNOWN_AUTHN_LEVEL, RPC_NT_INVALID_AUTH_IDENTITY, RPC_S_INVALID_AUTH_IDENTITY, RPC_NT_UNKNOWN_AUTHZ_SERVICE, RPC_S_UNKNOWN_AUTHZ_SERVICE, RPC_NT_NOTHING_TO_EXPORT, RPC_S_NOTHING_TO_EXPORT, RPC_NT_INCOMPLETE_NAME, RPC_S_INCOMPLETE_NAME, RPC_NT_INVALID_VERS_OPTION, RPC_S_INVALID_VERS_OPTION, RPC_NT_NO_MORE_MEMBERS, RPC_S_NO_MORE_MEMBERS, RPC_NT_NOT_ALL_OBJS_UNEXPORTED, RPC_S_NOT_ALL_OBJS_UNEXPORTED, RPC_NT_INTERFACE_NOT_FOUND, RPC_S_INTERFACE_NOT_FOUND, RPC_NT_ENTRY_ALREADY_EXISTS, RPC_S_ENTRY_ALREADY_EXISTS, RPC_NT_ENTRY_NOT_FOUND, RPC_S_ENTRY_NOT_FOUND, RPC_NT_NAME_SERVICE_UNAVAILABLE, RPC_S_NAME_SERVICE_UNAVAILABLE, RPC_NT_INVALID_NAF_ID, RPC_S_INVALID_NAF_ID, RPC_NT_CANNOT_SUPPORT, RPC_S_CANNOT_SUPPORT, RPC_NT_NO_CONTEXT_AVAILABLE, RPC_S_NO_CONTEXT_AVAILABLE, RPC_NT_INTERNAL_ERROR, RPC_S_INTERNAL_ERROR, RPC_NT_ZERO_DIVIDE, RPC_S_ZERO_DIVIDE, RPC_NT_ADDRESS_ERROR, RPC_S_ADDRESS_ERROR, RPC_NT_FP_DIV_ZERO, RPC_S_FP_DIV_ZERO, RPC_NT_FP_UNDERFLOW, RPC_S_FP_UNDERFLOW, RPC_NT_FP_OVERFLOW, RPC_S_FP_OVERFLOW, RPC_NT_NO_MORE_ENTRIES, RPC_X_NO_MORE_ENTRIES, RPC_NT_SS_CHAR_TRANS_OPEN_FAIL, RPC_X_SS_CHAR_TRANS_OPEN_FAIL, RPC_NT_SS_CHAR_TRANS_SHORT_FILE, RPC_X_SS_CHAR_TRANS_SHORT_FILE, RPC_NT_SS_CONTEXT_MISMATCH, ERROR_INVALID_HANDLE, RPC_NT_SS_CONTEXT_DAMAGED, RPC_X_SS_CONTEXT_DAMAGED, RPC_NT_SS_HANDLES_MISMATCH, RPC_X_SS_HANDLES_MISMATCH, RPC_NT_SS_CANNOT_GET_CALL_HANDLE, RPC_X_SS_CANNOT_GET_CALL_HANDLE, RPC_NT_NULL_REF_POINTER, RPC_X_NULL_REF_POINTER, RPC_NT_ENUM_VALUE_OUT_OF_RANGE, RPC_X_ENUM_VALUE_OUT_OF_RANGE, RPC_NT_BYTE_COUNT_TOO_SMALL, RPC_X_BYTE_COUNT_TOO_SMALL, RPC_NT_BAD_STUB_DATA, RPC_X_BAD_STUB_DATA, RPC_NT_INVALID_OBJECT, RPC_S_INVALID_OBJECT, RPC_NT_GROUP_MEMBER_NOT_FOUND, RPC_S_GROUP_MEMBER_NOT_FOUND, RPC_NT_NO_INTERFACES, RPC_S_NO_INTERFACES, RPC_NT_CALL_CANCELLED, RPC_S_CALL_CANCELLED, RPC_NT_BINDING_INCOMPLETE, RPC_S_BINDING_INCOMPLETE, RPC_NT_COMM_FAILURE, RPC_S_COMM_FAILURE, RPC_NT_UNSUPPORTED_AUTHN_LEVEL, RPC_S_UNSUPPORTED_AUTHN_LEVEL, RPC_NT_NO_PRINC_NAME, RPC_S_NO_PRINC_NAME, RPC_NT_NOT_RPC_ERROR, RPC_S_NOT_RPC_ERROR, RPC_NT_UUID_LOCAL_ONLY, RPC_S_UUID_LOCAL_ONLY, RPC_NT_SEC_PKG_ERROR, RPC_S_SEC_PKG_ERROR, RPC_NT_NOT_CANCELLED, RPC_S_NOT_CANCELLED, RPC_NT_INVALID_ES_ACTION, RPC_X_INVALID_ES_ACTION, RPC_NT_WRONG_ES_VERSION, RPC_X_WRONG_ES_VERSION, RPC_NT_WRONG_STUB_VERSION, RPC_X_WRONG_STUB_VERSION, RPC_NT_INVALID_PIPE_OBJECT, RPC_X_INVALID_PIPE_OBJECT, RPC_NT_WRONG_PIPE_VERSION, RPC_X_WRONG_PIPE_VERSION, RPC_NT_SEND_INCOMPLETE, RPC_S_SEND_INCOMPLETE, RPC_NT_INVALID_ASYNC_HANDLE, RPC_S_INVALID_ASYNC_HANDLE, RPC_NT_INVALID_ASYNC_CALL, RPC_S_INVALID_ASYNC_CALL, RPC_NT_PIPE_CLOSED, RPC_X_PIPE_CLOSED, RPC_NT_PIPE_EMPTY, RPC_X_PIPE_EMPTY, RPC_NT_PIPE_DISCIPLINE_ERROR, RPC_X_PIPE_DISCIPLINE_ERROR,
// Terminal Server Specific Status.
STATUS_CTX_CLOSE_PENDING, ERROR_CTX_CLOSE_PENDING, STATUS_CTX_NO_OUTBUF, ERROR_CTX_NO_OUTBUF, STATUS_CTX_MODEM_INF_NOT_FOUND, ERROR_CTX_MODEM_INF_NOT_FOUND, STATUS_CTX_INVALID_MODEMNAME, ERROR_CTX_INVALID_MODEMNAME, STATUS_CTX_RESPONSE_ERROR, ERROR_CTX_MODEM_RESPONSE_ERROR, STATUS_CTX_MODEM_RESPONSE_TIMEOUT, ERROR_CTX_MODEM_RESPONSE_TIMEOUT, STATUS_CTX_MODEM_RESPONSE_NO_CARRIER, ERROR_CTX_MODEM_RESPONSE_NO_CARRIER, STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE, ERROR_CTX_MODEM_RESPONSE_NO_DIALTONE, STATUS_CTX_MODEM_RESPONSE_BUSY, ERROR_CTX_MODEM_RESPONSE_BUSY, STATUS_CTX_MODEM_RESPONSE_VOICE, ERROR_CTX_MODEM_RESPONSE_VOICE, STATUS_CTX_TD_ERROR, ERROR_CTX_TD_ERROR, STATUS_LPC_REPLY_LOST, ERROR_CONNECTION_ABORTED, STATUS_CTX_WINSTATION_NAME_INVALID, ERROR_CTX_WINSTATION_NAME_INVALID, STATUS_CTX_WINSTATION_NOT_FOUND, ERROR_CTX_WINSTATION_NOT_FOUND, STATUS_CTX_WINSTATION_NAME_COLLISION, ERROR_CTX_WINSTATION_ALREADY_EXISTS, STATUS_CTX_WINSTATION_BUSY, ERROR_CTX_WINSTATION_BUSY, STATUS_CTX_GRAPHICS_INVALID, ERROR_CTX_GRAPHICS_INVALID, STATUS_CTX_BAD_VIDEO_MODE, ERROR_CTX_BAD_VIDEO_MODE, STATUS_CTX_NOT_CONSOLE, ERROR_CTX_NOT_CONSOLE, STATUS_CTX_CLIENT_QUERY_TIMEOUT, ERROR_CTX_CLIENT_QUERY_TIMEOUT, STATUS_CTX_CONSOLE_DISCONNECT, ERROR_CTX_CONSOLE_DISCONNECT, STATUS_CTX_CONSOLE_CONNECT, ERROR_CTX_CONSOLE_CONNECT, STATUS_CTX_SHADOW_DENIED, ERROR_CTX_SHADOW_DENIED, STATUS_CTX_SHADOW_INVALID, ERROR_CTX_SHADOW_INVALID, STATUS_CTX_SHADOW_DISABLED, ERROR_CTX_SHADOW_DISABLED, STATUS_CTX_WINSTATION_ACCESS_DENIED, ERROR_CTX_WINSTATION_ACCESS_DENIED, STATUS_CTX_INVALID_PD, ERROR_CTX_INVALID_PD, STATUS_CTX_PD_NOT_FOUND, ERROR_CTX_PD_NOT_FOUND, STATUS_CTX_INVALID_WD, ERROR_CTX_INVALID_WD, STATUS_CTX_WD_NOT_FOUND, ERROR_CTX_WD_NOT_FOUND, STATUS_CTX_CLIENT_LICENSE_IN_USE, ERROR_CTX_CLIENT_LICENSE_IN_USE, STATUS_CTX_CLIENT_LICENSE_NOT_SET, ERROR_CTX_CLIENT_LICENSE_NOT_SET, STATUS_CTX_LICENSE_NOT_AVAILABLE, ERROR_CTX_LICENSE_NOT_AVAILABLE, STATUS_CTX_LICENSE_CLIENT_INVALID, ERROR_CTX_LICENSE_CLIENT_INVALID, STATUS_CTX_LICENSE_EXPIRED, ERROR_CTX_LICENSE_EXPIRED,
};
/*
* WinStationWinerrorToNtStatus * Translate a Windows error code into an NTSTATUS code. */
NTSTATUS WinStationWinerrorToNtStatus(ULONG ulWinError) { ULONG ulIndex;
for (ulIndex = 0 ; ulIndex < sizeof(CodePairs)/sizeof(CodePairs[0]) ; ulIndex+=2) { if (CodePairs[ ulIndex+1 ] == ulWinError ) { return (NTSTATUS) CodePairs[ ulIndex]; } } return STATUS_UNSUCCESSFUL; }
/*
* WinStationSetMaxOustandingConnections() set the default values * for the maximum number of outstanding connection connections. * Reads the registry configuration for it if it exists. */
VOID WinStationSetMaxOustandingConnections() { SYSTEM_BASIC_INFORMATION BasicInfo; HKEY hKey; NTSTATUS Status; BOOL bLargeMachine = FALSE;
// Initialize date of last delayed connection that was logged into
// event log. In order not to flood event log with what may not be a DOS
// attack but just a normal regulation action, delayed connection are not
// logged more than once in 24h.
GetSystemTime(&LastLoggedDelayConnection);
// Init the default values for maximum outstanding connection and
// Maximumn outstanding connections from single IP address. For
// Non server platforms these are fixed values.
if (!gbServer) { MaxOutStandingConnect = MAX_DEFAULT_CONNECTIONS_PRO; MaxSingleOutStandingConnect = MAX_DEFAULT_SINGLE_CONNECTIONS_PRO; } else { // Determine if this Machine has over 512Mb of memory
// In order to set defaults Values (registry settings overide this anyway).
// Default value are not changed for machines over 512 Mb : Session regulation
// is trigered if we have 50 outstanding connection and we will wait 30 seconds
// before acception new connections. For machines with less than 512 Mb, regulation
// needs to be stronger : it is trigered at lower number of outstanding connections and we will
// wait 70 seconds before accepting new connections.
MaxOutStandingConnect = MAX_DEFAULT_CONNECTIONS;
Status = NtQuerySystemInformation( SystemBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL );
if (NT_SUCCESS(Status)) { if (BasicInfo.PageSize > 1024*1024) { MaxOutStandingConnect = MAX_DEFAULT_CONNECTIONS; DelayConnectionTime = 30*1000;
}else{ ULONG ulPagesPerMeg = 1024*1024/BasicInfo.PageSize; ULONG ulMemSizeInMegabytes = (ULONG)BasicInfo.NumberOfPhysicalPages/ulPagesPerMeg ;
if (ulMemSizeInMegabytes >= 512) { MaxOutStandingConnect = MAX_DEFAULT_CONNECTIONS; DelayConnectionTime = 70*1000; } else if (ulMemSizeInMegabytes >= 256) { MaxOutStandingConnect = 15; DelayConnectionTime = 70*1000;
} else if (ulMemSizeInMegabytes >= 128) { MaxOutStandingConnect = 10; DelayConnectionTime = 70*1000; } else { MaxOutStandingConnect = 5; DelayConnectionTime = 70*1000; } } }
//
// set max number of outstanding connection from single IP
//
if ( MaxOutStandingConnect < MAX_SINGLE_CONNECT_THRESHOLD_DIFF*5) { MaxSingleOutStandingConnect = MaxOutStandingConnect - 1; } else { MaxSingleOutStandingConnect = MaxOutStandingConnect - MAX_SINGLE_CONNECT_THRESHOLD_DIFF; } }
}
/*
* Make sure we can Preallocate an Idle session before allowing console disconnect. * */
NTSTATUS CheckIdleWinstation() { PWINSTATION pWinStation; NTSTATUS Status; pWinStation = FindIdleWinStation();
if ( pWinStation == NULL ) {
/*
* Create another idle WinStation */ Status = WinStationCreateWorker( NULL, NULL, TRUE ); if ( NT_SUCCESS( Status ) ) { pWinStation = FindIdleWinStation(); if ( pWinStation == NULL ) { return STATUS_INSUFFICIENT_RESOURCES; } } else { return STATUS_INSUFFICIENT_RESOURCES; }
}
ReleaseWinStation(pWinStation); return STATUS_SUCCESS; }
NTSTATUS InitializeWinStationSecurityLock( VOID ) { NTSTATUS Status ;
try { RtlInitializeResource( &WinStationSecurityLock ); Status = STATUS_SUCCESS ; } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); }
return Status; }
//gets the product id from the registry
NTSTATUS GetProductIdFromRegistry( WCHAR* DigProductId, DWORD dwSize ) { HKEY hKey = NULL; NTSTATUS status = STATUS_UNSUCCESSFUL; ZeroMemory( DigProductId, dwSize ); if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, REG_WINDOWS_KEY, 0, KEY_READ, &hKey) == ERROR_SUCCESS ) { DWORD dwType = REG_SZ; if( RegQueryValueEx( hKey, L"ProductId", NULL, &dwType, (LPBYTE)DigProductId, &dwSize ) == ERROR_SUCCESS ) status = STATUS_SUCCESS; }
if (hKey) RegCloseKey( hKey );
return status; }
//
// Gets the remote IP address of the connections
// and supports statistics of how many outstanding connections
// are there for this client, if the number of outstanding connections
// reaches MaxSingleOutStandingConnections, *pbBlocked is returned FALSE
// the functions returns TRUE on success
//
// Paramters:
// pContext
// pEndpoint - handle of this connection
// EndpointLength - td layer needs the length
// pin_addr - returns remote IP address
// pbBlocked - returns TRUE if the connection has to be blocked, because of excessive number of
// outstanding connections
//
BOOL Filter_AddOutstandingConnection( IN HANDLE pContext, IN PVOID pEndpoint, IN ULONG EndpointLength, OUT PBYTE pin_addr, OUT PUINT puAddrSize, OUT BOOLEAN *pbBlocked ) { BOOL rv = FALSE; PTS_OUTSTANDINGCONNECTION pIter, pPrev; TS_OUTSTANDINGCONNECTION key; struct sockaddr_in6 addr6; ULONG AddrBytesReturned; NTSTATUS Status; PVOID paddr; BOOL bLocked = FALSE; PVOID bSucc; BOOLEAN bNewElement; ULONGLONG currentTime;
*pbBlocked = FALSE;
Status = IcaStackIoControl( pContext, IOCTL_TS_STACK_QUERY_REMOTEADDRESS, pEndpoint, EndpointLength, &addr6, sizeof( addr6 ), &AddrBytesReturned ); if ( !NT_SUCCESS( Status )) { goto exitpt; }
if ( AF_INET == addr6.sin6_family ) { key.uAddrSize = 4; paddr = &(((struct sockaddr_in *)&addr6)->sin_addr.s_addr); } else if ( AF_INET6 == addr6.sin6_family ) { key.uAddrSize = 16; paddr = &(addr6.sin6_addr); } else { ASSERT( 0 ); } ASSERT ( *puAddrSize >= key.uAddrSize );
RtlCopyMemory( pin_addr, paddr, key.uAddrSize ); *puAddrSize = key.uAddrSize;
ENTERCRIT( &FilterLock ); bLocked = TRUE;
//
// Check first in the outstanding connections
//
RtlCopyMemory( key.addr, paddr, key.uAddrSize ); pIter = RtlLookupElementGenericTable( &gOutStandingConnections, &key );
if ( NULL == pIter ) {
//
// check in the blocked connections list
//
pPrev = NULL; pIter = g_pBlockedConnections; while ( NULL != pIter ) { if ( key.uAddrSize == pIter->uAddrSize && key.uAddrSize == RtlCompareMemory( pIter->addr, paddr, key.uAddrSize )) { break; } pPrev = pIter; pIter = pIter->pNext; }
if ( NULL != pIter ) { pIter->NumOutStandingConnect ++; //
// already blocked, check for exparation time
//
GetSystemTimeAsFileTime( (LPFILETIME)¤tTime ); if ( currentTime > pIter->blockUntilTime ) { //
// unblock, remove from list
//
pIter->blockUntilTime = 0; if ( NULL != pPrev ) { pPrev->pNext = pIter->pNext; } else { g_pBlockedConnections = pIter->pNext; }
bSucc = RtlInsertElementGenericTable( &gOutStandingConnections, pIter, sizeof( *pIter ), &bNewElement ); if ( !bSucc ) { MemFree( pIter ); goto exitpt; } ASSERT( bNewElement ); MemFree( pIter );
} else { *pbBlocked = TRUE; }
} else { //
// this will be a new connection
//
key.NumOutStandingConnect = 1;
bSucc = RtlInsertElementGenericTable( &gOutStandingConnections, &key, sizeof( key ), &bNewElement ); if ( !bSucc ) { goto exitpt; } ASSERT( bNewElement ); } } else {
pIter->NumOutStandingConnect ++; //
// Check if we need to block this connection
//
if ( pIter->NumOutStandingConnect > MaxSingleOutStandingConnect ) { *pbBlocked = TRUE; key.NumOutStandingConnect = pIter->NumOutStandingConnect;
GetSystemTimeAsFileTime( (LPFILETIME)¤tTime ); // DelayConnectionTime is in ms
// currentTime is in 100s ns
key.blockUntilTime = currentTime + ((ULONGLONG)10000) * ((ULONGLONG)DelayConnectionTime);
RtlDeleteElementGenericTable( &gOutStandingConnections, &key );
//
// add to the blocked connections
//
pIter = MemAlloc( sizeof( *pIter )); if ( NULL == pIter ) { goto exitpt; }
RtlCopyMemory( pIter, &key, sizeof( *pIter )); pIter->pNext = g_pBlockedConnections; g_pBlockedConnections = pIter;
//
// log at most one event on every 15 minutes
//
if ( LastLoggedBlockedConnection + ((ULONGLONG)10000) * (15 * 60 * 1000) < currentTime ) { LastLoggedBlockedConnection = currentTime; WriteErrorLogEntry( EVENT_TOO_MANY_CONNECTIONS, &key.addr, key.uAddrSize ); } }
}
rv = TRUE;
exitpt:
if ( bLocked ) { LEAVECRIT( &FilterLock ); }
return rv; }
//
// Removes outstanding connections added in AddOutStandingConnection
//
BOOL Filter_RemoveOutstandingConnection( IN PBYTE paddr, IN UINT uAddrSize ) { PTS_OUTSTANDINGCONNECTION pIter, pPrev, pNext; TS_OUTSTANDINGCONNECTION key; ULONGLONG currentTime; NTSTATUS Status; ULONG AddrBytesReturned; #if DBG
BOOL bFound = FALSE; #endif
pPrev = NULL; GetSystemTimeAsFileTime( (LPFILETIME)¤tTime );
key.uAddrSize = uAddrSize; RtlCopyMemory( key.addr, paddr, uAddrSize );
ENTERCRIT( &FilterLock );
pIter = RtlLookupElementGenericTable( &gOutStandingConnections, &key ); if ( NULL != pIter ) { #if DBG
bFound = TRUE; #endif
pIter->NumOutStandingConnect--;
//
// cleanup connections w/o reference
//
if ( 0 == pIter->NumOutStandingConnect ) { RtlDeleteElementGenericTable( &gOutStandingConnections, &key ); }
}
//
// work through the blocked list
//
pIter = g_pBlockedConnections;
while( pIter ) { if ( uAddrSize == pIter->uAddrSize && uAddrSize == RtlCompareMemory( pIter->addr, paddr, uAddrSize )) { ASSERT( 0 != pIter->NumOutStandingConnect ); pIter->NumOutStandingConnect--; #if DBG
ASSERT( !bFound ); bFound = TRUE; #endif
}
//
// cleanup all connections w/o references
//
if ( 0 == pIter->NumOutStandingConnect && currentTime > pIter->blockUntilTime ) { if ( NULL == pPrev ) { g_pBlockedConnections = pIter->pNext; } else { pPrev->pNext = pIter->pNext; } //
// remove item and advance to the next
//
pNext = pIter->pNext; MemFree( pIter ); pIter = pNext; } else { //
// advance to the next item
//
pPrev = pIter; pIter = pIter->pNext; }
}
ASSERT( bFound );
/*
* Decrement the number of outstanding connections. * If connections drop back to max value, set the connect event. */ #if DBG
//
// ensure proper cleanup
//
bFound = ( 0 == gOutStandingConnections.NumberGenericTableElements ); for( pIter = g_pBlockedConnections; pIter; pIter = pIter->pNext ) { bFound = bFound & ( 0 == pIter->NumOutStandingConnect ); }
#endif
LEAVECRIT( &FilterLock );
return TRUE; }
/*****************************************************************************
* * Filter_CompareConnectionEntry * * Generic table support.Compare two connection entries * * ****************************************************************************/
RTL_GENERIC_COMPARE_RESULTS NTAPI Filter_CompareConnectionEntry( IN struct _RTL_GENERIC_TABLE *Table, IN PVOID FirstInstance, IN PVOID SecondInstance ) { PTS_OUTSTANDINGCONNECTION pFirst, pSecond; INT rc;
pFirst = (PTS_OUTSTANDINGCONNECTION)FirstInstance; pSecond = (PTS_OUTSTANDINGCONNECTION)SecondInstance;
if ( pFirst->uAddrSize < pSecond->uAddrSize ) { return GenericLessThan; } else if ( pFirst->uAddrSize > pSecond->uAddrSize ) { return GenericGreaterThan; }
rc = memcmp( pFirst->addr, pSecond->addr, pFirst->uAddrSize ); return ( rc < 0 )?GenericLessThan: ( rc > 0 )?GenericGreaterThan: GenericEqual; }
/*****************************************************************************
* * Filter_AllocateConnectionEntry * * Generic table support. Allocates a new table entry * * ****************************************************************************/
PVOID Filter_AllocateConnectionEntry( IN struct _RTL_GENERIC_TABLE *Table, IN CLONG ByteSize ) { return MemAlloc( ByteSize ); }
/*****************************************************************************
* * Filter_FreeConnectionEntry * * Generic table support. frees a new table entry * * ****************************************************************************/
VOID Filter_FreeConnectionEntry ( IN struct _RTL_GENERIC_TABLE *Table, IN PVOID Buffer ) { MemFree( Buffer ); }
VOID Filter_DestroyList( VOID ) { PTS_OUTSTANDINGCONNECTION p; TS_OUTSTANDINGCONNECTION con;
while ( NULL != g_pBlockedConnections ) { p = g_pBlockedConnections->pNext; MemFree( g_pBlockedConnections ); g_pBlockedConnections = p; }
while (p = RtlEnumerateGenericTable( &gOutStandingConnections, TRUE)) { RtlCopyMemory( &con, p, sizeof( con )); RtlDeleteElementGenericTable( &gOutStandingConnections, &con); } }
//
// ComputeHMACVerifier
// Compute the HMAC verifier from the random
// and the cookie
//
BOOL ComputeHMACVerifier( PBYTE pCookie, //IN - the shared secret
LONG cbCookieLen, //IN - the shared secret len
PBYTE pRandom, //IN - the session random
LONG cbRandomLen, //IN - the session random len
PBYTE pVerifier, //OUT- the verifier
LONG cbVerifierLen //IN - the verifier buffer length
) { HMACMD5_CTX hmacctx; BOOL fRet = FALSE;
ASSERT(cbVerifierLen >= MD5DIGESTLEN);
if (!(pCookie && cbCookieLen && pRandom && cbRandomLen && pVerifier && cbVerifierLen)) { goto bail_out; }
HMACMD5Init(&hmacctx, pCookie, cbCookieLen);
HMACMD5Update(&hmacctx, pRandom, cbRandomLen); HMACMD5Final(&hmacctx, pVerifier);
fRet = TRUE;
bail_out: return fRet; }
//
// Extract the session to reconnect to from the ARC info
// also do the necessary security checks
//
// Params:
// pClientArcInfo - autoreconnect information from the client
//
// Returns:
// If all security checks pass and pArc is valid then winstation
// to reconnect to is returned. Else NULL
//
// NOTE: WinStation returned is left LOCKED.
//
PWINSTATION GetWinStationFromArcInfo( PBYTE pClientRandom, LONG cbClientRandomLen, PTS_AUTORECONNECTINFO pClientArcInfo ) { NTSTATUS Status = STATUS_UNSUCCESSFUL; PWINSTATION pWinStation = NULL; PWINSTATION pFoundWinStation = NULL; ARC_CS_PRIVATE_PACKET UNALIGNED* pCSArcInfo = NULL; BYTE arcSCclientBlob[ARC_SC_SECURITY_TOKEN_LEN]; BYTE hmacVerifier[ARC_CS_SECURITY_TOKEN_LEN]; PBYTE pServerArcBits = NULL; ULONG BytesGot = 0; TS_AUTORECONNECTINFO SCAutoReconnectInfo;
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStation GetWinStationFromArcInfo pRandom:%p len:%d\n", pClientRandom, cbClientRandomLen));
if (!pClientArcInfo) { goto error; }
pCSArcInfo = (ARC_CS_PRIVATE_PACKET UNALIGNED*)pClientArcInfo->AutoReconnectInfo;
if (!pCSArcInfo->cbLen || pCSArcInfo->cbLen < sizeof(ARC_CS_PRIVATE_PACKET)) {
TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: GetWinStationFromArcInfo ARC length invalid bailing out\n")); goto error; }
memset(arcSCclientBlob, 0, sizeof(arcSCclientBlob)); pWinStation = FindWinStationById(pCSArcInfo->LogonId, FALSE); if (pWinStation) {
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: GetWinStationFromArcInfo found arc winstation: %d\n", pCSArcInfo->LogonId)); //
// Do security checks to ensure this is the same winstation
// that was connected to the client
//
//
// First obtain the last autoreconnect blob sent to the client
// since we do an inline cookie update in rdpwd
//
if (pWinStation->AutoReconnectInfo.Valid) { pServerArcBits = pWinStation->AutoReconnectInfo.ArcRandomBits;
Status = STATUS_SUCCESS; } else { if (pWinStation->pWsx && pWinStation->pWsx->pWsxEscape) {
if (pWinStation->Terminating || pWinStation->StateFlags & WSF_ST_WINSTATIONTERMINATE || !pWinStation->WinStationName[0]) {
TRACE((hTrace,TC_ICASRV,TT_ERROR, "GetWinStationFromArcInfo skipping escape" "to closed stack disconnected %d\n", LogonId));
Status = STATUS_ACCESS_DENIED; goto error; }
Status = pWinStation->pWsx->pWsxEscape( pWinStation->pWsxContext, GET_SC_AUTORECONNECT_INFO, NULL, 0, &SCAutoReconnectInfo, sizeof(SCAutoReconnectInfo), &BytesGot);
if (NT_SUCCESS(Status)) { ASSERT(SCAutoReconnectInfo.cbAutoReconnectInfo == ARC_SC_SECURITY_TOKEN_LEN); }
pServerArcBits = SCAutoReconnectInfo.AutoReconnectInfo; } } } else { Status = STATUS_ACCESS_DENIED; }
if (NT_SUCCESS(Status)) {
//
// Ensure we got the correct length for the server->client
// data
//
ASSERT(pServerArcBits);
//
// Get random
//
if (ComputeHMACVerifier(pServerArcBits, ARC_SC_SECURITY_TOKEN_LEN, pClientRandom, cbClientRandomLen, (PBYTE)hmacVerifier, sizeof(hmacVerifier))) {
//
// Check that the verifier matches that sent by the client
//
if (!memcmp(hmacVerifier, pCSArcInfo->SecurityVerifier, sizeof(pCSArcInfo->SecurityVerifier))) {
TRACE((hTrace,TC_ICASRV,TT_API1, "TERMSRV: WinStation ARC info matches - will autoreconnect\n"));
} else {
TRACE((hTrace,TC_ICASRV,TT_ERROR, "TERMSRV: autoreconnect verifier does not match targid:%d!!!\n", pWinStation->LogonId));
//
// Reset the autoreconnect info
//
pWinStation->AutoReconnectInfo.Valid = FALSE; memset(pWinStation->AutoReconnectInfo.ArcRandomBits, 0, sizeof(pWinStation->AutoReconnectInfo.ArcRandomBits));
//
// Log an event
//
PostErrorValueEvent(EVENT_AUTORECONNECT_AUTHENTICATION_FAILED, Status);
//
// Mark that no winstation target was found
//
goto error; } } pFoundWinStation = pWinStation; }
error: if ((NULL == pFoundWinStation) && pWinStation) { ReleaseWinStation(pWinStation); pWinStation = NULL; }
return pFoundWinStation; }
//
// Extract the session to reconnect to from the ARC info
// also do the necessary security checks
//
// Params:
// pWinStation - winstation to reset autoreconnect info for
//
VOID ResetAutoReconnectInfo( PWINSTATION pWinStation) { pWinStation->AutoReconnectInfo.Valid = FALSE; memset(pWinStation->AutoReconnectInfo.ArcRandomBits, 0, sizeof(pWinStation->AutoReconnectInfo.ArcRandomBits)); }
/*****************************************************************************
* * Filter_CompareConnectionEntry * * Generic table support.Compare two connection entries * * ****************************************************************************/
RTL_GENERIC_COMPARE_RESULTS NTAPI Filter_CompareFailedConnectionEntry( IN struct _RTL_GENERIC_TABLE *Table, IN PVOID FirstInstance, IN PVOID SecondInstance ) { PTS_FAILEDCONNECTION pFirst, pSecond; INT rc;
pFirst = (PTS_FAILEDCONNECTION)FirstInstance; pSecond = (PTS_FAILEDCONNECTION)SecondInstance;
if ( pFirst->uAddrSize < pSecond->uAddrSize ) { return GenericLessThan; } else if ( pFirst->uAddrSize > pSecond->uAddrSize ) { return GenericGreaterThan; }
rc = memcmp( pFirst->addr, pSecond->addr, pFirst->uAddrSize ); return ( rc < 0 )?GenericLessThan: ( rc > 0 )?GenericGreaterThan: GenericEqual; }
VOID ReadDoSParametersFromRegistry( HKEY hKeyTermSrv ) { DWORD dwLen; DWORD dwType; ULONG TimeLimitForFailedConnectionsMins, DoSBlockTimeMins; ULONG szBuffer[MAX_PATH/sizeof(ULONG)];
// Set Default values if Reg values are not set
MaxFailedConnect = 5; DoSBlockTime = 5 * 60 * 1000; TimeLimitForFailedConnections = 2 * 60 * 1000; g_BlackListPolicy = TRUE;
//
// Get DoS parameters from Registry
//
dwLen = sizeof(MaxFailedConnect); if (RegQueryValueEx(hKeyTermSrv, MAX_FAILED_CONNECT, NULL, &dwType, (PCHAR)&szBuffer, &dwLen) == ERROR_SUCCESS) { if (*(PULONG)szBuffer > 0) { MaxFailedConnect = *(PULONG)szBuffer; } }
// Note - for the next 2 parameters, whatever is present in the registry is in minutes
// So convert it into milliseconds after reading
dwLen = sizeof(TimeLimitForFailedConnectionsMins); if (RegQueryValueEx(hKeyTermSrv, TIME_LIMIT_FAILED_CONNECT, NULL, &dwType, (PCHAR)&szBuffer, &dwLen) == ERROR_SUCCESS) { if (*(PULONG)szBuffer > 0) { TimeLimitForFailedConnectionsMins = *(PULONG)szBuffer; TimeLimitForFailedConnections = TimeLimitForFailedConnectionsMins * 60 * 1000; } }
dwLen = sizeof(DoSBlockTimeMins); if (RegQueryValueEx(hKeyTermSrv, DOS_BLOCK_TIME, NULL, &dwType, (PCHAR)&szBuffer, &dwLen) == ERROR_SUCCESS) { if (*(PULONG)szBuffer > 0) { DoSBlockTimeMins = *(PULONG)szBuffer; DoSBlockTime = DoSBlockTimeMins * 60 * 1000; } }
// Table Cleanup time is twice the time within which a IP has to make "n" bad connections to get blocked
// This should be in milliseconds
CleanupTimeout = 2 * TimeLimitForFailedConnections;
dwLen = sizeof(g_BlackListPolicy); if (RegQueryValueEx(hKeyTermSrv, BLACK_LIST_POLICY, NULL, &dwType, (PCHAR)&szBuffer, &dwLen) == ERROR_SUCCESS) { if (*(PULONG)szBuffer > 0) { g_BlackListPolicy = *(PULONG)szBuffer; } }
}
/*******************************************************************************
* IsConfigValid * * Check the validity of the Winstation Config struct. * It's used for now for the machine-to-machine shadow as input from the * shadower's machine should not be trusted and should be checked. * * ENTRY: * IN pConfig - Winstation config struct to be checked * * EXIT: * STATUS_SUCCESS - if the struct can be used * Other status - the check failed ******************************************************************************/ NTSTATUS IsConfigValid(PWINSTATIONCONFIG2 pConfig) { ULONG i; NTSTATUS Status; PWCHAR pszModulePath = NULL;
// Verify that each member of the config is valid
//WINSTATIONCREATEW Create;
// ULONG fEnableWinStation : 1;
// ULONG MaxInstanceCount;
//PDCONFIGW Pd[ MAX_PDCONFIG ];
// PDCONFIG2W Create;
// PDNAMEW PdName; // descriptive name of PD
// SDCLASS SdClass; // type of PD
// DLLNAMEW PdDLL; // name of PD dll
// ULONG PdFlag; // PD flags
// ULONG OutBufLength; // optimal output buffer length
// ULONG OutBufCount; // optimal number of output buffers
// ULONG OutBufDelay; // write delay in msecs
// ULONG InteractiveDelay; // write delay during active input
// ULONG PortNumber; // network listen port number
// ULONG KeepAliveTimeout; // network watchdog frequence
// PDPARAMSW Params;
// SDCLASS SdClass;
// NETWORKCONFIGW Network;
// LONG LanAdapter;
// DEVICENAMEW NetworkName;
// ULONG Flags;
// ASYNCCONFIGW Async;
// DEVICENAMEW DeviceName;
// MODEMNAMEW ModemName;
// ULONG BaudRate;
// ULONG Parity;
// ULONG StopBits;
// ULONG ByteSize;
// ULONG fEnableDsrSensitivity: 1;
// ULONG fConnectionDriver: 1;
// FLOWCONTROLCONFIG FlowControl;
// CONNECTCONFIG Connect;
// NASICONFIGW Nasi;
// NASISPECIFICNAMEW SpecificName;
// NASIUSERNAMEW UserName;
// NASIPASSWORDW PassWord;
// NASISESIONNAMEW SessionName;
// NASIFILESERVERW FileServer;
// BOOLEAN GlobalSession;
// OEMTDCONFIGW OemTd;
// LONG Adapter;
// DEVICENAMEW DeviceName;
// ULONG Flags;
// allocate the path string to verify dlls and drivers
pszModulePath = MemAlloc( (MAX_PATH + 1) * sizeof(WCHAR) ); if (pszModulePath == NULL) { Status = STATUS_NO_MEMORY; goto done; }
if ((lstrlen(szDriverDir) + DLLNAME_LENGTH + 4 > MAX_PATH) || (lstrlen(szSystemDir) + DLLNAME_LENGTH + 4 > MAX_PATH)) { Status = STATUS_UNSUCCESSFUL; goto done; }
for (i=0; i < MAX_PDCONFIG; i++) {
PPDCONFIG pPdConfig;
pPdConfig = &(pConfig->Pd[i]);
if (pPdConfig->Create.SdClass == SdNone) break;
Status = IsZeroterminateStringW(pPdConfig->Create.PdName, PDNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
Status = IsZeroterminateStringW(pPdConfig->Create.PdDLL, DLLNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
lstrcpyn(pszModulePath, szDriverDir, MAX_PATH+1); lstrcat(pszModulePath, pPdConfig->Create.PdDLL); lstrcat(pszModulePath, L".SYS");
if (!VerifyFile(pszModulePath, &VfyLock)) { Status = STATUS_UNSUCCESSFUL; goto done; }
switch(pPdConfig->Params.SdClass) {
case SdNetwork: Status = IsZeroterminateStringW(pPdConfig->Params.Network.NetworkName, DEVICENAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done; break;
case SdAsync: // needed ?
Status = IsZeroterminateStringW(pPdConfig->Params.Async.DeviceName, DEVICENAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
Status = IsZeroterminateStringW(pPdConfig->Params.Async.ModemName, MODEMNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done; break;
case SdNasi: // needed ?
break;
case SdOemTransport: // needed ?
break;
default: break; } }
//WDCONFIGW Wd;
// WDNAMEW WdName;
// DLLNAMEW WdDLL;
// DLLNAMEW WsxDLL;
// ULONG WdFlag;
// ULONG WdInputBufferLength;
// DLLNAMEW CfgDLL;
// WDPREFIXW WdPrefix;
Status = IsZeroterminateStringW(pConfig->Wd.WdName, WDNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
Status = IsZeroterminateStringW(pConfig->Wd.WdDLL, DLLNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
// Verify the Wd
lstrcpyn(pszModulePath, szDriverDir, MAX_PATH+1); lstrcat(pszModulePath, pConfig->Wd.WdDLL); lstrcat(pszModulePath, L".SYS");
if (!VerifyFile(pszModulePath, &VfyLock)) { Status = STATUS_UNSUCCESSFUL; goto done; }
Status = IsZeroterminateStringW(pConfig->Wd.WsxDLL, DLLNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
// Verify the Wsx if defined
if ( pConfig->Wd.WsxDLL[0] != L'\0' ) {
lstrcpyn(pszModulePath, szSystemDir, MAX_PATH+1); lstrcat(pszModulePath, pConfig->Wd.WsxDLL); lstrcat(pszModulePath, L".DLL");
if (!VerifyFile(pszModulePath, &VfyLock)) { Status = STATUS_UNSUCCESSFUL; goto done; } }
Status = IsZeroterminateStringW(pConfig->Wd.CfgDLL, DLLNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
Status = IsZeroterminateStringW(pConfig->Wd.WdPrefix, WDPREFIX_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
//CDCONFIGW Cd;
// CDCLASS CdClass;
// CDNAMEW CdName;
// DLLNAMEW CdDLL;
// ULONG CdFlag;
//
Status = IsZeroterminateStringW(pConfig->Cd.CdName, CDNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
Status = IsZeroterminateStringW(pConfig->Cd.CdDLL, DLLNAME_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
//WINSTATIONCONFIGW Config;
// WCHAR Comment[ WINSTATIONCOMMENT_LENGTH + 1 ];
// USERCONFIGW User;
// char OEMId[4]; // WinFrame Server OEM Id
//
Status = IsZeroterminateStringW(pConfig->Config.Comment, WINSTATIONCOMMENT_LENGTH + 1); if (!NT_SUCCESS(Status)) goto done;
done: if (pszModulePath) MemFree(pszModulePath);
return Status;
}
|