|
|
/****************************************************************************/ // notify.c
//
// Copyright (C) 1997-1999 Microsoft Corp.
/****************************************************************************/
#include "precomp.h"
#pragma hdrstop
#include "errorlog.h"
#include "regapi.h"
#include "drdbg.h"
#include "rdpprutl.h"
#include "Sddl.h"
//
// Some helpful tips about winlogon's notify events
//
// 1) If you plan to put up any UI at logoff, you have to set
// Asynchronous flag to 0. If this isn't set to 0, the user's
// profile will fail to unload because UI is still active.
//
// 2) If you need to spawn child processes, you have to use
// CreateProcessAsUser() otherwise the process will start
// on winlogon's desktop (not the user's)
//
// 2) The logon notification comes before the user's network
// connections are restored. If you need the user's persisted
// net connections, use the StartShell event.
//
//
// Global debug flag.
extern DWORD GLOBAL_DEBUG_FLAGS;
BOOL g_Console = TRUE; ULONG g_SessionId; BOOL g_InitialProg = FALSE; HANDLE hExecProg; HINSTANCE g_hInstance = NULL; CRITICAL_SECTION GlobalsLock; CRITICAL_SECTION ExecProcLock; BOOL g_IsPersonal; BOOL bInitLocks = FALSE;
#define NOTIFY_PATH TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify\\HydraNotify")
#define VOLATILE_PATH TEXT("Volatile Environment")
#define STARTUP_PROGRAM TEXT("StartupPrograms")
#define APPLICATION_DESKTOP_NAME TEXT("Default")
#define WINDOW_STATION_NAME TEXT("WinSta0")
#define IsTerminalServer() (BOOLEAN)(USER_SHARED_DATA->SuiteMask & (1 << TerminalServer))
PCRITICAL_SECTION CtxGetSyslibCritSect(void);
BOOL TSDLLInit(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { NTSTATUS Status; OSVERSIONINFOEX versionInfo; static BOOL sLogInit = FALSE;
switch (dwReason) { case DLL_PROCESS_ATTACH: { if (!IsTerminalServer()) { return FALSE; } g_hInstance = hInstance; if (g_SessionId = NtCurrentPeb()->SessionId) { g_Console = FALSE; }
Status = RtlInitializeCriticalSection( &GlobalsLock ); if( !NT_SUCCESS(Status) ) { OutputDebugString (TEXT("LibMain (PROCESS_ATTACH): Could not initialize critical section\n")); return(FALSE); } Status = RtlInitializeCriticalSection( &ExecProcLock ); if( !NT_SUCCESS(Status) ) { OutputDebugString (TEXT("LibMain (PROCESS_ATTACH): Could not initialize critical section\n")); RtlDeleteCriticalSection( &GlobalsLock ); return(FALSE); }
if (CtxGetSyslibCritSect() != NULL) { TsInitLogging(); sLogInit = TRUE; }else{ RtlDeleteCriticalSection( &GlobalsLock ); RtlDeleteCriticalSection( &ExecProcLock ); return FALSE; }
//
// Find out if we are running Personal.
//
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if (!GetVersionEx((LPOSVERSIONINFO)&versionInfo)) { DBGMSG(DBG_TRACE, ("GetVersionEx: %08X\n", GetLastError())); RtlDeleteCriticalSection( &GlobalsLock ); RtlDeleteCriticalSection( &ExecProcLock ); return FALSE; } g_IsPersonal = (versionInfo.wProductType == VER_NT_WORKSTATION) && (versionInfo.wSuiteMask & VER_SUITE_PERSONAL); bInitLocks = TRUE; } break; case DLL_PROCESS_DETACH: { PRTL_CRITICAL_SECTION pLock = NULL;
g_hInstance = NULL; if (sLogInit) { TsStopLogging(); pLock = CtxGetSyslibCritSect(); if (pLock) RtlDeleteCriticalSection(pLock); } if (bInitLocks) { RtlDeleteCriticalSection( &GlobalsLock ); RtlDeleteCriticalSection( &ExecProcLock ); bInitLocks = FALSE; } } break; }
return TRUE;
}
VOID ExecApplications() { BOOL rc; ULONG ReturnLength; WDCONFIG WdInfo;
//
// HelpAssistant session doesn't need rdpclip.exe
//
if( WinStationIsHelpAssistantSession(SERVERNAME_CURRENT, LOGONID_CURRENT) ) { return; }
//
// Query winstation driver info
//
rc = WinStationQueryInformation( SERVERNAME_CURRENT, LOGONID_CURRENT, WinStationWd, (PVOID)&WdInfo, sizeof(WDCONFIG), &ReturnLength);
if (rc) { if (ReturnLength == sizeof(WDCONFIG)) { HKEY hSpKey; WCHAR szRegPath[MAX_PATH];
//
// Open winstation driver reg key
//
wcscpy( szRegPath, WD_REG_NAME ); wcscat( szRegPath, L"\\" ); wcscat( szRegPath, WdInfo.WdPrefix ); wcscat( szRegPath, L"wd" );
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegPath, 0, KEY_READ, &hSpKey) == ERROR_SUCCESS) { DWORD dwLen; DWORD dwType; WCHAR szCmdLine[MAX_PATH];
//
// Get StartupPrograms string value
//
dwLen = sizeof( szCmdLine ); if (RegQueryValueEx(hSpKey, STARTUP_PROGRAM, NULL, &dwType, (PCHAR) &szCmdLine, &dwLen) == ERROR_SUCCESS) { PWSTR pszTok; WCHAR szDesktop[MAX_PATH]; STARTUPINFO si; PROCESS_INFORMATION pi; LPBYTE lpEnvironment = NULL;
//
// set STARTUPINFO fields
//
wsprintfW(szDesktop, L"%s\\%s", WINDOW_STATION_NAME, APPLICATION_DESKTOP_NAME); si.cb = sizeof(STARTUPINFO); si.lpReserved = NULL; si.lpTitle = NULL; si.lpDesktop = szDesktop; si.dwX = si.dwY = si.dwXSize = si.dwYSize = 0L; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWNORMAL | SW_SHOWMINNOACTIVE; si.lpReserved2 = NULL; si.cbReserved2 = 0;
//
// Get the user Environment block to be used in CreateProcessAsUser
//
if (CreateEnvironmentBlock (&lpEnvironment, g_UserToken, FALSE)) { //
// Enumerate the StartupPrograms string,
//
pszTok = wcstok(szCmdLine, L","); while (pszTok != NULL) { // skip any white space
if (*pszTok == L' ') { while (*pszTok++ == L' '); }
//
// Call CreateProcessAsUser to start the program
//
si.lpReserved = (LPTSTR)pszTok; si.lpTitle = (LPTSTR)pszTok; rc = CreateProcessAsUser( g_UserToken, NULL, (LPTSTR)pszTok, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, NULL, &si, &pi);
if (rc) { DebugLog((DEB_TRACE, "TSNOTIFY: successfully called CreateProcessAsUser for %s", (LPTSTR)pszTok));
CloseHandle(pi.hThread); //CloseHandle(pi.hProcess);
hExecProg = pi.hProcess; } else { DebugLog((DEB_ERROR, "TSNOTIFY: failed calling CreateProcessAsUser for %s", (LPTSTR)pszTok)); }
// move onto the next token
pszTok = wcstok(NULL, L","); } DestroyEnvironmentBlock(lpEnvironment); } else { DebugLog((DEB_ERROR, "TSNOTIFY: failed to get Environment block for user, %ld", GetLastError()));
} } else { DebugLog((DEB_ERROR, "TSNOTIFY: failed to read the StartupPrograms key")); }
RegCloseKey(hSpKey); } else { DebugLog((DEB_ERROR, "TSNOTIFY: failed to open the rdpwd key")); } } else { DebugLog((DEB_ERROR, "TSNOTIFY: WinStationQueryInformation didn't return correct length")); } } else { DebugLog((DEB_ERROR, "TSNOTIFY: WinStationQueryInformation call failed")); } }
VOID TSUpdateUserConfig( PWLX_NOTIFICATION_INFO pInfo) {
HINSTANCE hLib; typedef void ( WINAPI TypeDef_fp) ( HANDLE ); TypeDef_fp *fp1;
hLib = LoadLibrary(TEXT("winsta.dll"));
if ( !hLib) { DebugLog (( DEB_ERROR, "TSNOTIFY: Unable to load lib winsta.dll")); return; }
fp1 = ( TypeDef_fp *) GetProcAddress(hLib, "_WinStationUpdateUserConfig"); if (fp1) { fp1 ( pInfo->hToken ); } else { DebugLog (( DEB_ERROR, "TSNOTIFY: Unable to find proc in winsta.dll")); }
FreeLibrary(hLib); }
VOID TSEventLogon (PWLX_NOTIFICATION_INFO pInfo) {
if (!IsTerminalServer()) { return; }
g_UserToken = pInfo->hToken;
if (!g_Console) {
//
// Notify the EXEC service that the user is
// logged on
//
CtxExecServerLogon( pInfo->hToken ); }
EnterCriticalSection( &ExecProcLock ); if (!IsActiveConsoleSession() && (hExecProg == NULL)) {
//
// Search for StartupPrograms string in Terminal Server WD registry key
// and start processes as needed
//
ExecApplications(); } LeaveCriticalSection( &ExecProcLock ); }
VOID TSEventLogoff (PWLX_NOTIFICATION_INFO pInfo) {
if (!IsTerminalServer()) { return; }
if (!g_Console) {
RemovePerSessionTempDirs();
CtxExecServerLogoff();
}
if ( g_InitialProg ) {
DeleteUserProcessMonitor( UserProcessMonitor );
}
if (g_Console) {
//
//Turn off the install mode if the console user is logging off
//
SetTermsrvAppInstallMode( FALSE );
}
EnterCriticalSection( &ExecProcLock ); // Shut down the user-mode RDP device manager component.
if (!g_IsPersonal) { UMRDPDR_Shutdown(); }
g_UserToken = NULL; CloseHandle(hExecProg); hExecProg = NULL; LeaveCriticalSection( &ExecProcLock ); }
VOID TSEventStartup (PWLX_NOTIFICATION_INFO pInfo) { if (!IsTerminalServer()) {
return;
} if (!g_Console) {
//
// Start ExecServer thread
//
StartExecServerThread(); } }
VOID TSEventShutdown (PWLX_NOTIFICATION_INFO pInfo) { if (!IsTerminalServer()) {
return;
}
// Shut down the user-mode RDP device manager component. This function can be
// called multiple times, in the event that it was already called as a result of
// a log off.
if (!g_IsPersonal) { UMRDPDR_Shutdown(); } }
LPTSTR GetStringSid(PWLX_NOTIFICATION_INFO pInfo) { LPTSTR sStringSid = NULL; DWORD ReturnLength = 0; PTOKEN_USER pTokenUser = NULL; PSID pSid = NULL;
NtQueryInformationToken(pInfo->hToken, TokenUser, NULL, 0, &ReturnLength);
if (ReturnLength == 0) return NULL;
pTokenUser = RtlAllocateHeap(RtlProcessHeap(), 0, ReturnLength);
if (pTokenUser != NULL) { if (NT_SUCCESS(NtQueryInformationToken(pInfo->hToken, TokenUser, pTokenUser, ReturnLength, &ReturnLength))) { pSid = pTokenUser->User.Sid; if (pSid != NULL) { if (!ConvertSidToStringSid(pSid, &sStringSid)) sStringSid = NULL; } } }
if (pTokenUser != NULL) RtlFreeHeap(RtlProcessHeap(), 0, pTokenUser);
return sStringSid; }
BOOL IsAppServer(void) { OSVERSIONINFOEX osVersionInfo; DWORDLONG dwlConditionMask = 0; BOOL fIsWTS = FALSE; osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); fIsWTS = GetVersionEx((OSVERSIONINFO *)&osVersionInfo) && (osVersionInfo.wSuiteMask & VER_SUITE_TERMINAL) && !(osVersionInfo.wSuiteMask & VER_SUITE_SINGLEUSERTS);
return fIsWTS; }
VOID RemoveClassesKey(PWLX_NOTIFICATION_INFO pInfo) { HINSTANCE hLib; typedef BOOL ( WINAPI TypeDef_fp) (LPTSTR); TypeDef_fp *fp1; LPTSTR sStringSid = NULL;
sStringSid = GetStringSid(pInfo); if (sStringSid == NULL) { DebugLog((DEB_ERROR, "TSNOTIFY: Unable to obtain sid")); return; }
hLib = LoadLibrary(TEXT("tsappcmp.dll"));
if (!hLib) { DebugLog((DEB_ERROR, "TSNOTIFY: Unable to load lib tsappcmp.dll")); return; }
fp1 = (TypeDef_fp*) GetProcAddress(hLib, "TermsrvRemoveClassesKey"); if (fp1) fp1(sStringSid); else DebugLog((DEB_ERROR, "TSNOTIFY: Unable to find proc in tsappcmp.dll"));
FreeLibrary(hLib); }
VOID TSEventStartShell (PWLX_NOTIFICATION_INFO pInfo) { if (!IsTerminalServer()) return;
// We are either a TS-App-Server, a TS-Remote-Admin, or a PTS since
// IsTerminalServer() call is using the kernel flag to check this.
// by now, group policy has update user's hive, so we can tell termsrv
// to update user's config.
TSUpdateUserConfig(pInfo);
if (IsAppServer()) RemoveClassesKey(pInfo); }
VOID TSEventReconnect (PWLX_NOTIFICATION_INFO pInfo) { if (!IsTerminalServer()) {
return;
}
EnterCriticalSection( &ExecProcLock ); if (!IsActiveConsoleSession()) {
if (g_UserToken && hExecProg == NULL) { //
// Search for StartupPrograms string in Terminal Server WD registry key
// and start processes as needed
//
ExecApplications(); // Initialize the user-mode RDP device manager component.
if (!g_IsPersonal) { if (!UMRDPDR_Initialize(g_UserToken)) { WCHAR buf[256]; WCHAR *parms[1]; parms[0] = buf; wsprintf(buf, L"%ld", g_SessionId); TsLogError(EVENT_NOTIFY_INIT_FAILED, EVENTLOG_ERROR_TYPE, 1, parms, __LINE__); } } }
} else {
if (hExecProg) { TerminateProcess(hExecProg, 0); CloseHandle(hExecProg); hExecProg = NULL; } // Shut down the user-mode RDP device manager component.
if (!g_IsPersonal) { UMRDPDR_Shutdown(); } } LeaveCriticalSection( &ExecProcLock ); }
VOID TSEventDisconnect (PWLX_NOTIFICATION_INFO pInfo) { if (!IsTerminalServer()) {
return;
}
}
VOID TSEventPostShell (PWLX_NOTIFICATION_INFO pInfo) { OSVERSIONINFOEX versionInfo;
if (!IsTerminalServer()) {
return;
}
if ( !g_Console ) { ULONG Length; BOOLEAN Result; WINSTATIONCONFIG ConfigData;
Result = WinStationQueryInformation( SERVERNAME_CURRENT, LOGONID_CURRENT, WinStationConfiguration, &ConfigData, sizeof(ConfigData), &Length );
if (Result && ConfigData.User.InitialProgram[0] && lstrcmpi(ConfigData.User.InitialProgram, TEXT("explorer.exe"))) {
if ( !(UserProcessMonitor = StartUserProcessMonitor()) ) { DebugLog((DEB_ERROR, "Failed to start user process monitor thread")); }
g_InitialProg = TRUE;
} }
//
// Clean up old TS queues on Pro.
//
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if (!GetVersionEx((LPOSVERSIONINFO)&versionInfo)) { DBGMSG(DBG_TRACE, ("GetVersionEx: %08X\n", GetLastError())); ASSERT(FALSE); } //
// This code only runs on Pro because it's the only platform where
// we can guarantee that we have one session per machine. Printers are
// cleaned up on boot in Server.
//
else if ((versionInfo.wProductType == VER_NT_WORKSTATION) && !(versionInfo.wSuiteMask & VER_SUITE_PERSONAL)) { RDPDRUTL_RemoveAllTSPrinters(); }
//
// This code shouldn't run on Personal. Device redirection isn't
// supported for Personal.
//
if (!g_IsPersonal) { EnterCriticalSection( &ExecProcLock ); // Initialize the user-mode RDP device manager component.
if (!UMRDPDR_Initialize(pInfo->hToken)) { WCHAR buf[256]; WCHAR *parms[1]; wsprintf(buf, L"%ld", g_SessionId); parms[0] = buf; TsLogError(EVENT_NOTIFY_INIT_FAILED, EVENTLOG_ERROR_TYPE, 1, parms, __LINE__); } LeaveCriticalSection(&ExecProcLock);
} }
|