// -------------------------------------------------------------------------- // Module Name: PowerButton.cpp // // Copyright (c) 2000, Microsoft Corporation // // Implementation file for CPowerButton class which handles the ACPI power // button. // // History: 2000-04-17 vtan created // -------------------------------------------------------------------------- #include "StandardHeader.h" #include "PowerButton.h" #include #include #include #include #include #include #include #include "DimmedWindow.h" #include "Impersonation.h" #include "PrivilegeEnable.h" #include "SystemSettings.h" #define WM_HIDEOURSELVES (WM_USER + 10000) #define WM_READY (WM_USER + 10001) // -------------------------------------------------------------------------- // CPowerButton::CPowerButton // // Arguments: pWlxContext = PGLOBALS allocated at WlxInitialize. // hDllInstance = HINSTANCE of the hosting DLL or EXE. // // Returns: // // Purpose: Constructor for the CPowerButton class. It opens the effective // token of the caller (which is actually impersonating the // current user) for assignment in its thread token when // execution begins. The token cannot be assigned now because // the current thread is impersonating the user context and it // cannot assign the token to the newly created thread running in // the SYSTEM context. // // History: 2000-04-18 vtan created // -------------------------------------------------------------------------- CPowerButton::CPowerButton (void *pWlxContext, HINSTANCE hDllInstance) : CThread(), _pWlxContext(pWlxContext), _hDllInstance(hDllInstance), _hToken(NULL), _pTurnOffDialog(NULL), _fCleanCompletion(true) { (BOOL)OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, FALSE, &_hToken); Resume(); } // -------------------------------------------------------------------------- // CPowerButton::~CPowerButton // // Arguments: // // Returns: // // Purpose: Destructor for the CPowerButton class. Cleans up resources // used by the class. // // History: 2000-04-18 vtan created // -------------------------------------------------------------------------- CPowerButton::~CPowerButton (void) { ASSERTMSG(_pTurnOffDialog == NULL, "_pTurnOffDialog is not NULL in CPowerButton::~CPowerButton"); ReleaseHandle(_hToken); } // -------------------------------------------------------------------------- // CPowerButton::IsValidExecutionCode // // Arguments: dwGinaCode // // Returns: bool // // Purpose: Returns whether the given MSGINA_DLG_xxx code is valid. It // does fully verify the validity of the MSGINA_DLG_xxx_FLAG // options. // // History: 2000-06-06 vtan created // -------------------------------------------------------------------------- bool CPowerButton::IsValidExecutionCode (DWORD dwGinaCode) { DWORD dwExecutionCode; dwExecutionCode = dwGinaCode & ~MSGINA_DLG_FLAG_MASK; return((dwExecutionCode == MSGINA_DLG_USER_LOGOFF) || (dwExecutionCode == MSGINA_DLG_SHUTDOWN) || (dwExecutionCode == MSGINA_DLG_DISCONNECT)); } // -------------------------------------------------------------------------- // CPowerButton::Entry // // Arguments: // // Returns: DWORD // // Purpose: Main function of the thread. Change the thread's desktop first // in case the actual input desktop is Winlogon's which is the // secure desktop. Then change the thread's token so that the // user's privileges are respected in the action choices. This // actually isn't critical because the physical button on the // keyboard is pressed which means they can physically remove the // power also! // // History: 2000-04-18 vtan created // -------------------------------------------------------------------------- DWORD CPowerButton::Entry (void) { DWORD dwResult; HDESK hDeskInput; CDesktop desktop; dwResult = MSGINA_DLG_FAILURE; // Get the input desktop. hDeskInput = OpenInputDesktop(0, FALSE, MAXIMUM_ALLOWED); if (hDeskInput != NULL) { bool fHandled; DWORD dwLengthNeeded; TCHAR szDesktopName[256]; fHandled = false; // Get the desktop's name. if (GetUserObjectInformation(hDeskInput, UOI_NAME, szDesktopName, sizeof(szDesktopName), &dwLengthNeeded) != FALSE) { // If the desktop is "Winlogon" (case insensitive) then // assume that the secure desktop is showing. It's safe // to display the dialog and handle it inline. if (lstrcmpi(szDesktopName, TEXT("winlogon")) == 0) { dwResult = ShowDialog(); fHandled = true; } else { CDesktop desktopTemp; // The input desktop is something else. Check the name. // If it's "Default" (case insensitive) then assume that // explorer is going to handle this message. Go find explorer's // tray window. Check it's not hung by probing with a // SendMessageTimeout. If that shows it's not hung then // send it the real message. If it's hung then don't let // explorer process this message. Instead handle it // internally with the funky desktop switch stuff. if (NT_SUCCESS(desktopTemp.SetInput())) { HWND hwnd; hwnd = FindWindow(TEXT("Shell_TrayWnd"), NULL); if (hwnd != NULL) { DWORD dwProcessID; DWORD_PTR dwUnused; (DWORD)GetWindowThreadProcessId(hwnd, &dwProcessID); if (SendMessageTimeout(hwnd, WM_NULL, 0, 0, SMTO_NORMAL, 500, &dwUnused) != 0) { // Before asking explorer to bring up the dialog // allow it to set the foreground window. We have // this power because win32k gave it to us when // the ACPI power button message was sent to winlogon. (BOOL)AllowSetForegroundWindow(dwProcessID); (LRESULT)SendMessage(hwnd, WM_CLOSE, 0, 0); fHandled = true; } } } } } // If the request couldn't be handled then switch the desktop to // winlogon's desktop and handle it here. This secures the dialog // on the secure desktop from rogue processes sending bogus messages // and crashing processes. The input desktop is required to be // switched. If this fails there's little that can be done. Ignore // this gracefully. if (!fHandled) { if (SwitchDesktop(GetThreadDesktop(GetCurrentThreadId())) != FALSE) { dwResult = ShowDialog(); TBOOL(SwitchDesktop(hDeskInput)); } } } (BOOL)CloseDesktop(hDeskInput); return(dwResult); } // -------------------------------------------------------------------------- // CPowerButton::ShowDialog // // Arguments: // // Returns: DWORD // // Purpose: Handles showing the dialog. This is called when the input // desktop is already winlogon's desktop or the desktop got // switched to winlogon's desktop. This should never be used on // WinSta0\Default in winlogon's process context. // // History: 2001-02-14 vtan created // -------------------------------------------------------------------------- DWORD CPowerButton::ShowDialog (void) { DWORD dwResult; bool fCorrectContext; dwResult = MSGINA_DLG_FAILURE; if (_hToken != NULL) { fCorrectContext = (ImpersonateLoggedOnUser(_hToken) != FALSE); } else { fCorrectContext = true; } if (fCorrectContext) { TBOOL(_Gina_SetTimeout(_pWlxContext, LOGON_TIMEOUT)); // In friendly UI bring up a Win32 dialog thru winlogon which // will get SAS and timeout events. Use this dialog to control // the lifetime of the friendly Turn Off Computer dialog. if (CSystemSettings::IsFriendlyUIActive()) { dwResult = static_cast(_Gina_DialogBoxParam(_pWlxContext, _hDllInstance, MAKEINTRESOURCE(IDD_GINA_TURNOFFCOMPUTER), NULL, DialogProc, reinterpret_cast(this))); } // In classic UI just bring up the classic UI dialog. // Ensure that invalid options are not allowed in the // combobox selections. This depends on whether a user // is logged onto the window station or not. else { DWORD dwExcludeOptions; HWND hwndParent; CDimmedWindow *pDimmedWindow; pDimmedWindow = new CDimmedWindow(_hDllInstance); if (pDimmedWindow != NULL) { hwndParent = pDimmedWindow->Create(); } else { hwndParent = NULL; } if (_hToken != NULL) { dwExcludeOptions = SHTDN_RESTART_DOS | SHTDN_SLEEP2; } else { dwExcludeOptions = SHTDN_LOGOFF | SHTDN_RESTART_DOS | SHTDN_SLEEP2 | SHTDN_DISCONNECT; } dwResult = static_cast(_Gina_ShutdownDialog(_pWlxContext, hwndParent, dwExcludeOptions)); if (pDimmedWindow != NULL) { pDimmedWindow->Release(); } } TBOOL(_Gina_SetTimeout(_pWlxContext, 0)); } if (fCorrectContext && (_hToken != NULL)) { TBOOL(RevertToSelf()); } return(dwResult); } // -------------------------------------------------------------------------- // CPowerButton::DialogProc // // Arguments: See the platform SDK under DialogProc. // // Returns: INT_PTR // // Purpose: Handles dialog messages from the dialog manager. In particular // this traps SAS messages from winlogon. // // History: 2000-06-06 vtan created // -------------------------------------------------------------------------- INT_PTR CALLBACK CPowerButton::DialogProc (HWND hwndDialog, UINT uMsg, WPARAM wParam, LPARAM lParam) { INT_PTR iResult; CPowerButton *pThis; pThis = reinterpret_cast(GetWindowLongPtr(hwndDialog, GWLP_USERDATA)); switch (uMsg) { case WM_INITDIALOG: { (LONG_PTR)SetWindowLongPtr(hwndDialog, GWLP_USERDATA, lParam); TBOOL(SetWindowPos(hwndDialog, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER)); TBOOL(PostMessage(hwndDialog, WM_HIDEOURSELVES, 0, 0)); iResult = TRUE; break; } case WM_HIDEOURSELVES: { (BOOL)ShowWindow(hwndDialog, SW_HIDE); TBOOL(PostMessage(hwndDialog, WM_READY, 0, 0)); iResult = TRUE; break; } case WM_READY: { pThis->Handle_WM_READY(hwndDialog); iResult = TRUE; break; } case WLX_WM_SAS: { // Blow off CONTROL-ALT-DELETE presses. if (wParam == WLX_SAS_TYPE_CTRL_ALT_DEL) { iResult = TRUE; } else { // This dialog gets a WM_NULL from the Win32 dialog manager // when the dialog is ended from a timeout. This is input // timeout and not a screen saver timeout. Screen saver // timeouts will cause a WLX_SAS_TYPE_SCRNSVR_TIMEOUT to // be generated which is handled by RootDlgProc in winlogon. // The input timeout should be treated the same as the screen // saver timeout and cause the Turn Off dialog to go away. case WM_NULL: if (pThis->_pTurnOffDialog != NULL) { pThis->_pTurnOffDialog->Destroy(); } pThis->_fCleanCompletion = false; iResult = FALSE; } break; } default: { iResult = FALSE; break; } } return(iResult); } // -------------------------------------------------------------------------- // CPowerButton::Handle_WM_READY // // Arguments: hwndDialog = HWND of the hosting dialog. // // Returns: // // Purpose: Handles showing the Turn Off Computer dialog hosted under // another dialog to trap SAS messages. Only change the returned // code via user32!EndDialog if the dialog was ended normally. // In abnormal circumstances winlogon has ended the dialog for // us with a specific code (e.g. screen saver timeout). // // History: 2000-06-06 vtan created // -------------------------------------------------------------------------- INT_PTR CPowerButton::Handle_WM_READY (HWND hwndDialog) { INT_PTR iResult; iResult = SHTDN_NONE; _pTurnOffDialog = new CTurnOffDialog(_hDllInstance); if (_pTurnOffDialog != NULL) { iResult = _pTurnOffDialog->Show(NULL); delete _pTurnOffDialog; _pTurnOffDialog = NULL; if (_fCleanCompletion) { TBOOL(EndDialog(hwndDialog, CTurnOffDialog::ShellCodeToGinaCode(static_cast(iResult)))); } } return(iResult); } // -------------------------------------------------------------------------- // CPowerButtonExecution::CPowerButtonExecution // // Arguments: dwShutdownRequest = SHTDN_xxx request. // // Returns: // // Purpose: Constructor for the CPowerButtonExecution class. Invokes the // appropriate shutdown request on a different thread so the // SASWndProc thread is NOT blocked. // // History: 2000-04-18 vtan created // -------------------------------------------------------------------------- CPowerButtonExecution::CPowerButtonExecution (DWORD dwShutdownRequest) : CThread(), _dwShutdownRequest(dwShutdownRequest), _hToken(NULL) { (BOOL)OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, FALSE, &_hToken); Resume(); } // -------------------------------------------------------------------------- // CPowerButtonExecution::~CPowerButtonExecution // // Arguments: // // Returns: // // Purpose: Destructor for the CPowerButtonExecution class. Releases // resources used by the class. // // History: 2000-04-18 vtan created // -------------------------------------------------------------------------- CPowerButtonExecution::~CPowerButtonExecution (void) { ReleaseHandle(_hToken); } // -------------------------------------------------------------------------- // CPowerButtonExecution::Entry // // Arguments: // // Returns: DWORD // // Purpose: Main entry function. This performs the request and exits the // thread. // // History: 2000-04-18 vtan created // -------------------------------------------------------------------------- DWORD CPowerButtonExecution::Entry (void) { bool fCorrectContext; if (_hToken != NULL) { fCorrectContext = (ImpersonateLoggedOnUser(_hToken) != FALSE); } else { fCorrectContext = true; } if (fCorrectContext) { CPrivilegeEnable privilege(SE_SHUTDOWN_NAME); switch (_dwShutdownRequest & ~MSGINA_DLG_FLAG_MASK) { case MSGINA_DLG_USER_LOGOFF: case MSGINA_DLG_SHUTDOWN: { DWORD dwRequestFlags; dwRequestFlags = _dwShutdownRequest & MSGINA_DLG_FLAG_MASK; switch (dwRequestFlags) { case 0: case MSGINA_DLG_SHUTDOWN_FLAG: case MSGINA_DLG_REBOOT_FLAG: case MSGINA_DLG_POWEROFF_FLAG: { UINT uiFlags; if (dwRequestFlags == 0) { uiFlags = EWX_LOGOFF; } else if (dwRequestFlags == MSGINA_DLG_REBOOT_FLAG) { uiFlags = EWX_WINLOGON_OLD_REBOOT; } else { SYSTEM_POWER_CAPABILITIES spc; (NTSTATUS)NtPowerInformation(SystemPowerCapabilities, NULL, 0, &spc, sizeof(spc)); if (spc.SystemS4) { uiFlags = EWX_WINLOGON_OLD_POWEROFF; } else { uiFlags = EWX_WINLOGON_OLD_SHUTDOWN; } } TBOOL(ExitWindowsEx(uiFlags, 0)); break; } case MSGINA_DLG_SLEEP_FLAG: case MSGINA_DLG_SLEEP2_FLAG: case MSGINA_DLG_HIBERNATE_FLAG: { POWER_ACTION pa; if (dwRequestFlags == MSGINA_DLG_HIBERNATE_FLAG) { pa = PowerActionHibernate; } else { pa = PowerActionSleep; } (NTSTATUS)NtInitiatePowerAction(pa, PowerSystemSleeping1, POWER_ACTION_QUERY_ALLOWED | POWER_ACTION_UI_ALLOWED, FALSE); break; } default: { WARNINGMSG("Unknown MSGINA_DLG_xxx_FLAG used in CPowerButtonExecution::Entry"); break; } } break; } case MSGINA_DLG_DISCONNECT: { (BOOLEAN)WinStationDisconnect(SERVERNAME_CURRENT, LOGONID_CURRENT, FALSE); break; } default: { WARNINGMSG("Unknown MSGINA_DLG_xxx_ used in CPowerButtonExecution::Entry"); break; } } } if (fCorrectContext && (_hToken != NULL)) { TBOOL(RevertToSelf()); } return(0); }