windows-nt/Source/XPSP1/NT/termsrv/winsta/server/conntfy.c
2020-09-26 16:20:57 +08:00

1479 lines
40 KiB
C

#include "precomp.h"
#pragma hdrstop
#define SECURITY_WIN32
#ifdef NTSDDEBUG
#define NTSDDBGPRINT(x) DbgPrint x
#else
#define NTSDDBGPRINT(x)
#endif
#include "winsvcp.h" // defines I_ScSendTSMessage
#include "conntfy.h"
//#ifdef MAKARANDS_HIGHER_WARNING_LEVEL
#pragma warning(push, 4)
#pragma warning(disable:4201) // nameless structure.
//#endif
#define INVALID_SESSIONID 0xffffffff
#define INVALID_SESSIONSERIAL 0xffffffff
// 0x1 fConnected
// 0x2 fLoggedOn
// 0x3 fRemote
// 0x4 fWelcome
typedef struct _WTSSESSION_STATE
{
unsigned int bConnected: 1;
unsigned int bLoggedOn: 1;
unsigned int bConsole: 1;
unsigned int bRemote: 1;
unsigned int bLocked: 1;
} WTSSESSION_STATE, *PWTSSESSION_STATE;
/*
WTS_CONSOLE_CONNECT bConnected, bConsole, !bRemote,
WTS_CONSOLE_DISCONNECT !bConnected, !bConsole, !bRemote
WTS_REMOTE_CONNECT bConnected, !bConsole, bremote
WTS_REMOTE_DISCONNECT !bConnected, !bConsole, !bRemote
WTS_SESSION_LOGON bLoggedOn
WTS_SESSION_LOGOFF !bLoggedOn
WTS_SESSION_LOCK bLocked
WTS_SESSION_UNLOCK !bLocked
*/
//
// this is head for hwnds list.
// this links NOTIFY_ENTRY or NOTIFY_ENTRY_GLOBAL together.
//
typedef struct _NOTIFY_LIST
{
LIST_ENTRY Links; // links to other NOTIFY_LISTs. not used in case of global notification list.
LIST_ENTRY ListHead; // head of notification entries. links NOTIFY_ENTRYs (or NOTIFY_ENTRY_GLOBAL) together
RTL_CRITICAL_SECTION ListLock; // lock to travel the entries.
ULONG SessionId; // session id ( not used in case of global list)
ULONG SessonSerialNumber; // serial number ( not used in case of global list)
WTSSESSION_STATE SessionState; // state of the session.
} NOTIFY_LIST, *PNOTIFY_LIST;
//
// entry in notification list per winstation.
//
typedef struct _NOTIFY_ENTRY
{
LIST_ENTRY Links; // links to other entries
ULONG hWnd; // hwnd to be notified
ULONG RefCount; // how many times was this hwnd registered ?
} NOTIFY_ENTRY, *PNOTIFY_ENTRY;
//
// Entry in Notification list for all sessions Notifications.
//
typedef struct _NOTIFY_ENTRY_GLOBAL
{
struct _NOTIFY_ENTRY; // above structure +
ULONG SessionId; // since this is global entry, it needs to keep session id per hwnd.
} NOTIFY_ENTRY_GLOBAL, *PNOTIFY_ENTRY_GLOBAL;
//
// The notification Queue.
//
typedef struct _NOTIFICATION_QUEUE
{
LIST_ENTRY ListHead; // head of queue reuests. links NOTIFICATION_REQUESTs together
RTL_CRITICAL_SECTION ListLock; // lock to travel the queue
HANDLE hNotificationEvent; // syncronization between woker and caller of queue.
} NOTIFICATION_QUEUE, *PNOTIFICATION_QUEUE;
//
// Entry in Notification Queue.
//
typedef struct _NOTIFICATION_REQUEST
{
LIST_ENTRY Links; // links to other entries.
ULONG SessionId; // session id for the session this notificaiton is to be sent.
ULONG SessonSerialNumber; // serial number for the session this notificaiton is to be sent.
WPARAM NotificationCode; // notificaiton code
} NOTIFICATION_REQUEST, *PNOTIFICATION_REQUEST;
//
// our main data structure.
//
typedef struct _NOTIFY_LLIST
{
LIST_ENTRY ListHead; // head of notification lists. links NOTIFY_LISTs together.
RTL_CRITICAL_SECTION ListLock; // lock to travel the head list.
NOTIFY_LIST GlobalList; // global notification list.
NOTIFICATION_QUEUE RequestQueue; // notification queue.
NOTIFY_LIST InvlidHwndList; // invalid window list
} NOTIFY_LLIST, PNOTIFY_LLIST;
//
// File Globals.
//
NOTIFY_LLIST gNotifyLList;
//
// private functions
//
BOOL DoesHWndExists (
PNOTIFY_LIST pNotifyList,
ULONG hWnd
);
PNOTIFY_ENTRY GetHWndEntryFromSessionList (
PNOTIFY_LIST pNotifyList,
ULONG hWnd
);
PNOTIFY_ENTRY_GLOBAL GetHWndEntryFromGlobalList (
PNOTIFY_LIST pNotifyList,
ULONG hWnd,
ULONG SessionId
);
NTSTATUS GetNoficationListFromSessionId (
ULONG SessionId,
PNOTIFY_LIST *ppNofificationList,
BOOL bKeepLListLocked
);
NTSTATUS GetGlobalNotificationList (
PNOTIFY_LIST *ppConChgNtfy
);
NTSTATUS GetInvlidHwndList(PNOTIFY_LIST *ppConChgNtfy);
NTSTATUS NotifyConsole (
ULONG SessionId,
ULONG SessionSerialNumber,
WPARAM wParam
);
NTSTATUS SendConsoleNotification (
ULONG SessionId,
ULONG hWnd,
ULONG Msg,
WPARAM wParam,
WTSSESSION_NOTIFICATION wtsConsoleNotification
);
BOOL IsGlobalList(PNOTIFY_LIST pNtfyList);
int GetListCount (
LIST_ENTRY *pListHead
);
NTSTATUS DestroyLock ( PNOTIFY_LIST pNtfyList);
NTSTATUS CreateLock ( PNOTIFY_LIST pNtfyList);
NTSTATUS
InitializeNotificationQueue ();
NTSTATUS
QueueNotificationRequest (
ULONG SessionSerialNumber,
ULONG SessionId,
WPARAM notification
);
PNOTIFICATION_REQUEST
UnQueueNotificationRequest ();
DWORD NotificationQueueWorker (
LPVOID
);
NTSTATUS RemoveGlobalNotification (ULONG SessionId);
NTSTATUS RemoveInvalidWindowsFromLists ();
NTSTATUS UnRegisterConsoleNotificationInternal (ULONG hWnd, ULONG SessionId, BOOL bDcrRef);
void ReleaseNotificationList (PNOTIFY_LIST pNotifyList);
void UpdateSessionState(PNOTIFY_LIST pNotifyList, WPARAM wNotification)
{
/*
WTS_CONSOLE_CONNECT bConnected, bConsole, !bRemote,
WTS_CONSOLE_DISCONNECT !bConnected, !bConsole, !bRemote
WTS_REMOTE_CONNECT bConnected, !bConsole, bremote
WTS_REMOTE_DISCONNECT !bConnected, !bConsole, !bRemote
WTS_SESSION_LOGON bLoggedOn
WTS_SESSION_LOGOFF !bLoggedOn
WTS_SESSION_LOCK bLocked
WTS_SESSION_UNLOCK !bLocked
*/
ASSERT(!IsGlobalList(pNotifyList));
ASSERT(!pNotifyList->SessionState.bConsole || !pNotifyList->SessionState.bRemote);
ASSERT(!pNotifyList->SessionState.bConnected || pNotifyList->SessionState.bConsole || pNotifyList->SessionState.bRemote);
switch (wNotification)
{
case WTS_CONSOLE_CONNECT:
ASSERT(!pNotifyList->SessionState.bConsole);
ASSERT(!pNotifyList->SessionState.bRemote);
pNotifyList->SessionState.bConnected = 1;
pNotifyList->SessionState.bConsole = 1;
break;
case WTS_CONSOLE_DISCONNECT:
ASSERT(pNotifyList->SessionState.bConsole);
ASSERT(pNotifyList->SessionState.bConnected);
ASSERT(!pNotifyList->SessionState.bRemote);
pNotifyList->SessionState.bConnected = 0;
pNotifyList->SessionState.bConsole = 0;
break;
case WTS_REMOTE_DISCONNECT:
ASSERT(pNotifyList->SessionState.bRemote);
ASSERT(pNotifyList->SessionState.bConnected);
ASSERT(!pNotifyList->SessionState.bConsole);
pNotifyList->SessionState.bConnected = 0;
pNotifyList->SessionState.bRemote = 0;
break;
case WTS_REMOTE_CONNECT:
ASSERT(!pNotifyList->SessionState.bRemote);
ASSERT(!pNotifyList->SessionState.bConnected);
ASSERT(!pNotifyList->SessionState.bConsole);
pNotifyList->SessionState.bConnected = 1;
pNotifyList->SessionState.bRemote = 1;
break;
case WTS_SESSION_LOGON:
ASSERT(pNotifyList->SessionState.bLoggedOn == 0);
pNotifyList->SessionState.bLoggedOn = 1;
break;
case WTS_SESSION_LOGOFF:
ASSERT(pNotifyList->SessionState.bLoggedOn == 1);
pNotifyList->SessionState.bLoggedOn = 0;
break;
case WTS_SESSION_LOCK:
ASSERT(pNotifyList->SessionState.bLocked == 0);
pNotifyList->SessionState.bLocked = 1;
break;
case WTS_SESSION_UNLOCK:
ASSERT(pNotifyList->SessionState.bLocked == 1);
pNotifyList->SessionState.bLocked = 0;
break;
default:
ASSERT(FALSE);
}
ASSERT(!pNotifyList->SessionState.bConsole || !pNotifyList->SessionState.bRemote);
ASSERT(!pNotifyList->SessionState.bConnected || pNotifyList->SessionState.bConsole || pNotifyList->SessionState.bRemote);
}
//
// Global initialization.
//
NTSTATUS InitializeConsoleNotification ()
{
NTSTATUS Status;
InitializeListHead( &gNotifyLList.ListHead );
Status = RtlInitializeCriticalSection( &gNotifyLList.ListLock );
if ( !NT_SUCCESS( Status ) )
{
return (Status);
}
//
// following members are unused in for global list.
//
gNotifyLList.GlobalList.Links.Blink = NULL;
gNotifyLList.GlobalList.Links.Flink = NULL;
gNotifyLList.GlobalList.SessionId = INVALID_SESSIONID;
gNotifyLList.GlobalList.SessonSerialNumber = INVALID_SESSIONSERIAL;
InitializeListHead( &gNotifyLList.GlobalList.ListHead);
Status = RtlInitializeCriticalSection( &gNotifyLList.GlobalList.ListLock );
if ( !NT_SUCCESS( Status ) )
{
RtlDeleteCriticalSection( &gNotifyLList.ListLock );
return (Status);
}
gNotifyLList.InvlidHwndList.Links.Blink = NULL;
gNotifyLList.InvlidHwndList.Links.Flink = NULL;
gNotifyLList.InvlidHwndList.SessionId = INVALID_SESSIONID;
gNotifyLList.InvlidHwndList.SessonSerialNumber = INVALID_SESSIONSERIAL;
InitializeListHead(&gNotifyLList.InvlidHwndList.ListHead) ;
Status = RtlInitializeCriticalSection( &gNotifyLList.InvlidHwndList.ListLock );
if ( !NT_SUCCESS( Status ) )
{
RtlDeleteCriticalSection( &gNotifyLList.ListLock );
RtlDeleteCriticalSection( &gNotifyLList.GlobalList.ListLock );
return (Status);
}
Status = InitializeNotificationQueue ();
if ( !NT_SUCCESS( Status ) )
{
RtlDeleteCriticalSection( &gNotifyLList.ListLock );
RtlDeleteCriticalSection( &gNotifyLList.GlobalList.ListLock );
RtlDeleteCriticalSection( &gNotifyLList.InvlidHwndList.ListLock );
}
return (Status);
}
//
// per winstation initialization.
//
NTSTATUS InitializeSessionNotification (PWINSTATION pWinStation)
{
NTSTATUS Status;
PNOTIFY_LIST pNewNotifyList;
ASSERT(pWinStation);
if (pWinStation->Terminating)
{
// dont create notification list if this winstation is already terminating.
// its possible that a winstation is being terminated before getting completely created,
// in such case we might end up calling RemoveSessionNotification before InitializeSessionNotification.
// so essentially leaving this session never to deleted. (Bug #414330)
return STATUS_SUCCESS;
}
#ifdef DBG
// BUGBUG - is it possible that a old session with the same session id is still there?
Status = GetNoficationListFromSessionId(pWinStation->LogonId, &pNewNotifyList, FALSE);
//
// we are just being asked to initialize notification
// we must not find list for this session in our LList.
//
ASSERT( STATUS_NO_SUCH_LOGON_SESSION == Status );
#endif
//
// create a new hwnd list for this session
//
pNewNotifyList = MemAlloc(sizeof(NOTIFY_LIST));
if (!pNewNotifyList)
{
return STATUS_NO_MEMORY;
}
pNewNotifyList->SessionId = pWinStation->LogonId;
pNewNotifyList->SessonSerialNumber = pWinStation->SessionSerialNumber;
//
// initialize session state.
//
{
pNewNotifyList->SessionState.bConnected = 0;
pNewNotifyList->SessionState.bConsole = 0;
pNewNotifyList->SessionState.bLoggedOn = 0;
pNewNotifyList->SessionState.bRemote = 0;
pNewNotifyList->SessionState.bLocked = 0; // bugbug we dont know the real welcome state ;(
}
InitializeListHead( &pNewNotifyList->ListHead);
Status = RtlInitializeCriticalSection( &pNewNotifyList->ListLock );
if ( !NT_SUCCESS( Status ) )
{
MemFree(pNewNotifyList);
pNewNotifyList = NULL;
return Status;
}
// now link this new list into our main list of lists.
ENTERCRIT(&gNotifyLList.ListLock);
InsertTailList( &gNotifyLList.ListHead, &pNewNotifyList->Links);
LEAVECRIT(&gNotifyLList.ListLock);
return STATUS_SUCCESS;
}
//
// must be called when a session ends.
//
NTSTATUS RemoveSessionNotification(ULONG SessionId, ULONG SessionSerialNumber)
{
NTSTATUS Status;
PNOTIFY_LIST pListTobeRemoved;
UNREFERENCED_PARAMETER(SessionSerialNumber); // it's referenced only for Chk builds.
// BUGBUG - is it possible that a new session with the same session id was created while we are here ?
Status = GetNoficationListFromSessionId( SessionId, &pListTobeRemoved, TRUE);
if (!NT_SUCCESS( Status ))
{
//
// we are being asked to remove session notification
// but its possible that we dont have session notification list created for this session.
// This can happen if the session is being terminate during session creation process.
//
ASSERT( !pListTobeRemoved );
return Status;
}
ASSERT( pListTobeRemoved );
ASSERT( SessionSerialNumber == pListTobeRemoved->SessonSerialNumber );
RemoveEntryList( &pListTobeRemoved->Links );
LEAVECRIT(&gNotifyLList.ListLock);
//
// walk throught this list and free all the nodes.
//
while (!IsListEmpty(&pListTobeRemoved->ListHead))
{
PNOTIFY_ENTRY pEntry;
PLIST_ENTRY Next;
Next = pListTobeRemoved->ListHead.Flink;
ASSERT(Next);
pEntry = CONTAINING_RECORD( Next, NOTIFY_ENTRY, Links );
ASSERT(pEntry);
RemoveEntryList( &pEntry->Links );
MemFree(pEntry);
pEntry = NULL;
}
// we are no more going to use this list lock.
RtlDeleteCriticalSection( &pListTobeRemoved->ListLock );
MemFree(pListTobeRemoved);
pListTobeRemoved = NULL;
return RemoveGlobalNotification (SessionId);
// return QueueNotificationRequest(pWinStation->SessionSerialNumber, pWinStation->LogonId, 0);
}
NTSTATUS RemoveGlobalNotification (ULONG SessionId)
{
PLIST_ENTRY Head, Next;
PNOTIFY_LIST pListTobeRemoved = NULL;
NTSTATUS Status = GetGlobalNotificationList(&pListTobeRemoved);
if ( !NT_SUCCESS( Status ) )
{
return (Status);
}
ASSERT(pListTobeRemoved);
Head = &pListTobeRemoved->ListHead;
Next = Head->Flink;
while (Head != Next)
{
PNOTIFY_ENTRY_GLOBAL pEntryGlobal = CONTAINING_RECORD( Next, NOTIFY_ENTRY_GLOBAL, Links );
Next = Next->Flink;
ASSERT(pEntryGlobal);
if (pEntryGlobal->SessionId == SessionId)
{
RemoveEntryList( &pEntryGlobal->Links );
MemFree(pEntryGlobal);
pEntryGlobal = NULL;
}
}
ReleaseNotificationList( pListTobeRemoved );
pListTobeRemoved = NULL;
// now lets remove the invalid Windows associated with this session.
// from the list if there is any.
pListTobeRemoved = NULL;
Status = GetInvlidHwndList(&pListTobeRemoved);
if ( !NT_SUCCESS( Status ) )
{
return (Status);
}
ASSERT(pListTobeRemoved);
Head = &pListTobeRemoved->ListHead;
Next = Head->Flink;
while (Head != Next)
{
PNOTIFY_ENTRY_GLOBAL pEntryGlobal = CONTAINING_RECORD( Next, NOTIFY_ENTRY_GLOBAL, Links );
Next = Next->Flink;
ASSERT(pEntryGlobal);
if (pEntryGlobal->SessionId == SessionId)
{
RemoveEntryList( &pEntryGlobal->Links );
MemFree(pEntryGlobal);
pEntryGlobal = NULL;
}
}
ReleaseNotificationList(pListTobeRemoved);
return STATUS_SUCCESS;
}
NTSTATUS RegisterConsoleNotification ( ULONG hWnd, ULONG SessionId, DWORD dwFlags )
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
PNOTIFY_LIST pNotifyList = NULL;
PNOTIFY_LIST pNotifyListGlobal = NULL;
PNOTIFY_ENTRY pEntry = NULL;
PNOTIFY_ENTRY_GLOBAL pEntryGlobal = NULL;
if (dwFlags != NOTIFY_FOR_THIS_SESSION && dwFlags != NOTIFY_FOR_ALL_SESSIONS)
{
//
// invalid flag value
//
return STATUS_INVALID_PARAMETER_3;
}
//
// get the global session notificaiton list
//
Status = GetGlobalNotificationList(&pNotifyListGlobal);
if (!NT_SUCCESS(Status))
{
return Status;
}
//
// get the session specific list for this window
//
Status = GetNoficationListFromSessionId( SessionId, &pNotifyList, FALSE);
if (!NT_SUCCESS(Status))
{
ReleaseNotificationList (pNotifyListGlobal);
return Status;
}
ASSERT( pNotifyList );
ASSERT( pNotifyListGlobal );
pEntry = GetHWndEntryFromSessionList(pNotifyList, hWnd);
pEntryGlobal = GetHWndEntryFromGlobalList(pNotifyListGlobal, hWnd, SessionId);
//
// entry must not exist in both the lists.
//
ASSERT(!(pEntry && pEntryGlobal));
if (pEntry)
{
//
// Let other list go
//
ReleaseNotificationList( pNotifyListGlobal );
ASSERT( pEntry );
ASSERT( pEntry->RefCount > 0 );
//
// entry already exists, just increment its reference count.
//
pEntry->RefCount++;
ReleaseNotificationList( pNotifyList );
}
else if (pEntryGlobal)
{
ReleaseNotificationList (pNotifyList);
ASSERT( pEntryGlobal );
ASSERT( pEntryGlobal->RefCount > 0 );
//
// entry already exists, just increment its reference count.
//
pEntryGlobal->RefCount++;
ReleaseNotificationList( pNotifyListGlobal );
}
else
{
//
// the entry does not exists in either of the lists.
// so we need to create a new entry
//
if (dwFlags & NOTIFY_FOR_ALL_SESSIONS)
{
ReleaseNotificationList (pNotifyList);
pEntryGlobal = MemAlloc( sizeof(NOTIFY_ENTRY_GLOBAL) );
if (pEntryGlobal == NULL )
{
Status = STATUS_NO_MEMORY;
}
else
{
pEntryGlobal->hWnd = hWnd;
pEntryGlobal->SessionId = SessionId;
pEntryGlobal->RefCount = 1;
InsertTailList( &(pNotifyListGlobal->ListHead), &(pEntryGlobal->Links) );
}
ReleaseNotificationList( pNotifyListGlobal );
}
else
{
ReleaseNotificationList( pNotifyListGlobal );
pEntry = MemAlloc( sizeof(NOTIFY_ENTRY) );
if (pEntry == NULL )
{
Status = STATUS_NO_MEMORY;
}
else
{
pEntry->hWnd = hWnd;
pEntry->RefCount = 1;
InsertTailList( &(pNotifyList->ListHead), &(pEntry->Links) );
}
ReleaseNotificationList (pNotifyList);
}
}
return (Status);
}
NTSTATUS UnRegisterConsoleNotification (ULONG hWnd, ULONG SessionId)
{
return UnRegisterConsoleNotificationInternal (hWnd, SessionId, TRUE);
}
NTSTATUS UnRegisterConsoleNotificationInternal (ULONG hWnd, ULONG SessionId, BOOL bDcrRef)
{
NTSTATUS Status;
PNOTIFY_LIST pNotifyList;
PNOTIFY_ENTRY pEntry;
//
// get the notification list for the Session
//
Status = GetNoficationListFromSessionId( SessionId, &pNotifyList, FALSE);
if (NT_SUCCESS(Status))
{
ASSERT(pNotifyList);
pEntry = GetHWndEntryFromSessionList(pNotifyList,hWnd);
if (pEntry)
{
ASSERT( pEntry->RefCount > 0 );
// decrement ref count
pEntry->RefCount--;
if (pEntry->RefCount == 0 || !bDcrRef)
{
RemoveEntryList( &pEntry->Links );
MemFree(pEntry);
pEntry = NULL;
}
ReleaseNotificationList (pNotifyList);
}
else
{
PNOTIFY_LIST pNotifyListGlobal = NULL;
PNOTIFY_ENTRY_GLOBAL pEntryGlobal = NULL;
ReleaseNotificationList (pNotifyList);
//
// now check the global session notificaiton entry
//
Status = GetGlobalNotificationList(&pNotifyListGlobal);
if (NT_SUCCESS(Status))
{
pEntryGlobal = GetHWndEntryFromGlobalList(pNotifyListGlobal, hWnd, SessionId);
if (pEntryGlobal)
{
ASSERT(pEntryGlobal->RefCount > 0);
pEntryGlobal->RefCount--;
if (pEntryGlobal->RefCount == 0 || !bDcrRef)
{
RemoveEntryList( &pEntryGlobal->Links );
MemFree(pEntryGlobal);
pEntryGlobal = NULL;
}
}
else
{
Status = STATUS_NOT_FOUND;
}
ReleaseNotificationList( pNotifyListGlobal );
}
}
}
return (Status);
}
NTSTATUS NotifySessionChange (PWINSTATION pWinStation, WPARAM wNotification)
{
return QueueNotificationRequest(pWinStation->SessionSerialNumber, pWinStation->LogonId, wNotification);
}
NTSTATUS NotifyLogon(PWINSTATION pWinStation)
{
return NotifySessionChange(pWinStation, WTS_SESSION_LOGON);
}
NTSTATUS NotifyLogoff(PWINSTATION pWinStation)
{
return NotifySessionChange(pWinStation, WTS_SESSION_LOGOFF);
}
NTSTATUS NotifyConnect (PWINSTATION pWinStation, BOOL bConsole)
{
return NotifySessionChange(pWinStation, bConsole ? WTS_CONSOLE_CONNECT : WTS_REMOTE_CONNECT);
}
NTSTATUS NotifyDisconnect (PWINSTATION pWinStation, BOOL bConsole)
{
return NotifySessionChange(pWinStation, bConsole ? WTS_CONSOLE_DISCONNECT : WTS_REMOTE_DISCONNECT);
}
NTSTATUS NofifyWelcomeOn (PWINSTATION pWinStation)
{
return NotifySessionChange(pWinStation, WTS_SESSION_LOCK);
}
NTSTATUS NotifyWelcomeOff (PWINSTATION pWinStation)
{
return NotifySessionChange(pWinStation, WTS_SESSION_UNLOCK);
}
NTSTATUS SendNotificationToHwnd(PWINSTATION pWinstation, ULONG hWnd, ULONG SessionId, WPARAM wParam)
{
WINSTATION_APIMSG WMsg;
//
// now pupulate the WMSG for delievery.
//
WMsg.u.sMsg.Msg = WM_WTSSESSION_CHANGE;
WMsg.u.sMsg.wParam = wParam;
WMsg.ApiNumber = SMWinStationSendWindowMessage ;
WMsg.WaitForReply = FALSE;
WMsg.u.sMsg.dataBuffer = NULL;
WMsg.u.sMsg.bufferSize = 0;
WMsg.u.sMsg.lParam = SessionId;
WMsg.u.sMsg.hWnd = (HWND)ULongToHandle( hWnd );
return SendWinStationCommand( pWinstation, &WMsg, 0);
}
NTSTATUS NotifyConsole (ULONG SessionId, ULONG SessionSerialNumber, WPARAM wParam)
{
NTSTATUS Status = STATUS_SUCCESS;
DWORD dwError;
PWINSTATION pWinStation=NULL;
Status = RemoveInvalidWindowsFromLists();
ASSERT(NT_SUCCESS(Status));
pWinStation = FindWinStationById(SessionId, FALSE);
//
// if we find the session we were looking for
// note: we must check for the serialnumber, as the session id is not unique.
//
if (pWinStation)
{
if (SessionSerialNumber == pWinStation->SessionSerialNumber)
{
PNOTIFY_LIST pConsoleList;
Status = GetNoficationListFromSessionId(pWinStation->LogonId, &pConsoleList, FALSE);
if (NT_SUCCESS(Status) && pConsoleList)
{
PLIST_ENTRY Head, Next;
Head = &pConsoleList->ListHead;
for ( Next = Head->Flink; Next != Head; Next = Next->Flink )
{
PNOTIFY_ENTRY pEntry;
pEntry = CONTAINING_RECORD( Next, NOTIFY_ENTRY, Links );
ASSERT(pEntry);
Status = SendNotificationToHwnd(pWinStation, pEntry->hWnd, SessionId, wParam);
if (!NT_SUCCESS(Status))
{
NTSDDBGPRINT(("conntfy.c - SendWinStationCommand failed, Status = %d.\n", Status));
}
}
UpdateSessionState(pConsoleList, wParam);
ReleaseNotificationList( pConsoleList );
}
}
ReleaseWinStation( pWinStation );
}
//
// now send notifications to windows registered for all session notificaitons.
//
{
PNOTIFY_LIST pNotifyListGlobal = NULL;
Status = GetGlobalNotificationList(&pNotifyListGlobal);
if (NT_SUCCESS(Status))
{
PLIST_ENTRY Head, Next;
Head = &pNotifyListGlobal->ListHead;
for ( Next = Head->Flink; Next != Head; Next = Next->Flink )
{
PNOTIFY_ENTRY_GLOBAL pEntryGlobal = NULL;
pEntryGlobal = CONTAINING_RECORD( Next, NOTIFY_ENTRY_GLOBAL, Links );
ASSERT(pEntryGlobal);
pWinStation = FindWinStationById(pEntryGlobal->SessionId, FALSE);
if (pWinStation)
{
if (!pWinStation->Terminating)
{
Status = SendNotificationToHwnd(pWinStation, pEntryGlobal->hWnd, SessionId, wParam);
if (!NT_SUCCESS(Status))
{
NTSDDBGPRINT(("conntfy.c - SendWinStationCommand failed, Status = %d.\n", Status));
}
}
ReleaseWinStation( pWinStation );
}
}
ReleaseNotificationList(pNotifyListGlobal);
}
else
{
NTSDDBGPRINT(("conntfy.c - Failed to get all session notification list - status = 0x%x.\n", Status));
}
}
//
// now lets notify SCM which will notify all the services registered for SERVICE_ACCEPT_SESSIONCHANGE
//
//
// logon logoff notifications for session 0 are sent by winlogon. rest are handled here.
//
if (SessionId != 0 || ( wParam != WTS_SESSION_LOGON && wParam != WTS_SESSION_LOGOFF))
{
WTSSESSION_NOTIFICATION wtsConsoleNotification;
wtsConsoleNotification.cbSize = sizeof(WTSSESSION_NOTIFICATION);
wtsConsoleNotification.dwSessionId = SessionId;
dwError = I_ScSendTSMessage(
SERVICE_CONTROL_SESSIONCHANGE, // op code
(DWORD)wParam, // event code,
wtsConsoleNotification.cbSize, // data size
(LPBYTE)&wtsConsoleNotification // data.
);
}
return Status;
}
NTSTATUS DestroyLock( PNOTIFY_LIST pNtfyList)
{
return RtlDeleteCriticalSection( &pNtfyList->ListLock );
}
NTSTATUS CreateLock (PNOTIFY_LIST pNtfyList)
{
return RtlInitializeCriticalSection( &pNtfyList->ListLock );
}
BOOL IsInvalidHWndList (PNOTIFY_LIST pNtfyList)
{
return (pNtfyList == &gNotifyLList.InvlidHwndList);
}
BOOL IsGlobalList(PNOTIFY_LIST pNtfyList)
{
return (pNtfyList == &gNotifyLList.GlobalList);
}
int GetListCount (LIST_ENTRY *pListHead)
{
PLIST_ENTRY Head, Next;
int iCount = 0;
ASSERT(pListHead);
Head = pListHead;
for ( Next = Head->Flink; Next != Head; Next = Next->Flink )
{
iCount++;
}
return iCount;
}
PNOTIFY_ENTRY GetHWndEntryFromSessionList(PNOTIFY_LIST pNotifyList, ULONG hWnd)
{
PLIST_ENTRY Head = NULL;
PLIST_ENTRY Next = NULL;
PNOTIFY_ENTRY pEntry = NULL;
Head = &pNotifyList->ListHead;
for ( Next = Head->Flink; Next != Head; Next = Next->Flink )
{
pEntry = CONTAINING_RECORD( Next, NOTIFY_ENTRY, Links );
ASSERT(pEntry);
if (pEntry->hWnd == hWnd)
{
return pEntry;
}
}
return NULL;
}
PNOTIFY_ENTRY_GLOBAL GetHWndEntryFromGlobalList(PNOTIFY_LIST pNotifyList, ULONG hWnd, ULONG SessionId)
{
PLIST_ENTRY Head = NULL;
PLIST_ENTRY Next = NULL;
PNOTIFY_ENTRY_GLOBAL pEntry = NULL;
Head = &pNotifyList->ListHead;
for ( Next = Head->Flink; Next != Head; Next = Next->Flink )
{
pEntry = CONTAINING_RECORD( Next, NOTIFY_ENTRY_GLOBAL, Links );
ASSERT(pEntry);
if (pEntry->hWnd == hWnd && SessionId == pEntry->SessionId)
{
return pEntry;
}
}
return NULL;
}
//
// returns PNOTIFY_LIST list for the given session.
//
NTSTATUS GetNoficationListFromSessionId (ULONG SessionId, PNOTIFY_LIST *ppNotifyList, BOOL bKeepLListLocked)
{
PLIST_ENTRY Next, Head;
ASSERT(ppNotifyList);
*ppNotifyList = NULL;
// lock our list of lists.
ENTERCRIT(&gNotifyLList.ListLock);
Head = &gNotifyLList.ListHead;
Next = Head->Flink;
while (Head != Next)
{
PNOTIFY_LIST pNotifyList = CONTAINING_RECORD( Next, NOTIFY_LIST, Links );
ASSERT( pNotifyList );
//
// we always take gNotifyLList.ListLock first and then the Listlock
// therefore we must never have PNOTIFY_LIST.ListLock at this time.
//
ASSERT( (HANDLE)LongToHandle( GetCurrentThreadId() ) != pNotifyList->ListLock.OwningThread );
if (pNotifyList->SessionId == SessionId)
{
//
// did we find more that 1 matching notify list ???, should never happen!
//
ASSERT(*ppNotifyList == NULL);
//
// ok we found the session list we were looking for
//
*ppNotifyList = pNotifyList;
#ifndef DBG
break;
#endif
}
Next = Next->Flink;
}
//
// if we have found the list we were looking for
//
if (*ppNotifyList)
{
//
// lock the list before returning.
//
ENTERCRIT(&(*ppNotifyList)->ListLock);
}
if (!(*ppNotifyList) || !bKeepLListLocked)
{
//
// unlock llist lock.
//
LEAVECRIT(&gNotifyLList.ListLock);
}
if (*ppNotifyList)
{
return STATUS_SUCCESS;
}
else
{
return STATUS_NO_SUCH_LOGON_SESSION;
}
}
void ReleaseNotificationList (PNOTIFY_LIST pNotifyList)
{
ASSERT(pNotifyList);
if (IsInvalidHWndList(pNotifyList))
{
// we must take invalid hwnd list before taking global list.
ASSERT( (HANDLE)LongToHandle( GetCurrentThreadId() ) != (gNotifyLList.GlobalList.ListLock).OwningThread );
// we must take invalid hwnd list before taking LList.
ASSERT( (HANDLE)LongToHandle( GetCurrentThreadId() ) != (gNotifyLList.ListLock).OwningThread );
}
else if (IsGlobalList(pNotifyList))
{
// we must take invalid hwnd list before taking LList.
ASSERT( (HANDLE)LongToHandle( GetCurrentThreadId() ) != (gNotifyLList.ListLock).OwningThread );
}
LEAVECRIT(&pNotifyList->ListLock);
}
NTSTATUS GetInvlidHwndList(PNOTIFY_LIST *ppConChgNtfy)
{
ASSERT(ppConChgNtfy);
// we must take invalid hwnd list before taking global list.
ASSERT( (HANDLE)LongToHandle( GetCurrentThreadId() ) != (gNotifyLList.GlobalList.ListLock).OwningThread );
// we must take invalid hwnd list before taking LList.
ASSERT( (HANDLE)LongToHandle( GetCurrentThreadId() ) != (gNotifyLList.ListLock).OwningThread );
*ppConChgNtfy = &gNotifyLList.InvlidHwndList;
ENTERCRIT(&(*ppConChgNtfy)->ListLock);
ASSERT(gNotifyLList.InvlidHwndList.Links.Blink == NULL);
ASSERT(gNotifyLList.InvlidHwndList.Links.Flink == NULL);
ASSERT(gNotifyLList.InvlidHwndList.SessionId == INVALID_SESSIONID);
ASSERT(gNotifyLList.InvlidHwndList.SessonSerialNumber == INVALID_SESSIONSERIAL);
return STATUS_SUCCESS;
}
NTSTATUS GetGlobalNotificationList(PNOTIFY_LIST *ppConChgNtfy)
{
ASSERT(ppConChgNtfy);
// we must LLIst after global list.
ASSERT( (HANDLE)LongToHandle( GetCurrentThreadId() ) != (gNotifyLList.ListLock).OwningThread );
*ppConChgNtfy = &gNotifyLList.GlobalList;
ENTERCRIT(&(*ppConChgNtfy)->ListLock);
ASSERT(gNotifyLList.GlobalList.Links.Blink == NULL);
ASSERT(gNotifyLList.GlobalList.Links.Flink == NULL);
ASSERT(gNotifyLList.GlobalList.SessionId == INVALID_SESSIONID);
ASSERT(gNotifyLList.GlobalList.SessonSerialNumber == INVALID_SESSIONSERIAL);
return (STATUS_SUCCESS);
}
NTSTATUS InitializeNotificationQueue ()
{
DWORD ThreadId;
NTSTATUS Status;
HANDLE hSessionNotifyThread;
InitializeListHead( &gNotifyLList.RequestQueue.ListHead);
gNotifyLList.RequestQueue.hNotificationEvent = CreateEvent(
NULL, // SD
FALSE, // reset type
FALSE, // initial state
NULL // object name
);
if (gNotifyLList.RequestQueue.hNotificationEvent == NULL)
{
// we failed to create event.
// return GetLastError()
return STATUS_UNSUCCESSFUL;
}
Status = RtlInitializeCriticalSection( &gNotifyLList.RequestQueue.ListLock );
if (!NT_SUCCESS(Status))
{
CloseHandle(gNotifyLList.RequestQueue.hNotificationEvent);
gNotifyLList.RequestQueue.hNotificationEvent = NULL;
return Status;
}
//
// now create thread for notifications.
//
hSessionNotifyThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)NotificationQueueWorker,
NULL,
0,
&ThreadId);
//
// Just close it, we can do without this handle.
//
if( hSessionNotifyThread )
{
NtClose( hSessionNotifyThread );
}
else
{
RtlDeleteCriticalSection( &gNotifyLList.RequestQueue.ListLock );
CloseHandle(gNotifyLList.RequestQueue.hNotificationEvent);
gNotifyLList.RequestQueue.hNotificationEvent = NULL;
return STATUS_UNSUCCESSFUL;
}
return STATUS_SUCCESS;
}
void LockNotificationQueue()
{
ENTERCRIT(&gNotifyLList.RequestQueue.ListLock);
}
void UnLockNotificationQueue()
{
LEAVECRIT(&gNotifyLList.RequestQueue.ListLock);
}
//
// Queues a notification entry
//
NTSTATUS QueueNotificationRequest(ULONG SessionSerialNumber, ULONG SessionId, WPARAM notification)
{
PNOTIFICATION_REQUEST pRequest = NULL;
pRequest = MemAlloc( sizeof(NOTIFICATION_REQUEST) );
if (!pRequest)
{
return STATUS_NO_MEMORY;
}
pRequest->SessonSerialNumber = SessionSerialNumber;
pRequest->SessionId = SessionId;
pRequest->NotificationCode = notification;
// now lock the queue
LockNotificationQueue();
InsertHeadList(&gNotifyLList.RequestQueue.ListHead, &pRequest->Links);
UnLockNotificationQueue();
// let the waiting thread process this notification.
PulseEvent(gNotifyLList.RequestQueue.hNotificationEvent);
return STATUS_SUCCESS;
}
//
// takes out a notification entry from queue.
//
PNOTIFICATION_REQUEST UnQueueNotificationRequest()
{
PLIST_ENTRY pEntry;
PNOTIFICATION_REQUEST pRequest = NULL;
//
// Remove a request from the list.
//
LockNotificationQueue();
if (!IsListEmpty(&gNotifyLList.RequestQueue.ListHead))
{
pEntry = RemoveTailList(&gNotifyLList.RequestQueue.ListHead);
pRequest = CONTAINING_RECORD(pEntry, NOTIFICATION_REQUEST, Links);
}
UnLockNotificationQueue();
return pRequest;
}
// This thread is a helper for the next function. We do this because the
// compiler defies reason by insisting not all control paths return a value.
VOID NotificationQueueWorkerEx()
{
PNOTIFICATION_REQUEST pRequest = NULL;
for(;;)
{
WaitForSingleObject(gNotifyLList.RequestQueue.hNotificationEvent, INFINITE); // wait for the event to be signaled.
while ((pRequest = UnQueueNotificationRequest()) != NULL)
{
if (!pRequest->NotificationCode)
{
ASSERT(FALSE);
// this is not a real notificaiton request.
// this request is for session removal.
// RemoveGlobalNotification(pRequest->SessionId, pRequest->SessonSerialNumber);
}
else
{
NotifyConsole (pRequest->SessionId, pRequest->SessonSerialNumber, pRequest->NotificationCode);
}
MemFree(pRequest);
pRequest = NULL;
}
}
}
//
// this thread takes a notification request from queue and executes it.
// this thread gets signaled when a new item is added to the queue.
//
DWORD NotificationQueueWorker(LPVOID ThreadParameter)
{
UNREFERENCED_PARAMETER(ThreadParameter);
NotificationQueueWorkerEx();
return 0;
}
NTSTATUS SetLockedState (PWINSTATION pWinStation, BOOL bLocked)
{
ASSERT(pWinStation);
if (bLocked)
{
return NofifyWelcomeOn (pWinStation);
}
else
{
return NotifyWelcomeOff (pWinStation);
}
}
NTSTATUS GetLockedState (PWINSTATION pWinStation, BOOL *pbLocked)
{
NTSTATUS Status;
PNOTIFY_LIST pNotifyList;
ASSERT(pbLocked);
ASSERT(pWinStation);
Status = GetNoficationListFromSessionId(pWinStation->LogonId, &pNotifyList, FALSE);
if ( !NT_SUCCESS( Status ) )
{
return (Status);
}
*pbLocked = pNotifyList->SessionState.bLocked;
ReleaseNotificationList(pNotifyList);
return STATUS_SUCCESS;
}
/*
NTSTATUS GetSessionState (PWINSTATION pWinStation, WTSSESSION_STATE *pSessionState)
{
NTSTATUS Status;
PNOTIFY_LIST pNotifyList;
ASSERT(pSessionState);
ASSERT(pWinStation);
Status = GetNoficationListFromSessionId(pWinStation->LogonId, &pNotifyList, FALSE);
if ( !NT_SUCCESS( Status ) )
{
return (Status);
}
*pSessionState = pNotifyList->SessionState;
ReleaseNotificationList(pNotifyList);
return STATUS_SUCCESS;
}
*/
NTSTATUS RemoveBadHwnd(ULONG hWnd, ULONG SessionId)
{
PNOTIFY_ENTRY_GLOBAL pInvalidHwndEntry;
PNOTIFY_LIST pInvalidHwndList;
NTSTATUS Status;
Status = GetInvlidHwndList(&pInvalidHwndList);
if ( !NT_SUCCESS( Status ) )
{
return (Status);
}
pInvalidHwndEntry = GetHWndEntryFromGlobalList(pInvalidHwndList, hWnd, SessionId);
//
// this entry must not already exist in invalid list.
//
if(!pInvalidHwndEntry)
{
// it alreay exists in our list.
pInvalidHwndEntry = MemAlloc(sizeof(NOTIFY_ENTRY_GLOBAL));
if (pInvalidHwndEntry)
{
pInvalidHwndEntry->hWnd = hWnd;
pInvalidHwndEntry->SessionId = SessionId;
InsertHeadList(&pInvalidHwndList->ListHead, &pInvalidHwndEntry->Links);
}
}
ReleaseNotificationList( pInvalidHwndList );
if (pInvalidHwndEntry)
return STATUS_SUCCESS;
else
return STATUS_NO_MEMORY;
}
NTSTATUS RemoveInvalidWindowsFromLists()
{
PNOTIFY_LIST pInvalidHwndList;
PLIST_ENTRY Next, Head;
NTSTATUS Status;
Status = GetInvlidHwndList(&pInvalidHwndList);
if ( !NT_SUCCESS( Status ) )
{
return (Status);
}
Head = &pInvalidHwndList->ListHead;
Next = Head->Flink;
while (Head != Next)
{
PNOTIFY_ENTRY_GLOBAL pInvalidHwndEntry = CONTAINING_RECORD( Next, NOTIFY_ENTRY_GLOBAL, Links );
Next = Next->Flink;
ASSERT(pInvalidHwndEntry);
Status = UnRegisterConsoleNotificationInternal (pInvalidHwndEntry->hWnd, pInvalidHwndEntry->SessionId, FALSE);
// we are done removing this invalid hwnd entry from our lists.
RemoveEntryList( &pInvalidHwndEntry->Links );
MemFree(pInvalidHwndEntry);
pInvalidHwndEntry = NULL;
}
ReleaseNotificationList(pInvalidHwndList);
return STATUS_SUCCESS;
}
/*
our order of locks is
0. Invalid Hwnd List.
1. Global Notification List
2. Winstation
3. List of Lists lock.
4. Session Notification List
*/
//#ifdef MAKARANDS_HIGHER_WARNING_LEVEL
#pragma warning(pop)
//#endif