reactos/reactos/base/system/winlogon/sas.c
Rafal Harabien 4772994fec [WINLOGON]
- Move playing log on sound to HandleLogon
- Don't use HKEY_CURRENT_USER for accessing user registry key. This key points to settings of user which started Winlogon process (SYSTEM) instead of logged user. Instead use HKU\SID.
See issue #5436 for more details.

svn path=/trunk/; revision=54232
2011-10-22 18:28:05 +00:00

1297 lines
32 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS Winlogon
* FILE: base/system/winlogon/sas.c
* PURPOSE: Secure Attention Sequence
* PROGRAMMERS: Thomas Weidenmueller (w3seek@users.sourceforge.net)
* Hervé Poussineau (hpoussin@reactos.org)
* UPDATE HISTORY:
* Created 28/03/2004
*/
/* INCLUDES *****************************************************************/
#include "winlogon.h"
#include <wine/debug.h>
WINE_DEFAULT_DEBUG_CHANNEL(winlogon);
/* GLOBALS ******************************************************************/
#define WINLOGON_SAS_CLASS L"SAS Window class"
#define WINLOGON_SAS_TITLE L"SAS window"
#define HK_CTRL_ALT_DEL 0
#define HK_CTRL_SHIFT_ESC 1
static BOOL inScrn = FALSE;
/* FUNCTIONS ****************************************************************/
static BOOL
StartTaskManager(
IN OUT PWLSESSION Session)
{
LPVOID lpEnvironment;
BOOL ret;
if (!Session->Gina.Functions.WlxStartApplication)
return FALSE;
if (!CreateEnvironmentBlock(
&lpEnvironment,
Session->UserToken,
TRUE))
{
return FALSE;
}
ret = Session->Gina.Functions.WlxStartApplication(
Session->Gina.Context,
L"Default",
lpEnvironment,
L"taskmgr.exe");
DestroyEnvironmentBlock(lpEnvironment);
return ret;
}
static BOOL
StartUserShell(
IN OUT PWLSESSION Session)
{
LPVOID lpEnvironment = NULL;
BOOLEAN Old;
BOOL ret;
/* Create environment block for the user */
if (!CreateEnvironmentBlock(&lpEnvironment, Session->UserToken, TRUE))
{
WARN("WL: CreateEnvironmentBlock() failed\n");
return FALSE;
}
/* Get privilege */
/* FIXME: who should do it? winlogon or gina? */
/* FIXME: reverting to lower privileges after creating user shell? */
RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE, TRUE, FALSE, &Old);
ret = Session->Gina.Functions.WlxActivateUserShell(
Session->Gina.Context,
L"Default",
NULL, /* FIXME */
lpEnvironment);
DestroyEnvironmentBlock(lpEnvironment);
return ret;
}
BOOL
SetDefaultLanguage(
IN BOOL UserProfile)
{
HKEY BaseKey;
LPCWSTR SubKey;
LPCWSTR ValueName;
LONG rc;
HKEY hKey = NULL;
DWORD dwType, dwSize;
LPWSTR Value = NULL;
UNICODE_STRING ValueString;
NTSTATUS Status;
LCID Lcid;
BOOL ret = FALSE;
if (UserProfile)
{
BaseKey = HKEY_CURRENT_USER;
SubKey = L"Control Panel\\International";
ValueName = L"Locale";
}
else
{
BaseKey = HKEY_LOCAL_MACHINE;
SubKey = L"System\\CurrentControlSet\\Control\\Nls\\Language";
ValueName = L"Default";
}
rc = RegOpenKeyExW(
BaseKey,
SubKey,
0,
KEY_READ,
&hKey);
if (rc != ERROR_SUCCESS)
{
TRACE("RegOpenKeyEx() failed with error %lu\n", rc);
goto cleanup;
}
rc = RegQueryValueExW(
hKey,
ValueName,
NULL,
&dwType,
NULL,
&dwSize);
if (rc != ERROR_SUCCESS)
{
TRACE("RegQueryValueEx() failed with error %lu\n", rc);
goto cleanup;
}
else if (dwType != REG_SZ)
{
TRACE("Wrong type for %S\\%S registry entry (got 0x%lx, expected 0x%x)\n",
SubKey, ValueName, dwType, REG_SZ);
goto cleanup;
}
Value = HeapAlloc(GetProcessHeap(), 0, dwSize);
if (!Value)
{
TRACE("HeapAlloc() failed\n");
goto cleanup;
}
rc = RegQueryValueExW(
hKey,
ValueName,
NULL,
NULL,
(LPBYTE)Value,
&dwSize);
if (rc != ERROR_SUCCESS)
{
TRACE("RegQueryValueEx() failed with error %lu\n", rc);
goto cleanup;
}
/* Convert Value to a Lcid */
ValueString.Length = ValueString.MaximumLength = (USHORT)dwSize;
ValueString.Buffer = Value;
Status = RtlUnicodeStringToInteger(&ValueString, 16, (PULONG)&Lcid);
if (!NT_SUCCESS(Status))
{
TRACE("RtlUnicodeStringToInteger() failed with status 0x%08lx\n", Status);
goto cleanup;
}
TRACE("%s language is 0x%08lx\n",
UserProfile ? "User" : "System", Lcid);
Status = NtSetDefaultLocale(UserProfile, Lcid);
if (!NT_SUCCESS(Status))
{
TRACE("NtSetDefaultLocale() failed with status 0x%08lx\n", Status);
goto cleanup;
}
ret = TRUE;
cleanup:
if (hKey)
RegCloseKey(hKey);
if (Value)
HeapFree(GetProcessHeap(), 0, Value);
return ret;
}
BOOL
PlaySoundRoutine(
IN LPCWSTR FileName,
IN UINT bLogon,
IN UINT Flags)
{
typedef BOOL (WINAPI *PLAYSOUNDW)(LPCWSTR,HMODULE,DWORD);
typedef UINT (WINAPI *WAVEOUTGETNUMDEVS)(VOID);
PLAYSOUNDW Play;
WAVEOUTGETNUMDEVS waveOutGetNumDevs;
UINT NumDevs;
HMODULE hLibrary;
BOOL Ret = FALSE;
hLibrary = LoadLibraryW(L"winmm.dll");
if (hLibrary)
{
waveOutGetNumDevs = (WAVEOUTGETNUMDEVS)GetProcAddress(hLibrary, "waveOutGetNumDevs");
if (waveOutGetNumDevs)
{
NumDevs = waveOutGetNumDevs();
if (!NumDevs)
{
if (!bLogon)
{
Beep(500, 500);
}
FreeLibrary(hLibrary);
return FALSE;
}
}
Play = (PLAYSOUNDW)GetProcAddress(hLibrary, "PlaySoundW");
if (Play)
{
Ret = Play(FileName, NULL, Flags);
}
FreeLibrary(hLibrary);
}
return Ret;
}
DWORD
WINAPI
PlayLogonSoundThread(
IN LPVOID lpParameter)
{
BYTE TokenUserBuffer[256];
PTOKEN_USER pTokenUser = (TOKEN_USER*)TokenUserBuffer;
ULONG Length;
HKEY hKey;
WCHAR wszBuffer[MAX_PATH] = {0};
WCHAR wszDest[MAX_PATH];
DWORD dwSize = sizeof(wszBuffer), dwType;
SERVICE_STATUS_PROCESS Info;
UNICODE_STRING SidString;
NTSTATUS Status;
ULONG Index = 0;
SC_HANDLE hSCManager, hService;
/* Get SID of current user */
Status = NtQueryInformationToken((HANDLE)lpParameter,
TokenUser,
TokenUserBuffer,
sizeof(TokenUserBuffer),
&Length);
if (!NT_SUCCESS(Status))
{
ERR("NtQueryInformationToken failed: %x!\n", Status);
return 0;
}
/* Convert SID to string */
RtlInitEmptyUnicodeString(&SidString, wszBuffer, sizeof(wszBuffer));
Status = RtlConvertSidToUnicodeString(&SidString, pTokenUser->User.Sid, FALSE);
if (!NT_SUCCESS(Status))
{
ERR("RtlConvertSidToUnicodeString failed: %x!\n", Status);
return 0;
}
/* Build path to logon sound registry key.
Note: We can't use HKCU here, because Winlogon is owned by SYSTEM user */
if (FAILED(StringCbCopyW(wszBuffer + SidString.Length/sizeof(WCHAR),
sizeof(wszBuffer) - SidString.Length,
L"\\AppEvents\\Schemes\\Apps\\.Default\\WindowsLogon\\.Current")))
{
/* SID is too long. Should not happen. */
ERR("StringCbCopyW failed!\n");
return 0;
}
/* Open registry key and query sound path */
if (RegOpenKeyExW(HKEY_USERS, wszBuffer, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
{
ERR("RegOpenKeyExW(%ls) failed!\n", wszBuffer);
return 0;
}
if (RegQueryValueExW(hKey, NULL, NULL, &dwType,
(LPBYTE)wszBuffer, &dwSize) != ERROR_SUCCESS ||
(dwType != REG_SZ && dwType != REG_EXPAND_SZ))
{
ERR("RegQueryValueExW failed!\n");
RegCloseKey(hKey);
return 0;
}
RegCloseKey(hKey);
if (!wszBuffer[0])
{
/* No sound has been set */
ERR("No sound has been set\n");
return 0;
}
/* Expand environment variables */
if (!ExpandEnvironmentStringsW(wszBuffer, wszDest, MAX_PATH))
{
ERR("ExpandEnvironmentStringsW failed!\n");
return 0;
}
/* Open service manager */
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCManager)
{
ERR("OpenSCManager failed (%x)\n", GetLastError());
return 0;
}
/* Open wdmaud service */
hService = OpenServiceW(hSCManager, L"wdmaud", GENERIC_READ);
if (!hService)
{
/* Sound is not installed */
TRACE("Failed to open wdmaud service (%x)\n", GetLastError());
CloseServiceHandle(hSCManager);
return 0;
}
/* Wait for wdmaud start */
do
{
if (!QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&Info, sizeof(SERVICE_STATUS_PROCESS), &dwSize))
{
TRACE("QueryServiceStatusEx failed (%x)\n", GetLastError());
break;
}
if (Info.dwCurrentState == SERVICE_RUNNING)
break;
Sleep(1000);
} while (Index++ < 20);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
/* If wdmaud is not running exit */
if (Info.dwCurrentState != SERVICE_RUNNING)
{
WARN("wdmaud has not started!\n");
return 0;
}
/* Sound subsystem is running. Play logon sound. */
TRACE("Playing logon sound: %ls\n", wszDest);
PlaySoundRoutine(wszDest, TRUE, SND_FILENAME);
return 0;
}
static VOID
PlayLogonSound(
IN OUT PWLSESSION Session)
{
HANDLE hThread;
hThread = CreateThread(NULL, 0, PlayLogonSoundThread, (PVOID)Session->UserToken, 0, NULL);
if (hThread)
CloseHandle(hThread);
}
static BOOL
HandleLogon(
IN OUT PWLSESSION Session)
{
PROFILEINFOW ProfileInfo;
BOOL ret = FALSE;
/* Loading personal settings */
DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_LOADINGYOURPERSONALSETTINGS);
ProfileInfo.hProfile = INVALID_HANDLE_VALUE;
if (0 == (Session->Options & WLX_LOGON_OPT_NO_PROFILE))
{
if (Session->Profile == NULL
|| (Session->Profile->dwType != WLX_PROFILE_TYPE_V1_0
&& Session->Profile->dwType != WLX_PROFILE_TYPE_V2_0))
{
ERR("WL: Wrong profile\n");
goto cleanup;
}
/* Load the user profile */
ZeroMemory(&ProfileInfo, sizeof(PROFILEINFOW));
ProfileInfo.dwSize = sizeof(PROFILEINFOW);
ProfileInfo.dwFlags = 0;
ProfileInfo.lpUserName = Session->MprNotifyInfo.pszUserName;
ProfileInfo.lpProfilePath = Session->Profile->pszProfile;
if (Session->Profile->dwType >= WLX_PROFILE_TYPE_V2_0)
{
ProfileInfo.lpDefaultPath = Session->Profile->pszNetworkDefaultUserProfile;
ProfileInfo.lpServerName = Session->Profile->pszServerName;
ProfileInfo.lpPolicyPath = Session->Profile->pszPolicy;
}
if (!LoadUserProfileW(Session->UserToken, &ProfileInfo))
{
ERR("WL: LoadUserProfileW() failed\n");
goto cleanup;
}
}
/* Create environment block for the user */
if (!CreateUserEnvironment(Session))
{
WARN("WL: SetUserEnvironment() failed\n");
goto cleanup;
}
DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_APPLYINGYOURPERSONALSETTINGS);
UpdatePerUserSystemParameters(0, TRUE);
/* Set default language */
if (!SetDefaultLanguage(TRUE))
{
WARN("WL: SetDefaultLanguage() failed\n");
goto cleanup;
}
if (!StartUserShell(Session))
{
//WCHAR StatusMsg[256];
WARN("WL: WlxActivateUserShell() failed\n");
//LoadStringW(hAppInstance, IDS_FAILEDACTIVATEUSERSHELL, StatusMsg, sizeof(StatusMsg));
//MessageBoxW(0, StatusMsg, NULL, MB_ICONERROR);
goto cleanup;
}
if (!InitializeScreenSaver(Session))
WARN("WL: Failed to initialize screen saver\n");
Session->hProfileInfo = ProfileInfo.hProfile;
/* Logon has successed. Play sound. */
PlayLogonSound(Session);
ret = TRUE;
cleanup:
if (Session->Profile)
{
HeapFree(GetProcessHeap(), 0, Session->Profile->pszProfile);
HeapFree(GetProcessHeap(), 0, Session->Profile);
}
Session->Profile = NULL;
if (!ret
&& ProfileInfo.hProfile != INVALID_HANDLE_VALUE)
{
UnloadUserProfile(WLSession->UserToken, ProfileInfo.hProfile);
}
RemoveStatusMessage(Session);
if (!ret)
{
CloseHandle(Session->UserToken);
Session->UserToken = NULL;
}
return ret;
}
#define EWX_ACTION_MASK 0xffffffeb
#define EWX_FLAGS_MASK 0x00000014
typedef struct tagLOGOFF_SHUTDOWN_DATA
{
UINT Flags;
PWLSESSION Session;
} LOGOFF_SHUTDOWN_DATA, *PLOGOFF_SHUTDOWN_DATA;
static DWORD WINAPI
LogoffShutdownThread(LPVOID Parameter)
{
PLOGOFF_SHUTDOWN_DATA LSData = (PLOGOFF_SHUTDOWN_DATA)Parameter;
if (LSData->Session->UserToken != NULL && !ImpersonateLoggedOnUser(LSData->Session->UserToken))
{
ERR("ImpersonateLoggedOnUser() failed with error %lu\n", GetLastError());
return 0;
}
/* Close processes of the interactive user */
if (!ExitWindowsEx(
EWX_INTERNAL_KILL_USER_APPS | (LSData->Flags & EWX_FLAGS_MASK) |
(EWX_LOGOFF == (LSData->Flags & EWX_ACTION_MASK) ? EWX_INTERNAL_FLAG_LOGOFF : 0),
0))
{
ERR("Unable to kill user apps, error %lu\n", GetLastError());
RevertToSelf();
return 0;
}
/* FIXME: Call ExitWindowsEx() to terminate COM processes */
if (LSData->Session->UserToken)
RevertToSelf();
return 1;
}
static NTSTATUS
CreateLogoffSecurityAttributes(
OUT PSECURITY_ATTRIBUTES* ppsa)
{
/* The following code is not working yet and messy */
/* Still, it gives some ideas about data types and functions involved and */
/* required to set up a SECURITY_DESCRIPTOR for a SECURITY_ATTRIBUTES */
/* instance for a thread, to allow that thread to ImpersonateLoggedOnUser(). */
/* Specifically THREAD_SET_THREAD_TOKEN is required. */
PSECURITY_DESCRIPTOR SecurityDescriptor = NULL;
PSECURITY_ATTRIBUTES psa = 0;
BYTE* pMem;
PACL pACL;
EXPLICIT_ACCESS Access;
PSID pEveryoneSID = NULL;
static SID_IDENTIFIER_AUTHORITY WorldAuthority = { SECURITY_WORLD_SID_AUTHORITY };
*ppsa = NULL;
// Let's first try to enumerate what kind of data we need for this to ever work:
// 1. The Winlogon SID, to be able to give it THREAD_SET_THREAD_TOKEN.
// 2. The users SID (the user trying to logoff, or rather shut down the system).
// 3. At least two EXPLICIT_ACCESS instances:
// 3.1 One for Winlogon itself, giving it the rights
// required to THREAD_SET_THREAD_TOKEN (as it's needed to successfully call
// ImpersonateLoggedOnUser).
// 3.2 One for the user, to allow *that* thread to perform its work.
// 4. An ACL to hold the these EXPLICIT_ACCESS ACE's.
// 5. A SECURITY_DESCRIPTOR to hold the ACL, and finally.
// 6. A SECURITY_ATTRIBUTES instance to pull all of this required stuff
// together, to hand it to CreateThread.
//
// However, it seems struct LOGOFF_SHUTDOWN_DATA doesn't contain
// these required SID's, why they'd have to be added.
// The Winlogon's own SID should probably only be created once,
// while the user's SID obviously must be created for each new user.
// Might as well store it when the user logs on?
if(!AllocateAndInitializeSid(&WorldAuthority,
1,
SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0,
&pEveryoneSID))
{
ERR("Failed to initialize security descriptor for logoff thread!\n");
return STATUS_UNSUCCESSFUL;
}
/* set up the required security attributes to be able to shut down */
/* To save space and time, allocate a single block of memory holding */
/* both SECURITY_ATTRIBUTES and SECURITY_DESCRIPTOR */
pMem = HeapAlloc(GetProcessHeap(),
0,
sizeof(SECURITY_ATTRIBUTES) +
SECURITY_DESCRIPTOR_MIN_LENGTH +
sizeof(ACL));
if (!pMem)
{
ERR("Failed to allocate memory for logoff security descriptor!\n");
return STATUS_NO_MEMORY;
}
/* Note that the security descriptor needs to be in _absolute_ format, */
/* meaning its members must be pointers to other structures, rather */
/* than the relative format using offsets */
psa = (PSECURITY_ATTRIBUTES)pMem;
SecurityDescriptor = (PSECURITY_DESCRIPTOR)(pMem + sizeof(SECURITY_ATTRIBUTES));
pACL = (PACL)(((PBYTE)SecurityDescriptor) + SECURITY_DESCRIPTOR_MIN_LENGTH);
// Initialize an EXPLICIT_ACCESS structure for an ACE.
// The ACE will allow this thread to log off (and shut down the system, currently).
ZeroMemory(&Access, sizeof(Access));
Access.grfAccessPermissions = THREAD_SET_THREAD_TOKEN;
Access.grfAccessMode = SET_ACCESS; // GRANT_ACCESS?
Access.grfInheritance = NO_INHERITANCE;
Access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
Access.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
Access.Trustee.ptstrName = pEveryoneSID;
if (SetEntriesInAcl(1, &Access, NULL, &pACL) != ERROR_SUCCESS)
{
ERR("Failed to set Access Rights for logoff thread. Logging out will most likely fail.\n");
HeapFree(GetProcessHeap(), 0, pMem);
return STATUS_UNSUCCESSFUL;
}
if (!InitializeSecurityDescriptor(SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION))
{
ERR("Failed to initialize security descriptor for logoff thread!\n");
HeapFree(GetProcessHeap(), 0, pMem);
return STATUS_UNSUCCESSFUL;
}
if (!SetSecurityDescriptorDacl(SecurityDescriptor,
TRUE, // bDaclPresent flag
pACL,
FALSE)) // not a default DACL
{
ERR("SetSecurityDescriptorDacl Error %lu\n", GetLastError());
HeapFree(GetProcessHeap(), 0, pMem);
return STATUS_UNSUCCESSFUL;
}
psa->nLength = sizeof(SECURITY_ATTRIBUTES);
psa->lpSecurityDescriptor = SecurityDescriptor;
psa->bInheritHandle = FALSE;
*ppsa = psa;
return STATUS_SUCCESS;
}
static VOID
DestroyLogoffSecurityAttributes(
IN PSECURITY_ATTRIBUTES psa)
{
if (psa)
{
HeapFree(GetProcessHeap(), 0, psa);
}
}
static NTSTATUS
HandleLogoff(
IN OUT PWLSESSION Session,
IN UINT Flags)
{
PLOGOFF_SHUTDOWN_DATA LSData;
PSECURITY_ATTRIBUTES psa;
HANDLE hThread;
DWORD exitCode;
NTSTATUS Status;
DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_SAVEYOURSETTINGS);
/* Prepare data for logoff thread */
LSData = HeapAlloc(GetProcessHeap(), 0, sizeof(LOGOFF_SHUTDOWN_DATA));
if (!LSData)
{
ERR("Failed to allocate mem for thread data\n");
return STATUS_NO_MEMORY;
}
LSData->Flags = Flags;
LSData->Session = Session;
Status = CreateLogoffSecurityAttributes(&psa);
if (!NT_SUCCESS(Status))
{
ERR("Failed to create a required security descriptor. Status 0x%08lx\n", Status);
HeapFree(GetProcessHeap(), 0, LSData);
return Status;
}
/* Run logoff thread */
hThread = CreateThread(psa, 0, LogoffShutdownThread, (LPVOID)LSData, 0, NULL);
/* we're done with the SECURITY_DESCRIPTOR */
DestroyLogoffSecurityAttributes(psa);
psa = NULL;
if (!hThread)
{
ERR("Unable to create logoff thread, error %lu\n", GetLastError());
HeapFree(GetProcessHeap(), 0, LSData);
return STATUS_UNSUCCESSFUL;
}
WaitForSingleObject(hThread, INFINITE);
HeapFree(GetProcessHeap(), 0, LSData);
if (!GetExitCodeThread(hThread, &exitCode))
{
ERR("Unable to get exit code of logoff thread (error %lu)\n", GetLastError());
CloseHandle(hThread);
return STATUS_UNSUCCESSFUL;
}
CloseHandle(hThread);
if (exitCode == 0)
{
ERR("Logoff thread returned failure\n");
return STATUS_UNSUCCESSFUL;
}
UnloadUserProfile(Session->UserToken, Session->hProfileInfo);
CloseHandle(Session->UserToken);
UpdatePerUserSystemParameters(0, FALSE);
Session->LogonStatus = WKSTA_IS_LOGGED_OFF;
Session->UserToken = NULL;
return STATUS_SUCCESS;
}
static INT_PTR CALLBACK
ShutdownComputerWindowProc(
IN HWND hwndDlg,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (uMsg)
{
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_BTNSHTDOWNCOMPUTER:
EndDialog(hwndDlg, IDC_BTNSHTDOWNCOMPUTER);
return TRUE;
}
break;
}
case WM_INITDIALOG:
{
RemoveMenu(GetSystemMenu(hwndDlg, FALSE), SC_CLOSE, MF_BYCOMMAND);
SetFocus(GetDlgItem(hwndDlg, IDC_BTNSHTDOWNCOMPUTER));
return TRUE;
}
}
return FALSE;
}
static VOID
UninitializeSAS(
IN OUT PWLSESSION Session)
{
if (Session->SASWindow)
{
DestroyWindow(Session->SASWindow);
Session->SASWindow = NULL;
}
if (Session->hEndOfScreenSaverThread)
SetEvent(Session->hEndOfScreenSaverThread);
UnregisterClassW(WINLOGON_SAS_CLASS, hAppInstance);
}
NTSTATUS
HandleShutdown(
IN OUT PWLSESSION Session,
IN DWORD wlxAction)
{
PLOGOFF_SHUTDOWN_DATA LSData;
HANDLE hThread;
DWORD exitCode;
DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_REACTOSISSHUTTINGDOWN);
/* Prepare data for shutdown thread */
LSData = HeapAlloc(GetProcessHeap(), 0, sizeof(LOGOFF_SHUTDOWN_DATA));
if (!LSData)
{
ERR("Failed to allocate mem for thread data\n");
return STATUS_NO_MEMORY;
}
if (wlxAction == WLX_SAS_ACTION_SHUTDOWN_POWER_OFF)
LSData->Flags = EWX_POWEROFF;
else if (wlxAction == WLX_SAS_ACTION_SHUTDOWN_REBOOT)
LSData->Flags = EWX_REBOOT;
else
LSData->Flags = EWX_SHUTDOWN;
LSData->Session = Session;
/* Run shutdown thread */
hThread = CreateThread(NULL, 0, LogoffShutdownThread, (LPVOID)LSData, 0, NULL);
if (!hThread)
{
ERR("Unable to create shutdown thread, error %lu\n", GetLastError());
HeapFree(GetProcessHeap(), 0, LSData);
return STATUS_UNSUCCESSFUL;
}
WaitForSingleObject(hThread, INFINITE);
HeapFree(GetProcessHeap(), 0, LSData);
if (!GetExitCodeThread(hThread, &exitCode))
{
ERR("Unable to get exit code of shutdown thread (error %lu)\n", GetLastError());
CloseHandle(hThread);
return STATUS_UNSUCCESSFUL;
}
CloseHandle(hThread);
if (exitCode == 0)
{
ERR("Shutdown thread returned failure\n");
return STATUS_UNSUCCESSFUL;
}
/* Destroy SAS window */
UninitializeSAS(Session);
FIXME("FIXME: Call SMSS API #1\n");
if (wlxAction == WLX_SAS_ACTION_SHUTDOWN_REBOOT)
NtShutdownSystem(ShutdownReboot);
else
{
if (FALSE)
{
/* FIXME - only show this dialog if it's a shutdown and the computer doesn't support APM */
DialogBox(hAppInstance, MAKEINTRESOURCE(IDD_SHUTDOWNCOMPUTER), GetDesktopWindow(), ShutdownComputerWindowProc);
}
NtShutdownSystem(ShutdownNoReboot);
}
return STATUS_SUCCESS;
}
static VOID
DoGenericAction(
IN OUT PWLSESSION Session,
IN DWORD wlxAction)
{
switch (wlxAction)
{
case WLX_SAS_ACTION_LOGON: /* 0x01 */
if (HandleLogon(Session))
{
SwitchDesktop(Session->ApplicationDesktop);
Session->LogonStatus = WKSTA_IS_LOGGED_ON;
}
else
Session->Gina.Functions.WlxDisplaySASNotice(Session->Gina.Context);
break;
case WLX_SAS_ACTION_NONE: /* 0x02 */
break;
case WLX_SAS_ACTION_LOCK_WKSTA: /* 0x03 */
if (Session->Gina.Functions.WlxIsLockOk(Session->Gina.Context))
{
SwitchDesktop(WLSession->WinlogonDesktop);
Session->LogonStatus = WKSTA_IS_LOCKED;
Session->Gina.Functions.WlxDisplayLockedNotice(Session->Gina.Context);
}
break;
case WLX_SAS_ACTION_LOGOFF: /* 0x04 */
case WLX_SAS_ACTION_SHUTDOWN: /* 0x05 */
case WLX_SAS_ACTION_SHUTDOWN_POWER_OFF: /* 0x0a */
case WLX_SAS_ACTION_SHUTDOWN_REBOOT: /* 0x0b */
if (Session->LogonStatus != WKSTA_IS_LOGGED_OFF)
{
if (!Session->Gina.Functions.WlxIsLogoffOk(Session->Gina.Context))
break;
SwitchDesktop(WLSession->WinlogonDesktop);
Session->Gina.Functions.WlxLogoff(Session->Gina.Context);
if (!NT_SUCCESS(HandleLogoff(Session, EWX_LOGOFF)))
{
RemoveStatusMessage(Session);
break;
}
}
if (WLX_SHUTTINGDOWN(wlxAction))
{
Session->Gina.Functions.WlxShutdown(Session->Gina.Context, wlxAction);
if (!NT_SUCCESS(HandleShutdown(Session, wlxAction)))
{
RemoveStatusMessage(Session);
Session->Gina.Functions.WlxDisplaySASNotice(Session->Gina.Context);
}
}
else
{
RemoveStatusMessage(Session);
Session->Gina.Functions.WlxDisplaySASNotice(Session->Gina.Context);
}
break;
case WLX_SAS_ACTION_TASKLIST: /* 0x07 */
SwitchDesktop(WLSession->ApplicationDesktop);
StartTaskManager(Session);
break;
case WLX_SAS_ACTION_UNLOCK_WKSTA: /* 0x08 */
SwitchDesktop(WLSession->ApplicationDesktop);
Session->LogonStatus = WKSTA_IS_LOGGED_ON;
break;
default:
WARN("Unknown SAS action 0x%lx\n", wlxAction);
}
}
static VOID
DispatchSAS(
IN OUT PWLSESSION Session,
IN DWORD dwSasType)
{
DWORD wlxAction = WLX_SAS_ACTION_NONE;
if (Session->LogonStatus == WKSTA_IS_LOGGED_ON)
wlxAction = (DWORD)Session->Gina.Functions.WlxLoggedOnSAS(Session->Gina.Context, dwSasType, NULL);
else if (Session->LogonStatus == WKSTA_IS_LOCKED)
wlxAction = (DWORD)Session->Gina.Functions.WlxWkstaLockedSAS(Session->Gina.Context, dwSasType);
else
{
/* Display a new dialog (if necessary) */
switch (dwSasType)
{
case WLX_SAS_TYPE_TIMEOUT: /* 0x00 */
{
Session->Gina.Functions.WlxDisplaySASNotice(Session->Gina.Context);
break;
}
default:
{
PSID LogonSid = NULL; /* FIXME */
Session->Options = 0;
wlxAction = (DWORD)Session->Gina.Functions.WlxLoggedOutSAS(
Session->Gina.Context,
Session->SASAction,
&Session->LogonId,
LogonSid,
&Session->Options,
&Session->UserToken,
&Session->MprNotifyInfo,
(PVOID*)&Session->Profile);
break;
}
}
}
if (dwSasType == WLX_SAS_TYPE_SCRNSVR_TIMEOUT)
{
BOOL bSecure = TRUE;
if (!Session->Gina.Functions.WlxScreenSaverNotify(Session->Gina.Context, &bSecure))
{
/* Skip start of screen saver */
SetEvent(Session->hEndOfScreenSaver);
}
else
{
if (bSecure)
DoGenericAction(Session, WLX_SAS_ACTION_LOCK_WKSTA);
StartScreenSaver(Session);
}
}
else if (dwSasType == WLX_SAS_TYPE_SCRNSVR_ACTIVITY)
SetEvent(Session->hUserActivity);
DoGenericAction(Session, wlxAction);
}
static BOOL
RegisterHotKeys(
IN PWLSESSION Session,
IN HWND hwndSAS)
{
/* Register Ctrl+Alt+Del Hotkey */
if (!RegisterHotKey(hwndSAS, HK_CTRL_ALT_DEL, MOD_CONTROL | MOD_ALT, VK_DELETE))
{
ERR("WL: Unable to register Ctrl+Alt+Del hotkey!\n");
return FALSE;
}
/* Register Ctrl+Shift+Esc (optional) */
Session->TaskManHotkey = RegisterHotKey(hwndSAS, HK_CTRL_SHIFT_ESC, MOD_CONTROL | MOD_SHIFT, VK_ESCAPE);
if (!Session->TaskManHotkey)
WARN("WL: Warning: Unable to register Ctrl+Alt+Esc hotkey!\n");
return TRUE;
}
static BOOL
UnregisterHotKeys(
IN PWLSESSION Session,
IN HWND hwndSAS)
{
/* Unregister hotkeys */
UnregisterHotKey(hwndSAS, HK_CTRL_ALT_DEL);
if (Session->TaskManHotkey)
UnregisterHotKey(hwndSAS, HK_CTRL_SHIFT_ESC);
return TRUE;
}
static NTSTATUS
CheckForShutdownPrivilege(
IN DWORD RequestingProcessId)
{
HANDLE Process;
HANDLE Token;
BOOL CheckResult;
PPRIVILEGE_SET PrivSet;
TRACE("CheckForShutdownPrivilege()\n");
Process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, RequestingProcessId);
if (!Process)
{
WARN("OpenProcess() failed with error %lu\n", GetLastError());
return STATUS_INVALID_HANDLE;
}
if (!OpenProcessToken(Process, TOKEN_QUERY, &Token))
{
WARN("OpenProcessToken() failed with error %lu\n", GetLastError());
CloseHandle(Process);
return STATUS_INVALID_HANDLE;
}
CloseHandle(Process);
PrivSet = HeapAlloc(GetProcessHeap(), 0, sizeof(PRIVILEGE_SET) + sizeof(LUID_AND_ATTRIBUTES));
if (!PrivSet)
{
ERR("Failed to allocate mem for privilege set\n");
CloseHandle(Token);
return STATUS_NO_MEMORY;
}
PrivSet->PrivilegeCount = 1;
PrivSet->Control = PRIVILEGE_SET_ALL_NECESSARY;
if (!LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &PrivSet->Privilege[0].Luid))
{
WARN("LookupPrivilegeValue() failed with error %lu\n", GetLastError());
HeapFree(GetProcessHeap(), 0, PrivSet);
CloseHandle(Token);
return STATUS_UNSUCCESSFUL;
}
if (!PrivilegeCheck(Token, PrivSet, &CheckResult))
{
WARN("PrivilegeCheck() failed with error %lu\n", GetLastError());
HeapFree(GetProcessHeap(), 0, PrivSet);
CloseHandle(Token);
return STATUS_ACCESS_DENIED;
}
HeapFree(GetProcessHeap(), 0, PrivSet);
CloseHandle(Token);
if (!CheckResult)
{
WARN("SE_SHUTDOWN privilege not enabled\n");
return STATUS_ACCESS_DENIED;
}
return STATUS_SUCCESS;
}
BOOL
WINAPI
HandleMessageBeep(UINT uType)
{
LPWSTR EventName;
switch(uType)
{
case 0xFFFFFFFF:
EventName = NULL;
break;
case MB_OK:
EventName = L"SystemDefault";
break;
case MB_ICONASTERISK:
EventName = L"SystemAsterisk";
break;
case MB_ICONEXCLAMATION:
EventName = L"SystemExclamation";
break;
case MB_ICONHAND:
EventName = L"SystemHand";
break;
case MB_ICONQUESTION:
EventName = L"SystemQuestion";
break;
default:
WARN("Unhandled type %d\n", uType);
EventName = L"SystemDefault";
}
return PlaySoundRoutine(EventName, FALSE, SND_ALIAS | SND_NOWAIT | SND_NOSTOP | SND_ASYNC);
}
static LRESULT CALLBACK
SASWindowProc(
IN HWND hwndDlg,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam)
{
PWLSESSION Session = (PWLSESSION)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
switch (uMsg)
{
case WM_HOTKEY:
{
switch (lParam)
{
case MAKELONG(MOD_CONTROL | MOD_ALT, VK_DELETE):
{
TRACE("SAS: CONTROL+ALT+DELETE\n");
if (!Session->Gina.UseCtrlAltDelete)
break;
PostMessageW(Session->SASWindow, WLX_WM_SAS, WLX_SAS_TYPE_CTRL_ALT_DEL, 0);
return TRUE;
}
case MAKELONG(MOD_CONTROL | MOD_SHIFT, VK_ESCAPE):
{
TRACE("SAS: CONTROL+SHIFT+ESCAPE\n");
DoGenericAction(Session, WLX_SAS_ACTION_TASKLIST);
return TRUE;
}
}
break;
}
case WM_CREATE:
{
/* Get the session pointer from the create data */
Session = (PWLSESSION)((LPCREATESTRUCT)lParam)->lpCreateParams;
/* Save the Session pointer */
SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)Session);
if (GetSetupType())
return TRUE;
return RegisterHotKeys(Session, hwndDlg);
}
case WM_DESTROY:
{
if (!GetSetupType())
UnregisterHotKeys(Session, hwndDlg);
return TRUE;
}
case WM_SETTINGCHANGE:
{
UINT uiAction = (UINT)wParam;
if (uiAction == SPI_SETSCREENSAVETIMEOUT
|| uiAction == SPI_SETSCREENSAVEACTIVE)
{
SetEvent(Session->hScreenSaverParametersChanged);
}
return TRUE;
}
case WM_LOGONNOTIFY:
{
switch(wParam)
{
case LN_MESSAGE_BEEP:
{
return HandleMessageBeep(lParam);
}
case LN_SHELL_EXITED:
{
/* lParam is the exit code */
if(lParam != 1)
{
SetTimer(hwndDlg, 1, 1000, NULL);
}
break;
}
case LN_START_SCREENSAVE:
{
BOOL bSecure = FALSE;
if (inScrn)
break;
inScrn = TRUE;
// lParam 1 == Secure
if (lParam)
{
if (Session->Gina.Functions.WlxScreenSaverNotify(Session->Gina.Context, &bSecure))
{
if (bSecure) DoGenericAction(Session, WLX_SAS_ACTION_LOCK_WKSTA);
}
}
StartScreenSaver(Session);
inScrn = FALSE;
break;
}
default:
{
ERR("WM_LOGONNOTIFY case %d is unimplemented\n", wParam);
}
}
return 0;
}
case WM_TIMER:
{
if (wParam == 1)
{
KillTimer(hwndDlg, 1);
StartUserShell(Session);
}
break;
}
case WLX_WM_SAS:
{
DispatchSAS(Session, (DWORD)wParam);
return TRUE;
}
case PM_WINLOGON_EXITWINDOWS:
{
UINT Flags = (UINT)lParam;
UINT Action = Flags & EWX_ACTION_MASK;
DWORD wlxAction;
/* Check parameters */
switch (Action)
{
case EWX_LOGOFF: wlxAction = WLX_SAS_ACTION_LOGOFF; break;
case EWX_SHUTDOWN: wlxAction = WLX_SAS_ACTION_SHUTDOWN; break;
case EWX_REBOOT: wlxAction = WLX_SAS_ACTION_SHUTDOWN_REBOOT; break;
case EWX_POWEROFF: wlxAction = WLX_SAS_ACTION_SHUTDOWN_POWER_OFF; break;
default:
{
ERR("Invalid ExitWindows action 0x%x\n", Action);
return STATUS_INVALID_PARAMETER;
}
}
if (WLX_SHUTTINGDOWN(wlxAction))
{
NTSTATUS Status = CheckForShutdownPrivilege((DWORD)wParam);
if (!NT_SUCCESS(Status))
return Status;
}
DoGenericAction(Session, wlxAction);
return 1;
}
}
return DefWindowProc(hwndDlg, uMsg, wParam, lParam);
}
BOOL
InitializeSAS(
IN OUT PWLSESSION Session)
{
WNDCLASSEXW swc;
BOOL ret = FALSE;
if (!SwitchDesktop(Session->WinlogonDesktop))
{
ERR("WL: Failed to switch to winlogon desktop\n");
goto cleanup;
}
/* Register SAS window class */
swc.cbSize = sizeof(WNDCLASSEXW);
swc.style = CS_SAVEBITS;
swc.lpfnWndProc = SASWindowProc;
swc.cbClsExtra = 0;
swc.cbWndExtra = 0;
swc.hInstance = hAppInstance;
swc.hIcon = NULL;
swc.hCursor = NULL;
swc.hbrBackground = NULL;
swc.lpszMenuName = NULL;
swc.lpszClassName = WINLOGON_SAS_CLASS;
swc.hIconSm = NULL;
if (RegisterClassExW(&swc) == 0)
{
ERR("WL: Failed to register SAS window class\n");
goto cleanup;
}
/* Create invisible SAS window */
Session->SASWindow = CreateWindowExW(
0,
WINLOGON_SAS_CLASS,
WINLOGON_SAS_TITLE,
WS_POPUP,
0, 0, 0, 0, 0, 0,
hAppInstance, Session);
if (!Session->SASWindow)
{
ERR("WL: Failed to create SAS window\n");
goto cleanup;
}
/* Register SAS window to receive SAS notifications */
if (!SetLogonNotifyWindow(Session->SASWindow, Session->InteractiveWindowStation))
{
ERR("WL: Failed to register SAS window\n");
goto cleanup;
}
if (!SetDefaultLanguage(FALSE))
return FALSE;
ret = TRUE;
cleanup:
if (!ret)
UninitializeSAS(Session);
return ret;
}