// -------------------------------------------------------------------------- // Module Name: ThemeServerClient.cpp // // Copyright (c) 2000, Microsoft Corporation // // This file contains a class that implements the theme server functions that // are executed in a client context (winlogon context). // // History: 2000-11-29 vtan created // -------------------------------------------------------------------------- #include "StandardHeader.h" #include "ThemeServerClient.h" #include #include #include #include "SingleThreadedExecution.h" #include "StatusCode.h" #include "ThemeManagerService.h" #include // -------------------------------------------------------------------------- // CThemeManagerAPI::s_pThemeManagerAPIServer // CThemeManagerAPI::s_hPort // CThemeManagerAPI::s_hToken // CThemeManagerAPI::s_hEvent // CThemeManagerAPI::s_hWaitObject // CThemeManagerAPI::s_pLock // // Purpose: Static member variables. // // History: 2000-11-29 vtan created // -------------------------------------------------------------------------- CThemeManagerAPIServer* CThemeServerClient::s_pThemeManagerAPIServer = NULL; HANDLE CThemeServerClient::s_hPort = NULL; HANDLE CThemeServerClient::s_hToken = NULL; HANDLE CThemeServerClient::s_hEvent = NULL; HANDLE CThemeServerClient::s_hWaitObject = NULL; HMODULE CThemeServerClient::s_hModuleUxTheme = NULL; CCriticalSection* CThemeServerClient::s_pLock = NULL; // -------------------------------------------------------------------------- // CThemeServerClient::WaitForServiceReady // // Arguments: dwTimeout = Number of ticks to wait. // // Returns: DWORD // // Purpose: Check if the service is autostart. If so then wait the // designated amount of time for the service. If the service // is then running or was running but isn't autostart then // re-establish the connection to the server. // // History: 2000-10-10 vtan created // 2000-11-29 vtan converted to a Win32 service // -------------------------------------------------------------------------- DWORD CThemeServerClient::WaitForServiceReady (DWORD dwTimeout) { DWORD dwWaitResult; NTSTATUS status; dwWaitResult = WAIT_TIMEOUT; if (s_pThemeManagerAPIServer->IsAutoStart()) { status = s_pThemeManagerAPIServer->Wait(dwTimeout); #ifdef DBG if (STATUS_TIMEOUT == status) { INFORMATIONMSG("Wait on auto start theme service timed out."); } #endif /* DBG */ } else { status = STATUS_SUCCESS; } if (NT_SUCCESS(status) && s_pThemeManagerAPIServer->IsRunning()) { status = ReestablishConnection(); if (NT_SUCCESS(status)) { THR(InitUserRegistry()); THR(InitUserTheme(FALSE)); dwWaitResult = WAIT_OBJECT_0; } } return(dwWaitResult); } // -------------------------------------------------------------------------- // CThemeServerClient::WatchForStart // // Arguments: // // Returns: NTSTATUS // // Purpose: Opens or creates the theme server announce event. This is a // manual reset event which the theme server pulses when it // starts up. This allows winlogon to initiate new connections // to the theme server without having to wait for logon or // logoff events to happen. // // This event is intentionally leaked and cleaned up when the // winlogon process for the session goes away. // // History: 2000-11-29 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::WatchForStart (void) { NTSTATUS status; s_hEvent = CThemeManagerService::OpenStartEvent(NtCurrentPeb()->SessionId, SYNCHRONIZE); if (s_hEvent != NULL) { if (RegisterWaitForSingleObject(&s_hWaitObject, s_hEvent, CB_ServiceStart, NULL, INFINITE, WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE) != FALSE) { status = STATUS_SUCCESS; } else { status = CStatusCode::StatusCodeOfLastError(); } } else { status = CStatusCode::StatusCodeOfLastError(); } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::UserLogon // // Arguments: hToken = Token of user logging on. // // Returns: NTSTATUS // // Purpose: Signals the server that a user is logging on and gives the // server the handle to the token. The server will grant access // to the port based on the user's logon SID. Then perform work // to initialize the environment for the user logging on. // // History: 2000-10-10 vtan created // 2000-11-29 vtan converted to a Win32 service // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::UserLogon (HANDLE hToken) { NTSTATUS status; status = NotifyUserLogon(hToken); if (STATUS_PORT_DISCONNECTED == status) { status = ReestablishConnection(); if (NT_SUCCESS(status)) { status = NotifyUserLogon(hToken); } } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::UserLogoff // // Arguments: // // Returns: NTSTATUS // // Purpose: Signals the server that the current user for this session is // logging off. The server will remove the access that was // granted at logon and reinitialize the theme settings to the // ".Default" settings. // // History: 2000-10-10 vtan created // 2000-11-29 vtan converted to a Win32 service // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::UserLogoff (void) { NTSTATUS status; status = NotifyUserLogoff(); if (STATUS_PORT_DISCONNECTED == status) { status = ReestablishConnection(); if (NT_SUCCESS(status)) { status = NotifyUserLogoff(); } } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::UserInitTheme // // Arguments: BOOL // // Returns: NTSTATUS // // Purpose: Called at logon, or when Terminal Server connects a user to a // remote session or reconnects to a local session. Needs to // evaluate the environment and decide if themes need to be loaded // or unloaded. // // History: 2000-01-18 rfernand created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::UserInitTheme (BOOL fPolicyCheckOnly) { bool fSuccessfulImpersonation; // If there's a token impersonate the user. Otherwise use the system context. if (s_hToken != NULL) { fSuccessfulImpersonation = NT_SUCCESS(CImpersonation::ImpersonateUser(GetCurrentThread(), s_hToken)); } else { fSuccessfulImpersonation = true; } if (fSuccessfulImpersonation) { (HRESULT)InitUserTheme(fPolicyCheckOnly); } if (fSuccessfulImpersonation && (s_hToken != NULL)) { TBOOL(RevertToSelf()); } return STATUS_SUCCESS; } // -------------------------------------------------------------------------- // CThemeServerClient::StaticInitialize // // Arguments: // // Returns: NTSTATUS // // Purpose: Initializes static member variables. Allocate a // CThemeManagerAPIServer and a lock for this object. // // History: 2000-11-29 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::StaticInitialize (void) { NTSTATUS status; if (s_pThemeManagerAPIServer == NULL) { status = STATUS_NO_MEMORY; s_pThemeManagerAPIServer = new CThemeManagerAPIServer; if (s_pThemeManagerAPIServer != NULL) { s_pLock = new CCriticalSection; if (s_pLock != NULL) { status = STATUS_SUCCESS; } } } else { status = STATUS_SUCCESS; } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::StaticTerminate // // Arguments: // // Returns: NTSTATUS // // Purpose: Release static member variables initialized. // // History: 2000-11-29 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::StaticTerminate (void) { if (s_pLock != NULL) { delete s_pLock; s_pLock = NULL; } if (s_pThemeManagerAPIServer != NULL) { s_pThemeManagerAPIServer->Release(); s_pThemeManagerAPIServer = NULL; } return(STATUS_SUCCESS); } // -------------------------------------------------------------------------- // CThemeServerClient::NotifyUserLogon // // Arguments: // // Returns: NTSTATUS // // Purpose: Execute the send message to the server and tell it that the // given user is now logged on. This will instruct the server // to grant access to the ThemeApiPort. // // History: 2000-11-29 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::NotifyUserLogon (HANDLE hToken) { NTSTATUS status; CSingleThreadedExecution lock(*s_pLock); if (s_hPort != NULL) { status = InformServerUserLogon(hToken); } else { status = STATUS_PORT_DISCONNECTED; } // Keep a copy of the token as well in case of demand start of // the theme server so we can impersonate the user when we load // their theme using InitUserTheme. Don't copy it if it already // exists. if (s_hToken == NULL) { TBOOL(DuplicateHandle(GetCurrentProcess(), hToken, GetCurrentProcess(), &s_hToken, 0, FALSE, DUPLICATE_SAME_ACCESS)); } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::NotifyUserLogoff // // Arguments: // // Returns: NTSTATUS // // Purpose: Tell the server that the logged on user is logged off. This // will remove access to ThemeApiPort. // // History: 2000-11-29 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::NotifyUserLogoff (void) { NTSTATUS status; CSingleThreadedExecution lock(*s_pLock); if (s_hToken != NULL) { ReleaseHandle(s_hToken); if (s_hPort != NULL) { status = InformServerUserLogoff(); } else { status = STATUS_PORT_DISCONNECTED; } } else { status = STATUS_SUCCESS; } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::InformServerUserLogon // // Arguments: // // Returns: NTSTATUS // // Purpose: Tell the server that the logged on user is logged off. This // will remove access to ThemeApiPort. // // History: 2000-12-05 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::InformServerUserLogon (HANDLE hToken) { NTSTATUS status; THEMESAPI_PORT_MESSAGE portMessageIn, portMessageOut; ZeroMemory(&portMessageIn, sizeof(portMessageIn)); ZeroMemory(&portMessageOut, sizeof(portMessageOut)); portMessageIn.apiThemes.apiGeneric.ulAPINumber = API_THEMES_USERLOGON; portMessageIn.apiThemes.apiSpecific.apiUserLogon.in.hToken = hToken; portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_THEMES); portMessageIn.portMessage.u1.s1.TotalLength = static_cast(sizeof(THEMESAPI_PORT_MESSAGE)); status = NtRequestWaitReplyPort(s_hPort, &portMessageIn.portMessage, &portMessageOut.portMessage); if (NT_SUCCESS(status)) { status = portMessageOut.apiThemes.apiGeneric.status; if (NT_SUCCESS(status)) { THR(InitUserTheme(FALSE)); } } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::InformServerUserLogoff // // Arguments: // // Returns: NTSTATUS // // Purpose: Tell the server that the logged on user is logged off. This // will remove access to ThemeApiPort. // // History: 2000-12-05 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::InformServerUserLogoff (void) { NTSTATUS status; THEMESAPI_PORT_MESSAGE portMessageIn, portMessageOut; ZeroMemory(&portMessageIn, sizeof(portMessageIn)); ZeroMemory(&portMessageOut, sizeof(portMessageOut)); portMessageIn.apiThemes.apiGeneric.ulAPINumber = API_THEMES_USERLOGOFF; portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_THEMES); portMessageIn.portMessage.u1.s1.TotalLength = static_cast(sizeof(THEMESAPI_PORT_MESSAGE)); status = NtRequestWaitReplyPort(s_hPort, &portMessageIn.portMessage, &portMessageOut.portMessage); if (NT_SUCCESS(status)) { status = portMessageOut.apiThemes.apiGeneric.status; if (NT_SUCCESS(status)) { THR(InitUserRegistry()); THR(InitUserTheme(FALSE)); } } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::SessionCreate // // Arguments: // // Returns: NTSTATUS // // Purpose: Signal the server that a new session is being created. This // allows the server to allocate a data blob for this session. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::SessionCreate (void) { NTSTATUS status; CSingleThreadedExecution lock(*s_pLock); if (s_hModuleUxTheme == NULL) { s_hModuleUxTheme = LoadLibrary(TEXT("uxtheme.dll")); } if (s_hModuleUxTheme != NULL) { void *pfnRegister, *pfnUnregister, *pfnClearStockObjects; // Get the uxtheme function addresses in this process address space. // 34 = ThemeHooksInstall // 35 = ThemeHooksRemove // 62 = ServerClearStockObjects pfnRegister = GetProcAddress(s_hModuleUxTheme, MAKEINTRESOURCEA(34)); pfnUnregister = GetProcAddress(s_hModuleUxTheme, MAKEINTRESOURCEA(35)); pfnClearStockObjects = GetProcAddress(s_hModuleUxTheme, MAKEINTRESOURCEA(62)); if ((pfnRegister != NULL) && (pfnUnregister != NULL) && (pfnClearStockObjects != NULL)) { DWORD dwStackSizeReserve, dwStackSizeCommit; ULONG ulReturnLength; IMAGE_NT_HEADERS *pNTHeaders; SYSTEM_BASIC_INFORMATION systemBasicInformation; THEMESAPI_PORT_MESSAGE portMessageIn, portMessageOut; // Get system basic information for stack size defaults. status = NtQuerySystemInformation(SystemBasicInformation, &systemBasicInformation, sizeof(systemBasicInformation), &ulReturnLength); if (NT_SUCCESS(status)) { dwStackSizeReserve = systemBasicInformation.AllocationGranularity; dwStackSizeCommit = systemBasicInformation.PageSize; } else { dwStackSizeReserve = dwStackSizeCommit = 0; } // Go to the image header for this process and get the stack size // defaults if they are specified. Otherwise use system defaults (above). pNTHeaders = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress); if (pNTHeaders != NULL) { dwStackSizeReserve = static_cast(pNTHeaders->OptionalHeader.SizeOfStackReserve); dwStackSizeCommit = static_cast(pNTHeaders->OptionalHeader.SizeOfStackCommit); } // Make the call. ZeroMemory(&portMessageIn, sizeof(portMessageIn)); ZeroMemory(&portMessageOut, sizeof(portMessageOut)); portMessageIn.apiThemes.apiGeneric.ulAPINumber = API_THEMES_SESSIONCREATE; portMessageIn.apiThemes.apiSpecific.apiSessionCreate.in.pfnRegister = pfnRegister; portMessageIn.apiThemes.apiSpecific.apiSessionCreate.in.pfnUnregister = pfnUnregister; portMessageIn.apiThemes.apiSpecific.apiSessionCreate.in.pfnClearStockObjects = pfnClearStockObjects; portMessageIn.apiThemes.apiSpecific.apiSessionCreate.in.dwStackSizeReserve = dwStackSizeReserve; portMessageIn.apiThemes.apiSpecific.apiSessionCreate.in.dwStackSizeCommit = dwStackSizeCommit; portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_THEMES); portMessageIn.portMessage.u1.s1.TotalLength = static_cast(sizeof(THEMESAPI_PORT_MESSAGE)); status = NtRequestWaitReplyPort(s_hPort, &portMessageIn.portMessage, &portMessageOut.portMessage); if (NT_SUCCESS(status)) { status = portMessageOut.apiThemes.apiGeneric.status; } } else { status = CStatusCode::StatusCodeOfLastError(); } } else { status = CStatusCode::StatusCodeOfLastError(); } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::SessionDestroy // // Arguments: // // Returns: NTSTATUS // // Purpose: Signal the server that the current session is about to be // destroyed. This allows the server to release the data blob // allocated. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::SessionDestroy (void) { NTSTATUS status; THEMESAPI_PORT_MESSAGE portMessageIn, portMessageOut; CSingleThreadedExecution lock(*s_pLock); ZeroMemory(&portMessageIn, sizeof(portMessageIn)); ZeroMemory(&portMessageOut, sizeof(portMessageOut)); portMessageIn.apiThemes.apiGeneric.ulAPINumber = API_THEMES_SESSIONDESTROY; portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_THEMES); portMessageIn.portMessage.u1.s1.TotalLength = static_cast(sizeof(THEMESAPI_PORT_MESSAGE)); status = NtRequestWaitReplyPort(s_hPort, &portMessageIn.portMessage, &portMessageOut.portMessage); if (NT_SUCCESS(status)) { status = portMessageOut.apiThemes.apiGeneric.status; } if (s_hModuleUxTheme != NULL) { TBOOL(FreeLibrary(s_hModuleUxTheme)); s_hModuleUxTheme = NULL; } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::ReestablishConnection // // Arguments: // // Returns: NTSTATUS // // Purpose: Reconnects to theme server. If the reconnection is established // the re-create the session data. This will not correct any // disconnected ports that some clients may have but because this // is called in winlogon it re-establish this correctly for // session 0 in all cases. // // UnregisterUserApiHook must be called to clear any left over // registrations from a server that died. Then go ahead and // re-initialize the environment anyway. // // History: 2000-11-17 vtan created // -------------------------------------------------------------------------- NTSTATUS CThemeServerClient::ReestablishConnection (void) { NTSTATUS status; ReleaseHandle(s_hPort); status = s_pThemeManagerAPIServer->ConnectToServer(&s_hPort); if (NT_SUCCESS(status)) { status = SessionCreate(); if (NT_SUCCESS(status)) { (BOOL)UnregisterUserApiHook(); THR(ReestablishServerConnection()); } } return(status); } // -------------------------------------------------------------------------- // CThemeServerClient::CB_ServiceStart // // Arguments: pParameter = User parameter. // TimerOrWaitFired = Timer or wait fired. // // Returns: // // Purpose: Callback called when the theme server ready event is signaled. // This indicates that the service was demand started or // restarted in the event of failure. // // History: 2000-11-29 vtan created // -------------------------------------------------------------------------- void CALLBACK CThemeServerClient::CB_ServiceStart (void *pParameter, BOOLEAN TimerOrWaitFired) { UNREFERENCED_PARAMETER(pParameter); UNREFERENCED_PARAMETER(TimerOrWaitFired); NTSTATUS status; CSingleThreadedExecution lock(*s_pLock); // If there is a connection ping it. if (s_hPort != NULL) { THEMESAPI_PORT_MESSAGE portMessageIn, portMessageOut; ZeroMemory(&portMessageIn, sizeof(portMessageIn)); ZeroMemory(&portMessageOut, sizeof(portMessageOut)); portMessageIn.apiThemes.apiGeneric.ulAPINumber = API_THEMES_PING; portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_THEMES); portMessageIn.portMessage.u1.s1.TotalLength = static_cast(sizeof(THEMESAPI_PORT_MESSAGE)); status = NtRequestWaitReplyPort(s_hPort, &portMessageIn.portMessage, &portMessageOut.portMessage); if (NT_SUCCESS(status)) { status = portMessageOut.apiThemes.apiGeneric.status; } } else { status = STATUS_PORT_DISCONNECTED; } if (STATUS_PORT_DISCONNECTED == status) { HDESK hDeskCurrent, hDeskInput; // Set this thread's desktop to the input desktop so // that the theme change can be broadcast to the input // desktop. This is Default in most cases where a logged // on user is active but in the non-logged on user case // this will be Winlogon. Restore the thread's desktop // when done. TSTATUS(ReestablishConnection()); hDeskCurrent = hDeskInput = NULL; if (s_hToken != NULL) { hDeskCurrent = GetThreadDesktop(GetCurrentThreadId()); hDeskInput = OpenInputDesktop(0, FALSE, MAXIMUM_ALLOWED); if ((hDeskCurrent != NULL) && (hDeskInput != NULL)) { TBOOL(SetThreadDesktop(hDeskInput)); } if (NT_SUCCESS(CImpersonation::ImpersonateUser(GetCurrentThread(), s_hToken))) { TSTATUS(InformServerUserLogon(s_hToken)); } if ((hDeskCurrent != NULL) && (hDeskInput != NULL)) { SetThreadDesktop(hDeskCurrent); (BOOL)CloseDesktop(hDeskInput); } TBOOL(RevertToSelf()); } else { THR(InitUserRegistry()); THR(InitUserTheme(FALSE)); } } // Reset the event here and now. TBOOL(ResetEvent(s_hEvent)); // Unregister the original wait (it only executes once anyway). This // call will return a failure code with the callback in progress. // Ignore this error. The thread pool will clean up the wait. (BOOL)UnregisterWait(s_hWaitObject); // Reregister the wait as execute once only again waiting for // the next time the event is signaled. TBOOL(RegisterWaitForSingleObject(&s_hWaitObject, s_hEvent, CB_ServiceStart, NULL, INFINITE, WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)); }