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.
1810 lines
49 KiB
1810 lines
49 KiB
// SessionResolver.cpp : Implementation of CSessionResolver
|
|
#include "stdafx.h"
|
|
#include "SAFSessionResolver.h"
|
|
#include "SessionResolver.h"
|
|
#include <sddl.h>
|
|
#include <userenv.h>
|
|
#include <winerror.h>
|
|
#include <wtsapi32.h>
|
|
#include <winsta.h>
|
|
#include <wchar.h>
|
|
#include <stdarg.h>
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#define ANSI
|
|
#include <stdarg.h>
|
|
#include <psapi.h>
|
|
|
|
|
|
#include <rderror.h>
|
|
|
|
const WCHAR c_wszHelpCtr[] = L"\\PCHealth\\HelpCtr\\Binaries\\HelpCtr.exe\"";
|
|
const WCHAR c_wszCmdLine[] = L" -Mode \"hcp://system/Remote Assistance/RAHelpeeAcceptLayout.xml\" -url ";
|
|
const WCHAR c_wszRdsAddin[] = L"rdsaddin.exe";
|
|
|
|
#define ARRAYSIZE(x) sizeof(x)/sizeof(x[0])
|
|
|
|
#define BUF_SZ 200
|
|
|
|
typedef struct _WTS_USER_SESSION_INFO {
|
|
DWORD dwIndex; // Index into full table of these
|
|
DWORD dwSessionId; // WTS Session ID
|
|
HANDLE hUserToken; // Access token for user
|
|
HANDLE hEvent; // filled in by launchex, yank on this to say "yes"
|
|
HANDLE hProcess; // filled in by CreateProcessAsUser
|
|
HANDLE hThread; // so's this
|
|
DWORD dwProcessId; // and this
|
|
DWORD dwThreadId; // and this
|
|
} WTS_USER_SESSION_INFO, *PWTS_USER_SESSION_INFO;
|
|
|
|
// stolen from internal/ds/inc
|
|
|
|
#define RtlGenRandom SystemFunction036
|
|
|
|
extern "C" {
|
|
BOOL WINAPI
|
|
RtlGenRandom(
|
|
OUT PVOID RandomBuffer,
|
|
IN ULONG RandomBufferLength
|
|
);
|
|
}
|
|
|
|
/*
|
|
* Forward declarations
|
|
*/
|
|
PSID GetRealSID( BSTR pTextSID);
|
|
DWORD getUserName(PSID pUserSID, WCHAR **lpName, WCHAR **lpDomain);
|
|
|
|
HANDLE launchEx(PSID pUserSID, WTS_USER_SESSION_INFO *UserInfo, WCHAR *ConnectParms, WCHAR *HelpUrl, WCHAR *lpName, WCHAR *lpDomain, WCHAR *expertHelpBlob, WCHAR *userHelpBlob, SECURITY_ATTRIBUTES *sa, WCHAR *lpszMutex);
|
|
|
|
DWORD GetUserSessions(PSID pUserSID, PWTS_USER_SESSION_INFO *pUserTbl, DWORD *pEntryCnt, WCHAR *lpName, WCHAR *lpDomain);
|
|
PSECURITY_DESCRIPTOR CreateSd(PSID pUserSID);
|
|
BOOL SecurityCheck(PSID pUserSID);
|
|
DWORD localKill(WTS_USER_SESSION_INFO *SessInfo, LPTHREAD_START_ROUTINE killThrd, LPSECURITY_ATTRIBUTES lpSA);
|
|
LPTHREAD_START_ROUTINE getKillProc(void);
|
|
BOOL ListFind(PSPLASHLIST pSplash, PSID user);
|
|
BOOL ListInsert(PSPLASHLIST pSplash, PSID user);
|
|
BOOL ListDelete(PSPLASHLIST pSplash, PSID user);
|
|
BOOL GetPropertyValueFromBlob(BSTR bstrHelpBlob, WCHAR * pName, WCHAR** ppValue);
|
|
|
|
/************ things that should remain as defines ****************/
|
|
// Some environment variables used to communicate with scripts
|
|
#define ENV_USER L"USERNAME"
|
|
#define ENV_DOMAIN L"USERDOMAIN"
|
|
#define ENV_EVENT L"PCHEVENTNAME"
|
|
#define ENV_INDEX L"PCHSESSIONENUM"
|
|
#define ENV_PARMS L"PCHCONNECTPARMS"
|
|
#define EVENT_PREFIX L"Alex:PCH"
|
|
|
|
#define MODULE_NAME L"safrslv"
|
|
|
|
// I can't imagine a user having more logins than this on one server, but...
|
|
#define MAX_SESSIONS 30 // used to be 256
|
|
|
|
/************ our debug spew stuff ******************/
|
|
void DbgSpew(int DbgClass, BSTR lpFormat, va_list ap);
|
|
void TrivialSpew(BSTR lpFormat, ...);
|
|
void InterestingSpew(BSTR lpFormat, ...);
|
|
void ImportantSpew(BSTR lpFormat, ...);
|
|
void HeinousESpew(BSTR lpFormat, ...);
|
|
void HeinousISpew(BSTR lpFormat, ...);
|
|
|
|
#define DBG_MSG_TRIVIAL 0x001
|
|
#define DBG_MSG_INTERESTING 0x002
|
|
#define DBG_MSG_IMPORTANT 0x003
|
|
#define DBG_MSG_HEINOUS 0x004
|
|
#define DBG_MSG_DEST_DBG 0x010
|
|
#define DBG_MSG_DEST_FILE 0x020
|
|
#define DBG_MSG_DEST_EVENT 0x040
|
|
#define DBG_MSG_TIME_MSGS 0x080
|
|
#define DBG_MSG_CLASS_ERROR 0x100
|
|
#define DBG_MSG_CLASS_SECURE 0x200
|
|
|
|
#define TRIVIAL_MSG(msg) TrivialSpew msg
|
|
#define INTERESTING_MSG(msg) InterestingSpew msg
|
|
#define IMPORTANT_MSG(msg) ImportantSpew msg
|
|
#define HEINOUS_E_MSG(msg) HeinousESpew msg
|
|
#define HEINOUS_I_MSG(msg) HeinousISpew msg
|
|
|
|
/* Strings for some error spewage. I waste the space since these friendly strings
|
|
* do make it into the Event Logs...
|
|
*/
|
|
WCHAR *lpszConnectState[] = {
|
|
L"State_Active",
|
|
L"State_Connected",
|
|
L"State_ConnectQuery",
|
|
L"State_Shadow",
|
|
L"State_Disconnected",
|
|
L"State_Idle",
|
|
L"State_Listen",
|
|
L"State_Reset",
|
|
L"State_Down",
|
|
L"State_Init"
|
|
};
|
|
|
|
/*
|
|
* This global flag controls the amount of spew that we
|
|
* produce. Legit values are as follows:
|
|
* 1 = Trivial msgs displayed
|
|
* 2 = Interesting msgs displayed
|
|
* 3 = Important msgs displayed
|
|
* 4 = only the most Heinous msgs displayed
|
|
* The ctor actually sets this to 3 by default, but it can
|
|
* be overridden by setting:
|
|
* HKLM, Software/Microsoft/SAFSessionResolver, DebugSpew, DWORD
|
|
*/
|
|
int gDbgFlag = 0x1;
|
|
int iDbgFileHandle = 0;
|
|
long lSessionTag;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CSessionResolver Methods
|
|
/*************************************************************
|
|
*
|
|
* NewResolveTSRDPSessionID(ConnectParms, userSID, *sessionID)
|
|
* Returns the WTS SessionID for the one enabled and
|
|
* ready to accept Remote Control.
|
|
*
|
|
* RETURN CODES:
|
|
* WTS_Session_ID Connection accepted by user
|
|
* RC_REFUSED Connection refused by user
|
|
* RC_TIMEOUT User never responded
|
|
* NONE_ACTIVE Found no active WTS sessions
|
|
* API_FAILURE Something bad happened
|
|
*
|
|
*************************************************************/
|
|
STDMETHODIMP
|
|
CSessionResolver::ResolveUserSessionID(
|
|
/*[in]*/BSTR connectParms,
|
|
/*[in]*/BSTR userSID,
|
|
/*[in]*/ BSTR expertHelpBlob,
|
|
/*[in]*/ BSTR userHelpBlob,
|
|
/*[in]*/ ULONG_PTR hShutDown,
|
|
/*[out, retval]*/long *sessionID,
|
|
/*[in*/ DWORD dwPID,
|
|
/*[out]*/ULONG_PTR *hHelpCtr
|
|
,/*[out, retval]*/int *userResponse
|
|
)
|
|
{
|
|
INTERESTING_MSG((L"CSessionResolver::ResolveUserSessionID"));
|
|
|
|
DWORD result;
|
|
HANDLE hRdsAddin = NULL;
|
|
HANDLE hReserveMutex = NULL;
|
|
PSID pRealSID = NULL;
|
|
WCHAR *pUsername=NULL, *pDomainname=NULL;
|
|
PWTS_USER_SESSION_INFO pUserSessionInfo=NULL;
|
|
PSECURITY_DESCRIPTOR pSD=NULL;
|
|
HRESULT ret_code;
|
|
int i;
|
|
int TsIndex=-1;
|
|
DWORD dwUserSessionCnt, dwSessionCnt;
|
|
HANDLE pHandles[(MAX_SESSIONS*2)+2];
|
|
DWORD dwhIndex = 0;
|
|
SECURITY_ATTRIBUTES sa;
|
|
BOOL bAlreadyHelped, bRemoval=FALSE;
|
|
WCHAR *pExpertId=NULL, *pUserId=NULL;
|
|
|
|
/* param validation */
|
|
if (!connectParms || !userSID || !sessionID || !hHelpCtr
|
|
|| !userResponse
|
|
)
|
|
{
|
|
IMPORTANT_MSG((L"Invalid params ConnectParms=0x%x, UserSID=0x%x, SessionID=0x%x", connectParms, userSID, sessionID));
|
|
ret_code = E_INVALIDARG;
|
|
goto done;
|
|
}
|
|
|
|
if( m_bCriticalError )
|
|
{
|
|
IMPORTANT_MSG( (L"Possible low resources-InitCritSec failed" ) );
|
|
ret_code = E_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
|
|
// set a default ret code
|
|
*userResponse = SAFERROR_INTERNALERROR;
|
|
|
|
if (dwPID)
|
|
hRdsAddin = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
|
|
|
|
if (hRdsAddin)
|
|
{
|
|
/*
|
|
* Make certain this is the correct process. If it is not
|
|
* (rdsaddin.exe) then we bail out.
|
|
*/
|
|
|
|
DWORD dwRes;
|
|
WCHAR szTmp[32];
|
|
|
|
dwRes = GetModuleBaseNameW(hRdsAddin, NULL, szTmp, ARRAYSIZE(szTmp));
|
|
if (!dwRes || StrCmpI(c_wszRdsAddin, szTmp))
|
|
{
|
|
IMPORTANT_MSG((L"ERROR: the process handle for rdsaddin.exe has been recycled"));
|
|
CloseHandle(hRdsAddin);
|
|
hRdsAddin = 0;
|
|
}
|
|
}
|
|
|
|
if (!hRdsAddin)
|
|
{
|
|
// If we don't have a process handle, it is because
|
|
// the expert has already cancelled
|
|
ret_code = E_ACCESSDENIED;
|
|
*userResponse = SAFERROR_CANTFORMLINKTOUSERSESSION;
|
|
goto done;
|
|
}
|
|
|
|
pRealSID = GetRealSID(userSID);
|
|
if (!pRealSID)
|
|
{
|
|
IMPORTANT_MSG((L"GetRealSID failed"));
|
|
|
|
ret_code = E_ACCESSDENIED;
|
|
*userResponse = SAFERROR_INVALIDPARAMETERSTRING;
|
|
goto done;
|
|
}
|
|
|
|
EnterCriticalSection(&m_CritSec);
|
|
bAlreadyHelped = ListFind(m_pSplash, pRealSID);
|
|
ListInsert(m_pSplash, pRealSID);
|
|
// mark the SID for removal
|
|
bRemoval=TRUE;
|
|
LeaveCriticalSection(&m_CritSec);
|
|
|
|
if (bAlreadyHelped)
|
|
{
|
|
INTERESTING_MSG((L"Helpee already has a ticket on the screen"));
|
|
*sessionID = 0;
|
|
*userResponse = SAFERROR_HELPEECONSIDERINGHELP;
|
|
ret_code = E_ACCESSDENIED;
|
|
goto done;
|
|
}
|
|
|
|
TRIVIAL_MSG((L"userHelpBlob=[%s], expertHelpBlob=[%s]",userHelpBlob?userHelpBlob:L"NULL", expertHelpBlob?expertHelpBlob:L"NULL" ));
|
|
/* check password: skip is UNSOLICITED=1 */
|
|
if (!GetPropertyValueFromBlob(userHelpBlob, L"UNSOLICITED", &pUserId) ||
|
|
!pUserId || *pUserId != L'1')
|
|
{
|
|
// Need to check password.
|
|
if (pUserId)
|
|
{
|
|
LocalFree(pUserId);
|
|
pUserId = NULL;
|
|
}
|
|
if (GetPropertyValueFromBlob(userHelpBlob, L"PASS", &pUserId))
|
|
{
|
|
if (!GetPropertyValueFromBlob(expertHelpBlob, L"PASS", &pExpertId) || wcscmp(pExpertId, pUserId) != 0)
|
|
{
|
|
IMPORTANT_MSG((L"Passwords don't match, x:%s, n:%s ", pExpertId, pUserId));
|
|
|
|
ret_code = E_ACCESSDENIED;
|
|
*userResponse = SAFERROR_INVALIDPASSWORD;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* get the user's account strings */
|
|
if (!getUserName(pRealSID, &pUsername, &pDomainname))
|
|
{
|
|
DWORD error = GetLastError();
|
|
HEINOUS_E_MSG((L"getUserName() failed, err=0x%x", error));
|
|
ret_code = E_ACCESSDENIED;
|
|
*userResponse = SAFERROR_INVALIDPARAMETERSTRING;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Get a list of all the active sessions on this WTS Server
|
|
* For a specific user
|
|
*/
|
|
// keeps the compiler happy
|
|
dwSessionCnt = 0;
|
|
result = GetUserSessions(pRealSID,
|
|
&pUserSessionInfo,
|
|
&dwUserSessionCnt,
|
|
pUsername, pDomainname);
|
|
|
|
if (!result )
|
|
{
|
|
IMPORTANT_MSG((L"GetUserSessions failed %08x", GetLastError()));
|
|
ret_code = E_FAIL;
|
|
goto done;
|
|
}
|
|
|
|
/* If no sessions are found, then exit! */
|
|
if (dwUserSessionCnt == 0)
|
|
{
|
|
INTERESTING_MSG((L"no sessions found"));
|
|
*sessionID = 0;
|
|
*userResponse = SAFERROR_HELPEENOTFOUND;
|
|
ret_code = E_ACCESSDENIED;
|
|
goto done;
|
|
}
|
|
/* make certain we don't overflow our handle buffers */
|
|
else if (dwUserSessionCnt > MAX_SESSIONS)
|
|
{
|
|
HEINOUS_I_MSG((L"Found %d active sessions for %ws/%ws, limitting to %d", dwUserSessionCnt, pDomainname, pUsername, MAX_SESSIONS));
|
|
|
|
i = MAX_SESSIONS;
|
|
|
|
// free the extra WTS tokens
|
|
while (i < (int)dwSessionCnt)
|
|
{
|
|
if (pUserSessionInfo[i].hUserToken)
|
|
CloseHandle(pUserSessionInfo[i].hUserToken);
|
|
i++;
|
|
}
|
|
dwUserSessionCnt = MAX_SESSIONS;
|
|
}
|
|
|
|
pSD = CreateSd(pRealSID);
|
|
if (!pSD)
|
|
{
|
|
IMPORTANT_MSG((L"CreateSd failed err=%08x", GetLastError()));
|
|
ret_code = E_ACCESSDENIED;
|
|
goto done;
|
|
}
|
|
|
|
sa.nLength = sizeof(sa);
|
|
sa.lpSecurityDescriptor = pSD;
|
|
sa.bInheritHandle = FALSE;
|
|
|
|
memset(&pHandles[0], 0, sizeof(pHandles));
|
|
|
|
pHandles[0] = hRdsAddin;
|
|
|
|
lSessionTag = InterlockedIncrement(&m_lSessionTag);
|
|
|
|
WCHAR lpNameBfr[256];
|
|
|
|
wnsprintfW(lpNameBfr, ARRAYSIZE(lpNameBfr), L"Global\\AlexDont%lx", lSessionTag);
|
|
|
|
hReserveMutex = CreateMutex(&sa, FALSE, &lpNameBfr[0]);
|
|
if (!hReserveMutex || ERROR_ALREADY_EXISTS == GetLastError())
|
|
{
|
|
/*
|
|
* If we failed to create this event, it is most likely because one is already
|
|
* in name-space, perhaps an event, or else a mutex... In any case, we will
|
|
* try again, with a more random name. If that fails, then we must bail out.
|
|
*/
|
|
long lRand;
|
|
|
|
if (hReserveMutex)
|
|
CloseHandle(hReserveMutex);
|
|
|
|
RtlGenRandom(&lRand, sizeof(lRand));
|
|
wnsprintfW(lpNameBfr, ARRAYSIZE(lpNameBfr), L"Global\\%lx%lx",
|
|
lRand, lSessionTag);
|
|
hReserveMutex = CreateMutex(&sa, FALSE, &lpNameBfr[0]);
|
|
|
|
if (!hReserveMutex || ERROR_ALREADY_EXISTS == GetLastError())
|
|
{
|
|
if (hReserveMutex)
|
|
CloseHandle(hReserveMutex);
|
|
hReserveMutex = 0;
|
|
HEINOUS_E_MSG((L"The named event \"%s\" was in use- potential security issue, so Remote Assistance will be denied.", lpNameBfr));
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Start up the HelpCtr in all the various TS sessions
|
|
*/
|
|
|
|
ret_code = S_OK;
|
|
|
|
for(i=0; i<(int)dwUserSessionCnt; i++)
|
|
{
|
|
TRIVIAL_MSG((L"calling launchEx[%d] for session %d", i, pUserSessionInfo[i].dwSessionId));
|
|
|
|
pUserSessionInfo[i].hProcess = launchEx(pRealSID,
|
|
&pUserSessionInfo[i],
|
|
connectParms, m_bstrResolveURL,
|
|
pUsername, pDomainname,
|
|
expertHelpBlob,userHelpBlob, &sa, lpNameBfr);
|
|
|
|
if( pUserSessionInfo[i].hProcess == NULL || pUserSessionInfo[i].hEvent == NULL )
|
|
{
|
|
ret_code = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
pHandles[i+1] = pUserSessionInfo[i].hProcess;
|
|
pHandles[i+1+dwUserSessionCnt] = pUserSessionInfo[i].hEvent;
|
|
}
|
|
|
|
//
|
|
// Last in list is our shutdown event
|
|
//
|
|
pHandles[dwUserSessionCnt*2+1] = (HANDLE)hShutDown;
|
|
|
|
if( ret_code == S_OK )
|
|
{
|
|
|
|
/*
|
|
* Then wait for somebody to click on a "Yes", or a "No"
|
|
*/
|
|
|
|
// We use CoWaitForMultipleHandles otherwise, rdsaddin and sessmg
|
|
// deadlock since sessmgr might be apartment threaded
|
|
TRIVIAL_MSG((L"Waiting. m_iWaitDuration: %ld seconds", m_iWaitDuration/1000));
|
|
|
|
ret_code = CoWaitForMultipleHandles (
|
|
COWAIT_ALERTABLE,
|
|
m_iWaitDuration,
|
|
(dwUserSessionCnt*2)+2,
|
|
pHandles,
|
|
&dwhIndex
|
|
);
|
|
// try to take the mutex (in case somebody asks for it later...
|
|
CoWaitForMultipleHandles (COWAIT_ALERTABLE, 0, 1, &hReserveMutex, &result);
|
|
}
|
|
|
|
|
|
if( ret_code == S_OK )
|
|
{
|
|
|
|
/*
|
|
* Then wait for somebody to click on a "Yes", or a "No"
|
|
*/
|
|
|
|
// We use CoWaitForMultipleHandles otherwise, rdsaddin and sessmg
|
|
// deadlock since sessmgr might be apartment threaded
|
|
TRIVIAL_MSG((L"Waiting. m_iWaitDuration: %ld seconds", m_iWaitDuration/1000));
|
|
|
|
ret_code = CoWaitForMultipleHandles (
|
|
COWAIT_ALERTABLE,
|
|
m_iWaitDuration,
|
|
(dwUserSessionCnt*2)+1,
|
|
pHandles,
|
|
&dwhIndex
|
|
);
|
|
// try to take the mutex (in case somebody asks for it later...
|
|
CoWaitForMultipleHandles (COWAIT_ALERTABLE, 0, 1, &hReserveMutex, &result);
|
|
}
|
|
|
|
if (S_OK == ret_code)
|
|
{
|
|
if(dwhIndex == dwUserSessionCnt*2+1)
|
|
{
|
|
// shutdown event has signaled.
|
|
TsIndex = -1;
|
|
ret_code = E_ACCESSDENIED;
|
|
*userResponse = SAFERROR_CANTFORMLINKTOUSERSESSION;
|
|
}
|
|
else if (dwhIndex > dwUserSessionCnt)
|
|
{
|
|
/* somebody said "yes" */
|
|
long l_iWaitDuration = m_iWaitDuration/2;
|
|
|
|
TsIndex = dwhIndex-dwUserSessionCnt-1;
|
|
TRIVIAL_MSG((L"User responded YES for session 0x%x, waiting for Salem setup", TsIndex));
|
|
|
|
/*
|
|
* Then we wait for the HelpCtr scripts to establish the Salem connection
|
|
*/
|
|
ResetEvent(pHandles[dwhIndex]);
|
|
ret_code = CoWaitForMultipleHandles (COWAIT_ALERTABLE, l_iWaitDuration, 1, &pHandles[dwhIndex], &result);
|
|
|
|
if (ret_code != S_OK || result != WAIT_OBJECT_0)
|
|
{
|
|
IMPORTANT_MSG((L"Bad script- it didn't signal acknowledgement within %d seconds", l_iWaitDuration/1000));
|
|
bRemoval = TRUE;
|
|
ret_code = E_ACCESSDENIED;
|
|
*userResponse = SAFERROR_INTERNALERROR;
|
|
TsIndex = -1;
|
|
}
|
|
else
|
|
{
|
|
TRIVIAL_MSG((L"HelpCtr set up Salem in time."));
|
|
|
|
*userResponse = SAFERROR_NOERROR;
|
|
// mark the SID for non-removal
|
|
bRemoval=FALSE;
|
|
|
|
*hHelpCtr = NULL; // start with NULL
|
|
// return the hProc of the HelpCenter instance that wants to start RA
|
|
DuplicateHandle(GetCurrentProcess(), pUserSessionInfo[TsIndex].hProcess,
|
|
hRdsAddin, (HANDLE *)hHelpCtr, SYNCHRONIZE, FALSE, 0);
|
|
}
|
|
}
|
|
else if (dwhIndex == 0)
|
|
{
|
|
// we got here because the expert bailed out, or we lost the connection
|
|
INTERESTING_MSG((L"Expert killed RdsAddin"));
|
|
TsIndex = -1;
|
|
ret_code = E_ACCESSDENIED;
|
|
*userResponse = SAFERROR_CANTFORMLINKTOUSERSESSION;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We get here because the novice "killed" a HelpCtr session
|
|
* or else the novice just said "NO"
|
|
*/
|
|
INTERESTING_MSG((L"User killed session or clicked NO for session 0x%x", dwhIndex-1));
|
|
/* this keeps us from trying to kill something the user has already closed */
|
|
TsIndex = dwhIndex-1;
|
|
ret_code = E_ACCESSDENIED;
|
|
*userResponse = SAFERROR_HELPEESAIDNO;
|
|
}
|
|
}
|
|
else if (RPC_S_CALLPENDING == ret_code)
|
|
{
|
|
TRIVIAL_MSG((L"User response timed out after %d seconds", m_iWaitDuration/1000));
|
|
|
|
TsIndex = -1;
|
|
ret_code = E_PENDING;
|
|
*userResponse = SAFERROR_HELPEENEVERRESPONDED;
|
|
}
|
|
else
|
|
{
|
|
IMPORTANT_MSG((L"WaitForObject failed %08x err=%08x", result, GetLastError()));
|
|
TsIndex = -1;
|
|
ret_code = E_FAIL;
|
|
}
|
|
|
|
/*
|
|
* Then close all the windows (except the one in TsIndex)
|
|
*/
|
|
for(i=0; i<(int)dwUserSessionCnt; i++)
|
|
{
|
|
LPTHREAD_START_ROUTINE lpKill = getKillProc();
|
|
|
|
if (pUserSessionInfo[i].dwIndex != TsIndex &&
|
|
pUserSessionInfo[i].hProcess)
|
|
{
|
|
/* This has to be done for each instance, since we call into the process
|
|
* to kill itself. If we did not get "lpKill" for each seperate occurance
|
|
* of HelpCtr, then Very Bad Things could happen...
|
|
*/
|
|
TRIVIAL_MSG((L"Killing HelpCtr in process %d", pUserSessionInfo[i].hProcess));
|
|
localKill(&pUserSessionInfo[i], lpKill, &sa);
|
|
}
|
|
}
|
|
|
|
if (ret_code == S_OK)
|
|
*sessionID = (long) pUserSessionInfo[TsIndex].dwSessionId;
|
|
done:
|
|
if (bRemoval)
|
|
{
|
|
// remove the SID from the list
|
|
EnterCriticalSection(&m_CritSec);
|
|
ListDelete(m_pSplash, pRealSID);
|
|
LeaveCriticalSection(&m_CritSec);
|
|
}
|
|
|
|
if (hReserveMutex)
|
|
CloseHandle(hReserveMutex);
|
|
if (hRdsAddin)
|
|
CloseHandle(hRdsAddin);
|
|
|
|
if (pRealSID)
|
|
LocalFree(pRealSID);
|
|
|
|
if (pUserSessionInfo)
|
|
{
|
|
|
|
/* close all the handles */
|
|
for(i=0; i<(int)dwUserSessionCnt; i++)
|
|
{
|
|
if (pUserSessionInfo[i].hProcess)
|
|
CloseHandle(pUserSessionInfo[i].hProcess);
|
|
|
|
if (pUserSessionInfo[i].hUserToken)
|
|
CloseHandle(pUserSessionInfo[i].hUserToken);
|
|
|
|
if (pUserSessionInfo[i].hEvent)
|
|
CloseHandle(pUserSessionInfo[i].hEvent);
|
|
}
|
|
|
|
LocalFree(pUserSessionInfo);
|
|
}
|
|
|
|
if (pUsername)
|
|
LocalFree(pUsername);
|
|
|
|
if (pDomainname)
|
|
LocalFree(pDomainname);
|
|
|
|
if (pSD)
|
|
LocalFree(pSD);
|
|
|
|
if (pUserId) LocalFree(pUserId);
|
|
if (pExpertId) LocalFree(pExpertId);
|
|
|
|
INTERESTING_MSG((L"CSessionResolver::ResolveUserSessionID returns %x\n", ret_code ));
|
|
return ret_code;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* OnDisconnect([in] BSTR connectParms, [in] BSTR userSID, [in] long sessionID)
|
|
* Notifies us when an RA session ends
|
|
*
|
|
* NOTES:
|
|
* This is called so we can maintain the state of our user prompts
|
|
*
|
|
* WARNING: ACHTUNG: ATTENZIONE:
|
|
* This method must do a minimal amount of work before returning
|
|
* and must NEVER do anything that would cause COM to pump
|
|
* messages. To do so would screw Salem immensely.a
|
|
*
|
|
* RETURN CODES:
|
|
* NONE_ACTIVE Found no active WTS sessions
|
|
* API_FAILURE Something bad happened
|
|
*
|
|
*************************************************************/
|
|
|
|
STDMETHODIMP
|
|
CSessionResolver::OnDisconnect(
|
|
/*[in]*/BSTR connectParms,
|
|
/*[in]*/BSTR userSID,
|
|
/*[in]*/long sessionID
|
|
)
|
|
{
|
|
PSID pRealSID;
|
|
|
|
if (!connectParms || !userSID)
|
|
{
|
|
HEINOUS_I_MSG((L"Invalid params in OnDisconnect- ConnectParms=0x%x, UserSID=0x%x", connectParms, userSID));
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
|
|
INTERESTING_MSG((L"CSessionResolver::OnDisconnect-(%ws)", userSID));
|
|
|
|
pRealSID = GetRealSID(userSID);
|
|
if (pRealSID)
|
|
{
|
|
EnterCriticalSection(&m_CritSec);
|
|
ListDelete(m_pSplash, pRealSID);
|
|
LeaveCriticalSection(&m_CritSec);
|
|
LocalFree(pRealSID);
|
|
}
|
|
|
|
|
|
INTERESTING_MSG((L"CSessionResolver::OnDisconnect; leaving"));
|
|
return S_OK;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* GetRealSID([in] BSTR pTextSID)
|
|
* Converts a string-based SID into a REAL usable SID
|
|
*
|
|
* NOTES:
|
|
* This is a stub into "ConvertStringSidToSid".
|
|
*
|
|
* RETURN CODES:
|
|
* NULL Failed for some reason
|
|
* PSID Pointer to a real SID. Must be
|
|
* freed with "LocalFree"
|
|
*
|
|
*************************************************************/
|
|
PSID GetRealSID( BSTR pTextSID)
|
|
{
|
|
PSID pRetSID = NULL;
|
|
|
|
if (!ConvertStringSidToSidW(pTextSID, &pRetSID))
|
|
IMPORTANT_MSG((L"ConvertStringSidToSidW(%ws) failed %08x\n", pTextSID, GetLastError()));
|
|
|
|
return pRetSID;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* launchEx(PSID, WTS_USER_SESSION_INFO, char * ConnectParms, char * EventName)
|
|
*
|
|
*
|
|
* RETURN CODES:
|
|
* 0 Failed to start process
|
|
* <> HANDLE to started process
|
|
*
|
|
*************************************************************/
|
|
|
|
HANDLE launchEx(PSID pUserSID, WTS_USER_SESSION_INFO *UserInfo,
|
|
WCHAR *ConnectParms, WCHAR *HelpPageURL,
|
|
WCHAR *pUsername, WCHAR *pDomainname,
|
|
WCHAR *expertHelpBlob, WCHAR *userHelpBlob,
|
|
SECURITY_ATTRIBUTES *sa,
|
|
WCHAR *lpszMutex
|
|
)
|
|
{
|
|
BOOL result=FALSE;
|
|
HANDLE retval = 0;
|
|
STARTUPINFOW StartUp;
|
|
PROCESS_INFORMATION p_i;
|
|
WCHAR buf1[BUF_SZ], buf2[BUF_SZ], *lpUtf8ConnectParms=NULL;
|
|
static WCHAR *szEnvUser = ENV_USER;
|
|
static WCHAR *szEnvDomain = ENV_DOMAIN;
|
|
static WCHAR *szEnvEvent = ENV_EVENT;
|
|
static WCHAR *szEnvIndex = ENV_INDEX;
|
|
static WCHAR *szEnvParms = ENV_PARMS;
|
|
static WCHAR *szEnvExpertBlob = L"PCHEXPERTBLOB";
|
|
static WCHAR *szEnvUserBlob = L"PCHUSERBLOB";
|
|
static WCHAR *szEnvMutex = L"PCHMutexName";
|
|
|
|
VOID *pEnvBlock = NULL;
|
|
DWORD dwUsername=0, dwDomainname=0, dwStrSz;
|
|
WCHAR *wszExe = NULL;
|
|
|
|
dwStrSz = 3 + GetSystemWindowsDirectory(NULL, 0);
|
|
dwStrSz += wcslen(c_wszHelpCtr);
|
|
dwStrSz += wcslen(c_wszCmdLine);
|
|
dwStrSz += wcslen(HelpPageURL);
|
|
|
|
__try { wszExe = (LPWSTR)_alloca(dwStrSz * sizeof(*wszExe)); }
|
|
__except(EXCEPTION_STACK_OVERFLOW) { wszExe = NULL; }
|
|
if (wszExe == NULL)
|
|
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
goto done;
|
|
}
|
|
|
|
ZeroMemory(wszExe, dwStrSz * sizeof(*wszExe));
|
|
wszExe[0] = L'\"';
|
|
GetSystemWindowsDirectory(wszExe+1, dwStrSz-1);
|
|
wcscat(wszExe, c_wszHelpCtr);
|
|
wcscat(wszExe, c_wszCmdLine);
|
|
wcscat(wszExe, HelpPageURL);
|
|
|
|
|
|
/*
|
|
* Here, we must start up a Help Center script in a WTS Session
|
|
* It gets a bit sticky, though as we do not have access to the
|
|
* user's desktop (any desktop), and this must appear on only
|
|
* one particular desktop. I am relying on the WTS-User-Token
|
|
* to get the desktop for me.
|
|
*
|
|
* The main component is our call to CreateProcessAsUser.
|
|
* Before we call that we must:
|
|
* Set up Environment as follows:
|
|
* PATH=%SystemPath%
|
|
* WINDIR=SystemRoot%
|
|
* USERNAME=(from WTS)
|
|
* USERDOMAIN=
|
|
* PCHEVENTNAME=EventName
|
|
* PCHSESSIONENUM=UserInfo->dwIndex
|
|
* PCHCONNECTPARMS=ConnectParms
|
|
*/
|
|
TRIVIAL_MSG((L"Launch %ws", wszExe));
|
|
|
|
/* Step on the ENVIRONMENT */
|
|
WCHAR lpNameBfr[256];
|
|
|
|
wnsprintfW(lpNameBfr, ARRAYSIZE(lpNameBfr), L"Global\\%ws%lx_%02d", EVENT_PREFIX, lSessionTag, UserInfo->dwIndex);
|
|
|
|
UserInfo->hEvent = CreateEvent(sa, TRUE, FALSE, lpNameBfr);
|
|
if (!UserInfo->hEvent || ERROR_ALREADY_EXISTS == GetLastError())
|
|
{
|
|
/*
|
|
* If we failed to create this event, it is most likely because one is already
|
|
* in name-space, perhaps an event, or else a mutex... In any case, we will
|
|
* try again, with a more random name. If that fails, then we must bail out.
|
|
*/
|
|
long lRand;
|
|
|
|
if (UserInfo->hEvent)
|
|
CloseHandle(UserInfo->hEvent);
|
|
|
|
RtlGenRandom(&lRand, sizeof(lRand));
|
|
wnsprintfW(lpNameBfr, ARRAYSIZE(lpNameBfr), L"Global\\%lx%lx%lx_%02d",
|
|
lRand, lSessionTag, UserInfo->dwIndex);
|
|
UserInfo->hEvent = CreateEvent(sa, TRUE, FALSE, lpNameBfr);
|
|
|
|
if (!UserInfo->hEvent || ERROR_ALREADY_EXISTS == GetLastError())
|
|
{
|
|
if (UserInfo->hEvent)
|
|
CloseHandle(UserInfo->hEvent);
|
|
UserInfo->hEvent = 0;
|
|
HEINOUS_E_MSG((L"The named event \"%s\" was in use- potential security issue, so Remote Assistance will be denied.", lpNameBfr));
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
SetEnvironmentVariable(szEnvEvent, lpNameBfr);
|
|
|
|
wsprintf(buf1, L"%d", UserInfo->dwIndex);
|
|
SetEnvironmentVariable(szEnvIndex, buf1);
|
|
SetEnvironmentVariable(szEnvParms, ConnectParms);
|
|
SetEnvironmentVariable(szEnvMutex, lpszMutex);
|
|
SetEnvironmentVariable(szEnvUser, pUsername );
|
|
SetEnvironmentVariable(szEnvDomain, pDomainname );
|
|
SetEnvironmentVariable(szEnvExpertBlob, expertHelpBlob);
|
|
SetEnvironmentVariable(szEnvUserBlob, userHelpBlob);
|
|
|
|
if (!CreateEnvironmentBlock(&pEnvBlock, UserInfo->hUserToken, TRUE))
|
|
{
|
|
IMPORTANT_MSG((L"CreateEnvironmentBlock failed in resolver:launchex, err=0x%x", GetLastError()));
|
|
goto done;
|
|
}
|
|
|
|
// initialize our structs
|
|
ZeroMemory(&p_i, sizeof(p_i));
|
|
ZeroMemory(&StartUp, sizeof(StartUp));
|
|
StartUp.cb = sizeof(StartUp);
|
|
StartUp.dwFlags = STARTF_USESHOWWINDOW;
|
|
StartUp.wShowWindow = SW_SHOWNORMAL;
|
|
|
|
result = CreateProcessAsUserW(UserInfo->hUserToken,
|
|
NULL, wszExe,
|
|
NULL, NULL, FALSE,
|
|
NORMAL_PRIORITY_CLASS + CREATE_UNICODE_ENVIRONMENT ,
|
|
pEnvBlock, // Environment block (must use the CREATE_UNICODE_ENVIRONMENT flag)
|
|
NULL, &StartUp, &p_i);
|
|
|
|
if (result)
|
|
{
|
|
// keep a leak from happening, as we never need the hThread...
|
|
CloseHandle(p_i.hThread);
|
|
UserInfo->hProcess = p_i.hProcess;
|
|
UserInfo->hThread = 0;
|
|
UserInfo->dwProcessId = p_i.dwProcessId;
|
|
UserInfo->dwThreadId = p_i.dwThreadId;
|
|
|
|
retval = p_i.hProcess;
|
|
|
|
TRIVIAL_MSG((L"CreateProcessAsUserW started up [%ws]", wszExe));
|
|
}
|
|
else
|
|
{
|
|
IMPORTANT_MSG((L"CreateProcessAsUserW failed, err=0x%x command line=[%ws]", GetLastError(), wszExe));
|
|
result=0;
|
|
}
|
|
done:
|
|
if (!result)
|
|
{
|
|
UserInfo->hProcess = 0;
|
|
UserInfo->hThread = 0;
|
|
UserInfo->dwProcessId = 0;
|
|
UserInfo->dwThreadId = 0;
|
|
if( UserInfo->hEvent != NULL )
|
|
{
|
|
CloseHandle( UserInfo->hEvent );
|
|
|
|
UserInfo->hEvent = 0;
|
|
}
|
|
}
|
|
|
|
// restore any memory we borrowed
|
|
|
|
if (pEnvBlock) DestroyEnvironmentBlock(pEnvBlock);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* CreateSids([in] BSTR pTextSID)
|
|
* Create 3 Security IDs
|
|
*
|
|
* NOTES:
|
|
* Caller must free memory allocated to SIDs on success.
|
|
*
|
|
* RETURN CODES: TRUE if successfull, FALSE if not.
|
|
*
|
|
*************************************************************/
|
|
|
|
BOOL
|
|
CreateSids(
|
|
PSID *BuiltInAdministrators,
|
|
PSID *PowerUsers,
|
|
PSID *AuthenticatedUsers
|
|
)
|
|
{
|
|
//
|
|
// An SID is built from an Identifier Authority and a set of Relative IDs
|
|
// (RIDs). The Authority of interest to us SECURITY_NT_AUTHORITY.
|
|
//
|
|
|
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
|
|
|
//
|
|
// Each RID represents a sub-unit of the authority. Two of the SIDs we
|
|
// want to build, Local Administrators, and Power Users, are in the "built
|
|
// in" domain. The other SID, for Authenticated users, is based directly
|
|
// off of the authority.
|
|
//
|
|
// For examples of other useful SIDs consult the list in
|
|
// \nt\public\sdk\inc\ntseapi.h.
|
|
//
|
|
|
|
if (!AllocateAndInitializeSid(&NtAuthority,
|
|
2, // 2 sub-authorities
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_ADMINS,
|
|
0,0,0,0,0,0,
|
|
BuiltInAdministrators))
|
|
{
|
|
// error
|
|
HEINOUS_E_MSG((L"Could not allocate security credentials for admins."));
|
|
}
|
|
else if (!AllocateAndInitializeSid(&NtAuthority,
|
|
2, // 2 sub-authorities
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_POWER_USERS,
|
|
0,0,0,0,0,0,
|
|
PowerUsers))
|
|
{
|
|
// error
|
|
|
|
HEINOUS_E_MSG((L"Could not allocate security credentials for power users."));
|
|
FreeSid(*BuiltInAdministrators);
|
|
*BuiltInAdministrators = NULL;
|
|
|
|
}
|
|
else if (!AllocateAndInitializeSid(&NtAuthority,
|
|
1, // 1 sub-authority
|
|
SECURITY_AUTHENTICATED_USER_RID,
|
|
0,0,0,0,0,0,0,
|
|
AuthenticatedUsers))
|
|
{
|
|
// error
|
|
HEINOUS_E_MSG((L"Could not allocate security credentials for users."));
|
|
|
|
FreeSid(*BuiltInAdministrators);
|
|
*BuiltInAdministrators = NULL;
|
|
|
|
FreeSid(*PowerUsers);
|
|
*PowerUsers = NULL;
|
|
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*************************************************************
|
|
*
|
|
* CreateSd(void)
|
|
* Creates a SECURITY_DESCRIPTOR with specific DACLs.
|
|
*
|
|
* NOTES:
|
|
* Caller must free the returned buffer if not NULL.
|
|
*
|
|
* RETURN CODES:
|
|
* NULL Failed for some reason
|
|
* PSECURITY_DESCRIPTOR Pointer to a SECURITY_DESCRIPTOR.
|
|
* Must be freed with "LocalFree"
|
|
*
|
|
*************************************************************/
|
|
PSECURITY_DESCRIPTOR CreateSd(PSID pUserSID)
|
|
{
|
|
PSID AuthenticatedUsers;
|
|
PSID BuiltInAdministrators;
|
|
PSID PowerUsers;
|
|
PSECURITY_DESCRIPTOR Sd = NULL;
|
|
ULONG AclSize;
|
|
ACL *Acl;
|
|
|
|
|
|
if (!CreateSids(&BuiltInAdministrators,
|
|
&PowerUsers,
|
|
&AuthenticatedUsers))
|
|
{
|
|
// error
|
|
IMPORTANT_MSG((L"CreateSids failed"));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Calculate the size of and allocate a buffer for the DACL, we need
|
|
// this value independently of the total alloc size for ACL init.
|
|
//
|
|
|
|
//
|
|
// "- sizeof (ULONG)" represents the SidStart field of the
|
|
// ACCESS_ALLOWED_ACE. Since we're adding the entire length of the
|
|
// SID, this field is counted twice.
|
|
//
|
|
|
|
AclSize = sizeof (ACL) +
|
|
(4 * (sizeof (ACCESS_ALLOWED_ACE) - sizeof (ULONG))) +
|
|
GetLengthSid(AuthenticatedUsers) +
|
|
GetLengthSid(BuiltInAdministrators) +
|
|
GetLengthSid(PowerUsers) +
|
|
GetLengthSid(pUserSID);
|
|
|
|
Sd = LocalAlloc(LMEM_FIXED + LMEM_ZEROINIT,
|
|
SECURITY_DESCRIPTOR_MIN_LENGTH + AclSize);
|
|
|
|
if (!Sd)
|
|
{
|
|
IMPORTANT_MSG((L"Cound not allocate 0x%x bytes for Security Descriptor", SECURITY_DESCRIPTOR_MIN_LENGTH + AclSize));
|
|
goto error;
|
|
}
|
|
|
|
Acl = (ACL *)((BYTE *)Sd + SECURITY_DESCRIPTOR_MIN_LENGTH);
|
|
|
|
if (!InitializeAcl(Acl,
|
|
AclSize,
|
|
ACL_REVISION))
|
|
{
|
|
// error
|
|
IMPORTANT_MSG((L"Cound not initialize ACL err=0x%x", GetLastError()));
|
|
goto error;
|
|
}
|
|
TRIVIAL_MSG((L"initialized Successfully"));
|
|
|
|
|
|
if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
STANDARD_RIGHTS_ALL | GENERIC_WRITE,
|
|
pUserSID))
|
|
{
|
|
// Failed to build the ACE granted to OWNER
|
|
// (STANDARD_RIGHTS_ALL) access.
|
|
IMPORTANT_MSG((L"Cound not add owner rights to ACL err=0x%x", GetLastError()));
|
|
goto error;
|
|
}
|
|
|
|
|
|
if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
GENERIC_READ,
|
|
AuthenticatedUsers))
|
|
{
|
|
// Failed to build the ACE granting "Authenticated users"
|
|
// (SYNCHRONIZE | GENERIC_READ) access.
|
|
IMPORTANT_MSG((L"Cound not add user rights to ACL err=0x%x", GetLastError()));
|
|
goto error;
|
|
}
|
|
|
|
if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
PowerUsers))
|
|
{
|
|
// Failed to build the ACE granting "Power users"
|
|
// (SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE) access.
|
|
IMPORTANT_MSG((L"Cound not add power user rights to ACL err=0x%x", GetLastError()));
|
|
goto error;
|
|
}
|
|
|
|
if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
STANDARD_RIGHTS_ALL,
|
|
BuiltInAdministrators))
|
|
{
|
|
// Failed to build the ACE granting "Built-in Administrators"
|
|
// STANDARD_RIGHTS_ALL access.
|
|
IMPORTANT_MSG((L"Cound not add admin rights to ACL err=0x%x", GetLastError()));
|
|
goto error;
|
|
}
|
|
|
|
|
|
if (!InitializeSecurityDescriptor(Sd,SECURITY_DESCRIPTOR_REVISION))
|
|
{
|
|
// error
|
|
IMPORTANT_MSG((L"Cound not initialize SD err=0x%x", GetLastError()));
|
|
goto error;
|
|
}
|
|
|
|
if (!SetSecurityDescriptorDacl(Sd,
|
|
TRUE,
|
|
Acl,
|
|
FALSE))
|
|
{
|
|
// error
|
|
IMPORTANT_MSG((L"SetSecurityDescriptorDacl failed err=0x%x", GetLastError()));
|
|
goto error;
|
|
}
|
|
|
|
FreeSid(AuthenticatedUsers);
|
|
FreeSid(BuiltInAdministrators);
|
|
FreeSid(PowerUsers);
|
|
|
|
// TRIVIAL_MSG((L"CreateSd succeeded."));
|
|
|
|
return Sd;
|
|
|
|
error:
|
|
/* A jump of last resort */
|
|
if (Sd)
|
|
LocalFree(Sd);
|
|
|
|
// error
|
|
if (AuthenticatedUsers)
|
|
FreeSid(AuthenticatedUsers);
|
|
if (BuiltInAdministrators)
|
|
FreeSid(BuiltInAdministrators);
|
|
if (PowerUsers)
|
|
FreeSid(PowerUsers);
|
|
return NULL;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* GetUserSessions(PSID, PWTS_USER_SESSION_INFOW , *DWORD)
|
|
* Returns a table of all the active WTS Sessions for this
|
|
* user, on this server.
|
|
*
|
|
* RETURN CODES:
|
|
*
|
|
* NOTES:
|
|
* Should log NT Events for failures
|
|
*
|
|
*************************************************************/
|
|
DWORD GetUserSessions(PSID pUserSID, PWTS_USER_SESSION_INFO *pUserTbl, DWORD *pEntryCnt, WCHAR *pUsername, WCHAR *pDomainname)
|
|
{
|
|
int i,ii=0;
|
|
DWORD retval=0,
|
|
dwSessions=0,
|
|
dwValidSessions,
|
|
dwOwnedSessions;
|
|
PWTS_SESSION_INFO pSessionInfo=NULL;
|
|
DWORD dwRetSize = 0;
|
|
WINSTATIONINFORMATION WSInfo;
|
|
|
|
/* param validation */
|
|
if (!pUserSID || !pUserTbl || !pEntryCnt)
|
|
{
|
|
IMPORTANT_MSG((L"GetUserSessions parameter violation"));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Initialize */
|
|
|
|
*pUserTbl = NULL;
|
|
|
|
/* Start with WTSEnumerateSessions,
|
|
* narrow it down to only active sessions,
|
|
* then filter further against the logonID
|
|
* then use WinStationQueryInformation to get the logged on users token
|
|
*/
|
|
if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwSessions) ||
|
|
!pSessionInfo || !dwSessions)
|
|
{
|
|
/* if we can't do this, then we must give up */
|
|
IMPORTANT_MSG((L"GetUserSessions parameter violation"));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Narrow down to only active sessions -
|
|
* a quick test
|
|
*/
|
|
for (i=0,dwValidSessions=0;i<(int)dwSessions;i++)
|
|
{
|
|
if (pSessionInfo[i].State == WTSActive)
|
|
dwValidSessions++;
|
|
}
|
|
|
|
/* bail out early if none found */
|
|
if (dwValidSessions == 0)
|
|
{
|
|
/* free the memory we asked for */
|
|
WTSFreeMemory(pSessionInfo);
|
|
|
|
INTERESTING_MSG((L"No active sessions found"));
|
|
return 0;
|
|
}
|
|
|
|
INTERESTING_MSG((L"%d sessions found", dwValidSessions));
|
|
|
|
/*
|
|
* Now see who owns each session. Only one problem- session details kept
|
|
* by WTS have no concept of SIDs- just a user name & domain. Oh well,
|
|
* it is a safe assumption that the two are just as reliable forms of
|
|
* identification. One little caveat- the names can be stored using
|
|
* different case variants. Example: "TomFr" and "tomfr" are equivalent,
|
|
* according to NT domain rules, so I have to use a case-insensitive
|
|
* check here.
|
|
*
|
|
* So, now that I know there is at least one possible WTS Session here,
|
|
* I will compare these IDs to determine if we want to notify this particular
|
|
* session.
|
|
*/
|
|
|
|
/* now look at all the active sessions and compare domain & user names */
|
|
for (i=0,dwOwnedSessions=0;i<(int)dwSessions;i++)
|
|
{
|
|
if (pSessionInfo[i].State == WTSActive)
|
|
{
|
|
DWORD CurSessionId = pSessionInfo[i].SessionId;
|
|
BOOL bLockedState=FALSE;
|
|
|
|
memset( &WSInfo, 0, sizeof(WSInfo) );
|
|
|
|
if (!WinStationQueryInformationW(
|
|
SERVERNAME_CURRENT,
|
|
CurSessionId,
|
|
WinStationInformation,
|
|
&WSInfo,
|
|
sizeof(WSInfo),
|
|
&dwRetSize
|
|
))
|
|
{
|
|
IMPORTANT_MSG((L"WinStationQueryInformation failed err=0x%x", GetLastError()));
|
|
|
|
// SECURITY : if bad user's list is infront of good novice and
|
|
// bad user just happen to logoff at the right time, it will
|
|
// cause code to break out of loop instead of looking further
|
|
// for the intended novice.
|
|
continue;
|
|
}
|
|
|
|
if (StrCmpI(WSInfo.Domain, pDomainname))
|
|
continue;
|
|
|
|
if (StrCmpI(WSInfo.UserName, pUsername))
|
|
continue;
|
|
|
|
if (!WinStationQueryInformationW(
|
|
SERVERNAME_CURRENT,
|
|
CurSessionId,
|
|
WinStationLockedState,
|
|
&bLockedState,
|
|
sizeof(bLockedState),
|
|
&dwRetSize
|
|
))
|
|
{
|
|
IMPORTANT_MSG((L"WinStationQueryInformation failed err=0x%x", GetLastError()));
|
|
|
|
break;
|
|
}
|
|
// if the screen is locked, don't add to the list
|
|
if (bLockedState)
|
|
continue;
|
|
|
|
INTERESTING_MSG((L"WinStaQI[%d]: ConnectState=0x%x bLockedState=0x%x user=%s/%s ", i,WSInfo.ConnectState, bLockedState, WSInfo.Domain,WSInfo.UserName));
|
|
|
|
/*
|
|
* If we got this far, then we know we want this session
|
|
*/
|
|
dwOwnedSessions++;
|
|
|
|
// mark this as a "session of interest"
|
|
pSessionInfo[i].State = (WTS_CONNECTSTATE_CLASS)0x45;
|
|
}
|
|
}
|
|
|
|
/* did we find any sessions? */
|
|
if (dwOwnedSessions == 0)
|
|
{
|
|
TRIVIAL_MSG((L"No matching sessions found"));
|
|
goto none_found;
|
|
}
|
|
|
|
/* Get some memory for our session tables */
|
|
*pUserTbl = (PWTS_USER_SESSION_INFO)LocalAlloc(LMEM_FIXED, dwOwnedSessions * sizeof(WTS_USER_SESSION_INFO));
|
|
|
|
if (!*pUserTbl)
|
|
{
|
|
HEINOUS_E_MSG((L"Could not allocate memory for %d sessions, err=0x%x", dwOwnedSessions, GetLastError()));
|
|
goto none_found;
|
|
}
|
|
|
|
*pEntryCnt = dwOwnedSessions;
|
|
|
|
for (i=0,ii=0;i<(int)dwSessions; i++)
|
|
{
|
|
/*
|
|
* If this is one of our "sessions of interest", get the session ID
|
|
* and User Token.
|
|
*/
|
|
if (pSessionInfo[i].State == (WTS_CONNECTSTATE_CLASS)0x45)
|
|
{
|
|
WINSTATIONUSERTOKEN WsUserToken;
|
|
ULONG ulRet;
|
|
PWTS_USER_SESSION_INFO lpi = &((*pUserTbl)[ii]);
|
|
|
|
WsUserToken.ProcessId = LongToHandle(GetCurrentProcessId());
|
|
WsUserToken.ThreadId = LongToHandle(GetCurrentThreadId());
|
|
|
|
lpi->dwIndex = (DWORD)ii;
|
|
lpi->dwSessionId = pSessionInfo[i].SessionId;
|
|
|
|
if (!WinStationQueryInformationW(WTS_CURRENT_SERVER_HANDLE, pSessionInfo[i].SessionId,
|
|
WinStationUserToken, &WsUserToken, sizeof(WsUserToken), &ulRet))
|
|
{
|
|
goto none_found;
|
|
}
|
|
|
|
lpi->hUserToken = WsUserToken.UserToken;
|
|
ii++;
|
|
}
|
|
}
|
|
|
|
/* Yahoo! we finally built our table. Now we can leave after cleaning up */
|
|
WTSFreeMemory(pSessionInfo);
|
|
|
|
IMPORTANT_MSG((L"GetUserSessions exiting: %d sessions found", *pEntryCnt));
|
|
|
|
return 1;
|
|
|
|
none_found:
|
|
/* free the memory we asked for */
|
|
if (pSessionInfo) WTSFreeMemory(pSessionInfo);
|
|
|
|
// we found no entries
|
|
*pEntryCnt =0;
|
|
|
|
if( *pUserTbl != NULL )
|
|
{
|
|
LocalFree( *pUserTbl );
|
|
*pUserTbl = NULL;
|
|
}
|
|
|
|
IMPORTANT_MSG((L"GetUserSessions exiting: no sessions found"));
|
|
return 1;
|
|
}
|
|
|
|
LPTHREAD_START_ROUTINE getKillProc(void)
|
|
{
|
|
LPTHREAD_START_ROUTINE lpKill = NULL;
|
|
|
|
HMODULE hKernel = LoadLibrary(L"kernel32");
|
|
if (hKernel)
|
|
{
|
|
lpKill = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel, "ExitProcess");
|
|
FreeLibrary(hKernel);
|
|
}
|
|
|
|
return lpKill;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* localKill(WTS_USER_SESSION_INFO *SessInfo, LPTHREAD_START_ROUTINE lpKill)
|
|
* kills the process for us.
|
|
*
|
|
*************************************************************/
|
|
DWORD localKill(WTS_USER_SESSION_INFO *SessInfo, LPTHREAD_START_ROUTINE lpKill, LPSECURITY_ATTRIBUTES lpSA)
|
|
{
|
|
TRIVIAL_MSG((L"Entered localKill(0x%x, 0x%x)", SessInfo, lpKill));
|
|
|
|
if (lpKill)
|
|
{
|
|
HANDLE hKillThrd= CreateRemoteThread(
|
|
SessInfo->hProcess,
|
|
lpSA,
|
|
NULL,
|
|
lpKill,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if (hKillThrd)
|
|
{
|
|
CloseHandle(hKillThrd);
|
|
return 1;
|
|
}
|
|
IMPORTANT_MSG((L"CreateRemoteThread failed. Err: %08x", GetLastError()));
|
|
// the fall-through is by design...
|
|
}
|
|
|
|
if(!TerminateProcess( SessInfo->hProcess, 0 ))
|
|
{
|
|
IMPORTANT_MSG((L"TerminateProcess failed. Err: %08x", GetLastError()));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* getUserName()
|
|
* get the user's name & domain.
|
|
*
|
|
*************************************************************/
|
|
DWORD getUserName(PSID pUserSID, WCHAR **pUsername, WCHAR **pDomainname)
|
|
{
|
|
if (!pUsername || !pDomainname || !pUserSID)
|
|
return 0;
|
|
|
|
DWORD dwUsername=0, dwDomainname=0;
|
|
SID_NAME_USE eUse;
|
|
|
|
TRIVIAL_MSG((L"Entered getUserName"));
|
|
|
|
*pUsername=NULL, *pDomainname=NULL;
|
|
|
|
/* get the size of the buffers we will need */
|
|
if (!LookupAccountSid(NULL, pUserSID, NULL, &dwUsername, NULL, &dwDomainname, &eUse) &&
|
|
(!dwUsername || !dwDomainname))
|
|
{
|
|
IMPORTANT_MSG((L"LookupAccountSid(nulls) failed err=0x%x", GetLastError()));
|
|
return 0;
|
|
}
|
|
|
|
/* now get enough memory for our name & domain strings */
|
|
*pUsername = (WCHAR *)LocalAlloc(LMEM_FIXED, (dwUsername+16) * sizeof(WCHAR));
|
|
*pDomainname = (WCHAR *)LocalAlloc(LMEM_FIXED, (dwDomainname+16) * sizeof(WCHAR));
|
|
|
|
if (!*pUsername || !*pDomainname ||
|
|
!LookupAccountSid(NULL, pUserSID, *pUsername, &dwUsername, *pDomainname, &dwDomainname, &eUse))
|
|
{
|
|
/* if we get here, it is very bad!! */
|
|
DWORD error = GetLastError();
|
|
if (!*pUsername || !*pDomainname)
|
|
IMPORTANT_MSG((L"LocalAlloc failed err=0x%x", error));
|
|
else
|
|
IMPORTANT_MSG((L"LookupAccountSid(ptrs) failed err=0x%x", error));
|
|
|
|
return 0;
|
|
}
|
|
|
|
TRIVIAL_MSG((L"Requested user=[%ws] dom=[%ws]", *pUsername, *pDomainname));
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* ListFind(PSPLASHLIST pSplash, PSID user)
|
|
* returns TRUE if SID is found in our "splash table"
|
|
*
|
|
*************************************************************/
|
|
BOOL ListFind(PSPLASHLIST pSplash, PSID user)
|
|
{
|
|
PSPLASHLIST lpSplash;
|
|
|
|
if (!pSplash)
|
|
{
|
|
IMPORTANT_MSG((L"ListFind: no splash table memory"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!user)
|
|
{
|
|
IMPORTANT_MSG((L"Fatal error: no SID pointer"));
|
|
return FALSE;
|
|
}
|
|
|
|
lpSplash = pSplash;
|
|
|
|
while (lpSplash)
|
|
{
|
|
if (lpSplash->refcount &&
|
|
EqualSid(user, &lpSplash->Sid))
|
|
{
|
|
return TRUE;
|
|
}
|
|
lpSplash = (PSPLASHLIST) lpSplash->next;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* ListInsert(PSPLASHLIST pSplash, int iSplashCnt, PSID user)
|
|
* inserts SID in our "splash table"
|
|
*
|
|
* NOTES:
|
|
* if already there, we increment the refcount for SID
|
|
*
|
|
*************************************************************/
|
|
BOOL ListInsert(PSPLASHLIST pSplash, PSID user)
|
|
{
|
|
PSPLASHLIST lpSplash;
|
|
|
|
if (!pSplash)
|
|
{
|
|
IMPORTANT_MSG((L"Fatal error: no splash table memory"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!user)
|
|
{
|
|
IMPORTANT_MSG((L"Fatal error: no SID pointer"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!IsValidSid(user))
|
|
{
|
|
IMPORTANT_MSG((L"Fatal error: bad SID"));
|
|
return FALSE;
|
|
}
|
|
|
|
// first, look for one already in the list
|
|
lpSplash = pSplash;
|
|
|
|
while (lpSplash)
|
|
{
|
|
if (lpSplash->refcount && EqualSid(user, &lpSplash->Sid))
|
|
{
|
|
TRIVIAL_MSG((L"Recycling SID in ListInsert"));
|
|
lpSplash->refcount++;
|
|
return TRUE;
|
|
}
|
|
lpSplash = (PSPLASHLIST) lpSplash->next;
|
|
}
|
|
|
|
|
|
// since we did not find one, let's add a new one
|
|
lpSplash = pSplash;
|
|
|
|
while (lpSplash->next)
|
|
lpSplash = (PSPLASHLIST) lpSplash->next;
|
|
|
|
int iSidSz = GetLengthSid(user);
|
|
|
|
lpSplash->next = LocalAlloc(LMEM_FIXED, sizeof(SPLASHLIST) + iSidSz - sizeof(SID));
|
|
|
|
if (lpSplash->next)
|
|
{
|
|
lpSplash = (PSPLASHLIST) lpSplash->next;
|
|
|
|
CopySid(iSidSz, &lpSplash->Sid, user);
|
|
lpSplash->refcount=1;
|
|
lpSplash->next = NULL;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
IMPORTANT_MSG((L"Fatal error: no memory available to extend splash tables"));
|
|
}
|
|
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*************************************************************
|
|
*
|
|
* ListDelete(PSPLASHLIST pSplash, PSID user)
|
|
* deletes SID from our "splash table"
|
|
*
|
|
* NOTES:
|
|
* if there, we decrement the refcount for SID
|
|
* when refcount hits zero, SID is removed
|
|
*
|
|
*************************************************************/
|
|
BOOL ListDelete(PSPLASHLIST pSplash, PSID user)
|
|
{
|
|
PSPLASHLIST lpSplash, lpLast;
|
|
|
|
if (!pSplash)
|
|
{
|
|
IMPORTANT_MSG((L"Fatal error: no splash table memory"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!user)
|
|
{
|
|
IMPORTANT_MSG((L"Fatal error: no SID pointer"));
|
|
return FALSE;
|
|
}
|
|
|
|
lpSplash = lpLast = pSplash;
|
|
|
|
while (lpSplash)
|
|
{
|
|
if (lpSplash->refcount &&
|
|
EqualSid(user, &lpSplash->Sid))
|
|
{
|
|
lpSplash->refcount--;
|
|
TRIVIAL_MSG((L"found SID in ListDelete"));
|
|
|
|
if (!lpSplash->refcount)
|
|
{
|
|
// blow away the SID data
|
|
TRIVIAL_MSG((L"deleting SID entry"));
|
|
lpLast->next = lpSplash->next;
|
|
LocalFree(lpSplash);
|
|
}
|
|
return TRUE;
|
|
}
|
|
lpLast = lpSplash;
|
|
lpSplash = (PSPLASHLIST) lpSplash->next;
|
|
}
|
|
|
|
IMPORTANT_MSG((L"Fatal error: attempted to delete non-existant SID from splash table memory"));
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*************************************************************
|
|
*
|
|
* DbgSpew(DbgClass, char *, ...)
|
|
* Sends debug information.
|
|
*
|
|
*************************************************************/
|
|
void DbgSpew(int DbgClass, BSTR lpFormat, va_list ap)
|
|
{
|
|
WCHAR szMessage[2400+3]; // extra space for '\r', '\n' and NULL
|
|
DWORD bufSize = sizeof(szMessage)/sizeof(szMessage[0]) - 3;
|
|
int iLen=0;
|
|
DWORD dwTick;
|
|
int seconds, millisec;
|
|
|
|
memset( szMessage, 0, sizeof(szMessage) );
|
|
|
|
if (gDbgFlag & DBG_MSG_TIME_MSGS)
|
|
{
|
|
dwTick = GetTickCount();
|
|
seconds = (int) dwTick / 1000;
|
|
millisec = (int) dwTick % 1000;
|
|
|
|
_snwprintf( szMessage, bufSize, L"%06d.%03d- ", seconds, millisec);
|
|
iLen = lstrlen(szMessage);
|
|
}
|
|
|
|
if ((DbgClass & 0x0F) >= (gDbgFlag & 0x0F))
|
|
{
|
|
// bufSize does not include three extra characters for L"\r\n\0"
|
|
if( (int)bufSize > iLen )
|
|
{
|
|
_vsnwprintf(szMessage+iLen, bufSize - iLen, lpFormat, ap);
|
|
}
|
|
|
|
wcscat(szMessage, L"\r\n");
|
|
|
|
// should this be sent to the debugger?
|
|
if (DbgClass & DBG_MSG_DEST_DBG)
|
|
OutputDebugStringW(szMessage);
|
|
|
|
// should this go to our log file?
|
|
if (iDbgFileHandle)
|
|
_write(iDbgFileHandle, szMessage, (2*lstrlen(szMessage)));
|
|
}
|
|
|
|
// should this msg go to the Event Logs?
|
|
if (DbgClass & DBG_MSG_DEST_EVENT)
|
|
{
|
|
WORD wType;
|
|
DWORD dwEvent;
|
|
|
|
if (DbgClass & DBG_MSG_CLASS_SECURE)
|
|
{
|
|
if (DbgClass & DBG_MSG_CLASS_ERROR)
|
|
{
|
|
wType = EVENTLOG_AUDIT_FAILURE;
|
|
dwEvent = SESSRSLR_E_SECURE;
|
|
}
|
|
else
|
|
{
|
|
wType = EVENTLOG_AUDIT_SUCCESS;
|
|
dwEvent = SESSRSLR_I_SECURE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (DbgClass & DBG_MSG_CLASS_ERROR)
|
|
{
|
|
wType = EVENTLOG_ERROR_TYPE;
|
|
dwEvent = SESSRSLR_E_GENERAL;
|
|
}
|
|
else
|
|
{
|
|
wType = EVENTLOG_INFORMATION_TYPE;
|
|
dwEvent = SESSRSLR_I_GENERAL;
|
|
}
|
|
}
|
|
|
|
/* write out an NT Event */
|
|
HANDLE hEvent = RegisterEventSource(NULL, MODULE_NAME);
|
|
LPCWSTR ArgsArray[1]={szMessage};
|
|
|
|
if (hEvent)
|
|
{
|
|
ReportEvent(hEvent, wType,
|
|
0,
|
|
dwEvent,
|
|
NULL,
|
|
1,
|
|
0,
|
|
ArgsArray,
|
|
NULL);
|
|
|
|
DeregisterEventSource(hEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrivialSpew(BSTR lpFormat, ...)
|
|
{
|
|
va_list vd;
|
|
va_start(vd, lpFormat);
|
|
DbgSpew(DBG_MSG_TRIVIAL+DBG_MSG_DEST_DBG, lpFormat, vd);
|
|
va_end(vd);
|
|
}
|
|
|
|
void InterestingSpew(BSTR lpFormat, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, lpFormat);
|
|
DbgSpew(DBG_MSG_INTERESTING+DBG_MSG_DEST_DBG, lpFormat, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void ImportantSpew(BSTR lpFormat, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, lpFormat);
|
|
DbgSpew(DBG_MSG_IMPORTANT+DBG_MSG_DEST_DBG+DBG_MSG_DEST_FILE, lpFormat, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void HeinousESpew(BSTR lpFormat, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, lpFormat);
|
|
DbgSpew(DBG_MSG_HEINOUS+DBG_MSG_DEST_DBG+DBG_MSG_DEST_FILE+DBG_MSG_DEST_EVENT+DBG_MSG_CLASS_ERROR, lpFormat, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void HeinousISpew(BSTR lpFormat, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, lpFormat);
|
|
DbgSpew(DBG_MSG_HEINOUS+DBG_MSG_DEST_DBG+DBG_MSG_DEST_FILE+DBG_MSG_DEST_EVENT, lpFormat, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
// Blob format: propertylength;propertyName=value.
|
|
BOOL GetPropertyValueFromBlob(BSTR bstrHelpBlob, WCHAR * pName, WCHAR** ppValue)
|
|
{
|
|
WCHAR *p1, *p2, *pEnd;
|
|
BOOL bRet = FALSE;
|
|
LONG lTotal =0;
|
|
size_t lProp = 0;
|
|
size_t iNameLen;
|
|
|
|
if (!bstrHelpBlob || *bstrHelpBlob==L'\0' || !pName || *pName ==L'\0'|| !ppValue)
|
|
return FALSE;
|
|
|
|
iNameLen = wcslen(pName);
|
|
|
|
pEnd = bstrHelpBlob + wcslen(bstrHelpBlob);
|
|
p1 = p2 = bstrHelpBlob;
|
|
|
|
while (1)
|
|
{
|
|
// get property length
|
|
while (*p2 != L';' && *p2 != L'\0' && iswdigit(*p2) ) p2++;
|
|
if (*p2 != L';')
|
|
goto done;
|
|
|
|
*p2 = L'\0'; // set it to get length
|
|
lProp = _wtol(p1);
|
|
*p2 = L';'; // revert it back.
|
|
|
|
// security fix: In the case where we get a malformed header for
|
|
// the HelpBlob - exit
|
|
if (lProp <= iNameLen)
|
|
goto done;
|
|
|
|
// get property string
|
|
p1 = ++p2;
|
|
|
|
while (*p2 != L'=' && *p2 != L'\0' && p2 < p1+lProp) p2++;
|
|
if (*p2 != L'=')
|
|
goto done;
|
|
|
|
if (wcsncmp(p1, pName, iNameLen) == 0)
|
|
{
|
|
if (lProp == iNameLen+1) // A=B= case (no value)
|
|
goto done;
|
|
|
|
*ppValue = (WCHAR*)LocalAlloc(LMEM_FIXED, sizeof(WCHAR) * (lProp-iNameLen));
|
|
if (*ppValue == NULL)
|
|
goto done;
|
|
|
|
wcsncpy(*ppValue, p2+1, lProp-iNameLen-1);
|
|
(*ppValue)[lProp-iNameLen-1]=L'\0';
|
|
bRet = TRUE;
|
|
break;
|
|
}
|
|
|
|
// check next property
|
|
p2 = p1 = p1 + lProp;
|
|
if (p2 > pEnd)
|
|
break;
|
|
}
|
|
|
|
done:
|
|
return bRet;
|
|
}
|