[CMAKE] Zap builddir.h.cmake and instead define macros globally
[reactos.git] / win32ss / user / ntuser / shutdown.c
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 */