WindowsXP-SP1/shell/services/bamsrv/badapplicationmanager.cpp

1243 lines
46 KiB
C++

// --------------------------------------------------------------------------
// Module Name: BadApplicationManager.cpp
//
// Copyright (c) 2000, Microsoft Corporation
//
// Classes to manage bad applications in the fast user switching environment.
//
// History: 2000-08-25 vtan created
// --------------------------------------------------------------------------
#ifdef _X86_
#include "StandardHeader.h"
#include "BadApplicationManager.h"
#include <wtsapi32.h>
#include <winsta.h>
#include "GracefulTerminateApplication.h"
#include "RestoreApplication.h"
#include "SingleThreadedExecution.h"
#include "StatusCode.h"
#include "TokenInformation.h"
// --------------------------------------------------------------------------
// CBadApplicationManager::INDEX_EVENT
// CBadApplicationManager::INDEX_HANDLES
// CBadApplicationManager::INDEX_RESERVED
// CBadApplicationManager::s_szDefaultDesktop
//
// Purpose: Constant indicies into a HANDLE array passed to
// user32!MsgWaitForMultipleObjects. The first handle is always
// the synchronization event. Subsequent HANDLEs are built into
// a static ARRAY passed with the dynamic amount.
//
// History: 2000-08-25 vtan created
// --------------------------------------------------------------------------
const int CBadApplicationManager::INDEX_EVENT = 0;
const int CBadApplicationManager::INDEX_HANDLES = INDEX_EVENT + 1;
const int CBadApplicationManager::INDEX_RESERVED = 2;
const WCHAR CBadApplicationManager::s_szDefaultDesktop[] = L"WinSta0\\Default";
// --------------------------------------------------------------------------
// CBadApplicationManager::CBadApplicationManager
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Constructor for CBadApplicationManager. This creates a thread
// that watches HANDLEs in the bad application list. The watcher
// knows when the offending process dies. It also creates a
// synchronization event that is signalled when the array of
// bad applications changes (is incremented). The thread
// maintains removal cases.
//
// History: 2000-08-25 vtan created
// --------------------------------------------------------------------------
CBadApplicationManager::CBadApplicationManager (HINSTANCE hInstance) :
CThread(),
_hInstance(hInstance),
_hModule(NULL),
_atom(NULL),
_hwnd(NULL),
_fTerminateWatcherThread(false),
_fRegisteredNotification(false),
_dwSessionIDLastConnect(static_cast<DWORD>(-1)),
_hTokenLastUser(NULL),
_hEvent(NULL),
_badApplications(sizeof(BAD_APPLICATION_INFO)),
_restoreApplications()
{
Resume();
}
// --------------------------------------------------------------------------
// CBadApplicationManager::~CBadApplicationManager
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Destructor for CBadApplicationManager. Releases any resources
// used.
//
// History: 2000-08-25 vtan created
// --------------------------------------------------------------------------
CBadApplicationManager::~CBadApplicationManager (void)
{
// In case the token hasn't been released yet - release it.
ReleaseHandle(_hTokenLastUser);
Cleanup();
}
// --------------------------------------------------------------------------
// CBadApplicationManager::Terminate
//
// Arguments: <none>
//
// Returns: NTSTATUS
//
// Purpose: Forces the watcher thread to terminate. Acquire the lock. Walk
// the list of entries and release the HANDLE on the process
// objects so they don't leak. Set the bool to terminate the
// thread. Set the event to wake the thread up. Release the lock.
//
// History: 2000-08-25 vtan created
// --------------------------------------------------------------------------
NTSTATUS CBadApplicationManager::Terminate (void)
{
int i;
CSingleThreadedExecution listLock(_lock);
for (i = _badApplications.GetCount() - 1; i >= 0; --i)
{
BAD_APPLICATION_INFO badApplicationInfo;
if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
{
TBOOL(CloseHandle(badApplicationInfo.hProcess));
}
_badApplications.Remove(i);
}
_fTerminateWatcherThread = true;
return(_hEvent.Set());
}
// --------------------------------------------------------------------------
// CBadApplicationManager::QueryRunning
//
// Arguments: badApplication = Bad application identifier to query.
// dwSessionID = Session ID of the request.
//
// Returns: bool
//
// Purpose: Queries the current running known bad applications list
// looking for a match. Again because this typically runs on a
// different thread to the watcher thread access to the list is
// protected by a critical section.
//
// History: 2000-08-25 vtan created
// --------------------------------------------------------------------------
bool CBadApplicationManager::QueryRunning (const CBadApplication& badApplication, DWORD dwSessionID)
{
bool fResult;
NTSTATUS status;
int i;
CSingleThreadedExecution listLock(_lock);
status = STATUS_SUCCESS;
fResult = false;
// Loop looking for a match. This uses the overloaded operator ==.
for (i = _badApplications.GetCount() - 1; !fResult && (i >= 0); --i)
{
BAD_APPLICATION_INFO badApplicationInfo;
status = _badApplications.Get(&badApplicationInfo, i);
if (NT_SUCCESS(status))
{
// Make sure the client is not in the same session as the running
// bad application. This API exists to prevent cross session instances.
// It's assumed that applications have their own mechanisms for multiple
// instances in the same session (or object name space).
fResult = ((badApplicationInfo.dwSessionID != dwSessionID) &&
(badApplicationInfo.badApplication == badApplication));
}
}
TSTATUS(status);
return(fResult);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::RegisterRunning
//
// Arguments: badApplication = Bad application identifier to add.
// hProcess = HANDLE to the process.
//
// Returns: NTSTATUS
//
// Purpose: Adds the given bad application to the known running list. The
// process object is added as well so that when the process
// terminates it can be cleaned up out of the list.
//
// Access to the bad application list is serialized with a
// critical section. This is important because the thread
// watching for termination always run on a different thread to
// the thread on which this function executes. Because they both
// access the same member variables this must be protected with
// a critical section.
//
// History: 2000-08-25 vtan created
// --------------------------------------------------------------------------
NTSTATUS CBadApplicationManager::RegisterRunning (const CBadApplication& badApplication, HANDLE hProcess, BAM_TYPE bamType)
{
NTSTATUS status;
CSingleThreadedExecution listLock(_lock);
ASSERTMSG((bamType > BAM_TYPE_MINIMUM) && (bamType < BAM_TYPE_MAXIMUM), "Invalid BAM_TYPE value passed to CBadApplicationManager::AddRunning");
// Have we reached the maximum number of wait object allowed? If not
// then proceed to add this. Otherwise reject the call. This is a
// hard coded limit in the kernel so we abide by it.
if (_badApplications.GetCount() < (MAXIMUM_WAIT_OBJECTS - INDEX_RESERVED))
{
BOOL fResult;
BAD_APPLICATION_INFO badApplicationInfo;
// Duplicate the HANDLE with SYNCHRONIZE access. That's
// all we need to call the wait function.
fResult = DuplicateHandle(GetCurrentProcess(),
hProcess,
GetCurrentProcess(),
&badApplicationInfo.hProcess,
SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
FALSE,
0);
if (fResult != FALSE)
{
PROCESS_SESSION_INFORMATION processSessionInformation;
ULONG ulReturnLength;
// Add the information to the list.
badApplicationInfo.bamType = bamType;
badApplicationInfo.badApplication = badApplication;
status = NtQueryInformationProcess(badApplicationInfo.hProcess,
ProcessSessionInformation,
&processSessionInformation,
sizeof(processSessionInformation),
&ulReturnLength);
if (NT_SUCCESS(status))
{
badApplicationInfo.dwSessionID = processSessionInformation.SessionId;
status = _badApplications.Add(&badApplicationInfo);
if (NT_SUCCESS(status))
{
status = _hEvent.Set();
}
}
}
else
{
status = CStatusCode::StatusCodeOfLastError();
}
}
else
{
status = STATUS_UNSUCCESSFUL;
}
return(status);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::QueryInformation
//
// Arguments: badApplication = Bad application identifier to query.
// hProcess = Handle to running process.
//
// Returns: NTSTATUS
//
// Purpose: Finds the given application in the running bad application
// list and returns a duplicated handle to the caller.
//
// History: 2000-08-25 vtan created
// --------------------------------------------------------------------------
NTSTATUS CBadApplicationManager::QueryInformation (const CBadApplication& badApplication, HANDLE& hProcess)
{
NTSTATUS status;
bool fResult;
int i;
CSingleThreadedExecution listLock(_lock);
// Assume failure
hProcess = NULL;
status = STATUS_OBJECT_NAME_NOT_FOUND;
fResult = false;
// Loop looking for a match. This uses the overloaded operator ==.
for (i = _badApplications.GetCount() - 1; !fResult && (i >= 0); --i)
{
BAD_APPLICATION_INFO badApplicationInfo;
if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
{
// Make sure the client is not in the same session as the running
// bad application. This API exists to prevent cross session instances.
// It's assumed that applications have their own mechanisms for multiple
// instances in the same session (or object name space).
fResult = (badApplicationInfo.badApplication == badApplication);
if (fResult)
{
if (DuplicateHandle(GetCurrentProcess(),
badApplicationInfo.hProcess,
GetCurrentProcess(),
&hProcess,
0,
FALSE,
DUPLICATE_SAME_ACCESS) != FALSE)
{
status = STATUS_SUCCESS;
}
else
{
status = CStatusCode::StatusCodeOfLastError();
}
}
}
}
return(status);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::RequestSwitchUser
//
// Arguments: <none>
//
// Returns: NTSTATUS
//
// Purpose: Execute terminate of BAM_TYPE_SWITCH_USER. These appications
// are really poorly behaved. A good example is a DVD player
// which bypasses GDI and draws directly into the VGA stream.
//
// Try to kill these and reject the request if it fails.
//
// History: 2000-11-02 vtan created
// --------------------------------------------------------------------------
NTSTATUS CBadApplicationManager::RequestSwitchUser (void)
{
NTSTATUS status;
int i;
// Walk the _badApplications list.
status = STATUS_SUCCESS;
_lock.Acquire();
i = _badApplications.GetCount() - 1;
while (NT_SUCCESS(status) && (i >= 0))
{
BAD_APPLICATION_INFO badApplicationInfo;
if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
{
// Look for BAM_TYPE_SWITCH_USER processes. It doesn't matter
// what session ID is tagged. This process is getting terminated.
if (badApplicationInfo.bamType == BAM_TYPE_SWITCH_USER)
{
// In any case release the lock, kill the process
// remove it from the watch list. Then reset the
// index back to the end of the list. Make sure to
// account for the "--i;" instruction below by not
// decrementing by 1.
_lock.Release();
status = PerformTermination(badApplicationInfo.hProcess, false);
_lock.Acquire();
i = _badApplications.GetCount();
}
}
--i;
}
_lock.Release();
return(status);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::PerformTermination
//
// Arguments: hProcess = Handle to running process.
//
// Returns: NTSTATUS
//
// Purpose: Terminates the given process. This is a common routine used
// by both the internal wait thread of this class as well as
// externally by bad application server itself.
//
// History: 2000-10-23 vtan created
// --------------------------------------------------------------------------
NTSTATUS CBadApplicationManager::PerformTermination (HANDLE hProcess, bool fAllowForceTerminate)
{
NTSTATUS status;
status = TerminateGracefully(hProcess);
if (!NT_SUCCESS(status) && fAllowForceTerminate)
{
status = TerminateForcibly(hProcess);
}
return(status);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::Entry
//
// Arguments: <none>
//
// Returns: DWORD
//
// Purpose: Watcher thread for process objects. This thread builds the
// array of proces handles to wait on as well as including the
// synchronization event that gets signaled by the Add member
// function. When that event is signaled the wait is re-executed
// with the new array of objects to wait on.
//
// When a process object is signaled it is cleared out of the
// known list to allow further creates to succeed.
//
// Acquisition of the critical section is carefully placed in
// this function so that the critical section is not held when
// the wait call is made.
//
// Added to this is a window and a message pump to enable
// listening for session notifications from terminal server.
//
// History: 2000-08-25 vtan created
// 2000-10-23 vtan added HWND message pump mechanism
// --------------------------------------------------------------------------
DWORD CBadApplicationManager::Entry (void)
{
WNDCLASSEX wndClassEx;
// Register this window class.
ZeroMemory(&wndClassEx, sizeof(wndClassEx));
wndClassEx.cbSize = sizeof(WNDCLASSEX);
wndClassEx.lpfnWndProc = NotificationWindowProc;
wndClassEx.hInstance = _hInstance;
wndClassEx.lpszClassName = TEXT("BadApplicationNotificationWindowClass");
_atom = RegisterClassEx(&wndClassEx);
// Create the notification window
_hwnd = CreateWindow(MAKEINTRESOURCE(_atom),
TEXT("BadApplicationNotificationWindow"),
WS_OVERLAPPED,
0, 0,
0, 0,
NULL,
NULL,
_hInstance,
this);
if (_hwnd != NULL)
{
_fRegisteredNotification = (WinStationRegisterConsoleNotification(SERVERNAME_CURRENT, _hwnd, NOTIFY_FOR_ALL_SESSIONS) != FALSE);
if (!_fRegisteredNotification)
{
_hModule = LoadLibrary(TEXT("shsvcs.dll"));
if (_hModule != NULL)
{
DWORD dwThreadID;
HANDLE hThread;
// If the register fails then create a thread to wait on the event
// and then register onces it's available. If the thread cannot be
// created it's no biggy. The notification mechanism fails and the
// welcome screen isn't updated.
AddRef();
hThread = CreateThread(NULL,
0,
RegisterThreadProc,
this,
0,
&dwThreadID);
if (hThread != NULL)
{
TBOOL(CloseHandle(hThread));
}
else
{
Release();
TBOOL(FreeLibrary(_hModule));
_hModule = NULL;
}
}
}
}
// Acquire the lock. This is necessary because to fill the array of
// handles to wait on requires access to the internal list.
_lock.Acquire();
do
{
DWORD dwWaitResult;
int i, iLimit;
BAD_APPLICATION_INFO badApplicationInfo;
HANDLE hArray[MAXIMUM_WAIT_OBJECTS];
ZeroMemory(&hArray, sizeof(hArray));
hArray[INDEX_EVENT] = _hEvent;
iLimit = _badApplications.GetCount();
for (i = 0; i < iLimit; ++i)
{
if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
{
hArray[INDEX_HANDLES + i] = badApplicationInfo.hProcess;
}
}
// Release the lock before we enter the wait state.
// Wait on ANY of the objects to be signaled.
_lock.Release();
dwWaitResult = MsgWaitForMultipleObjects(INDEX_HANDLES + iLimit,
hArray,
FALSE,
INFINITE,
QS_ALLINPUT);
ASSERTMSG(dwWaitResult != WAIT_FAILED, "WaitForMultipleObjects failed in CBadApplicationManager::Entry");
// We were woken up by an object being signaled. Is this the
// synchronization object?
dwWaitResult -= WAIT_OBJECT_0;
if (dwWaitResult == INDEX_EVENT)
{
// Yes. Acquire the lock. Reset the synchronization event. It's
// important to acquire the lock before resetting the event because
// the Add function could have the lock and be adding to the list.
// Once the Add function releases the lock it cannot signal the event.
// Otherwise we could reset the event during the Add function adding
// a new object and this would be missed.
_lock.Acquire();
TSTATUS(_hEvent.Reset());
}
// No. Is this a message that requires dispatching as part of the
// message pump?
else if (dwWaitResult == WAIT_OBJECT_0 + INDEX_HANDLES + static_cast<DWORD>(iLimit))
{
// Yes. Remove the message from the message queue and dispatch it.
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != FALSE)
{
(BOOL)TranslateMessage(&msg);
(LRESULT)DispatchMessage(&msg);
}
_lock.Acquire();
}
else
{
// No. One of the bad applications we are watching has terminated
// and its proces object is now signaled. Go to the correct index
// in the array. Acquire the lock. Close the HANDLE. It's not needed
// anymore. Then remove the entry from the list.
dwWaitResult -= INDEX_HANDLES;
_lock.Acquire();
if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, dwWaitResult)))
{
TBOOL(CloseHandle(badApplicationInfo.hProcess));
}
TSTATUS(_badApplications.Remove(dwWaitResult));
}
// At this point we still hold the lock. This is important because the top
// of the loop expects the lock to be held to build the HANDLE array.
} while (!_fTerminateWatcherThread);
// Clean up stuff that happened on this thread.
Cleanup();
// If we here then the thread is being terminated for some reason.
// Release the lock. It doesn't matter what happens now anyway.
_lock.Release();
return(0);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::TerminateForcibly
//
// Arguments: hProcess = Process to terminate.
//
// Returns: NTSTATUS
//
// Purpose: Inject a user mode thread into the process which calls
// kernel32!ExitProcess. If the thread injection fails then fall
// back to kernel32!TerminatProcess to force in.
//
// History: 2000-10-27 vtan created
// --------------------------------------------------------------------------
NTSTATUS CBadApplicationManager::TerminateForcibly (HANDLE hProcess)
{
NTSTATUS status;
HANDLE hProcessTerminate;
// Duplicate the process handle and request all the access required
// to create a remote thread in the process.
if (DuplicateHandle(GetCurrentProcess(),
hProcess,
GetCurrentProcess(),
&hProcessTerminate,
SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
FALSE,
0) != FALSE)
{
DWORD dwWaitResult;
HANDLE hThread, hWaitArray[2];
// Go and create the remote thread that immediately turns
// around and calls kernel32!ExitProcess. This allows
// a clean process shutdown to occur. If this times out
// then kill the process with terminate process.
status = RtlCreateUserThread(hProcessTerminate,
NULL,
FALSE,
0,
0,
0,
reinterpret_cast<PUSER_THREAD_START_ROUTINE>(ExitProcess),
NULL,
&hThread,
NULL);
if (NT_SUCCESS(status))
{
hWaitArray[0] = hThread;
hWaitArray[1] = hProcessTerminate;
dwWaitResult = WaitForMultipleObjects(ARRAYSIZE(hWaitArray),
hWaitArray,
TRUE,
5000);
TBOOL(CloseHandle(hThread));
if (dwWaitResult != WAIT_TIMEOUT)
{
status = STATUS_SUCCESS;
}
else
{
status = STATUS_TIMEOUT;
}
}
if (status != STATUS_SUCCESS)
{
if (TerminateProcess(hProcessTerminate, 0) != FALSE)
{
status = STATUS_SUCCESS;
}
else
{
status = CStatusCode::StatusCodeOfLastError();
}
}
TBOOL(CloseHandle(hProcessTerminate));
}
else
{
status = CStatusCode::StatusCodeOfLastError();
}
return(status);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::TerminateGracefully
//
// Arguments: hProcess = Process to terminate.
//
// Returns: NTSTATUS
//
// Purpose: Creates a rundll32 process on the session of the target
// process in WinSta0\Default which will re-enter this dll and
// call the "terminate" functionality. This allows the process to
// walk the window list corresponding to that session and send
// those windows close messages and wait for graceful
// termination.
//
// History: 2000-10-24 vtan created
// --------------------------------------------------------------------------
NTSTATUS CBadApplicationManager::TerminateGracefully (HANDLE hProcess)
{
NTSTATUS status;
ULONG ulReturnLength;
PROCESS_BASIC_INFORMATION processBasicInformation;
status = NtQueryInformationProcess(hProcess,
ProcessBasicInformation,
&processBasicInformation,
sizeof(processBasicInformation),
&ulReturnLength);
if (NT_SUCCESS(status))
{
HANDLE hToken;
if (OpenProcessToken(hProcess,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
&hToken) != FALSE)
{
STARTUPINFOW startupInfo;
PROCESS_INFORMATION processInformation;
WCHAR szCommandLine[MAX_PATH];
ZeroMemory(&startupInfo, sizeof(startupInfo));
ZeroMemory(&processInformation, sizeof(processInformation));
startupInfo.cb = sizeof(startupInfo);
startupInfo.lpDesktop = const_cast<WCHAR*>(s_szDefaultDesktop);
wsprintfW(szCommandLine, L"rundll32 shsvcs.dll,FUSCompatibilityEntry terminate %d", static_cast<DWORD>(processBasicInformation.UniqueProcessId));
if (CreateProcessAsUserW(hToken,
NULL,
szCommandLine,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&startupInfo,
&processInformation) != FALSE)
{
DWORD dwWaitResult;
HANDLE hArray[2];
// Assume that this whole thing failed.
status = STATUS_UNSUCCESSFUL;
TBOOL(CloseHandle(processInformation.hThread));
// Wait on both process objects. If the process to be terminated
// is signaled then the rundll32 stub did its job. If the rundll32
// stub is signaled then find out what its exit code is and either
// continue waiting on the process to be terminated or return back
// a code to the caller indicating success or failure. Failure
// forces the process to be terminated abruptly.
hArray[0] = hProcess;
hArray[1] = processInformation.hProcess;
dwWaitResult = WaitForMultipleObjects(ARRAYSIZE(hArray),
hArray,
FALSE,
10000);
// If the process to be terminated is signaled then we're done.
if (dwWaitResult == WAIT_OBJECT_0)
{
status = STATUS_SUCCESS;
}
// If the rundll32 stub is signaled then find out what it found.
else if (dwWaitResult == WAIT_OBJECT_0 + 1)
{
DWORD dwExitCode;
dwExitCode = STILL_ACTIVE;
if (GetExitCodeProcess(processInformation.hProcess, &dwExitCode) != FALSE)
{
ASSERTMSG((dwExitCode == CGracefulTerminateApplication::NO_WINDOWS_FOUND) || (dwExitCode == CGracefulTerminateApplication::WAIT_WINDOWS_FOUND), "Unexpected process exit code in CBadApplicationManager::TerminateGracefully");
// If the rundll32 stub says it found some windows then
// wait for the process to terminate itself.
if (dwExitCode == CGracefulTerminateApplication::WAIT_WINDOWS_FOUND)
{
// If the process terminates within the timeout period
// then we're done.
if (WaitForSingleObject(hProcess, 10000) == WAIT_OBJECT_0)
{
status = STATUS_SUCCESS;
}
}
}
}
TBOOL(CloseHandle(processInformation.hProcess));
}
else
{
status = CStatusCode::StatusCodeOfLastError();
}
TBOOL(CloseHandle(hToken));
}
else
{
status = CStatusCode::StatusCodeOfLastError();
}
}
return(status);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::Cleanup
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Releases used resources in the class. Used by both the
// constructor and the thread - whoever wins.
//
// History: 2000-12-12 vtan created
// --------------------------------------------------------------------------
void CBadApplicationManager::Cleanup (void)
{
if (_fRegisteredNotification)
{
(BOOL)WinStationUnRegisterConsoleNotification(SERVERNAME_CURRENT, _hwnd);
_fRegisteredNotification = false;
}
if (_hwnd != NULL)
{
TBOOL(DestroyWindow(_hwnd));
_hwnd = NULL;
}
if (_atom != 0)
{
TBOOL(UnregisterClass(MAKEINTRESOURCE(_atom), _hInstance));
_atom = 0;
}
}
// --------------------------------------------------------------------------
// CBadApplicationManager::Handle_Logon
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Nothing at present.
//
// History: 2000-10-24 vtan created
// --------------------------------------------------------------------------
void CBadApplicationManager::Handle_Logon (void)
{
}
// --------------------------------------------------------------------------
// CBadApplicationManager::Handle_Logoff
//
// Arguments: dwSessionID = Session ID that is logging off.
//
// Returns: <none>
//
// Purpose: Remove any restore processes we have in the list. The user
// is logging off so they shouldn't come back. Releases the last
// user to actively connect to the machine.
//
// History: 2000-10-24 vtan created
// --------------------------------------------------------------------------
void CBadApplicationManager::Handle_Logoff (DWORD dwSessionID)
{
int i;
CSingleThreadedExecution listLock(_lock);
for (i = _restoreApplications.GetCount() - 1; i >= 0; --i)
{
CRestoreApplication *pRestoreApplication;
pRestoreApplication = static_cast<CRestoreApplication*>(_restoreApplications.Get(i));
if ((pRestoreApplication != NULL) &&
pRestoreApplication->IsEqualSessionID(dwSessionID))
{
TSTATUS(_restoreApplications.Remove(i));
}
}
ReleaseHandle(_hTokenLastUser);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::Handle_Connect
//
// Arguments: dwSessionID = Session ID connecting.
// hToken = Handle to token of user connecting.
//
// Returns: <none>
//
// Purpose: Handles BAM3. This is the save for restoration all processes
// that use resources that aren't easily shared and restore all
// processes that were saved which aren't easily shared.
//
// It's optimized for not closing the processes of the same user
// should that user re-connect. This allows the screen saver to
// kick in and return to welcome without killing the user's
// processes unnecessarily.
//
// Also handles BAM4.
//
// History: 2000-10-24 vtan created
// --------------------------------------------------------------------------
void CBadApplicationManager::Handle_Connect (DWORD dwSessionID, HANDLE hToken)
{
if ((_hTokenLastUser != NULL) && (hToken != NULL))
{
PSID pSIDLastUser, pSIDCurrentUser;
CTokenInformation tokenLastUser(_hTokenLastUser);
CTokenInformation tokenCurrentUser(hToken);
pSIDLastUser = tokenLastUser.GetUserSID();
pSIDCurrentUser = tokenCurrentUser.GetUserSID();
if ((pSIDLastUser != NULL) && (pSIDCurrentUser != NULL) && !EqualSid(pSIDLastUser, pSIDCurrentUser))
{
int i;
DWORD dwSessionIDMatch;
ULONG ulReturnLength;
CRestoreApplication *pRestoreApplication;
if (NT_SUCCESS(NtQueryInformationToken(_hTokenLastUser,
TokenSessionId,
&dwSessionIDMatch,
sizeof(dwSessionIDMatch),
&ulReturnLength)))
{
// Walk the _badApplications list.
_lock.Acquire();
i = _badApplications.GetCount() - 1;
while (i >= 0)
{
BAD_APPLICATION_INFO badApplicationInfo;
if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
{
bool fTerminateProcess;
fTerminateProcess = false;
// Look for BAM_TYPE_SWITCH_TO_NEW_USER_WITH_RESTORE processes
// which have token session IDs that match the _hTokenLastUser
// session ID. These processes must be terminated and added to
// a list to be restarted on reconnection.
if ((badApplicationInfo.bamType == BAM_TYPE_SWITCH_TO_NEW_USER_WITH_RESTORE) &&
(badApplicationInfo.dwSessionID == dwSessionIDMatch))
{
pRestoreApplication = new CRestoreApplication;
if (pRestoreApplication != NULL)
{
if (NT_SUCCESS(pRestoreApplication->GetInformation(badApplicationInfo.hProcess)))
{
TSTATUS(_restoreApplications.Add(pRestoreApplication));
fTerminateProcess = true;
}
pRestoreApplication->Release();
}
}
// Look for BAM_TYPE_SWITCH_TO_NEW_USER (even though this is
// a connect/reconnect). Always kill these processes.
if (badApplicationInfo.bamType == BAM_TYPE_SWITCH_TO_NEW_USER)
{
fTerminateProcess = true;
}
if (fTerminateProcess)
{
// In any case release the lock, kill the process
// remove it from the watch list. Then reset the
// index back to the end of the list. Make sure to
// account for the "--i;" instruction below by not
// decrementing by 1.
_lock.Release();
TSTATUS(PerformTermination(badApplicationInfo.hProcess, true));
_lock.Acquire();
TBOOL(CloseHandle(badApplicationInfo.hProcess));
TSTATUS(_badApplications.Remove(i));
i = _badApplications.GetCount();
}
}
--i;
}
_lock.Release();
}
// Now walk the restore list looking for matches against the
// connecting session ID. Restore these processes.
_lock.Acquire();
i = _restoreApplications.GetCount() - 1;
while (i >= 0)
{
pRestoreApplication = static_cast<CRestoreApplication*>(_restoreApplications.Get(i));
if ((pRestoreApplication != NULL) &&
pRestoreApplication->IsEqualSessionID(dwSessionID))
{
HANDLE hProcess;
_lock.Release();
if (NT_SUCCESS(pRestoreApplication->Restore(&hProcess)))
{
CBadApplication badApplication(pRestoreApplication->GetCommandLine());
TBOOL(CloseHandle(hProcess));
}
_lock.Acquire();
TSTATUS(_restoreApplications.Remove(i));
i = _restoreApplications.GetCount();
}
--i;
}
_lock.Release();
}
}
if (hToken != NULL)
{
_dwSessionIDLastConnect = static_cast<DWORD>(-1);
}
else
{
_dwSessionIDLastConnect = dwSessionID;
}
}
// --------------------------------------------------------------------------
// CBadApplicationManager::Handle_Disconnect
//
// Arguments: dwSessionID = Session ID that is disconnecting.
// hToken = Token of the user disconnecting.
//
// Returns: <none>
//
// Purpose: If the session isn't the same as the last connected session
// then release the last user token and save the current one.
//
// History: 2000-10-24 vtan created
// --------------------------------------------------------------------------
void CBadApplicationManager::Handle_Disconnect (DWORD dwSessionID, HANDLE hToken)
{
if (_dwSessionIDLastConnect != dwSessionID)
{
ReleaseHandle(_hTokenLastUser);
if (hToken != NULL)
{
TBOOL(DuplicateHandle(GetCurrentProcess(),
hToken,
GetCurrentProcess(),
&_hTokenLastUser,
0,
FALSE,
DUPLICATE_SAME_ACCESS));
}
}
}
// --------------------------------------------------------------------------
// CBadApplicationManager::Handle_WM_WTSSESSION_CHANGE
//
// Arguments: wParam = Type of session change.
// lParam = Pointer to WTSSESSION_NOTIFICATION struct.
//
// Returns: LRESULT
//
// Purpose: Handles WM_WTSSESSION_CHANGE messages.
//
// History: 2000-10-23 vtan created
// --------------------------------------------------------------------------
LRESULT CBadApplicationManager::Handle_WM_WTSSESSION_CHANGE (WPARAM wParam, LPARAM lParam)
{
ULONG ulReturnLength;
WINSTATIONUSERTOKEN winStationUserToken;
winStationUserToken.ProcessId = reinterpret_cast<HANDLE>(GetCurrentProcessId());
winStationUserToken.ThreadId = reinterpret_cast<HANDLE>(GetCurrentThreadId());
winStationUserToken.UserToken = NULL;
(BOOLEAN)WinStationQueryInformation(SERVERNAME_CURRENT,
lParam,
WinStationUserToken,
&winStationUserToken,
sizeof(winStationUserToken),
&ulReturnLength);
switch (wParam)
{
case WTS_SESSION_LOGOFF:
Handle_Logoff(lParam);
break;
case WTS_SESSION_LOGON:
Handle_Logon();
// Fall thru to connect case.
case WTS_CONSOLE_CONNECT:
case WTS_REMOTE_CONNECT:
Handle_Connect(lParam, winStationUserToken.UserToken);
break;
case WTS_CONSOLE_DISCONNECT:
case WTS_REMOTE_DISCONNECT:
Handle_Disconnect(lParam, winStationUserToken.UserToken);
break;
default:
break;
}
if (winStationUserToken.UserToken != NULL)
{
TBOOL(CloseHandle(winStationUserToken.UserToken));
}
return(1);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::NotificationWindowProc
//
// Arguments: See the platform SDK under WindowProc.
//
// Returns: LRESULT
//
// Purpose: Handles messages for the Notification window.
//
// History: 2000-10-23 vtan created
// --------------------------------------------------------------------------
LRESULT CALLBACK CBadApplicationManager::NotificationWindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult;
CBadApplicationManager *pThis;
pThis = reinterpret_cast<CBadApplicationManager*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
switch (uMsg)
{
case WM_CREATE:
{
CREATESTRUCT *pCreateStruct;
pCreateStruct = reinterpret_cast<CREATESTRUCT*>(lParam);
(LONG_PTR)SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCreateStruct->lpCreateParams));
lResult = 0;
break;
}
case WM_WTSSESSION_CHANGE:
lResult = pThis->Handle_WM_WTSSESSION_CHANGE(wParam, lParam);
break;
default:
lResult = DefWindowProc(hwnd, uMsg, wParam, lParam);
break;
}
return(lResult);
}
// --------------------------------------------------------------------------
// CBadApplicationManager::RegisterThreadProc
//
// Arguments: pParameter = Object pointer.
//
// Returns: DWORD
//
// Purpose: Opens the TermSrvReadyEvent and waits on it. Once ready it
// registers for a notifications.
//
// History: 2000-10-23 vtan created
// --------------------------------------------------------------------------
DWORD WINAPI CBadApplicationManager::RegisterThreadProc (void *pParameter)
{
int iCounter;
HANDLE hTermSrvReadyEvent;
HMODULE hModule;
CBadApplicationManager *pThis;
pThis = reinterpret_cast<CBadApplicationManager*>(pParameter);
hModule = pThis->_hModule;
ASSERTMSG(hModule != NULL, "NULL HMODULE in CBadApplicationManager::RegisterThreadProc");
iCounter = 0;
hTermSrvReadyEvent = OpenEvent(SYNCHRONIZE,
FALSE,
TEXT("TermSrvReadyEvent"));
while ((hTermSrvReadyEvent == NULL) && (iCounter < 60))
{
++iCounter;
Sleep(1000);
hTermSrvReadyEvent = OpenEvent(SYNCHRONIZE,
FALSE,
TEXT("TermSrvReadyEvent"));
}
if (hTermSrvReadyEvent != NULL)
{
if (WaitForSingleObject(hTermSrvReadyEvent, 60000) == WAIT_OBJECT_0)
{
pThis->_fRegisteredNotification = (WinStationRegisterConsoleNotification(SERVERNAME_CURRENT, pThis->_hwnd, NOTIFY_FOR_ALL_SESSIONS) != FALSE);
}
TBOOL(CloseHandle(hTermSrvReadyEvent));
}
pThis->Release();
FreeLibraryAndExitThread(hModule, 0);
}
#endif /* _X86_ */