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 92 NTSTATUS 93 GetProcessLuid(IN PETHREAD Thread OPTIONAL, 94 OUT PLUID Luid) 95 { 96 NTSTATUS Status; 97 PACCESS_TOKEN Token; 98 SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; 99 BOOLEAN CopyOnOpen, EffectiveOnly; 100 101 if (Thread == NULL) 102 Thread = PsGetCurrentThread(); 103 104 /* Use a thread token */ 105 Token = PsReferenceImpersonationToken(Thread, 106 &CopyOnOpen, 107 &EffectiveOnly, 108 &ImpersonationLevel); 109 if (Token == NULL) 110 { 111 /* We don't have a thread token, use a process token */ 112 Token = PsReferencePrimaryToken(PsGetThreadProcess(Thread)); 113 114 /* If no token, fail */ 115 if (Token == NULL) 116 return STATUS_NO_TOKEN; 117 } 118 119 /* Query the LUID */ 120 Status = SeQueryAuthenticationIdToken(Token, Luid); 121 122 /* Get rid of the token and return */ 123 ObDereferenceObject(Token); 124 return Status; 125 } 126 127 BOOLEAN 128 HasPrivilege(IN PPRIVILEGE_SET Privilege) 129 { 130 BOOLEAN Result; 131 SECURITY_SUBJECT_CONTEXT SubjectContext; 132 133 /* Capture and lock the security subject context */ 134 SeCaptureSubjectContext(&SubjectContext); 135 SeLockSubjectContext(&SubjectContext); 136 137 /* Do privilege check */ 138 Result = SePrivilegeCheck(Privilege, &SubjectContext, UserMode); 139 140 /* Audit the privilege */ 141 #if 0 142 SePrivilegeObjectAuditAlarm(NULL, 143 &SubjectContext, 144 0, 145 Privilege, 146 Result, 147 UserMode); 148 #endif 149 150 /* Unlock and release the security subject context and return */ 151 SeUnlockSubjectContext(&SubjectContext); 152 SeReleaseSubjectContext(&SubjectContext); 153 return Result; 154 } 155 156 BOOL 157 NotifyLogon(IN HWND hWndSta, 158 IN PLUID CallerLuid, 159 IN ULONG Flags, 160 IN NTSTATUS ShutdownStatus) 161 { 162 // LUID SystemLuid = SYSTEM_LUID; 163 ULONG Notif, Param; 164 165 ERR("NotifyLogon(0x%lx, 0x%lx)\n", Flags, ShutdownStatus); 166 167 /* If no Winlogon notifications are needed, just return */ 168 if (Flags & EWX_NONOTIFY) 169 return FALSE; 170 171 /* In case we cancelled the shutdown...*/ 172 if (Flags & EWX_SHUTDOWN_CANCELED) 173 { 174 /* ... send a LN_LOGOFF_CANCELED to Winlogon with the real cancel status... */ 175 Notif = LN_LOGOFF_CANCELED; 176 Param = ShutdownStatus; 177 } 178 else 179 { 180 /* ... otherwise it's a real logoff. Send the shutdown flags in that case. */ 181 Notif = LN_LOGOFF; 182 Param = Flags; 183 } 184 185 // FIXME: At the moment, always send the notifications... In real world some checks are done. 186 // if (hwndSAS && ( (Flags & EWX_SHUTDOWN) || RtlEqualLuid(CallerLuid, &SystemLuid)) ) 187 if (hwndSAS) 188 { 189 TRACE("\tSending %s message to Winlogon\n", Notif == LN_LOGOFF ? "LN_LOGOFF" : "LN_LOGOFF_CANCELED"); 190 UserPostMessage(hwndSAS, WM_LOGONNOTIFY, Notif, (LPARAM)Param); 191 return TRUE; 192 } 193 else 194 { 195 ERR("hwndSAS == NULL\n"); 196 } 197 198 return FALSE; 199 } 200 201 NTSTATUS 202 UserInitiateShutdown(IN PETHREAD Thread, 203 IN OUT PULONG pFlags) 204 { 205 NTSTATUS Status; 206 ULONG Flags = *pFlags; 207 LUID CallerLuid; 208 LUID SystemLuid = SYSTEM_LUID; 209 static PRIVILEGE_SET ShutdownPrivilege = 210 { 211 1, PRIVILEGE_SET_ALL_NECESSARY, 212 { {{SE_SHUTDOWN_PRIVILEGE, 0}, 0} } 213 }; 214 215 PPROCESSINFO ppi; 216 217 TRACE("UserInitiateShutdown\n"); 218 219 /* Get the caller's LUID */ 220 Status = GetProcessLuid(Thread, &CallerLuid); 221 if (!NT_SUCCESS(Status)) 222 { 223 ERR("UserInitiateShutdown: GetProcessLuid failed\n"); 224 return Status; 225 } 226 227 /* 228 * Check if this is the System LUID, and adjust flags if needed. 229 * In particular, be sure there is no EWX_CALLER_SYSTEM flag 230 * spuriously set (could be the sign of malicous app!). 231 */ 232 if (RtlEqualLuid(&CallerLuid, &SystemLuid)) 233 Flags |= EWX_CALLER_SYSTEM; 234 else 235 Flags &= ~EWX_CALLER_SYSTEM; 236 237 *pFlags = Flags; 238 239 /* Retrieve the Win32 process info */ 240 ppi = PsGetProcessWin32Process(PsGetThreadProcess(Thread)); 241 if (ppi == NULL) 242 { 243 ERR("UserInitiateShutdown: Failed to get win32 thread!\n"); 244 return STATUS_INVALID_HANDLE; 245 } 246 247 /* If the caller is not Winlogon, do some security checks */ 248 if (PsGetThreadProcessId(Thread) != gpidLogon) 249 { 250 /* 251 * Here also, be sure there is no EWX_CALLER_WINLOGON flag 252 * spuriously set (could be the sign of malicous app!). 253 */ 254 Flags &= ~EWX_CALLER_WINLOGON; 255 256 *pFlags = Flags; 257 258 /* Check whether the current process is attached to a window station */ 259 if (ppi->prpwinsta == NULL) 260 { 261 ERR("UserInitiateShutdown: Process is not attached to a desktop\n"); 262 return STATUS_INVALID_HANDLE; 263 } 264 265 /* Check whether the window station of the current process can send exit requests */ 266 if (!RtlAreAllAccessesGranted(ppi->amwinsta, WINSTA_EXITWINDOWS)) 267 { 268 ERR("UserInitiateShutdown: Caller doesn't have the rights to shutdown\n"); 269 return STATUS_ACCESS_DENIED; 270 } 271 272 /* 273 * NOTE: USERSRV automatically adds the shutdown flag when we poweroff or reboot. 274 * 275 * If the caller wants to shutdown / reboot / power-off... 276 */ 277 if (Flags & EWX_SHUTDOWN) 278 { 279 /* ... check whether it has shutdown privilege */ 280 if (!HasPrivilege(&ShutdownPrivilege)) 281 { 282 ERR("UserInitiateShutdown: Caller doesn't have the rights to shutdown\n"); 283 return STATUS_PRIVILEGE_NOT_HELD; 284 } 285 } 286 else 287 { 288 /* 289 * ... but if it just wants to log-off, in case its 290 * window station is a non-IO one, fail the call. 291 */ 292 if (ppi->prpwinsta->Flags & WSS_NOIO) 293 { 294 ERR("UserInitiateShutdown: Caller doesn't have the rights to logoff\n"); 295 return STATUS_INVALID_DEVICE_REQUEST; 296 } 297 } 298 } 299 300 /* If the caller is not Winlogon, possibly notify it to perform the real shutdown */ 301 if (PsGetThreadProcessId(Thread) != gpidLogon) 302 { 303 // FIXME: HACK!! Do more checks!! 304 TRACE("UserInitiateShutdown: Notify Winlogon for shutdown\n"); 305 NotifyLogon(hwndSAS, &CallerLuid, Flags, STATUS_SUCCESS); 306 return STATUS_PENDING; 307 } 308 309 // If we reach this point, that means it's Winlogon that triggered the shutdown. 310 TRACE("UserInitiateShutdown: Winlogon is doing a shutdown\n"); 311 312 /* 313 * Update and save the shutdown flags globally for renotifying 314 * Winlogon if needed, when calling EndShutdown. 315 */ 316 Flags |= EWX_CALLER_WINLOGON; // Winlogon is doing a shutdown, be sure the internal flag is set. 317 *pFlags = Flags; 318 319 /* Save the shutdown flags now */ 320 gdwShutdownFlags = Flags; 321 322 return STATUS_SUCCESS; 323 } 324 325 NTSTATUS 326 UserEndShutdown(IN PETHREAD Thread, 327 IN NTSTATUS ShutdownStatus) 328 { 329 NTSTATUS Status; 330 ULONG Flags; 331 LUID CallerLuid; 332 333 TRACE("UserEndShutdown called\n"); 334 335 /* 336 * FIXME: Some cleanup should be done when shutdown succeeds, 337 * and some reset should be done when shutdown is cancelled. 338 */ 339 //STUB; 340 341 Status = GetProcessLuid(Thread, &CallerLuid); 342 if (!NT_SUCCESS(Status)) 343 { 344 ERR("GetProcessLuid failed\n"); 345 return Status; 346 } 347 348 /* Copy the global flags because we're going to modify them for our purposes */ 349 Flags = gdwShutdownFlags; 350 351 if (NT_SUCCESS(ShutdownStatus)) 352 { 353 /* Just report success, and keep the shutdown flags as they are */ 354 ShutdownStatus = STATUS_SUCCESS; 355 } 356 else 357 { 358 /* Report the status to Winlogon and say that we cancel the shutdown */ 359 Flags |= EWX_SHUTDOWN_CANCELED; 360 // FIXME: Should we reset gdwShutdownFlags to 0 ?? 361 } 362 363 TRACE("UserEndShutdown: Notify Winlogon for end of shutdown\n"); 364 NotifyLogon(hwndSAS, &CallerLuid, Flags, ShutdownStatus); 365 366 /* Always return success */ 367 return STATUS_SUCCESS; 368 } 369 370 /* EOF */ 371