602 lines
20 KiB
C++
602 lines
20 KiB
C++
// --------------------------------------------------------------------------
|
|
// 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 <msginaexports.h>
|
|
#include <shlobj.h>
|
|
#include <shlobjp.h>
|
|
#include <shellapi.h>
|
|
#include <shlapip.h>
|
|
#include <winsta.h>
|
|
|
|
#include <ginarcid.h>
|
|
|
|
#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: <none>
|
|
//
|
|
// 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: <none>
|
|
//
|
|
// Returns: <none>
|
|
//
|
|
// 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: <none>
|
|
//
|
|
// 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: <none>
|
|
//
|
|
// 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<DWORD>(_Gina_DialogBoxParam(_pWlxContext,
|
|
_hDllInstance,
|
|
MAKEINTRESOURCE(IDD_GINA_TURNOFFCOMPUTER),
|
|
NULL,
|
|
DialogProc,
|
|
reinterpret_cast<LPARAM>(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<DWORD>(_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<CPowerButton*>(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: <none>
|
|
//
|
|
// 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<DWORD>(iResult))));
|
|
}
|
|
}
|
|
return(iResult);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// CPowerButtonExecution::CPowerButtonExecution
|
|
//
|
|
// Arguments: dwShutdownRequest = SHTDN_xxx request.
|
|
//
|
|
// Returns: <none>
|
|
//
|
|
// 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: <none>
|
|
//
|
|
// Returns: <none>
|
|
//
|
|
// 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: <none>
|
|
//
|
|
// 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);
|
|
}
|
|
|