VBoxServiceVMInfo-win.cpp revision c1a200b0143781501feaf6309ab831992316ef7e
/* $Id$ */
/** @file
* VBoxService - Virtual Machine Information for the Host, Windows specifics.
*/
/*
* 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;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#endif
#include <Windows.h>
#include <wtsapi32.h> /* For WTS* calls. */
#include <psapi.h> /* EnumProcesses. */
#include <Ntsecapi.h> /* Needed for process security information. */
#include <iprt/localipc.h>
#include <iprt/semaphore.h>
#include <VBox/VBoxGuestLib.h>
#include "VBoxServiceInternal.h"
#include "VBoxServiceUtils.h"
#include "VBoxServiceVMInfo.h"
#include "../../WINNT/VBoxTray/VBoxTrayMsg.h" /* For IPC. */
static uint32_t s_uGuestPropClientID = 0;
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/** Structure for storing the looked up user information. */
typedef struct VBOXSERVICEVMINFOUSER
{
/** Number of assigned user processes. */
/** Last (highest) session ID. This
* is needed for distinguishing old session
* process counts from new (current) session
* ones. */
/** Structure for the file information lookup. */
typedef struct VBOXSERVICEVMINFOFILE
{
char *pszFilePath;
char *pszFileName;
/** Structure for process information lookup. */
typedef struct VBOXSERVICEVMINFOPROC
{
/** The PID. */
/** The SID. */
/** The LUID. */
/** Interactive process. */
bool fInteractive;
/*******************************************************************************
* Prototypes
*******************************************************************************/
uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs);
int vboxServiceVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain);
#ifndef TARGET_NT4
static bool vboxServiceVMInfoSession0Separation(void)
{
/** @todo Only do this once. Later. */
{
/* Platform other than NT (e.g. Win9x) not supported. */
return false;
}
&& OSInfoEx.dwMinorVersion >= 0)
{
return true;
}
return false;
}
/**
* Retrieves the module name of a given process.
*
* @return IPRT status code.
* @param pProc
* @param pszBuf
* @param cbBuf
*/
{
{
/* Platform other than NT (e.g. Win9x) not supported. */
return VERR_NOT_SUPPORTED;
}
int rc = VINF_SUCCESS;
if (h == NULL)
{
if (g_cVerbosity)
VBoxServiceError("Unable to open process with PID=%ld, error=%ld\n",
}
else
{
/* Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps cannot query 64-bit
apps and vice verse) we have to use a different code path for Vista and up. */
/* Note: For 2000 + NT4 we might just use GetModuleFileName() instead. */
{
/* 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. */
/** @todo r=bird: WTF don't we query the UNICODE name? */
{
/** @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.) */
}
}
else
{
}
CloseHandle(h);
}
return rc;
}
/**
* Fills in more data for a process.
*
* @returns VBox status code.
* @param pProc The process structure to fill data into.
* @param tkClass The kind of token information to get.
*/
{
if (h == NULL)
{
dwErr = GetLastError();
if (g_cVerbosity > 4)
VBoxServiceError("Unable to open process with PID=%ld, error=%ld\n",
return RTErrConvertFromWin32(dwErr);
}
int rc = VINF_SUCCESS;
{
void *pvTokenInfo = NULL;
switch (tkClass)
{
case TokenStatistics:
dwTokenInfoSize = sizeof(TOKEN_STATISTICS);
break;
case TokenGroups:
dwTokenInfoSize = 0;
/* Allocation will follow in a second step. */
break;
case TokenUser:
dwTokenInfoSize = 0;
/* Allocation will follow in a second step. */
break;
default:
break;
}
if (RT_SUCCESS(rc))
{
{
dwErr = GetLastError();
if (dwErr == ERROR_INSUFFICIENT_BUFFER)
{
switch (tkClass)
{
case TokenGroups:
if (!pvTokenInfo)
dwErr = GetLastError();
break;
case TokenUser:
if (!pvTokenInfo)
dwErr = GetLastError();
break;
default:
AssertMsgFailed(("Re-allocating of token information for token class not implemented\n"));
break;
}
if (dwErr == ERROR_SUCCESS)
{
dwErr = GetLastError();
}
}
}
if (dwErr == ERROR_SUCCESS)
{
rc = VINF_SUCCESS;
switch (tkClass)
{
case TokenStatistics:
{
/** @todo Add more information of TOKEN_STATISTICS as needed. */
break;
}
case TokenGroups:
{
pProc->fInteractive = false;
dwErr = GetLastError();
if (dwErr == ERROR_SUCCESS)
{
dwErr = GetLastError();
}
if (dwErr == ERROR_SUCCESS)
{
{
{
pProc->fInteractive = true;
break;
}
}
}
if (pSidInteractive)
if (pSidLocal)
break;
}
case TokenUser:
{
if (dwLength)
{
{
}
else
dwErr = GetLastError();
}
else
if (dwErr != ERROR_SUCCESS)
VBoxServiceError("Error retrieving SID of process PID=%ld: %ld\n",
break;
}
default:
AssertMsgFailed(("Unhandled token information class\n"));
break;
}
}
if (pvTokenInfo)
}
}
else
dwErr = GetLastError();
if (dwErr != ERROR_SUCCESS)
{
if (g_cVerbosity)
VBoxServiceError("Unable to query token information for PID=%ld, error=%ld\n",
}
CloseHandle(h);
return rc;
}
/**
* Enumerate all the processes in the system and get the logon user IDs for
* them.
*
* @returns VBox status code.
* @param ppaProcs Where to return the process snapshot. This must be
* freed by calling VBoxServiceVMInfoWinProcessesFree.
*
* @param pcProcs Where to store the returned process count.
*/
{
/*
* Call EnumProcesses with an increasingly larger buffer until it all fits
* or we think something is screwed up.
*/
int rc = VINF_SUCCESS;
do
{
/* Allocate / grow the buffer first. */
cProcesses *= 2;
if (!pvNew)
{
rc = VERR_NO_MEMORY;
break;
}
/* Query the processes. Not the cbRet == buffer size means there could be more work to be done. */
{
break;
}
{
break;
}
} while (cProcesses <= _32K); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */
if (RT_SUCCESS(rc))
{
/*
* Allocate out process structures and fill data into them.
* We currently only try lookup their LUID's.
*/
if (paProcs)
{
for (DWORD i = 0; i < cProcesses; i++)
{
VBoxServiceError("Get token class \"user\" for process %ld failed, rc=%Rrc\n",
VBoxServiceError("Get token class \"groups\" for process %ld failed, rc=%Rrc\n",
VBoxServiceError("Get token class \"statistics\" for process %ld failed, rc=%Rrc\n",
}
/* Save number of processes */
if (RT_SUCCESS(rc))
{
*pcProcs = cProcesses;
}
else
}
else
rc = VERR_NO_MEMORY;
}
return rc;
}
/**
* Frees the process structures returned by
* VBoxServiceVMInfoWinProcessesEnumerate() before.
*
* @param paProcs What
*/
{
{
}
}
/**
* Determines whether the specified session has processes on the system.
*
* @returns Number of processes found for a specified 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.
*/
{
if (!pSession)
{
return 0;
}
if (rcNt != STATUS_SUCCESS)
{
return 0;
}
{
return 0;
}
int rc = VINF_SUCCESS;
/*
* session. So check if we have some processes bound to it by comparing the
* session <-> process LUIDs.
*/
{
if (g_cVerbosity)
{
if (RT_SUCCESS(rc))
}
if ( RT_SUCCESS(rc)
&& pProcSID
&& IsValidSid(pProcSID))
{
&& paProcs[i].fInteractive)
{
cNumProcs++;
if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */
break;
}
}
}
if (puTerminalSession)
return cNumProcs;
}
/**
* Save and noisy string copy.
*
* @param pwszDst Destination buffer.
* @param cbDst Size in bytes - not WCHAR count!
* @param pSrc Source string.
* @param pszWhat What this is. For the log.
*/
static void VBoxServiceVMInfoWinSafeCopy(PWCHAR pwszDst, size_t cbDst, LSA_UNICODE_STRING const *pSrc, const char *pszWhat)
{
{
VBoxServiceVerbose(0, "%s is too long - %u bytes, buffer %u bytes! It will be truncated.\n",
}
if (cbCopy)
}
/**
* Detects whether a user is logged on.
*
* @returns true if logged in, false if not (or error).
* @param pUserInfo Where to return the user information.
* @param pSession The session to check.
*/
{
AssertPtrReturn(pUserInfo, false);
if (!pSession)
return false;
if (rcNt != STATUS_SUCCESS)
{
switch (ulError)
{
case ERROR_NOT_ENOUGH_MEMORY:
/* If we don't have enough memory it's hard to judge whether the specified user
break;
/* Skip session data which is not valid anymore because it may have been
* already terminated. */
break;
default:
break;
}
if (pSessionData)
return false;
}
if (!pSessionData)
{
VBoxServiceError("Invalid logon session data!\n");
return false;
}
{
/* 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. */
{
return false;
}
}
/*
* Only handle users which can login interactively or logged in
* remotely over native RDP.
*/
bool fFoundUser = false;
/* Note: We also need CachedInteractive in case Windows cached the credentials
* or just wants to reuse them! */
{
/*
* Copy out relevant data.
*/
VBoxServiceVMInfoWinSafeCopy(pUserInfo->wszAuthenticationPackage, sizeof(pUserInfo->wszAuthenticationPackage),
if (!LookupAccountSid(NULL,
&enmOwnerType))
{
/*
* If a network time-out prevents the function from finding the name or
* if a SID that does not have a corresponding account name (such as a
* logon SID that identifies a logon session), we get ERROR_NONE_MAPPED
* here that we just skip.
*/
if (dwErr != ERROR_NONE_MAPPED)
VBoxServiceError("Failed looking up account info for user=%ls, error=$ld!\n",
}
else
{
{
/* Detect RDP sessions as well. */
int iState = -1;
&pBuffer,
&cbRet))
{
if (cbRet)
{
/** @todo On Vista and W2K, always "old" user name are still
* there. Filter out the old one! */
fFoundUser = true;
}
if (pBuffer)
}
else
{
switch (dwLastErr)
{
/*
* Terminal services don't run (for example in W2K,
* nothing to worry about ...). ... or is on the Vista
* fast user switching page!
*/
break;
default:
break;
}
fFoundUser = true;
}
}
else
}
}
if (fFoundUser)
return fFoundUser;
}
{
int rc = VINF_SUCCESS;
char szPipeName[255];
{
if (RT_SUCCESS(rc))
{
VBOXTRAYIPCMSGTYPE_USERLASTINPUT, 0 /* No msg */ };
if (RT_SUCCESS(rc))
NULL /* Exact read */);
if (RT_SUCCESS(rc))
{
? "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.
*/
if (rc == VINF_SUCCESS)
{
#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)
else
NULL /* Delete property */);
if (RT_SUCCESS(rc))
#endif
NULL /* No details */, 0);
}
}
#ifdef DEBUG
#endif
if (RT_SUCCESS(rc))
}
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. */
pszUser);
break;
default:
break;
}
rc = VINF_SUCCESS;
}
}
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.
*/
{
/* This function can report stale or orphaned interactive logon sessions
of already logged off users (especially in Windows 2000). */
if (rcNt != STATUS_SUCCESS)
{
switch (ulError)
{
case ERROR_NOT_ENOUGH_MEMORY:
VBoxServiceError("Not enough memory to enumerate logon sessions!\n");
break;
/* If we're about to shutdown when we were in the middle of enumerating the logon
* sessions, skip the error to not confuse the user with an unnecessary log message. */
break;
default:
break;
}
return RTErrConvertFromWin32(ulError);
}
if (RT_FAILURE(rc))
{
if (rc == VERR_NO_MEMORY)
VBoxServiceError("Not enough memory to enumerate processes\n");
else
}
else
{
if (!pUserInfo)
VBoxServiceError("Not enough memory to store enumerated users!\n");
else
{
ULONG cUniqueUsers = 0;
/**
* Note: The cSessions loop variable does *not* correlate with
* the Windows session ID!
*/
{
{
/* Retrieve assigned processes of current session. */
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
if (g_cVerbosity > 3)
{
char szDebugSessionPath[255]; RTStrPrintf(szDebugSessionPath, sizeof(szDebugSessionPath), "/VirtualBox/GuestInfo/Debug/LSA/Session/%RU32",
}
bool fFoundUser = false;
for (ULONG a = 0; a < cUniqueUsers; a++)
{
{
/*
* Only respect the highest session for the current user.
*/
{
if (!cCurSessionProcs)
VBoxServiceVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n",
}
/* 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. */
{
VBoxServiceVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n",
}
fFoundUser = true;
break;
}
}
if (!fFoundUser)
{
cUniqueUsers++;
}
}
}
if (g_cVerbosity > 3)
"#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32",
*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);
"#%RU32: szName=%ls, sessionID=%RU32, cProcs=%RU32",
}
bool fAddUser = false;
if (pUserInfo[i].ulNumProcs)
fAddUser = true;
if (fAddUser)
{
if (*pcUsersInList > 0)
{
}
*pcUsersInList += 1;
if ( RT_SUCCESS(rc)
&& pUserInfo[i].wszLogonDomain)
if (RT_SUCCESS(rc))
{
/* Append user to users list. */
/* Do idle detection. */
if (RT_SUCCESS(rc))
}
else
}
}
}
}
s_uIter++;
return rc;
}
#endif /* TARGET_NT4 */
{
int rc;
/* ASSUME: szSysDir and szWinDir and derivatives are always ASCII compatible. */
#ifdef RT_ARCH_AMD64
#endif
/* The file information table. */
#ifndef TARGET_NT4
const VBOXSERVICEVMINFOFILE aVBoxFiles[] =
{
{ szSysDir, "VBoxControl.exe" },
{ szSysDir, "VBoxHook.dll" },
{ szSysDir, "VBoxDisp.dll" },
{ szSysDir, "VBoxMRXNP.dll" },
{ szSysDir, "VBoxService.exe" },
{ 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" },
{ szSysWowDir, "VBoxOGLpackspu.dll" },
{ szSysWowDir, "VBoxOGLpassthroughspu.dll" },
{ 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" },
{ szSysDir, "VBoxOGLpackspu.dll" },
{ szSysDir, "VBoxOGLpassthroughspu.dll" },
{ szSysDir, "VBoxOGLfeedbackspu.dll" },
{ szSysDir, "VBoxOGL.dll" },
# endif /* !RT_ARCH_AMD64 */
{ szDriversDir, "VBoxGuest.sys" },
{ szDriversDir, "VBoxMouse.sys" },
{ szDriversDir, "VBoxSF.sys" },
{ szDriversDir, "VBoxVideo.sys" },
};
#else /* TARGET_NT4 */
const VBOXSERVICEVMINFOFILE aVBoxFiles[] =
{
{ szSysDir, "VBoxControl.exe" },
{ szSysDir, "VBoxHook.dll" },
{ szSysDir, "VBoxDisp.dll" },
{ szSysDir, "VBoxServiceNT.exe" },
{ szSysDir, "VBoxTray.exe" },
{ szDriversDir, "VBoxGuestNT.sys" },
{ szDriversDir, "VBoxMouseNT.sys" },
{ szDriversDir, "VBoxVideo.sys" },
};
#endif /* TARGET_NT4 */
for (unsigned i = 0; i < RT_ELEMENTS(aVBoxFiles); i++)
{
char szVer[128];
VBoxServiceGetFileVersionString(aVBoxFiles[i].pszFilePath, aVBoxFiles[i].pszFileName, szVer, sizeof(szVer));
char szPropPath[256];
RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestAdd/Components/%s", aVBoxFiles[i].pszFileName);
}
return VINF_SUCCESS;
}