Windows2003-3790/termsrv/notify/notify.c

667 lines
19 KiB
C

/****************************************************************************/
// 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);
}
}