1
/* Licensed to the Apache Software Foundation (ASF) under one or more
2
* contributor license agreements. See the NOTICE file distributed with
3
* this work for additional information regarding copyright ownership.
4
* The ASF licenses this file to You under the Apache License, Version 2.0
5
* (the "License"); you may not use this file except in compliance with
6
* the License. You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
20
* Win9xConHook.dll - a hook proc to clean up Win95/98 console behavior.
22
* It is well(?) documented by Microsoft that the Win9x HandlerRoutine
23
* hooked by the SetConsoleCtrlHandler never receives the CTRL_CLOSE_EVENT,
24
* CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT signals.
26
* It is possible to have a second window to monitor the WM_ENDSESSION
27
* message, but the close button still fails..
29
* There is a 16bit polling method for the close window option, but this
30
* is CPU intensive and requires thunking.
32
* Attempts to subclass the 'tty' console fail, since that message thread
33
* is actually owned by the 16 bit winoldap.mod process, although the
34
* window reports it is owned by the process/thread of the console app.
36
* Win9xConHook is thunks the WM_CLOSE and WM_ENDSESSION messages,
37
* first through a window hook procedure in the winoldap context, into
38
* a subclass WndProc, and on to a second hidden monitor window in the
39
* console application's context that dispatches them to the console app's
40
* registered HandlerRoutine.
43
/* This debugging define turns on output to COM1, although you better init
44
* the port first (even using hyperterm). It's the only way to catch the
45
* goings on within system logoff/shutdown.
51
/* Variables used within any process context:
52
* hookwndmsg is a shared message to send Win9xConHook signals
53
* origwndprop is a wndprop atom to store the orig wndproc of the tty
54
* hookwndprop is a wndprop atom to store the hwnd of the hidden child
55
* is_service reminds us to unmark this process on the way out
57
static UINT hookwndmsg = 0;
58
static LPCTSTR origwndprop;
59
static LPCTSTR hookwndprop;
60
static BOOL is_service = 0;
61
//static HMODULE hmodThis = NULL;
63
/* Variables used within the tty processes' context:
64
* is_tty flags this process; -1 == unknown, 1 == if tty, 0 == if not
65
* hw_tty is the handle of the top level tty in this process context
66
* is_subclassed is toggled to assure DllMain removes the subclass on unload
67
* hmodLock is there to try and prevent this dll from being unloaded if the
68
* hook is removed while we are subclassed
70
static int is_tty = -1;
71
static HWND hwtty = NULL;
72
static BOOL is_subclassed = 0;
74
// This simply causes a gpfault the moment it tries to FreeLibrary within
75
// the subclass procedure ... not good.
76
//static HMODULE hmodLock = NULL;
78
/* Variables used within the service or console app's context:
79
* hmodHook is the instance handle of this module for registering the hooks
80
* hhkGetMessage is the hook handle for catching Posted messages
81
* hhkGetMessage is the hook handle for catching Sent messages
82
* monitor_hwnd is the invisible window that handles our tty messages
83
* the tty_info strucure is used to pass args into the hidden window's thread
85
static HMODULE hmodHook = NULL;
86
static HHOOK hhkGetMessage;
87
//static HHOOK hhkCallWndProc;
88
static HWND monitor_hwnd = NULL;
91
PHANDLER_ROUTINE phandler;
98
/* These are the GetWindowLong offsets for the hidden window's internal info
99
* gwltty_phandler is the address of the app's HandlerRoutine
100
* gwltty_ttywnd is the tty this hidden window will handle messages from
102
#define gwltty_phandler 0
103
#define gwltty_ttywnd 4
105
/* Forward declaration prototypes for internal functions
107
static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd);
108
static LRESULT WINAPI RegisterWindows9xService(BOOL set_service);
109
static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg,
110
WPARAM wParam, LPARAM lParam);
111
static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty);
112
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
113
WPARAM wParam, LPARAM lParam);
114
static int HookProc(int hc, HWND *hwnd, UINT *msg,
115
WPARAM *wParam, LPARAM *lParam);
117
static VOID DbgPrintf(LPTSTR fmt, ...);
121
/* DllMain is invoked by every process in the entire system that is hooked
122
* by our window hooks, notably the tty processes' context, and by the user
123
* who wants tty messages (the app). Keep it light and simple.
125
BOOL __declspec(dllexport) APIENTRY DllMain(HINSTANCE hModule, ULONG ulReason,
128
if (ulReason == DLL_PROCESS_ATTACH)
130
//hmodThis = hModule;
132
origwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookOrigProc"));
133
hookwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookThunkWnd"));
134
hookwndmsg = RegisterWindowMessage("Win9xConHookMsg");
137
// DbgPrintf("H ProcessAttach:%8.8x\r\n",
138
// GetCurrentProcessId());
141
else if ( ulReason == DLL_PROCESS_DETACH )
144
// DbgPrintf("H ProcessDetach:%8.8x\r\n", GetCurrentProcessId());
147
SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
149
SendMessage(hwtty, hookwndmsg, 0, (LPARAM)hwtty);
153
UnhookWindowsHookEx(hhkGetMessage);
154
hhkGetMessage = NULL;
156
//if (hhkCallWndProc) {
157
// UnhookWindowsHookEx(hhkCallWndProc);
158
// hhkCallWndProc = NULL;
160
FreeLibrary(hmodHook);
164
RegisterWindows9xService(FALSE);
166
GlobalDeleteAtom((ATOM)origwndprop);
167
GlobalDeleteAtom((ATOM)hookwndprop);
175
/* This group of functions are provided for the service/console app
176
* to register itself a HandlerRoutine to accept tty or service messages
180
/* Exported function that creates a Win9x 'service' via a hidden window,
181
* that notifies the process via the HandlerRoutine messages.
183
BOOL __declspec(dllexport) WINAPI Windows9xServiceCtrlHandler(
184
PHANDLER_ROUTINE phandler,
187
/* If we have not yet done so */
194
/* NOTE: this is static so the module can continue to
195
* access these args while we go on to other things
198
tty.instance = GetModuleHandle(NULL);
199
tty.phandler = phandler;
203
RegisterWindows9xService(TRUE);
204
hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread,
205
(LPVOID)&tty, 0, &tid);
208
CloseHandle(hThread);
215
SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
216
RegisterWindows9xService(FALSE);
223
/* Exported function that registers a HandlerRoutine to accept missing
224
* Win9x CTRL_EVENTs from the tty window, as NT does without a hassle.
225
* If add is 1 or 2, register the handler, if 2 also mark it as a service.
226
* If add is 0 deregister the handler, and unmark if a service
228
BOOL __declspec(dllexport) WINAPI FixConsoleCtrlHandler(
229
PHANDLER_ROUTINE phandler,
238
/* NOTE: this is static so the module can continue to
239
* access these args while we go on to other things
242
EnumWindows(EnumttyWindow, (LPARAM)&parent);
245
DbgPrintf("A EnumttyWindow failed (%d)\r\n", GetLastError());
249
tty.instance = GetModuleHandle(NULL);
250
tty.phandler = phandler;
254
tty.name = "ttyService";
255
RegisterWindows9xService(TRUE);
258
tty.name = "ttyMonitor";
259
hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread,
260
(LPVOID)&tty, 0, &tid);
263
CloseHandle(hThread);
264
hmodHook = LoadLibrary("Win9xConHook.dll");
267
hhkGetMessage = SetWindowsHookEx(WH_GETMESSAGE,
268
(HOOKPROC)GetProcAddress(hmodHook, "GetMsgProc"), hmodHook, 0);
269
//hhkCallWndProc = SetWindowsHookEx(WH_CALLWNDPROC,
270
// (HOOKPROC)GetProcAddress(hmodHook, "CallWndProc"), hmodHook, 0);
277
SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
282
UnhookWindowsHookEx(hhkGetMessage);
283
hhkGetMessage = NULL;
285
//if (hhkCallWndProc) {
286
// UnhookWindowsHookEx(hhkCallWndProc);
287
// hhkCallWndProc = NULL;
289
FreeLibrary(hmodHook);
293
RegisterWindows9xService(FALSE);
300
/* The following internal helpers are only used within the app's context
303
/* ttyConsoleCreateThread is the process that runs within the user app's
304
* context. It creates and pumps the messages of a hidden monitor window,
305
* watching for messages from the system, or the associated subclassed tty
306
* window. Things can happen in our context that can't be done from the
307
* tty's context, and visa versa, so the subclass procedure and this hidden
308
* window work together to make it all happen.
310
static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty)
314
wc.style = CS_GLOBALCLASS;
315
wc.lpfnWndProc = ttyConsoleCtrlWndProc;
321
wc.hbrBackground = NULL;
322
wc.lpszMenuName = NULL;
323
if (((tty_info*)tty)->parent)
324
wc.lpszClassName = "ttyConHookChild";
326
wc.lpszClassName = "ApacheWin95ServiceMonitor";
328
if (!RegisterClass(&wc)) {
330
DbgPrintf("A proc %8.8x Error creating class %s (%d)\r\n",
331
GetCurrentProcessId(), wc.lpszClassName, GetLastError());
336
/* Create an invisible window */
337
monitor_hwnd = CreateWindow(wc.lpszClassName, ((tty_info*)tty)->name,
338
WS_OVERLAPPED & ~WS_VISIBLE,
339
CW_USEDEFAULT, CW_USEDEFAULT,
340
CW_USEDEFAULT, CW_USEDEFAULT,
342
((tty_info*)tty)->instance, tty);
346
DbgPrintf("A proc %8.8x Error creating window %s %s (%d)\r\n",
347
GetCurrentProcessId(), wc.lpszClassName,
348
((tty_info*)tty)->name, GetLastError());
353
while (GetMessage(&msg, NULL, 0, 0))
355
TranslateMessage(&msg);
356
DispatchMessage(&msg);
359
/* Tag again as deleted, just in case we missed WM_DESTROY */
365
/* This is the WndProc procedure for our invisible window.
366
* When our subclasssed tty window receives the WM_CLOSE, WM_ENDSESSION,
367
* or WM_QUERYENDSESSION messages, the message is dispatched to our hidden
368
* window (this message process), and we call the installed HandlerRoutine
369
* that was registered by the app.
371
static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg,
372
WPARAM wParam, LPARAM lParam)
374
if (msg == WM_CREATE)
376
tty_info *tty = (tty_info*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
377
SetWindowLong(hwnd, gwltty_phandler, (LONG)tty->phandler);
378
SetWindowLong(hwnd, gwltty_ttywnd, (LONG)tty->parent);
380
DbgPrintf("A proc %8.8x created %8.8x %s for tty wnd %8.8x\r\n",
381
GetCurrentProcessId(), hwnd,
382
tty->name, tty->parent);
385
SetProp(tty->parent, hookwndprop, hwnd);
386
PostMessage(tty->parent, hookwndmsg,
387
tty->type, (LPARAM)tty->parent);
391
else if (msg == WM_DESTROY)
393
HWND parent = (HWND)GetWindowLong(hwnd, gwltty_ttywnd);
395
DbgPrintf("A proc %8.8x destroyed %8.8x ttyConHookChild\r\n",
396
GetCurrentProcessId(), hwnd);
399
RemoveProp(parent, hookwndprop);
400
SendMessage(parent, hookwndmsg, 0, (LPARAM)parent);
404
else if (msg == WM_CLOSE)
406
PHANDLER_ROUTINE phandler =
407
(PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
408
LRESULT rv = phandler(CTRL_CLOSE_EVENT);
410
DbgPrintf("A proc %8.8x invoked CTRL_CLOSE_EVENT "
412
GetCurrentProcessId(), rv);
417
else if ((msg == WM_QUERYENDSESSION) || (msg == WM_ENDSESSION))
419
if (lParam & ENDSESSION_LOGOFF)
421
PHANDLER_ROUTINE phandler =
422
(PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
423
LRESULT rv = phandler(CTRL_LOGOFF_EVENT);
425
DbgPrintf("A proc %8.8x invoked CTRL_LOGOFF_EVENT "
427
GetCurrentProcessId(), rv);
430
return ((msg == WM_QUERYENDSESSION) ? rv : !rv);
434
PHANDLER_ROUTINE phandler =
435
(PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
436
LRESULT rv = phandler(CTRL_SHUTDOWN_EVENT);
438
DbgPrintf("A proc %8.8x invoked CTRL_SHUTDOWN_EVENT "
439
"returning %d\r\n", GetCurrentProcessId(), rv);
442
return ((msg == WM_QUERYENDSESSION) ? rv : !rv);
445
return (DefWindowProc(hwnd, msg, wParam, lParam));
449
/* The following internal helpers are invoked by the hooked tty and our app
453
/* Register or deregister the current process as a Windows9x style service.
454
* Experience shows this call is ignored across processes, so the second
455
* arg to RegisterServiceProcess (process group id) is effectively useless.
457
static LRESULT WINAPI RegisterWindows9xService(BOOL set_service)
459
static HINSTANCE hkernel;
460
static DWORD (WINAPI *register_service_process)(DWORD, DWORD) = NULL;
463
if (set_service == is_service)
467
DbgPrintf("R %s proc %8.8x as a service\r\n",
468
set_service ? "installing" : "removing",
469
GetCurrentProcessId());
472
if (!register_service_process)
474
/* Obtain a handle to the kernel library */
475
hkernel = LoadLibrary("KERNEL32.DLL");
479
/* Find the RegisterServiceProcess function */
480
register_service_process = (DWORD (WINAPI *)(DWORD, DWORD))
481
GetProcAddress(hkernel, "RegisterServiceProcess");
482
if (register_service_process == NULL) {
483
FreeLibrary(hkernel);
488
/* Register this process as a service */
489
rv = register_service_process(0, set_service != FALSE);
491
is_service = set_service;
495
/* Unload the kernel library */
496
FreeLibrary(hkernel);
497
register_service_process = NULL;
504
* This function only works when this process is the active process
505
* (e.g. once it is running a child process, it can no longer determine
506
* which console window is its own.)
508
static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd)
511
if (GetClassName(wnd, tmp, sizeof(tmp)) && !strcmp(tmp, "tty"))
513
DWORD wndproc, thisproc = GetCurrentProcessId();
514
GetWindowThreadProcessId(wnd, &wndproc);
515
if (wndproc == thisproc) {
516
*((HWND*)retwnd) = wnd;
524
/* The remaining code all executes --in the tty's own process context--
526
* That means special attention must be paid to what it's doing...
529
/* Subclass message process for the tty window
531
* This code -handles- WM_CLOSE, WM_ENDSESSION and WM_QUERYENDSESSION
532
* by dispatching them to the window identified by the hookwndprop
533
* property atom set against our window. Messages are then dispatched
534
* to origwndprop property atom we set against the window when we
535
* injected this subclass. This trick did not work with simply a hook.
537
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
538
WPARAM wParam, LPARAM lParam)
540
WNDPROC origproc = (WNDPROC) GetProp(hwnd, origwndprop);
544
if (msg == WM_NCDESTROY
545
|| (msg == hookwndmsg && !LOWORD(wParam) && (HWND)lParam == hwnd))
549
DbgPrintf("W proc %08x hwnd:%08x Subclass removed\r\n",
550
GetCurrentProcessId(), hwnd);
553
RegisterWindows9xService(FALSE);
554
SetWindowLong(hwnd, GWL_WNDPROC, (LONG)origproc);
555
RemoveProp(hwnd, origwndprop);
556
RemoveProp(hwnd, hookwndprop);
557
is_subclassed = FALSE;
559
// FreeLibrary(hmodLock);
563
else if (msg == WM_CLOSE || msg == WM_ENDSESSION
564
|| msg == WM_QUERYENDSESSION)
566
HWND child = (HWND)GetProp(hwnd, hookwndprop);
569
DbgPrintf("W proc %08x hwnd:%08x forwarded msg:%d\r\n",
570
GetCurrentProcessId(), hwnd, msg);
572
return SendMessage(child, msg, wParam, lParam);
575
return CallWindowProc(origproc, hwnd, msg, wParam, lParam);
579
/* HookProc, once installed, is responsible for subclassing the system
580
* tty windows. It generally does nothing special itself, since
581
* research indicates that it cannot deal well with the messages we are
582
* interested in, that is, WM_CLOSE, WM_QUERYSHUTDOWN and WM_SHUTDOWN
583
* of the tty process.
585
* Respond and subclass only when a WM_NULL is received by the window.
587
int HookProc(int hc, HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam)
589
if (is_tty == -1 && *hwnd)
594
while (htty = GetParent(hwtty))
596
is_tty = (GetClassName(hwtty, ttybuf, sizeof(ttybuf))
597
&& !strcmp(ttybuf, "tty"));
600
DbgPrintf("H proc %08x tracking hwnd %08x\r\n",
601
GetCurrentProcessId(), hwtty);
605
if (*msg == hookwndmsg && *wParam && *lParam == (LPARAM)hwtty && is_tty)
607
WNDPROC origproc = (WNDPROC)GetWindowLong(hwtty, GWL_WNDPROC);
608
//char myname[MAX_PATH];
609
//if (GetModuleFileName(hmodThis, myname, sizeof(myname)))
610
// hmodLock = LoadLibrary(myname);
611
SetProp(hwtty, origwndprop, origproc);
612
SetWindowLong(hwtty, GWL_WNDPROC, (LONG)WndProc);
613
is_subclassed = TRUE;
615
DbgPrintf("H proc %08x hwnd:%08x Subclassed\r\n",
616
GetCurrentProcessId(), hwtty);
618
if (LOWORD(*wParam) == 2)
619
RegisterWindows9xService(TRUE);
629
LRESULT __declspec(dllexport) CALLBACK GetMsgProc(INT hc, WPARAM wParam,
637
int rv = HookProc(hc, &pmsg->hwnd, &pmsg->message,
638
&pmsg->wParam, &pmsg->lParam);
643
* CallNextHookEx apparently ignores the hhook argument, so pass NULL
645
return CallNextHookEx(NULL, hc, wParam, lParam);
652
LRESULT __declspec(dllexport) CALLBACK CallWndProc(INT hc, WPARAM wParam,
655
PCWPSTRUCT pcwps = (PCWPSTRUCT)lParam;
658
int rv = HookProc(hc, &pcwps->hwnd, &pcwps->message,
659
&pcwps->wParam, &pcwps->lParam);
664
* CallNextHookEx apparently ignores the hhook argument, so pass NULL
666
return CallNextHookEx(NULL, hc, wParam, lParam);
682
va_start(marker, fmt);
683
wvsprintf(szBuf, fmt, marker);
687
mutex = CreateMutex(NULL, FALSE, "Win9xConHookDbgOut");
688
WaitForSingleObject(mutex, INFINITE);
689
gDbgOut = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0,
690
NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL);
691
WriteFile(gDbgOut, szBuf, strlen(szBuf), &t, NULL);
692
CloseHandle(gDbgOut);