VirtualBox

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

Last change on this file since 83958 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

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