1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS Win32k subsystem 4 * PURPOSE: Shutdown routines 5 * FILE: win32ss/user/ntuser/shutdown.c 6 * PROGRAMER: Hermes Belusca 7 */ 8 9 #include <win32k.h> 10 11 DBG_DEFAULT_CHANNEL(UserShutdown); 12 13 /* Our local copy of shutdown flags */ 14 static ULONG gdwShutdownFlags = 0; 15 16 /* 17 * Based on CSRSS and described in pages 1115 - 1118 "Windows Internals, Fifth Edition". 18 * CSRSS sends WM_CLIENTSHUTDOWN messages to top-level windows, and it is our job 19 * to send WM_QUERYENDSESSION / WM_ENDSESSION messages in response. 20 */ 21 LRESULT 22 IntClientShutdown(IN PWND pWindow, 23 IN WPARAM wParam, 24 IN LPARAM lParam) 25 { 26 LPARAM lParams; 27 BOOL KillTimers; 28 INT i; 29 LRESULT lResult = MCSR_GOODFORSHUTDOWN; 30 HWND *List; 31 32 KillTimers = wParam & MCS_ENDSESSION ? TRUE : FALSE; 33 lParams = lParam & (ENDSESSION_LOGOFF | ENDSESSION_CRITICAL | ENDSESSION_CLOSEAPP); 34 35 /* First, send end sessions to children */ 36 List = IntWinListChildren(pWindow); 37 38 if (List) 39 { 40 for (i = 0; List[i]; i++) 41 { 42 PWND WndChild; 43 44 if (!(WndChild = UserGetWindowObject(List[i]))) 45 continue; 46 47 if (wParam & MCS_QUERYENDSESSION) 48 { 49 if (!co_IntSendMessage(WndChild->head.h, WM_QUERYENDSESSION, 0, lParams)) 50 { 51 lResult = MCSR_DONOTSHUTDOWN; 52 break; 53 } 54 } 55 else 56 { 57 co_IntSendMessage(WndChild->head.h, WM_ENDSESSION, KillTimers, lParams); 58 if (KillTimers) 59 { 60 DestroyTimersForWindow(WndChild->head.pti, WndChild); 61 } 62 lResult = MCSR_SHUTDOWNFINISHED; 63 } 64 } 65 ExFreePoolWithTag(List, USERTAG_WINDOWLIST); 66 if (lResult == MCSR_DONOTSHUTDOWN) 67 return lResult; 68 } 69 70 /* Send to the caller */ 71 if (wParam & MCS_QUERYENDSESSION) 72 { 73 if (!co_IntSendMessage(pWindow->head.h, WM_QUERYENDSESSION, 0, lParams)) 74 { 75 lResult = MCSR_DONOTSHUTDOWN; 76 } 77 } 78 else 79 { 80 co_IntSendMessage(pWindow->head.h, WM_ENDSESSION, KillTimers, lParams); 81 if (KillTimers) 82 { 83 DestroyTimersForWindow(pWindow->head.pti, pWindow); 84 } 85 lResult = MCSR_SHUTDOWNFINISHED; 86 } 87 88 return lResult; 89 } 90 91 BOOLEAN 92 HasPrivilege(IN PPRIVILEGE_SET Privilege) 93 { 94 BOOLEAN Result; 95 SECURITY_SUBJECT_CONTEXT SubjectContext; 96 97 /* Capture and lock the security subject context */ 98 SeCaptureSubjectContext(&SubjectContext); 99 SeLockSubjectContext(&SubjectContext); 100 101 /* Do privilege check */ 102 Result = SePrivilegeCheck(Privilege, &SubjectContext, UserMode); 103 104 /* Audit the privilege */ 105 #if 0 106 SePrivilegeObjectAuditAlarm(NULL, 107 &SubjectContext, 108 0, 109 Privilege, 110 Result, 111 UserMode); 112 #endif 113 114 /* Unlock and release the security subject context and return */ 115 SeUnlockSubjectContext(&SubjectContext); 116 SeReleaseSubjectContext(&SubjectContext); 117 return Result; 118 } 119 120 BOOL 121 NotifyLogon(IN HWND hWndSta, 122 IN PLUID CallerLuid, 123 IN ULONG Flags, 124 IN NTSTATUS ShutdownStatus) 125 { 126 // LUID SystemLuid = SYSTEM_LUID; 127 ULONG Notif, Param; 128 129 ERR("NotifyLogon(0x%lx, 0x%lx)\n", Flags, ShutdownStatus); 130 131 /* If no Winlogon notifications are needed, just return */ 132 if (Flags & EWX_NONOTIFY) 133 return FALSE; 134 135 /* In case we cancelled the shutdown...*/ 136 if (Flags & EWX_SHUTDOWN_CANCELED) 137 { 138 /* ... send a LN_LOGOFF_CANCELED to Winlogon with the real cancel status... */ 139 Notif = LN_LOGOFF_CANCELED; 140 Param = ShutdownStatus; 141 } 142 else 143 { 144 /* ... otherwise it's a real logoff. Send the shutdown flags in that case. */ 145 Notif = LN_LOGOFF; 146 Param = Flags; 147 } 148 149 // FIXME: At the moment, always send the notifications... In real world some checks are done. 150 // if (hwndSAS && ( (Flags & EWX_SHUTDOWN) || RtlEqualLuid(CallerLuid, &SystemLuid)) ) 151 if (hwndSAS) 152 { 153 TRACE("\tSending %s message to Winlogon\n", Notif == LN_LOGOFF ? "LN_LOGOFF" : "LN_LOGOFF_CANCELED"); 154 UserPostMessage(hwndSAS, WM_LOGONNOTIFY, Notif, (LPARAM)Param); 155 return TRUE; 156 } 157 else 158 { 159 ERR("hwndSAS == NULL\n"); 160 } 161 162 return FALSE; 163 } 164 165 NTSTATUS 166 UserInitiateShutdown(IN PETHREAD Thread, 167 IN OUT PULONG pFlags) 168 { 169 NTSTATUS Status; 170 ULONG Flags = *pFlags; 171 LUID CallerLuid; 172 LUID SystemLuid = SYSTEM_LUID; 173 static PRIVILEGE_SET ShutdownPrivilege = 174 { 175 1, PRIVILEGE_SET_ALL_NECESSARY, 176 { {{SE_SHUTDOWN_PRIVILEGE, 0}, 0} } 177 }; 178 179 PPROCESSINFO ppi; 180 181 TRACE("UserInitiateShutdown\n"); 182 183 /* Get the caller's LUID */ 184 Status = GetProcessLuid(Thread, NULL, &CallerLuid); 185 if (!NT_SUCCESS(Status)) 186 { 187 ERR("UserInitiateShutdown: GetProcessLuid failed\n"); 188 return Status; 189 } 190 191 /* 192 * Check if this is the System LUID, and adjust flags if needed. 193 * In particular, be sure there is no EWX_CALLER_SYSTEM flag 194 * spuriously set (could be the sign of malicous app!). 195 */ 196 if (RtlEqualLuid(&CallerLuid, &SystemLuid)) 197 Flags |= EWX_CALLER_SYSTEM; 198 else 199 Flags &= ~EWX_CALLER_SYSTEM; 200 201 *pFlags = Flags; 202 203 /* Retrieve the Win32 process info */ 204 ppi = PsGetProcessWin32Process(PsGetThreadProcess(Thread)); 205 if (ppi == NULL) 206 { 207 ERR("UserInitiateShutdown: Failed to get win32 thread!\n"); 208 return STATUS_INVALID_HANDLE; 209 } 210 211 /* If the caller is not Winlogon, do some security checks */ 212 if (PsGetThreadProcessId(Thread) != gpidLogon) 213 { 214 /* 215 * Here also, be sure there is no EWX_CALLER_WINLOGON flag 216 * spuriously set (could be the sign of malicous app!). 217 */ 218 Flags &= ~EWX_CALLER_WINLOGON; 219 220 *pFlags = Flags; 221 222 /* Check whether the current process is attached to a window station */ 223 if (ppi->prpwinsta == NULL) 224 { 225 ERR("UserInitiateShutdown: Process is not attached to a desktop\n"); 226 return STATUS_INVALID_HANDLE; 227 } 228 229 /* Check whether the window station of the current process can send exit requests */ 230 if (!RtlAreAllAccessesGranted(ppi->amwinsta, WINSTA_EXITWINDOWS)) 231 { 232 ERR("UserInitiateShutdown: Caller doesn't have the rights to shutdown\n"); 233 return STATUS_ACCESS_DENIED; 234 } 235 236 /* 237 * NOTE: USERSRV automatically adds the shutdown flag when we poweroff or reboot. 238 * 239 * If the caller wants to shutdown / reboot / power-off... 240 */ 241 if (Flags & EWX_SHUTDOWN) 242 { 243 /* ... check whether it has shutdown privilege */ 244 if (!HasPrivilege(&ShutdownPrivilege)) 245 { 246 ERR("UserInitiateShutdown: Caller doesn't have the rights to shutdown\n"); 247 return STATUS_PRIVILEGE_NOT_HELD; 248 } 249 } 250 else 251 { 252 /* 253 * ... but if it just wants to log-off, in case its 254 * window station is a non-IO one, fail the call. 255 */ 256 if (ppi->prpwinsta->Flags & WSS_NOIO) 257 { 258 ERR("UserInitiateShutdown: Caller doesn't have the rights to logoff\n"); 259 return STATUS_INVALID_DEVICE_REQUEST; 260 } 261 } 262 } 263 264 /* If the caller is not Winlogon, possibly notify it to perform the real shutdown */ 265 if (PsGetThreadProcessId(Thread) != gpidLogon) 266 { 267 // FIXME: HACK!! Do more checks!! 268 TRACE("UserInitiateShutdown: Notify Winlogon for shutdown\n"); 269 NotifyLogon(hwndSAS, &CallerLuid, Flags, STATUS_SUCCESS); 270 return STATUS_PENDING; 271 } 272 273 // If we reach this point, that means it's Winlogon that triggered the shutdown. 274 TRACE("UserInitiateShutdown: Winlogon is doing a shutdown\n"); 275 276 /* 277 * Update and save the shutdown flags globally for renotifying 278 * Winlogon if needed, when calling EndShutdown. 279 */ 280 Flags |= EWX_CALLER_WINLOGON; // Winlogon is doing a shutdown, be sure the internal flag is set. 281 *pFlags = Flags; 282 283 /* Save the shutdown flags now */ 284 gdwShutdownFlags = Flags; 285 286 return STATUS_SUCCESS; 287 } 288 289 NTSTATUS 290 UserEndShutdown(IN PETHREAD Thread, 291 IN NTSTATUS ShutdownStatus) 292 { 293 NTSTATUS Status; 294 ULONG Flags; 295 LUID CallerLuid; 296 297 TRACE("UserEndShutdown called\n"); 298 299 /* 300 * FIXME: Some cleanup should be done when shutdown succeeds, 301 * and some reset should be done when shutdown is cancelled. 302 */ 303 //STUB; 304 305 Status = GetProcessLuid(Thread, NULL, &CallerLuid); 306 if (!NT_SUCCESS(Status)) 307 { 308 ERR("UserEndShutdown: GetProcessLuid failed\n"); 309 return Status; 310 } 311 312 /* Copy the global flags because we're going to modify them for our purposes */ 313 Flags = gdwShutdownFlags; 314 315 if (NT_SUCCESS(ShutdownStatus)) 316 { 317 /* Just report success, and keep the shutdown flags as they are */ 318 ShutdownStatus = STATUS_SUCCESS; 319 } 320 else 321 { 322 /* Report the status to Winlogon and say that we cancel the shutdown */ 323 Flags |= EWX_SHUTDOWN_CANCELED; 324 // FIXME: Should we reset gdwShutdownFlags to 0 ?? 325 } 326 327 TRACE("UserEndShutdown: Notify Winlogon for end of shutdown\n"); 328 NotifyLogon(hwndSAS, &CallerLuid, Flags, ShutdownStatus); 329 330 /* Always return success */ 331 return STATUS_SUCCESS; 332 } 333 334 /* EOF */ 335