/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS Win32k subsystem * PURPOSE: Shutdown routines * FILE: win32ss/user/ntuser/shutdown.c * PROGRAMER: Hermes Belusca */ #include DBG_DEFAULT_CHANNEL(UserShutdown); /* Our local copy of shutdown flags */ static ULONG gdwShutdownFlags = 0; /* * Based on CSRSS and described in pages 1115 - 1118 "Windows Internals, Fifth Edition". * CSRSS sends WM_CLIENTSHUTDOWN messages to top-level windows, and it is our job * to send WM_QUERYENDSESSION / WM_ENDSESSION messages in response. */ LRESULT IntClientShutdown(IN PWND pWindow, IN WPARAM wParam, IN LPARAM lParam) { LPARAM lParams; BOOL KillTimers; INT i; LRESULT lResult = MCSR_GOODFORSHUTDOWN; HWND *List; KillTimers = wParam & MCS_ENDSESSION ? TRUE : FALSE; lParams = lParam & (ENDSESSION_LOGOFF | ENDSESSION_CRITICAL | ENDSESSION_CLOSEAPP); /* First, send end sessions to children */ List = IntWinListChildren(pWindow); if (List) { for (i = 0; List[i]; i++) { PWND WndChild; if (!(WndChild = UserGetWindowObject(List[i]))) continue; if (wParam & MCS_QUERYENDSESSION) { if (!co_IntSendMessage(WndChild->head.h, WM_QUERYENDSESSION, 0, lParams)) { lResult = MCSR_DONOTSHUTDOWN; break; } } else { co_IntSendMessage(WndChild->head.h, WM_ENDSESSION, KillTimers, lParams); if (KillTimers) { DestroyTimersForWindow(WndChild->head.pti, WndChild); } lResult = MCSR_SHUTDOWNFINISHED; } } ExFreePoolWithTag(List, USERTAG_WINDOWLIST); if (lResult == MCSR_DONOTSHUTDOWN) return lResult; } /* Send to the caller */ if (wParam & MCS_QUERYENDSESSION) { if (!co_IntSendMessage(pWindow->head.h, WM_QUERYENDSESSION, 0, lParams)) { lResult = MCSR_DONOTSHUTDOWN; } } else { co_IntSendMessage(pWindow->head.h, WM_ENDSESSION, KillTimers, lParams); if (KillTimers) { DestroyTimersForWindow(pWindow->head.pti, pWindow); } lResult = MCSR_SHUTDOWNFINISHED; } return lResult; } BOOLEAN HasPrivilege(IN PPRIVILEGE_SET Privilege) { BOOLEAN Result; SECURITY_SUBJECT_CONTEXT SubjectContext; /* Capture and lock the security subject context */ SeCaptureSubjectContext(&SubjectContext); SeLockSubjectContext(&SubjectContext); /* Do privilege check */ Result = SePrivilegeCheck(Privilege, &SubjectContext, UserMode); /* Audit the privilege */ #if 0 SePrivilegeObjectAuditAlarm(NULL, &SubjectContext, 0, Privilege, Result, UserMode); #endif /* Unlock and release the security subject context and return */ SeUnlockSubjectContext(&SubjectContext); SeReleaseSubjectContext(&SubjectContext); return Result; } BOOL NotifyLogon(IN HWND hWndSta, IN PLUID CallerLuid, IN ULONG Flags, IN NTSTATUS ShutdownStatus) { // LUID SystemLuid = SYSTEM_LUID; ULONG Notif, Param; ERR("NotifyLogon(0x%lx, 0x%lx)\n", Flags, ShutdownStatus); /* If no Winlogon notifications are needed, just return */ if (Flags & EWX_NONOTIFY) return FALSE; /* In case we cancelled the shutdown...*/ if (Flags & EWX_SHUTDOWN_CANCELED) { /* ... send a LN_LOGOFF_CANCELED to Winlogon with the real cancel status... */ Notif = LN_LOGOFF_CANCELED; Param = ShutdownStatus; } else { /* ... otherwise it's a real logoff. Send the shutdown flags in that case. */ Notif = LN_LOGOFF; Param = Flags; } // FIXME: At the moment, always send the notifications... In real world some checks are done. // if (hwndSAS && ( (Flags & EWX_SHUTDOWN) || RtlEqualLuid(CallerLuid, &SystemLuid)) ) if (hwndSAS) { TRACE("\tSending %s message to Winlogon\n", Notif == LN_LOGOFF ? "LN_LOGOFF" : "LN_LOGOFF_CANCELED"); UserPostMessage(hwndSAS, WM_LOGONNOTIFY, Notif, (LPARAM)Param); return TRUE; } else { ERR("hwndSAS == NULL\n"); } return FALSE; } NTSTATUS UserInitiateShutdown(IN PETHREAD Thread, IN OUT PULONG pFlags) { NTSTATUS Status; ULONG Flags = *pFlags; LUID CallerLuid; LUID SystemLuid = SYSTEM_LUID; static PRIVILEGE_SET ShutdownPrivilege = { 1, PRIVILEGE_SET_ALL_NECESSARY, { {{SE_SHUTDOWN_PRIVILEGE, 0}, 0} } }; PPROCESSINFO ppi; TRACE("UserInitiateShutdown\n"); /* Get the caller's LUID */ Status = GetProcessLuid(Thread, NULL, &CallerLuid); if (!NT_SUCCESS(Status)) { ERR("UserInitiateShutdown: GetProcessLuid failed\n"); return Status; } /* * Check if this is the System LUID, and adjust flags if needed. * In particular, be sure there is no EWX_CALLER_SYSTEM flag * spuriously set (could be the sign of malicous app!). */ if (RtlEqualLuid(&CallerLuid, &SystemLuid)) Flags |= EWX_CALLER_SYSTEM; else Flags &= ~EWX_CALLER_SYSTEM; *pFlags = Flags; /* Retrieve the Win32 process info */ ppi = PsGetProcessWin32Process(PsGetThreadProcess(Thread)); if (ppi == NULL) { ERR("UserInitiateShutdown: Failed to get win32 thread!\n"); return STATUS_INVALID_HANDLE; } /* If the caller is not Winlogon, do some security checks */ if (PsGetThreadProcessId(Thread) != gpidLogon) { /* * Here also, be sure there is no EWX_CALLER_WINLOGON flag * spuriously set (could be the sign of malicous app!). */ Flags &= ~EWX_CALLER_WINLOGON; *pFlags = Flags; /* Check whether the current process is attached to a window station */ if (ppi->prpwinsta == NULL) { ERR("UserInitiateShutdown: Process is not attached to a desktop\n"); return STATUS_INVALID_HANDLE; } /* Check whether the window station of the current process can send exit requests */ if (!RtlAreAllAccessesGranted(ppi->amwinsta, WINSTA_EXITWINDOWS)) { ERR("UserInitiateShutdown: Caller doesn't have the rights to shutdown\n"); return STATUS_ACCESS_DENIED; } /* * NOTE: USERSRV automatically adds the shutdown flag when we poweroff or reboot. * * If the caller wants to shutdown / reboot / power-off... */ if (Flags & EWX_SHUTDOWN) { /* ... check whether it has shutdown privilege */ if (!HasPrivilege(&ShutdownPrivilege)) { ERR("UserInitiateShutdown: Caller doesn't have the rights to shutdown\n"); return STATUS_PRIVILEGE_NOT_HELD; } } else { /* * ... but if it just wants to log-off, in case its * window station is a non-IO one, fail the call. */ if (ppi->prpwinsta->Flags & WSS_NOIO) { ERR("UserInitiateShutdown: Caller doesn't have the rights to logoff\n"); return STATUS_INVALID_DEVICE_REQUEST; } } } /* If the caller is not Winlogon, possibly notify it to perform the real shutdown */ if (PsGetThreadProcessId(Thread) != gpidLogon) { // FIXME: HACK!! Do more checks!! TRACE("UserInitiateShutdown: Notify Winlogon for shutdown\n"); NotifyLogon(hwndSAS, &CallerLuid, Flags, STATUS_SUCCESS); return STATUS_PENDING; } // If we reach this point, that means it's Winlogon that triggered the shutdown. TRACE("UserInitiateShutdown: Winlogon is doing a shutdown\n"); /* * Update and save the shutdown flags globally for renotifying * Winlogon if needed, when calling EndShutdown. */ Flags |= EWX_CALLER_WINLOGON; // Winlogon is doing a shutdown, be sure the internal flag is set. *pFlags = Flags; /* Save the shutdown flags now */ gdwShutdownFlags = Flags; return STATUS_SUCCESS; } NTSTATUS UserEndShutdown(IN PETHREAD Thread, IN NTSTATUS ShutdownStatus) { NTSTATUS Status; ULONG Flags; LUID CallerLuid; TRACE("UserEndShutdown called\n"); /* * FIXME: Some cleanup should be done when shutdown succeeds, * and some reset should be done when shutdown is cancelled. */ //STUB; Status = GetProcessLuid(Thread, NULL, &CallerLuid); if (!NT_SUCCESS(Status)) { ERR("UserEndShutdown: GetProcessLuid failed\n"); return Status; } /* Copy the global flags because we're going to modify them for our purposes */ Flags = gdwShutdownFlags; if (NT_SUCCESS(ShutdownStatus)) { /* Just report success, and keep the shutdown flags as they are */ ShutdownStatus = STATUS_SUCCESS; } else { /* Report the status to Winlogon and say that we cancel the shutdown */ Flags |= EWX_SHUTDOWN_CANCELED; // FIXME: Should we reset gdwShutdownFlags to 0 ?? } TRACE("UserEndShutdown: Notify Winlogon for end of shutdown\n"); NotifyLogon(hwndSAS, &CallerLuid, Flags, ShutdownStatus); /* Always return success */ return STATUS_SUCCESS; } /* EOF */