You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
666 lines
19 KiB
666 lines
19 KiB
/****************************************************************************/
|
|
// 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);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|