/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifdef WIN32 /* * Win9xConHook.dll - a hook proc to clean up Win95/98 console behavior. * * It is well(?) documented by Microsoft that the Win9x HandlerRoutine * hooked by the SetConsoleCtrlHandler never receives the CTRL_CLOSE_EVENT, * CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT signals. * * It is possible to have a second window to monitor the WM_ENDSESSION * message, but the close button still fails.. * * There is a 16bit polling method for the close window option, but this * is CPU intensive and requires thunking. * * Attempts to subclass the 'tty' console fail, since that message thread * is actually owned by the 16 bit winoldap.mod process, although the * window reports it is owned by the process/thread of the console app. * * Win9xConHook is thunks the WM_CLOSE and WM_ENDSESSION messages, * first through a window hook procedure in the winoldap context, into * a subclass WndProc, and on to a second hidden monitor window in the * console application's context that dispatches them to the console app's * registered HandlerRoutine. */ /* This debugging define turns on output to COM1, although you better init * the port first (even using hyperterm). It's the only way to catch the * goings on within system logoff/shutdown. * #define DBG 1 */ #include /* Variables used within any process context: * hookwndmsg is a shared message to send Win9xConHook signals * origwndprop is a wndprop atom to store the orig wndproc of the tty * hookwndprop is a wndprop atom to store the hwnd of the hidden child * is_service reminds us to unmark this process on the way out */ static UINT hookwndmsg = 0; static LPCTSTR origwndprop; static LPCTSTR hookwndprop; static BOOL is_service = 0; //static HMODULE hmodThis = NULL; /* Variables used within the tty processes' context: * is_tty flags this process; -1 == unknown, 1 == if tty, 0 == if not * hw_tty is the handle of the top level tty in this process context * is_subclassed is toggled to assure DllMain removes the subclass on unload * hmodLock is there to try and prevent this dll from being unloaded if the * hook is removed while we are subclassed */ static int is_tty = -1; static HWND hwtty = NULL; static BOOL is_subclassed = 0; // This simply causes a gpfault the moment it tries to FreeLibrary within // the subclass procedure ... not good. //static HMODULE hmodLock = NULL; /* Variables used within the service or console app's context: * hmodHook is the instance handle of this module for registering the hooks * hhkGetMessage is the hook handle for catching Posted messages * hhkGetMessage is the hook handle for catching Sent messages * monitor_hwnd is the invisible window that handles our tty messages * the tty_info strucure is used to pass args into the hidden window's thread */ static HMODULE hmodHook = NULL; static HHOOK hhkGetMessage; //static HHOOK hhkCallWndProc; static HWND monitor_hwnd = NULL; typedef struct { PHANDLER_ROUTINE phandler; HINSTANCE instance; HWND parent; INT type; LPCSTR name; } tty_info; /* These are the GetWindowLong offsets for the hidden window's internal info * gwltty_phandler is the address of the app's HandlerRoutine * gwltty_ttywnd is the tty this hidden window will handle messages from */ #define gwltty_phandler 0 #define gwltty_ttywnd 4 /* Forward declaration prototypes for internal functions */ static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd); static LRESULT WINAPI RegisterWindows9xService(BOOL set_service); static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty); static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); static int HookProc(int hc, HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam); #ifdef DBG static VOID DbgPrintf(LPTSTR fmt, ...); #endif /* DllMain is invoked by every process in the entire system that is hooked * by our window hooks, notably the tty processes' context, and by the user * who wants tty messages (the app). Keep it light and simple. */ BOOL __declspec(dllexport) APIENTRY DllMain(HINSTANCE hModule, ULONG ulReason, LPVOID pctx) { if (ulReason == DLL_PROCESS_ATTACH) { //hmodThis = hModule; if (!hookwndmsg) { origwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookOrigProc")); hookwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookThunkWnd")); hookwndmsg = RegisterWindowMessage("Win9xConHookMsg"); } #ifdef DBG // DbgPrintf("H ProcessAttach:%8.8x\r\n", // GetCurrentProcessId()); #endif } else if ( ulReason == DLL_PROCESS_DETACH ) { #ifdef DBG // DbgPrintf("H ProcessDetach:%8.8x\r\n", GetCurrentProcessId()); #endif if (monitor_hwnd) SendMessage(monitor_hwnd, WM_DESTROY, 0, 0); if (is_subclassed) SendMessage(hwtty, hookwndmsg, 0, (LPARAM)hwtty); if (hmodHook) { if (hhkGetMessage) { UnhookWindowsHookEx(hhkGetMessage); hhkGetMessage = NULL; } //if (hhkCallWndProc) { // UnhookWindowsHookEx(hhkCallWndProc); // hhkCallWndProc = NULL; //} FreeLibrary(hmodHook); hmodHook = NULL; } if (is_service) RegisterWindows9xService(FALSE); if (hookwndmsg) { GlobalDeleteAtom((ATOM)origwndprop); GlobalDeleteAtom((ATOM)hookwndprop); hookwndmsg = 0; } } return TRUE; } /* This group of functions are provided for the service/console app * to register itself a HandlerRoutine to accept tty or service messages */ /* Exported function that creates a Win9x 'service' via a hidden window, * that notifies the process via the HandlerRoutine messages. */ BOOL __declspec(dllexport) WINAPI Windows9xServiceCtrlHandler( PHANDLER_ROUTINE phandler, LPCSTR name) { /* If we have not yet done so */ FreeConsole(); if (name) { DWORD tid; HANDLE hThread; /* NOTE: this is static so the module can continue to * access these args while we go on to other things */ static tty_info tty; tty.instance = GetModuleHandle(NULL); tty.phandler = phandler; tty.parent = NULL; tty.name = name; tty.type = 2; RegisterWindows9xService(TRUE); hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread, (LPVOID)&tty, 0, &tid); if (hThread) { CloseHandle(hThread); return TRUE; } } else /* remove */ { if (monitor_hwnd) SendMessage(monitor_hwnd, WM_DESTROY, 0, 0); RegisterWindows9xService(FALSE); return TRUE; } return FALSE; } /* Exported function that registers a HandlerRoutine to accept missing * Win9x CTRL_EVENTs from the tty window, as NT does without a hassle. * If add is 1 or 2, register the handler, if 2 also mark it as a service. * If add is 0 deregister the handler, and unmark if a service */ BOOL __declspec(dllexport) WINAPI FixConsoleCtrlHandler( PHANDLER_ROUTINE phandler, INT add) { HWND parent; if (add) { HANDLE hThread; DWORD tid; /* NOTE: this is static so the module can continue to * access these args while we go on to other things */ static tty_info tty; EnumWindows(EnumttyWindow, (LPARAM)&parent); if (!parent) { #ifdef DBG DbgPrintf("A EnumttyWindow failed (%d)\r\n", GetLastError()); #endif return FALSE; } tty.instance = GetModuleHandle(NULL); tty.phandler = phandler; tty.parent = parent; tty.type = add; if (add == 2) { tty.name = "ttyService"; RegisterWindows9xService(TRUE); } else tty.name = "ttyMonitor"; hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread, (LPVOID)&tty, 0, &tid); if (!hThread) return FALSE; CloseHandle(hThread); hmodHook = LoadLibrary("Win9xConHook.dll"); if (hmodHook) { hhkGetMessage = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetProcAddress(hmodHook, "GetMsgProc"), hmodHook, 0); //hhkCallWndProc = SetWindowsHookEx(WH_CALLWNDPROC, // (HOOKPROC)GetProcAddress(hmodHook, "CallWndProc"), hmodHook, 0); } return TRUE; } else /* remove */ { if (monitor_hwnd) { SendMessage(monitor_hwnd, WM_DESTROY, 0, 0); } if (hmodHook) { if (hhkGetMessage) { UnhookWindowsHookEx(hhkGetMessage); hhkGetMessage = NULL; } //if (hhkCallWndProc) { // UnhookWindowsHookEx(hhkCallWndProc); // hhkCallWndProc = NULL; //} FreeLibrary(hmodHook); hmodHook = NULL; } if (is_service) RegisterWindows9xService(FALSE); return TRUE; } return FALSE; } /* The following internal helpers are only used within the app's context */ /* ttyConsoleCreateThread is the process that runs within the user app's * context. It creates and pumps the messages of a hidden monitor window, * watching for messages from the system, or the associated subclassed tty * window. Things can happen in our context that can't be done from the * tty's context, and visa versa, so the subclass procedure and this hidden * window work together to make it all happen. */ static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty) { WNDCLASS wc; MSG msg; wc.style = CS_GLOBALCLASS; wc.lpfnWndProc = ttyConsoleCtrlWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 8; wc.hInstance = NULL; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; if (((tty_info*)tty)->parent) wc.lpszClassName = "ttyConHookChild"; else wc.lpszClassName = "ApacheWin95ServiceMonitor"; if (!RegisterClass(&wc)) { #ifdef DBG DbgPrintf("A proc %8.8x Error creating class %s (%d)\r\n", GetCurrentProcessId(), wc.lpszClassName, GetLastError()); #endif return 0; } /* Create an invisible window */ monitor_hwnd = CreateWindow(wc.lpszClassName, ((tty_info*)tty)->name, WS_OVERLAPPED & ~WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, ((tty_info*)tty)->instance, tty); if (!monitor_hwnd) { #ifdef DBG DbgPrintf("A proc %8.8x Error creating window %s %s (%d)\r\n", GetCurrentProcessId(), wc.lpszClassName, ((tty_info*)tty)->name, GetLastError()); #endif return 0; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /* Tag again as deleted, just in case we missed WM_DESTROY */ monitor_hwnd = NULL; return 0; } /* This is the WndProc procedure for our invisible window. * When our subclasssed tty window receives the WM_CLOSE, WM_ENDSESSION, * or WM_QUERYENDSESSION messages, the message is dispatched to our hidden * window (this message process), and we call the installed HandlerRoutine * that was registered by the app. */ static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_CREATE) { tty_info *tty = (tty_info*)(((LPCREATESTRUCT)lParam)->lpCreateParams); SetWindowLong(hwnd, gwltty_phandler, (LONG)tty->phandler); SetWindowLong(hwnd, gwltty_ttywnd, (LONG)tty->parent); #ifdef DBG DbgPrintf("A proc %8.8x created %8.8x %s for tty wnd %8.8x\r\n", GetCurrentProcessId(), hwnd, tty->name, tty->parent); #endif if (tty->parent) { SetProp(tty->parent, hookwndprop, hwnd); PostMessage(tty->parent, hookwndmsg, tty->type, (LPARAM)tty->parent); } return 0; } else if (msg == WM_DESTROY) { HWND parent = (HWND)GetWindowLong(hwnd, gwltty_ttywnd); #ifdef DBG DbgPrintf("A proc %8.8x destroyed %8.8x ttyConHookChild\r\n", GetCurrentProcessId(), hwnd); #endif if (parent) { RemoveProp(parent, hookwndprop); SendMessage(parent, hookwndmsg, 0, (LPARAM)parent); } monitor_hwnd = NULL; } else if (msg == WM_CLOSE) { PHANDLER_ROUTINE phandler = (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler); LRESULT rv = phandler(CTRL_CLOSE_EVENT); #ifdef DBG DbgPrintf("A proc %8.8x invoked CTRL_CLOSE_EVENT " "returning %d\r\n", GetCurrentProcessId(), rv); #endif if (rv) return !rv; } else if ((msg == WM_QUERYENDSESSION) || (msg == WM_ENDSESSION)) { if (lParam & ENDSESSION_LOGOFF) { PHANDLER_ROUTINE phandler = (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler); LRESULT rv = phandler(CTRL_LOGOFF_EVENT); #ifdef DBG DbgPrintf("A proc %8.8x invoked CTRL_LOGOFF_EVENT " "returning %d\r\n", GetCurrentProcessId(), rv); #endif if (rv) return ((msg == WM_QUERYENDSESSION) ? rv : !rv); } else { PHANDLER_ROUTINE phandler = (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler); LRESULT rv = phandler(CTRL_SHUTDOWN_EVENT); #ifdef DBG DbgPrintf("A proc %8.8x invoked CTRL_SHUTDOWN_EVENT " "returning %d\r\n", GetCurrentProcessId(), rv); #endif if (rv) return ((msg == WM_QUERYENDSESSION) ? rv : !rv); } } return (DefWindowProc(hwnd, msg, wParam, lParam)); } /* The following internal helpers are invoked by the hooked tty and our app */ /* Register or deregister the current process as a Windows9x style service. * Experience shows this call is ignored across processes, so the second * arg to RegisterServiceProcess (process group id) is effectively useless. */ static LRESULT WINAPI RegisterWindows9xService(BOOL set_service) { static HINSTANCE hkernel; static DWORD (WINAPI *register_service_process)(DWORD, DWORD) = NULL; BOOL rv; if (set_service == is_service) return 1; #ifdef DBG DbgPrintf("R %s proc %8.8x as a service\r\n", set_service ? "installing" : "removing", GetCurrentProcessId()); #endif if (!register_service_process) { /* Obtain a handle to the kernel library */ hkernel = LoadLibrary("KERNEL32.DLL"); if (!hkernel) return 0; /* Find the RegisterServiceProcess function */ register_service_process = (DWORD (WINAPI *)(DWORD, DWORD)) GetProcAddress(hkernel, "RegisterServiceProcess"); if (register_service_process == NULL) { FreeLibrary(hkernel); return 0; } } /* Register this process as a service */ rv = register_service_process(0, set_service != FALSE); if (rv) is_service = set_service; if (!is_service) { /* Unload the kernel library */ FreeLibrary(hkernel); register_service_process = NULL; } return rv; } /* * This function only works when this process is the active process * (e.g. once it is running a child process, it can no longer determine * which console window is its own.) */ static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd) { char tmp[8]; if (GetClassName(wnd, tmp, sizeof(tmp)) && !strcmp(tmp, "tty")) { DWORD wndproc, thisproc = GetCurrentProcessId(); GetWindowThreadProcessId(wnd, &wndproc); if (wndproc == thisproc) { *((HWND*)retwnd) = wnd; return FALSE; } } return TRUE; } /* The remaining code all executes --in the tty's own process context-- * * That means special attention must be paid to what it's doing... */ /* Subclass message process for the tty window * * This code -handles- WM_CLOSE, WM_ENDSESSION and WM_QUERYENDSESSION * by dispatching them to the window identified by the hookwndprop * property atom set against our window. Messages are then dispatched * to origwndprop property atom we set against the window when we * injected this subclass. This trick did not work with simply a hook. */ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { WNDPROC origproc = (WNDPROC) GetProp(hwnd, origwndprop); if (!origproc) return 0; if (msg == WM_NCDESTROY || (msg == hookwndmsg && !LOWORD(wParam) && (HWND)lParam == hwnd)) { if (is_subclassed) { #ifdef DBG DbgPrintf("W proc %08x hwnd:%08x Subclass removed\r\n", GetCurrentProcessId(), hwnd); #endif if (is_service) RegisterWindows9xService(FALSE); SetWindowLong(hwnd, GWL_WNDPROC, (LONG)origproc); RemoveProp(hwnd, origwndprop); RemoveProp(hwnd, hookwndprop); is_subclassed = FALSE; //if (hmodLock) // FreeLibrary(hmodLock); //hmodLock = NULL; } } else if (msg == WM_CLOSE || msg == WM_ENDSESSION || msg == WM_QUERYENDSESSION) { HWND child = (HWND)GetProp(hwnd, hookwndprop); if (child) { #ifdef DBG DbgPrintf("W proc %08x hwnd:%08x forwarded msg:%d\r\n", GetCurrentProcessId(), hwnd, msg); #endif return SendMessage(child, msg, wParam, lParam); } } return CallWindowProc(origproc, hwnd, msg, wParam, lParam); } /* HookProc, once installed, is responsible for subclassing the system * tty windows. It generally does nothing special itself, since * research indicates that it cannot deal well with the messages we are * interested in, that is, WM_CLOSE, WM_QUERYSHUTDOWN and WM_SHUTDOWN * of the tty process. * * Respond and subclass only when a WM_NULL is received by the window. */ int HookProc(int hc, HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam) { if (is_tty == -1 && *hwnd) { char ttybuf[8]; HWND htty; hwtty = *hwnd; while (htty = GetParent(hwtty)) hwtty = htty; is_tty = (GetClassName(hwtty, ttybuf, sizeof(ttybuf)) && !strcmp(ttybuf, "tty")); #ifdef DBG if (is_tty) DbgPrintf("H proc %08x tracking hwnd %08x\r\n", GetCurrentProcessId(), hwtty); #endif } if (*msg == hookwndmsg && *wParam && *lParam == (LPARAM)hwtty && is_tty) { WNDPROC origproc = (WNDPROC)GetWindowLong(hwtty, GWL_WNDPROC); //char myname[MAX_PATH]; //if (GetModuleFileName(hmodThis, myname, sizeof(myname))) // hmodLock = LoadLibrary(myname); SetProp(hwtty, origwndprop, origproc); SetWindowLong(hwtty, GWL_WNDPROC, (LONG)WndProc); is_subclassed = TRUE; #ifdef DBG DbgPrintf("H proc %08x hwnd:%08x Subclassed\r\n", GetCurrentProcessId(), hwtty); #endif if (LOWORD(*wParam) == 2) RegisterWindows9xService(TRUE); } return -1; } /* * PostMessage Hook: */ LRESULT __declspec(dllexport) CALLBACK GetMsgProc(INT hc, WPARAM wParam, LPARAM lParam) { PMSG pmsg; pmsg = (PMSG)lParam; if (pmsg) { int rv = HookProc(hc, &pmsg->hwnd, &pmsg->message, &pmsg->wParam, &pmsg->lParam); if (rv != -1) return rv; } /* * CallNextHookEx apparently ignores the hhook argument, so pass NULL */ return CallNextHookEx(NULL, hc, wParam, lParam); } /* * SendMessage Hook: */ LRESULT __declspec(dllexport) CALLBACK CallWndProc(INT hc, WPARAM wParam, LPARAM lParam) { PCWPSTRUCT pcwps = (PCWPSTRUCT)lParam; if (pcwps) { int rv = HookProc(hc, &pcwps->hwnd, &pcwps->message, &pcwps->wParam, &pcwps->lParam); if (rv != -1) return rv; } /* * CallNextHookEx apparently ignores the hhook argument, so pass NULL */ return CallNextHookEx(NULL, hc, wParam, lParam); } #ifdef DBG VOID DbgPrintf( LPTSTR fmt, ... ) { static HANDLE mutex; va_list marker; TCHAR szBuf[256]; DWORD t; HANDLE gDbgOut; va_start(marker, fmt); wvsprintf(szBuf, fmt, marker); va_end(marker); if (!mutex) mutex = CreateMutex(NULL, FALSE, "Win9xConHookDbgOut"); WaitForSingleObject(mutex, INFINITE); gDbgOut = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL); WriteFile(gDbgOut, szBuf, strlen(szBuf), &t, NULL); CloseHandle(gDbgOut); ReleaseMutex(mutex); } #endif #endif /* WIN32 */