VirtualBox

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

Last change on this file was 102772, checked in by vboxsync, 4 months ago

Guest Properties/VBoxService: On user RID fallback, also write the resolved user name into a dedicated key, so that it's easier to look it up for the host [build fix]. ​bugref:10575

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 62.2 KB
Line 
1/* $Id: VBoxServiceVMInfo-win.cpp 102772 2024-01-05 08:54:11Z 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 <sddl.h> /* For ConvertSidToStringSidW. */
40#include <Ntsecapi.h> /* Needed for process security information. */
41
42#include <iprt/assert.h>
43#include <iprt/ldr.h>
44#include <iprt/localipc.h>
45#include <iprt/mem.h>
46#include <iprt/once.h>
47#include <iprt/process.h>
48#include <iprt/string.h>
49#include <iprt/semaphore.h>
50#include <iprt/system.h>
51#include <iprt/time.h>
52#include <iprt/thread.h>
53#include <iprt/utf16.h>
54
55#include <VBox/VBoxGuestLib.h>
56#include "VBoxServiceInternal.h"
57#include "VBoxServiceUtils.h"
58#include "VBoxServiceVMInfo.h"
59#include "../../WINNT/VBoxTray/VBoxTrayMsg.h" /* For IPC. */
60
61
62/*********************************************************************************************************************************
63* Structures and Typedefs *
64*********************************************************************************************************************************/
65/* advapi32.dll: */
66typedef BOOL (WINAPI *PFNCONVERTSIDTOSTRINGSIDW)(PSID, LPWSTR *);
67
68/* advapi32.dll: */
69static PFNCONVERTSIDTOSTRINGSIDW g_pfnConvertSidToStringSidW = NULL;
70
71/** Structure for storing the looked up user information. */
72typedef struct VBOXSERVICEVMINFOUSER
73{
74 WCHAR wszUser[MAX_PATH];
75 WCHAR wszAuthenticationPackage[MAX_PATH];
76 WCHAR wszLogonDomain[MAX_PATH];
77 /** Number of assigned user processes. */
78 ULONG ulNumProcs;
79 /** Last (highest) session ID. This
80 * is needed for distinguishing old session
81 * process counts from new (current) session
82 * ones. */
83 ULONG ulLastSession;
84} VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER;
85
86/** Structure for the file information lookup. */
87typedef struct VBOXSERVICEVMINFOFILE
88{
89 const char *pszFilePath;
90 const char *pszFileName;
91} VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE;
92
93/** Structure for process information lookup. */
94typedef struct VBOXSERVICEVMINFOPROC
95{
96 /** The PID. */
97 DWORD id;
98 /** The SID. */
99 PSID pSid;
100 /** The LUID. */
101 LUID luid;
102 /** Interactive process. */
103 bool fInteractive;
104} VBOXSERVICEVMINFOPROC, *PVBOXSERVICEVMINFOPROC;
105
106
107/*********************************************************************************************************************************
108* Internal Functions *
109*********************************************************************************************************************************/
110static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs);
111static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession);
112static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount);
113static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs);
114static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain);
115
116
117/*********************************************************************************************************************************
118* Global Variables *
119*********************************************************************************************************************************/
120static uint32_t s_uDebugGuestPropClientID = 0;
121static uint32_t s_uDebugIter = 0;
122/** Whether to skip the logged-in user detection over RDP or not.
123 * See notes in this section why we might want to skip this. */
124static bool s_fSkipRDPDetection = false;
125
126static RTONCE g_vgsvcWinVmInitOnce = RTONCE_INITIALIZER;
127
128/** @name Secur32.dll imports are dynamically resolved because of NT4.
129 * @{ */
130static decltype(LsaGetLogonSessionData) *g_pfnLsaGetLogonSessionData = NULL;
131static decltype(LsaEnumerateLogonSessions) *g_pfnLsaEnumerateLogonSessions = NULL;
132static decltype(LsaFreeReturnBuffer) *g_pfnLsaFreeReturnBuffer = NULL;
133/** @} */
134
135/** @name WtsApi32.dll imports are dynamically resolved because of NT4.
136 * @{ */
137static decltype(WTSFreeMemory) *g_pfnWTSFreeMemory = NULL;
138static decltype(WTSQuerySessionInformationA) *g_pfnWTSQuerySessionInformationA = NULL;
139/** @} */
140
141/** @name PsApi.dll imports are dynamically resolved because of NT4.
142 * @{ */
143static decltype(EnumProcesses) *g_pfnEnumProcesses = NULL;
144static decltype(GetModuleFileNameExW) *g_pfnGetModuleFileNameExW = NULL;
145/** @} */
146
147/** @name New Kernel32.dll APIs we may use when present.
148 * @{ */
149static decltype(QueryFullProcessImageNameW) *g_pfnQueryFullProcessImageNameW = NULL;
150
151/** @} */
152
153
154/**
155 * An RTOnce callback function.
156 */
157static DECLCALLBACK(int) vgsvcWinVmInfoInitOnce(void *pvIgnored)
158{
159 RT_NOREF1(pvIgnored);
160
161 /* SECUR32 */
162 RTLDRMOD hLdrMod;
163 int rc = RTLdrLoadSystem("secur32.dll", true /*fNoUnload*/, &hLdrMod);
164 if (RT_SUCCESS(rc))
165 {
166 rc = RTLdrGetSymbol(hLdrMod, "LsaGetLogonSessionData", (void **)&g_pfnLsaGetLogonSessionData);
167 if (RT_SUCCESS(rc))
168 rc = RTLdrGetSymbol(hLdrMod, "LsaEnumerateLogonSessions", (void **)&g_pfnLsaEnumerateLogonSessions);
169 if (RT_SUCCESS(rc))
170 rc = RTLdrGetSymbol(hLdrMod, "LsaFreeReturnBuffer", (void **)&g_pfnLsaFreeReturnBuffer);
171 AssertRC(rc);
172 RTLdrClose(hLdrMod);
173 }
174 if (RT_FAILURE(rc))
175 {
176 VGSvcVerbose(1, "Secur32.dll APIs are not available (%Rrc)\n", rc);
177 g_pfnLsaGetLogonSessionData = NULL;
178 g_pfnLsaEnumerateLogonSessions = NULL;
179 g_pfnLsaFreeReturnBuffer = NULL;
180 Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
181 }
182
183 /* WTSAPI32 */
184 rc = RTLdrLoadSystem("wtsapi32.dll", true /*fNoUnload*/, &hLdrMod);
185 if (RT_SUCCESS(rc))
186 {
187 rc = RTLdrGetSymbol(hLdrMod, "WTSFreeMemory", (void **)&g_pfnWTSFreeMemory);
188 if (RT_SUCCESS(rc))
189 rc = RTLdrGetSymbol(hLdrMod, "WTSQuerySessionInformationA", (void **)&g_pfnWTSQuerySessionInformationA);
190 AssertRC(rc);
191 RTLdrClose(hLdrMod);
192 }
193 if (RT_FAILURE(rc))
194 {
195 VGSvcVerbose(1, "WtsApi32.dll APIs are not available (%Rrc)\n", rc);
196 g_pfnWTSFreeMemory = NULL;
197 g_pfnWTSQuerySessionInformationA = NULL;
198 Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
199 }
200
201 /* PSAPI */
202 rc = RTLdrLoadSystem("psapi.dll", true /*fNoUnload*/, &hLdrMod);
203 if (RT_SUCCESS(rc))
204 {
205 rc = RTLdrGetSymbol(hLdrMod, "EnumProcesses", (void **)&g_pfnEnumProcesses);
206 if (RT_SUCCESS(rc))
207 rc = RTLdrGetSymbol(hLdrMod, "GetModuleFileNameExW", (void **)&g_pfnGetModuleFileNameExW);
208 AssertRC(rc);
209 RTLdrClose(hLdrMod);
210 }
211 if (RT_FAILURE(rc))
212 {
213 VGSvcVerbose(1, "psapi.dll APIs are not available (%Rrc)\n", rc);
214 g_pfnEnumProcesses = NULL;
215 g_pfnGetModuleFileNameExW = NULL;
216 Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
217 }
218
219 /* Kernel32: */
220 rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod);
221 AssertRCReturn(rc, rc);
222 rc = RTLdrGetSymbol(hLdrMod, "QueryFullProcessImageNameW", (void **)&g_pfnQueryFullProcessImageNameW);
223 if (RT_FAILURE(rc))
224 {
225 Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(6, 0, 0));
226 g_pfnQueryFullProcessImageNameW = NULL;
227 }
228 RTLdrClose(hLdrMod);
229
230 /* advapi32: */
231 rc = RTLdrLoadSystem("advapi32.dll", true /*fNoUnload*/, &hLdrMod);
232 if (RT_SUCCESS(rc))
233 RTLdrGetSymbol(hLdrMod, "ConvertSidToStringSidW", (void **)&g_pfnConvertSidToStringSidW);
234
235 return VINF_SUCCESS;
236}
237
238
239static bool vgsvcVMInfoSession0Separation(void)
240{
241 return RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0); /* Vista */
242}
243
244
245/**
246 * Retrieves the module name of a given process.
247 *
248 * @return IPRT status code.
249 */
250static int vgsvcVMInfoWinProcessesGetModuleNameW(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName)
251{
252 *ppszName = NULL;
253 AssertPtrReturn(ppszName, VERR_INVALID_POINTER);
254 AssertPtrReturn(pProc, VERR_INVALID_POINTER);
255 AssertReturn(g_pfnGetModuleFileNameExW || g_pfnQueryFullProcessImageNameW, VERR_NOT_SUPPORTED);
256
257 /*
258 * Open the process.
259 */
260 DWORD dwFlags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
261 if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)) /* Vista and later */
262 dwFlags = PROCESS_QUERY_LIMITED_INFORMATION; /* possible to do on more processes */
263
264 HANDLE hProcess = OpenProcess(dwFlags, FALSE, pProc->id);
265 if (hProcess == NULL)
266 {
267 DWORD dwErr = GetLastError();
268 if (g_cVerbosity)
269 VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr);
270 return RTErrConvertFromWin32(dwErr);
271 }
272
273 /*
274 * Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps
275 * cannot query 64-bit apps and vice verse) we have to use a different code
276 * path for Vista and up.
277 *
278 * So use QueryFullProcessImageNameW when available (Vista+), fall back on
279 * GetModuleFileNameExW on older windows version (
280 */
281 WCHAR wszName[_1K];
282 DWORD dwLen = _1K;
283 BOOL fRc;
284 if (g_pfnQueryFullProcessImageNameW)
285 fRc = g_pfnQueryFullProcessImageNameW(hProcess, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen);
286 else
287 fRc = g_pfnGetModuleFileNameExW(hProcess, NULL /* Get main executable */, wszName, dwLen);
288
289 int rc;
290 if (fRc)
291 rc = RTUtf16DupEx(ppszName, wszName, 0);
292 else
293 {
294 DWORD dwErr = GetLastError();
295 if (g_cVerbosity > 3)
296 VGSvcError("Unable to retrieve process name for PID=%u, LastError=%Rwc\n", pProc->id, dwErr);
297 rc = RTErrConvertFromWin32(dwErr);
298 }
299
300 CloseHandle(hProcess);
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 = vgsvcVMInfoWinProcessesGetModuleNameW(&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 || !g_pfnLsaNtStatusToWinError)
753 return false;
754
755 PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
756 NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData);
757 if (rcNt != STATUS_SUCCESS)
758 {
759 ULONG ulError = g_pfnLsaNtStatusToWinError(rcNt);
760 switch (ulError)
761 {
762 case ERROR_NOT_ENOUGH_MEMORY:
763 /* If we don't have enough memory it's hard to judge whether the specified user
764 * is logged in or not, so just assume he/she's not. */
765 VGSvcVerbose(3, "Not enough memory to retrieve logon session data!\n");
766 break;
767
768 case ERROR_NO_SUCH_LOGON_SESSION:
769 /* Skip session data which is not valid anymore because it may have been
770 * already terminated. */
771 break;
772
773 default:
774 VGSvcError("LsaGetLogonSessionData failed with error %u\n", ulError);
775 break;
776 }
777 if (pSessionData)
778 g_pfnLsaFreeReturnBuffer(pSessionData);
779 return false;
780 }
781 if (!pSessionData)
782 {
783 VGSvcError("Invalid logon session data!\n");
784 return false;
785 }
786
787 VGSvcVerbose(3, "Session data: Name=%ls, SessionID=%RU32, LogonID=%d,%u, LogonType=%u\n",
788 pSessionData->UserName.Buffer, pSessionData->Session,
789 pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart, pSessionData->LogonType);
790
791 if (vgsvcVMInfoSession0Separation())
792 {
793 /* Starting at Windows Vista user sessions begin with session 1, so
794 * ignore (stale) session 0 users. */
795 if ( pSessionData->Session == 0
796 /* Also check the logon time. */
797 || pSessionData->LogonTime.QuadPart == 0)
798 {
799 g_pfnLsaFreeReturnBuffer(pSessionData);
800 return false;
801 }
802 }
803
804 /*
805 * Only handle users which can login interactively or logged in
806 * remotely over native RDP.
807 */
808 bool fFoundUser = false;
809 if ( IsValidSid(pSessionData->Sid)
810 && ( (SECURITY_LOGON_TYPE)pSessionData->LogonType == Interactive
811 || (SECURITY_LOGON_TYPE)pSessionData->LogonType == RemoteInteractive
812 /* Note: We also need CachedInteractive in case Windows cached the credentials
813 * or just wants to reuse them! */
814 || (SECURITY_LOGON_TYPE)pSessionData->LogonType == CachedInteractive))
815 {
816 VGSvcVerbose(3, "Session LogonType=%u is supported -- looking up SID + type ...\n", pSessionData->LogonType);
817
818 /*
819 * Copy out relevant data.
820 */
821 vgsvcVMInfoWinSafeCopy(pUserInfo->wszUser, sizeof(pUserInfo->wszUser), &pSessionData->UserName, "User name");
822 vgsvcVMInfoWinSafeCopy(pUserInfo->wszAuthenticationPackage, sizeof(pUserInfo->wszAuthenticationPackage),
823 &pSessionData->AuthenticationPackage, "Authentication pkg name");
824 vgsvcVMInfoWinSafeCopy(pUserInfo->wszLogonDomain, sizeof(pUserInfo->wszLogonDomain),
825 &pSessionData->LogonDomain, "Logon domain name");
826
827 TCHAR szOwnerName[MAX_PATH] = { 0 };
828 DWORD dwOwnerNameSize = sizeof(szOwnerName);
829 TCHAR szDomainName[MAX_PATH] = { 0 };
830 DWORD dwDomainNameSize = sizeof(szDomainName);
831 SID_NAME_USE enmOwnerType = SidTypeInvalid;
832 if (!LookupAccountSid(NULL,
833 pSessionData->Sid,
834 szOwnerName,
835 &dwOwnerNameSize,
836 szDomainName,
837 &dwDomainNameSize,
838 &enmOwnerType))
839 {
840 DWORD dwErr = GetLastError();
841 /*
842 * If a network time-out prevents the function from finding the name or
843 * if a SID that does not have a corresponding account name (such as a
844 * logon SID that identifies a logon session), we get ERROR_NONE_MAPPED
845 * here that we just skip.
846 */
847 if (dwErr != ERROR_NONE_MAPPED)
848 VGSvcError("Failed looking up account info for user=%ls, error=$ld!\n", pUserInfo->wszUser, dwErr);
849 }
850 else
851 {
852 if (enmOwnerType == SidTypeUser) /* Only recognize users; we don't care about the rest! */
853 {
854 VGSvcVerbose(3, "Account User=%ls, Session=%u, LogonID=%d,%u, AuthPkg=%ls, Domain=%ls\n",
855 pUserInfo->wszUser, pSessionData->Session, pSessionData->LogonId.HighPart,
856 pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage, pUserInfo->wszLogonDomain);
857
858 /* KB970910 (check http://support.microsoft.com/kb/970910 on archive.org)
859 * indicates that WTSQuerySessionInformation may leak memory and return the
860 * wrong status code for WTSApplicationName and WTSInitialProgram queries.
861 *
862 * The system must be low on resources, and presumably some internal operation
863 * must fail because of this, triggering an error handling path that forgets
864 * to free memory and set last error.
865 *
866 * bird 2022-08-26: However, we do not query either of those info items. We
867 * query WTSConnectState, which is a rather simple affair. So, I've
868 * re-enabled the code for all systems that includes the API.
869 */
870 if (!s_fSkipRDPDetection)
871 {
872 /* Skip if we don't have the WTS API. */
873 if (!g_pfnWTSQuerySessionInformationA)
874 s_fSkipRDPDetection = true;
875#if 0 /* bird: see above */
876 /* Skip RDP detection on Windows 2000 and older.
877 For Windows 2000 however we don't have any hotfixes, so just skip the
878 RDP detection in any case. */
879 else if (RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 1, 0)) /* older than XP */
880 s_fSkipRDPDetection = true;
881#endif
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
955/**
956 * Destroys an allocated SID.
957 *
958 * @param pSid SID to dsetroy. The pointer will be invalid on return.
959 */
960static void vgsvcVMInfoWinUserSidDestroy(PSID pSid)
961{
962 RTMemFree(pSid);
963 pSid = NULL;
964}
965
966
967/**
968 * Looks up and returns a SID for a given user.
969 *
970 * @returns VBox status code.
971 * @param pszUser User to look up a SID for.
972 * @param ppSid Where to return the allocated SID.
973 * Must be destroyed with vgsvcVMInfoWinUserSidDestroy().
974 */
975static int vgsvcVMInfoWinUserSidLookup(const char *pszUser, PSID *ppSid)
976{
977 RTUTF16 *pwszUser = NULL;
978 size_t cwUser = 0;
979 int rc = RTStrToUtf16Ex(pszUser, RTSTR_MAX, &pwszUser, 0, &cwUser);
980 AssertRCReturn(rc, rc);
981
982 PSID pSid = NULL;
983 DWORD cbSid = 0;
984 DWORD cbDomain = 0;
985 SID_NAME_USE enmSidUse = SidTypeUser;
986 if (!LookupAccountNameW(NULL, pwszUser, pSid, &cbSid, NULL, &cbDomain, &enmSidUse))
987 {
988 DWORD const dwErr = GetLastError();
989 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
990 {
991 pSid = (PSID)RTMemAllocZ(cbSid);
992 if (pSid)
993 {
994 PRTUTF16 pwszDomain = (PRTUTF16)RTMemAllocZ(cbDomain * sizeof(RTUTF16));
995 if (pwszDomain)
996 {
997 if (LookupAccountNameW(NULL, pwszUser, pSid, &cbSid, pwszDomain, &cbDomain, &enmSidUse))
998 {
999 if (IsValidSid(pSid))
1000 *ppSid = pSid;
1001 else
1002 rc = VERR_INVALID_PARAMETER;
1003 }
1004 else
1005 rc = RTErrConvertFromWin32(GetLastError());
1006 }
1007 else
1008 rc = VERR_NO_MEMORY;
1009 }
1010 else
1011 rc = VERR_NO_MEMORY;
1012 }
1013 else
1014 rc = RTErrConvertFromWin32(dwErr);
1015 }
1016 else
1017 rc = RTErrConvertFromWin32(GetLastError());
1018
1019 if (RT_FAILURE(rc))
1020 vgsvcVMInfoWinUserSidDestroy(pSid);
1021
1022 return rc;
1023}
1024
1025
1026/**
1027 * Fallback function in case writing the user name failed within vgsvcVMInfoWinUserUpdateF().
1028 *
1029 * This uses the following approach:
1030 * - only use the user name as part of the property name from now on
1031 * - write the domain name into a separate "Domain" property
1032 * - write the (full) SID into a separate "SID" property
1033 *
1034 * @returns VBox status code.
1035 * @retval VERR_BUFFER_OVERFLOW if the final property name length exceeds the maximum supported length.
1036 * @param pCache Pointer to guest property cache to update user in.
1037 * @param pszUser Name of guest user to update.
1038 * @param pszDomain Domain of guest user to update. Optional.
1039 * @param pwszSid The user's SID as a string. Might be NULL if not supported (NT4).
1040 * @param pszKey Key name of guest property to update.
1041 * @param pszValueFormat Guest property value to set. Pass NULL for deleting
1042 * the property.
1043 * @param va Variable arguments.
1044 */
1045int vgsvcVMInfoWinUserUpdateFallbackV(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain,
1046 WCHAR *pwszSid, const char *pszKey, const char *pszValueFormat, va_list va)
1047{
1048 int rc = VGSvcUserUpdateF(pCache, pszUser, NULL /* pszDomain */, "Domain", pszDomain);
1049 if (pwszSid && RT_SUCCESS(rc))
1050 rc = VGSvcUserUpdateF(pCache, pszUser, NULL /* pszDomain */, "SID", "%ls", pwszSid);
1051
1052 /* Last but no least, write the actual guest property value we initially were called for.
1053 * We always do this, no matter of what the outcome from above was. */
1054 int rc2 = VGSvcUserUpdateV(pCache, pszUser, NULL /* pszDomain */, pszKey, pszValueFormat, va);
1055 if (RT_SUCCESS(rc))
1056 rc2 = rc;
1057
1058 return rc;
1059}
1060
1061
1062/**
1063 * Wrapper function for VGSvcUserUpdateF() that deals with too long guest property names.
1064 *
1065 * @return VBox status code.
1066 * @retval VERR_BUFFER_OVERFLOW if the final property name length exceeds the maximum supported length.
1067 * @param pCache Pointer to guest property cache to update user in.
1068 * @param pszUser Name of guest user to update.
1069 * @param pszDomain Domain of guest user to update. Optional.
1070 * @param pszKey Key name of guest property to update.
1071 * @param pszValueFormat Guest property value to set. Pass NULL for deleting
1072 * the property.
1073 * @param ... Variable arguments.
1074 */
1075int vgsvcVMInfoWinUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain,
1076 const char *pszKey, const char *pszValueFormat, ...)
1077{
1078 va_list va;
1079 va_start(va, pszValueFormat);
1080
1081 /* First, try to write stuff as we always did, to not break older VBox versions. */
1082 int rc = VGSvcUserUpdateV(pCache, pszUser, pszDomain, pszKey, pszValueFormat, va);
1083 if (rc == VERR_BUFFER_OVERFLOW)
1084 {
1085 /**
1086 * If the constructed property name was too long, we have to be a little more creative here:
1087 *
1088 * - only use the user name as part of the property name from now on
1089 * - write the domain name into a separate "Domain" property
1090 * - write the (full) SID into a separate "SID" property
1091 */
1092 PSID pSid;
1093 rc = vgsvcVMInfoWinUserSidLookup(pszUser, &pSid); /** @todo Shall we cache this? */
1094 if (RT_SUCCESS(rc))
1095 {
1096 WCHAR *pwszSid = NULL;
1097 if (g_pfnConvertSidToStringSidW)
1098 g_pfnConvertSidToStringSidW(pSid, &pwszSid); /** @todo Ditto. */
1099
1100 rc = vgsvcVMInfoWinUserUpdateFallbackV(pCache, pszUser, pszDomain, pwszSid, pszKey, pszValueFormat, va);
1101 if (RT_FAILURE(rc))
1102 {
1103 /**
1104 * If using the sole user name as a property name still is too long or something else failed,
1105 * at least try to look up the user's RID (relative identifier). Note that the RID always is bound to the
1106 * to the authority that issued the SID.
1107 */
1108 int const cSubAuth = *GetSidSubAuthorityCount(pSid);
1109 if (cSubAuth > 1)
1110 {
1111 DWORD const dwUserRid = *GetSidSubAuthority(pSid, cSubAuth - 1);
1112 char szUserRid[16 + 1];
1113 if (RTStrPrintf2(szUserRid, sizeof(szUserRid), "%ld", dwUserRid) > 0)
1114 {
1115 rc = vgsvcVMInfoWinUserUpdateFallbackV(pCache, szUserRid, pszDomain, pwszSid, pszKey,
1116 pszValueFormat, va);
1117 /* Also write the resolved user name into a dedicated key,
1118 * so that it's easier to look it up for the host. */
1119 if (RT_SUCCESS(rc))
1120 rc = VGSvcUserUpdateF(pCache, szUserRid, NULL /* pszDomain */, "User", pszUser);
1121 }
1122 else
1123 rc = VERR_BUFFER_OVERFLOW;
1124 }
1125 /* else not much else we can do then. */
1126 }
1127
1128 if (pwszSid)
1129 {
1130 LocalFree(pwszSid);
1131 pwszSid = NULL;
1132 }
1133
1134 vgsvcVMInfoWinUserSidDestroy(pSid);
1135 }
1136 else
1137 VGSvcError("Looking up SID for user '%s' (domain '%s') failed with %Rrc\n", pszUser, pszDomain, rc);
1138 }
1139 va_end(va);
1140 return rc;
1141}
1142
1143
1144static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain)
1145{
1146 AssertPtrReturn(pCache, VERR_INVALID_POINTER);
1147 AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
1148 /* pszDomain is optional. */
1149
1150 char szPipeName[512 + sizeof(VBOXTRAY_IPC_PIPE_PREFIX)];
1151 memcpy(szPipeName, VBOXTRAY_IPC_PIPE_PREFIX, sizeof(VBOXTRAY_IPC_PIPE_PREFIX));
1152 int rc = RTStrCat(szPipeName, sizeof(szPipeName), pszUser);
1153 if (RT_SUCCESS(rc))
1154 {
1155 bool fReportToHost = false;
1156 VBoxGuestUserState userState = VBoxGuestUserState_Unknown;
1157
1158 RTLOCALIPCSESSION hSession;
1159 rc = RTLocalIpcSessionConnect(&hSession, szPipeName, RTLOCALIPC_FLAGS_NATIVE_NAME);
1160 if (RT_SUCCESS(rc))
1161 {
1162 VBOXTRAYIPCHEADER ipcHdr =
1163 {
1164 /* .uMagic = */ VBOXTRAY_IPC_HDR_MAGIC,
1165 /* .uVersion = */ VBOXTRAY_IPC_HDR_VERSION,
1166 /* .enmMsgType = */ VBOXTRAYIPCMSGTYPE_USER_LAST_INPUT,
1167 /* .cbPayload = */ 0 /* No payload */
1168 };
1169
1170 rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr));
1171 if (RT_SUCCESS(rc))
1172 {
1173 VBOXTRAYIPCREPLY_USER_LAST_INPUT_T ipcReply;
1174 rc = RTLocalIpcSessionRead(hSession, &ipcReply, sizeof(ipcReply), NULL /* Exact read */);
1175 if ( RT_SUCCESS(rc)
1176 /* If uLastInput is set to UINT32_MAX VBoxTray was not able to retrieve the
1177 * user's last input time. This might happen when running on Windows NT4 or older. */
1178 && ipcReply.cSecSinceLastInput != UINT32_MAX)
1179 {
1180 userState = ipcReply.cSecSinceLastInput * 1000 < g_uVMInfoUserIdleThresholdMS
1181 ? VBoxGuestUserState_InUse
1182 : VBoxGuestUserState_Idle;
1183
1184 rc = vgsvcVMInfoWinUserUpdateF(pCache, pszUser, pszDomain, "UsageState",
1185 userState == VBoxGuestUserState_InUse ? "InUse" : "Idle");
1186 /*
1187 * Note: vboxServiceUserUpdateF can return VINF_NO_CHANGE in case there wasn't anything
1188 * to update. So only report the user's status to host when we really got something
1189 * new.
1190 */
1191 fReportToHost = rc == VINF_SUCCESS;
1192 VGSvcVerbose(4, "User '%s' (domain '%s') is idle for %RU32, fReportToHost=%RTbool\n",
1193 pszUser, pszDomain ? pszDomain : "<None>", ipcReply.cSecSinceLastInput, fReportToHost);
1194
1195#if 0 /* Do we want to write the idle time as well? */
1196 /* Also write the user's current idle time, if there is any. */
1197 if (userState == VBoxGuestUserState_Idle)
1198 rc = vgsvcVMInfoWinUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", "%RU32",
1199 ipcReply.cSecSinceLastInput);
1200 else
1201 rc = vgsvcVMInfoWinUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs",
1202 NULL /* Delete property */);
1203 if (RT_SUCCESS(rc))
1204#endif
1205 }
1206#ifdef DEBUG
1207 else if (RT_SUCCESS(rc) && ipcReply.cSecSinceLastInput == UINT32_MAX)
1208 VGSvcVerbose(4, "Last input for user '%s' is not supported, skipping\n", pszUser, rc);
1209#endif
1210 }
1211#ifdef DEBUG
1212 VGSvcVerbose(4, "Getting last input for user '%s' ended with rc=%Rrc\n", pszUser, rc);
1213#endif
1214 int rc2 = RTLocalIpcSessionClose(hSession);
1215 if (RT_SUCCESS(rc) && RT_FAILURE(rc2))
1216 rc = rc2;
1217 }
1218 else
1219 {
1220 switch (rc)
1221 {
1222 case VERR_FILE_NOT_FOUND:
1223 {
1224 /* No VBoxTray (or too old version which does not support IPC) running
1225 for the given user. Not much we can do then. */
1226 VGSvcVerbose(4, "VBoxTray for user '%s' not running (anymore), no last input available\n", pszUser);
1227
1228 /* Overwrite rc from above. */
1229 rc = vgsvcVMInfoWinUserUpdateF(pCache, pszUser, pszDomain, "UsageState", "Idle");
1230
1231 fReportToHost = rc == VINF_SUCCESS;
1232 if (fReportToHost)
1233 userState = VBoxGuestUserState_Idle;
1234 break;
1235 }
1236
1237 default:
1238 VGSvcError("Error querying last input for user '%s', rc=%Rrc\n", pszUser, rc);
1239 break;
1240 }
1241 }
1242
1243 if (fReportToHost)
1244 {
1245 Assert(userState != VBoxGuestUserState_Unknown);
1246 int rc2 = VbglR3GuestUserReportState(pszUser, pszDomain, userState, NULL /* No details */, 0);
1247 if (RT_FAILURE(rc2))
1248 VGSvcError("Error reporting usage state %d for user '%s' to host, rc=%Rrc\n", userState, pszUser, rc2);
1249 if (RT_SUCCESS(rc))
1250 rc = rc2;
1251 }
1252 }
1253
1254 return rc;
1255}
1256
1257
1258/**
1259 * Retrieves the currently logged in users and stores their names along with the
1260 * user count.
1261 *
1262 * @returns VBox status code.
1263 * @param pCache Property cache to use for storing some of the lookup
1264 * data in between calls.
1265 * @param ppszUserList Where to store the user list (separated by commas).
1266 * Must be freed with RTStrFree().
1267 * @param pcUsersInList Where to store the number of users in the list.
1268 */
1269int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList)
1270{
1271 AssertPtrReturn(pCache, VERR_INVALID_POINTER);
1272 AssertPtrReturn(ppszUserList, VERR_INVALID_POINTER);
1273 AssertPtrReturn(pcUsersInList, VERR_INVALID_POINTER);
1274
1275 int rc = RTOnce(&g_vgsvcWinVmInitOnce, vgsvcWinVmInfoInitOnce, NULL);
1276 if (RT_FAILURE(rc))
1277 return rc;
1278 if (!g_pfnLsaEnumerateLogonSessions || !g_pfnEnumProcesses || !g_pfnLsaNtStatusToWinError)
1279 return VERR_NOT_SUPPORTED;
1280
1281 rc = VbglR3GuestPropConnect(&s_uDebugGuestPropClientID);
1282 AssertRC(rc);
1283
1284 char *pszUserList = NULL;
1285 uint32_t cUsersInList = 0;
1286
1287 /* This function can report stale or orphaned interactive logon sessions
1288 of already logged off users (especially in Windows 2000). */
1289 PLUID paSessions = NULL;
1290 ULONG cSessions = 0;
1291 NTSTATUS rcNt = g_pfnLsaEnumerateLogonSessions(&cSessions, &paSessions);
1292 if (rcNt != STATUS_SUCCESS)
1293 {
1294 ULONG uError = g_pfnLsaNtStatusToWinError(rcNt);
1295 switch (uError)
1296 {
1297 case ERROR_NOT_ENOUGH_MEMORY:
1298 VGSvcError("Not enough memory to enumerate logon sessions!\n");
1299 break;
1300
1301 case ERROR_SHUTDOWN_IN_PROGRESS:
1302 /* If we're about to shutdown when we were in the middle of enumerating the logon
1303 * sessions, skip the error to not confuse the user with an unnecessary log message. */
1304 VGSvcVerbose(3, "Shutdown in progress ...\n");
1305 uError = ERROR_SUCCESS;
1306 break;
1307
1308 default:
1309 VGSvcError("LsaEnumerate failed with error %RU32\n", uError);
1310 break;
1311 }
1312
1313 if (paSessions)
1314 g_pfnLsaFreeReturnBuffer(paSessions);
1315
1316 return RTErrConvertFromWin32(uError);
1317 }
1318 VGSvcVerbose(3, "Found %u sessions\n", cSessions);
1319
1320 PVBOXSERVICEVMINFOPROC paProcs;
1321 DWORD cProcs;
1322 rc = vgsvcVMInfoWinProcessesEnumerate(&paProcs, &cProcs);
1323 if (RT_FAILURE(rc))
1324 {
1325 if (rc == VERR_NO_MEMORY)
1326 VGSvcError("Not enough memory to enumerate processes\n");
1327 else
1328 VGSvcError("Failed to enumerate processes, rc=%Rrc\n", rc);
1329 }
1330 else
1331 {
1332 PVBOXSERVICEVMINFOUSER pUserInfo;
1333 pUserInfo = (PVBOXSERVICEVMINFOUSER)RTMemAllocZ(cSessions * sizeof(VBOXSERVICEVMINFOUSER) + 1);
1334 if (!pUserInfo)
1335 VGSvcError("Not enough memory to store enumerated users!\n");
1336 else
1337 {
1338 ULONG cUniqueUsers = 0;
1339
1340 /*
1341 * Note: The cSessions loop variable does *not* correlate with
1342 * the Windows session ID!
1343 */
1344 for (ULONG i = 0; i < cSessions; i++)
1345 {
1346 VGSvcVerbose(3, "Handling session %RU32 (of %RU32)\n", i + 1, cSessions);
1347
1348 VBOXSERVICEVMINFOUSER userSession;
1349 if (vgsvcVMInfoWinIsLoggedIn(&userSession, &paSessions[i]))
1350 {
1351 VGSvcVerbose(4, "Handling user=%ls, domain=%ls, package=%ls, session=%RU32\n",
1352 userSession.wszUser, userSession.wszLogonDomain, userSession.wszAuthenticationPackage,
1353 userSession.ulLastSession);
1354
1355 /* Retrieve assigned processes of current session. */
1356 uint32_t cCurSessionProcs = vgsvcVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs,
1357 NULL /* Terminal session ID */);
1358 /* Don't return here when current session does not have assigned processes
1359 * anymore -- in that case we have to search through the unique users list below
1360 * and see if got a stale user/session entry. */
1361
1362 if (g_cVerbosity > 3)
1363 {
1364 char szDebugSessionPath[255];
1365 RTStrPrintf(szDebugSessionPath, sizeof(szDebugSessionPath),
1366 "/VirtualBox/GuestInfo/Debug/LSA/Session/%RU32", userSession.ulLastSession);
1367 VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugSessionPath,
1368 "#%RU32: cSessionProcs=%RU32 (of %RU32 procs total)",
1369 s_uDebugIter, cCurSessionProcs, cProcs);
1370 }
1371
1372 bool fFoundUser = false;
1373 for (ULONG a = 0; a < cUniqueUsers; a++)
1374 {
1375 PVBOXSERVICEVMINFOUSER pCurUser = &pUserInfo[a];
1376 AssertPtr(pCurUser);
1377
1378 if ( !RTUtf16Cmp(userSession.wszUser, pCurUser->wszUser)
1379 && !RTUtf16Cmp(userSession.wszLogonDomain, pCurUser->wszLogonDomain)
1380 && !RTUtf16Cmp(userSession.wszAuthenticationPackage, pCurUser->wszAuthenticationPackage))
1381 {
1382 /*
1383 * Only respect the highest session for the current user.
1384 */
1385 if (userSession.ulLastSession > pCurUser->ulLastSession)
1386 {
1387 VGSvcVerbose(4, "Updating user=%ls to %u processes (last used session: %RU32)\n",
1388 pCurUser->wszUser, cCurSessionProcs, userSession.ulLastSession);
1389
1390 if (!cCurSessionProcs)
1391 VGSvcVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n",
1392 pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs,
1393 pCurUser->ulLastSession, userSession.ulLastSession);
1394
1395 pCurUser->ulNumProcs = cCurSessionProcs;
1396 pCurUser->ulLastSession = userSession.ulLastSession;
1397 }
1398 /* There can be multiple session objects using the same session ID for the
1399 * current user -- so when we got the same session again just add the found
1400 * processes to it. */
1401 else if (pCurUser->ulLastSession == userSession.ulLastSession)
1402 {
1403 VGSvcVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n",
1404 pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, pCurUser->ulLastSession);
1405
1406 pCurUser->ulNumProcs = cCurSessionProcs;
1407 }
1408
1409 fFoundUser = true;
1410 break;
1411 }
1412 }
1413
1414 if (!fFoundUser)
1415 {
1416 VGSvcVerbose(4, "Adding new user=%ls (session=%RU32) with %RU32 processes\n",
1417 userSession.wszUser, userSession.ulLastSession, cCurSessionProcs);
1418
1419 memcpy(&pUserInfo[cUniqueUsers], &userSession, sizeof(VBOXSERVICEVMINFOUSER));
1420 pUserInfo[cUniqueUsers].ulNumProcs = cCurSessionProcs;
1421 cUniqueUsers++;
1422 Assert(cUniqueUsers <= cSessions);
1423 }
1424 }
1425 }
1426
1427 if (g_cVerbosity > 3)
1428 VGSvcWritePropF(s_uDebugGuestPropClientID, "/VirtualBox/GuestInfo/Debug/LSA",
1429 "#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32",
1430 s_uDebugIter, cSessions, cProcs, cUniqueUsers);
1431
1432 VGSvcVerbose(3, "Found %u unique logged-in user(s)\n", cUniqueUsers);
1433
1434 for (ULONG i = 0; i < cUniqueUsers; i++)
1435 {
1436 if (g_cVerbosity > 3)
1437 {
1438 char szDebugUserPath[255]; RTStrPrintf(szDebugUserPath, sizeof(szDebugUserPath), "/VirtualBox/GuestInfo/Debug/LSA/User/%RU32", i);
1439 VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugUserPath,
1440 "#%RU32: szName=%ls, sessionID=%RU32, cProcs=%RU32",
1441 s_uDebugIter, pUserInfo[i].wszUser, pUserInfo[i].ulLastSession, pUserInfo[i].ulNumProcs);
1442 }
1443
1444 bool fAddUser = false;
1445 if (pUserInfo[i].ulNumProcs)
1446 fAddUser = true;
1447
1448 if (fAddUser)
1449 {
1450 VGSvcVerbose(3, "User '%ls' has %RU32 interactive processes (session=%RU32)\n",
1451 pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulLastSession);
1452
1453 if (cUsersInList > 0)
1454 {
1455 rc = RTStrAAppend(&pszUserList, ",");
1456 AssertRCBreakStmt(rc, RTStrFree(pszUserList));
1457 }
1458
1459 cUsersInList += 1;
1460
1461 char *pszUser = NULL;
1462 char *pszDomain = NULL;
1463 rc = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszUser);
1464 if ( RT_SUCCESS(rc)
1465 && pUserInfo[i].wszLogonDomain)
1466 rc = RTUtf16ToUtf8(pUserInfo[i].wszLogonDomain, &pszDomain);
1467 if (RT_SUCCESS(rc))
1468 {
1469 /* Append user to users list. */
1470 rc = RTStrAAppend(&pszUserList, pszUser);
1471
1472 /* Do idle detection. */
1473 if (RT_SUCCESS(rc))
1474 rc = vgsvcVMInfoWinWriteLastInput(pCache, pszUser, pszDomain);
1475 }
1476 else
1477 rc = RTStrAAppend(&pszUserList, "<string-conversion-error>");
1478
1479 RTStrFree(pszUser);
1480 RTStrFree(pszDomain);
1481
1482 AssertRCBreakStmt(rc, RTStrFree(pszUserList));
1483 }
1484 }
1485
1486 RTMemFree(pUserInfo);
1487 }
1488 vgsvcVMInfoWinProcessesFree(cProcs, paProcs);
1489 }
1490 if (paSessions)
1491 g_pfnLsaFreeReturnBuffer(paSessions);
1492
1493 if (RT_SUCCESS(rc))
1494 {
1495 *ppszUserList = pszUserList;
1496 *pcUsersInList = cUsersInList;
1497 }
1498
1499 s_uDebugIter++;
1500 VbglR3GuestPropDisconnect(s_uDebugGuestPropClientID);
1501
1502 return rc;
1503}
1504
1505
1506int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID)
1507{
1508 int rc;
1509 char szSysDir[MAX_PATH] = {0};
1510 char szWinDir[MAX_PATH] = {0};
1511 char szDriversDir[MAX_PATH + 32] = {0};
1512
1513 /* ASSUME: szSysDir and szWinDir and derivatives are always ASCII compatible. */
1514 GetSystemDirectory(szSysDir, MAX_PATH);
1515 GetWindowsDirectory(szWinDir, MAX_PATH);
1516 RTStrPrintf(szDriversDir, sizeof(szDriversDir), "%s\\drivers", szSysDir);
1517#ifdef RT_ARCH_AMD64
1518 char szSysWowDir[MAX_PATH + 32] = {0};
1519 RTStrPrintf(szSysWowDir, sizeof(szSysWowDir), "%s\\SysWow64", szWinDir);
1520#endif
1521
1522 /* The file information table. */
1523 const VBOXSERVICEVMINFOFILE aVBoxFiles[] =
1524 {
1525 { szSysDir, "VBoxControl.exe" },
1526 { szSysDir, "VBoxHook.dll" },
1527 { szSysDir, "VBoxDisp.dll" },
1528 { szSysDir, "VBoxTray.exe" },
1529 { szSysDir, "VBoxService.exe" },
1530 { szSysDir, "VBoxMRXNP.dll" },
1531 { szSysDir, "VBoxGINA.dll" },
1532 { szSysDir, "VBoxCredProv.dll" },
1533
1534 /* On 64-bit we don't yet have the OpenGL DLLs in native format.
1535 So just enumerate the 32-bit files in the SYSWOW directory. */
1536#ifdef RT_ARCH_AMD64
1537 { szSysWowDir, "VBoxOGL-x86.dll" },
1538#else /* !RT_ARCH_AMD64 */
1539 { szSysDir, "VBoxOGL.dll" },
1540#endif /* !RT_ARCH_AMD64 */
1541
1542 { szDriversDir, "VBoxGuest.sys" },
1543 { szDriversDir, "VBoxMouseNT.sys" },
1544 { szDriversDir, "VBoxMouse.sys" },
1545 { szDriversDir, "VBoxSF.sys" },
1546 { szDriversDir, "VBoxVideo.sys" },
1547 };
1548
1549 for (unsigned i = 0; i < RT_ELEMENTS(aVBoxFiles); i++)
1550 {
1551 char szVer[128];
1552 rc = VGSvcUtilWinGetFileVersionString(aVBoxFiles[i].pszFilePath, aVBoxFiles[i].pszFileName, szVer, sizeof(szVer));
1553 char szPropPath[256];
1554 RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestAdd/Components/%s", aVBoxFiles[i].pszFileName);
1555 if ( rc != VERR_FILE_NOT_FOUND
1556 && rc != VERR_PATH_NOT_FOUND)
1557 VGSvcWritePropF(uClientID, szPropPath, "%s", szVer);
1558 else
1559 VGSvcWritePropF(uClientID, szPropPath, NULL);
1560 }
1561
1562 return VINF_SUCCESS;
1563}
1564
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use