diff options
Diffstat (limited to 'src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp')
-rw-r--r-- | src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp | 666 |
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" }, |