[USER32] Add support for navigating a group of radio buttons using a keyboard.
[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 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 */