xref: /reactos/win32ss/user/ntuser/shutdown.c (revision 845faec4)
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