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