Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2619 lines
85 KiB

/****************************** Module Header ******************************\
* Module Name: hooks.c
*
* Copyright (c) 1985 - 1999, Microsoft Corporation
*
* This module contains the user hook APIs and support routines.
*
* History:
* 01-28-91 DavidPe Created.
* 08 Feb 1992 IanJa Unicode/ANSI aware & neutral
\***************************************************************************/
#include "precomp.h"
#pragma hdrstop
/*
* This table is used to determine whether a particular hook
* can be set for the system or a task, and other hook-ID specific things.
*/
#define HKF_SYSTEM 0x01
#define HKF_TASK 0x02
#define HKF_JOURNAL 0x04 // JOURNAL the mouse on set
#define HKF_NZRET 0x08 // Always return NZ hook for <=3.0 compatibility
#define HKF_INTERSENDABLE 0x10 // OK to call hookproc in context of hooking thread
#define HKF_LOWLEVEL 0x20 // Low level hook
CONST int ampiHookError[CWINHOOKS] = {
0, // WH_MSGFILTER (-1)
0, // WH_JOURNALRECORD 0
-1, // WH_JOURNALPLAYBACK 1
0, // WH_KEYBOARD 2
0, // WH_GETMESSAGE 3
0, // WH_CALLWNDPROC 4
0, // WH_CBT 5
0, // WH_SYSMSGFILTER 6
0, // WH_MOUSE 7
0, // WH_HARDWARE 8
0, // WH_DEBUG 9
0, // WH_SHELL 10
0, // WH_FOREGROUNDIDLE 11
0, // WH_CALLWNDPROCRET 12
0, // WH_KEYBOARD_LL 13
0 // WH_MOUSE_LL 14
#ifdef REDIRECTION
,0 // WH_HITTEST
#endif // REDIRECTION
};
CONST BYTE abHookFlags[CWINHOOKS] = {
HKF_SYSTEM | HKF_TASK | HKF_NZRET , // WH_MSGFILTER (-1)
HKF_SYSTEM | HKF_JOURNAL | HKF_INTERSENDABLE , // WH_JOURNALRECORD 0
HKF_SYSTEM | HKF_JOURNAL | HKF_INTERSENDABLE , // WH_JOURNALPLAYBACK 1
HKF_SYSTEM | HKF_TASK | HKF_NZRET | HKF_INTERSENDABLE , // WH_KEYBOARD 2
HKF_SYSTEM | HKF_TASK , // WH_GETMESSAGE 3
HKF_SYSTEM | HKF_TASK , // WH_CALLWNDPROC 4
HKF_SYSTEM | HKF_TASK , // WH_CBT 5
HKF_SYSTEM , // WH_SYSMSGFILTER 6
HKF_SYSTEM | HKF_TASK | HKF_INTERSENDABLE , // WH_MOUSE 7
HKF_SYSTEM | HKF_TASK , // WH_HARDWARE 8
HKF_SYSTEM | HKF_TASK , // WH_DEBUG 9
HKF_SYSTEM | HKF_TASK , // WH_SHELL 10
HKF_SYSTEM | HKF_TASK , // WH_FOREGROUNDIDLE 11
HKF_SYSTEM | HKF_TASK , // WH_CALLWNDPROCRET 12
HKF_SYSTEM | HKF_LOWLEVEL | HKF_INTERSENDABLE , // WH_KEYBOARD_LL 13
HKF_SYSTEM | HKF_LOWLEVEL | HKF_INTERSENDABLE // WH_MOUSE_LL 14
#ifdef REDIRECTION
,HKF_SYSTEM | HKF_LOWLEVEL | HKF_INTERSENDABLE // WH_HITTEST 15
#endif // REDIRECTION
};
/*
* HACK (hiroyama) see xxxCallJournalPlaybackHook()
* Optimization: faster determination whether the message is one of
* WM_[SYS][DEAD]CHAR.
* Argument (msg) requires to be one of keyboard messages. Range check
* should be done before calling IS_CHAR_MSG() macro.
*
* (i.e. WM_KEYFIRST <= msg < WM_KEYLAST)
*
* We expect bit 0x02 of all WM_*CHAR messages to be set.
* and bit 0x02 of all WM_*KEY* messages to be clear
*
* WM_KEYDOWN 0x100 000
* WM_KEYUP 0x101 001
* WM_CHAR 0x102 010
* WM_DEADCHAR 0x103 011
*
* WM_SYSKEYDOWN 0x104 100
* WM_SYSKEYUP 0x105 101
* WM_SYSCHAR 0x106 110
* WM_SYSDEADCHAR 0x107 111
*
*/
/*
*/
#if (WM_KEYFIRST != 0x100) || \
(WM_KEYLAST != 0x109) || \
(WM_KEYLAST != WM_UNICHAR) || \
(WM_KEYDOWN & 0x2) || \
(WM_KEYUP & 0x2) || \
(WM_SYSKEYDOWN & 0x2) || \
(WM_SYSKEYUP & 0x2) || \
!(WM_CHAR & 0x02) || \
!(WM_DEADCHAR & 0x02) || \
!(WM_SYSCHAR & 0x02) || \
!(WM_SYSDEADCHAR & 0x02)
#error "unexpected value in keyboard messages."
#endif
#if DBG
BOOL IsCharMsg(UINT msg)
{
UserAssert(msg >= WM_KEYFIRST && msg < WM_KEYLAST);
return msg & 0x02;
}
#define IS_CHAR_MSG(msg) IsCharMsg(msg)
#else
#define IS_CHAR_MSG(msg) ((msg) & 0x02)
#endif
void UnlinkHook(PHOOK phkFree);
/***************************************************************************\
* DbgValidateThisHook
*
* Validates a hook structure and returns the start of its chain.
*
* History:
* 03-25-97 GerardoB Created
\***************************************************************************/
#if DBG
PHOOK * DbgValidateThisHook (PHOOK phk, int iType, PTHREADINFO ptiHooked)
{
CheckCritIn();
/*
* No bogus flags
*/
UserAssert(!(phk->flags & ~HF_DBGUSED));
/*
* Type
*/
UserAssert(phk->iHook == iType);
/*
* HF_GLOBAL & ptiHooked. return the start of its hook chain.
*/
if (phk->flags & HF_GLOBAL) {
UserAssert(phk->ptiHooked == NULL);
if (phk->rpdesk != NULL) {
UserAssert(GETPTI(phk) == gptiRit);
return &phk->rpdesk->pDeskInfo->aphkStart[iType + 1];
} else {
return &GETPTI(phk)->pDeskInfo->aphkStart[iType + 1];
}
} else {
UserAssert((phk->ptiHooked == ptiHooked)
|| (abHookFlags[iType + 1] & HKF_INTERSENDABLE));
return &(phk->ptiHooked->aphkStart[iType + 1]);
}
}
/***************************************************************************\
* DbgValidatefsHook
*
* Make sure that the fsHook bit masks are in sync. If the bits
* are out of sync, some hook must have the HF_INCHECKWHF flag
* (this means the bits are being adjusted right now)
*
* History:
* 05-20-97 GerardoB Extracted from PhkFirst*Valid
\***************************************************************************/
void DbgValidatefsHook(PHOOK phk, int nFilterType, PTHREADINFO pti, BOOL fGlobal)
{
CheckCritIn();
/*
* If no pti is provided, figure out what it should be.
* phk is expected to be NULL.
*/
if (pti == NULL) {
fGlobal = (phk->flags & HF_GLOBAL);
if (fGlobal) {
pti = GETPTI(phk);
} else {
pti = phk->ptiHooked;
UserAssert(pti != NULL);
}
}
if (fGlobal) {
if ((phk != NULL) ^ IsGlobalHooked(pti, WHF_FROM_WH(nFilterType))) {
PHOOK phkTemp = pti->pDeskInfo->aphkStart[nFilterType + 1];
while ((phkTemp != NULL) && !(phkTemp->flags & HF_INCHECKWHF)) {
phkTemp = phkTemp->phkNext;
}
UserAssert(phkTemp != NULL);
}
} else {
if ((phk != NULL) ^ IsHooked(pti, WHF_FROM_WH(nFilterType))) {
PHOOK phkTemp = pti->aphkStart[nFilterType + 1];
while ((phkTemp != NULL) && !(phkTemp->flags & HF_INCHECKWHF)) {
phkTemp = phkTemp->phkNext;
}
if (phkTemp == NULL) {
phkTemp = pti->pDeskInfo->aphkStart[nFilterType + 1];
while ((phkTemp != NULL) && !(phkTemp->flags & HF_INCHECKWHF)) {
phkTemp = phkTemp->phkNext;
}
}
UserAssert(phkTemp != NULL);
}
}
}
/***************************************************************************\
* DbgValidateHooks
*
* This functions expects valid (not destroyed) and properly linked.
* History:
* 03-25-97 GerardoB Created
\***************************************************************************/
void DbgValidateHooks (PHOOK phk, int iType)
{
PHOOK *pphkStart, *pphkNext;
if (phk == NULL) {
return;
}
/*
* It shouldn't be destroyed
*/
UserAssert(!(phk->flags & (HF_DESTROYED | HF_FREED)));
/*
* Validate fsHooks
*/
DbgValidatefsHook(phk, iType, NULL, FALSE);
/*
* Validate this hook and get the beginning of the hook chain
*/
pphkStart = DbgValidateThisHook(phk, iType, phk->ptiHooked);
/*
* There must be at least one hook in the chain
*/
UserAssert(*pphkStart != NULL);
/*
* Validate the link.
* And while your're at it, validate all hooks!
*/
pphkNext = pphkStart;
while ((*pphkNext != phk) && (*pphkNext != NULL)) {
UserAssert(pphkStart == DbgValidateThisHook(*pphkNext, iType, phk->ptiHooked));
pphkNext = &(*pphkNext)->phkNext;
}
/*
* Verify that we found it.
*/
UserAssert(*pphkNext == phk);
/*
* Walk until the end of the chain
*/
while (*pphkNext != NULL) {
UserAssert(pphkStart == DbgValidateThisHook(*pphkNext, iType, phk->ptiHooked));
pphkNext = &(*pphkNext)->phkNext;
}
}
#else
#define DbgValidatefsHook(phk, nFilterType, pti, fGlobal)
#endif /* DBG */
/***************************************************************************\
* zzzJournalAttach
*
* This attaches/detaches threads to one input queue so input is synchronized.
* Journalling requires this.
*
* 12-10-92 ScottLu Created.
\***************************************************************************/
BOOL zzzJournalAttach(
PTHREADINFO pti,
BOOL fAttach)
{
PTHREADINFO ptiT;
PQ pq;
PLIST_ENTRY pHead, pEntry;
/*
* If we're attaching, calculate the pqAttach for all threads journalling.
* If we're unattaching, just call zzzReattachThreads() and it will calculate
* the non-journalling queues to attach to.
*/
if (fAttach) {
if ((pq = AllocQueue(pti, NULL)) == NULL)
return FALSE;
pHead = &pti->rpdesk->PtiList;
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
ptiT = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
/*
* This is the Q to attach to for all threads that will do
* journalling.
*/
if (!(ptiT->TIF_flags & (TIF_DONTJOURNALATTACH | TIF_INCLEANUP))) {
ptiT->pqAttach = pq;
ptiT->pqAttach->cThreads++;
}
}
}
return zzzReattachThreads(fAttach);
}
/***************************************************************************\
* InterQueueMsgCleanup
*
* Walk gpsmsList looking for inter queue messages with a hung receiver;
* if one is found and it's a message that would have been an async event or
* intra queue if not journalling, then it cleans it up.
*
* While Journalling most threads are attached to the same queue. This causes
* activation and input stuff to be synchronous; if a thread hangs or dies,
* any other thread sending a message to the hung/dead thread will be
* blocked for good.
* This is critical when the blocked thread is cssr; this can happen with
* console windows or when some one requests a hard error box, specially
* during window activation.
*
* This function must be called when all queues have been detached (unless previously attached),
* so we can take care of hung/dead receivers with pending SMSs.
*
* 03-28-96 GerardoB Created
\***************************************************************************/
void InterQueueMsgCleanup (DWORD dwTimeFromLastRead)
{
PSMS *ppsms;
PSMS psmsNext;
CheckCritIn();
/*
* Walk gpsmsList
*/
for (ppsms = &gpsmsList; *ppsms; ) {
psmsNext = (*ppsms)->psmsNext;
/*
* If this is an inter queue message
*/
if (((*ppsms)->ptiSender != NULL)
&& ((*ppsms)->ptiReceiver != NULL)
&& ((*ppsms)->ptiSender->pq != (*ppsms)->ptiReceiver->pq)) {
/*
* If the receiver has been hung for a while
*/
if (FHungApp ((*ppsms)->ptiReceiver, dwTimeFromLastRead)) {
switch ((*ppsms)->message) {
/*
* Activation messages
*/
case WM_NCACTIVATE:
case WM_ACTIVATEAPP:
case WM_ACTIVATE:
case WM_SETFOCUS:
case WM_KILLFOCUS:
case WM_QUERYNEWPALETTE:
/*
* Sent to spwndFocus, which now can be in a different queue
*/
case WM_INPUTLANGCHANGE:
RIPMSG3 (RIP_WARNING, "InterQueueMsgCleanup: ptiSender:%#p ptiReceiver:%#p message:%#lx",
(*ppsms)->ptiSender, (*ppsms)->ptiReceiver, (*ppsms)->message);
ReceiverDied(*ppsms, ppsms);
break;
} /* switch */
} /* If hung receiver */
} /* If inter queue message */
/*
* If the message was not unlinked, go to the next one.
*/
if (*ppsms != psmsNext)
ppsms = &(*ppsms)->psmsNext;
} /* for */
}
/***************************************************************************\
* zzzCancelJournalling
*
* Journalling is cancelled with control-escape is pressed, or when the desktop
* is switched.
*
* 01-27-93 ScottLu Created.
\***************************************************************************/
void zzzCancelJournalling(void)
{
PTHREADINFO ptiCancelJournal;
PHOOK phook;
PHOOK phookNext;
/*
* Mouse buttons sometimes get stuck down due to hardware glitches,
* usually due to input concentrator switchboxes or faulty serial
* mouse COM ports, so clear the global button state here just in case,
* otherwise we may not be able to change focus with the mouse.
* Also do this in Alt-Tab processing.
*/
#if DBG
if (gwMouseOwnerButton)
RIPMSG1(RIP_WARNING,
"gwMouseOwnerButton=%x, being cleared forcibly\n",
gwMouseOwnerButton);
#endif
gwMouseOwnerButton = 0;
/*
* Remove journal hooks. This'll cause threads to associate with
* different queues.
* DeferWinEventNotify() so we can traverse the phook list safely
*/
DeferWinEventNotify();
UserAssert(gptiRit->pDeskInfo == grpdeskRitInput->pDeskInfo);
phook = PhkFirstGlobalValid(gptiRit, WH_JOURNALPLAYBACK);
while (phook != NULL) {
ptiCancelJournal = phook->head.pti;
if (ptiCancelJournal != NULL) {
/*
* Let the thread that set the journal hook know this is happening.
*/
_PostThreadMessage(ptiCancelJournal, WM_CANCELJOURNAL, 0, 0);
/*
* If there was an app waiting for a response back from the journal
* application, cancel that request so the app can continue running
* (for example, we don't want winlogon or console to wait for an
* app that may be hung!)
*/
SendMsgCleanup(ptiCancelJournal);
}
phookNext = PhkNextValid(phook);
zzzUnhookWindowsHookEx(phook); // May free phook memory
phook = phookNext;
}
zzzEndDeferWinEventNotify();
/*
* DeferWinEventNotify() so we can traverse the phook list safely
*/
DeferWinEventNotify();
UserAssert(gptiRit->pDeskInfo == grpdeskRitInput->pDeskInfo);
phook = PhkFirstGlobalValid(gptiRit, WH_JOURNALRECORD);
while (phook != NULL) {
ptiCancelJournal = phook->head.pti;
if (ptiCancelJournal != NULL) {
/*
* Let the thread that set the journal hook know this is happening.
*/
_PostThreadMessage(ptiCancelJournal, WM_CANCELJOURNAL, 0, 0);
/*
* If there was an app waiting for a response back from the journal
* application, cancel that request so the app can continue running
* (for example, we don't want winlogon or console to wait for an
* app that may be hung!)
*/
SendMsgCleanup(ptiCancelJournal);
}
phookNext = PhkNextValid(phook);
zzzUnhookWindowsHookEx(phook); // May free phook memory
phook = phookNext;
}
zzzEndDeferWinEventNotify();
/*
* Make sure journalling ssync mode didn't hose any one
*/
InterQueueMsgCleanup(CMSWAITTOKILLTIMEOUT);
/*
* Unlock SetForegroundWindow (if locked)
*/
gppiLockSFW = NULL;
/*
* NT5's last minute hack for evil applications, who disables the desktop window
* (perhaps by accidents though) leaving the system pretty unusable.
* See Raid #423704.
*/
if (grpdeskRitInput && grpdeskRitInput->pDeskInfo) {
PWND pwndDesktop = grpdeskRitInput->pDeskInfo->spwnd;
if (pwndDesktop && TestWF(pwndDesktop, WFDISABLED)) {
ClrWF(pwndDesktop, WFDISABLED);
}
}
}
/***************************************************************************\
* zzzSetWindowsHookAW (API)
*
* This is the Win32 version of the SetWindowsHook() call. It has the
* same characteristics as far as return values, but only sets 'local'
* hooks. This is because we weren't provided a DLL we can load into
* other processes. Because of this WH_SYSMSGFILTER is no longer a
* valid hook. Apps will either need to call with WH_MSGFILTER or call
* the new API SetWindowsHookEx(). Essentially this API is obsolete and
* everyone should call SetWindowsHookEx().
*
* History:
* 10-Feb-1991 DavidPe Created.
* 30-Jan-1992 IanJa Added bAnsi parameter
\***************************************************************************/
PROC zzzSetWindowsHookAW(
int nFilterType,
PROC pfnFilterProc,
DWORD dwFlags)
{
PHOOK phk;
phk = zzzSetWindowsHookEx(NULL, NULL, PtiCurrent(),
nFilterType, pfnFilterProc, dwFlags);
/*
* If we get an error from zzzSetWindowsHookEx() then we return
* -1 to be compatible with older version of Windows.
*/
if (phk == NULL) {
return (PROC)-1;
}
/*
* Handle the backwards compatibility return value cases for
* SetWindowsHook. If this was the first hook in the chain,
* then return NULL, else return something non-zero. HKF_NZRET
* is a special case where SetWindowsHook would always return
* something because there was a default hook installed. Some
* apps relied on a non-zero return value in those cases.
*/
if ((phk->phkNext != NULL) || (abHookFlags[nFilterType + 1] & HKF_NZRET)) {
return (PROC)phk;
}
return NULL;
}
/***************************************************************************\
* zzzSetWindowsHookEx
*
* SetWindowsHookEx() is the updated version of SetWindowsHook(). It allows
* applications to set hooks on specific threads or throughout the entire
* system. The function returns a hook handle to the application if
* successful and NULL if a failure occured.
*
* History:
* 28-Jan-1991 DavidPe Created.
* 15-May-1991 ScottLu Changed to work client/server.
* 30-Jan-1992 IanJa Added bAnsi parameter
\***************************************************************************/
PHOOK zzzSetWindowsHookEx(
HANDLE hmod,
PUNICODE_STRING pstrLib,
PTHREADINFO ptiThread,
int nFilterType,
PROC pfnFilterProc,
DWORD dwFlags)
{
ACCESS_MASK amDesired;
PHOOK phkNew;
TL tlphkNew;
PHOOK *pphkStart;
PTHREADINFO ptiCurrent;
/*
* Check to see if filter type is valid.
*/
if (nFilterType < WH_MIN || nFilterType > WH_MAX) {
RIPERR1(ERROR_INVALID_HOOK_FILTER,
RIP_WARNING,
"Invalid hook type 0x%x",
nFilterType);
return NULL;
}
/*
* Check to see if filter proc is valid.
*/
if (pfnFilterProc == NULL) {
RIPERR0(ERROR_INVALID_FILTER_PROC,
RIP_WARNING,
"NULL hook callback");
return NULL;
}
ptiCurrent = PtiCurrent();
if (ptiThread == NULL) {
/*
* Is the app trying to set a global hook without a library?
* If so return an error.
*/
if (hmod == NULL) {
RIPERR0(ERROR_HOOK_NEEDS_HMOD,
RIP_WARNING,
"Global hook requires a non-NULL hmod");
return NULL;
}
} else {
/*
* Is the app trying to set a local hook that is global-only?
* If so return an error.
*/
if (!(abHookFlags[nFilterType + 1] & HKF_TASK)) {
RIPERR1(ERROR_GLOBAL_ONLY_HOOK,
RIP_WARNING,
"Hook type 0x%x must be global",
nFilterType);
return NULL;
}
/*
* Can't hook outside our own desktop.
*/
if (ptiThread->rpdesk != ptiCurrent->rpdesk) {
RIPERR0(ERROR_ACCESS_DENIED,
RIP_WARNING,
"Access denied to desktop in zzzSetWindowsHookEx - can't hook other desktops");
return NULL;
}
if (ptiCurrent->ppi != ptiThread->ppi) {
/*
* Is the app trying to set hook in another process without a library?
* If so return an error.
*/
if (hmod == NULL) {
RIPERR0(ERROR_HOOK_NEEDS_HMOD,
RIP_WARNING,
"Cross-thread hook needs a non-NULL hmod");
return NULL;
}
/*
* Is the app hooking another user without access?
* If so return an error. Note that this check is done
* for global hooks every time the hook is called.
*/
if ((!RtlEqualLuid(&ptiThread->ppi->luidSession,
&ptiCurrent->ppi->luidSession)) &&
!(ptiThread->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) {
RIPERR0(ERROR_ACCESS_DENIED,
RIP_WARNING,
"Access denied to other user in zzzSetWindowsHookEx");
return NULL;
}
if ((ptiThread->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)) &&
!(abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) {
/*
* Can't hook console or GUI system thread if inter-thread
* calling isn't implemented for this hook type.
*/
RIPERR1(ERROR_HOOK_TYPE_NOT_ALLOWED,
RIP_WARNING,
"nFilterType (%ld) not allowed in zzzSetWindowsHookEx",
nFilterType);
return NULL;
}
}
}
/*
* Check if this thread has access to hook its desktop.
*/
switch( nFilterType ) {
case WH_JOURNALRECORD:
amDesired = DESKTOP_JOURNALRECORD;
break;
case WH_JOURNALPLAYBACK:
amDesired = DESKTOP_JOURNALPLAYBACK;
break;
default:
amDesired = DESKTOP_HOOKCONTROL;
break;
}
/*
* The console input thread *must* be allowed to set this hook, even if
* the account it's impersonating doesn't have hook access to the desktop
* in question.
*/
if (!RtlAreAllAccessesGranted(ptiCurrent->amdesk, amDesired) &&
!(ISCSRSS() && nFilterType == WH_MSGFILTER)) {
UserAssert(!ISCSRSS() ||
(PsGetCurrentThreadId() ==
UlongToHandle(ptiCurrent->rpdesk->dwConsoleThreadId)));
RIPERR0(ERROR_ACCESS_DENIED,
RIP_WARNING,
"Access denied to desktop in zzzSetWindowsHookEx");
return NULL;
}
if (amDesired != DESKTOP_HOOKCONTROL &&
(ptiCurrent->rpdesk->rpwinstaParent->dwWSF_Flags & WSF_NOIO)) {
RIPERR0(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION,
RIP_WARNING,
"Journal hooks invalid on a desktop belonging to a non-interactive WindowStation.");
return NULL;
}
/*
* Allocate the new HOOK structure.
*/
phkNew = HMAllocObject(ptiCurrent, ptiCurrent->rpdesk, TYPE_HOOK, sizeof(HOOK));
if (phkNew == NULL) {
return NULL;
}
/*
* If a DLL is required for this hook, register the library with
* the library management routines so we can assure it's loaded
* into all the processes necessary.
*/
phkNew->ihmod = -1;
if (hmod != NULL) {
phkNew->ihmod = GetHmodTableIndex(pstrLib);
if (phkNew->ihmod == -1) {
RIPERR2(ERROR_MOD_NOT_FOUND,
RIP_WARNING,
"Couldn't load DLL %.*ws",
pstrLib->Length,
pstrLib->Buffer);
HMFreeObject((PVOID)phkNew);
return NULL;
}
/*
* Add a dependency on this module - meaning, increment a count
* that simply counts the number of hooks set into this module.
*/
if (phkNew->ihmod >= 0) {
AddHmodDependency(phkNew->ihmod);
}
}
/*
* Depending on whether we're setting a global or local hook,
* get the start of the appropriate linked-list of HOOKs. Also
* set the HF_GLOBAL flag if it's a global hook.
*/
if (ptiThread != NULL) {
pphkStart = &ptiThread->aphkStart[nFilterType + 1];
/*
* Set the WHF_* in the THREADINFO so we know it's hooked.
*/
ptiThread->fsHooks |= WHF_FROM_WH(nFilterType);
/*
* Set the flags in the thread's TEB
*/
if (ptiThread->pClientInfo) {
BOOL fAttached;
/*
* If the thread being hooked is in another process, attach
* to that process so that we can access its ClientInfo.
*/
if (ptiThread->ppi != ptiCurrent->ppi) {
KeAttachProcess(PsGetProcessPcb(ptiThread->ppi->Process));
fAttached = TRUE;
} else
fAttached = FALSE;
try {
ptiThread->pClientInfo->fsHooks = ptiThread->fsHooks;
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
/*
* Worst case scenario is the the cleint side will
* be out of sync of which hooks are installed
*/
}
if (fAttached) {
KeDetachProcess();
}
}
/*
* Remember which thread we're hooking.
*/
phkNew->ptiHooked = ptiThread;
} else {
pphkStart = &ptiCurrent->pDeskInfo->aphkStart[nFilterType + 1];
phkNew->flags |= HF_GLOBAL;
/*
* Set the WHF_* in the SERVERINFO so we know it's hooked.
*/
ptiCurrent->pDeskInfo->fsHooks |= WHF_FROM_WH(nFilterType);
phkNew->ptiHooked = NULL;
}
/*
* Does the hook function expect ANSI or Unicode text?
*/
phkNew->flags |= (dwFlags & HF_ANSI);
/*
* Initialize the HOOK structure. Unreferenced parameters are assumed
* to be initialized to zero by LocalAlloc().
*/
phkNew->iHook = nFilterType;
/*
* Libraries are loaded at different linear addresses in different
* process contexts. For this reason, we need to convert the filter
* proc address into an offset while setting the hook, and then convert
* it back to a real per-process function pointer when calling a
* hook. Do this by subtracting the 'hmod' (which is a pointer to the
* linear and contiguous .exe header) from the function index.
*/
phkNew->offPfn = ((ULONG_PTR)pfnFilterProc) - ((ULONG_PTR)hmod);
#ifdef HOOKBATCH
phkNew->cEventMessages = 0;
phkNew->iCurrentEvent = 0;
phkNew->CacheTimeOut = 0;
phkNew->aEventCache = NULL;
#endif //HOOKBATCH
/*
* Link this hook into the front of the hook-list.
*/
phkNew->phkNext = *pphkStart;
*pphkStart = phkNew;
/*
* If this is a journal hook, setup synchronized input processing
* AFTER we set the hook - so this synchronization can be cancelled
* with control-esc.
*/
if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
/*
* Attach everyone to us so journal-hook processing
* will be synchronized.
* No need to DeferWinEventNotify() here, since we lock phkNew.
*/
ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew);
if (!zzzJournalAttach(ptiCurrent, TRUE)) {
RIPMSG1(RIP_WARNING, "zzzJournalAttach failed, so abort hook %#p", phkNew);
if (ThreadUnlock(&tlphkNew) != NULL) {
zzzUnhookWindowsHookEx(phkNew);
}
return NULL;
}
if ((phkNew = ThreadUnlock(&tlphkNew)) == NULL) {
return NULL;
}
}
UserAssert(phkNew != NULL);
/*
* Later 5.0 GerardoB: The old code just to check this but
* I think it's some left over stuff from server side days.
.* Let's assert on it for a while
* Also, I added the assertions in the else's below because I reorganized
* the code and want to make sure we don't change behavior
*/
UserAssert(ptiCurrent->pEThread && PsGetThreadProcess(ptiCurrent->pEThread));
/*
* Can't allow a process that has set a global hook that works
* on server-side winprocs to run at background priority! Bump
* up it's dynamic priority and mark it so it doesn't get reset.
*/
if ((phkNew->flags & HF_GLOBAL) &&
(abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) {
ptiCurrent->TIF_flags |= TIF_GLOBALHOOKER;
KeSetPriorityThread(PsGetThreadTcb(ptiCurrent->pEThread), LOW_REALTIME_PRIORITY-2);
if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew);
/*
* If we're changing the journal hooks, jiggle the mouse.
* This way the first event will always be a mouse move, which
* will ensure that the cursor is set properly.
*/
zzzSetFMouseMoved();
phkNew = ThreadUnlock(&tlphkNew);
/*
* If setting a journal playback hook, this process is the input
* provider. This gives it the right to call SetForegroundWindow
*/
if (nFilterType == WH_JOURNALPLAYBACK) {
gppiInputProvider = ptiCurrent->ppi;
}
} else {
UserAssert(nFilterType != WH_JOURNALPLAYBACK);
}
} else {
UserAssert(!(abHookFlags[nFilterType + 1] & HKF_JOURNAL));
UserAssert(nFilterType != WH_JOURNALPLAYBACK);
}
/*
* Return pointer to our internal hook structure so we know
* which hook to call next in CallNextHookEx().
*/
DbgValidateHooks(phkNew, phkNew->iHook);
return phkNew;
}
/***************************************************************************\
* xxxCallNextHookEx
*
* In the new world DefHookProc() is a bit deceptive since SetWindowsHook()
* isn't returning the actual address of the next hook to call, but instead
* a hook handle. CallNextHookEx() is a slightly clearer picture of what's
* going on so apps don't get tempted to try and call the value we return.
*
* As a side note we don't actually use the hook handle passed in. We keep
* track of which hooks is currently being called on a thread in the Q
* structure and use that. This is because SetWindowsHook() will sometimes
* return NULL to be compatible with the way it used to work, but even though
* we may be dealing with the last 'local' hook, there may be further 'global'
* hooks we need to call. PhkNext() is smart enough to jump over to the
* 'global' hook chain if it reaches the end of the 'local' hook chain.
*
* History:
* 01-30-91 DavidPe Created.
\***************************************************************************/
LRESULT xxxCallNextHookEx(
int nCode,
WPARAM wParam,
LPARAM lParam)
{
BOOL bAnsiHook;
if (PtiCurrent()->sphkCurrent == NULL) {
return 0;
}
return xxxCallHook2(PhkNextValid(PtiCurrent()->sphkCurrent), nCode, wParam, lParam, &bAnsiHook);
}
/***************************************************************************\
* CheckWHFBits
*
* This routine checks to see if any hooks for nFilterType exist, and clear
* the appropriate WHF_ in the THREADINFO and SERVERINFO.
*
* History:
* 08-17-92 DavidPe Created.
\***************************************************************************/
VOID CheckWHFBits(
PTHREADINFO pti,
int nFilterType)
{
BOOL fClearThreadBits;
BOOL fClearDesktopBits;
PHOOK phook;
/*
* Assume we're are going to clear local(thread) and
* global(desktop) bits.
*/
fClearThreadBits = TRUE;
fClearDesktopBits = TRUE;
/*
* Get the first valid hook for this thread
*/
phook = PhkFirstValid(pti, nFilterType);
if (phook != NULL) {
/*
* If it found a global hook, don't clear the desktop bits
* (that would mean that there are no local(thread) hooks
* so we fall through to clear the thread bits)
*/
if (phook->flags & HF_GLOBAL) {
fClearDesktopBits = FALSE;
} else {
/*
* It found a thread hook so don't clear the thread bits
*/
fClearThreadBits = FALSE;
/*
* Check for global hooks now. If there is one, don't
* clear the desktop bits
*/
phook = PhkFirstGlobalValid(pti, nFilterType);
fClearDesktopBits = (phook == NULL);
}
} /* if (phook != NULL) */
if (fClearThreadBits) {
pti->fsHooks &= ~(WHF_FROM_WH(nFilterType));
/*
* Set the flags in the thread's TEB
*/
if (pti->pClientInfo) {
BOOL fAttached;
/*
* If the hooked thread is in another process, attach
* to that process to access its address space.
*/
if (pti->ppi != PpiCurrent()) {
KeAttachProcess(PsGetProcessPcb(pti->ppi->Process));
fAttached = TRUE;
} else
fAttached = FALSE;
try {
pti->pClientInfo->fsHooks = pti->fsHooks;
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
/*
* Worst case scenario is the the cleint side will
* be out of sync of which hooks are installed
*/
}
if (fAttached)
KeDetachProcess();
}
}
if (fClearDesktopBits) {
pti->pDeskInfo->fsHooks &= ~(WHF_FROM_WH(nFilterType));
}
}
/***************************************************************************\
* zzzUnhookWindowsHook (API)
*
* This is the old version of the Unhook API. It does the same thing as
* zzzUnhookWindowsHookEx(), but takes a filter-type and filter-proc to
* identify which hook to unhook.
*
* History:
* 01-28-91 DavidPe Created.
\***************************************************************************/
BOOL zzzUnhookWindowsHook(
int nFilterType,
PROC pfnFilterProc)
{
PHOOK phk;
PTHREADINFO ptiCurrent;
if ((nFilterType < WH_MIN) || (nFilterType > WH_MAX)) {
RIPERR0(ERROR_INVALID_HOOK_FILTER, RIP_VERBOSE, "");
return FALSE;
}
ptiCurrent = PtiCurrent();
for (phk = PhkFirstValid(ptiCurrent, nFilterType); phk != NULL; phk = PhkNextValid(phk)) {
/*
* Is this the hook we're looking for?
*/
if (PFNHOOK(phk) == pfnFilterProc) {
/*
* Are we on the thread that set the hook?
* If not return an error.
*/
if (GETPTI(phk) != ptiCurrent) {
RIPERR0(ERROR_ACCESS_DENIED,
RIP_WARNING,
"Access denied in zzzUnhookWindowsHook: "
"this thread is not the same as that which set the hook");
return FALSE;
}
return zzzUnhookWindowsHookEx( phk );
}
}
/*
* Didn't find the hook we were looking for so return FALSE.
*/
RIPERR0(ERROR_HOOK_NOT_INSTALLED, RIP_VERBOSE, "");
return FALSE;
}
/***************************************************************************\
* zzzUnhookWindowsHookEx (API)
*
* Applications call this API to 'unhook' a hook. First we check if someone
* is currently calling this hook. If no one is we go ahead and free the
* HOOK structure now. If someone is then we simply clear the filter-proc
* in the HOOK structure. In xxxCallHook2() we check for this and if by
* that time no one is calling the hook in question we free it there.
*
* History:
* 01-28-91 DavidPe Created.
\***************************************************************************/
BOOL zzzUnhookWindowsHookEx(
PHOOK phkFree)
{
PTHREADINFO pti;
pti = GETPTI(phkFree);
/*
* If this hook is already destroyed, bail
*/
if (phkFree->flags & HF_DESTROYED) {
RIPMSG1(RIP_WARNING, "_UnhookWindowsHookEx(%#p) already destroyed", phkFree);
return FALSE;
}
/*
* Clear the journaling flags in all the queues.
*/
if (abHookFlags[phkFree->iHook + 1] & HKF_JOURNAL) {
zzzJournalAttach(pti, FALSE);
/*
* If someone got stuck because of the hook, let him go
*
* I want to get some performance numbers before checking this in.
* MSTest hooks and unhooks all the time when running a script.
* This code has never been in. 5/22/96. GerardoB
*/
// InterQueueMsgCleanup(3 * CMSWAITTOKILLTIMEOUT);
}
/*
* If no one is currently calling this hook,
* go ahead and free it now.
*/
FreeHook(phkFree);
/*
* If this thread has no more global hooks that are able to hook
* server-side window procs, we must clear it's TIF_GLOBALHOOKER bit.
*/
if (pti->TIF_flags & TIF_GLOBALHOOKER) {
int iHook;
PHOOK phk;
for (iHook = WH_MIN ; iHook <= WH_MAX ; ++iHook) {
/*
* Ignore those that can't hook server-side winprocs
*/
if (!(abHookFlags[iHook + 1] & HKF_INTERSENDABLE)) {
continue;
}
/*
* Scan the global hooks
*/
for (phk = PhkFirstGlobalValid(pti, iHook);
phk != NULL; phk = PhkNextValid(phk)) {
if (GETPTI(phk) == pti) {
goto StillHasGlobalHooks;
}
}
}
pti->TIF_flags &= ~TIF_GLOBALHOOKER;
}
StillHasGlobalHooks:
/*
* Success, return TRUE.
*/
return TRUE;
}
/***************************************************************************\
* _CallMsgFilter (API)
*
* CallMsgFilter() allows applications to call the WH_*MSGFILTER hooks.
* If there's a sysmodal window we return FALSE right away. WH_MSGFILTER
* isn't called if WH_SYSMSGFILTER returned non-zero.
*
* History:
* 01-29-91 DavidPe Created.
\***************************************************************************/
BOOL _CallMsgFilter(
LPMSG pmsg,
int nCode)
{
PTHREADINFO pti;
pti = PtiCurrent();
/*
* First call WH_SYSMSGFILTER. If it returns non-zero, don't
* bother calling WH_MSGFILTER, just return TRUE. Otherwise
* return what WH_MSGFILTER gives us.
*/
if (IsHooked(pti, WHF_SYSMSGFILTER) && xxxCallHook(nCode, 0, (LPARAM)pmsg,
WH_SYSMSGFILTER)) {
return TRUE;
}
if (IsHooked(pti, WHF_MSGFILTER)) {
return (BOOL)xxxCallHook(nCode, 0, (LPARAM)pmsg, WH_MSGFILTER);
}
return FALSE;
}
/***************************************************************************\
* xxxCallHook
*
* User code calls this function to call the first hook of a specific
* type.
*
* History:
* 01-29-91 DavidPe Created.
\***************************************************************************/
int xxxCallHook(
int nCode,
WPARAM wParam,
LPARAM lParam,
int iHook)
{
BOOL bAnsiHook;
return (int)xxxCallHook2(PhkFirstValid(PtiCurrent(), iHook), nCode, wParam, lParam, &bAnsiHook);
}
/***************************************************************************\
* xxxCallHook2
*
* When you have an actual HOOK structure to call, you'd use this function.
* It will check to see if the hook hasn't already been unhooked, and if
* is it will free it and keep looking until it finds a hook it can call
* or hits the end of the list. We also make sure any needed DLLs are loaded
* here. We also check to see if the HOOK was unhooked inside the call
* after we return.
*
* Note: Hooking server-side window procedures (such as the desktop and console
* windows) can only be done by sending the hook message to the hooking app.
* (This is because we must not load the hookproc DLL into the server process).
* The hook types this can be done with are currently WH_JOURNALRECORD,
* WH_JOURNALPLAYBACK, WH_KEYBOARD and WH_MOUSE : these are all marked as
* HKF_INTERSENDABLE. In order to prevent a global hooker from locking up the whole
* system, the hook message is sent with a timeout. To ensure minimal
* performance degradation, the hooker process is set to foreground priority,
* and prevented from being set back to background priority with the
* TIF_GLOBALHOOKER bit in hooking thread's pti->flags.
* Hooking emulated DOS apps is prevented with the TIF_DOSEMULATOR bit in the
* console thread: this is because these apps typically hog the CPU so much that
* the hooking app does not respond rapidly enough to the hook messsages sent
* to it. IanJa Nov 1994.
*
* History:
* 02-07-91 DavidPe Created.
* 1994 Nov 02 IanJa Hooking desktop and console windows.
\***************************************************************************/
LRESULT xxxCallHook2(
PHOOK phkCall,
int nCode,
WPARAM wParam,
LPARAM lParam,
LPBOOL lpbAnsiHook)
{
UINT iHook;
PHOOK phkSave;
LONG_PTR nRet;
PTHREADINFO ptiCurrent;
BOOL fLoadSuccess;
TL tlphkCall;
TL tlphkSave;
BYTE bHookFlags;
BOOL fMustIntersend;
CheckCritIn();
if (phkCall == NULL) {
return 0;
}
iHook = phkCall->iHook;
ptiCurrent = PtiCurrent();
/*
* Only low level hooks are allowed in the RIT context
* (This check used to be done in PhkFirstValid).
*/
if (ptiCurrent == gptiRit) {
switch (iHook) {
case WH_MOUSE_LL:
case WH_KEYBOARD_LL:
#ifdef REDIRECTION
case WH_HITTEST:
#endif // REDIRECTION
break;
default:
return 0;
}
}
/*
* If this queue is in cleanup, exit: it has no business calling back
* a hook proc. Also check if hooks are disabled for the thread.
*/
if ( ptiCurrent->TIF_flags & (TIF_INCLEANUP | TIF_DISABLEHOOKS) ||
((ptiCurrent->rpdesk == NULL) && (phkCall->iHook != WH_MOUSE_LL))) {
return ampiHookError[iHook + 1];
}
/*
* Try to call each hook in the list until one is successful or
* we reach the end of the list.
*/
do {
*lpbAnsiHook = phkCall->flags & HF_ANSI;
bHookFlags = abHookFlags[phkCall->iHook + 1];
/*
* Some WH_SHELL hook types can be called from console
* HSHELL_APPCOMMAND added for bug 346575 DefWindowProc invokes a shell hook
* for console windows if they don't handle the wm_appcommand message - we need the hook
* to go through for csrss.
*/
if ((phkCall->iHook == WH_SHELL) && (ptiCurrent->TIF_flags & TIF_CSRSSTHREAD)) {
if ((nCode == HSHELL_LANGUAGE) || (nCode == HSHELL_WINDOWACTIVATED) ||
(nCode == HSHELL_APPCOMMAND)) {
bHookFlags |= HKF_INTERSENDABLE;
}
}
if ((phkCall->iHook == WH_SHELL) && (ptiCurrent->TIF_flags & TIF_SYSTEMTHREAD)) {
if ((nCode == HSHELL_ACCESSIBILITYSTATE) ) {
bHookFlags |= HKF_INTERSENDABLE;
}
}
fMustIntersend =
(GETPTI(phkCall) != ptiCurrent) &&
(
/*
* We always want to intersend journal hooks.
* CONSIDER (adams): Why? There's a performance hit by
* doing so, so if we haven't a reason, we shouldn't
* do it.
*
* we also need to intersend low level hooks. They can be called
* from the desktop thread, the raw input thread AND also from
* any thread that calls CallNextHookEx.
*/
(bHookFlags & (HKF_JOURNAL | HKF_LOWLEVEL))
/*
* We must intersend if a 16bit app hooks a 32bit app
* because we can't load a 16bit dll into a 32bit process.
* We must also intersend if a 16bit app hooks another 16bit app
* in a different VDM, because we can't load a 16bit dll from
* one VDM into a 16bit app in another VDM (because that
* VDM is actually a 32bit process).
*/
||
( GETPTI(phkCall)->TIF_flags & TIF_16BIT &&
( !(ptiCurrent->TIF_flags & TIF_16BIT) ||
ptiCurrent->ppi != GETPTI(phkCall)->ppi))
#if defined(_WIN64)
/*
* Intersend if a 64bit app hooks a 32bit app or
* a 32bit app hooks a 64bit app.
* This is necessary since a hook DLL can not be loaded
* cross bit type.
*/
||
( (GETPTI(phkCall)->TIF_flags & TIF_WOW64) !=
(ptiCurrent->TIF_flags & TIF_WOW64)
)
#endif /* defined(_WIN64) */
/*
* We must intersend if a console or system thread is calling a hook
* that is not in the same console or the system process.
*/
||
( ptiCurrent->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD) &&
GETPTI(phkCall)->ppi != ptiCurrent->ppi)
/*
* If this is a global and non-journal hook, do a security
* check on the current desktop to see if we can call here.
* Note that we allow processes with the SYSTEM_LUID to hook
* other processes even if the other process says that it
* doesn't allow other accounts to hook them. We did this
* because there was a bug in NT 3.x that allowed it and some
* services were written to use it.
*/
||
( phkCall->flags & HF_GLOBAL &&
!RtlEqualLuid(&GETPTI(phkCall)->ppi->luidSession, &ptiCurrent->ppi->luidSession) &&
!(ptiCurrent->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK) &&
!RtlEqualLuid(&GETPTI(phkCall)->ppi->luidSession, &luidSystem))
/*
* We must intersend if the hooking thread is running in
* another process and is restricted.
*/
||
( GETPTI(phkCall)->ppi != ptiCurrent->ppi &&
IsRestricted(GETPTI(phkCall)->pEThread))
);
/*
* We're calling back... make sure the hook doesn't go away while
* we're calling back. We've thread locked here: we must unlock before
* returning or enumerating the next hook in the chain.
*/
ThreadLockAlwaysWithPti(ptiCurrent, phkCall, &tlphkCall);
if (!fMustIntersend) {
/*
* Make sure the DLL for this hook, if any, has been loaded
* for the current process.
*/
if ((phkCall->ihmod != -1) &&
(TESTHMODLOADED(ptiCurrent, phkCall->ihmod) == 0)) {
/*
* Try loading the library, since it isn't loaded in this processes
* context. First lock this hook so it doesn't go away while we're
* loading this library.
*/
fLoadSuccess = (xxxLoadHmodIndex(phkCall->ihmod) != NULL);
/*
* If the LoadLibrary() failed, skip to the next hook and try
* again.
*/
if (!fLoadSuccess) {
goto LoopAgain;
}
}
/*
* Is WH_DEBUG installed? If we're not already calling it, do so.
*/
if (IsHooked(ptiCurrent, WHF_DEBUG) && (phkCall->iHook != WH_DEBUG)) {
DEBUGHOOKINFO debug;
debug.idThread = TIDq(ptiCurrent);
debug.idThreadInstaller = 0;
debug.code = nCode;
debug.wParam = wParam;
debug.lParam = lParam;
if (xxxCallHook(HC_ACTION, phkCall->iHook, (LPARAM)&debug, WH_DEBUG)) {
/*
* If WH_DEBUG returned non-zero, skip this hook and
* try the next one.
*/
goto LoopAgain;
}
}
/*
* Make sure the hook is still around before we
* try and call it.
*/
if (HMIsMarkDestroy(phkCall)) {
goto LoopAgain;
}
/*
* Time to call the hook! Lock it first so that it doesn't go away
* while we're using it. Thread lock right away in case the lock frees
* the previous contents.
*/
#if DBG
if (phkCall->flags & HF_GLOBAL) {
UserAssert(phkCall->ptiHooked == NULL);
} else {
UserAssert(phkCall->ptiHooked == ptiCurrent);
}
#endif
phkSave = ptiCurrent->sphkCurrent;
ThreadLockWithPti(ptiCurrent, phkSave, &tlphkSave);
if (ptiCurrent->pClientInfo) {
try {
ptiCurrent->pClientInfo->phkCurrent = phkCall;
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
nRet = 0;
goto Error;
}
}
Lock(&ptiCurrent->sphkCurrent, phkCall);
nRet = xxxHkCallHook(phkCall, nCode, wParam, lParam);
Lock(&ptiCurrent->sphkCurrent, phkSave);
if (ptiCurrent->pClientInfo) {
try {
ptiCurrent->pClientInfo->phkCurrent = phkSave;
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
nRet = 0;
goto Error;
}
}
ThreadUnlock(&tlphkSave);
/*
* This hook proc faulted, so unhook it and try the next one.
*/
if (phkCall->flags & HF_HOOKFAULTED) {
PHOOK phkFault;
phkCall = PhkNextValid(phkCall);
phkFault = ThreadUnlock(&tlphkCall);
if (phkFault != NULL) {
FreeHook(phkFault);
}
continue;
}
/*
* Lastly, we're done with this hook so it is ok to unlock it (it may
* get freed here!
*/
ThreadUnlock(&tlphkCall);
return nRet;
} else if (bHookFlags & HKF_INTERSENDABLE) {
/*
* Receiving thread can access this structure since the
* sender thread's stack is locked down during xxxInterSendMsgEx
*/
HOOKMSGSTRUCT hkmp;
int timeout = 200; // 1/5 second !!!
hkmp.lParam = lParam;
hkmp.phk = phkCall;
hkmp.nCode = nCode;
/*
* Thread lock right away in case the lock frees the previous contents
*/
phkSave = ptiCurrent->sphkCurrent;
ThreadLockWithPti(ptiCurrent, phkSave, &tlphkSave);
Lock(&ptiCurrent->sphkCurrent, phkCall);
if (ptiCurrent->pClientInfo) {
try {
ptiCurrent->pClientInfo->phkCurrent = phkCall;
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
nRet = 0;
goto Error;
}
}
/*
* Make sure we don't get hung!
*/
if (bHookFlags & HKF_LOWLEVEL) {
timeout = gnllHooksTimeout;
if (phkCall->fLastHookHung) {
/*
* WindowsBug: 307738
* Ever Quest hooks the Low Level hook.
* If the timeout occurred in the last hook
* callback, let's make the timeout shorter
* so that the RIT is not blocked by that.
*/
TAGMSG1(DBGTAG_KBD, "xxxCallHook2: LL Hook target pti=%p is marked as hung, adjusting timeout to 20", GETPTI(phkCall));
timeout = 20;
}
}
/*
* CONSIDER(adams): Why should a journaling hook be allowed to
* hang the console or a system thread? Will that interfere with
* the user's ability to cancel journaling through Ctrl+Esc?
*/
if (((bHookFlags & HKF_LOWLEVEL) == 0) &&
( (bHookFlags & HKF_JOURNAL) ||
!(ptiCurrent->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)))) {
nRet = xxxInterSendMsgEx(NULL, WM_HOOKMSG, wParam,
(LPARAM)&hkmp, ptiCurrent, GETPTI(phkCall), NULL);
} else {
/*
* We are a server thread (console/desktop) and we aren't
* journalling, so we can't allow the hookproc to hang us -
* we must use a timeout.
*/
INTRSENDMSGEX ism;
ism.fuCall = ISM_TIMEOUT;
ism.fuSend = SMTO_ABORTIFHUNG | SMTO_NORMAL;
ism.uTimeout = timeout;
ism.lpdwResult = &nRet;
/*
* Don't hook DOS apps connected to the emulator - they often
* grab too much CPU for the callback to the hookproc to
* complete in a timely fashion, causing poor response.
*/
if ((ptiCurrent->TIF_flags & TIF_DOSEMULATOR) ||
FHungApp(GETPTI(phkCall), CMSHUNGAPPTIMEOUT) ||
!xxxInterSendMsgEx(NULL, WM_HOOKMSG, wParam,
(LPARAM)&hkmp, ptiCurrent, GETPTI(phkCall), &ism)) {
nRet = ampiHookError[iHook + 1];
}
/*
* If the low-level hook is eaten, the app may wake up from
* MsgWaitForMultipleObjects, clear the wake mask, but not get
* anything in GetMessage / PeekMessage and we will think it's
* hung. This causes problems in DirectInput because then the
* app may miss some hooks if FHungApp returns true, see bug
* NTBug 430342 for more details on this.
*/
if ((bHookFlags & HKF_LOWLEVEL) && nRet) {
SET_TIME_LAST_READ(GETPTI(phkCall));
}
}
Lock(&ptiCurrent->sphkCurrent, phkSave);
if (ptiCurrent->pClientInfo) {
try {
ptiCurrent->pClientInfo->phkCurrent = phkSave;
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
nRet = 0;
}
}
Error:
ThreadUnlock(&tlphkSave);
ThreadUnlock(&tlphkCall);
return nRet;
}
// fall-through
LoopAgain:
phkCall = PhkNextValid(phkCall);
ThreadUnlock(&tlphkCall);
} while (phkCall != NULL);
return ampiHookError[iHook + 1];
}
/***************************************************************************\
* xxxCallMouseHook
*
* This is a helper routine that packages up a MOUSEHOOKSTRUCTEX and calls
* the WH_MOUSE hook.
*
* History:
* 02-09-91 DavidPe Created.
\***************************************************************************/
BOOL xxxCallMouseHook(
UINT message,
PMOUSEHOOKSTRUCTEX pmhs,
BOOL fRemove)
{
BOOL bAnsiHook;
/*
* Call the mouse hook.
*/
if (xxxCallHook2(PhkFirstValid(PtiCurrent(), WH_MOUSE), fRemove ?
HC_ACTION : HC_NOREMOVE, (DWORD)message, (LPARAM)pmhs, &bAnsiHook)) {
return TRUE;
}
return FALSE;
}
/***************************************************************************\
* xxxCallJournalRecordHook
*
* This is a helper routine that packages up an EVENTMSG and calls
* the WH_JOURNALRECORD hook.
*
* History:
* 02-28-91 DavidPe Created.
\***************************************************************************/
void xxxCallJournalRecordHook(
PQMSG pqmsg)
{
EVENTMSG emsg;
BOOL bAnsiHook;
/*
* Setup the EVENTMSG structure.
*/
emsg.message = pqmsg->msg.message;
emsg.time = pqmsg->msg.time;
if (RevalidateHwnd(pqmsg->msg.hwnd)) {
emsg.hwnd = pqmsg->msg.hwnd;
} else {
emsg.hwnd = NULL;
}
if ((emsg.message >= WM_MOUSEFIRST) && (emsg.message <= WM_MOUSELAST)) {
emsg.paramL = (UINT)pqmsg->msg.pt.x;
emsg.paramH = (UINT)pqmsg->msg.pt.y;
} else if ((emsg.message >= WM_KEYFIRST) && (emsg.message <= WM_KEYLAST)) {
BYTE bScanCode = LOBYTE(HIWORD(pqmsg->msg.lParam));
/*
* Build up a Win 3.1 compatible journal record key
* Win 3.1 ParamL 00 00 SC VK (SC=scan code VK=virtual key)
* Also set ParamH 00 00 00 SC to be compatible with our Playback
*
* If WM_*CHAR messages ever come this way we would have a problem
* because we would lose the top byte of the Unicode character. We'd
* We'd get ParamL 00 00 SC CH (SC=scan code, CH = low byte of WCHAR)
*
*/
if ((LOWORD(pqmsg->msg.wParam) == VK_PACKET) && (bScanCode == 0)) {
/*
* If we have an injected Unicode char (from SendInput), the
* character value was cached, let's give that to them too.
*/
emsg.paramL = (UINT)MAKELONG(pqmsg->msg.wParam, PtiCurrent()->wchInjected);
} else {
emsg.paramL = MAKELONG(MAKEWORD(pqmsg->msg.wParam, bScanCode),0);
}
emsg.paramH = bScanCode;
UserAssert((emsg.message != WM_CHAR) &&
(emsg.message != WM_DEADCHAR) &&
(emsg.message != WM_SYSCHAR) &&
(emsg.message != WM_SYSDEADCHAR));
/*
* Set extended-key bit.
*/
if (pqmsg->msg.lParam & 0x01000000) {
emsg.paramH |= 0x8000;
}
} else {
RIPMSG2(RIP_WARNING,
"Bad journal record message!\n"
" message = 0x%08lx\n"
" dwQEvent = 0x%08lx",
pqmsg->msg.message,
pqmsg->dwQEvent);
}
/*
* Call the journal recording hook.
*/
xxxCallHook2(PhkFirstGlobalValid(PtiCurrent(), WH_JOURNALRECORD), HC_ACTION, 0,
(LPARAM)&emsg, &bAnsiHook);
/*
* Write the MSG parameters back because the app may have modified it.
* AfterDark's screen saver password actually zero's out the keydown
* chars.
*
* If it was a mouse message patch up the mouse point. If it was a
* WM_KEYxxx message convert the Win 3.1 compatible journal record key
* back into a half backed WM_KEYxxx format. Only the VK and SC fields
* where initialized at this point.
*
* wParam 00 00 00 VK lParam 00 SC 00 00
*/
if ((pqmsg->msg.message >= WM_MOUSEFIRST) && (pqmsg->msg.message <= WM_MOUSELAST)) {
pqmsg->msg.pt.x = emsg.paramL;
pqmsg->msg.pt.y = emsg.paramH;
} else if ((pqmsg->msg.message >= WM_KEYFIRST) && (pqmsg->msg.message <= WM_KEYLAST)) {
(BYTE)pqmsg->msg.wParam = (BYTE)emsg.paramL;
((PBYTE)&pqmsg->msg.lParam)[2] = HIBYTE(LOWORD(emsg.paramL));
}
}
/***************************************************************************\
* xxxCallJournalPlaybackHook
*
*
* History:
* 03-01-91 DavidPe Created.
\***************************************************************************/
DWORD xxxCallJournalPlaybackHook(
PQMSG pqmsg)
{
EVENTMSG emsg;
LONG dt;
PWND pwnd;
WPARAM wParam;
LPARAM lParam;
POINT pt;
PTHREADINFO ptiCurrent;
BOOL bAnsiHook = FALSE;
PHOOK phkCall;
TL tlphkCall;
UserAssert(IsWinEventNotifyDeferredOK());
TryNextEvent:
/*
* Initialized to the current time for compatibility with
* <= 3.0.
*/
emsg.time = NtGetTickCount();
ptiCurrent = PtiCurrent();
pwnd = NULL;
phkCall = PhkFirstGlobalValid(ptiCurrent, WH_JOURNALPLAYBACK);
ThreadLockWithPti(ptiCurrent, phkCall, &tlphkCall);
dt = (DWORD)xxxCallHook2(phkCall, HC_GETNEXT, 0, (LPARAM)&emsg, &bAnsiHook);
/*
* -1 means some error occured. Return -1 for error.
*/
if (dt == 0xFFFFFFFF) {
ThreadUnlock(&tlphkCall);
return dt;
}
/*
* Update the message id. Need this if we decide to sleep.
*/
pqmsg->msg.message = emsg.message;
if (dt > 0) {
if (ptiCurrent->TIF_flags & TIF_IGNOREPLAYBACKDELAY) {
/*
* This flag tells us to ignore the requested delay (set in mnloop)
* We clear it to indicate that we did so.
*/
RIPMSG1(RIP_WARNING, "Journal Playback delay ignored (%lx)", emsg.message);
ptiCurrent->TIF_flags &= ~TIF_IGNOREPLAYBACKDELAY;
dt = 0;
} else {
ThreadUnlock(&tlphkCall);
return dt;
}
}
/*
* The app is ready to be asked for the next event
*/
if ((emsg.message >= WM_MOUSEFIRST) && (emsg.message <= WM_MOUSELAST)) {
pt.x = (int)emsg.paramL;
pt.y = (int)emsg.paramH;
lParam = MAKELONG(LOWORD(pt.x), LOWORD(pt.y));
wParam = 0;
/*
* If the message has changed the mouse position,
* update the cursor.
*/
if (pt.x != gpsi->ptCursor.x || pt.y != gpsi->ptCursor.y) {
zzzInternalSetCursorPos(pt.x, pt.y);
}
} else if ((emsg.message >= WM_KEYFIRST) && (emsg.message < WM_KEYLAST)) {
UINT wExtraStuff = 0;
if ((emsg.message == WM_KEYUP) || (emsg.message == WM_SYSKEYUP)) {
wExtraStuff |= 0x8000;
}
if ((emsg.message == WM_SYSKEYUP) || (emsg.message == WM_SYSKEYDOWN)) {
wExtraStuff |= 0x2000;
}
if (emsg.paramH & 0x8000) {
wExtraStuff |= 0x0100;
}
if (TestKeyStateDown(ptiCurrent->pq, (BYTE)emsg.paramL)) {
wExtraStuff |= 0x4000;
}
lParam = MAKELONG(1, (UINT)((emsg.paramH & 0xFF) | wExtraStuff));
if ((LOWORD(emsg.paramL) == VK_PACKET) && (LOBYTE(emsg.paramH) == 0)) {
/*
* We are playing back an injected Unicode char (see SendInput)
* save the character for TranslateMessage to pick up.
*/
ptiCurrent->wchInjected = HIWORD(emsg.paramL);
} else {
/*
* Raid# 65331
* WM_KEY* and WM_SYSKEY* messages should only contain 8bit Virtual Keys.
* Some applications passes scan code in HIBYTE and could mess up
* the system. E.g. Tab Keydown, paramL: 0x0f09 where 0f is scan code
*/
DWORD dwMask = 0xff;
/*
* There are old ANSI apps that only fill in the byte for when
* they generate journal playback so we used to strip everything
* else off. That however breaks unicode journalling; 22645
* (Yes, some apps apparently do Playback WM_*CHAR msgs!)
*
*/
if (!bAnsiHook || IS_DBCS_ENABLED()) {
if (IS_CHAR_MSG(emsg.message)) {
RIPMSG1(RIP_VERBOSE, "Unusual char message(%x) passed through JournalPlayback.", emsg.message);
/*
* Don't mask off HIBYTE(LOWORD(paramL)) for DBCS and UNICODE.
*/
dwMask = 0xffff;
}
}
wParam = emsg.paramL & dwMask;
}
} else if (emsg.message == WM_QUEUESYNC) {
if (emsg.paramL == 0) {
pwnd = ptiCurrent->pq->spwndActive;
} else {
if ((pwnd = RevalidateHwnd((HWND)IntToPtr( emsg.paramL ))) == NULL)
pwnd = ptiCurrent->pq->spwndActive;
}
} else {
/*
* This event doesn't match up with what we're looking
* for. If the hook is still valid, then skip this message
* and try the next.
*/
if (phkCall == NULL || phkCall->offPfn == 0L) {
/* Hook is nolonger valid, return -1 */
ThreadUnlock(&tlphkCall);
return 0xFFFFFFFF;
}
RIPMSG1(RIP_WARNING,
"Bad journal playback message=0x%08lx",
emsg.message);
xxxCallHook(HC_SKIP, 0, 0, WH_JOURNALPLAYBACK);
ThreadUnlock(&tlphkCall);
goto TryNextEvent;
}
StoreQMessage(pqmsg, pwnd, emsg.message, wParam, lParam, 0, 0, 0);
ThreadUnlock(&tlphkCall);
return 0;
}
/***************************************************************************\
* FreeHook
*
* Free hook unlinks the HOOK structure from its hook-list and removes
* any hmod dependencies on this hook. It also frees the HOOK structure.
*
* History:
* 01-31-91 DavidPe Created.
\***************************************************************************/
VOID FreeHook(
PHOOK phkFree)
{
/*
* Paranoia...
*/
UserAssert(!(phkFree->flags & HF_FREED));
/*
* If we came from zzzUnhookWindowsHookEx, journalling hooks have
* already been cleaned up. Otherwise, they'll get cleaned up in
* xxxInternalGetMessage through the gpdeskRecalcQueueAttach mechanism.
*/
/*
* Clear fsHooks bits the first time around (and mark it as destroyed).
*/
if (!(phkFree->flags & HF_DESTROYED)) {
DbgValidateHooks (phkFree, phkFree->iHook);
phkFree->flags |= HF_DESTROYED;
/*
* This hook has been marked as destroyed so CheckWHSBits
* won't take it into account when updating the fsHooks bits.
* However, this means that right at this moment fsHooks is
* out of sync. So we need a flag to make the assertion
* happy.
*/
#if DBG
phkFree->flags |= HF_INCHECKWHF;
#endif
UserAssert((phkFree->ptiHooked != NULL) || (phkFree->flags & HF_GLOBAL));
CheckWHFBits(phkFree->ptiHooked != NULL
? phkFree->ptiHooked
: GETPTI(phkFree),
phkFree->iHook);
#if DBG
phkFree->flags &= ~HF_INCHECKWHF;
#endif
}
/*
* Mark it for destruction. If it the object is locked it can't
* be freed right now.
*/
if (!HMMarkObjectDestroy((PVOID)phkFree)) {
return;
}
/*
* We're going to free this hook so get it off the list.
*/
UnlinkHook(phkFree);
/*
* Now remove the hmod dependency and free the
* HOOK structure.
*/
if (phkFree->ihmod >= 0) {
RemoveHmodDependency(phkFree->ihmod);
}
#ifdef HOOKBATCH
/*
* Free the cached Events
*/
if (phkFree->aEventCache) {
UserFreePool(phkFree->aEventCache);
phkFree->aEventCache = NULL;
}
#endif //HOOKBATCH
#if DBG
phkFree->flags |= HF_FREED;
#endif
HMFreeObject((PVOID)phkFree);
return;
}
/***************************************************************************\
* UnlinkHook
*
* Gets a hook out of its chain. Note that FreeThreadsWindowHooks unlinks
* some hooks but don't free them. So this function doesn't assume that
* the hook is going away.
*
* History:
* 04-25-97 GerardoB Added Header
\***************************************************************************/
void UnlinkHook(
PHOOK phkFree)
{
PHOOK *pphkNext;
PTHREADINFO ptiT;
CheckCritIn();
/*
* Since we have the HOOK structure, we can tell if this a global
* or local hook and start on the right list.
*/
if (phkFree->flags & HF_GLOBAL) {
pphkNext = &GETPTI(phkFree)->pDeskInfo->aphkStart[phkFree->iHook + 1];
} else {
ptiT = phkFree->ptiHooked;
if (ptiT == NULL) {
/*
* Already unlinked (by FreeThreadsWindowHooks)
*/
return;
} else {
/*
* Clear ptiHooked so we won't try to unlink it again.
*/
phkFree->ptiHooked = NULL;
}
pphkNext = &(ptiT->aphkStart[phkFree->iHook + 1]);
/*
* There must be at least one hook in the chain
*/
UserAssert(*pphkNext != NULL);
}
/*
* Find the address of the phkNext pointing to phkFree
*/
while ((*pphkNext != phkFree) && (*pphkNext != NULL)) {
pphkNext = &(*pphkNext)->phkNext;
}
/*
* If we haven't found it, it must be global hook whose owner is gone or
* has switched desktops.
*/
if (*pphkNext == NULL) {
UserAssert(phkFree->flags & HF_GLOBAL);
/*
* if we saved a pdesk, use it. Else use the one we allocated it from
*/
if (phkFree->rpdesk != NULL) {
UserAssert(GETPTI(phkFree) == gptiRit);
UserAssert(phkFree->rpdesk != NULL);
UserAssert(phkFree->rpdesk->pDeskInfo != gptiRit->pDeskInfo);
pphkNext = &phkFree->rpdesk->pDeskInfo->aphkStart[phkFree->iHook + 1];
} else {
UserAssert(GETPTI(phkFree)->pDeskInfo != phkFree->head.rpdesk->pDeskInfo);
pphkNext = &phkFree->head.rpdesk->pDeskInfo->aphkStart[phkFree->iHook + 1];
}
UserAssert(*pphkNext != NULL);
while ((*pphkNext != phkFree) && (*pphkNext != NULL)) {
pphkNext = &(*pphkNext)->phkNext;
}
}
/*
* We're supposed to find it
*/
UserAssert(*pphkNext == phkFree);
/*
* Unlink it
*/
*pphkNext = phkFree->phkNext;
phkFree->phkNext = NULL;
/*
* If we had a desktop, unlock it
*/
if (phkFree->rpdesk != NULL) {
UserAssert(phkFree->flags & HF_GLOBAL);
UserAssert(GETPTI(phkFree) == gptiRit);
UnlockDesktop(&phkFree->rpdesk, LDU_HOOK_DESK, 0);
}
}
/***************************************************************************\
* PhkFirstGlobalValid
*
* Returns the first not-destroyed hook on the given desktop info.
*
* History:
* 03/24/97 GerardoB Created
\***************************************************************************/
PHOOK PhkFirstGlobalValid(PTHREADINFO pti, int nFilterType)
{
PHOOK phk;
CheckCritIn();
phk = pti->pDeskInfo->aphkStart[nFilterType + 1];
/*
* Return the first hook that it's not destroyed (i.e, the
* first valid one).
*/
if ((phk != NULL) && (phk->flags & HF_DESTROYED)) {
phk = PhkNextValid(phk);
}
/*
* Good place to check fsHooks. If the bits are out of sync,
* someone must be adjusting them.
*/
DbgValidatefsHook(phk, nFilterType, pti, TRUE);
DbgValidateHooks(phk, nFilterType);
return phk;
}
/***************************************************************************\
* PhkFirstValid
*
* Given a filter-type PhkFirstValid() returns the first hook, if any, of the
* specified type.
*
* History:
* 02-10-91 DavidPe Created.
\***************************************************************************/
PHOOK PhkFirstValid(
PTHREADINFO pti,
int nFilterType)
{
PHOOK phk;
CheckCritIn();
/*
* Grab the first hook off the local hook-list
* for the current queue.
*/
phk = pti->aphkStart[nFilterType + 1];
/*
* If there aren't any local hooks, try the global hooks.
*/
if (phk == NULL) {
phk = pti->pDeskInfo->aphkStart[nFilterType + 1];
}
/*
* Return the first hook that it's not destroyed (i.e, the
* first valid one).
*/
if ((phk != NULL) && (phk->flags & HF_DESTROYED)) {
phk = PhkNextValid(phk);
}
/*
* Good place to check fsHooks. If the bits are out of sync,
* someone must be adjusting them.
*/
DbgValidatefsHook(phk, nFilterType, pti, FALSE);
DbgValidateHooks(phk, nFilterType);
return phk;
}
/***************************************************************************\
* FreeThreadsWindowHooks
*
* During 'exit-list' processing this function is called to free any hooks
* created on, or set for the current queue.
*
* History:
* 02-10-91 DavidPe Created.
\***************************************************************************/
VOID FreeThreadsWindowHooks(VOID)
{
int iHook;
PHOOK phk, phkNext;
PTHREADINFO ptiCurrent = PtiCurrent();
/*
* If there is not thread info, there are not hooks to worry about
*/
if (ptiCurrent == NULL || ptiCurrent->rpdesk == NULL) {
return;
}
/*
* In case we have a hook locked in as the current hook unlock it
* so it can be freed
*/
Unlock(&ptiCurrent->sphkCurrent);
UserAssert(ptiCurrent->TIF_flags & TIF_INCLEANUP);
/*
* Loop through all the hook types.
*/
for (iHook = WH_MIN ; iHook <= WH_MAX ; ++iHook) {
/*
* Loop through all the hooks of this type, including the
* ones already marked as destroyed (so don't call
* PhkFirstValid and PhkNextValid).
*/
phk = ptiCurrent->aphkStart[iHook + 1];
if (phk == NULL) {
phk = ptiCurrent->pDeskInfo->aphkStart[iHook + 1];
UserAssert((phk == NULL) || (phk->flags & HF_GLOBAL));
}
while (phk != NULL) {
/*
* We might free phk below, so grab the next now
* If at end of local chain, jump to the global chain
*/
phkNext = phk->phkNext;
if ((phkNext == NULL) && !(phk->flags & HF_GLOBAL)) {
phkNext = ptiCurrent->pDeskInfo->aphkStart[iHook + 1];
UserAssert((phkNext == NULL) || (phkNext->flags & HF_GLOBAL));
}
/*
* If this is a local(thread) hook, unlink it and mark it as
* destroyed so we won't call it anymore. We want to do
* this even if not calling FreeHook; also note that
* FreeHook won't unlink it if locked so we do it here anyway.
*/
if (!(phk->flags & HF_GLOBAL)) {
UserAssert(ptiCurrent == phk->ptiHooked);
UnlinkHook(phk);
phk->flags |= HF_DESTROYED;
phk->phkNext = NULL;
}
/*
* If this hook was created by this thread, free it
*/
if (GETPTI(phk) == ptiCurrent) {
FreeHook(phk);
}
phk = phkNext;
}
/*
* All local hooks should be unlinked
*/
UserAssert(ptiCurrent->aphkStart[iHook + 1] == NULL);
} /* for (iHook = WH_MIN....*/
/*
* Keep fsHooks in sync.
*/
ptiCurrent->fsHooks = 0;
}
/***************************************************************************\
* zzzRegisterSystemThread: Private API
*
* Used to set various attributes pertaining to a thread.
*
* History:
* 21-Jun-1994 from Chicago Created.
\***************************************************************************/
VOID zzzRegisterSystemThread (DWORD dwFlags, DWORD dwReserved)
{
PTHREADINFO ptiCurrent;
UserAssert(dwReserved == 0);
if (dwReserved != 0)
return;
ptiCurrent = PtiCurrent();
if (dwFlags & RST_DONTATTACHQUEUE)
ptiCurrent->TIF_flags |= TIF_DONTATTACHQUEUE;
if (dwFlags & RST_DONTJOURNALATTACH) {
ptiCurrent->TIF_flags |= TIF_DONTJOURNALATTACH;
/*
* If we are already journaling, then this queue was already
* journal attached. We need to unattach and reattach journaling
* so that we are removed from the journal attached queues.
*/
if (FJOURNALPLAYBACK() || FJOURNALRECORD()) {
zzzJournalAttach(ptiCurrent, FALSE);
zzzJournalAttach(ptiCurrent, TRUE);
}
}
}
/***************************************************************************\
* xxxGetCursorPos
*
\***************************************************************************/
BOOL
xxxGetCursorPos(
LPPOINT lpPt)
{
POINT pt;
PTHREADINFO ptiCurrent = PtiCurrent();
CheckCritIn();
if (ptiCurrent->rpdesk && ptiCurrent->rpdesk != grpdeskRitInput) {
return FALSE;
}
pt.x = gpsi->ptCursor.x;
pt.y = gpsi->ptCursor.y;
try {
ProbeForWrite(lpPt, sizeof(POINT), DATAALIGN);
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
return FALSE;
}
#ifdef REDIRECTION
/*
* If there is no CBT hook installed bail out.
*/
if (IsHooked(PtiCurrent(), WHF_CBT)) {
xxxCallHook(HCBT_GETCURSORPOS, 0, (LPARAM)&pt, WH_CBT);
}
#endif // REDIRECTION
try {
RtlCopyMemory(lpPt, &pt, sizeof(POINT));
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
return FALSE;
}
return TRUE;
}
/***************************************************************************\
* _RegisterUserApiHook
*
* History:
* 03-Mar-2000 JerrySh Created.
\***************************************************************************/
BOOL _RegisterUserApiHook(
PUNICODE_STRING pstrLib,
ULONG_PTR offPfnInitUserApiHook)
{
/*
* If we've already registered the UserApiHook, don't do it again.
*/
if (IsInsideUserApiHook()) {
RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "UserApiHook already registered");
return FALSE;
}
/*
* If this thread is restricted, don't let it install a hook.
*/
if (IsRestricted(PsGetCurrentThread())) {
RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "Thread is restricted");
return FALSE;
}
/*
* Register the library with the library management routines so we
* can assure it's loaded into all the processes necessary.
*/
gihmodUserApiHook = GetHmodTableIndex(pstrLib);
if (gihmodUserApiHook == -1) {
RIPERR0(ERROR_MOD_NOT_FOUND, RIP_VERBOSE, "");
return FALSE;
}
/*
* Add a dependency on this module - meaning, increment a count
* that simply counts the number of hooks set into this module.
*/
AddHmodDependency(gihmodUserApiHook);
/*
* Remember which process registered the hook.
*/
gppiUserApiHook = PpiCurrent();
/*
* Remember the offset to the DefWindowProc init routine.
*/
goffPfnInitUserApiHook = offPfnInitUserApiHook;
/*
* Set the global flag in gpsi to signal hooking ( later used
* as a check at beginning of hooked API's for loading DLL. )
*/
SET_SRVIF(SRVIF_HOOKED);
return TRUE;
}
/***************************************************************************\
* _UnregisterUserApiHook
*
* History:
* 03-Mar-2000 JerrySh Created.
\***************************************************************************/
BOOL _UnregisterUserApiHook(VOID)
{
/*
* The window proc handler must be unregistered by the same process that
* registered it.
*/
if (PpiCurrent() != gppiUserApiHook) {
RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "Process hasn't registered UserApiHook");
return FALSE;
}
gppiUserApiHook = NULL;
/*
* Make sure each hooked thread will unload the hook proc DLL
*/
if (gihmodUserApiHook >= 0) {
RemoveHmodDependency(gihmodUserApiHook);
gihmodUserApiHook = -1;
}
/*
* Clear the global flag in gpsi to signal disabled hooking ( later used
* as a check at beginning of hooked API's for loading DLL. )
*/
CLEAR_SRVIF(SRVIF_HOOKED);
return TRUE;
}
#ifdef MESSAGE_PUMP_HOOK
/***************************************************************************\
* _DoInitMessagePumpHook
*
* History:
* 05-Dec-2000 JStall Created
\***************************************************************************/
BOOL _DoInitMessagePumpHook(VOID)
{
PCLIENTTHREADINFO pcti = gptiCurrent->pcti;
++pcti->cMessagePumpHooks;
return TRUE;
}
/***************************************************************************\
* _DoUninitMessagePumpHook
*
* History:
* 05-Dec-2000 JStall Created
\***************************************************************************/
BOOL _DoUninitMessagePumpHook(VOID)
{
PCLIENTTHREADINFO pcti = gptiCurrent->pcti;
if (pcti->cMessagePumpHooks <= 0) {
return FALSE;
}
--pcti->cMessagePumpHooks;
return TRUE;
}
#endif // MESSAGE_PUMP_HOOK
/***************************************************************************\
* xxxLoadUserApiHook
*
* History:
* 03-Mar-2000 JerrySh Created.
\***************************************************************************/
BOOL xxxLoadUserApiHook(VOID)
{
PTHREADINFO ptiCurrent = PtiCurrent();
/*
* A check is made here to see if hooking is still applicable after we
* dropped to kernel. There is a timing issue that could create a problem
* where we check for hooking in user mode and decide to load but as we make the
* drop to kernel, someone else is unregistering hooks and resetting all
* globals.
*/
if (!IsInsideUserApiHook()){
return FALSE;
}
/*
* If the DLL is already loaded in this process, we're done.
*/
if (TESTHMODLOADED(ptiCurrent, gihmodUserApiHook)) {
return TRUE;
}
/*
* Don't load the DLL if:
* - it's a system or CSRSS thread, it's in cleanup, or it's disabled hooks
* - it's another user who hasn't granted access
* - it's Win64 status doesn't match
*/
if ((ptiCurrent->TIF_flags & (TIF_SYSTEMTHREAD | TIF_CSRSSTHREAD | TIF_INCLEANUP | TIF_DISABLEHOOKS)) ||
(!RtlEqualLuid(&gppiUserApiHook->luidSession, &luidSystem) &&
!RtlEqualLuid(&gppiUserApiHook->luidSession, &ptiCurrent->ppi->luidSession) &&
!(ptiCurrent->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) ||
((gppiUserApiHook->W32PF_Flags & W32PF_WOW64) != (ptiCurrent->ppi->W32PF_Flags & W32PF_WOW64))) {
return FALSE;
}
/*
* Try loading the library, since it isn't loaded in this processes
* context.
*/
return (xxxLoadHmodIndex(gihmodUserApiHook) != NULL);
}