summaryrefslogtreecommitdiff
path: root/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp')
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp666
1 files changed, 506 insertions, 160 deletions
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp
index a23128a5..e9191a10 100644
--- a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp
@@ -4,7 +4,7 @@
*/
/*
- * Copyright (C) 2009-2010 Oracle Corporation
+ * Copyright (C) 2009-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
@@ -24,12 +24,13 @@
# define _WIN32_WINNT 0x0502 /* CachedRemoteInteractive in recent SDKs. */
#endif
#include <Windows.h>
-#include <wtsapi32.h> /* For WTS* calls. */
-#include <psapi.h> /* EnumProcesses. */
-#include <Ntsecapi.h> /* Needed for process security information. */
+#include <wtsapi32.h> /* For WTS* calls. */
+#include <psapi.h> /* EnumProcesses. */
+#include <Ntsecapi.h> /* Needed for process security information. */
#include <iprt/assert.h>
#include <iprt/ldr.h>
+#include <iprt/localipc.h>
#include <iprt/mem.h>
#include <iprt/thread.h>
#include <iprt/string.h>
@@ -39,38 +40,47 @@
#include <VBox/VBoxGuestLib.h>
#include "VBoxServiceInternal.h"
#include "VBoxServiceUtils.h"
+#include "VBoxServiceVMInfo.h"
+#include "../../WINNT/VBoxTray/VBoxTrayMsg.h" /* For IPC. */
+static uint32_t s_uDebugGuestPropClientID = 0;
+static uint32_t s_uDebugIter = 0;
+/** Whether to skip the logged-in user detection over RDP or not.
+ * See notes in this section why we might want to skip this. */
+static bool s_fSkipRDPDetection = false;
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/** Structure for storing the looked up user information. */
-typedef struct
+typedef struct VBOXSERVICEVMINFOUSER
{
WCHAR wszUser[_MAX_PATH];
WCHAR wszAuthenticationPackage[_MAX_PATH];
WCHAR wszLogonDomain[_MAX_PATH];
/** Number of assigned user processes. */
ULONG ulNumProcs;
- /** Last (highest) session number. This
+ /** Last (highest) session ID. This
* is needed for distinguishing old session
* process counts from new (current) session
* ones. */
- ULONG ulSession;
+ ULONG ulLastSession;
} VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER;
/** Structure for the file information lookup. */
-typedef struct
+typedef struct VBOXSERVICEVMINFOFILE
{
char *pszFilePath;
char *pszFileName;
} VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE;
/** Structure for process information lookup. */
-typedef struct
+typedef struct VBOXSERVICEVMINFOPROC
{
/** The PID. */
DWORD id;
+ /** The SID. */
+ PSID pSid;
/** The LUID. */
LUID luid;
/** Interactive process. */
@@ -84,7 +94,8 @@ typedef struct
uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs);
bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession);
int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount);
-void VBoxServiceVMInfoWinProcessesFree(PVBOXSERVICEVMINFOPROC paProcs);
+void VBoxServiceVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs);
+int vboxServiceVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain);
typedef BOOL WINAPI FNQUERYFULLPROCESSIMAGENAME(HANDLE, DWORD, LPTSTR, PDWORD);
typedef FNQUERYFULLPROCESSIMAGENAME *PFNQUERYFULLPROCESSIMAGENAME;
@@ -92,6 +103,28 @@ typedef FNQUERYFULLPROCESSIMAGENAME *PFNQUERYFULLPROCESSIMAGENAME;
#ifndef TARGET_NT4
+static bool vboxServiceVMInfoSession0Separation(void)
+{
+ /** @todo Only do this once. Later. */
+ OSVERSIONINFOEX OSInfoEx;
+ RT_ZERO(OSInfoEx);
+ OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ if ( !GetVersionEx((LPOSVERSIONINFO) &OSInfoEx)
+ || OSInfoEx.dwPlatformId != VER_PLATFORM_WIN32_NT)
+ {
+ /* Platform other than NT (e.g. Win9x) not supported. */
+ return false;
+ }
+
+ if ( OSInfoEx.dwMajorVersion >= 6
+ && OSInfoEx.dwMinorVersion >= 0)
+ {
+ return true;
+ }
+
+ return false;
+}
+
/**
* Retrieves the module name of a given process.
*
@@ -107,6 +140,7 @@ static int VBoxServiceVMInfoWinProcessesGetModuleName(PVBOXSERVICEVMINFOPROC con
AssertPtrReturn(pszName, VERR_INVALID_POINTER);
AssertReturn(cbName, VERR_INVALID_PARAMETER);
+ /** @todo Only do this once. Later. */
OSVERSIONINFOEX OSInfoEx;
RT_ZERO(OSInfoEx);
OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
@@ -142,20 +176,19 @@ static int VBoxServiceVMInfoWinProcessesGetModuleName(PVBOXSERVICEVMINFOPROC con
{
/* Loading the module and getting the symbol for each and every process is expensive
* -- since this function (at the moment) only is used for debugging purposes it's okay. */
- RTLDRMOD hMod;
- rc = RTLdrLoad("kernel32.dll", &hMod);
- if (RT_SUCCESS(rc))
+ PFNQUERYFULLPROCESSIMAGENAME pfnQueryFullProcessImageName;
+ pfnQueryFullProcessImageName = (PFNQUERYFULLPROCESSIMAGENAME)
+ RTLdrGetSystemSymbol("kernel32.dll", "QueryFullProcessImageNameA");
+ /** @todo r=bird: WTF don't we query the UNICODE name? */
+ if (pfnQueryFullProcessImageName)
{
- PFNQUERYFULLPROCESSIMAGENAME pfnQueryFullProcessImageName;
- rc = RTLdrGetSymbol(hMod, "QueryFullProcessImageNameA", (void **)&pfnQueryFullProcessImageName);
- if (RT_SUCCESS(rc))
- {
- DWORD dwLen = cbName / sizeof(TCHAR);
- if (!pfnQueryFullProcessImageName(h, 0 /*PROCESS_NAME_NATIVE*/, pszName, &dwLen))
- rc = VERR_ACCESS_DENIED;
- }
-
- RTLdrClose(hMod);
+ /** @todo r=bird: Completely bogus use of TCHAR.
+ * !!ALL USE OF TCHAR IS HEREWITH BANNED IN ALL VBOX SOURCES!!
+ * We use WCHAR when talking to windows, everything else is WRONG. (We don't
+ * want Chinese MBCS being treated as UTF-8.) */
+ DWORD dwLen = cbName / sizeof(TCHAR);
+ if (!pfnQueryFullProcessImageName(h, 0 /*PROCESS_NAME_NATIVE*/, pszName, &dwLen))
+ rc = VERR_ACCESS_DENIED;
}
}
else
@@ -211,10 +244,13 @@ static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pPro
case TokenGroups:
dwTokenInfoSize = 0;
- /* Allocating will follow in a second step. */
+ /* Allocation will follow in a second step. */
break;
- /** @todo Implement more token classes here. */
+ case TokenUser:
+ dwTokenInfoSize = 0;
+ /* Allocation will follow in a second step. */
+ break;
default:
VBoxServiceError("Token class not implemented: %ld", tkClass);
@@ -242,6 +278,14 @@ static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pPro
dwTokenInfoSize = dwRetLength;
break;
+ case TokenUser:
+ pvTokenInfo = (PTOKEN_USER)HeapAlloc(GetProcessHeap(),
+ HEAP_ZERO_MEMORY, dwRetLength);
+ if (!pvTokenInfo)
+ dwErr = GetLastError();
+ dwTokenInfoSize = dwRetLength;
+ break;
+
default:
AssertMsgFailed(("Re-allocating of token information for token class not implemented\n"));
break;
@@ -264,6 +308,7 @@ static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pPro
case TokenStatistics:
{
PTOKEN_STATISTICS pStats = (PTOKEN_STATISTICS)pvTokenInfo;
+ AssertPtr(pStats);
memcpy(&pProc->luid, &pStats->AuthenticationId, sizeof(LUID));
/** @todo Add more information of TOKEN_STATISTICS as needed. */
break;
@@ -309,6 +354,42 @@ static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pPro
break;
}
+ case TokenUser:
+ {
+ PTOKEN_USER pUser = (PTOKEN_USER)pvTokenInfo;
+ AssertPtr(pUser);
+
+ DWORD dwLength = GetLengthSid(pUser->User.Sid);
+ Assert(dwLength);
+ if (dwLength)
+ {
+ pProc->pSid = (PSID)HeapAlloc(GetProcessHeap(),
+ HEAP_ZERO_MEMORY, dwLength);
+ AssertPtr(pProc->pSid);
+ if (CopySid(dwLength, pProc->pSid, pUser->User.Sid))
+ {
+ if (!IsValidSid(pProc->pSid))
+ dwErr = ERROR_INVALID_NAME;
+ }
+ else
+ dwErr = GetLastError();
+ }
+ else
+ dwErr = ERROR_NO_DATA;
+
+ if (dwErr != ERROR_SUCCESS)
+ {
+ VBoxServiceError("Error retrieving SID of process PID=%ld: %ld\n",
+ pProc->id, dwErr);
+ if (pProc->pSid)
+ {
+ HeapFree(GetProcessHeap(), 0 /* Flags */, pProc->pSid);
+ pProc->pSid = NULL;
+ }
+ }
+ break;
+ }
+
default:
AssertMsgFailed(("Unhandled token information class\n"));
break;
@@ -383,6 +464,7 @@ int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDW
break;
}
} while (cProcesses <= _32K); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */
+
if (RT_SUCCESS(rc))
{
/*
@@ -396,23 +478,22 @@ int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDW
for (DWORD i = 0; i < cProcesses; i++)
{
paProcs[i].id = paPID[i];
- rc = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups);
- if (RT_FAILURE(rc))
- {
- /* Because some processes cannot be opened/parsed on
- Windows, we should not consider to be this an error here. */
- rc = VINF_SUCCESS;
- }
- else
- {
- rc = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics);
- if (RT_FAILURE(rc))
- {
- /* Because some processes cannot be opened/parsed on
- Windows, we should not consider to be this an error here. */
- rc = VINF_SUCCESS;
- }
- }
+ paProcs[i].pSid = NULL;
+
+ int rc2 = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenUser);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VBoxServiceError("Get token class \"user\" for process %ld failed, rc=%Rrc\n",
+ paProcs[i].id, rc2);
+
+ rc2 = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VBoxServiceError("Get token class \"groups\" for process %ld failed, rc=%Rrc\n",
+ paProcs[i].id, rc2);
+
+ rc2 = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VBoxServiceError("Get token class \"statistics\" for process %ld failed, rc=%Rrc\n",
+ paProcs[i].id, rc2);
}
/* Save number of processes */
@@ -422,7 +503,7 @@ int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDW
*ppaProcs = paProcs;
}
else
- RTMemFree(paProcs);
+ VBoxServiceVMInfoWinProcessesFree(cProcesses, paProcs);
}
else
rc = VERR_NO_MEMORY;
@@ -438,8 +519,17 @@ int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDW
*
* @param paProcs What
*/
-void VBoxServiceVMInfoWinProcessesFree(PVBOXSERVICEVMINFOPROC paProcs)
+void VBoxServiceVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs)
{
+ for (DWORD i = 0; i < cProcs; i++)
+ {
+ if (paProcs[i].pSid)
+ {
+ HeapFree(GetProcessHeap(), 0 /* Flags */, paProcs[i].pSid);
+ paProcs[i].pSid = NULL;
+ }
+
+ }
RTMemFree(paProcs);
}
@@ -447,14 +537,14 @@ void VBoxServiceVMInfoWinProcessesFree(PVBOXSERVICEVMINFOPROC paProcs)
* Determines whether the specified session has processes on the system.
*
* @returns Number of processes found for a specified session.
- * @param pSession The session.
+ * @param pSession The current user's SID.
* @param paProcs The process snapshot.
* @param cProcs The number of processes in the snaphot.
* @param puSession Looked up session number. Optional.
*/
uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession,
PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs,
- PULONG puSession)
+ PULONG puTerminalSession)
{
if (!pSession)
{
@@ -466,10 +556,20 @@ uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession,
NTSTATUS rcNt = LsaGetLogonSessionData(pSession, &pSessionData);
if (rcNt != STATUS_SUCCESS)
{
- VBoxServiceError("Could not get logon session data! rcNt=%#x", rcNt);
+ VBoxServiceError("Could not get logon session data! rcNt=%#x\n", rcNt);
return 0;
}
+ if (!IsValidSid(pSessionData->Sid))
+ {
+ VBoxServiceError("User SID=%p is not valid\n", pSessionData->Sid);
+ if (pSessionData)
+ LsaFreeReturnBuffer(pSessionData);
+ return 0;
+ }
+
+ int rc = VINF_SUCCESS;
+
/*
* Even if a user seems to be logged in, it could be a stale/orphaned logon
* session. So check if we have some processes bound to it by comparing the
@@ -478,40 +578,35 @@ uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession,
uint32_t cNumProcs = 0;
for (DWORD i = 0; i < cProcs; i++)
{
- VBoxServiceVerbose(4, "PID=%ld: (Interactive: %RTbool) %ld:%ld <-> %ld:%ld\n",
- paProcs[i].id, paProcs[i].fInteractive,
- paProcs[i].luid.HighPart, paProcs[i].luid.LowPart,
- pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart);
if (g_cVerbosity)
{
TCHAR szModule[_1K];
- int rc2 = VBoxServiceVMInfoWinProcessesGetModuleName(&paProcs[i], szModule, sizeof(szModule));
- if (RT_SUCCESS(rc2))
+ rc = VBoxServiceVMInfoWinProcessesGetModuleName(&paProcs[i], szModule, sizeof(szModule));
+ if (RT_SUCCESS(rc))
VBoxServiceVerbose(4, "PID=%ld: %s\n",
paProcs[i].id, szModule);
}
- if ( paProcs[i].fInteractive
- && ( paProcs[i].luid.HighPart == pSessionData->LogonId.HighPart
- && paProcs[i].luid.LowPart == pSessionData->LogonId.LowPart))
+ PSID pProcSID = paProcs[i].pSid;
+ if ( RT_SUCCESS(rc)
+ && pProcSID
+ && IsValidSid(pProcSID))
{
- cNumProcs++;
- if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */
- break;
+ if ( EqualSid(pSessionData->Sid, paProcs[i].pSid)
+ && paProcs[i].fInteractive)
+ {
+ cNumProcs++;
+ if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */
+ break;
+ }
}
}
- if (g_cVerbosity)
- VBoxServiceVerbose(3, "Session %u has %u processes total\n",
- pSessionData->Session, cNumProcs);
- else
- VBoxServiceVerbose(3, "Session %u has at least one process\n",
- pSessionData->Session);
-
- if (puSession)
- *puSession = pSessionData->Session;
+ if (puTerminalSession)
+ *puTerminalSession = pSessionData->Session;
LsaFreeReturnBuffer(pSessionData);
+
return cNumProcs;
}
@@ -586,12 +681,25 @@ bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSes
return false;
}
- VBoxServiceVerbose(3, "Session data: Name=%ls, Session=%u, LogonID=%ld,%ld, LogonType=%ld\n",
+ VBoxServiceVerbose(3, "Session data: Name=%ls, SessionID=%RU32, LogonID=%ld,%ld, LogonType=%ld\n",
pSessionData->UserName.Buffer,
pSessionData->Session,
pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart,
pSessionData->LogonType);
+ if (vboxServiceVMInfoSession0Separation())
+ {
+ /* Starting at Windows Vista user sessions begin with session 1, so
+ * ignore (stale) session 0 users. */
+ if ( pSessionData->Session == 0
+ /* Also check the logon time. */
+ || pSessionData->LogonTime.QuadPart == 0)
+ {
+ LsaFreeReturnBuffer(pSessionData);
+ return false;
+ }
+ }
+
/*
* Only handle users which can login interactively or logged in
* remotely over native RDP.
@@ -651,55 +759,90 @@ bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSes
pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage,
pUserInfo->wszLogonDomain);
- /* Detect RDP sessions as well. */
- LPTSTR pBuffer = NULL;
- DWORD cbRet = 0;
- int iState = -1;
- if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
- pSessionData->Session,
- WTSConnectState,
- &pBuffer,
- &cbRet))
+ /**
+ * Note: On certain Windows OSes WTSQuerySessionInformation leaks memory when used
+ * under a heavy stress situation. There are hotfixes available from Microsoft.
+ *
+ * See: http://support.microsoft.com/kb/970910
+ */
+ if (!s_fSkipRDPDetection)
{
- if (cbRet)
- iState = *pBuffer;
- VBoxServiceVerbose(3, "Account User=%ls, WTSConnectState=%d (%ld)\n",
- pUserInfo->wszUser, iState, cbRet);
- if ( iState == WTSActive /* User logged on to WinStation. */
- || iState == WTSShadow /* Shadowing another WinStation. */
- || iState == WTSDisconnected) /* WinStation logged on without client. */
+ /** @todo Only do this once. Later. */
+ OSVERSIONINFOEX OSInfoEx;
+ RT_ZERO(OSInfoEx);
+ OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+ /* Skip RDP detection on non-NT systems. */
+ if ( !GetVersionEx((LPOSVERSIONINFO) &OSInfoEx)
+ || OSInfoEx.dwPlatformId != VER_PLATFORM_WIN32_NT)
{
- /** @todo On Vista and W2K, always "old" user name are still
- * there. Filter out the old one! */
- VBoxServiceVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n",
- pUserInfo->wszUser, iState);
- fFoundUser = true;
+ s_fSkipRDPDetection = true;
}
- if (pBuffer)
- WTSFreeMemory(pBuffer);
+ /* Skip RDP detection on Windows 2000.
+ * For Windows 2000 however we don't have any hotfixes, so just skip the
+ * RDP detection in any case. */
+ if ( OSInfoEx.dwMajorVersion == 5
+ && OSInfoEx.dwMinorVersion == 0)
+ {
+ s_fSkipRDPDetection = true;
+ }
+
+ if (s_fSkipRDPDetection)
+ VBoxServiceVerbose(0, "Detection of logged-in users via RDP is disabled\n");
}
- else
+
+ if (!s_fSkipRDPDetection)
{
- DWORD dwLastErr = GetLastError();
- switch (dwLastErr)
+ /* Detect RDP sessions as well. */
+ LPTSTR pBuffer = NULL;
+ DWORD cbRet = 0;
+ int iState = -1;
+ if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
+ pSessionData->Session,
+ WTSConnectState,
+ &pBuffer,
+ &cbRet))
{
- /*
- * Terminal services don't run (for example in W2K,
- * nothing to worry about ...). ... or is on the Vista
- * fast user switching page!
- */
- case ERROR_CTX_WINSTATION_NOT_FOUND:
- VBoxServiceVerbose(3, "No WinStation found for user=%ls\n",
- pUserInfo->wszUser);
- break;
-
- default:
- VBoxServiceVerbose(3, "Cannot query WTS connection state for user=%ls, error=%ld\n",
- pUserInfo->wszUser, dwLastErr);
- break;
+ if (cbRet)
+ iState = *pBuffer;
+ VBoxServiceVerbose(3, "Account User=%ls, WTSConnectState=%d (%ld)\n",
+ pUserInfo->wszUser, iState, cbRet);
+ if ( iState == WTSActive /* User logged on to WinStation. */
+ || iState == WTSShadow /* Shadowing another WinStation. */
+ || iState == WTSDisconnected) /* WinStation logged on without client. */
+ {
+ /** @todo On Vista and W2K, always "old" user name are still
+ * there. Filter out the old one! */
+ VBoxServiceVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n",
+ pUserInfo->wszUser, iState);
+ fFoundUser = true;
+ }
+ if (pBuffer)
+ WTSFreeMemory(pBuffer);
}
+ else
+ {
+ DWORD dwLastErr = GetLastError();
+ switch (dwLastErr)
+ {
+ /*
+ * Terminal services don't run (for example in W2K,
+ * nothing to worry about ...). ... or is on the Vista
+ * fast user switching page!
+ */
+ case ERROR_CTX_WINSTATION_NOT_FOUND:
+ VBoxServiceVerbose(3, "No WinStation found for user=%ls\n",
+ pUserInfo->wszUser);
+ break;
+
+ default:
+ VBoxServiceVerbose(3, "Cannot query WTS connection state for user=%ls, error=%ld\n",
+ pUserInfo->wszUser, dwLastErr);
+ break;
+ }
- fFoundUser = true;
+ fFoundUser = true;
+ }
}
}
else
@@ -711,27 +854,163 @@ bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSes
pUserInfo->wszUser, fFoundUser ? "is" : "is not");
}
+ if (fFoundUser)
+ pUserInfo->ulLastSession = pSessionData->Session;
+
LsaFreeReturnBuffer(pSessionData);
return fFoundUser;
}
+static int vboxServiceVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache,
+ const char *pszUser, const char *pszDomain)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
+ /* pszDomain is optional. */
+
+ int rc = VINF_SUCCESS;
+
+ char szPipeName[255];
+ if (RTStrPrintf(szPipeName, sizeof(szPipeName), "%s%s",
+ VBOXTRAY_IPC_PIPE_PREFIX, pszUser))
+ {
+ bool fReportToHost = false;
+ VBoxGuestUserState userState = VBoxGuestUserState_Unknown;
+
+ RTLOCALIPCSESSION hSession;
+ rc = RTLocalIpcSessionConnect(&hSession, szPipeName, 0 /* Flags */);
+ if (RT_SUCCESS(rc))
+ {
+ VBOXTRAYIPCHEADER ipcHdr = { VBOXTRAY_IPC_HDR_MAGIC, 0 /* Header version */,
+ VBOXTRAYIPCMSGTYPE_USERLASTINPUT, 0 /* No msg */ };
+
+ rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr));
+
+ VBOXTRAYIPCRES_USERLASTINPUT ipcRes;
+ if (RT_SUCCESS(rc))
+ rc = RTLocalIpcSessionRead(hSession, &ipcRes, sizeof(ipcRes),
+ NULL /* Exact read */);
+ if ( RT_SUCCESS(rc)
+ /* If uLastInput is set to UINT32_MAX VBoxTray was not able to retrieve the
+ * user's last input time. This might happen when running on Windows NT4 or older. */
+ && ipcRes.uLastInput != UINT32_MAX)
+ {
+ userState = (ipcRes.uLastInput * 1000) < g_uVMInfoUserIdleThresholdMS
+ ? VBoxGuestUserState_InUse
+ : VBoxGuestUserState_Idle;
+
+ rc = vboxServiceUserUpdateF(pCache, pszUser, pszDomain, "UsageState",
+ userState == VBoxGuestUserState_InUse
+ ? "InUse" : "Idle");
+
+ /*
+ * Note: vboxServiceUserUpdateF can return VINF_NO_CHANGE in case there wasn't anything
+ * to update. So only report the user's status to host when we really got something
+ * new.
+ */
+ fReportToHost = rc == VINF_SUCCESS;
+ VBoxServiceVerbose(4, "User \"%s\" (domain \"%s\") is idle for %RU32, fReportToHost=%RTbool\n",
+ pszUser, pszDomain ? pszDomain : "<None>", ipcRes.uLastInput, fReportToHost);
+
+#if 0 /* Do we want to write the idle time as well? */
+ /* Also write the user's current idle time, if there is any. */
+ if (userState == VBoxGuestUserState_Idle)
+ rc = vboxServiceUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs",
+ "%RU32", ipcRes.uLastInputMs);
+ else
+ rc = vboxServiceUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs",
+ NULL /* Delete property */);
+
+ if (RT_SUCCESS(rc))
+#endif
+ }
+#ifdef DEBUG
+ else if (ipcRes.uLastInput == UINT32_MAX)
+ VBoxServiceVerbose(4, "Last input for user \"%s\" is not supported, skipping\n",
+ pszUser, rc);
+
+ VBoxServiceVerbose(4, "Getting last input for user \"%s\" ended with rc=%Rrc\n",
+ pszUser, rc);
+#endif
+ int rc2 = RTLocalIpcSessionClose(hSession);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ else
+ {
+ switch (rc)
+ {
+ case VERR_FILE_NOT_FOUND:
+ {
+ /* No VBoxTray (or too old version which does not support IPC) running
+ for the given user. Not much we can do then. */
+ VBoxServiceVerbose(4, "VBoxTray for user \"%s\" not running (anymore), no last input available\n",
+ pszUser);
+
+ /* Overwrite rc from above. */
+ rc = vboxServiceUserUpdateF(pCache, pszUser, pszDomain,
+ "UsageState", "Idle");
+
+ fReportToHost = rc == VINF_SUCCESS;
+ if (fReportToHost)
+ userState = VBoxGuestUserState_Idle;
+ break;
+ }
+
+ default:
+ VBoxServiceError("Error querying last input for user \"%s\", rc=%Rrc\n",
+ pszUser, rc);
+ break;
+ }
+ }
+
+ if (fReportToHost)
+ {
+ Assert(userState != VBoxGuestUserState_Unknown);
+ int rc2 = VbglR3GuestUserReportState(pszUser, pszDomain, userState,
+ NULL /* No details */, 0);
+ if (RT_FAILURE(rc2))
+ VBoxServiceError("Error reporting usage state %ld for user \"%s\" to host, rc=%Rrc\n",
+ userState, pszUser, rc2);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ return rc;
+}
+
+
/**
* Retrieves the currently logged in users and stores their names along with the
* user count.
*
* @returns VBox status code.
+ * @param pCachce Property cache to use for storing some of the lookup
+ * data in between calls.
* @param ppszUserList Where to store the user list (separated by commas).
* Must be freed with RTStrFree().
* @param pcUsersInList Where to store the number of users in the list.
*/
-int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList)
+int VBoxServiceVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache,
+ char **ppszUserList, uint32_t *pcUsersInList)
{
- PLUID paSessions = NULL;
- ULONG cSessions = 0;
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszUserList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcUsersInList, VERR_INVALID_POINTER);
+
+ int rc2 = VbglR3GuestPropConnect(&s_uDebugGuestPropClientID);
+ AssertRC(rc2);
+
+ char *pszUserList = NULL;
+ uint32_t cUsersInList = 0;
/* This function can report stale or orphaned interactive logon sessions
of already logged off users (especially in Windows 2000). */
+ PLUID paSessions = NULL;
+ ULONG cSessions = 0;
NTSTATUS rcNt = LsaEnumerateLogonSessions(&cSessions, &paSessions);
if (rcNt != STATUS_SUCCESS)
{
@@ -750,10 +1029,13 @@ int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList)
break;
default:
- VBoxServiceError("LsaEnumerate failed with error %u\n", ulError);
+ VBoxServiceError("LsaEnumerate failed with error %RU32\n", ulError);
break;
}
+ if (paSessions)
+ LsaFreeReturnBuffer(paSessions);
+
return RTErrConvertFromWin32(ulError);
}
VBoxServiceVerbose(3, "Found %ld sessions\n", cSessions);
@@ -777,56 +1059,73 @@ int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList)
else
{
ULONG cUniqueUsers = 0;
+
+ /**
+ * Note: The cSessions loop variable does *not* correlate with
+ * the Windows session ID!
+ */
for (ULONG i = 0; i < cSessions; i++)
{
- VBoxServiceVerbose(3, "Handling session %u\n", i);
+ VBoxServiceVerbose(3, "Handling session %RU32 (of %RU32)\n", i + 1, cSessions);
- VBOXSERVICEVMINFOUSER UserInfo;
- if (VBoxServiceVMInfoWinIsLoggedIn(&UserInfo, &paSessions[i]))
+ VBOXSERVICEVMINFOUSER userSession;
+ if (VBoxServiceVMInfoWinIsLoggedIn(&userSession, &paSessions[i]))
{
- VBoxServiceVerbose(4, "Handling user=%ls, domain=%ls, package=%ls\n",
- UserInfo.wszUser, UserInfo.wszLogonDomain, UserInfo.wszAuthenticationPackage);
+ VBoxServiceVerbose(4, "Handling user=%ls, domain=%ls, package=%ls, session=%RU32\n",
+ userSession.wszUser, userSession.wszLogonDomain, userSession.wszAuthenticationPackage,
+ userSession.ulLastSession);
/* Retrieve assigned processes of current session. */
- ULONG ulSession;
- uint32_t cSessionProcs = VBoxServiceVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs, &ulSession);
+ uint32_t cCurSessionProcs = VBoxServiceVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs,
+ NULL /* Terminal session ID */);
/* Don't return here when current session does not have assigned processes
* anymore -- in that case we have to search through the unique users list below
* and see if got a stale user/session entry. */
+ if (g_cVerbosity > 3)
+ {
+ char szDebugSessionPath[255]; RTStrPrintf(szDebugSessionPath, sizeof(szDebugSessionPath), "/VirtualBox/GuestInfo/Debug/LSA/Session/%RU32",
+ userSession.ulLastSession);
+ VBoxServiceWritePropF(s_uDebugGuestPropClientID, szDebugSessionPath,
+ "#%RU32: cSessionProcs=%RU32 (of %RU32 procs total)", s_uDebugIter, cCurSessionProcs, cProcs);
+ }
+
bool fFoundUser = false;
- for (ULONG i = 0; i < cUniqueUsers; i++)
+ for (ULONG a = 0; a < cUniqueUsers; a++)
{
- if ( !wcscmp(UserInfo.wszUser, pUserInfo[i].wszUser)
- && !wcscmp(UserInfo.wszLogonDomain, pUserInfo[i].wszLogonDomain)
- && !wcscmp(UserInfo.wszAuthenticationPackage, pUserInfo[i].wszAuthenticationPackage)
- && cSessionProcs)
+ PVBOXSERVICEVMINFOUSER pCurUser = &pUserInfo[a];
+ AssertPtr(pCurUser);
+
+ if ( !wcscmp(userSession.wszUser, pCurUser->wszUser)
+ && !wcscmp(userSession.wszLogonDomain, pCurUser->wszLogonDomain)
+ && !wcscmp(userSession.wszAuthenticationPackage, pCurUser->wszAuthenticationPackage))
{
/*
* Only respect the highest session for the current user.
*/
- if (ulSession > pUserInfo[i].ulSession)
+ if (userSession.ulLastSession > pCurUser->ulLastSession)
{
- VBoxServiceVerbose(4, "Updating user=%ls to %u processes (last session: %u)\n",
- UserInfo.wszUser, cSessionProcs, ulSession);
+ VBoxServiceVerbose(4, "Updating user=%ls to %u processes (last used session: %RU32)\n",
+ pCurUser->wszUser, cCurSessionProcs, userSession.ulLastSession);
- pUserInfo[i].ulNumProcs = cSessionProcs;
- pUserInfo[i].ulSession = ulSession;
+ if (!cCurSessionProcs)
+ VBoxServiceVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n",
+ pCurUser->wszUser,
+ pCurUser->ulNumProcs, cCurSessionProcs,
+ pCurUser->ulLastSession, userSession.ulLastSession);
- if (!cSessionProcs)
- VBoxServiceVerbose(3, "Stale session for user=%ls detected! Old processes: %u, new: %u\n",
- pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, cSessionProcs);
+ pCurUser->ulNumProcs = cCurSessionProcs;
+ pCurUser->ulLastSession = userSession.ulLastSession;
}
/* There can be multiple session objects using the same session ID for the
* current user -- so when we got the same session again just add the found
* processes to it. */
- else if (pUserInfo[i].ulSession == ulSession)
+ else if (pCurUser->ulLastSession == userSession.ulLastSession)
{
- VBoxServiceVerbose(4, "Adding %u processes to user=%ls (session %u)\n",
- cSessionProcs, UserInfo.wszUser, ulSession);
+ VBoxServiceVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n",
+ pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, pCurUser->ulLastSession);
- pUserInfo[i].ulNumProcs += cSessionProcs;
- pUserInfo[i].ulSession = ulSession;
+ pCurUser->ulNumProcs = cCurSessionProcs;
}
fFoundUser = true;
@@ -836,55 +1135,93 @@ int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList)
if (!fFoundUser)
{
- VBoxServiceVerbose(4, "Adding new user=%ls (session %u) with %u processes\n",
- UserInfo.wszUser, ulSession, cSessionProcs);
+ VBoxServiceVerbose(4, "Adding new user=%ls (session=%RU32) with %RU32 processes\n",
+ userSession.wszUser, userSession.ulLastSession, cCurSessionProcs);
- memcpy(&pUserInfo[cUniqueUsers], &UserInfo, sizeof(VBOXSERVICEVMINFOUSER));
- pUserInfo[cUniqueUsers].ulNumProcs = cSessionProcs;
- pUserInfo[cUniqueUsers].ulSession = ulSession;
+ memcpy(&pUserInfo[cUniqueUsers], &userSession, sizeof(VBOXSERVICEVMINFOUSER));
+ pUserInfo[cUniqueUsers].ulNumProcs = cCurSessionProcs;
cUniqueUsers++;
Assert(cUniqueUsers <= cSessions);
}
}
}
+ if (g_cVerbosity > 3)
+ VBoxServiceWritePropF(s_uDebugGuestPropClientID, "/VirtualBox/GuestInfo/Debug/LSA",
+ "#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32",
+ s_uDebugIter, cSessions, cProcs, cUniqueUsers);
+
VBoxServiceVerbose(3, "Found %u unique logged-in user(s)\n",
cUniqueUsers);
- *pcUsersInList = 0;
for (ULONG i = 0; i < cUniqueUsers; i++)
{
+ if (g_cVerbosity > 3)
+ {
+ char szDebugUserPath[255]; RTStrPrintf(szDebugUserPath, sizeof(szDebugUserPath), "/VirtualBox/GuestInfo/Debug/LSA/User/%RU32", i);
+ VBoxServiceWritePropF(s_uDebugGuestPropClientID, szDebugUserPath,
+ "#%RU32: szName=%ls, sessionID=%RU32, cProcs=%RU32",
+ s_uDebugIter, pUserInfo[i].wszUser, pUserInfo[i].ulLastSession, pUserInfo[i].ulNumProcs);
+ }
+
+ bool fAddUser = false;
if (pUserInfo[i].ulNumProcs)
+ fAddUser = true;
+
+ if (fAddUser)
{
- VBoxServiceVerbose(3, "User %ls has %ld processes (session %u)\n",
- pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulSession);
+ VBoxServiceVerbose(3, "User \"%ls\" has %RU32 interactive processes (session=%RU32)\n",
+ pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulLastSession);
- if (*pcUsersInList > 0)
+ if (cUsersInList > 0)
{
- rc = RTStrAAppend(ppszUserList, ",");
- AssertRCBreakStmt(rc, RTStrFree(*ppszUserList));
+ rc = RTStrAAppend(&pszUserList, ",");
+ AssertRCBreakStmt(rc, RTStrFree(pszUserList));
}
- *pcUsersInList += 1;
+ cUsersInList += 1;
- char *pszTemp;
- int rc2 = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszTemp);
- if (RT_SUCCESS(rc2))
+ char *pszUser = NULL;
+ char *pszDomain = NULL;
+ rc = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszUser);
+ if ( RT_SUCCESS(rc)
+ && pUserInfo[i].wszLogonDomain)
+ rc = RTUtf16ToUtf8(pUserInfo[i].wszLogonDomain, &pszDomain);
+ if (RT_SUCCESS(rc))
{
- rc = RTStrAAppend(ppszUserList, pszTemp);
- RTMemFree(pszTemp);
+ /* Append user to users list. */
+ rc = RTStrAAppend(&pszUserList, pszUser);
+
+ /* Do idle detection. */
+ if (RT_SUCCESS(rc))
+ rc = vboxServiceVMInfoWinWriteLastInput(pCache, pszUser, pszDomain);
}
else
- rc = RTStrAAppend(ppszUserList, "<string-conversion-error>");
- AssertRCBreakStmt(rc, RTStrFree(*ppszUserList));
+ rc = RTStrAAppend(&pszUserList, "<string-conversion-error>");
+
+ RTStrFree(pszUser);
+ RTStrFree(pszDomain);
+
+ AssertRCBreakStmt(rc, RTStrFree(pszUserList));
}
}
RTMemFree(pUserInfo);
}
- VBoxServiceVMInfoWinProcessesFree(paProcs);
+ VBoxServiceVMInfoWinProcessesFree(cProcs, paProcs);
}
- LsaFreeReturnBuffer(paSessions);
+ if (paSessions)
+ LsaFreeReturnBuffer(paSessions);
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszUserList = pszUserList;
+ *pcUsersInList = cUsersInList;
+ }
+
+ s_uDebugIter++;
+ VbglR3GuestPropDisconnect(s_uDebugGuestPropClientID);
+
return rc;
}
@@ -918,10 +1255,16 @@ int VBoxServiceWinGetComponentVersions(uint32_t uClientID)
{ szSysDir, "VBoxTray.exe" },
{ szSysDir, "VBoxGINA.dll" },
{ szSysDir, "VBoxCredProv.dll" },
+# ifdef VBOX_WITH_MMR
+ { szSysDir, "VBoxMMR.exe" },
+# endif /* VBOX_WITH_MMR */
/* On 64-bit we don't yet have the OpenGL DLLs in native format.
So just enumerate the 32-bit files in the SYSWOW directory. */
# ifdef RT_ARCH_AMD64
+# ifdef VBOX_WITH_MMR
+ { szSysWowDir, "VBoxMMRHook.dll" },
+# endif /* VBOX_WITH_MMR */
{ szSysWowDir, "VBoxOGLarrayspu.dll" },
{ szSysWowDir, "VBoxOGLcrutil.dll" },
{ szSysWowDir, "VBoxOGLerrorspu.dll" },
@@ -930,6 +1273,9 @@ int VBoxServiceWinGetComponentVersions(uint32_t uClientID)
{ szSysWowDir, "VBoxOGLfeedbackspu.dll" },
{ szSysWowDir, "VBoxOGL.dll" },
# else /* !RT_ARCH_AMD64 */
+# ifdef VBOX_WITH_MMR
+ { szSysDir, "VBoxMMRHook.dll" },
+# endif /* VBOX_WITH_MMR */
{ szSysDir, "VBoxOGLarrayspu.dll" },
{ szSysDir, "VBoxOGLcrutil.dll" },
{ szSysDir, "VBoxOGLerrorspu.dll" },