/**************************** Module Header ********************************\ * Module Name: exitwin.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * History: * 07-23-92 ScottLu Created. \***************************************************************************/ #include "precomp.h" #pragma hdrstop #define OPTIONMASK (EWX_SHUTDOWN | EWX_REBOOT | EWX_FORCE) /* * Globals local to this file only */ PWINDOWSTATION gpwinstaLogoff; DWORD gdwLocks; DWORD gdwShutdownFlags; HANDLE gpidEndSession; #ifdef PRERELEASE DWORD gdwllParamCopy, gdwStatusCopy, gdwFlagsCopy; BOOL gfNotifiedCopy; #endif // PRERELEASE /* * Called by ExitWindowsEx() to check whether the thread is permitted to logoff. * If it is, and this is WinLogon calling, then also save any of the user's * setting that have not yet been stored in the profile. */ BOOL PrepareForLogoff( UINT uFlags) { PTHREADINFO ptiCurrent = PtiCurrent(); CheckCritIn(); if (ptiCurrent->TIF_flags & TIF_RESTRICTED) { PW32JOB pW32Job; pW32Job = ptiCurrent->ppi->pW32Job; UserAssert(pW32Job != NULL); if (pW32Job->restrictions & JOB_OBJECT_UILIMIT_EXITWINDOWS) { // Not permitted to ExitWindows. return FALSE; } } /* * There are no restrictions, or the restriction do not deny shutdown: * The caller is about to ExitWindowsEx via CSR, so save the volatile * elements of the User preferences in their profile */ if (PsGetThreadProcessId(ptiCurrent->pEThread) == gpidLogon) { /* * Save the current user's NumLock state */ TL tlName; PUNICODE_STRING pProfileUserName = CreateProfileUserName(&tlName); RegisterPerUserKeyboardIndicators(pProfileUserName); FreeProfileUserName(pProfileUserName, &tlName); } return TRUE; UNREFERENCED_PARAMETER(uFlags); } BOOL NotifyLogon( PWINDOWSTATION pwinsta, PLUID pluidCaller, DWORD dwFlags, NTSTATUS StatusCode) { BOOL fNotified = FALSE; DWORD dwllParam; DWORD dwStatus; if (!(dwFlags & EWX_NONOTIFY)) { if (dwFlags & EWX_CANCELED) { dwllParam = LOGON_LOGOFFCANCELED; dwStatus = StatusCode; } else { dwllParam = LOGON_LOGOFF; dwStatus = dwFlags; } if (dwFlags & EWX_SHUTDOWN) { /* * Post the message to the global logon notify window */ if (gspwndLogonNotify != NULL) { _PostMessage(gspwndLogonNotify, WM_LOGONNOTIFY, dwllParam, (LONG)dwStatus); fNotified = TRUE; } } else { if (gspwndLogonNotify != NULL && (RtlEqualLuid(&pwinsta->luidUser, pluidCaller) || RtlEqualLuid(&luidSystem, pluidCaller))) { _PostMessage(gspwndLogonNotify, WM_LOGONNOTIFY, dwllParam, (LONG)dwStatus); fNotified = TRUE; } } } #ifdef PRERELEASE /* * Remember what these were for debugging purposes. */ gdwllParamCopy = dwllParam; gdwFlagsCopy = dwFlags; gdwStatusCopy = dwStatus; gfNotifiedCopy = fNotified; #endif // PRERELEASE return fNotified; } NTSTATUS InitiateShutdown( PETHREAD Thread, PULONG lpdwFlags) { static PRIVILEGE_SET psShutdown = { 1, PRIVILEGE_SET_ALL_NECESSARY, { SE_SHUTDOWN_PRIVILEGE, 0 } }; PEPROCESS Process; LUID luidCaller; PPROCESSINFO ppi; PWINDOWSTATION pwinsta; HWINSTA hwinsta; PTHREADINFO ptiClient; NTSTATUS Status; DWORD dwFlags; /* * Find out the callers sid. Only want to shutdown processes in the * callers sid. */ Process = PsGetThreadProcess(Thread); ptiClient = PtiFromThread(Thread); Status = GetProcessLuid(Thread, &luidCaller); if (!NT_SUCCESS(Status)) { return Status; } /* * Set the system flag if the caller is a system process. * Winlogon uses this to determine in which context to perform * a shutdown operation. */ dwFlags = *lpdwFlags; if (RtlEqualLuid(&luidCaller, &luidSystem)) { dwFlags |= EWX_SYSTEM_CALLER; } else { dwFlags &= ~EWX_SYSTEM_CALLER; } /* * Find a windowstation. If the process does not have one * assigned, use the standard one. */ ppi = PpiFromProcess(Process); if (ppi == NULL) { /* * We ran into a case where the thread was terminated and had already * been cleaned up by USER. Thus, the ppi and ptiClient was NULL. */ return STATUS_INVALID_HANDLE; } pwinsta = ppi->rpwinsta; hwinsta = ppi->hwinsta; /* * If we're not being called by Winlogon, validate the call and * notify the logon process to do the actual shutdown. */ if (PsGetThreadProcessId(Thread) != gpidLogon) { dwFlags &= ~EWX_WINLOGON_CALLER; *lpdwFlags = dwFlags; if (pwinsta == NULL) { #ifndef LATER return STATUS_INVALID_HANDLE; #else hwinsta = ppi->pOpenObjectTable[HI_WINDOWSTATION].h; if (hwinsta == NULL) { return STATUS_INVALID_HANDLE; } pwinsta = (PWINDOWSTATION)ppi->pOpenObjectTable[HI_WINDOWSTATION].phead; #endif } /* * Check security first - does this thread have access? */ if (!RtlAreAllAccessesGranted(ppi->amwinsta, WINSTA_EXITWINDOWS)) { return STATUS_ACCESS_DENIED; } /* * If the client requested shutdown, reboot, or poweroff they must have * the shutdown privilege. */ if (dwFlags & EWX_SHUTDOWN) { if (!IsPrivileged(&psShutdown) ) { return STATUS_PRIVILEGE_NOT_HELD; } } else { /* * If this is a non-IO windowstation and we are not shutting down, * fail the call. */ if (pwinsta->dwWSF_Flags & WSF_NOIO) { return STATUS_INVALID_DEVICE_REQUEST; } } } /* * Is there a shutdown already in progress? */ if (gdwThreadEndSession != 0) { DWORD dwNew; /* * If the current shutdown in another sid and is not being done by * winlogon, override it. */ if (!RtlEqualLuid(&luidCaller, &gpwinstaLogoff->luidEndSession) && (gpidEndSession != gpidLogon)) { return STATUS_RETRY; } /* * Calculate new flags */ dwNew = dwFlags & OPTIONMASK & (~gdwShutdownFlags); /* * Should we override the other shutdown? Make sure * winlogon does not recurse. */ if (dwNew && HandleToUlong(PsGetCurrentThreadId()) != gdwThreadEndSession) { /* * Only one windowstation can be logged off at a time. */ if (!(dwFlags & EWX_SHUTDOWN) && pwinsta != gpwinstaLogoff) { return STATUS_DEVICE_BUSY; } /* Bug# 453872 * Since we are about to fail this call. Do not change gdwShutdownFlags * Later when we notify winlogon in EndShtdown, if we changed gdwShutdownFlags * and the call does not have EWX_WINLOGON_CALLER, winlogon will abort the call * to take care of the case when an application keeps calling ExitWindows. * [msadek- 08/08/2001] */ #if 0 /* * Set the new flags */ gdwShutdownFlags = dwFlags; #endif if (dwNew & EWX_FORCE) { return STATUS_RETRY; } else { return STATUS_PENDING; } } else { /* * Don't override */ return STATUS_PENDING; } } /* * If the caller is not winlogon, signal winlogon to start * the real shutdown. */ if (PsGetThreadProcessId(Thread) != gpidLogon) { if (dwFlags & EWX_NOTIFY) { if (ptiClient && ptiClient->TIF_flags & TIF_16BIT) gptiShutdownNotify = ptiClient; dwFlags &= ~EWX_NOTIFY; *lpdwFlags = dwFlags; } if (NotifyLogon(pwinsta, &luidCaller, dwFlags, STATUS_SUCCESS)) return STATUS_PENDING; else if (ptiClient && ptiClient->cWindows) return STATUS_CANT_WAIT; } /* * Mark this thread as the one that is currently processing * exit windows, and set the global saying someone is exiting */ dwFlags |= EWX_WINLOGON_CALLER; *lpdwFlags = dwFlags; gdwShutdownFlags = dwFlags; gdwThreadEndSession = HandleToUlong(PsGetCurrentThreadId()); SETSYSMETBOOL(SHUTTINGDOWN, TRUE); gpidEndSession = PsGetCurrentThreadProcessId(); gpwinstaLogoff = pwinsta; pwinsta->luidEndSession = luidCaller; /* * Lock the windowstation to prevent apps from starting * while we're doing shutdown processing. */ gdwLocks = pwinsta->dwWSF_Flags & (WSF_SWITCHLOCK | WSF_OPENLOCK); pwinsta->dwWSF_Flags |= (WSF_OPENLOCK | WSF_SHUTDOWN); /* * Set the flag WSF_REALSHUTDOWN if we are not doing just a * logoff */ if (dwFlags & (EWX_WINLOGON_OLD_SHUTDOWN | EWX_WINLOGON_OLD_REBOOT | EWX_SHUTDOWN | EWX_REBOOT)) { pwinsta->dwWSF_Flags |= WSF_REALSHUTDOWN; } return STATUS_SUCCESS; } NTSTATUS EndShutdown( PETHREAD Thread, NTSTATUS StatusShutdown) { PWINDOWSTATION pwinsta = gpwinstaLogoff; PDESKTOP pdesk; LUID luidCaller; UserAssert(gpwinstaLogoff); gpwinstaLogoff = NULL; gpidEndSession = NULL; gdwThreadEndSession = 0; SETSYSMETBOOL(SHUTTINGDOWN, FALSE); pwinsta->dwWSF_Flags &= ~WSF_SHUTDOWN; if (!NT_SUCCESS(GetProcessLuid(Thread, &luidCaller))) { luidCaller = RtlConvertUlongToLuid(0); // null luid } if (!NT_SUCCESS(StatusShutdown)) { /* * We need to notify the process that called ExitWindows that * the logoff was aborted. */ if (gptiShutdownNotify) { _PostThreadMessage(gptiShutdownNotify, WM_ENDSESSION, FALSE, 0); gptiShutdownNotify = NULL; } /* * Reset the windowstation lock flags so apps can start * again. */ pwinsta->dwWSF_Flags = (pwinsta->dwWSF_Flags & ~WSF_OPENLOCK) | gdwLocks; /* * Bug 294204 - joejo * Tell winlogon that we we cancelled shutdown/logoff. */ NotifyLogon(pwinsta, &luidCaller, gdwShutdownFlags | EWX_CANCELED, StatusShutdown); return STATUS_SUCCESS; } gptiShutdownNotify = NULL; /* * If logoff is occuring for the user set by winlogon, perform * the normal logoff cleanup. Otherwise, clear the open lock * and continue. */ if (((pwinsta->luidUser.LowPart != 0) || (pwinsta->luidUser.HighPart != 0)) && RtlEqualLuid(&pwinsta->luidUser, &luidCaller)) { /* * Zero out the free blocks in all desktop heaps. */ for (pdesk = pwinsta->rpdeskList; pdesk != NULL; pdesk = pdesk->rpdeskNext) { RtlZeroHeap(Win32HeapGetHandle(pdesk->pheapDesktop), 0); } /* * Logoff/shutdown was successful. In case this is a logoff, remove * everything from the clipboard so the next logged on user can't get * at this stuff. */ ForceEmptyClipboard(pwinsta); /* * Destroy all non-pinned atoms in the global atom table. User can't * create pinned atoms. Currently only the OLE atoms are pinned. */ RtlEmptyAtomTable(pwinsta->pGlobalAtomTable, FALSE); // this code path is hit only on logoff and also on shutdown // We do not want to unload fonts twice when we attempt shutdown // so we mark that the fonts have been unloaded at a logoff time if (TEST_PUDF(PUDF_FONTSARELOADED)) { LeaveCrit(); GreRemoveAllButPermanentFonts(); EnterCrit(); CLEAR_PUDF(PUDF_FONTSARELOADED); } } else { pwinsta->dwWSF_Flags &= ~WSF_OPENLOCK; } /* * Tell winlogon that we successfully shutdown/logged off. */ NotifyLogon(pwinsta, &luidCaller, gdwShutdownFlags, STATUS_SUCCESS); return STATUS_SUCCESS; } /***************************************************************************\ * xxxClientShutdown2 * * Called by xxxClientShutdown \***************************************************************************/ LONG xxxClientShutdown2( PBWL pbwl, UINT msg, WPARAM wParam) { HWND *phwnd; PWND pwnd; TL tlpwnd; BOOL fEnd; PTHREADINFO ptiCurrent = PtiCurrent(); BOOL fDestroyTimers; LPARAM lParam; /* * Make sure we don't send this window any more WM_TIMER * messages if the session is ending. This was causing * AfterDark to fault when it freed some memory on the * WM_ENDSESSION and then tried to reference it on the * WM_TIMER. * LATER GerardoB: Do we still need to do this?? * Do this horrible thing only if the process is in the * context being logged off. * Perhaps someday we should post a WM_CLOSE so the app * gets a better chance to clean up (if this process is in * the context being logged off, winsrv is going to call * TerminateProcess soon after this). */ fDestroyTimers = (wParam & WMCS_EXIT) && (wParam & WMCS_CONTEXTLOGOFF); /* * fLogOff and fEndSession parameters (WM_ENDSESSION only) */ lParam = wParam & ENDSESSION_LOGOFF; wParam &= WMCS_EXIT; /* * Now enumerate these windows and send the WM_QUERYENDSESSION or * WM_ENDSESSION messages. */ for (phwnd = pbwl->rghwnd; *phwnd != (HWND)1; phwnd++) { if ((pwnd = RevalidateHwnd(*phwnd)) == NULL) continue; ThreadLockAlways(pwnd, &tlpwnd); /* * Send the message. */ switch (msg) { case WM_QUERYENDSESSION: /* * Windows does not send the WM_QUERYENDSESSION to the app * that called ExitWindows */ if (ptiCurrent == gptiShutdownNotify) { fEnd = TRUE; } else { fEnd = (xxxSendMessage(pwnd, WM_QUERYENDSESSION, FALSE, lParam) != 0); if (!fEnd) { RIPMSG2(RIP_WARNING, "xxxClientShutdown2: pwnd:%p canceled shutdown. lParam:%p", pwnd, lParam); } } break; case WM_ENDSESSION: xxxSendMessage(pwnd, WM_ENDSESSION, wParam, lParam); fEnd = TRUE; if (fDestroyTimers) { DestroyWindowsTimers(pwnd); } break; } ThreadUnlock(&tlpwnd); if (!fEnd) return WMCSR_CANCEL; } return WMCSR_ALLOWSHUTDOWN; } /***************************************************************************\ * xxxClientShutdown * * This is the processing that occurs when an application receives a * WM_CLIENTSHUTDOWN message. * * 10-01-92 ScottLu Created. \***************************************************************************/ LONG xxxClientShutdown( PWND pwnd, WPARAM wParam) { PBWL pbwl; PTHREADINFO ptiT; LONG lRet; /* * Build a list of windows first. */ ptiT = GETPTI(pwnd); if ((pbwl = BuildHwndList(ptiT->rpdesk->pDeskInfo->spwnd->spwndChild, BWL_ENUMLIST, ptiT)) == NULL) { /* * Can't allocate memory to notify this thread's windows of shutdown. * Can't do more than kill the app */ return WMCSR_ALLOWSHUTDOWN; } if (wParam & WMCS_QUERYEND) { lRet = xxxClientShutdown2(pbwl, WM_QUERYENDSESSION, wParam); } else { xxxClientShutdown2(pbwl, WM_ENDSESSION, wParam); lRet = WMCSR_DONE; } FreeHwndList(pbwl); return lRet; } /***************************************************************************\ * xxxRegisterUserHungAppHandlers * * This routine simply records the WOW callback address for notification of * "hung" wow apps. * * History: * 01-Apr-1992 jonpa Created. * Added saving and duping of wowexc event handle \***************************************************************************/ BOOL xxxRegisterUserHungAppHandlers( PFNW32ET pfnW32EndTask, HANDLE hEventWowExec) { BOOL bRetVal; PPROCESSINFO ppi; PWOWPROCESSINFO pwpi; ULONG ProcessInfo; NTSTATUS Status; // // Check the Target Process to see if this is a 16-bit process // Status = ZwQueryInformationProcess( NtCurrentProcess(), ProcessWx86Information, &ProcessInfo, sizeof(ProcessInfo), NULL ); if (!NT_SUCCESS(Status) || ProcessInfo == 0) { return FALSE; } // // Allocate the per wow process info stuff // ensuring the memory is Zero init. // pwpi = (PWOWPROCESSINFO) UserAllocPoolWithQuotaZInit( sizeof(WOWPROCESSINFO), TAG_WOWPROCESSINFO); if (!pwpi) return FALSE; // // Reference the WowExec event for kernel access // bRetVal = NT_SUCCESS(ObReferenceObjectByHandle( hEventWowExec, EVENT_ALL_ACCESS, *ExEventObjectType, UserMode, &pwpi->pEventWowExec, NULL )); // // if sucess then intialize the pwpi, ppi structs // else free allocated memory // if (bRetVal) { pwpi->hEventWowExecClient = hEventWowExec; pwpi->lpfnWowExitTask = pfnW32EndTask; ppi = PpiCurrent(); ppi->pwpi = pwpi; // add to the list, order doesn't matter pwpi->pwpiNext = gpwpiFirstWow; gpwpiFirstWow = pwpi; } else { UserFreePool(pwpi); } return bRetVal; }