VirtualBox

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

Last change on this file since 98831 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

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

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