9980 lines
316 KiB
C
9980 lines
316 KiB
C
//****************************************************************************/
|
|
// 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"
|
|
|
|
#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
|
|
|
|
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;
|
|
|
|
/*
|
|
* External procedures defined
|
|
*/
|
|
|
|
|
|
VOID StartAllWinStations(HKEY);
|
|
NTSTATUS QueueWinStationCreate( PWINSTATIONNAME );
|
|
NTSTATUS WinStationCreateWorker(PWINSTATIONNAME pWinStationName, PULONG pLogonId );
|
|
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 );
|
|
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();
|
|
|
|
BOOL IsClientOnSameMachine(PWINSTATION pWinStation);
|
|
|
|
NTSTATUS GetProductIdFromRegistry( WCHAR* DigProductId, DWORD dwSize );
|
|
|
|
|
|
/*
|
|
* External procedures used
|
|
*/
|
|
NTSTATUS WinStationInitRPC( VOID );
|
|
NTSTATUS WinStationInitLPC( VOID );
|
|
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
|
|
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
|
|
);
|
|
|
|
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);
|
|
|
|
|
|
/*
|
|
* 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;
|
|
|
|
ULONG IdleWinStationPoolCount = 2;
|
|
|
|
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;
|
|
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;
|
|
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 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 );
|
|
|
|
// Global TermSrv counter values
|
|
DWORD g_TermSrvTotalSessions;
|
|
DWORD g_TermSrvDiscSessions;
|
|
DWORD g_TermSrvReconSessions;
|
|
|
|
// Global system SID
|
|
|
|
PSID gSystemSid = NULL;
|
|
PSID gAdminSid = 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;
|
|
|
|
// 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 );
|
|
|
|
Status = RtlInitializeCriticalSection( &FilterLock );
|
|
ASSERT( NT_SUCCESS( Status ));
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto badFilterLock;
|
|
}
|
|
|
|
RtlInitializeGenericTable( &gOutStandingConnections,
|
|
Filter_CompareConnectionEntry,
|
|
Filter_AllocateConnectionEntry,
|
|
Filter_FreeConnectionEntry,
|
|
NULL );
|
|
|
|
|
|
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 (!gbServer) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
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 (!gbServer) {
|
|
RtlDeleteCriticalSection( &WinStationListenersLock );
|
|
}
|
|
badWinStationListenersLock:
|
|
RtlDeleteCriticalSection( &WinStationListLock );
|
|
badWinstationListLock:
|
|
RtlDeleteCriticalSection( &UserProfileLock );
|
|
badUserProfileLock:
|
|
RtlDeleteCriticalSection( &ConsoleLock );
|
|
badAuthzCritSection:
|
|
RtlDeleteCriticalSection( &g_AuthzCritSection );
|
|
badConsoleLock:
|
|
RtlDeleteCriticalSection( &FilterLock );
|
|
badFilterLock:
|
|
return Status;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* GroupPolicyNotifyThread
|
|
* Entry:
|
|
* nothing
|
|
*
|
|
*
|
|
*******************************************************************************/
|
|
DWORD GroupPolicyNotifyThread(DWORD notUsed )
|
|
{
|
|
DWORD dwError;
|
|
BOOL rc;
|
|
HANDLE hEvent;
|
|
BOOLEAN bWaitForAccept;
|
|
BOOLEAN bSystemStartup;
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// 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 );
|
|
|
|
//
|
|
// 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() && TSIsMachineInHelpMode()))) {
|
|
|
|
// 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() && TSIsMachineInHelpMode())) {
|
|
// 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) {
|
|
UpdateSessionDirectory();
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// should we not do something else?
|
|
Sleep( 5000 );
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
if (rc) {
|
|
UnregisterGPNotification(g_hMachineGPEvent);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//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 );
|
|
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 );
|
|
}
|
|
(char *)pWinStationName += sizeof(WINSTATIONNAME);
|
|
}
|
|
|
|
MemFree( pBuffer );
|
|
}
|
|
|
|
bReturn = TRUE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Do this only on PRO and PER (i.e. WORKSTATION)
|
|
//
|
|
|
|
if ( gbServer ) {
|
|
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 );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
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;
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
if ( pWinStation->WindowsSubSysProcess ) {
|
|
pIdArray[EventCount] = pWinStation->LogonId;
|
|
pHandleArray[EventCount++] = pWinStation->WindowsSubSysProcess;
|
|
}
|
|
if ( pWinStation->InitialCommandProcess ) {
|
|
pIdArray[EventCount] = pWinStation->LogonId;
|
|
pHandleArray[EventCount++] = pWinStation->InitialCommandProcess;
|
|
}
|
|
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 ));
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 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 ) );
|
|
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 )
|
|
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 ( !gbServer ) {
|
|
//
|
|
// On Personal and Pro 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
goto done;
|
|
}
|
|
pListenWinStation->Flags |= WSF_DELETE;
|
|
|
|
/*
|
|
* Make sure this WinStation is ready to delete
|
|
*/
|
|
WinStationTerminate( pListenWinStation );
|
|
|
|
/*
|
|
* Call the WinStationDelete worker
|
|
*/
|
|
WinStationDeleteWorker( pListenWinStation );
|
|
}
|
|
}
|
|
|
|
done:
|
|
|
|
if ( pEndpoint ){
|
|
Status = _CloseEndpoint( &pListenWinStation->Config,
|
|
pEndpoint,
|
|
EndpointLength,
|
|
pListenWinStation,
|
|
TRUE ); // Use a temporary stack
|
|
MemFree( pEndpoint );
|
|
}
|
|
|
|
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;
|
|
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;
|
|
|
|
//
|
|
// 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
|
|
//
|
|
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;
|
|
}
|
|
}
|
|
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
|
|
*/
|
|
Status = WinStationCreateWorker( NULL, NULL );
|
|
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 );
|
|
ASSERT( pTargetWinStation->ConnectEvent );
|
|
|
|
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, LogonId=%d, Status=0x%x\n",
|
|
pTargetWinStation->LogonId, 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 and clear the IDLEBUSY flag.
|
|
*/
|
|
rc = RelockWinStation(pTargetWinStation);
|
|
pTargetWinStation->Flags &= ~WSF_IDLEBUSY;
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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 );
|
|
if (Status != STATUS_SUCCESS) {
|
|
// badconnect free pEndpoint, WinStationTerminate() will try to free this
|
|
// end point again causing double free.
|
|
pTargetWinStation->pEndpoint = NULL;
|
|
goto badconnect;
|
|
}
|
|
RtlReleaseResource(&WinStationSecurityLock);
|
|
} else {
|
|
ReadWinStationSecurityDescriptor( pTargetWinStation );
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize client data
|
|
*/
|
|
pTargetWinStation->Client.ClientSessionId = LOGONID_NONE;
|
|
ZeroMemory( pTargetWinStation->Client.clientDigProductId, sizeof( pTargetWinStation->Client.clientDigProductId ));
|
|
pTargetWinStation->Client.PerformanceFlags = TS_PERF_DISABLE_NOTHING;
|
|
|
|
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 );
|
|
}
|
|
|
|
if ( pTargetWinStation->Config.Config.User.fWallPaperDisabled )
|
|
{
|
|
pTargetWinStation->Client.PerformanceFlags |= TS_PERF_DISABLE_WALLPAPER;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// 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 );
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
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.
|
|
//
|
|
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;
|
|
|
|
} 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
|
|
*/
|
|
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 ) {
|
|
QueueWinStationReset( pTargetWinStation->LogonId);
|
|
// clear this so it doesn't get selected as idle when unlocked
|
|
pTargetWinStation->Flags &= ~WSF_IDLE;
|
|
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 ) );
|
|
Status = IcaStackOpen( pTargetWinStation->hIca,
|
|
Stack_Primary,
|
|
(PROC)WsxStackIoControl,
|
|
pTargetWinStation,
|
|
&pTargetWinStation->hStack );
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
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
|
|
*
|
|
* 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 )
|
|
{
|
|
BOOL fConsole;
|
|
PWINSTATION pWinStation;
|
|
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->LastReconnectType = NeverReconnected;
|
|
pWinStation->fDisallowAutoReconnect = FALSE;
|
|
|
|
|
|
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 ));
|
|
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
|
|
ENTERCRIT( &WinStationListLock );
|
|
}
|
|
|
|
/*
|
|
* Allocate LogonId and insert in WinStation list
|
|
*/
|
|
InsertTailList( &WinStationListHead, &pWinStation->Links );
|
|
LEAVECRIT( &WinStationListLock );
|
|
|
|
/*
|
|
* 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
|
|
|
|
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;
|
|
SID_IDENTIFIER_AUTHORITY siaWorld = SECURITY_WORLD_SID_AUTHORITY;
|
|
PSID pSidWorld;
|
|
PACL pNewDAcl;
|
|
DWORD dwres;
|
|
|
|
if ( AllocateAndInitializeSid( &siaWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pSidWorld))
|
|
{
|
|
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
|
|
ea.grfAccessPermissions = SYNCHRONIZE;
|
|
ea.grfAccessMode = GRANT_ACCESS;
|
|
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
|
ea.Trustee.ptstrName = (LPTSTR)pSidWorld;
|
|
|
|
dwres = SetEntriesInAcl(1, &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");
|
|
}
|
|
}
|
|
LocalFree( pNewDAcl );
|
|
}
|
|
LocalFree( pSidWorld );
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pWinStation->hWinmmConsoleAudioEvent = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Start the WinStation
|
|
*/
|
|
Status = WinStationStart( pWinStation );
|
|
|
|
/*
|
|
* Ignore errors from console, otherwise keep going
|
|
*/
|
|
if ( ( pWinStation->LogonId ) && ( Status != STATUS_SUCCESS ) )
|
|
goto starterror;
|
|
|
|
|
|
/*
|
|
* Return LogonId to caller
|
|
*/
|
|
if ( pLogonId )
|
|
*pLogonId = 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 starterror;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set WinStationEvent to indicate another WinStation has been created
|
|
*/
|
|
ENTERCRIT( &WinStationListLock );
|
|
pWinStation->Starting = FALSE;
|
|
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 );
|
|
|
|
/*
|
|
* Release WinStation now
|
|
*/
|
|
ReleaseWinStation( pWinStation );
|
|
|
|
/*
|
|
* Notify clients of WinStation create
|
|
*/
|
|
|
|
|
|
NotifySystemEvent( WEVENT_CREATE );
|
|
|
|
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:
|
|
ReleaseWinStation( pWinStation );
|
|
NtClose( pWinStation->Lock.Mutex );
|
|
|
|
/*
|
|
* Could not create WinStation lock
|
|
*/
|
|
nolock:
|
|
LCDestroyContext(pWinStation);
|
|
|
|
/*
|
|
* Could not allocate licensing context
|
|
*/
|
|
nolicensecontext:
|
|
MemFree( pWinStation );
|
|
|
|
|
|
/*
|
|
* Could not allocate WinStation
|
|
*/
|
|
nomem:
|
|
PostErrorValueEvent(EVENT_TS_WINSTATION_START_FAILED, Status);
|
|
|
|
/*
|
|
* WinStation is disabled
|
|
* System shutdown is in progress
|
|
*/
|
|
disabled:
|
|
shutdown:
|
|
|
|
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;
|
|
ICA_TRACE Trace;
|
|
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 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 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 IcaStackOpen: Error 0x%x from IcaStackOpen, last error %d\n",
|
|
Status, GetLastError() ));
|
|
goto done;
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IcaClose( pWinStation->hIca );
|
|
pWinStation->hIca = NULL;
|
|
DBGPRINT(( "TERMSRV IcaStackOpen for console stack: Error 0x%x from IcaStackOpen, last error %d\n",
|
|
Status, GetLastError() ));
|
|
}
|
|
|
|
/*
|
|
* Enable trace
|
|
*/
|
|
RtlZeroMemory( &Trace , sizeof( ICA_TRACE ) );
|
|
InitializeTrace( pWinStation, FALSE, &Trace );
|
|
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* 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;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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) && pWinStation->hWinmmConsoleAudioEvent) {
|
|
CloseHandle(pWinStation->hWinmmConsoleAudioEvent);
|
|
}
|
|
|
|
/*
|
|
* 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 ));
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
NtClose( pWinStation->InitialCommandProcess );
|
|
pWinStation->InitialCommandProcess = NULL;
|
|
}
|
|
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
NtClose( pWinStation->InitialCommandProcess );
|
|
pWinStation->InitialCommandProcess = NULL;
|
|
|
|
}
|
|
|
|
/*
|
|
* Now check to see if there are any remaining processes in
|
|
* the system other than CSRSS with this SessionId. If so, terminate them now.
|
|
*/
|
|
|
|
if (ShutDownFromSessionID && ShutDownFromSessionID == pWinStation->LogonId)
|
|
iLoop = 90;
|
|
else
|
|
iLoop = 3;
|
|
|
|
for (i = 0 ; i < iLoop; 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;
|
|
}
|
|
|
|
if ((i == (iLoop - 1)) && (AllExited == FALSE)) {
|
|
/*
|
|
* Last iteration and not all processes terminated
|
|
*/
|
|
|
|
// DbgBreakPoint();
|
|
}
|
|
|
|
/*
|
|
* This is a hack to give enough time to processess to terminate
|
|
*/
|
|
|
|
if (ShutDownFromSessionID && ShutDownFromSessionID == pWinStation->LogonId)
|
|
{
|
|
Sleep(1*1000);
|
|
}
|
|
else
|
|
{
|
|
Sleep(30*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 < 3; 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;
|
|
}
|
|
|
|
if ((i == 2) && (AllExited == FALSE)) {
|
|
/*
|
|
* Last iteration and not all processes terminated
|
|
*/
|
|
// DbgBreakPoint();
|
|
}
|
|
|
|
/*
|
|
* This is a hack to give enough time to processess to terminate
|
|
*/
|
|
Sleep(30*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();
|
|
//}
|
|
NtClose( pWinStation->WindowsSubSysProcess );
|
|
pWinStation->WindowsSubSysProcess = NULL;
|
|
}
|
|
} 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
|
|
|
|
if (pWinStation->Lock.RefCount == 1) {
|
|
ASSERT( pWinStation->InitialCommandProcess == NULL );
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
// Clean up the updated Notification Credentials
|
|
if (pWinStation->pNewNotificationCredentials != NULL) {
|
|
MemFree(pWinStation->pNewNotificationCredentials);
|
|
pWinStation->pNewNotificationCredentials = 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;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
VOID
|
|
_IncrementPnpEvent( VOID )
|
|
{
|
|
HANDLE hPnpMutex = NULL;
|
|
HANDLE hPnpEvent = NULL;
|
|
HANDLE hPnpInfo = NULL;
|
|
typedef struct
|
|
{
|
|
DWORD cbSize;
|
|
LONG cPnpEvents;
|
|
} *PMMPNPINFO;
|
|
PMMPNPINFO pPnpInfo;
|
|
|
|
//
|
|
// bump the cPnpEvents
|
|
//
|
|
hPnpMutex = OpenMutex( SYNCHRONIZE, FALSE, L"Global\\GuardMutexmmGlobalPnpInfoGuard" );
|
|
if ( hPnpMutex )
|
|
{
|
|
hPnpEvent = OpenEvent(SYNCHRONIZE, FALSE, L"Global\\GuardEventmmGlobalPnpInfoGuard" );
|
|
if ( hPnpEvent )
|
|
{
|
|
//
|
|
// acquire exclusive ownership on the PnP section
|
|
//
|
|
HANDLE ah[2];
|
|
DWORD dw;
|
|
|
|
ah[0] = hPnpEvent;
|
|
ah[1] = hPnpMutex;
|
|
dw = WaitForMultipleObjects( 2, ah, TRUE, 5000 );
|
|
if ( WAIT_TIMEOUT != dw )
|
|
{
|
|
hPnpInfo = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, L"Global\\mmGlobalPnpInfo" );
|
|
if (hPnpInfo) {
|
|
pPnpInfo = (PMMPNPINFO)MapViewOfFile(hPnpInfo, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
|
|
if (pPnpInfo) {
|
|
pPnpInfo->cPnpEvents ++;
|
|
UnmapViewOfFile( pPnpInfo );
|
|
} else {
|
|
DBGPRINT(( "Can't map PnP section: %d\n", GetLastError()));
|
|
}
|
|
CloseHandle(hPnpInfo);
|
|
} else {
|
|
DBGPRINT(( "Can't open PnP section: %d\n", GetLastError()));
|
|
}
|
|
|
|
ReleaseMutex( hPnpMutex );
|
|
} else {
|
|
DBGPRINT(( "Timed out to open mmGlobalPnpInfo" ));
|
|
}
|
|
CloseHandle( hPnpEvent );
|
|
} else {
|
|
DBGPRINT(( "Can't open PnP event: %d\n", GetLastError()));
|
|
}
|
|
|
|
CloseHandle( hPnpMutex );
|
|
} else {
|
|
DBGPRINT(( "Can't open PnP mutex: %d\n", GetLastError()));
|
|
}
|
|
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* 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;
|
|
|
|
|
|
// 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;
|
|
|
|
/*
|
|
* 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 ) );
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 );
|
|
}
|
|
|
|
|
|
{
|
|
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 && 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;
|
|
NTSTATUS TempStatus;
|
|
PWINSTATIONCONFIG2 pCurConfig = NULL;
|
|
PWINSTATIONCLIENT pCurClient = NULL;
|
|
|
|
// WinStation should not currently be connected
|
|
ASSERT( pWinStation->pEndpoint == NULL );
|
|
|
|
//
|
|
// 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;
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
_IncrementPnpEvent();
|
|
}
|
|
}
|
|
|
|
|
|
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) );
|
|
|
|
//
|
|
// Set session time zone information.
|
|
//
|
|
#ifdef TERMSRV_USE_CLIENT_TIME_ZONE
|
|
{
|
|
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 );
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Copy console owner info
|
|
*/
|
|
pWinStation->fOwnsConsoleTerminal = pReconnectInfo->fOwnsConsoleTerminal;
|
|
|
|
/*
|
|
* 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.
|
|
//
|
|
}
|
|
|
|
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;
|
|
|
|
/*
|
|
* 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) {
|
|
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(&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;
|
|
|
|
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 ));
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
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 &&
|
|
pWinStation->ConnectEvent ) {
|
|
|
|
/*
|
|
* 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 ||
|
|
!pWinStation->ConnectEvent ) {
|
|
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 )
|
|
Count++;
|
|
}
|
|
}
|
|
|
|
if ( !bLockHeld ) {
|
|
LEAVECRIT( &WinStationListLock );
|
|
}
|
|
|
|
TRACE((hTrace,TC_ICASRV,TT_API2,"TERMSRV: CountWinstationType %d\n", Count ));
|
|
return Count;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* LockWinStationByPointer
|
|
*
|
|
* Lock a WinStation given a pointer
|
|
*
|
|
* NOTE:
|
|
* WinStationListLock must be locked on entry and will be locked on return.
|
|
* If return value is FALSE, then the WinStation may have beed deleted
|
|
* and the pWinStation pointer should NOT be referenced.
|
|
*
|
|
* ENTRY:
|
|
* pWinStation (input)
|
|
* Pointer to WinStation to lock
|
|
*
|
|
* EXIT:
|
|
* On success - TRUE if WinStation was locked successfully
|
|
* On failure - FALSE otherwise
|
|
******************************************************************************/
|
|
BOOLEAN LockWinStationByPointer(PWINSTATION pWinStation)
|
|
{
|
|
/*
|
|
* Try to lock the WinStation.
|
|
*/
|
|
return LockRefLock(&pWinStation->Lock);
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* 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 ( *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 ( LockWinStationByPointer( pWinStation ) ) {
|
|
pConsole = pWinStation;
|
|
UnlockWinStation( pConsole );
|
|
}
|
|
}
|
|
//
|
|
// 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;
|
|
|
|
// 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 );
|
|
pWinStation = FindIdleWinStation();
|
|
if (pWinStation == NULL) {
|
|
DBGPRINT(("TERMSRV: WinStationCheckConsoleSession - Fail to get an idle session\n"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* 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 );
|
|
|
|
/*
|
|
* 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 = 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;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* IsClientOnSameMachine() Determines if the client is running on the same
|
|
* machine when this posible (currently implemted only for clients connected
|
|
* through TCP/IP). When we can detect the client is on the same machine, we can
|
|
* earlier fail operations like trying to reconecting the console to that client.
|
|
* This way we can fail the operation quicly without going through annoying
|
|
* protocol timeouts.
|
|
*/
|
|
|
|
BOOL
|
|
IsClientOnSameMachine(PWINSTATION pWinStation)
|
|
|
|
{
|
|
ADDRINFO *AddrInfo, *AI;
|
|
int RetVal;
|
|
struct sockaddr_in *pIPV4addr;
|
|
struct sockaddr_in6 *pIPV6addr;
|
|
PBYTE pServerAddrByte;
|
|
PBYTE pClientAddrByte;
|
|
DWORD dwIP4[4];
|
|
BYTE achIP4[4];
|
|
char achCompterName[256];
|
|
DWORD dwComputerNameSize;
|
|
|
|
|
|
//Return if WinSock couldn't be initialized
|
|
if (!gbWinSockInitialized) {
|
|
return FALSE;
|
|
}
|
|
|
|
// setup the client addrees for comparing with server adresses
|
|
|
|
switch (pWinStation->Client.ClientAddressFamily ) {
|
|
|
|
// For IPV4 the address in client data is represented as a WCHAR string
|
|
case AF_INET:
|
|
swscanf(pWinStation->Client.ClientAddress, L"%u.%u.%u.%u", &dwIP4[0], &dwIP4[1], &dwIP4[2], &dwIP4[3] );
|
|
achIP4[0] = (BYTE) dwIP4[0];
|
|
achIP4[1] = (BYTE) dwIP4[1];
|
|
achIP4[2] = (BYTE) dwIP4[2];
|
|
achIP4[3] = (BYTE) dwIP4[3];
|
|
pClientAddrByte = &achIP4[0];
|
|
break;
|
|
|
|
// For IPv6 the adress in client data is assumed to be in binary form
|
|
case AF_INET6:
|
|
pClientAddrByte = (PBYTE) pWinStation->Client.ClientAddress;
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
// Get the server adresses.
|
|
|
|
|
|
dwComputerNameSize = sizeof(achCompterName);
|
|
if (!GetComputerNameA(achCompterName,&dwComputerNameSize)) {
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
RetVal = getaddrinfo(achCompterName, NULL, NULL, &AddrInfo);
|
|
if (RetVal != 0) {
|
|
DBGPRINT (("Cannot resolve address, error: %d\n", RetVal));
|
|
return FALSE;
|
|
|
|
} else{
|
|
// Compare all server adresses with client till a match is found.
|
|
|
|
|
|
for (AI = AddrInfo; AI != NULL; AI = AI->ai_next) {
|
|
|
|
if (pWinStation->Client.ClientAddressFamily == (ULONG)AI->ai_family &&
|
|
AI->ai_addrlen <= sizeof(pWinStation->Client.ClientAddress) ) {
|
|
|
|
switch (pWinStation->Client.ClientAddressFamily) {
|
|
|
|
case AF_INET:
|
|
if (AI->ai_addrlen >= sizeof(struct sockaddr_in)) {
|
|
pIPV4addr = (struct sockaddr_in *) AI->ai_addr;
|
|
pServerAddrByte = (PBYTE)&pIPV4addr->sin_addr;
|
|
if (RtlEqualMemory(pClientAddrByte,pServerAddrByte, 4)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AF_INET6:
|
|
if (AI->ai_addrlen >= sizeof(struct sockaddr_in6)) {
|
|
pIPV6addr = (struct sockaddr_in6 *) AI->ai_addr;
|
|
pServerAddrByte = (PBYTE)&pIPV6addr->sin6_addr;
|
|
if (RtlEqualMemory(pClientAddrByte,pServerAddrByte, 16)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
return FALSE;
|
|
|
|
#if 0
|
|
|
|
char hostname[(512+1)*sizeof(TCHAR)];
|
|
int err;
|
|
int i,j;
|
|
struct hostent* phostent;
|
|
|
|
err=gethostname(hostname, sizeof(hostname));
|
|
if (err == 0) {
|
|
;
|
|
if ( (phostent = gethostbyname(hostname)) !=NULL) {
|
|
switch(phostent->h_addrtype){
|
|
case AF_INET:
|
|
if (pWinStation->Client.ClientAddressFamily == AF_INET) {
|
|
BYTE ipaddress[4];
|
|
swscanf(pWinStation->Client.ClientAddress, L"%u.%u.%u.%u", &ipaddress[0], &ipaddress[1], &ipaddress[2], &ipaddress[3] );
|
|
|
|
j=0;
|
|
while (phostent->h_addr_list[j] != NULL) {
|
|
for (i=0; i < 4 ; i++) {
|
|
if (ipaddress[i] != (BYTE) phostent->h_addr_list[j][i]) {
|
|
break;
|
|
}
|
|
if (i == 3 ) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
/*
|
|
* 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 );
|
|
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));
|
|
|
|
//
|
|
// 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));
|
|
}
|
|
|