/*
 * 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;
}
