VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/VBoxService/VBoxVMInfoUser.cpp@ 16684

Last change on this file since 16684 was 14409, checked in by vboxsync, 16 years ago

Windows Guest Additions: VBoxService:

  • Fixes CR6768927 (Users logged into windows directly via RDP are not accounted for in vbox guest properties).
  • Added security checks for copied buffers.
  • Added some more logging.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.4 KB
Line 
1/* $Id: VBoxVMInfoUser.cpp 14409 2008-11-20 13:19:20Z vboxsync $ */
2/** @file
3 * VBoxVMInfoUser - User information for the host.
4 */
5
6/*
7 * Copyright (C) 2006-2007 Sun Microsystems, Inc.
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 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22#include "VBoxService.h"
23#include "VBoxVMInfo.h"
24#include "VBoxVMInfoUser.h"
25
26#include <Ntsecapi.h>
27#include <wtsapi32.h> /* For WTS* calls. */
28#include <psapi.h> /* EnumProcesses. */
29
30/* Function GetLUIDsFromProcesses() written by Stefan Kuhr. */
31static DWORD GetLUIDsFromProcesses(PLUID *ppLuid)
32{
33 DWORD dwSize, dwSize2, dwIndex ;
34 LPDWORD lpdwPIDs ;
35 DWORD dwLastError = ERROR_SUCCESS;
36
37 if (!ppLuid)
38 {
39 SetLastError(ERROR_INVALID_PARAMETER);
40 return 0L;
41 }
42
43 // Call the PSAPI function EnumProcesses to get all of the
44 // ProcID's currently in the system.
45 // NOTE: In the documentation, the third parameter of
46 // EnumProcesses is named cbNeeded, which implies that you
47 // can call the function once to find out how much space to
48 // allocate for a buffer and again to fill the buffer.
49 // This is not the case. The cbNeeded parameter returns
50 // the number of PIDs returned, so if your buffer size is
51 // zero cbNeeded returns zero.
52 // NOTE: The "HeapAlloc" loop here ensures that we
53 // actually allocate a buffer large enough for all the
54 // PIDs in the system.
55 dwSize2 = 256 * sizeof( DWORD ) ;
56
57 lpdwPIDs = NULL ;
58 do
59 {
60 if( lpdwPIDs )
61 {
62 HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
63 dwSize2 *= 2 ;
64 }
65 lpdwPIDs = (unsigned long *)HeapAlloc( GetProcessHeap(), 0, dwSize2 );
66 if( lpdwPIDs == NULL )
67 return 0L; // Last error will be that of HeapAlloc
68
69 if( !EnumProcesses( lpdwPIDs, dwSize2, &dwSize ) )
70 {
71 DWORD dw = GetLastError();
72 HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
73 SetLastError(dw);
74 return 0L;
75 }
76 }
77 while( dwSize == dwSize2 ) ;
78
79 /* At this point we have an array of the PIDs at the
80 time of the last EnumProcesses invocation. We will
81 allocate an array of LUIDs passed back via the out
82 param ppLuid of exactly the number of PIDs. We will
83 only fill the first n values of this array, with n
84 being the number of unique LUIDs found in these PIDs. */
85
86 // How many ProcIDs did we get?
87 dwSize /= sizeof( DWORD ) ;
88 dwSize2 = 0L; /* Our return value of found luids. */
89
90 *ppLuid = (LUID *)LocalAlloc(LPTR, dwSize*sizeof(LUID));
91 if (!(*ppLuid))
92 {
93 dwLastError = GetLastError();
94 goto CLEANUP;
95 }
96 for( dwIndex = 0 ; dwIndex < dwSize ; dwIndex++ )
97 {
98 (*ppLuid)[dwIndex].LowPart =0L;
99 (*ppLuid)[dwIndex].HighPart=0;
100
101
102 // Open the process (if we can... security does not
103 // permit every process in the system).
104 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,FALSE, lpdwPIDs[ dwIndex ] ) ;
105 if( hProcess != NULL )
106 {
107 HANDLE hAccessToken;
108 if (OpenProcessToken( hProcess, TOKEN_QUERY, &hAccessToken))
109 {
110 TOKEN_STATISTICS ts;
111 DWORD dwSize;
112 if (GetTokenInformation(hAccessToken, TokenStatistics, &ts, sizeof ts, &dwSize))
113 {
114 DWORD dwTmp = 0L;
115 BOOL bFound = FALSE;
116 for (;dwTmp<dwSize2 && !bFound;dwTmp++)
117 bFound = (*ppLuid)[dwTmp].HighPart == ts.AuthenticationId.HighPart &&
118 (*ppLuid)[dwTmp].LowPart == ts.AuthenticationId.LowPart;
119
120 if (!bFound)
121 (*ppLuid)[dwSize2++] = ts.AuthenticationId;
122 }
123 CloseHandle(hAccessToken) ;
124 }
125
126 CloseHandle( hProcess ) ;
127 }
128
129 /// we don't really care if OpenProcess or OpenProcessToken fail or succeed, because
130 /// there are quite a number of system processes we cannot open anyway, not even as SYSTEM.
131 }
132
133CLEANUP:
134
135 if (lpdwPIDs)
136 HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
137
138 if (ERROR_SUCCESS !=dwLastError)
139 SetLastError(dwLastError);
140
141 return dwSize2;
142}
143
144BOOL isLoggedIn(VBOXINFORMATIONCONTEXT* a_pCtx,
145 VBOXUSERINFO* a_pUserInfo,
146 PLUID a_pSession,
147 PLUID a_pLuid,
148 DWORD a_dwNumOfProcLUIDs)
149{
150 BOOL bLoggedIn = FALSE;
151 BOOL bFoundUser = FALSE;
152 PSECURITY_LOGON_SESSION_DATA sessionData = NULL;
153 NTSTATUS r = 0;
154 WCHAR *usBuffer = NULL;
155 int iLength = 0;
156
157 if (!a_pSession)
158 return FALSE;
159
160 r = LsaGetLogonSessionData (a_pSession, &sessionData);
161 if (r != STATUS_SUCCESS)
162 {
163 Log(("vboxVMInfoThread: Users: LsaGetLogonSessionData failed %lu\n", LsaNtStatusToWinError(r)));
164
165 if (sessionData)
166 LsaFreeReturnBuffer(sessionData);
167
168 return FALSE;
169 }
170
171 if (!sessionData)
172 {
173 Log(("vboxVMInfoThread: Users: Invalid logon session data.\n"));
174 return FALSE;
175 }
176
177 Log(("vboxVMInfoThread: Users: Session data: Name = %ls, Len = %d, SID = %s, LogonID = %d,%d\n",
178 (sessionData->UserName).Buffer, (sessionData->UserName).Length, (sessionData->Sid != NULL) ? "1" : "0", sessionData->LogonId.HighPart, sessionData->LogonId.LowPart));
179
180 if ((sessionData->UserName.Buffer != NULL) &&
181 (sessionData->Sid != NULL) &&
182 (sessionData->LogonId.LowPart != 0))
183 {
184 /* Get the user name. */
185 usBuffer = (sessionData->UserName).Buffer;
186 iLength = (sessionData->UserName).Length;
187 if (iLength > sizeof(a_pUserInfo->szUser) - sizeof(TCHAR)) /* -sizeof(TCHAR) because we have to add the terminating null char at the end later. */
188 {
189 LogRel(("vboxVMInfoThread: Users: User name too long (%d bytes) for buffer! Name will be truncated.\n", iLength));
190 iLength = sizeof(a_pUserInfo->szUser) - sizeof(TCHAR);
191 }
192 wcsncpy (a_pUserInfo->szUser, usBuffer, iLength);
193 wcscat (a_pUserInfo->szUser, L""); /* Add terminating null char. */
194
195 /* Get authentication package. */
196 usBuffer = (sessionData->AuthenticationPackage).Buffer;
197 iLength = (sessionData->AuthenticationPackage).Length;
198 if (iLength > sizeof(a_pUserInfo->szAuthenticationPackage) - sizeof(TCHAR)) /* -sizeof(TCHAR) because we have to add the terminating null char at the end later. */
199 {
200 LogRel(("vboxVMInfoThread: Users: Authentication pkg name too long (%d bytes) for buffer! Name will be truncated.\n", iLength));
201 iLength = sizeof(a_pUserInfo->szAuthenticationPackage) - sizeof(TCHAR);
202 }
203 wcsncpy (a_pUserInfo->szAuthenticationPackage, usBuffer, iLength);
204 wcscat (a_pUserInfo->szAuthenticationPackage, L""); /* Add terminating null char. */
205
206 /* Get logon domain. */
207 usBuffer = (sessionData->LogonDomain).Buffer;
208 iLength = (sessionData->LogonDomain).Length;
209 if (iLength > sizeof(a_pUserInfo->szLogonDomain) - sizeof(TCHAR)) /* -sizeof(TCHAR) because we have to add the terminating null char at the end later. */
210 {
211 LogRel(("vboxVMInfoThread: Users: Logon domain name too long (%d bytes) for buffer! Name will be truncated.\n", iLength));
212 iLength = sizeof(a_pUserInfo->szLogonDomain) - sizeof(TCHAR);
213 }
214 wcsncpy (a_pUserInfo->szLogonDomain, usBuffer, iLength);
215 wcscat (a_pUserInfo->szLogonDomain, L""); /* Add terminating null char. */
216
217 /* Only handle users which can login interactively or logged in remotely over native RDP. */
218 if ( (((SECURITY_LOGON_TYPE)sessionData->LogonType == Interactive)
219 || ((SECURITY_LOGON_TYPE)sessionData->LogonType == RemoteInteractive))
220 && (sessionData->Sid != NULL))
221 {
222 TCHAR szOwnerName [_MAX_PATH] = { 0 };
223 DWORD dwOwnerNameSize = _MAX_PATH;
224
225 TCHAR szDomainName [_MAX_PATH] = { 0 };
226 DWORD dwDomainNameSize = _MAX_PATH;
227
228 SID_NAME_USE ownerType;
229
230 if (LookupAccountSid(NULL,
231 sessionData->Sid,
232 szOwnerName,
233 &dwOwnerNameSize,
234 szDomainName,
235 &dwDomainNameSize,
236 &ownerType))
237 {
238 Log(("vboxVMInfoThread: Users: Account User=%ls, Session=%ld, LUID=%ld,%ld, AuthPkg=%ls, Domain=%ls\n",
239 a_pUserInfo->szUser, sessionData->Session, sessionData->LogonId.HighPart, sessionData->LogonId.LowPart, a_pUserInfo->szAuthenticationPackage, a_pUserInfo->szLogonDomain));
240
241 /* The session ID increments/decrements on Vista often! So don't compare
242 the session data SID with the current SID here. */
243 DWORD dwActiveSession = 0;
244 if (a_pCtx->pfnWTSGetActiveConsoleSessionId != NULL) /* Check terminal session ID. */
245 dwActiveSession = a_pCtx->pfnWTSGetActiveConsoleSessionId();
246
247 /*Log(("vboxVMInfoThread: Users: Current active session ID: %ld\n", dwActiveSession));*/
248
249 if (SidTypeUser == ownerType)
250 {
251 LPWSTR pBuffer = NULL;
252 DWORD dwBytesRet = 0;
253 int iState = 0;
254
255 if (WTSQuerySessionInformation( /* Detect RDP sessions as well. */
256 WTS_CURRENT_SERVER_HANDLE,
257 WTS_CURRENT_SESSION,
258 WTSConnectState,
259 &pBuffer,
260 &dwBytesRet))
261 {
262 /*Log(("vboxVMInfoThread: Users: WTSQuerySessionInformation returned %ld bytes, p=%p, state=%d\n", dwBytesRet, pBuffer, pBuffer != NULL ? (INT)*pBuffer : -1));*/
263 if(dwBytesRet)
264 iState = *pBuffer;
265
266 if ( (iState == WTSActive) /* User logged on to WinStation. */
267 || (iState == WTSShadow) /* Shadowing another WinStation. */
268 || (iState == WTSDisconnected)) /* WinStation logged on without client. */
269 {
270 /** @todo On Vista and W2K, always "old" user name are still there. Filter out the old! */
271 Log(("vboxVMInfoThread: Users: Account User=%ls is logged in via TCS/RDP. State=%d\n", a_pUserInfo->szUser, iState));
272 bFoundUser = TRUE;
273 }
274 }
275 else
276 {
277 /* Terminal services don't run (for example in W2K, nothing to worry about ...). */
278 /* ... or is on Vista fast user switching page! */
279 bFoundUser = TRUE;
280 }
281
282 if (pBuffer)
283 WTSFreeMemory(pBuffer);
284
285 /* A user logged in, but it could be a stale/orphaned logon session. */
286 BOOL bFoundInLUIDs = FALSE;
287 for (DWORD dwIndex = 0; dwIndex < a_dwNumOfProcLUIDs; dwIndex++)
288 {
289 if ( (a_pLuid[dwIndex].HighPart == sessionData->LogonId.HighPart)
290 && (a_pLuid[dwIndex].LowPart == sessionData->LogonId.LowPart))
291 {
292 bLoggedIn = TRUE;
293 Log(("vboxVMInfoThread: Users: User \"%ls\" is logged in!\n", a_pUserInfo->szUser));
294 break;
295 }
296 }
297 }
298 }
299 }
300 }
301
302 LsaFreeReturnBuffer(sessionData);
303 return bLoggedIn;
304}
305
306int vboxVMInfoUser(VBOXINFORMATIONCONTEXT* a_pCtx)
307{
308 PLUID pSessions = NULL;
309 ULONG ulCount = 0;
310 NTSTATUS r = 0;
311
312 int iUserCount = 0;
313 char szUserList[4096] = {0};
314 char* pszTemp = NULL;
315
316 /* This function can report stale or orphaned interactive logon sessions of already logged
317 off users (especially in Windows 2000). */
318 r = LsaEnumerateLogonSessions(&ulCount, &pSessions);
319 Log(("vboxVMInfoThread: Users: Found %d users.\n", ulCount));
320
321 if (r != STATUS_SUCCESS)
322 {
323 Log(("vboxVMInfoThread: Users: LsaEnumerate failed %lu\n", LsaNtStatusToWinError(r)));
324 return 1;
325 }
326
327 PLUID pLuid = NULL;
328 DWORD dwNumOfProcLUIDs = GetLUIDsFromProcesses(&pLuid);
329
330 VBOXUSERINFO userInfo;
331 ZeroMemory (&userInfo, sizeof(VBOXUSERINFO));
332
333 for (int i = 0; i<(int)ulCount; i++)
334 {
335 if (isLoggedIn(a_pCtx, &userInfo, &pSessions[i], pLuid, dwNumOfProcLUIDs))
336 {
337 if (iUserCount > 0)
338 strcat (szUserList, ",");
339
340 iUserCount++;
341
342 RTUtf16ToUtf8(userInfo.szUser, &pszTemp);
343 strcat(szUserList, pszTemp);
344 RTMemFree(pszTemp);
345 }
346 }
347
348 if (NULL != pLuid)
349 LocalFree (pLuid);
350
351 LsaFreeReturnBuffer(pSessions);
352
353 /* Write information to host. */
354 vboxVMInfoWriteProp(a_pCtx, "GuestInfo/OS/LoggedInUsersList", (iUserCount > 0) ? szUserList : NULL);
355 vboxVMInfoWritePropInt(a_pCtx, "GuestInfo/OS/LoggedInUsers", iUserCount);
356 if (a_pCtx->cUsers != iUserCount || a_pCtx->cUsers == INT32_MAX)
357 {
358 /* Update this property ONLY if there is a real change from no users to
359 * users or vice versa. The only exception is that the initialization
360 * of a_pCtx->cUsers forces an update, but only once. This ensures
361 * consistent property settings even if the VM aborted previously. */
362 if (iUserCount == 0)
363 vboxVMInfoWriteProp(a_pCtx, "GuestInfo/OS/NoLoggedInUsers", "true");
364 else if (a_pCtx->cUsers == 0 || a_pCtx->cUsers == INT32_MAX)
365 vboxVMInfoWriteProp(a_pCtx, "GuestInfo/OS/NoLoggedInUsers", "false");
366 }
367 a_pCtx->cUsers = iUserCount;
368
369 return r;
370}
371
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