windows-nt/Source/XPSP1/NT/shell/ext/gina/powerbutton.cpp
2020-09-26 16:20:57 +08:00

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