VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp@ 70196

Last change on this file since 70196 was 70196, checked in by vboxsync, 7 years ago

VBoxService: Made it work on NT 3.51.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 54.4 KB
Line 
1/* $Id: VBoxServiceVMInfo-win.cpp 70196 2017-12-18 13:40:49Z vboxsync $ */
2/** @file
3 * VBoxService - Virtual Machine Information for the Host, Windows specifics.
4 */
5
6/*
7 * Copyright (C) 2009-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600
23# undef _WIN32_WINNT
24# define _WIN32_WINNT 0x0600 /* QueryFullProcessImageNameW in recent SDKs. */
25#endif
26#include <iprt/win/windows.h>
27#include <wtsapi32.h> /* For WTS* calls. */
28#include <psapi.h> /* EnumProcesses. */
29#include <Ntsecapi.h> /* Needed for process security information. */
30
31#include <iprt/assert.h>
32#include <iprt/ldr.h>
33#include <iprt/localipc.h>
34#include <iprt/mem.h>
35#include <iprt/once.h>
36#include <iprt/process.h>
37#include <iprt/string.h>
38#include <iprt/semaphore.h>
39#include <iprt/system.h>
40#include <iprt/time.h>
41#include <iprt/thread.h>
42
43#include <VBox/VBoxGuestLib.h>
44#include "VBoxServiceInternal.h"
45#include "VBoxServiceUtils.h"
46#include "VBoxServiceVMInfo.h"
47#include "../../WINNT/VBoxTray/VBoxTrayMsg.h" /* For IPC. */
48
49static uint32_t s_uDebugGuestPropClientID = 0;
50static uint32_t s_uDebugIter = 0;
51/** Whether to skip the logged-in user detection over RDP or not.
52 * See notes in this section why we might want to skip this. */
53static bool s_fSkipRDPDetection = false;
54
55
56/*********************************************************************************************************************************
57* Structures and Typedefs *
58*********************************************************************************************************************************/
59/** Structure for storing the looked up user information. */
60typedef struct VBOXSERVICEVMINFOUSER
61{
62 WCHAR wszUser[_MAX_PATH];
63 WCHAR wszAuthenticationPackage[_MAX_PATH];
64 WCHAR wszLogonDomain[_MAX_PATH];
65 /** Number of assigned user processes. */
66 ULONG ulNumProcs;
67 /** Last (highest) session ID. This
68 * is needed for distinguishing old session
69 * process counts from new (current) session
70 * ones. */
71 ULONG ulLastSession;
72} VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER;
73
74/** Structure for the file information lookup. */
75typedef struct VBOXSERVICEVMINFOFILE
76{
77 char *pszFilePath;
78 char *pszFileName;
79} VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE;
80
81/** Structure for process information lookup. */
82typedef struct VBOXSERVICEVMINFOPROC
83{
84 /** The PID. */
85 DWORD id;
86 /** The SID. */
87 PSID pSid;
88 /** The LUID. */
89 LUID luid;
90 /** Interactive process. */
91 bool fInteractive;
92} VBOXSERVICEVMINFOPROC, *PVBOXSERVICEVMINFOPROC;
93
94
95/*********************************************************************************************************************************
96* Internal Functions *
97*********************************************************************************************************************************/
98static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs);
99static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession);
100static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount);
101static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs);
102static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain);
103
104
105/*********************************************************************************************************************************
106* Global Variables *
107*********************************************************************************************************************************/
108static RTONCE g_vgsvcWinVmInitOnce = RTONCE_INITIALIZER;
109
110/** @name Secur32.dll imports are dynamically resolved because of NT4.
111 * @{ */
112static decltype(LsaGetLogonSessionData) *g_pfnLsaGetLogonSessionData = NULL;
113static decltype(LsaEnumerateLogonSessions) *g_pfnLsaEnumerateLogonSessions = NULL;
114static decltype(LsaFreeReturnBuffer) *g_pfnLsaFreeReturnBuffer = NULL;
115/** @} */
116
117/** @name WtsApi32.dll imports are dynamically resolved because of NT4.
118 * @{ */
119static decltype(WTSFreeMemory) *g_pfnWTSFreeMemory = NULL;
120static decltype(WTSQuerySessionInformationA) *g_pfnWTSQuerySessionInformationA = NULL;
121/** @} */
122
123/** @name PsApi.dll imports are dynamically resolved because of NT4.
124 * @{ */
125static decltype(EnumProcesses) *g_pfnEnumProcesses = NULL;
126static decltype(GetModuleFileNameExW) *g_pfnGetModuleFileNameExW = NULL;
127/** @} */
128
129/** @name New Kernel32.dll APIs we may use when present.
130 * @{ */
131static decltype(QueryFullProcessImageNameW) *g_pfnQueryFullProcessImageNameW = NULL;
132
133/** @} */
134
135/** Windows version. */
136static OSVERSIONINFOEXA g_WinVersion;
137
138
139/**
140 * An RTOnce callback function.
141 */
142static DECLCALLBACK(int) vgsvcWinVmInfoInitOnce(void *pvIgnored)
143{
144 RT_NOREF1(pvIgnored);
145
146 /* SECUR32 */
147 RTLDRMOD hLdrMod;
148 int rc = RTLdrLoadSystem("secur32.dll", true, &hLdrMod);
149 if (RT_SUCCESS(rc))
150 {
151 rc = RTLdrGetSymbol(hLdrMod, "LsaGetLogonSessionData", (void **)&g_pfnLsaGetLogonSessionData);
152 if (RT_SUCCESS(rc))
153 rc = RTLdrGetSymbol(hLdrMod, "LsaEnumerateLogonSessions", (void **)&g_pfnLsaEnumerateLogonSessions);
154 if (RT_SUCCESS(rc))
155 rc = RTLdrGetSymbol(hLdrMod, "LsaFreeReturnBuffer", (void **)&g_pfnLsaFreeReturnBuffer);
156 AssertRC(rc);
157 RTLdrClose(hLdrMod);
158 }
159 if (RT_FAILURE(rc))
160 {
161 VGSvcVerbose(1, "Secur32.dll APIs are not availble (%Rrc)\n", rc);
162 g_pfnLsaGetLogonSessionData = NULL;
163 g_pfnLsaEnumerateLogonSessions = NULL;
164 g_pfnLsaFreeReturnBuffer = NULL;
165 Assert(g_WinVersion.dwMajorVersion < 5);
166 }
167
168 /* WTSAPI32 */
169 rc = RTLdrLoadSystem("wtsapi32.dll", true, &hLdrMod);
170 if (RT_SUCCESS(rc))
171 {
172 rc = RTLdrGetSymbol(hLdrMod, "WTSFreeMemory", (void **)&g_pfnWTSFreeMemory);
173 if (RT_SUCCESS(rc))
174 rc = RTLdrGetSymbol(hLdrMod, "WTSQuerySessionInformationA", (void **)&g_pfnWTSQuerySessionInformationA);
175 AssertRC(rc);
176 RTLdrClose(hLdrMod);
177 }
178 if (RT_FAILURE(rc))
179 {
180 VGSvcVerbose(1, "WtsApi32.dll APIs are not availble (%Rrc)\n", rc);
181 g_pfnWTSFreeMemory = NULL;
182 g_pfnWTSQuerySessionInformationA = NULL;
183 Assert(g_WinVersion.dwMajorVersion < 5);
184 }
185
186 /* PSAPI */
187 rc = RTLdrLoadSystem("psapi.dll", true, &hLdrMod);
188 if (RT_SUCCESS(rc))
189 {
190 rc = RTLdrGetSymbol(hLdrMod, "EnumProcesses", (void **)&g_pfnEnumProcesses);
191 if (RT_SUCCESS(rc))
192 rc = RTLdrGetSymbol(hLdrMod, "GetModuleFileNameExW", (void **)&g_pfnGetModuleFileNameExW);
193 AssertRC(rc);
194 RTLdrClose(hLdrMod);
195 }
196 if (RT_FAILURE(rc))
197 {
198 VGSvcVerbose(1, "psapi.dll APIs are not availble (%Rrc)\n", rc);
199 g_pfnEnumProcesses = NULL;
200 g_pfnGetModuleFileNameExW = NULL;
201 Assert(g_WinVersion.dwMajorVersion < 5);
202 }
203
204 /* Kernel32: */
205 rc = RTLdrLoadSystem("kernel32.dll", true, &hLdrMod);
206 AssertRCReturn(rc, rc);
207 rc = RTLdrGetSymbol(hLdrMod, "QueryFullProcessImageNameW", (void **)&g_pfnQueryFullProcessImageNameW);
208 if (RT_FAILURE(rc))
209 {
210 Assert(g_WinVersion.dwMajorVersion < 6);
211 g_pfnQueryFullProcessImageNameW = NULL;
212 }
213 RTLdrClose(hLdrMod);
214
215 /*
216 * Get the extended windows version once and for all.
217 */
218 g_WinVersion.dwOSVersionInfoSize = sizeof(g_WinVersion);
219 if (!GetVersionExA((OSVERSIONINFO *)&g_WinVersion))
220 {
221 RT_ZERO(g_WinVersion);
222 g_WinVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
223 if (!GetVersionExA((OSVERSIONINFO *)&g_WinVersion))
224 {
225 AssertFailed();
226 RT_ZERO(g_WinVersion);
227 }
228 }
229
230 return VINF_SUCCESS;
231}
232
233
234static bool vgsvcVMInfoSession0Separation(void)
235{
236 return g_WinVersion.dwPlatformId == VER_PLATFORM_WIN32_NT
237 && g_WinVersion.dwMajorVersion >= 6; /* Vista = 6.0 */
238}
239
240
241/**
242 * Retrieves the module name of a given process.
243 *
244 * @return IPRT status code.
245 */
246static int vgsvcVMInfoWinProcessesGetModuleNameA(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName)
247{
248 AssertPtrReturn(pProc, VERR_INVALID_POINTER);
249 AssertPtrReturn(ppszName, VERR_INVALID_POINTER);
250
251 /** @todo Only do this once. Later. */
252 /* Platform other than NT (e.g. Win9x) not supported. */
253 if (g_WinVersion.dwPlatformId != VER_PLATFORM_WIN32_NT)
254 return VERR_NOT_SUPPORTED;
255
256 int rc = VINF_SUCCESS;
257
258 DWORD dwFlags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
259 if (g_WinVersion.dwMajorVersion >= 6 /* Vista or later */)
260 dwFlags = PROCESS_QUERY_LIMITED_INFORMATION; /* possible to do on more processes */
261
262 HANDLE h = OpenProcess(dwFlags, FALSE, pProc->id);
263 if (h == NULL)
264 {
265 DWORD dwErr = GetLastError();
266 if (g_cVerbosity)
267 VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr);
268 rc = RTErrConvertFromWin32(dwErr);
269 }
270 else
271 {
272 /* Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps cannot query 64-bit
273 apps and vice verse) we have to use a different code path for Vista and up. */
274 WCHAR wszName[_1K];
275 DWORD dwLen = sizeof(wszName); /** @todo r=bird: wrong? */
276
277 /* Use QueryFullProcessImageNameW if available (Vista+). */
278 if (g_pfnQueryFullProcessImageNameW)
279 {
280 if (!g_pfnQueryFullProcessImageNameW(h, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen))
281 rc = VERR_ACCESS_DENIED;
282 }
283 else if (!g_pfnGetModuleFileNameExW(h, NULL /* Get main executable */, wszName, dwLen))
284 rc = VERR_ACCESS_DENIED;
285
286 if ( RT_FAILURE(rc)
287 && g_cVerbosity > 3)
288 VGSvcError("Unable to retrieve process name for PID=%u, error=%u\n", pProc->id, GetLastError());
289 else
290 {
291 PRTUTF16 pszName = RTUtf16Dup(wszName);
292 if (pszName)
293 *ppszName = pszName;
294 else
295 rc = VERR_NO_MEMORY;
296 }
297
298 CloseHandle(h);
299 }
300
301 return rc;
302}
303
304
305/**
306 * Fills in more data for a process.
307 *
308 * @returns VBox status code.
309 * @param pProc The process structure to fill data into.
310 * @param tkClass The kind of token information to get.
311 */
312static int vgsvcVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pProc, TOKEN_INFORMATION_CLASS tkClass)
313{
314 AssertPtrReturn(pProc, VERR_INVALID_POINTER);
315
316 DWORD dwErr = ERROR_SUCCESS;
317 HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pProc->id);
318 if (h == NULL)
319 {
320 dwErr = GetLastError();
321 if (g_cVerbosity > 4)
322 VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr);
323 return RTErrConvertFromWin32(dwErr);
324 }
325
326 int rc = VINF_SUCCESS;
327 HANDLE hToken;
328 if (OpenProcessToken(h, TOKEN_QUERY, &hToken))
329 {
330 void *pvTokenInfo = NULL;
331 DWORD dwTokenInfoSize;
332 switch (tkClass)
333 {
334 case TokenStatistics:
335 /** @todo r=bird: Someone has been reading too many MSDN examples. You shall
336 * use RTMemAlloc here! There is absolutely not reason for
337 * complicating things uncessarily by using HeapAlloc! */
338 dwTokenInfoSize = sizeof(TOKEN_STATISTICS);
339 pvTokenInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwTokenInfoSize);
340 AssertPtr(pvTokenInfo);
341 break;
342
343 case TokenGroups:
344 dwTokenInfoSize = 0;
345 /* Allocation will follow in a second step. */
346 break;
347
348 case TokenUser:
349 dwTokenInfoSize = 0;
350 /* Allocation will follow in a second step. */
351 break;
352
353 default:
354 VGSvcError("Token class not implemented: %d\n", tkClass);
355 rc = VERR_NOT_IMPLEMENTED;
356 dwTokenInfoSize = 0; /* Shut up MSC. */
357 break;
358 }
359
360 if (RT_SUCCESS(rc))
361 {
362 DWORD dwRetLength;
363 if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength))
364 {
365 dwErr = GetLastError();
366 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
367 {
368 dwErr = ERROR_SUCCESS;
369
370 switch (tkClass)
371 {
372 case TokenGroups:
373 pvTokenInfo = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength);
374 if (!pvTokenInfo)
375 dwErr = GetLastError();
376 dwTokenInfoSize = dwRetLength;
377 break;
378
379 case TokenUser:
380 pvTokenInfo = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength);
381 if (!pvTokenInfo)
382 dwErr = GetLastError();
383 dwTokenInfoSize = dwRetLength;
384 break;
385
386 default:
387 AssertMsgFailed(("Re-allocating of token information for token class not implemented\n"));
388 break;
389 }
390
391 if (dwErr == ERROR_SUCCESS)
392 {
393 if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength))
394 dwErr = GetLastError();
395 }
396 }
397 }
398
399 if (dwErr == ERROR_SUCCESS)
400 {
401 rc = VINF_SUCCESS;
402
403 switch (tkClass)
404 {
405 case TokenStatistics:
406 {
407 PTOKEN_STATISTICS pStats = (PTOKEN_STATISTICS)pvTokenInfo;
408 AssertPtr(pStats);
409 memcpy(&pProc->luid, &pStats->AuthenticationId, sizeof(LUID));
410 /** @todo Add more information of TOKEN_STATISTICS as needed. */
411 break;
412 }
413
414 case TokenGroups:
415 {
416 pProc->fInteractive = false;
417
418 SID_IDENTIFIER_AUTHORITY sidAuthNT = SECURITY_NT_AUTHORITY;
419 PSID pSidInteractive = NULL; /* S-1-5-4 */
420 if (!AllocateAndInitializeSid(&sidAuthNT, 1, 4, 0, 0, 0, 0, 0, 0, 0, &pSidInteractive))
421 dwErr = GetLastError();
422
423 PSID pSidLocal = NULL; /* S-1-2-0 */
424 if (dwErr == ERROR_SUCCESS)
425 {
426 SID_IDENTIFIER_AUTHORITY sidAuthLocal = SECURITY_LOCAL_SID_AUTHORITY;
427 if (!AllocateAndInitializeSid(&sidAuthLocal, 1, 0, 0, 0, 0, 0, 0, 0, 0, &pSidLocal))
428 dwErr = GetLastError();
429 }
430
431 if (dwErr == ERROR_SUCCESS)
432 {
433 PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)pvTokenInfo;
434 AssertPtr(pGroups);
435 for (DWORD i = 0; i < pGroups->GroupCount; i++)
436 {
437 if ( EqualSid(pGroups->Groups[i].Sid, pSidInteractive)
438 || EqualSid(pGroups->Groups[i].Sid, pSidLocal)
439 || pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID)
440 {
441 pProc->fInteractive = true;
442 break;
443 }
444 }
445 }
446
447 if (pSidInteractive)
448 FreeSid(pSidInteractive);
449 if (pSidLocal)
450 FreeSid(pSidLocal);
451 break;
452 }
453
454 case TokenUser:
455 {
456 PTOKEN_USER pUser = (PTOKEN_USER)pvTokenInfo;
457 AssertPtr(pUser);
458
459 DWORD dwLength = GetLengthSid(pUser->User.Sid);
460 Assert(dwLength);
461 if (dwLength)
462 {
463 pProc->pSid = (PSID)HeapAlloc(GetProcessHeap(),
464 HEAP_ZERO_MEMORY, dwLength);
465 AssertPtr(pProc->pSid);
466 if (CopySid(dwLength, pProc->pSid, pUser->User.Sid))
467 {
468 if (!IsValidSid(pProc->pSid))
469 dwErr = ERROR_INVALID_NAME;
470 }
471 else
472 dwErr = GetLastError();
473 }
474 else
475 dwErr = ERROR_NO_DATA;
476
477 if (dwErr != ERROR_SUCCESS)
478 {
479 VGSvcError("Error retrieving SID of process PID=%u: %u\n", pProc->id, dwErr);
480 if (pProc->pSid)
481 {
482 HeapFree(GetProcessHeap(), 0 /* Flags */, pProc->pSid);
483 pProc->pSid = NULL;
484 }
485 }
486 break;
487 }
488
489 default:
490 AssertMsgFailed(("Unhandled token information class\n"));
491 break;
492 }
493 }
494
495 if (pvTokenInfo)
496 HeapFree(GetProcessHeap(), 0 /* Flags */, pvTokenInfo);
497 }
498 CloseHandle(hToken);
499 }
500 else
501 dwErr = GetLastError();
502
503 if (dwErr != ERROR_SUCCESS)
504 {
505 if (g_cVerbosity)
506 VGSvcError("Unable to query token information for PID=%u, error=%u\n", pProc->id, dwErr);
507 rc = RTErrConvertFromWin32(dwErr);
508 }
509
510 CloseHandle(h);
511 return rc;
512}
513
514
515/**
516 * Enumerate all the processes in the system and get the logon user IDs for
517 * them.
518 *
519 * @returns VBox status code.
520 * @param ppaProcs Where to return the process snapshot. This must be
521 * freed by calling vgsvcVMInfoWinProcessesFree.
522 *
523 * @param pcProcs Where to store the returned process count.
524 */
525static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDWORD pcProcs)
526{
527 AssertPtr(ppaProcs);
528 AssertPtr(pcProcs);
529
530 if (!g_pfnEnumProcesses)
531 return VERR_NOT_SUPPORTED;
532
533 /*
534 * Call EnumProcesses with an increasingly larger buffer until it all fits
535 * or we think something is screwed up.
536 */
537 DWORD cProcesses = 64;
538 PDWORD paPID = NULL;
539 int rc = VINF_SUCCESS;
540 do
541 {
542 /* Allocate / grow the buffer first. */
543 cProcesses *= 2;
544 void *pvNew = RTMemRealloc(paPID, cProcesses * sizeof(DWORD));
545 if (!pvNew)
546 {
547 rc = VERR_NO_MEMORY;
548 break;
549 }
550 paPID = (PDWORD)pvNew;
551
552 /* Query the processes. Not the cbRet == buffer size means there could be more work to be done. */
553 DWORD cbRet;
554 if (!g_pfnEnumProcesses(paPID, cProcesses * sizeof(DWORD), &cbRet))
555 {
556 rc = RTErrConvertFromWin32(GetLastError());
557 break;
558 }
559 if (cbRet < cProcesses * sizeof(DWORD))
560 {
561 cProcesses = cbRet / sizeof(DWORD);
562 break;
563 }
564 } while (cProcesses <= _32K); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */
565
566 if (RT_SUCCESS(rc))
567 {
568 /*
569 * Allocate out process structures and fill data into them.
570 * We currently only try lookup their LUID's.
571 */
572 PVBOXSERVICEVMINFOPROC paProcs;
573 paProcs = (PVBOXSERVICEVMINFOPROC)RTMemAllocZ(cProcesses * sizeof(VBOXSERVICEVMINFOPROC));
574 if (paProcs)
575 {
576 for (DWORD i = 0; i < cProcesses; i++)
577 {
578 paProcs[i].id = paPID[i];
579 paProcs[i].pSid = NULL;
580
581 int rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenUser);
582 if (RT_FAILURE(rc2) && g_cVerbosity)
583 VGSvcError("Get token class 'user' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
584
585 rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups);
586 if (RT_FAILURE(rc2) && g_cVerbosity)
587 VGSvcError("Get token class 'groups' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
588
589 rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics);
590 if (RT_FAILURE(rc2) && g_cVerbosity)
591 VGSvcError("Get token class 'statistics' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
592 }
593
594 /* Save number of processes */
595 if (RT_SUCCESS(rc))
596 {
597 *pcProcs = cProcesses;
598 *ppaProcs = paProcs;
599 }
600 else
601 vgsvcVMInfoWinProcessesFree(cProcesses, paProcs);
602 }
603 else
604 rc = VERR_NO_MEMORY;
605 }
606
607 RTMemFree(paPID);
608 return rc;
609}
610
611/**
612 * Frees the process structures returned by
613 * vgsvcVMInfoWinProcessesEnumerate() before.
614 *
615 * @param cProcs Number of processes in paProcs.
616 * @param paProcs The process array.
617 */
618static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs)
619{
620 for (DWORD i = 0; i < cProcs; i++)
621 if (paProcs[i].pSid)
622 {
623 HeapFree(GetProcessHeap(), 0 /* Flags */, paProcs[i].pSid);
624 paProcs[i].pSid = NULL;
625 }
626 RTMemFree(paProcs);
627}
628
629/**
630 * Determines whether the specified session has processes on the system.
631 *
632 * @returns Number of processes found for a specified session.
633 * @param pSession The current user's SID.
634 * @param paProcs The process snapshot.
635 * @param cProcs The number of processes in the snaphot.
636 * @param puTerminalSession Where to return terminal session number.
637 * Optional.
638 */
639/** @todo r=bird: The 'Has' indicates a predicate function, which this is
640 * not. Predicate functions always returns bool. */
641static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs,
642 PULONG puTerminalSession)
643{
644 if (!pSession)
645 {
646 VGSvcVerbose(1, "Session became invalid while enumerating!\n");
647 return 0;
648 }
649 if (!g_pfnLsaGetLogonSessionData)
650 return 0;
651
652 PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
653 NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData);
654 if (rcNt != STATUS_SUCCESS)
655 {
656 VGSvcError("Could not get logon session data! rcNt=%#x\n", rcNt);
657 return 0;
658 }
659
660 if (!IsValidSid(pSessionData->Sid))
661 {
662 VGSvcError("User SID=%p is not valid\n", pSessionData->Sid);
663 if (pSessionData)
664 g_pfnLsaFreeReturnBuffer(pSessionData);
665 return 0;
666 }
667
668
669 /*
670 * Even if a user seems to be logged in, it could be a stale/orphaned logon
671 * session. So check if we have some processes bound to it by comparing the
672 * session <-> process LUIDs.
673 */
674 int rc = VINF_SUCCESS;
675 uint32_t cProcessesFound = 0;
676 for (DWORD i = 0; i < cProcs; i++)
677 {
678 PSID pProcSID = paProcs[i].pSid;
679 if ( RT_SUCCESS(rc)
680 && pProcSID
681 && IsValidSid(pProcSID))
682 {
683 if (EqualSid(pSessionData->Sid, paProcs[i].pSid))
684 {
685 if (g_cVerbosity)
686 {
687 PRTUTF16 pszName;
688 int rc2 = vgsvcVMInfoWinProcessesGetModuleNameA(&paProcs[i], &pszName);
689 VGSvcVerbose(4, "Session %RU32: PID=%u (fInt=%RTbool): %ls\n",
690 pSessionData->Session, paProcs[i].id, paProcs[i].fInteractive,
691 RT_SUCCESS(rc2) ? pszName : L"<Unknown>");
692 if (RT_SUCCESS(rc2))
693 RTUtf16Free(pszName);
694 }
695
696 if (paProcs[i].fInteractive)
697 {
698 cProcessesFound++;
699 if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */
700 break;
701 }
702 }
703 }
704 }
705
706 if (puTerminalSession)
707 *puTerminalSession = pSessionData->Session;
708
709 g_pfnLsaFreeReturnBuffer(pSessionData);
710
711 return cProcessesFound;
712}
713
714
715/**
716 * Save and noisy string copy.
717 *
718 * @param pwszDst Destination buffer.
719 * @param cbDst Size in bytes - not WCHAR count!
720 * @param pSrc Source string.
721 * @param pszWhat What this is. For the log.
722 */
723static void vgsvcVMInfoWinSafeCopy(PWCHAR pwszDst, size_t cbDst, LSA_UNICODE_STRING const *pSrc, const char *pszWhat)
724{
725 Assert(RT_ALIGN(cbDst, sizeof(WCHAR)) == cbDst);
726
727 size_t cbCopy = pSrc->Length;
728 if (cbCopy + sizeof(WCHAR) > cbDst)
729 {
730 VGSvcVerbose(0, "%s is too long - %u bytes, buffer %u bytes! It will be truncated.\n", pszWhat, cbCopy, cbDst);
731 cbCopy = cbDst - sizeof(WCHAR);
732 }
733 if (cbCopy)
734 memcpy(pwszDst, pSrc->Buffer, cbCopy);
735 pwszDst[cbCopy / sizeof(WCHAR)] = '\0';
736}
737
738
739/**
740 * Detects whether a user is logged on.
741 *
742 * @returns true if logged in, false if not (or error).
743 * @param pUserInfo Where to return the user information.
744 * @param pSession The session to check.
745 */
746static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSession)
747{
748 AssertPtrReturn(pUserInfo, false);
749 if (!pSession)
750 return false;
751 if (!g_pfnLsaGetLogonSessionData)
752 return false;
753
754 PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
755 NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData);
756 if (rcNt != STATUS_SUCCESS)
757 {
758 ULONG ulError = LsaNtStatusToWinError(rcNt);
759 switch (ulError)
760 {
761 case ERROR_NOT_ENOUGH_MEMORY:
762 /* If we don't have enough memory it's hard to judge whether the specified user
763 * is logged in or not, so just assume he/she's not. */
764 VGSvcVerbose(3, "Not enough memory to retrieve logon session data!\n");
765 break;
766
767 case ERROR_NO_SUCH_LOGON_SESSION:
768 /* Skip session data which is not valid anymore because it may have been
769 * already terminated. */
770 break;
771
772 default:
773 VGSvcError("LsaGetLogonSessionData failed with error %u\n", ulError);
774 break;
775 }
776 if (pSessionData)
777 g_pfnLsaFreeReturnBuffer(pSessionData);
778 return false;
779 }
780 if (!pSessionData)
781 {
782 VGSvcError("Invalid logon session data!\n");
783 return false;
784 }
785
786 VGSvcVerbose(3, "Session data: Name=%ls, SessionID=%RU32, LogonID=%d,%u, LogonType=%u\n",
787 pSessionData->UserName.Buffer, pSessionData->Session,
788 pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart, pSessionData->LogonType);
789
790 if (vgsvcVMInfoSession0Separation())
791 {
792 /* Starting at Windows Vista user sessions begin with session 1, so
793 * ignore (stale) session 0 users. */
794 if ( pSessionData->Session == 0
795 /* Also check the logon time. */
796 || pSessionData->LogonTime.QuadPart == 0)
797 {
798 g_pfnLsaFreeReturnBuffer(pSessionData);
799 return false;
800 }
801 }
802
803 /*
804 * Only handle users which can login interactively or logged in
805 * remotely over native RDP.
806 */
807 bool fFoundUser = false;
808 if ( IsValidSid(pSessionData->Sid)
809 && ( (SECURITY_LOGON_TYPE)pSessionData->LogonType == Interactive
810 || (SECURITY_LOGON_TYPE)pSessionData->LogonType == RemoteInteractive
811 /* Note: We also need CachedInteractive in case Windows cached the credentials
812 * or just wants to reuse them! */
813 || (SECURITY_LOGON_TYPE)pSessionData->LogonType == CachedInteractive))
814 {
815 VGSvcVerbose(3, "Session LogonType=%u is supported -- looking up SID + type ...\n", pSessionData->LogonType);
816
817 /*
818 * Copy out relevant data.
819 */
820 vgsvcVMInfoWinSafeCopy(pUserInfo->wszUser, sizeof(pUserInfo->wszUser), &pSessionData->UserName, "User name");
821 vgsvcVMInfoWinSafeCopy(pUserInfo->wszAuthenticationPackage, sizeof(pUserInfo->wszAuthenticationPackage),
822 &pSessionData->AuthenticationPackage, "Authentication pkg name");
823 vgsvcVMInfoWinSafeCopy(pUserInfo->wszLogonDomain, sizeof(pUserInfo->wszLogonDomain),
824 &pSessionData->LogonDomain, "Logon domain name");
825
826 TCHAR szOwnerName[_MAX_PATH] = { 0 };
827 DWORD dwOwnerNameSize = sizeof(szOwnerName);
828 TCHAR szDomainName[_MAX_PATH] = { 0 };
829 DWORD dwDomainNameSize = sizeof(szDomainName);
830 SID_NAME_USE enmOwnerType = SidTypeInvalid;
831 if (!LookupAccountSid(NULL,
832 pSessionData->Sid,
833 szOwnerName,
834 &dwOwnerNameSize,
835 szDomainName,
836 &dwDomainNameSize,
837 &enmOwnerType))
838 {
839 DWORD dwErr = GetLastError();
840 /*
841 * If a network time-out prevents the function from finding the name or
842 * if a SID that does not have a corresponding account name (such as a
843 * logon SID that identifies a logon session), we get ERROR_NONE_MAPPED
844 * here that we just skip.
845 */
846 if (dwErr != ERROR_NONE_MAPPED)
847 VGSvcError("Failed looking up account info for user=%ls, error=$ld!\n", pUserInfo->wszUser, dwErr);
848 }
849 else
850 {
851 if (enmOwnerType == SidTypeUser) /* Only recognize users; we don't care about the rest! */
852 {
853 VGSvcVerbose(3, "Account User=%ls, Session=%u, LogonID=%d,%u, AuthPkg=%ls, Domain=%ls\n",
854 pUserInfo->wszUser, pSessionData->Session, pSessionData->LogonId.HighPart,
855 pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage, pUserInfo->wszLogonDomain);
856
857 /**
858 * Note: On certain Windows OSes WTSQuerySessionInformation leaks memory when used
859 * under a heavy stress situation. There are hotfixes available from Microsoft.
860 *
861 * See: http://support.microsoft.com/kb/970910
862 */
863 if (!s_fSkipRDPDetection)
864 {
865 /* Skip RDP detection on non-NT systems. */
866 if (g_WinVersion.dwPlatformId != VER_PLATFORM_WIN32_NT)
867 s_fSkipRDPDetection = true;
868
869 /* Skip RDP detection on Windows 2000.
870 * For Windows 2000 however we don't have any hotfixes, so just skip the
871 * RDP detection in any case. */
872 if ( g_WinVersion.dwMajorVersion == 5
873 && g_WinVersion.dwMinorVersion == 0)
874 s_fSkipRDPDetection = true;
875
876 /* Skip if we don't have the WTS API. */
877 if (!g_pfnWTSQuerySessionInformationA)
878 s_fSkipRDPDetection = true;
879
880 if (s_fSkipRDPDetection)
881 VGSvcVerbose(0, "Detection of logged-in users via RDP is disabled\n");
882 }
883
884 if (!s_fSkipRDPDetection)
885 {
886 Assert(g_pfnWTSQuerySessionInformationA);
887 Assert(g_pfnWTSFreeMemory);
888
889 /* Detect RDP sessions as well. */
890 LPTSTR pBuffer = NULL;
891 DWORD cbRet = 0;
892 int iState = -1;
893 if (g_pfnWTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE,
894 pSessionData->Session,
895 WTSConnectState,
896 &pBuffer,
897 &cbRet))
898 {
899 if (cbRet)
900 iState = *pBuffer;
901 VGSvcVerbose(3, "Account User=%ls, WTSConnectState=%d (%u)\n", pUserInfo->wszUser, iState, cbRet);
902 if ( iState == WTSActive /* User logged on to WinStation. */
903 || iState == WTSShadow /* Shadowing another WinStation. */
904 || iState == WTSDisconnected) /* WinStation logged on without client. */
905 {
906 /** @todo On Vista and W2K, always "old" user name are still
907 * there. Filter out the old one! */
908 VGSvcVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n", pUserInfo->wszUser, iState);
909 fFoundUser = true;
910 }
911 if (pBuffer)
912 g_pfnWTSFreeMemory(pBuffer);
913 }
914 else
915 {
916 DWORD dwLastErr = GetLastError();
917 switch (dwLastErr)
918 {
919 /*
920 * Terminal services don't run (for example in W2K,
921 * nothing to worry about ...). ... or is on the Vista
922 * fast user switching page!
923 */
924 case ERROR_CTX_WINSTATION_NOT_FOUND:
925 VGSvcVerbose(3, "No WinStation found for user=%ls\n", pUserInfo->wszUser);
926 break;
927
928 default:
929 VGSvcVerbose(3, "Cannot query WTS connection state for user=%ls, error=%u\n",
930 pUserInfo->wszUser, dwLastErr);
931 break;
932 }
933
934 fFoundUser = true;
935 }
936 }
937 }
938 else
939 VGSvcVerbose(3, "SID owner type=%d not handled, skipping\n", enmOwnerType);
940 }
941
942 VGSvcVerbose(3, "Account User=%ls %s logged in\n", pUserInfo->wszUser, fFoundUser ? "is" : "is not");
943 }
944
945 if (fFoundUser)
946 pUserInfo->ulLastSession = pSessionData->Session;
947
948 g_pfnLsaFreeReturnBuffer(pSessionData);
949 return fFoundUser;
950}
951
952
953static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain)
954{
955 AssertPtrReturn(pCache, VERR_INVALID_POINTER);
956 AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
957 /* pszDomain is optional. */
958
959 char szPipeName[512 + sizeof(VBOXTRAY_IPC_PIPE_PREFIX)];
960 memcpy(szPipeName, VBOXTRAY_IPC_PIPE_PREFIX, sizeof(VBOXTRAY_IPC_PIPE_PREFIX));
961 int rc = RTStrCat(szPipeName, sizeof(szPipeName), pszUser);
962 if (RT_SUCCESS(rc))
963 {
964 bool fReportToHost = false;
965 VBoxGuestUserState userState = VBoxGuestUserState_Unknown;
966
967 RTLOCALIPCSESSION hSession;
968 rc = RTLocalIpcSessionConnect(&hSession, szPipeName, RTLOCALIPC_FLAGS_NATIVE_NAME);
969 if (RT_SUCCESS(rc))
970 {
971 VBOXTRAYIPCHEADER ipcHdr =
972 {
973 /* .uMagic = */ VBOXTRAY_IPC_HDR_MAGIC,
974 /* .uHdrVersion = */ 0,
975 /* .uMsgType = */ VBOXTRAYIPCMSGTYPE_USERLASTINPUT,
976 /* .cbMsgData = */ 0 /* No msg */
977 };
978
979 rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr));
980
981 if (RT_SUCCESS(rc))
982 {
983 VBOXTRAYIPCRES_USERLASTINPUT ipcRes;
984 rc = RTLocalIpcSessionRead(hSession, &ipcRes, sizeof(ipcRes), NULL /* Exact read */);
985 if ( RT_SUCCESS(rc)
986 /* If uLastInput is set to UINT32_MAX VBoxTray was not able to retrieve the
987 * user's last input time. This might happen when running on Windows NT4 or older. */
988 && ipcRes.uLastInput != UINT32_MAX)
989 {
990 userState = (ipcRes.uLastInput * 1000) < g_uVMInfoUserIdleThresholdMS
991 ? VBoxGuestUserState_InUse
992 : VBoxGuestUserState_Idle;
993
994 rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState",
995 userState == VBoxGuestUserState_InUse ? "InUse" : "Idle");
996
997 /*
998 * Note: vboxServiceUserUpdateF can return VINF_NO_CHANGE in case there wasn't anything
999 * to update. So only report the user's status to host when we really got something
1000 * new.
1001 */
1002 fReportToHost = rc == VINF_SUCCESS;
1003 VGSvcVerbose(4, "User '%s' (domain '%s') is idle for %RU32, fReportToHost=%RTbool\n",
1004 pszUser, pszDomain ? pszDomain : "<None>", ipcRes.uLastInput, fReportToHost);
1005
1006#if 0 /* Do we want to write the idle time as well? */
1007 /* Also write the user's current idle time, if there is any. */
1008 if (userState == VBoxGuestUserState_Idle)
1009 rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", "%RU32", ipcRes.uLastInputMs);
1010 else
1011 rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", NULL /* Delete property */);
1012
1013 if (RT_SUCCESS(rc))
1014#endif
1015 }
1016#ifdef DEBUG
1017 else if (RT_SUCCESS(rc) && ipcRes.uLastInput == UINT32_MAX)
1018 VGSvcVerbose(4, "Last input for user '%s' is not supported, skipping\n", pszUser, rc);
1019#endif
1020 }
1021#ifdef DEBUG
1022 VGSvcVerbose(4, "Getting last input for user '%s' ended with rc=%Rrc\n", pszUser, rc);
1023#endif
1024 int rc2 = RTLocalIpcSessionClose(hSession);
1025 if (RT_SUCCESS(rc) && RT_FAILURE(rc2))
1026 rc = rc2;
1027 }
1028 else
1029 {
1030 switch (rc)
1031 {
1032 case VERR_FILE_NOT_FOUND:
1033 {
1034 /* No VBoxTray (or too old version which does not support IPC) running
1035 for the given user. Not much we can do then. */
1036 VGSvcVerbose(4, "VBoxTray for user '%s' not running (anymore), no last input available\n", pszUser);
1037
1038 /* Overwrite rc from above. */
1039 rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState", "Idle");
1040
1041 fReportToHost = rc == VINF_SUCCESS;
1042 if (fReportToHost)
1043 userState = VBoxGuestUserState_Idle;
1044 break;
1045 }
1046
1047 default:
1048 VGSvcError("Error querying last input for user '%s', rc=%Rrc\n", pszUser, rc);
1049 break;
1050 }
1051 }
1052
1053 if (fReportToHost)
1054 {
1055 Assert(userState != VBoxGuestUserState_Unknown);
1056 int rc2 = VbglR3GuestUserReportState(pszUser, pszDomain, userState, NULL /* No details */, 0);
1057 if (RT_FAILURE(rc2))
1058 VGSvcError("Error reporting usage state %d for user '%s' to host, rc=%Rrc\n", userState, pszUser, rc2);
1059 if (RT_SUCCESS(rc))
1060 rc = rc2;
1061 }
1062 }
1063
1064 return rc;
1065}
1066
1067
1068/**
1069 * Retrieves the currently logged in users and stores their names along with the
1070 * user count.
1071 *
1072 * @returns VBox status code.
1073 * @param pCache Property cache to use for storing some of the lookup
1074 * data in between calls.
1075 * @param ppszUserList Where to store the user list (separated by commas).
1076 * Must be freed with RTStrFree().
1077 * @param pcUsersInList Where to store the number of users in the list.
1078 */
1079int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList)
1080{
1081 AssertPtrReturn(pCache, VERR_INVALID_POINTER);
1082 AssertPtrReturn(ppszUserList, VERR_INVALID_POINTER);
1083 AssertPtrReturn(pcUsersInList, VERR_INVALID_POINTER);
1084
1085 int rc = RTOnce(&g_vgsvcWinVmInitOnce, vgsvcWinVmInfoInitOnce, NULL);
1086 if (RT_FAILURE(rc))
1087 return rc;
1088 if (!g_pfnLsaEnumerateLogonSessions || !g_pfnEnumProcesses)
1089 return VERR_NOT_SUPPORTED;
1090
1091 rc = VbglR3GuestPropConnect(&s_uDebugGuestPropClientID);
1092 AssertRC(rc);
1093
1094 char *pszUserList = NULL;
1095 uint32_t cUsersInList = 0;
1096
1097 /* This function can report stale or orphaned interactive logon sessions
1098 of already logged off users (especially in Windows 2000). */
1099 PLUID paSessions = NULL;
1100 ULONG cSessions = 0;
1101 NTSTATUS rcNt = g_pfnLsaEnumerateLogonSessions(&cSessions, &paSessions);
1102 if (rcNt != STATUS_SUCCESS)
1103 {
1104 ULONG uError = LsaNtStatusToWinError(rcNt);
1105 switch (uError)
1106 {
1107 case ERROR_NOT_ENOUGH_MEMORY:
1108 VGSvcError("Not enough memory to enumerate logon sessions!\n");
1109 break;
1110
1111 case ERROR_SHUTDOWN_IN_PROGRESS:
1112 /* If we're about to shutdown when we were in the middle of enumerating the logon
1113 * sessions, skip the error to not confuse the user with an unnecessary log message. */
1114 VGSvcVerbose(3, "Shutdown in progress ...\n");
1115 uError = ERROR_SUCCESS;
1116 break;
1117
1118 default:
1119 VGSvcError("LsaEnumerate failed with error %RU32\n", uError);
1120 break;
1121 }
1122
1123 if (paSessions)
1124 g_pfnLsaFreeReturnBuffer(paSessions);
1125
1126 return RTErrConvertFromWin32(uError);
1127 }
1128 VGSvcVerbose(3, "Found %u sessions\n", cSessions);
1129
1130 PVBOXSERVICEVMINFOPROC paProcs;
1131 DWORD cProcs;
1132 rc = vgsvcVMInfoWinProcessesEnumerate(&paProcs, &cProcs);
1133 if (RT_FAILURE(rc))
1134 {
1135 if (rc == VERR_NO_MEMORY)
1136 VGSvcError("Not enough memory to enumerate processes\n");
1137 else
1138 VGSvcError("Failed to enumerate processes, rc=%Rrc\n", rc);
1139 }
1140 else
1141 {
1142 PVBOXSERVICEVMINFOUSER pUserInfo;
1143 pUserInfo = (PVBOXSERVICEVMINFOUSER)RTMemAllocZ(cSessions * sizeof(VBOXSERVICEVMINFOUSER) + 1);
1144 if (!pUserInfo)
1145 VGSvcError("Not enough memory to store enumerated users!\n");
1146 else
1147 {
1148 ULONG cUniqueUsers = 0;
1149
1150 /*
1151 * Note: The cSessions loop variable does *not* correlate with
1152 * the Windows session ID!
1153 */
1154 for (ULONG i = 0; i < cSessions; i++)
1155 {
1156 VGSvcVerbose(3, "Handling session %RU32 (of %RU32)\n", i + 1, cSessions);
1157
1158 VBOXSERVICEVMINFOUSER userSession;
1159 if (vgsvcVMInfoWinIsLoggedIn(&userSession, &paSessions[i]))
1160 {
1161 VGSvcVerbose(4, "Handling user=%ls, domain=%ls, package=%ls, session=%RU32\n",
1162 userSession.wszUser, userSession.wszLogonDomain, userSession.wszAuthenticationPackage,
1163 userSession.ulLastSession);
1164
1165 /* Retrieve assigned processes of current session. */
1166 uint32_t cCurSessionProcs = vgsvcVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs,
1167 NULL /* Terminal session ID */);
1168 /* Don't return here when current session does not have assigned processes
1169 * anymore -- in that case we have to search through the unique users list below
1170 * and see if got a stale user/session entry. */
1171
1172 if (g_cVerbosity > 3)
1173 {
1174 char szDebugSessionPath[255];
1175 RTStrPrintf(szDebugSessionPath, sizeof(szDebugSessionPath),
1176 "/VirtualBox/GuestInfo/Debug/LSA/Session/%RU32", userSession.ulLastSession);
1177 VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugSessionPath,
1178 "#%RU32: cSessionProcs=%RU32 (of %RU32 procs total)",
1179 s_uDebugIter, cCurSessionProcs, cProcs);
1180 }
1181
1182 bool fFoundUser = false;
1183 for (ULONG a = 0; a < cUniqueUsers; a++)
1184 {
1185 PVBOXSERVICEVMINFOUSER pCurUser = &pUserInfo[a];
1186 AssertPtr(pCurUser);
1187
1188 if ( !wcscmp(userSession.wszUser, pCurUser->wszUser)
1189 && !wcscmp(userSession.wszLogonDomain, pCurUser->wszLogonDomain)
1190 && !wcscmp(userSession.wszAuthenticationPackage, pCurUser->wszAuthenticationPackage))
1191 {
1192 /*
1193 * Only respect the highest session for the current user.
1194 */
1195 if (userSession.ulLastSession > pCurUser->ulLastSession)
1196 {
1197 VGSvcVerbose(4, "Updating user=%ls to %u processes (last used session: %RU32)\n",
1198 pCurUser->wszUser, cCurSessionProcs, userSession.ulLastSession);
1199
1200 if (!cCurSessionProcs)
1201 VGSvcVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n",
1202 pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs,
1203 pCurUser->ulLastSession, userSession.ulLastSession);
1204
1205 pCurUser->ulNumProcs = cCurSessionProcs;
1206 pCurUser->ulLastSession = userSession.ulLastSession;
1207 }
1208 /* There can be multiple session objects using the same session ID for the
1209 * current user -- so when we got the same session again just add the found
1210 * processes to it. */
1211 else if (pCurUser->ulLastSession == userSession.ulLastSession)
1212 {
1213 VGSvcVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n",
1214 pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, pCurUser->ulLastSession);
1215
1216 pCurUser->ulNumProcs = cCurSessionProcs;
1217 }
1218
1219 fFoundUser = true;
1220 break;
1221 }
1222 }
1223
1224 if (!fFoundUser)
1225 {
1226 VGSvcVerbose(4, "Adding new user=%ls (session=%RU32) with %RU32 processes\n",
1227 userSession.wszUser, userSession.ulLastSession, cCurSessionProcs);
1228
1229 memcpy(&pUserInfo[cUniqueUsers], &userSession, sizeof(VBOXSERVICEVMINFOUSER));
1230 pUserInfo[cUniqueUsers].ulNumProcs = cCurSessionProcs;
1231 cUniqueUsers++;
1232 Assert(cUniqueUsers <= cSessions);
1233 }
1234 }
1235 }
1236
1237 if (g_cVerbosity > 3)
1238 VGSvcWritePropF(s_uDebugGuestPropClientID, "/VirtualBox/GuestInfo/Debug/LSA",
1239 "#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32",
1240 s_uDebugIter, cSessions, cProcs, cUniqueUsers);
1241
1242 VGSvcVerbose(3, "Found %u unique logged-in user(s)\n", cUniqueUsers);
1243
1244 for (ULONG i = 0; i < cUniqueUsers; i++)
1245 {
1246 if (g_cVerbosity > 3)
1247 {
1248 char szDebugUserPath[255]; RTStrPrintf(szDebugUserPath, sizeof(szDebugUserPath), "/VirtualBox/GuestInfo/Debug/LSA/User/%RU32", i);
1249 VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugUserPath,
1250 "#%RU32: szName=%ls, sessionID=%RU32, cProcs=%RU32",
1251 s_uDebugIter, pUserInfo[i].wszUser, pUserInfo[i].ulLastSession, pUserInfo[i].ulNumProcs);
1252 }
1253
1254 bool fAddUser = false;
1255 if (pUserInfo[i].ulNumProcs)
1256 fAddUser = true;
1257
1258 if (fAddUser)
1259 {
1260 VGSvcVerbose(3, "User '%ls' has %RU32 interactive processes (session=%RU32)\n",
1261 pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulLastSession);
1262
1263 if (cUsersInList > 0)
1264 {
1265 rc = RTStrAAppend(&pszUserList, ",");
1266 AssertRCBreakStmt(rc, RTStrFree(pszUserList));
1267 }
1268
1269 cUsersInList += 1;
1270
1271 char *pszUser = NULL;
1272 char *pszDomain = NULL;
1273 rc = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszUser);
1274 if ( RT_SUCCESS(rc)
1275 && pUserInfo[i].wszLogonDomain)
1276 rc = RTUtf16ToUtf8(pUserInfo[i].wszLogonDomain, &pszDomain);
1277 if (RT_SUCCESS(rc))
1278 {
1279 /* Append user to users list. */
1280 rc = RTStrAAppend(&pszUserList, pszUser);
1281
1282 /* Do idle detection. */
1283 if (RT_SUCCESS(rc))
1284 rc = vgsvcVMInfoWinWriteLastInput(pCache, pszUser, pszDomain);
1285 }
1286 else
1287 rc = RTStrAAppend(&pszUserList, "<string-conversion-error>");
1288
1289 RTStrFree(pszUser);
1290 RTStrFree(pszDomain);
1291
1292 AssertRCBreakStmt(rc, RTStrFree(pszUserList));
1293 }
1294 }
1295
1296 RTMemFree(pUserInfo);
1297 }
1298 vgsvcVMInfoWinProcessesFree(cProcs, paProcs);
1299 }
1300 if (paSessions)
1301 g_pfnLsaFreeReturnBuffer(paSessions);
1302
1303 if (RT_SUCCESS(rc))
1304 {
1305 *ppszUserList = pszUserList;
1306 *pcUsersInList = cUsersInList;
1307 }
1308
1309 s_uDebugIter++;
1310 VbglR3GuestPropDisconnect(s_uDebugGuestPropClientID);
1311
1312 return rc;
1313}
1314
1315
1316int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID)
1317{
1318 int rc;
1319 char szSysDir[_MAX_PATH] = {0};
1320 char szWinDir[_MAX_PATH] = {0};
1321 char szDriversDir[_MAX_PATH + 32] = {0};
1322
1323 /* ASSUME: szSysDir and szWinDir and derivatives are always ASCII compatible. */
1324 GetSystemDirectory(szSysDir, _MAX_PATH);
1325 GetWindowsDirectory(szWinDir, _MAX_PATH);
1326 RTStrPrintf(szDriversDir, sizeof(szDriversDir), "%s\\drivers", szSysDir);
1327#ifdef RT_ARCH_AMD64
1328 char szSysWowDir[_MAX_PATH + 32] = {0};
1329 RTStrPrintf(szSysWowDir, sizeof(szSysWowDir), "%s\\SysWow64", szWinDir);
1330#endif
1331
1332 /* The file information table. */
1333 const VBOXSERVICEVMINFOFILE aVBoxFiles[] =
1334 {
1335 { szSysDir, "VBoxControl.exe" },
1336 { szSysDir, "VBoxHook.dll" },
1337 { szSysDir, "VBoxDisp.dll" },
1338 { szSysDir, "VBoxTray.exe" },
1339 { szSysDir, "VBoxService.exe" },
1340 { szSysDir, "VBoxMRXNP.dll" },
1341 { szSysDir, "VBoxGINA.dll" },
1342 { szSysDir, "VBoxCredProv.dll" },
1343
1344 /* On 64-bit we don't yet have the OpenGL DLLs in native format.
1345 So just enumerate the 32-bit files in the SYSWOW directory. */
1346#ifdef RT_ARCH_AMD64
1347 { szSysWowDir, "VBoxOGLarrayspu.dll" },
1348 { szSysWowDir, "VBoxOGLcrutil.dll" },
1349 { szSysWowDir, "VBoxOGLerrorspu.dll" },
1350 { szSysWowDir, "VBoxOGLpackspu.dll" },
1351 { szSysWowDir, "VBoxOGLpassthroughspu.dll" },
1352 { szSysWowDir, "VBoxOGLfeedbackspu.dll" },
1353 { szSysWowDir, "VBoxOGL.dll" },
1354#else /* !RT_ARCH_AMD64 */
1355 { szSysDir, "VBoxOGLarrayspu.dll" },
1356 { szSysDir, "VBoxOGLcrutil.dll" },
1357 { szSysDir, "VBoxOGLerrorspu.dll" },
1358 { szSysDir, "VBoxOGLpackspu.dll" },
1359 { szSysDir, "VBoxOGLpassthroughspu.dll" },
1360 { szSysDir, "VBoxOGLfeedbackspu.dll" },
1361 { szSysDir, "VBoxOGL.dll" },
1362#endif /* !RT_ARCH_AMD64 */
1363
1364 { szDriversDir, "VBoxGuest.sys" },
1365 { szDriversDir, "VBoxMouseNT.sys" },
1366 { szDriversDir, "VBoxMouse.sys" },
1367 { szDriversDir, "VBoxSF.sys" },
1368 { szDriversDir, "VBoxVideo.sys" },
1369 };
1370
1371 for (unsigned i = 0; i < RT_ELEMENTS(aVBoxFiles); i++)
1372 {
1373 char szVer[128];
1374 rc = VGSvcUtilWinGetFileVersionString(aVBoxFiles[i].pszFilePath, aVBoxFiles[i].pszFileName, szVer, sizeof(szVer));
1375 char szPropPath[256];
1376 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestAdd/Components/%s", aVBoxFiles[i].pszFileName);
1377 if ( rc != VERR_FILE_NOT_FOUND
1378 && rc != VERR_PATH_NOT_FOUND)
1379 VGSvcWritePropF(uClientID, szPropPath, "%s", szVer);
1380 else
1381 VGSvcWritePropF(uClientID, szPropPath, NULL);
1382 }
1383
1384 return VINF_SUCCESS;
1385}
1386
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette