/* * 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) * Arnav Bhatt (arnavbhatt288@gmail.com) * UPDATE HISTORY: * Created 28/03/2004 */ /* INCLUDES *****************************************************************/ #include "winlogon.h" #define WIN32_LEAN_AND_MEAN #include #include #include #include #include /* 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 // #define EWX_FLAGS_MASK 0x00000014 // #define EWX_ACTION_MASK ~EWX_FLAGS_MASK // FIXME: At the moment we use this value (select the lowbyte flags and some highbytes ones). // It should be set such that it makes winlogon accepting only valid flags. #define EWX_ACTION_MASK 0x5C0F typedef struct tagLOGOFF_SHUTDOWN_DATA { UINT Flags; PWLSESSION Session; } LOGOFF_SHUTDOWN_DATA, *PLOGOFF_SHUTDOWN_DATA; static BOOL ExitReactOSInProgress = FALSE; LUID LuidNone = {0, 0}; /* 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 PWLSESSION Session) { BOOL ret = FALSE; BOOL UserProfile; LONG rc; HKEY UserKey, hKey = NULL; LPCWSTR SubKey, ValueName; DWORD dwType, dwSize; LPWSTR Value = NULL; UNICODE_STRING ValueString; NTSTATUS Status; LCID Lcid; UserProfile = (Session && Session->UserToken); if (UserProfile && !ImpersonateLoggedOnUser(Session->UserToken)) { ERR("WL: ImpersonateLoggedOnUser() failed with error %lu\n", GetLastError()); return FALSE; // FIXME: ... or use the default language of the system?? // UserProfile = FALSE; } if (UserProfile) { rc = RegOpenCurrentUser(MAXIMUM_ALLOWED, &UserKey); if (rc != ERROR_SUCCESS) { TRACE("RegOpenCurrentUser() failed with error %lu\n", rc); goto cleanup; } SubKey = L"Control Panel\\International"; ValueName = L"Locale"; } else { UserKey = NULL; SubKey = L"System\\CurrentControlSet\\Control\\Nls\\Language"; ValueName = L"Default"; } rc = RegOpenKeyExW(UserKey ? UserKey : HKEY_LOCAL_MACHINE, SubKey, 0, KEY_READ, &hKey); if (UserKey) RegCloseKey(UserKey); 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 (Value) HeapFree(GetProcessHeap(), 0, Value); if (hKey) RegCloseKey(hKey); if (UserProfile) RevertToSelf(); 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; // // FIXME: Isn't it possible to *JUST* impersonate the current user // *AND* open its HKCU?? // /* 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 the service manager */ hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (!hSCManager) { ERR("OpenSCManager failed (%x)\n", GetLastError()); return 0; } /* Open the wdmaud service */ hService = OpenServiceW(hSCManager, L"wdmaud", GENERIC_READ); if (!hService) { /* The service is not installed */ TRACE("Failed to open wdmaud service (%x)\n", GetLastError()); CloseServiceHandle(hSCManager); return 0; } /* Wait for wdmaud to 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 AllowWinstaAccess(PWLSESSION Session) { BOOL bSuccess = FALSE; DWORD dwIndex; DWORD dwLength = 0; PTOKEN_GROUPS ptg = NULL; PSID psid; TOKEN_STATISTICS Stats; DWORD cbStats; DWORD ret; // Get required buffer size and allocate the TOKEN_GROUPS buffer. if (!GetTokenInformation(Session->UserToken, TokenGroups, ptg, 0, &dwLength)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return FALSE; ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); if (ptg == NULL) return FALSE; } // Get the token group information from the access token. if (!GetTokenInformation(Session->UserToken, TokenGroups, ptg, dwLength, &dwLength)) { goto Cleanup; } // Loop through the groups to find the logon SID. for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++) { if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) { psid = ptg->Groups[dwIndex].Sid; break; } } dwLength = GetLengthSid(psid); if (!GetTokenInformation(Session->UserToken, TokenStatistics, &Stats, sizeof(TOKEN_STATISTICS), &cbStats)) { WARN("Couldn't get Authentication id from user token!\n"); goto Cleanup; } AddAceToWindowStation(Session->InteractiveWindowStation, psid); ret = SetWindowStationUser(Session->InteractiveWindowStation, &Stats.AuthenticationId, psid, dwLength); TRACE("SetWindowStationUser returned 0x%x\n", ret); bSuccess = TRUE; Cleanup: // Free the buffer for the token groups. if (ptg != NULL) HeapFree(GetProcessHeap(), 0, (LPVOID)ptg); return bSuccess; } static VOID RestoreAllConnections(PWLSESSION Session) { DWORD dRet; HANDLE hEnum; LPNETRESOURCE lpRes; DWORD dSize = 0x1000; DWORD dCount = -1; LPNETRESOURCE lpCur; BOOL UserProfile; UserProfile = (Session && Session->UserToken); if (!UserProfile) { return; } if (!ImpersonateLoggedOnUser(Session->UserToken)) { ERR("WL: ImpersonateLoggedOnUser() failed with error %lu\n", GetLastError()); return; } dRet = WNetOpenEnum(RESOURCE_REMEMBERED, RESOURCETYPE_DISK, 0, NULL, &hEnum); if (dRet != WN_SUCCESS) { ERR("Failed to open enumeration: %lu\n", dRet); goto quit; } lpRes = HeapAlloc(GetProcessHeap(), 0, dSize); if (!lpRes) { ERR("Failed to allocate memory\n"); WNetCloseEnum(hEnum); goto quit; } do { dSize = 0x1000; dCount = -1; memset(lpRes, 0, dSize); dRet = WNetEnumResource(hEnum, &dCount, lpRes, &dSize); if (dRet == WN_SUCCESS || dRet == WN_MORE_DATA) { lpCur = lpRes; for (; dCount; dCount--) { WNetAddConnection(lpCur->lpRemoteName, NULL, lpCur->lpLocalName); lpCur++; } } } while (dRet != WN_NO_MORE_ENTRIES); HeapFree(GetProcessHeap(), 0, lpRes); WNetCloseEnum(hEnum); quit: RevertToSelf(); } 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; } CallNotificationDlls(Session, LogonHandler); DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_APPLYINGYOURPERSONALSETTINGS); UpdatePerUserSystemParameters(0, TRUE); /* Set default user language */ if (!SetDefaultLanguage(Session)) { WARN("WL: SetDefaultLanguage() failed\n"); goto cleanup; } AllowWinstaAccess(Session); /* Connect remote resources */ RestoreAllConnections(Session); if (!StartUserShell(Session)) { //WCHAR StatusMsg[256]; WARN("WL: WlxActivateUserShell() failed\n"); //LoadStringW(hAppInstance, IDS_FAILEDACTIVATEUSERSHELL, StatusMsg, sizeof(StatusMsg) / sizeof(StatusMsg[0])); //MessageBoxW(0, StatusMsg, NULL, MB_ICONERROR); goto cleanup; } CallNotificationDlls(Session, StartShellHandler); if (!InitializeScreenSaver(Session)) WARN("WL: Failed to initialize screen saver\n"); Session->hProfileInfo = ProfileInfo.hProfile; /* Logon has succeeded. 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(Session->UserToken, ProfileInfo.hProfile); } RemoveStatusMessage(Session); if (!ret) { SetWindowStationUser(Session->InteractiveWindowStation, &LuidNone, NULL, 0); CloseHandle(Session->UserToken); Session->UserToken = NULL; } if (ret) { SwitchDesktop(Session->ApplicationDesktop); Session->LogonState = STATE_LOGGED_ON; } return ret; } static DWORD WINAPI LogoffShutdownThread( LPVOID Parameter) { DWORD ret = 1; PLOGOFF_SHUTDOWN_DATA LSData = (PLOGOFF_SHUTDOWN_DATA)Parameter; UINT uFlags; if (LSData->Session->UserToken != NULL && !ImpersonateLoggedOnUser(LSData->Session->UserToken)) { ERR("ImpersonateLoggedOnUser() failed with error %lu\n", GetLastError()); return 0; } // FIXME: To be really fixed: need to check what needs to be kept and what needs to be removed there. // // uFlags = EWX_INTERNAL_KILL_USER_APPS | (LSData->Flags & EWX_FLAGS_MASK) | // ((LSData->Flags & EWX_ACTION_MASK) == EWX_LOGOFF ? EWX_CALLER_WINLOGON_LOGOFF : 0); uFlags = EWX_CALLER_WINLOGON | (LSData->Flags & 0x0F); TRACE("In LogoffShutdownThread with uFlags == 0x%x; exit_in_progress == %s\n", uFlags, ExitReactOSInProgress ? "true" : "false"); ExitReactOSInProgress = TRUE; /* Close processes of the interactive user */ if (!ExitWindowsEx(uFlags, 0)) { ERR("Unable to kill user apps, error %lu\n", GetLastError()); ret = 0; } /* Cancel all the user connections */ WNetClearConnections(NULL); if (LSData->Session->UserToken) RevertToSelf(); return ret; } static DWORD WINAPI KillComProcesses( LPVOID Parameter) { DWORD ret = 1; PLOGOFF_SHUTDOWN_DATA LSData = (PLOGOFF_SHUTDOWN_DATA)Parameter; TRACE("In KillComProcesses\n"); if (LSData->Session->UserToken != NULL && !ImpersonateLoggedOnUser(LSData->Session->UserToken)) { ERR("ImpersonateLoggedOnUser() failed with error %lu\n", GetLastError()); return 0; } /* Attempt to kill remaining processes. No notifications needed. */ if (!ExitWindowsEx(EWX_CALLER_WINLOGON | EWX_NONOTIFY | EWX_FORCE | EWX_LOGOFF, 0)) { ERR("Unable to kill COM apps, error %lu\n", GetLastError()); ret = 0; } if (LSData->Session->UserToken) RevertToSelf(); return ret; } 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; /* 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); if (!hThread) { ERR("Unable to create logoff thread, error %lu\n", GetLastError()); DestroyLogoffSecurityAttributes(psa); HeapFree(GetProcessHeap(), 0, LSData); return STATUS_UNSUCCESSFUL; } WaitForSingleObject(hThread, INFINITE); if (!GetExitCodeThread(hThread, &exitCode)) { ERR("Unable to get exit code of logoff thread (error %lu)\n", GetLastError()); CloseHandle(hThread); DestroyLogoffSecurityAttributes(psa); HeapFree(GetProcessHeap(), 0, LSData); return STATUS_UNSUCCESSFUL; } CloseHandle(hThread); if (exitCode == 0) { ERR("Logoff thread returned failure\n"); DestroyLogoffSecurityAttributes(psa); HeapFree(GetProcessHeap(), 0, LSData); return STATUS_UNSUCCESSFUL; } SwitchDesktop(Session->WinlogonDesktop); // TODO: Play logoff sound! SetWindowStationUser(Session->InteractiveWindowStation, &LuidNone, NULL, 0); // DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_LOGGINGOFF); // FIXME: Closing network connections! // DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_CLOSINGNETWORKCONNECTIONS); /* Kill remaining COM apps. Only at logoff! */ hThread = CreateThread(psa, 0, KillComProcesses, (LPVOID)LSData, 0, NULL); if (hThread) { WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); } /* We're done with the SECURITY_DESCRIPTOR */ DestroyLogoffSecurityAttributes(psa); psa = NULL; HeapFree(GetProcessHeap(), 0, LSData); DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_SAVEYOURSETTINGS); UnloadUserProfile(Session->UserToken, Session->hProfileInfo); CallNotificationDlls(Session, LogoffHandler); CloseHandle(Session->UserToken); UpdatePerUserSystemParameters(0, FALSE); Session->LogonState = STATE_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; BOOLEAN Old; // SwitchDesktop(Session->WinlogonDesktop); /* If the system is rebooting, show the appropriate string */ if (wlxAction == WLX_SAS_ACTION_SHUTDOWN_REBOOT) DisplayStatusMessage(Session, Session->WinlogonDesktop, IDS_REACTOSISRESTARTING); else 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; // FIXME: We may need to specify this flag to really force application kill // (we are shutting down ReactOS, not just logging off so no hangs, etc... // should be allowed). // LSData->Flags |= EWX_FORCE; /* 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; } CallNotificationDlls(Session, ShutdownHandler); /* Destroy SAS window */ UninitializeSAS(Session); /* Now we can shut down NT */ ERR("Shutting down NT...\n"); RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, TRUE, FALSE, &Old); 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); } RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, Old, FALSE, &Old); return STATUS_SUCCESS; } static VOID DoGenericAction( IN OUT PWLSESSION Session, IN DWORD wlxAction) { switch (wlxAction) { case WLX_SAS_ACTION_LOGON: /* 0x01 */ if (Session->LogonState == STATE_LOGGED_OFF_SAS) { if (!HandleLogon(Session)) { Session->Gina.Functions.WlxDisplaySASNotice(Session->Gina.Context); CallNotificationDlls(Session, LogonHandler); } } break; case WLX_SAS_ACTION_NONE: /* 0x02 */ if (Session->LogonState == STATE_LOGGED_OFF_SAS) { Session->LogonState = STATE_LOGGED_OFF; Session->Gina.Functions.WlxDisplaySASNotice(Session->Gina.Context); } else if (Session->LogonState == STATE_LOGGED_ON_SAS) { Session->LogonState = STATE_LOGGED_ON; } else if (Session->LogonState == STATE_LOCKED_SAS) { Session->LogonState = STATE_LOCKED; Session->Gina.Functions.WlxDisplayLockedNotice(Session->Gina.Context); } break; case WLX_SAS_ACTION_LOCK_WKSTA: /* 0x03 */ if (Session->Gina.Functions.WlxIsLockOk(Session->Gina.Context)) { SwitchDesktop(Session->WinlogonDesktop); Session->LogonState = STATE_LOCKED; Session->Gina.Functions.WlxDisplayLockedNotice(Session->Gina.Context); CallNotificationDlls(Session, LockHandler); } 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->LogonState != STATE_LOGGED_OFF) { if (!Session->Gina.Functions.WlxIsLogoffOk(Session->Gina.Context)) break; if (!NT_SUCCESS(HandleLogoff(Session, EWX_LOGOFF))) { RemoveStatusMessage(Session); break; } Session->Gina.Functions.WlxLogoff(Session->Gina.Context); } if (WLX_SHUTTINGDOWN(wlxAction)) { // FIXME: WlxShutdown should be done from inside HandleShutdown, // after having displayed "ReactOS is shutting down" message. 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(Session->ApplicationDesktop); Session->LogonState = STATE_LOGGED_ON; StartTaskManager(Session); break; case WLX_SAS_ACTION_UNLOCK_WKSTA: /* 0x08 */ SwitchDesktop(Session->ApplicationDesktop); Session->LogonState = STATE_LOGGED_ON; CallNotificationDlls(Session, UnlockHandler); 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; PSID LogonSid = NULL; /* FIXME */ BOOL bSecure = TRUE; switch (dwSasType) { case WLX_SAS_TYPE_CTRL_ALT_DEL: switch (Session->LogonState) { case STATE_INIT: Session->LogonState = STATE_LOGGED_OFF; Session->Gina.Functions.WlxDisplaySASNotice(Session->Gina.Context); return; case STATE_LOGGED_OFF: Session->LogonState = STATE_LOGGED_OFF_SAS; CloseAllDialogWindows(); 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; case STATE_LOGGED_OFF_SAS: /* Ignore SAS if we are already in an SAS state */ return; case STATE_LOGGED_ON: Session->LogonState = STATE_LOGGED_ON_SAS; wlxAction = (DWORD)Session->Gina.Functions.WlxLoggedOnSAS(Session->Gina.Context, dwSasType, NULL); break; case STATE_LOGGED_ON_SAS: /* Ignore SAS if we are already in an SAS state */ return; case STATE_LOCKED: Session->LogonState = STATE_LOCKED_SAS; CloseAllDialogWindows(); wlxAction = (DWORD)Session->Gina.Functions.WlxWkstaLockedSAS(Session->Gina.Context, dwSasType); break; case STATE_LOCKED_SAS: /* Ignore SAS if we are already in an SAS state */ return; default: return; } break; case WLX_SAS_TYPE_TIMEOUT: return; case WLX_SAS_TYPE_SCRNSVR_TIMEOUT: if (!Session->Gina.Functions.WlxScreenSaverNotify(Session->Gina.Context, &bSecure)) { /* Skip start of screen saver */ SetEvent(Session->hEndOfScreenSaver); } else { StartScreenSaver(Session); if (bSecure) { wlxAction = WLX_SAS_ACTION_LOCK_WKSTA; // DoGenericAction(Session, WLX_SAS_ACTION_LOCK_WKSTA); } } break; case WLX_SAS_TYPE_SCRNSVR_ACTIVITY: SetEvent(Session->hUserActivity); break; } 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; } 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"); if (Session->LogonState == STATE_LOGGED_ON) 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 && Session->LogonState != STATE_LOGGED_OFF && Session->LogonState != STATE_LOGGED_OFF_SAS) { SetTimer(hwndDlg, 1, 1000, NULL); } break; } case LN_START_SCREENSAVE: { DispatchSAS(Session, WLX_SAS_TYPE_SCRNSVR_TIMEOUT); break; } case LN_LOCK_WORKSTATION: { DoGenericAction(Session, WLX_SAS_ACTION_LOCK_WKSTA); break; } case LN_LOGOFF: { UINT Flags = (UINT)lParam; UINT Action = Flags & EWX_ACTION_MASK; DWORD wlxAction; TRACE("\tFlags : 0x%lx\n", lParam); /* * Our caller (USERSRV) should have added the shutdown flag * when setting also poweroff or reboot. */ if (Action & (EWX_POWEROFF | EWX_REBOOT)) { if ((Action & EWX_SHUTDOWN) == 0) { ERR("Missing EWX_SHUTDOWN flag for poweroff or reboot; action 0x%x\n", Action); return STATUS_INVALID_PARAMETER; } /* Now we can locally remove it for performing checks */ Action &= ~EWX_SHUTDOWN; } /* Check parameters */ if (Action & EWX_FORCE) { // FIXME! ERR("FIXME: EWX_FORCE present for Winlogon, what to do?\n"); Action &= ~EWX_FORCE; } 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; } } TRACE("In LN_LOGOFF, exit_in_progress == %s\n", ExitReactOSInProgress ? "true" : "false"); /* * In case a parallel shutdown request is done (while we are * being to shut down) and it was not done by Winlogon itself, * then just stop here. */ #if 0 // This code is commented at the moment (even if it's correct) because // our log-offs do not really work: the shell is restarted, no app is killed // etc... and as a result you just get explorer opening "My Documents". And // if you try now a shut down, it won't work because winlogon thinks it is // still in the middle of a shutdown. // Maybe we also need to reset ExitReactOSInProgress somewhere else?? if (ExitReactOSInProgress && (lParam & EWX_CALLER_WINLOGON) == 0) { break; } #endif /* Now do the shutdown action proper */ DoGenericAction(Session, wlxAction); return 1; } case LN_LOGOFF_CANCELED: { ERR("Logoff canceled!!, before: exit_in_progress == %s, after will be false\n", ExitReactOSInProgress ? "true" : "false"); ExitReactOSInProgress = FALSE; return 1; } 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; } } 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)) { ERR("WL: Failed to register SAS window\n"); goto cleanup; } if (!SetDefaultLanguage(NULL)) return FALSE; ret = TRUE; cleanup: if (!ret) UninitializeSAS(Session); return ret; }