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);