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.
2091 lines
74 KiB
2091 lines
74 KiB
/****************************************************************************/
|
|
// sessdir.cpp
|
|
//
|
|
// TS Session Directory code used by TermSrv.exe.
|
|
//
|
|
// Copyright (C) 2000 Microsot Corporation
|
|
/****************************************************************************/
|
|
|
|
// precomp.h includes COM base headers.
|
|
#define INITGUID
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#include "icaevent.h"
|
|
|
|
#include "sessdir.h"
|
|
|
|
#pragma warning (push, 4)
|
|
|
|
#define CLSIDLENGTH 39
|
|
#define STORESERVERNAMELENGTH 64
|
|
#define CLUSTERNAMELENGTH 64
|
|
#define OPAQUESETTINGSLENGTH 256
|
|
#define IPADDRESSLENGTH 64
|
|
|
|
#define TOTAL_STRINGS_LENGTH 640
|
|
#define USERNAME_OFFSET 0
|
|
#define DOMAIN_OFFSET 256
|
|
#define APPLICATIONTYPE_OFFSET 384
|
|
|
|
#define SINGLE_SESSION_FLAG 0x1
|
|
|
|
// Extern defined in icasrv.c.
|
|
extern "C" WCHAR gpszServiceName[];
|
|
|
|
// Extern defined in winsta.c
|
|
extern "C" LIST_ENTRY WinStationListHead; // protected by WinStationListLock
|
|
|
|
extern "C" void PostErrorValueEvent(unsigned EventCode, DWORD ErrVal);
|
|
|
|
extern "C" BOOL IsCallerSystem( VOID );
|
|
extern "C" BOOL IsCallerAdmin( VOID );
|
|
|
|
WCHAR g_LocalServerAddress[64];
|
|
ULONG g_LocalIPAddress = 0;
|
|
|
|
BOOL g_SessDirUseServerAddr = TRUE;
|
|
|
|
DWORD g_WaitForRepopulate = TS_WAITFORREPOPULATE_TIMEOUT * 1000;
|
|
|
|
// Do not access directly. Use *TSSD functions.
|
|
//
|
|
// These variables are used to manage synchronization with retrieving the
|
|
// pointer to the COM object. See *TSSD, below, for details on how they are
|
|
// used.
|
|
ITSSessionDirectory *g_pTSSDPriv = NULL;
|
|
CRITICAL_SECTION g_CritSecComObj;
|
|
CRITICAL_SECTION g_CritSecInitialize;
|
|
int g_nComObjRefCount = 0;
|
|
BOOL g_bCritSecsInitialized = FALSE;
|
|
|
|
// Do not access directly. Use *TSSDEx functions.
|
|
//
|
|
// These variables are used to manage synchronization with retrieving the
|
|
// pointer to the COM object. See *TSSDEx, below, for details on how they are
|
|
// used.
|
|
ITSSessionDirectoryEx *g_pTSSDExPriv = NULL;
|
|
int g_nTSSDExObjRefCount = 0;
|
|
|
|
|
|
/****************************************************************************/
|
|
// SessDirGetLocalIPAddr
|
|
//
|
|
// Gets the local IP address of this machine. On success, returns 0. On
|
|
// failure, returns a failure code from the function that failed.
|
|
/****************************************************************************/
|
|
DWORD SessDirGetLocalIPAddr(WCHAR *LocalIP)
|
|
{
|
|
DWORD NameSize;
|
|
unsigned char *tempaddr;
|
|
WCHAR psServerName[64];
|
|
char psServerNameA[64];
|
|
|
|
NameSize = sizeof(psServerName) / sizeof(WCHAR);
|
|
if (GetComputerNameEx(ComputerNamePhysicalDnsHostname,
|
|
psServerName, &NameSize)) {
|
|
// Temporary code to get an IP address. This should be replaced in the
|
|
// fix to bug #323867.
|
|
struct hostent *hptr;
|
|
|
|
// change the wide character string to non-wide
|
|
sprintf(psServerNameA, "%S", psServerName);
|
|
|
|
if ((hptr = gethostbyname(psServerNameA)) == 0) {
|
|
DWORD Err = WSAGetLastError();
|
|
|
|
return Err;
|
|
}
|
|
|
|
tempaddr = (unsigned char *)*(hptr->h_addr_list);
|
|
wsprintf(LocalIP, L"%d.%d.%d.%d", tempaddr[0], tempaddr[1],
|
|
tempaddr[2], tempaddr[3]);
|
|
}
|
|
else {
|
|
DWORD Err = GetLastError();
|
|
|
|
return Err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// Remove preceding and succeding space in str
|
|
//
|
|
// Assume the str is NULL terminated
|
|
/****************************************************************************/
|
|
void RemoveSpaceInStr(WCHAR *str)
|
|
{
|
|
WCHAR *strEnd, *strTemp;
|
|
size_t len, i;
|
|
|
|
if ((str == NULL) || (wcslen(str) == 0)) {
|
|
return;
|
|
}
|
|
|
|
len = wcslen(str);
|
|
// strEnd point to the last char in str
|
|
strEnd = str + len -1;
|
|
// Remove the succeding blank space in the str
|
|
for (strTemp=strEnd; strTemp>=str; strTemp--) {
|
|
if (strTemp[0] == L' ') {
|
|
strTemp[0] = L'\0';
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
// Get the length of the new string
|
|
len = wcslen(str);
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
// Find the 1st non-space char in str
|
|
for (i=0; i<len; i++) {
|
|
if (str[i] != L' ') {
|
|
break;
|
|
}
|
|
}
|
|
if (i != 0) {
|
|
// New str length
|
|
len -= i;
|
|
wcsncpy(str, str + i, len);
|
|
}
|
|
str[len] = '\0';
|
|
|
|
return;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// InitSessionDirectoryEx
|
|
//
|
|
// Reads values from the registry, and either initializes the session
|
|
// directory or updates it, depending on the value of the Update parameter.
|
|
/****************************************************************************/
|
|
DWORD InitSessionDirectoryEx(DWORD UpdatePara)
|
|
{
|
|
DWORD Len;
|
|
DWORD Type;
|
|
DWORD DataSize;
|
|
BOOL hKeyTermSrvSucceeded = FALSE;
|
|
HRESULT hr;
|
|
DWORD ErrVal = 0;
|
|
CLSID TSSDCLSID;
|
|
CLSID TSSDEXCLSID;
|
|
LONG RegRetVal;
|
|
HKEY hKey = NULL;
|
|
HKEY hKeyTermSrv = NULL;
|
|
ITSSessionDirectory *pTSSD = NULL;
|
|
ITSSessionDirectoryEx *pTSSDEx = NULL;
|
|
BOOL bClusteringActive = FALSE;
|
|
BOOL bThisServerIsInSingleSessionMode;
|
|
WCHAR CLSIDStr[CLSIDLENGTH];
|
|
WCHAR CLSIDEXStr[CLSIDLENGTH];
|
|
WCHAR StoreServerName[STORESERVERNAMELENGTH];
|
|
WCHAR ClusterName[CLUSTERNAMELENGTH];
|
|
WCHAR OpaqueSettings[OPAQUESETTINGSLENGTH];
|
|
WCHAR SDRedirectionIP[IPADDRESSLENGTH];
|
|
unsigned char *tempaddr;
|
|
BOOL Update = FALSE;
|
|
BOOL ForceRejoin = FALSE;
|
|
LONG RepopulateWaitTimeout = TS_WAITFORREPOPULATE_TIMEOUT;
|
|
|
|
if (UpdatePara & TSSD_UPDATE)
|
|
Update = TRUE;
|
|
if (UpdatePara & TSSD_FORCEREJOIN)
|
|
ForceRejoin = TRUE;
|
|
|
|
|
|
if (g_bCritSecsInitialized == FALSE) {
|
|
ASSERT(FALSE);
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_INIT_TSSD,
|
|
(DWORD) E_OUTOFMEMORY);
|
|
return (DWORD) E_OUTOFMEMORY;
|
|
}
|
|
|
|
// trevorfo: Load only if any 1 loaded protocol needs it? Requires running
|
|
// off of StartAllWinStations.
|
|
|
|
// No more than one thread should be doing initialization.
|
|
EnterCriticalSection(&g_CritSecInitialize);
|
|
|
|
// Load registry keys.
|
|
RegRetVal = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_CONTROL_TSERVER, 0,
|
|
KEY_READ, &hKeyTermSrv);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_INIT_TSSD,
|
|
RegRetVal);
|
|
goto RegFailExit;
|
|
}
|
|
else {
|
|
hKeyTermSrvSucceeded = TRUE;
|
|
}
|
|
|
|
RegRetVal = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_TS_CLUSTERSETTINGS, 0,
|
|
KEY_READ, &hKey);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
DBGPRINT(("TERMSRV: RegOpenKeyEx for ClusterSettings err %u\n",
|
|
RegRetVal));
|
|
goto RegFailExit;
|
|
}
|
|
|
|
//
|
|
// First, we get the serious settings--active, SD location, and cluster
|
|
// name.
|
|
//
|
|
// If group policy exists for all three, use that. Otherwise, use what
|
|
// is in the registry.
|
|
//
|
|
|
|
StoreServerName[0] = L'\0';
|
|
ClusterName[0] = L'\0';
|
|
OpaqueSettings[0] = L'\0';
|
|
SDRedirectionIP[0] = L'\0';
|
|
|
|
if (g_MachinePolicy.fPolicySessionDirectoryActive) {
|
|
bClusteringActive = g_MachinePolicy.SessionDirectoryActive;
|
|
}
|
|
else { //Read from registry
|
|
Len = sizeof(bClusteringActive);
|
|
RegQueryValueEx(hKeyTermSrv, REG_TS_SESSDIRACTIVE, NULL, &Type,
|
|
(BYTE *)&bClusteringActive, &Len);
|
|
}
|
|
|
|
// Get SD server name
|
|
if (g_MachinePolicy.fPolicySessionDirectoryLocation) {
|
|
wcsncpy(StoreServerName, g_MachinePolicy.SessionDirectoryLocation,
|
|
STORESERVERNAMELENGTH);
|
|
StoreServerName[STORESERVERNAMELENGTH - 1] = '\0';
|
|
}
|
|
else { //Read from registry
|
|
// Not an error for the name to be absent or empty.
|
|
DataSize = sizeof(StoreServerName);
|
|
RegRetVal = RegQueryValueExW(hKey, REG_TS_CLUSTER_STORESERVERNAME,
|
|
NULL, &Type, (BYTE *)StoreServerName, &DataSize);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
DBGPRINT(("TERMSRV: Failed RegQuery for StoreSvrName - "
|
|
"err=%u, DataSize=%u, type=%u\n",
|
|
RegRetVal, DataSize, Type));
|
|
}
|
|
}
|
|
|
|
// Get SD cluster name
|
|
if (g_MachinePolicy.fPolicySessionDirectoryClusterName) {
|
|
wcsncpy(ClusterName, g_MachinePolicy.SessionDirectoryClusterName,
|
|
CLUSTERNAMELENGTH);
|
|
ClusterName[CLUSTERNAMELENGTH - 1] = '\0';
|
|
}
|
|
else { //Read from registry
|
|
// Not an error for the name to be absent or empty.
|
|
DataSize = sizeof(ClusterName);
|
|
RegRetVal = RegQueryValueExW(hKey, REG_TS_CLUSTER_CLUSTERNAME,
|
|
NULL, &Type, (BYTE *)ClusterName, &DataSize);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
DBGPRINT(("TERMSRV: Failed RegQuery for ClusterName - "
|
|
"err=%u, DataSize=%u, type=%u\n",
|
|
RegRetVal, DataSize, Type));
|
|
}
|
|
}
|
|
|
|
if (g_MachinePolicy.fPolicySessionDirectoryAdditionalParams) {
|
|
wcsncpy(OpaqueSettings,
|
|
g_MachinePolicy.SessionDirectoryAdditionalParams,
|
|
OPAQUESETTINGSLENGTH);
|
|
OpaqueSettings[OPAQUESETTINGSLENGTH - 1] = '\0';
|
|
}
|
|
else { //Read from registry
|
|
// Not an error for the string to be absent or empty.
|
|
DataSize = sizeof(OpaqueSettings);
|
|
RegRetVal = RegQueryValueExW(hKey, REG_TS_CLUSTER_OPAQUESETTINGS,
|
|
NULL, &Type, (BYTE *)OpaqueSettings, &DataSize);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
DBGPRINT(("TERMSRV: Failed RegQuery for OpaqueSettings - "
|
|
"err=%u, DataSize=%u, type=%u\n",
|
|
RegRetVal, DataSize, Type));
|
|
}
|
|
}
|
|
|
|
// Query for the IP address used for SD redirection
|
|
DataSize = sizeof(SDRedirectionIP);
|
|
RegRetVal = RegQueryValueExW(hKey, REG_TS_CLUSTER_REDIRECTIONIP,
|
|
NULL, &Type, (BYTE *)SDRedirectionIP, &DataSize);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
SDRedirectionIP[0] = L'\0';
|
|
DBGPRINT(("TERMSRV: Failed RegQuery for RedirectionIP for SD - "
|
|
"err=%u, DataSize=%u, type=%u\n",
|
|
RegRetVal, DataSize, Type));
|
|
}
|
|
|
|
//
|
|
// Now for the less crucial settings.
|
|
//
|
|
// Get the setting that determines whether the server's local address is
|
|
// visible to the client. Group Policy takes precedence over registry.
|
|
//
|
|
|
|
if (g_MachinePolicy.fPolicySessionDirectoryExposeServerIP) {
|
|
g_SessDirUseServerAddr = g_MachinePolicy.SessionDirectoryExposeServerIP;
|
|
}
|
|
else {
|
|
Len = sizeof(g_SessDirUseServerAddr);
|
|
RegRetVal = RegQueryValueEx(hKeyTermSrv, REG_TS_SESSDIR_EXPOSE_SERVER_ADDR,
|
|
NULL, &Type, (BYTE *)&g_SessDirUseServerAddr, &Len);
|
|
|
|
if (RegRetVal == ERROR_SUCCESS) {
|
|
//DBGPRINT(("TERMSRV: RegOpenKeyEx for allow server addr to client %d"
|
|
// "\n", g_SessDirUseServerAddr));
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: RegQueryValueEx for allow server addr to client"
|
|
" %d, err %u\n", g_SessDirUseServerAddr, RegRetVal));
|
|
}
|
|
}
|
|
|
|
// Get the single session per user setting from GP if it's active, otherwise
|
|
// from the registry.
|
|
if (g_MachinePolicy.fPolicySingleSessionPerUser) {
|
|
bThisServerIsInSingleSessionMode =
|
|
g_MachinePolicy.fSingleSessionPerUser;
|
|
}
|
|
else {
|
|
Len = sizeof(bThisServerIsInSingleSessionMode);
|
|
RegRetVal = RegQueryValueEx(hKeyTermSrv,
|
|
POLICY_TS_SINGLE_SESSION_PER_USER, NULL, &Type,
|
|
(BYTE *)&bThisServerIsInSingleSessionMode, &Len);
|
|
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
DBGPRINT(("TERMSRV: RegQueryValueEx for single session mode"
|
|
", Error %u\n", RegRetVal));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the default wait timeout for repopulate thread to complete
|
|
//
|
|
Len = sizeof(RepopulateWaitTimeout);
|
|
RegRetVal = RegQueryValueEx( hKeyTermSrv,
|
|
L"RepopulateWaitTimeout",
|
|
NULL,
|
|
&Type,
|
|
(LPBYTE)&RepopulateWaitTimeout,
|
|
&Len);
|
|
if( RegRetVal == ERROR_SUCCESS && REG_DWORD == Type ) {
|
|
if( RepopulateWaitTimeout < 0 ) {
|
|
g_WaitForRepopulate = INFINITE;
|
|
}
|
|
else {
|
|
g_WaitForRepopulate = RepopulateWaitTimeout * 1000;
|
|
}
|
|
}
|
|
|
|
DBGPRINT(("TERMSRV: WaitForRepopulateTimeout set to %d\n", g_WaitForRepopulate));
|
|
|
|
|
|
// Get the CLSID of the session directory object to instantiate.
|
|
CLSIDStr[0] = L'\0';
|
|
Len = sizeof(CLSIDStr);
|
|
RegQueryValueEx(hKeyTermSrv, REG_TS_SESSDIRCLSID, NULL, &Type,
|
|
(BYTE *)CLSIDStr, &Len);
|
|
|
|
// Get the CLSID of the session directory object to instantiate.
|
|
CLSIDEXStr[0] = L'\0';
|
|
Len = sizeof(CLSIDEXStr);
|
|
RegQueryValueEx(hKeyTermSrv, REG_TS_SESSDIR_EX_CLSID, NULL, &Type,
|
|
(BYTE *)CLSIDEXStr, &Len);
|
|
|
|
RegCloseKey(hKey);
|
|
RegCloseKey(hKeyTermSrv);
|
|
|
|
//
|
|
// Configuration loading complete.
|
|
//
|
|
// See what to do about activation/deactivation.
|
|
//
|
|
|
|
pTSSD = GetTSSD();
|
|
|
|
if (pTSSD == NULL) {
|
|
// This is the normal initialization path. If Update is true here, it
|
|
// should be treated as a normal initialize because the COM object was
|
|
// unloaded.
|
|
Update = false;
|
|
}
|
|
else {
|
|
// Clustering is already active. See whether we should deactivate it.
|
|
if (bClusteringActive == FALSE) {
|
|
ReleaseTSSD(); // Once here, once again at the end of the function.
|
|
pTSSDEx = GetTSSDEx();
|
|
if (pTSSDEx) {
|
|
ReleaseTSSDEx();
|
|
ReleaseTSSDEx();
|
|
pTSSDEx = NULL;
|
|
}
|
|
}
|
|
}
|
|
if (bClusteringActive) {
|
|
// We need to get the local machine's address to pass in to
|
|
// the directory.
|
|
// If SDRedirectionIP is not empty, i.e. RedirectionIP is selected in tscc or though WMI, use it,
|
|
// otherwise, use the IP we get from TermSrv or from winsock API
|
|
if (SDRedirectionIP[0] == L'\0') {
|
|
if (g_LocalIPAddress != 0) {
|
|
tempaddr = (unsigned char *)&g_LocalIPAddress;
|
|
wsprintf(g_LocalServerAddress, L"%d.%d.%d.%d", tempaddr[0], tempaddr[1],
|
|
tempaddr[2], tempaddr[3]);
|
|
}
|
|
else {
|
|
//RPD-Enabled NIC is not specified in TSCC, need to get through winsock API
|
|
ErrVal = SessDirGetLocalIPAddr(g_LocalServerAddress);
|
|
}
|
|
}
|
|
else {
|
|
wcsncpy(g_LocalServerAddress, SDRedirectionIP, IPADDRESSLENGTH);
|
|
}
|
|
|
|
if (ErrVal == 0) {
|
|
|
|
if (wcslen(CLSIDStr) > 0 &&
|
|
SUCCEEDED(CLSIDFromString(CLSIDStr, &TSSDCLSID))) {
|
|
|
|
// If it's not an update, create the TSSD object.
|
|
if (Update == false) {
|
|
hr = CoCreateInstance(TSSDCLSID, NULL,
|
|
CLSCTX_INPROC_SERVER, IID_ITSSessionDirectory,
|
|
(void **)&pTSSD);
|
|
if (SUCCEEDED(hr)) {
|
|
if (SetTSSD(pTSSD) != 0) {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: Could not set "
|
|
"TSSD", E_FAIL));
|
|
pTSSD->Release();
|
|
pTSSD = NULL;
|
|
hr = E_FAIL;
|
|
}
|
|
else {
|
|
// Add 1 to the ref count because we're gonna use
|
|
// it.
|
|
pTSSD = GetTSSD();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
hr = S_OK;
|
|
}
|
|
|
|
if (SUCCEEDED (hr)) {
|
|
// Right now the only flag we pass in to session directory
|
|
// says whether we are in single-session mode.
|
|
DWORD Flags = 0;
|
|
|
|
Flags |= (bThisServerIsInSingleSessionMode ?
|
|
SINGLE_SESSION_FLAG : 0x0);
|
|
|
|
if (UpdatePara & TSSD_NOREPOPULATE) {
|
|
Flags |= NO_REPOPULATE_SESSION;
|
|
}
|
|
|
|
// Remove preceding and succeeding space in ClusterName
|
|
RemoveSpaceInStr(ClusterName);
|
|
if (Update == false)
|
|
hr = pTSSD->Initialize(g_LocalServerAddress,
|
|
StoreServerName, ClusterName, OpaqueSettings,
|
|
Flags, RepopulateSessionDirectory, UpdateSessionDirectory);
|
|
else
|
|
hr = pTSSD->Update(g_LocalServerAddress,
|
|
StoreServerName, ClusterName, OpaqueSettings,
|
|
Flags, ForceRejoin);
|
|
if (FAILED(hr)) {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: Failed %s TSSD, "
|
|
"hr=0x%X\n", Update ? "update" : "init", hr));
|
|
ReleaseTSSD();
|
|
PostErrorValueEvent(
|
|
EVENT_TS_SESSDIR_FAIL_INIT_TSSD, hr);
|
|
}
|
|
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: Failed create TSSD, "
|
|
"hr=0x%X\n", hr));
|
|
PostErrorValueEvent(
|
|
EVENT_TS_SESSDIR_FAIL_CREATE_TSSD, hr);
|
|
}
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: Failed get or parse "
|
|
"CLSID\n"));
|
|
PostErrorValueEvent(
|
|
EVENT_TS_SESSDIR_FAIL_GET_TSSD_CLSID, 0);
|
|
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: Failed to get local DNS name, "
|
|
"lasterr=0x%X\n", ErrVal));
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_NO_COMPUTER_DNS_NAME,
|
|
ErrVal);
|
|
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
// Initialize the other COM object, but only if the above succeeded.
|
|
if (SUCCEEDED(hr)) {
|
|
if (wcslen(CLSIDEXStr) > 0 &&
|
|
SUCCEEDED(CLSIDFromString(CLSIDEXStr, &TSSDEXCLSID))) {
|
|
// If it's not an update, create the TSSDEX object.
|
|
if (Update == false) {
|
|
hr = CoCreateInstance(TSSDEXCLSID, NULL,
|
|
CLSCTX_INPROC_SERVER, IID_ITSSessionDirectoryEx,
|
|
(void **)&pTSSDEx);
|
|
if (SUCCEEDED(hr)) {
|
|
if (SetTSSDEx(pTSSDEx) != 0) {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: Could not set "
|
|
"TSSDEx\n", E_FAIL));
|
|
pTSSDEx->Release();
|
|
pTSSDEx = NULL;
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
hr = S_OK;
|
|
|
|
if (FAILED(hr)) {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: Failed create TSSDEx, "
|
|
"hr=0x%X\n", hr));
|
|
PostErrorValueEvent(
|
|
EVENT_TS_SESSDIR_FAIL_CREATE_TSSDEX, hr);
|
|
}
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: Failed get or parse "
|
|
"CLSIDSDEx\n"));
|
|
PostErrorValueEvent(
|
|
EVENT_TS_SESSDIR_FAIL_GET_TSSDEX_CLSID, 0);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: InitSessDirEx: SessDir not activated\n"));
|
|
}
|
|
|
|
if (pTSSD != NULL)
|
|
ReleaseTSSD();
|
|
|
|
// Initialization complete--someone else is allowed to enter now.
|
|
LeaveCriticalSection(&g_CritSecInitialize);
|
|
|
|
return S_OK;
|
|
|
|
RegFailExit:
|
|
// Initialization complete--someone else is allowed to enter now.
|
|
LeaveCriticalSection(&g_CritSecInitialize);
|
|
|
|
if (hKeyTermSrvSucceeded)
|
|
RegCloseKey(hKeyTermSrv);
|
|
|
|
return (DWORD) E_FAIL;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// InitSessionDirectory
|
|
//
|
|
// Initializes the directory by loading and initializing the session directory
|
|
// object, if load balancing is enabled. We assume COM has been initialized
|
|
// on the service main thread as COINIT_MULTITHREADED.
|
|
//
|
|
// This function should only be called once ever. It is the location of the
|
|
// initialization of the critical sections used by this module.
|
|
/****************************************************************************/
|
|
void InitSessionDirectory()
|
|
{
|
|
BOOL br1 = FALSE;
|
|
BOOL br2 = FALSE;
|
|
|
|
ASSERT(g_bCritSecsInitialized == FALSE);
|
|
|
|
// Initialize critical sections.
|
|
__try {
|
|
|
|
// Initialize the provider critical section to preallocate the event
|
|
// and spin 4096 times on each try (since we don't spend very
|
|
// long in our critical section).
|
|
br1 = InitializeCriticalSectionAndSpinCount(&g_CritSecComObj,
|
|
0x80001000);
|
|
br2 = InitializeCriticalSectionAndSpinCount(&g_CritSecInitialize,
|
|
0x80001000);
|
|
|
|
// Since this happens at startup time, we should not fail.
|
|
ASSERT(br1 && br2);
|
|
|
|
if (br1 && br2) {
|
|
g_bCritSecsInitialized = TRUE;
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: InitSessDir: critsec init failed\n"));
|
|
|
|
if (br1)
|
|
DeleteCriticalSection(&g_CritSecComObj);
|
|
if (br2)
|
|
DeleteCriticalSection(&g_CritSecInitialize);
|
|
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_INIT_TSSD,
|
|
GetLastError());
|
|
}
|
|
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
// Since this happens at startup time, we should not fail.
|
|
ASSERT(FALSE);
|
|
|
|
DBGPRINT(("TERMSRV: InitSessDir: critsec init failed\n"));
|
|
|
|
if (br1)
|
|
DeleteCriticalSection(&g_CritSecComObj);
|
|
if (br2)
|
|
DeleteCriticalSection(&g_CritSecInitialize);
|
|
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_INIT_TSSD,
|
|
GetExceptionCode());
|
|
}
|
|
|
|
// Now do the common initialization.
|
|
if (g_bCritSecsInitialized)
|
|
InitSessionDirectoryEx(0);
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// UpdateSessionDirectory
|
|
//
|
|
// Updates the session directory with new settings. Assumes COM has been
|
|
// initialized.
|
|
/****************************************************************************/
|
|
DWORD UpdateSessionDirectory(DWORD UpdatePara)
|
|
{
|
|
UpdatePara |= TSSD_UPDATE;
|
|
return InitSessionDirectoryEx(UpdatePara);
|
|
}
|
|
|
|
|
|
#define REPOP_FAIL 1
|
|
#define REPOP_SUCCESS 0
|
|
/****************************************************************************/
|
|
// RepopulateSessionDirectory
|
|
//
|
|
// Repopulates the session directory. Returns REPOP_FAIL (1) on failure,
|
|
// REPOP_SUCCESS(0) otherwise.
|
|
/****************************************************************************/
|
|
DWORD RepopulateSessionDirectory()
|
|
{
|
|
DWORD WinStationCount = 0;
|
|
PLIST_ENTRY Head, Next;
|
|
DWORD i = 0;
|
|
HRESULT hr = S_OK;
|
|
PWINSTATION pWinStation = NULL;
|
|
ITSSessionDirectory *pTSSD;
|
|
WCHAR *wBuffer = NULL;
|
|
|
|
#if DBG
|
|
DWORD dwStartTime;
|
|
DWORD dwEndTime;
|
|
#endif
|
|
|
|
// If we got here, it should be because of the session directory.
|
|
pTSSD = GetTSSD();
|
|
|
|
if (pTSSD != NULL) {
|
|
|
|
// Grab WinStationListLock
|
|
ENTERCRIT( &WinStationListLock );
|
|
|
|
Head = &WinStationListHead;
|
|
|
|
// Count the WinStations I care about.
|
|
for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) {
|
|
|
|
pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links );
|
|
|
|
//
|
|
// In winstation reset, we only mark flag not the state but
|
|
// we can't be sure progress of this logoff so we depends on
|
|
// SessDirWaitForRepopulate() and let logoff thread
|
|
// to notify session directory itself.
|
|
//
|
|
//
|
|
//if ( (pWinStation->Flags & (WSF_RESET | WSF_DELETE)) ) {
|
|
// continue;
|
|
//}
|
|
|
|
//
|
|
// WinStation was disconnected and no user was logged on.
|
|
//
|
|
if( RtlLargeIntegerEqualToZero( pWinStation->LogonTime ) ) {
|
|
// NotifyLogonWorker set winstation state and logontime after
|
|
// getting user SID and user/domain name, it is possible on
|
|
// next loop, this logon thread might finish setting logontime and
|
|
// causing we pick it up from next loop and that will
|
|
// cause buffer overwrite, so we increment counter here
|
|
WinStationCount += 1;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// We need to handle console session differently.
|
|
//
|
|
// Action Physical Console State | Remote Console State UserName
|
|
// ------- ---------------------- | -------------------- ---------
|
|
// After boot Conn |
|
|
// Logon Active | Active Logon User
|
|
// Logoff Conn | Disc <Blank>
|
|
// Disconnect | Disc Logon User
|
|
//
|
|
// We should not report to Session Directory when state is
|
|
// Disconnect and user name is blank.
|
|
//
|
|
switch (pWinStation->State) {
|
|
case State_Disconnected:
|
|
if( (pWinStation->LogonId == 0) && (pWinStation->UserName[0] == 0) ) {
|
|
break;
|
|
}
|
|
|
|
#if DBG
|
|
if( (pWinStation->LogonId == 0) ) {
|
|
DBGPRINT( ("TERMSRV: RepopulateSessDir: Include console session to Session Directory\n") );
|
|
}
|
|
#endif
|
|
|
|
case State_Active:
|
|
case State_Shadow:
|
|
|
|
WinStationCount += 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Allocate the memory for the structure to pass to the session
|
|
// directory.
|
|
TSSD_RepopulateSessionInfo *rsi = new TSSD_RepopulateSessionInfo[
|
|
WinStationCount];
|
|
|
|
if (rsi == NULL) {
|
|
DBGPRINT(("TERMSRV: RepopulateSessDir: mem alloc failed\n"));
|
|
|
|
// Release WinStationListLock
|
|
LEAVECRIT( &WinStationListLock );
|
|
|
|
goto CleanUp;
|
|
}
|
|
|
|
// Allocate string arrays (for now)
|
|
wBuffer = new WCHAR[WinStationCount * TOTAL_STRINGS_LENGTH];
|
|
if (wBuffer == NULL) {
|
|
DBGPRINT(("TERMSRV: RepopulateSessDir: mem alloc failed\n"));
|
|
|
|
// Release WinStationListLock
|
|
LEAVECRIT( &WinStationListLock );
|
|
|
|
delete [] rsi;
|
|
|
|
goto CleanUp;
|
|
}
|
|
|
|
// Set the pointers in the rsi
|
|
for ( i = 0; i < WinStationCount; i += 1) {
|
|
rsi[i].UserName = &(wBuffer[i * TOTAL_STRINGS_LENGTH +
|
|
USERNAME_OFFSET]);
|
|
rsi[i].Domain = &(wBuffer[i * TOTAL_STRINGS_LENGTH +
|
|
DOMAIN_OFFSET]);
|
|
rsi[i].ApplicationType = &(wBuffer[i * TOTAL_STRINGS_LENGTH +
|
|
APPLICATIONTYPE_OFFSET]);
|
|
}
|
|
|
|
// Now populate the structure to pass in.
|
|
|
|
// Reset index to 0
|
|
i = 0;
|
|
|
|
for ( Next = Head->Flink; Next != Head && i < WinStationCount; Next = Next->Flink ) {
|
|
|
|
pWinStation = CONTAINING_RECORD( Next, WINSTATION, Links );
|
|
|
|
//
|
|
// In winstation reset, we only mark flag not the state but
|
|
// we can't be sure progress of this logoff so we depends on
|
|
// SessDirWaitForRepopulate() and let logoff thread
|
|
// to notify session directory itself.
|
|
//
|
|
//if ( (pWinStation->Flags & (WSF_RESET | WSF_DELETE)) ) {
|
|
// continue;
|
|
//}
|
|
|
|
// refer to comment above regarding console session
|
|
if( (pWinStation->LogonId == 0) &&
|
|
(pWinStation->State == State_Disconnected) &&
|
|
(pWinStation->UserName[0] == 0) ) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// WinStation was disconnected or in the middle of connect.
|
|
// we will let notify logon do its job to report to SD
|
|
//
|
|
if( RtlLargeIntegerEqualToZero( pWinStation->LogonTime ) ) {
|
|
continue;
|
|
}
|
|
|
|
// There are two sets of information here: First, if the session
|
|
// is Active, we can do stuff, then we have an intentional
|
|
// fallthrough to the common code used by Disconnected and Active
|
|
// sessions for the common stuff. For now, if it's disconnected
|
|
// we then call the update function in our COM object.
|
|
switch (pWinStation->State) {
|
|
case State_Active:
|
|
case State_Shadow:
|
|
rsi[i].State = 0;
|
|
// NOTE INTENTIONAL FALLTHROUGH
|
|
case State_Disconnected:
|
|
rsi[i].TSProtocol = pWinStation->Client.ProtocolType;
|
|
rsi[i].ResolutionWidth = pWinStation->Client.HRes;
|
|
rsi[i].ResolutionHeight = pWinStation->Client.VRes;
|
|
rsi[i].ColorDepth = pWinStation->Client.ColorDepth;
|
|
|
|
// TODO: I don't get it--USERNAME_LENGTH is 20, yet in the csi,
|
|
// TODO: it's 256. Likewise, DOMAIN_LENGTH is 17.
|
|
wcsncpy(rsi[i].UserName, pWinStation->UserName,
|
|
USERNAME_LENGTH);
|
|
rsi[i].UserName[USERNAME_LENGTH] = '\0';
|
|
wcsncpy(rsi[i].Domain, pWinStation->Domain, DOMAIN_LENGTH);
|
|
rsi[i].Domain[DOMAIN_LENGTH] = '\0';
|
|
|
|
// TODO: Here there is a problem in that the INITIALPROGRAM
|
|
// TODO: length is 256 + 1, but the buffer we copy into is
|
|
// TODO: 256, hence we lose a character.
|
|
wcsncpy(rsi[i].ApplicationType, pWinStation->
|
|
Client.InitialProgram, INITIALPROGRAM_LENGTH - 1);
|
|
rsi[i].ApplicationType[INITIALPROGRAM_LENGTH - 2] = '\0';
|
|
rsi[i].SessionID = pWinStation->LogonId;
|
|
rsi[i].CreateTimeLow = pWinStation->LogonTime.LowPart;
|
|
rsi[i].CreateTimeHigh = pWinStation->LogonTime.HighPart;
|
|
if (pWinStation->State == State_Disconnected) {
|
|
rsi[i].DisconnectionTimeLow = pWinStation->DisconnectTime.
|
|
LowPart;
|
|
rsi[i].DisconnectionTimeHigh = pWinStation->DisconnectTime.
|
|
HighPart;
|
|
rsi[i].State = 1;
|
|
}
|
|
|
|
if( (pWinStation->LogonId == 0) &&
|
|
(pWinStation->State == State_Disconnected) &&
|
|
(pWinStation->UserName[0] == 0) ) {
|
|
break;
|
|
}
|
|
|
|
// make sure after we copy data over, winstation still valid.
|
|
ASSERT( rsi[i].UserName[0] != 0 );
|
|
ASSERT( rsi[i].Domain[0] != 0 );
|
|
|
|
i += 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Release WinStationListLock
|
|
LEAVECRIT( &WinStationListLock );
|
|
|
|
#if DBG
|
|
dwStartTime = GetTickCount();
|
|
DBGPRINT( ("RepopulateSessionDirectory: Start repopulating session\n") );
|
|
#endif
|
|
|
|
if( i > 0 ) {
|
|
// Call the session directory provider with our big struct.
|
|
hr = pTSSD->Repopulate(i, rsi);
|
|
}
|
|
|
|
#if DBG
|
|
dwEndTime = GetTickCount();
|
|
DBGPRINT( ("RepopulateSessionDirectory: End repopulating %d sessions takes %d ms\n", i, dwEndTime - dwStartTime) );
|
|
#endif
|
|
|
|
delete [] rsi;
|
|
delete [] wBuffer;
|
|
|
|
if (hr == S_OK) {
|
|
ReleaseTSSD();
|
|
return REPOP_SUCCESS;
|
|
}
|
|
else {
|
|
goto CleanUp;
|
|
}
|
|
|
|
CleanUp:
|
|
ReleaseTSSD();
|
|
}
|
|
|
|
return REPOP_FAIL;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// DestroySessionDirectory
|
|
//
|
|
// Destroys the directory, releasing any COM objects held and other memory
|
|
// used. Assumes COM has been initialized.
|
|
/****************************************************************************/
|
|
void DestroySessionDirectory()
|
|
{
|
|
ITSSessionDirectory *pTSSD = NULL;
|
|
ITSSessionDirectoryEx *pTSSDEx = NULL;
|
|
|
|
pTSSD = GetTSSD();
|
|
pTSSDEx = GetTSSDEx();
|
|
if (pTSSD != NULL) {
|
|
ReleaseTSSD();
|
|
ReleaseTSSD();
|
|
}
|
|
|
|
if (pTSSDEx != NULL) {
|
|
ReleaseTSSDEx();
|
|
ReleaseTSSDEx();
|
|
}
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// SessDirNotifyLogon
|
|
//
|
|
// Called to inform the session directory of session creation.
|
|
/****************************************************************************/
|
|
void SessDirNotifyLogon(TSSD_CreateSessionInfo *pCreateInfo)
|
|
{
|
|
HRESULT hr;
|
|
ITSSessionDirectory *pTSSD;
|
|
|
|
pTSSD = GetTSSD();
|
|
|
|
// We can get called even when the directory is inactive.
|
|
if (pTSSD != NULL) {
|
|
hr = pTSSD->NotifyCreateLocalSession(pCreateInfo);
|
|
if (FAILED(hr)) {
|
|
DBGPRINT(("TERMSRV: SessDirNotifyLogon: Call failed, "
|
|
"hr=0x%X\n", hr));
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_UPDATE, hr);
|
|
}
|
|
|
|
ReleaseTSSD();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// SessDirNotifyDisconnection
|
|
//
|
|
// Called on session disconnection to inform the session directory.
|
|
/****************************************************************************/
|
|
void SessDirNotifyDisconnection(DWORD SessionID, FILETIME DiscTime)
|
|
{
|
|
HRESULT hr;
|
|
ITSSessionDirectory *pTSSD;
|
|
|
|
pTSSD = GetTSSD();
|
|
// We can get called even when the directory is inactive.
|
|
if (pTSSD != NULL) {
|
|
hr = pTSSD->NotifyDisconnectLocalSession(SessionID, DiscTime);
|
|
if (FAILED(hr)) {
|
|
DBGPRINT(("TERMSRV: SessDirNotifyDisc: Call failed, "
|
|
"hr=0x%X\n", hr));
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_UPDATE, hr);
|
|
}
|
|
|
|
ReleaseTSSD();
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// SessDirNotifyReconnection
|
|
//
|
|
// Called on session reconnection to inform the session directory.
|
|
/****************************************************************************/
|
|
void SessDirNotifyReconnection(PWINSTATION pTargetWinStation, TSSD_ReconnectSessionInfo *pReconnInfo)
|
|
{
|
|
HRESULT hr;
|
|
ITSSessionDirectory *pTSSD;
|
|
|
|
pTSSD = GetTSSD();
|
|
|
|
// We can get called even when the directory is inactive.
|
|
if (pTSSD != NULL) {
|
|
PTS_LOAD_BALANCE_INFO pLBInfo = NULL;
|
|
ULONG ReturnLength;
|
|
NTSTATUS Status;
|
|
BYTE *pRedirInfo = NULL;
|
|
BYTE *pRedirInfoStart = NULL;
|
|
BYTE *LBInfo = NULL;
|
|
DWORD LBInfoSize = 0;
|
|
DWORD RedirInfoSize = 0;
|
|
DWORD ServerAddrLen = 0;
|
|
HKEY hKey = NULL;
|
|
DWORD Type, DataSize;
|
|
WCHAR SDRedirectionIP[IPADDRESSLENGTH];
|
|
WCHAR *pszServerIPAddress = NULL;
|
|
LONG RegRetVal;
|
|
|
|
// We need to send redirection packet with LB_NOREDIRECT set to the client
|
|
// to let it know the server address it actually connects to (for later
|
|
// auto-reconnect use)
|
|
|
|
// Get the client load balance capability info. We continue onward
|
|
// to do a session directory query only when the client supports
|
|
// redirection and has not already been redirected to this server.
|
|
pLBInfo = (PTS_LOAD_BALANCE_INFO)MemAlloc(sizeof(TS_LOAD_BALANCE_INFO));
|
|
if (NULL == pLBInfo) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
memset(pLBInfo, 0, sizeof(TS_LOAD_BALANCE_INFO));
|
|
Status = IcaStackIoControl(pTargetWinStation->hStack,
|
|
IOCTL_TS_STACK_QUERY_LOAD_BALANCE_INFO,
|
|
NULL, 0,
|
|
pLBInfo, sizeof(TS_LOAD_BALANCE_INFO),
|
|
&ReturnLength);
|
|
// Send the redirection packet to client if this is not a redirected connection
|
|
// and client support redirect-version4 and above
|
|
if (NT_SUCCESS(Status)
|
|
&& !pLBInfo->bRequestedSessionIDFieldValid &&
|
|
(pLBInfo->ClientRedirectionVersion >= TS_CLUSTER_REDIRECTION_VERSION4)) {
|
|
// Construct and send redirection packet
|
|
|
|
|
|
// Load registry keys.
|
|
RegRetVal = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_TS_CLUSTERSETTINGS, 0,
|
|
KEY_READ, &hKey);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
// Query for the IP address used for SD redirection
|
|
DataSize = sizeof(SDRedirectionIP);
|
|
RegRetVal = RegQueryValueExW(hKey, REG_TS_CLUSTER_REDIRECTIONIP,
|
|
NULL, &Type, (BYTE *)SDRedirectionIP, &DataSize);
|
|
RegCloseKey(hKey);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
SDRedirectionIP[0] = L'\0';
|
|
DBGPRINT(("TERMSRV: Failed RegQuery for RedirectionIP for SD - "
|
|
"err=%u, DataSize=%u, type=%u\n",
|
|
RegRetVal, DataSize, Type));
|
|
goto Cleanup;
|
|
}
|
|
pszServerIPAddress = SDRedirectionIP;
|
|
|
|
RedirInfoSize = sizeof(TS_CLIENT_REDIRECTION_INFO);
|
|
|
|
// Setup the server addr
|
|
if (g_SessDirUseServerAddr ||
|
|
pLBInfo->bClientRequireServerAddr) {
|
|
ServerAddrLen = (DWORD)((wcslen(pszServerIPAddress) + 1) *
|
|
sizeof(WCHAR));
|
|
RedirInfoSize += (ServerAddrLen + sizeof(ULONG));
|
|
|
|
DBGPRINT(("TERMSRV: SessDirCheckRedir: size=%d, "
|
|
"addr=%S\n", ServerAddrLen,
|
|
(WCHAR *)pszServerIPAddress));
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: SessDirCheckRedir no server "
|
|
"address: g_SessDirUseServerAddr = %d, "
|
|
"bClientRequireServerAddr = %d\n",
|
|
g_SessDirUseServerAddr,
|
|
pLBInfo->bClientRequireServerAddr));
|
|
}
|
|
|
|
// Setup the load balance info
|
|
if ((pLBInfo->bClientRequireServerAddr == 0) &&
|
|
SessDirGetLBInfo(
|
|
pszServerIPAddress, &LBInfoSize, &LBInfo)) {
|
|
|
|
if (LBInfo) {
|
|
DBGPRINT(("TERMSRV: SessDirCheckRedir: size=%d, "
|
|
"info=%S\n", LBInfoSize,
|
|
(WCHAR *)LBInfo));
|
|
RedirInfoSize += (LBInfoSize + sizeof(ULONG));
|
|
}
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: SessDirCheckRedir failed: "
|
|
"size=%d, info=%S\n", LBInfoSize,
|
|
(WCHAR *)LBInfo));
|
|
|
|
}
|
|
|
|
// Setup the load balance IOCTL
|
|
pRedirInfoStart = pRedirInfo = new BYTE[RedirInfoSize];
|
|
|
|
TS_CLIENT_REDIRECTION_INFO *pClientRedirInfo =
|
|
(TS_CLIENT_REDIRECTION_INFO *)pRedirInfo;
|
|
|
|
if (pRedirInfo != NULL) {
|
|
|
|
pClientRedirInfo->Flags = 0;
|
|
|
|
pRedirInfo += sizeof(TS_CLIENT_REDIRECTION_INFO);
|
|
|
|
if (ServerAddrLen) {
|
|
*((ULONG UNALIGNED*)(pRedirInfo)) =
|
|
ServerAddrLen;
|
|
|
|
memcpy((BYTE*)(pRedirInfo + sizeof(ULONG)),
|
|
(BYTE*)pszServerIPAddress,
|
|
ServerAddrLen);
|
|
|
|
pRedirInfo += ServerAddrLen + sizeof(ULONG);
|
|
|
|
pClientRedirInfo->Flags |= TARGET_NET_ADDRESS;
|
|
}
|
|
|
|
if (LBInfoSize) {
|
|
*((ULONG UNALIGNED*)(pRedirInfo)) = LBInfoSize;
|
|
memcpy((BYTE*)(pRedirInfo + sizeof(ULONG)),
|
|
LBInfo, LBInfoSize);
|
|
|
|
pRedirInfo += LBInfoSize + sizeof(ULONG);
|
|
|
|
pClientRedirInfo->Flags |= LOAD_BALANCE_INFO;
|
|
}
|
|
pClientRedirInfo->Flags |= LB_NOREDIRECT;
|
|
}
|
|
else {
|
|
Status = STATUS_NO_MEMORY;
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = IcaStackIoControl(pTargetWinStation->hStack,
|
|
IOCTL_TS_STACK_SEND_CLIENT_REDIRECTION,
|
|
pClientRedirInfo, RedirInfoSize,
|
|
NULL, 0,
|
|
&ReturnLength);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
// do nothing here
|
|
}
|
|
else {
|
|
// The stack returned failure. Continue
|
|
// the current connection.
|
|
TRACE((hTrace,TC_ICASRV,TT_API1,
|
|
"TERMSRV: Failed STACK_CLIENT_REDIR, "
|
|
"SessionID=%u, Status=0x%X\n",
|
|
pTargetWinStation->LogonId, Status));
|
|
}
|
|
|
|
Cleanup:
|
|
// Cleanup the buffers
|
|
if (LBInfo != NULL) {
|
|
SysFreeString((BSTR)LBInfo);
|
|
LBInfo = NULL;
|
|
}
|
|
|
|
if (pRedirInfo != NULL) {
|
|
delete [] pRedirInfoStart;
|
|
pRedirInfoStart = NULL;
|
|
}
|
|
}
|
|
if (pLBInfo != NULL) {
|
|
MemFree(pLBInfo);
|
|
pLBInfo = NULL;
|
|
}
|
|
|
|
hr = pTSSD->NotifyReconnectLocalSession(pReconnInfo);
|
|
if (FAILED(hr)) {
|
|
DBGPRINT(("TERMSRV: SessDirNotifyReconn: Call failed, "
|
|
"hr=0x%X\n", hr));
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_UPDATE, hr);
|
|
}
|
|
|
|
ReleaseTSSD();
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// SessDirNotifyLogoff
|
|
//
|
|
// Called on logoff to inform the session directory.
|
|
/****************************************************************************/
|
|
void SessDirNotifyLogoff(DWORD SessionID)
|
|
{
|
|
HRESULT hr;
|
|
ITSSessionDirectory *pTSSD;
|
|
|
|
pTSSD = GetTSSD();
|
|
|
|
// We can get called even when the directory is inactive.
|
|
if (pTSSD != NULL) {
|
|
hr = pTSSD->NotifyDestroyLocalSession(SessionID);
|
|
if (FAILED(hr)) {
|
|
DBGPRINT(("TERMSRV: SessDirNotifyLogoff: Call failed, "
|
|
"hr=0x%X\n", hr));
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_UPDATE, hr);
|
|
}
|
|
|
|
ReleaseTSSD();
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// SessDirNotifyReconnectPending
|
|
//
|
|
// Called to inform the session directory a session should start soon on
|
|
// another machine in the cluster (for Directory Integrity Service).
|
|
/****************************************************************************/
|
|
void SessDirNotifyReconnectPending(WCHAR *ServerName)
|
|
{
|
|
HRESULT hr;
|
|
ITSSessionDirectory *pTSSD;
|
|
|
|
pTSSD = GetTSSD();
|
|
|
|
// We can get called even when the directory is inactive.
|
|
if (pTSSD != NULL) {
|
|
hr = pTSSD->NotifyReconnectPending(ServerName);
|
|
if (FAILED(hr)) {
|
|
DBGPRINT(("TERMSRV: SessDirNotifyReconnectPending: Call failed, "
|
|
"hr=0x%X\n", hr));
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_UPDATE, hr);
|
|
}
|
|
|
|
ReleaseTSSD();
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// SessDirWaitForRepopulate
|
|
//
|
|
// Wait for session directory to complete repopulate
|
|
/****************************************************************************/
|
|
void SessDirWaitForRepopulate()
|
|
{
|
|
HRESULT hr;
|
|
ITSSessionDirectory *pTSSD;
|
|
|
|
#if DBG
|
|
DWORD dwStartTime;
|
|
#endif
|
|
|
|
// no waiting so just return
|
|
if( g_WaitForRepopulate == 0 ) {
|
|
return;
|
|
}
|
|
|
|
pTSSD = GetTSSD();
|
|
|
|
// We can get called even when the directory is inactive.
|
|
if (pTSSD != NULL) {
|
|
|
|
#if DBG
|
|
dwStartTime = GetTickCount();
|
|
#endif
|
|
|
|
hr = pTSSD->WaitForRepopulate(g_WaitForRepopulate);
|
|
if (FAILED(hr)) {
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_UPDATE, hr);
|
|
DBGPRINT(("TERMSRV: WaitForRepopulate: Call failed, "
|
|
"hr=0x%X\n", hr));
|
|
}
|
|
|
|
#if DBG
|
|
DBGPRINT(("SessDirWaitForRepopulate() takes %d ms\n", GetTickCount() - dwStartTime) );
|
|
#endif
|
|
|
|
ReleaseTSSD();
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// SessDirGetDisconnectedSessions
|
|
//
|
|
// Returns in the provided TSSD_DisconnectedSessionInfo buffer space
|
|
// up to TSSD_MaxDisconnectedSessions' worth of disconnected sessions
|
|
// from the session directory. Returns the number of sessions returned, which
|
|
// can be zero.
|
|
/****************************************************************************/
|
|
unsigned SessDirGetDisconnectedSessions(
|
|
WCHAR *UserName,
|
|
WCHAR *Domain,
|
|
TSSD_DisconnectedSessionInfo Info[TSSD_MaxDisconnectedSessions])
|
|
{
|
|
DWORD NumSessions = 0;
|
|
HRESULT hr;
|
|
ITSSessionDirectory *pTSSD;
|
|
|
|
pTSSD = GetTSSD();
|
|
|
|
// We can get called even when the directory is inactive.
|
|
if (pTSSD != NULL) {
|
|
hr = pTSSD->GetUserDisconnectedSessions(UserName, Domain,
|
|
&NumSessions, Info);
|
|
if (FAILED(hr)) {
|
|
DBGPRINT(("TERMSRV: SessDirGetDiscSessns: Call failed, "
|
|
"hr=0x%X\n", hr));
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_QUERY, hr);
|
|
}
|
|
ReleaseTSSD();
|
|
}
|
|
|
|
return NumSessions;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// SessDirGetLBInfo
|
|
//
|
|
// Call the SessDirEx COM object interface, using the server address, get
|
|
// the opaque load balance info back, will send the info to the client.
|
|
/****************************************************************************/
|
|
BOOL SessDirGetLBInfo(
|
|
WCHAR *ServerAddress,
|
|
DWORD* pLBInfoSize,
|
|
PBYTE* pLBInfo)
|
|
{
|
|
ITSSessionDirectoryEx *pTSSDEx;
|
|
HRESULT hr;
|
|
static BOOL EventLogged = FALSE;
|
|
|
|
*pLBInfoSize = 0;
|
|
*pLBInfo = NULL;
|
|
|
|
pTSSDEx = GetTSSDEx();
|
|
|
|
if (pTSSDEx != NULL) {
|
|
hr = pTSSDEx->GetLoadBalanceInfo(ServerAddress, (BSTR *)pLBInfo);
|
|
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
*pLBInfoSize = SysStringByteLen((BSTR)(*pLBInfo));
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT(("TERMSRV: SessDirGetLBInfo: Call failed, "
|
|
"hr=0x%X\n", hr));
|
|
if (EventLogged == FALSE) {
|
|
PostErrorValueEvent(EVENT_TS_SESSDIR_FAIL_LBQUERY, hr);
|
|
EventLogged = TRUE;
|
|
}
|
|
}
|
|
|
|
ReleaseTSSDEx();
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: SessDirGetLBInfo: Call failed, pTSSDEx is NULL "));
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
return SUCCEEDED(hr);
|
|
}
|
|
|
|
|
|
#define SERVER_ADDRESS_LENGTH 64
|
|
/****************************************************************************/
|
|
// IsSameAsCurrentIP
|
|
//
|
|
// Determines whether the IP address given is the same as the current machine.
|
|
// Returning FALSE in case of error is not a problem--the client will
|
|
// just get redirected right back here.
|
|
/****************************************************************************/
|
|
BOOL IsSameAsCurrentIP(WCHAR *SessionIPAddress)
|
|
{
|
|
// Get the server adresses.
|
|
int RetVal;
|
|
unsigned long NumericalSessionIPAddr = 0;
|
|
char achComputerName[256];
|
|
DWORD dwComputerNameSize;
|
|
PBYTE pServerAddrByte;
|
|
PBYTE pSessionAddrByte;
|
|
ADDRINFO *AddrInfo, *AI;
|
|
struct sockaddr_in *pIPV4addr;
|
|
char AnsiSessionIPAddress[SERVER_ADDRESS_LENGTH];
|
|
|
|
// Compute integer for the server address.
|
|
// First, get ServerAddress as an ANSI string.
|
|
RetVal = WideCharToMultiByte(CP_ACP, 0, SessionIPAddress, -1,
|
|
AnsiSessionIPAddress, SERVER_ADDRESS_LENGTH, NULL, NULL);
|
|
if (RetVal == 0) {
|
|
DBGPRINT(("IsSameServerIP: WideCharToMB failed %d\n", GetLastError()));
|
|
return FALSE;
|
|
}
|
|
|
|
// Now, get the numerical server address.
|
|
// Now, use inet_addr to turn into an unsigned long.
|
|
NumericalSessionIPAddr = inet_addr(AnsiSessionIPAddress);
|
|
if (NumericalSessionIPAddr == INADDR_NONE) {
|
|
DBGPRINT(("IsSameServerIP: inet_addr failed\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
pSessionAddrByte = (PBYTE) &NumericalSessionIPAddr;
|
|
|
|
dwComputerNameSize = sizeof(achComputerName);
|
|
if (!GetComputerNameA(achComputerName,&dwComputerNameSize)) {
|
|
return FALSE;
|
|
}
|
|
|
|
RetVal = getaddrinfo(achComputerName, NULL, NULL, &AddrInfo);
|
|
if (RetVal != 0) {
|
|
DBGPRINT (("Cannot resolve address, error: %d\n", RetVal));
|
|
return FALSE;
|
|
}
|
|
else {
|
|
// Compare all server adresses with client till a match is found.
|
|
// Currently only works for IPv4.
|
|
for (AI = AddrInfo; AI != NULL; AI = AI->ai_next) {
|
|
|
|
if (AI->ai_family == AF_INET) {
|
|
|
|
if (AI->ai_addrlen >= sizeof(struct sockaddr_in)) {
|
|
pIPV4addr = (struct sockaddr_in *) AI->ai_addr;
|
|
pServerAddrByte = (PBYTE)&pIPV4addr->sin_addr;
|
|
if (RtlEqualMemory(pSessionAddrByte, pServerAddrByte, 4)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// SessDirCheckRedirectClient
|
|
//
|
|
// Performs the set of steps needed to get the client's clustering
|
|
// capabilities, get the disconnected session list, and apply client
|
|
// redirection policy. Returns TRUE if the client was redirected, FALSE if
|
|
// the current WinStation transfer should be continued.
|
|
/****************************************************************************/
|
|
BOOL SessDirCheckRedirectClient(
|
|
PWINSTATION pTargetWinStation,
|
|
TS_LOAD_BALANCE_INFO *pLBInfo)
|
|
{
|
|
BOOL rc = FALSE;
|
|
ULONG ReturnLength;
|
|
unsigned i, NumSessions;
|
|
NTSTATUS Status;
|
|
ITSSessionDirectory *pTSSD;
|
|
BOOL fLogonUsingUPN = FALSE;
|
|
BOOL fNeedToRedirect = FALSE;
|
|
|
|
pTSSD = GetTSSD();
|
|
|
|
pTargetWinStation->NumClusterDiscSessions = 0;
|
|
|
|
if (pTSSD != NULL) {
|
|
if (pLBInfo->bClientSupportsRedirection &&
|
|
!pLBInfo->bRequestedSessionIDFieldValid) {
|
|
// The client has not been redirected to this machine. See if we
|
|
// have disconnected sessions in the database for redirecting.
|
|
NumSessions = pTargetWinStation->NumClusterDiscSessions =
|
|
SessDirGetDisconnectedSessions(
|
|
pLBInfo->UserName,
|
|
pLBInfo->Domain,
|
|
pTargetWinStation->ClusterDiscSessions);
|
|
if (pTargetWinStation->NumClusterDiscSessions > 0) {
|
|
|
|
// trevorfo: Applying policy here for reconnection to only one session
|
|
// (whichever is first). More general policy requires a selection UI at the
|
|
// client or in WinLogon.
|
|
|
|
// Find the first session in the list that matches the
|
|
// client's session requirements. Namely, we filter based
|
|
// on the client's TS protocol, wire protocol, and application
|
|
// type.
|
|
for (i = 0; i < NumSessions; i++) {
|
|
if ((pLBInfo->ProtocolType ==
|
|
pTargetWinStation->ClusterDiscSessions[i].
|
|
TSProtocol) &&
|
|
(!_wcsicmp(pLBInfo->InitialProgram,
|
|
pTargetWinStation->ClusterDiscSessions[i].
|
|
ApplicationType))) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == NumSessions) {
|
|
TRACE((hTrace,TC_ICASRV,TT_API1,
|
|
"TERMSRV: SessDirCheckRedir: No matching sessions "
|
|
"found\n"));
|
|
}
|
|
else {
|
|
// If the session is not on this server, redirect the
|
|
// client. See notes above about use of
|
|
// _IcaStackIoControl().
|
|
|
|
if (!IsSameAsCurrentIP(pTargetWinStation->
|
|
ClusterDiscSessions[i].ServerAddress)) {
|
|
fNeedToRedirect = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fNeedToRedirect ||
|
|
(pLBInfo->ClientRedirectionVersion >= TS_CLUSTER_REDIRECTION_VERSION4)) {
|
|
|
|
BYTE *pRedirInfo = NULL;
|
|
BYTE *pRedirInfoStart = NULL;
|
|
BYTE *LBInfo = NULL;
|
|
DWORD LBInfoSize = 0;
|
|
DWORD RedirInfoSize = 0;
|
|
DWORD ServerAddrLen = 0;
|
|
DWORD DomainSize = 0;
|
|
DWORD UserNameSize = 0;
|
|
DWORD PasswordSize = 0;
|
|
HKEY hKey = NULL;
|
|
DWORD Type, DataSize;
|
|
WCHAR SDRedirectionIP[IPADDRESSLENGTH];
|
|
WCHAR *pszServerIPAddress = NULL;
|
|
LONG RegRetVal;
|
|
|
|
// Even fNeedToRedirect is FALSE, we still need to send redirection packet
|
|
// with LB_NOREDIRECT set to the client to let it know the server address
|
|
// it actually connects to (for later auto-reconnect use)
|
|
if (!fNeedToRedirect) {
|
|
// Load registry keys.
|
|
RegRetVal = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_TS_CLUSTERSETTINGS, 0,
|
|
KEY_READ, &hKey);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
// Query for the IP address used for SD redirection
|
|
DataSize = sizeof(SDRedirectionIP);
|
|
RegRetVal = RegQueryValueExW(hKey, REG_TS_CLUSTER_REDIRECTIONIP,
|
|
NULL, &Type, (BYTE *)SDRedirectionIP, &DataSize);
|
|
RegCloseKey(hKey);
|
|
if (RegRetVal != ERROR_SUCCESS) {
|
|
SDRedirectionIP[0] = L'\0';
|
|
DBGPRINT(("TERMSRV: Failed RegQuery for RedirectionIP for SD - "
|
|
"err=%u, DataSize=%u, type=%u\n",
|
|
RegRetVal, DataSize, Type));
|
|
goto Cleanup;
|
|
}
|
|
pszServerIPAddress = SDRedirectionIP;
|
|
}
|
|
else {
|
|
pszServerIPAddress = pTargetWinStation->ClusterDiscSessions[i].ServerAddress;
|
|
}
|
|
|
|
RedirInfoSize = sizeof(TS_CLIENT_REDIRECTION_INFO);
|
|
|
|
// Setup the server addr
|
|
if (g_SessDirUseServerAddr ||
|
|
pLBInfo->bClientRequireServerAddr) {
|
|
ServerAddrLen = (DWORD)((wcslen(pszServerIPAddress) + 1) *
|
|
sizeof(WCHAR));
|
|
RedirInfoSize += (ServerAddrLen + sizeof(ULONG));
|
|
|
|
DBGPRINT(("TERMSRV: SessDirCheckRedir: size=%d, "
|
|
"addr=%S\n", ServerAddrLen,
|
|
(WCHAR *)pszServerIPAddress));
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: SessDirCheckRedir no server "
|
|
"address: g_SessDirUseServerAddr = %d, "
|
|
"bClientRequireServerAddr = %d\n",
|
|
g_SessDirUseServerAddr,
|
|
pLBInfo->bClientRequireServerAddr));
|
|
}
|
|
|
|
// Setup the load balance info
|
|
if ((pLBInfo->bClientRequireServerAddr == 0) &&
|
|
SessDirGetLBInfo(
|
|
pszServerIPAddress, &LBInfoSize, &LBInfo)) {
|
|
|
|
if (LBInfo) {
|
|
DBGPRINT(("TERMSRV: SessDirCheckRedir: size=%d, "
|
|
"info=%S\n", LBInfoSize,
|
|
(WCHAR *)LBInfo));
|
|
RedirInfoSize += (LBInfoSize + sizeof(ULONG));
|
|
}
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: SessDirCheckRedir failed: "
|
|
"size=%d, info=%S\n", LBInfoSize,
|
|
(WCHAR *)LBInfo));
|
|
|
|
}
|
|
|
|
// Only send domain, username and password info if client
|
|
// redirection version is 3 and above
|
|
if ((pLBInfo->ClientRedirectionVersion >= TS_CLUSTER_REDIRECTION_VERSION3) &&
|
|
fNeedToRedirect) {
|
|
//domain
|
|
if (pLBInfo->Domain) {
|
|
DomainSize = (DWORD)(wcslen(pLBInfo->Domain) + 1) * sizeof(WCHAR);
|
|
RedirInfoSize += DomainSize + sizeof(ULONG);
|
|
}
|
|
|
|
if( pTargetWinStation && pTargetWinStation->pNewNotificationCredentials &&
|
|
wcschr(pTargetWinStation->pNewNotificationCredentials->UserName, L'@') ) {
|
|
|
|
// User is logon using UPN address, we need to send back same UPN in case the target machine
|
|
// does not have the domain in the logon dialog list.
|
|
|
|
// WINLOGON calls TS's WinStationUpdateClientCachedCredentialsWorker() which sets up
|
|
// UPN address
|
|
UserNameSize = (DWORD)(wcslen(pTargetWinStation->pNewNotificationCredentials->UserName) + 1 ) * sizeof(WCHAR);
|
|
RedirInfoSize += UserNameSize + sizeof(ULONG);
|
|
fLogonUsingUPN = TRUE;
|
|
|
|
}
|
|
else if (pLBInfo->UserName) {
|
|
UserNameSize = (DWORD)(wcslen(pLBInfo->UserName) + 1) * sizeof(WCHAR);
|
|
RedirInfoSize += UserNameSize + sizeof(ULONG);
|
|
}
|
|
|
|
//password
|
|
if (pLBInfo->Password) {
|
|
PasswordSize = (DWORD)(wcslen(pLBInfo->Password) + 1) * sizeof(WCHAR);
|
|
RedirInfoSize += PasswordSize + sizeof(ULONG);
|
|
}
|
|
}
|
|
|
|
// Setup the load balance IOCTL
|
|
pRedirInfoStart = pRedirInfo = new BYTE[RedirInfoSize];
|
|
|
|
TS_CLIENT_REDIRECTION_INFO *pClientRedirInfo =
|
|
(TS_CLIENT_REDIRECTION_INFO *)pRedirInfo;
|
|
|
|
if (pRedirInfo != NULL) {
|
|
|
|
if (fNeedToRedirect) {
|
|
pClientRedirInfo->SessionID =
|
|
pTargetWinStation->ClusterDiscSessions[i].
|
|
SessionID;
|
|
}
|
|
|
|
pClientRedirInfo->Flags = 0;
|
|
|
|
pRedirInfo += sizeof(TS_CLIENT_REDIRECTION_INFO);
|
|
|
|
if (ServerAddrLen) {
|
|
*((ULONG UNALIGNED*)(pRedirInfo)) =
|
|
ServerAddrLen;
|
|
|
|
memcpy((BYTE*)(pRedirInfo + sizeof(ULONG)),
|
|
(BYTE*)pszServerIPAddress,
|
|
ServerAddrLen);
|
|
|
|
pRedirInfo += ServerAddrLen + sizeof(ULONG);
|
|
|
|
pClientRedirInfo->Flags |= TARGET_NET_ADDRESS;
|
|
}
|
|
|
|
if (LBInfoSize) {
|
|
*((ULONG UNALIGNED*)(pRedirInfo)) = LBInfoSize;
|
|
memcpy((BYTE*)(pRedirInfo + sizeof(ULONG)),
|
|
LBInfo, LBInfoSize);
|
|
|
|
pRedirInfo += LBInfoSize + sizeof(ULONG);
|
|
|
|
pClientRedirInfo->Flags |= LOAD_BALANCE_INFO;
|
|
}
|
|
|
|
if (UserNameSize) {
|
|
*((ULONG UNALIGNED*)(pRedirInfo)) = UserNameSize;
|
|
|
|
if( TRUE == fLogonUsingUPN ) {
|
|
memcpy((BYTE*)(pRedirInfo + sizeof(ULONG)),
|
|
(BYTE*)(pTargetWinStation->pNewNotificationCredentials->UserName), UserNameSize);
|
|
}
|
|
else {
|
|
memcpy((BYTE*)(pRedirInfo + sizeof(ULONG)),
|
|
(BYTE*)(pLBInfo->UserName), UserNameSize);
|
|
}
|
|
|
|
pRedirInfo += UserNameSize + sizeof(ULONG);
|
|
|
|
pClientRedirInfo->Flags |= LB_USERNAME;
|
|
}
|
|
|
|
if (DomainSize) {
|
|
*((ULONG UNALIGNED*)(pRedirInfo)) = DomainSize;
|
|
memcpy((BYTE*)(pRedirInfo + sizeof(ULONG)),
|
|
(BYTE*)(pLBInfo->Domain), DomainSize);
|
|
|
|
pRedirInfo += DomainSize + sizeof(ULONG);
|
|
|
|
pClientRedirInfo->Flags |= LB_DOMAIN;
|
|
}
|
|
|
|
if (PasswordSize) {
|
|
*((ULONG UNALIGNED*)(pRedirInfo)) = PasswordSize;
|
|
memcpy((BYTE*)(pRedirInfo + sizeof(ULONG)),
|
|
(BYTE*)(pLBInfo->Password), PasswordSize);
|
|
|
|
pRedirInfo += PasswordSize + sizeof(ULONG);
|
|
|
|
pClientRedirInfo->Flags |= LB_PASSWORD;
|
|
}
|
|
|
|
if (pTargetWinStation->fSmartCardLogon) {
|
|
pClientRedirInfo->Flags |= LB_SMARTCARD_LOGON;
|
|
}
|
|
|
|
if (!fNeedToRedirect) {
|
|
pClientRedirInfo->Flags |= LB_NOREDIRECT;
|
|
}
|
|
}
|
|
else {
|
|
Status = STATUS_NO_MEMORY;
|
|
|
|
// The stack returned failure. Continue
|
|
// the current connection.
|
|
TRACE((hTrace,TC_ICASRV,TT_API1,
|
|
"TERMSRV: Failed STACK_CLIENT_REDIR, "
|
|
"SessionID=%u, Status=0x%X\n",
|
|
pTargetWinStation->LogonId, Status));
|
|
PostErrorValueEvent(
|
|
EVENT_TS_SESSDIR_FAIL_CLIENT_REDIRECT,
|
|
Status);
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = IcaStackIoControl(pTargetWinStation->hStack,
|
|
IOCTL_TS_STACK_SEND_CLIENT_REDIRECTION,
|
|
pClientRedirInfo, RedirInfoSize,
|
|
NULL, 0,
|
|
&ReturnLength);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
// Notify session directory
|
|
//
|
|
// There is a relatively benign race condition here.
|
|
// If the second server logs in the user completely,
|
|
// it may end up hitting the session directory
|
|
// before this statement executes. In that case,
|
|
// the directory integrity service may end up
|
|
// pinging the machine once.
|
|
if (fNeedToRedirect) {
|
|
SessDirNotifyReconnectPending(pTargetWinStation->
|
|
ClusterDiscSessions[i].ServerAddress);
|
|
|
|
// Drop the current connection.
|
|
rc = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
// The stack returned failure. Continue
|
|
// the current connection.
|
|
TRACE((hTrace,TC_ICASRV,TT_API1,
|
|
"TERMSRV: Failed STACK_CLIENT_REDIR, "
|
|
"SessionID=%u, Status=0x%X\n",
|
|
pTargetWinStation->LogonId, Status));
|
|
PostErrorValueEvent(
|
|
EVENT_TS_SESSDIR_FAIL_CLIENT_REDIRECT,
|
|
Status);
|
|
}
|
|
|
|
Cleanup:
|
|
// Cleanup the buffers
|
|
if (LBInfo != NULL) {
|
|
SysFreeString((BSTR)LBInfo);
|
|
LBInfo = NULL;
|
|
}
|
|
|
|
if (pRedirInfo != NULL) {
|
|
delete [] pRedirInfoStart;
|
|
pRedirInfoStart = NULL;
|
|
}
|
|
}
|
|
}
|
|
ReleaseTSSD();
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// SetTSSD
|
|
//
|
|
// These three functions ensure protected access to the session directory
|
|
// provider at all times. SetTSSD sets the pointer and increments the
|
|
// reference count to 1.
|
|
//
|
|
// SetTSSD returns:
|
|
// 0 on success
|
|
// -1 if failed because there was still a reference count on the COM object.
|
|
// This could happen if set was called too quickly after the final release
|
|
// to attempt to delete the object, as there still may be pending calls using
|
|
// the COM object.
|
|
// -2 if failed because the critical section is not initialized. This
|
|
// shouldn't be reached in normal operation, because Init is the only
|
|
// function that can call Set, and it bails if it fails the creation of the
|
|
// critical section.
|
|
/****************************************************************************/
|
|
int SetTSSD(ITSSessionDirectory *pTSSD)
|
|
{
|
|
int retval = 0;
|
|
|
|
if (g_bCritSecsInitialized != FALSE) {
|
|
EnterCriticalSection(&g_CritSecComObj);
|
|
|
|
if (g_nComObjRefCount == 0) {
|
|
ASSERT(g_pTSSDPriv == NULL);
|
|
|
|
g_pTSSDPriv = pTSSD;
|
|
g_nComObjRefCount = 1;
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: SetTSSD: obj ref count not 0!\n"));
|
|
retval = -1;
|
|
}
|
|
|
|
LeaveCriticalSection(&g_CritSecComObj);
|
|
}
|
|
else {
|
|
ASSERT(g_bCritSecsInitialized == TRUE);
|
|
retval = -2;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// GetTSSD
|
|
//
|
|
// GetTSSD returns a pointer to the session directory provider, if any, and
|
|
// increments the reference count if there is one.
|
|
/****************************************************************************/
|
|
ITSSessionDirectory *GetTSSD()
|
|
{
|
|
ITSSessionDirectory *pTSSD = NULL;
|
|
|
|
if (g_bCritSecsInitialized != FALSE) {
|
|
EnterCriticalSection(&g_CritSecComObj);
|
|
|
|
if (g_pTSSDPriv != NULL) {
|
|
g_nComObjRefCount += 1;
|
|
}
|
|
else {
|
|
ASSERT(g_nComObjRefCount == 0);
|
|
}
|
|
|
|
pTSSD = g_pTSSDPriv;
|
|
LeaveCriticalSection(&g_CritSecComObj);
|
|
}
|
|
|
|
return pTSSD;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// ReleaseTSSD
|
|
//
|
|
// ReleaseTSSD decrements the reference count of the session directory provider
|
|
// after a thread has finished using it, or when it is going to be deleted.
|
|
//
|
|
// If the reference count goes to zero, the pointer to the session directory
|
|
// provider is set to NULL.
|
|
/****************************************************************************/
|
|
void ReleaseTSSD()
|
|
{
|
|
ITSSessionDirectory *killthispTSSD = NULL;
|
|
|
|
if (g_bCritSecsInitialized != FALSE) {
|
|
EnterCriticalSection(&g_CritSecComObj);
|
|
|
|
ASSERT(g_nComObjRefCount != 0);
|
|
|
|
if (g_nComObjRefCount != 0) {
|
|
g_nComObjRefCount -= 1;
|
|
|
|
if (g_nComObjRefCount == 0) {
|
|
killthispTSSD = g_pTSSDPriv;
|
|
g_pTSSDPriv = NULL;
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&g_CritSecComObj);
|
|
}
|
|
// Now, release the session directory provider if our temppTSSD is NULL.
|
|
// We didn't want to release it while holding the critical section because
|
|
// that might create a deadlock in the recovery thread. Well, it did once.
|
|
if (killthispTSSD != NULL)
|
|
killthispTSSD->Release();
|
|
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// SetTSSDEx
|
|
//
|
|
// These three functions ensure protected access to the session directory
|
|
// provider at all times. SetTSSDEx sets the pointer and increments the
|
|
// reference count to 1.
|
|
//
|
|
// SetTSSDEx returns:
|
|
// 0 on success
|
|
// -1 if failed because there was still a reference count on the COM object.
|
|
// This could happen if set was called too quickly after the final release
|
|
// to attempt to delete the object, as there still may be pending calls using
|
|
// the COM object.
|
|
/****************************************************************************/
|
|
int SetTSSDEx(ITSSessionDirectoryEx *pTSSDEx)
|
|
{
|
|
int retval = 0;
|
|
|
|
EnterCriticalSection(&g_CritSecComObj);
|
|
|
|
if (g_nTSSDExObjRefCount == 0) {
|
|
ASSERT(g_pTSSDExPriv == NULL);
|
|
|
|
g_pTSSDExPriv = pTSSDEx;
|
|
g_nTSSDExObjRefCount = 1;
|
|
}
|
|
else {
|
|
DBGPRINT(("TERMSRV: SetTSSDEx: obj ref count not 0!\n"));
|
|
retval = -1;
|
|
}
|
|
|
|
LeaveCriticalSection(&g_CritSecComObj);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// GetTSSDEx
|
|
//
|
|
// GetTSSDEx returns a pointer to the session directory provider, if any, and
|
|
// increments the reference count if there is one.
|
|
/****************************************************************************/
|
|
ITSSessionDirectoryEx *GetTSSDEx()
|
|
{
|
|
ITSSessionDirectoryEx *pTSSDEx = NULL;
|
|
|
|
if (g_bCritSecsInitialized != FALSE) {
|
|
EnterCriticalSection(&g_CritSecComObj);
|
|
|
|
if (g_pTSSDExPriv != NULL) {
|
|
g_nTSSDExObjRefCount += 1;
|
|
}
|
|
else {
|
|
ASSERT(g_nTSSDExObjRefCount == 0);
|
|
}
|
|
|
|
pTSSDEx = g_pTSSDExPriv;
|
|
LeaveCriticalSection(&g_CritSecComObj);
|
|
}
|
|
|
|
return pTSSDEx;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
// ReleaseTSSDEx
|
|
//
|
|
// ReleaseTSSDEx decrements the reference count of the session directory
|
|
// provider after a thread has finished using it, or when it is going to be
|
|
// deleted.
|
|
//
|
|
// If the reference count goes to zero, the pointer to the session directory
|
|
// provider is set to NULL.
|
|
/****************************************************************************/
|
|
void ReleaseTSSDEx()
|
|
{
|
|
ITSSessionDirectoryEx *killthispTSSDEx = NULL;
|
|
|
|
EnterCriticalSection(&g_CritSecComObj);
|
|
|
|
ASSERT(g_nTSSDExObjRefCount != 0);
|
|
if (g_nTSSDExObjRefCount != 0) {
|
|
g_nTSSDExObjRefCount -= 1;
|
|
|
|
if (g_nTSSDExObjRefCount == 0) {
|
|
killthispTSSDEx = g_pTSSDExPriv;
|
|
g_pTSSDExPriv = NULL;
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&g_CritSecComObj);
|
|
|
|
// Now, release the session directory provider if our temppTSSD is NULL.
|
|
// We didn't want to release it while holding the critical section because
|
|
// that might create a deadlock in the recovery thread. Well, it did once.
|
|
if (killthispTSSDEx != NULL)
|
|
killthispTSSDEx->Release();
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
****************************************************************************/
|
|
DWORD SessDirOpenSessionDirectory( LPWSTR pszServerName )
|
|
{
|
|
ITSSessionDirectory *pTSSD = NULL;
|
|
DWORD Len;
|
|
DWORD Type;
|
|
WCHAR CLSIDStr[CLSIDLENGTH + 1]; // CLSIDLENGTH is limit, one extra for NULL
|
|
CLSID TSSDCLSID;
|
|
DWORD Status = ERROR_SUCCESS;
|
|
HKEY hKeyTermSrv = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Load registry keys.
|
|
Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_CONTROL_TSERVER, 0,
|
|
KEY_READ, &hKeyTermSrv);
|
|
if (Status != ERROR_SUCCESS)
|
|
{
|
|
// Return Error Code as it is
|
|
goto Exit;
|
|
}
|
|
|
|
CLSIDStr[CLSIDLENGTH] = L'\0';
|
|
|
|
// Get the CLSID of the session directory object to instantiate.
|
|
Len = sizeof(CLSIDStr) - sizeof(CLSIDStr[0]);
|
|
Status = RegQueryValueEx(hKeyTermSrv, REG_TS_SESSDIRCLSID, NULL, &Type,
|
|
(BYTE *)CLSIDStr, &Len);
|
|
|
|
if( Status != ERROR_SUCCESS )
|
|
{
|
|
// Return Error Code as it is
|
|
goto Exit;
|
|
}
|
|
|
|
if( Type != REG_SZ || wcslen(CLSIDStr) == 0 )
|
|
{
|
|
// we have invalid data in registry, likely cause is setup not done.
|
|
Status = ERROR_INVALID_DATA;
|
|
goto Exit;
|
|
}
|
|
|
|
hr = CLSIDFromString(CLSIDStr, &TSSDCLSID);
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Get the instance of TSSessionDirectory interface
|
|
hr = CoCreateInstance(TSSDCLSID, NULL,
|
|
CLSCTX_INPROC_SERVER, IID_ITSSessionDirectory,
|
|
(void **)&pTSSD);
|
|
if (SUCCEEDED(hr)) {
|
|
// Call PingSD to make the RPC call to SD
|
|
hr = pTSSD->PingSD(pszServerName);
|
|
pTSSD->Release();
|
|
}
|
|
}
|
|
|
|
// none of code returns HRESULT.
|
|
Status = HRESULT_CODE(hr);
|
|
|
|
Exit:
|
|
if (hKeyTermSrv) {
|
|
RegCloseKey(hKeyTermSrv);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
#pragma warning (pop)
|
|
|