VBoxLA.cpp revision 51df2768ddb5c4dd975267b4ba82ba02da82d20a
/* $Id$ */
/** @file
* VBoxLA - VBox Location Awareness notifications.
*/
/*
* Copyright (C) 2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
// #define LOG_ENABLED
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include "VBoxTray.h"
#include "VBoxLA.h"
#define LALOGFORCE(a) do { LogRel(a); } while(0)
#define REG_KEY_LEN 1024
#define MAX_CLIENT_NAME_CHARS 1024
#define LA_DO_NOTHING 0
#define LA_DO_ATTACH 1
#define LA_DO_DETACH 2
#define LA_DO_DETACH_AND_ATTACH 3
#define LA_DO_ATTACH_AND_DETACH 4
#define LA_UTCINFO_CLIENT_NAME 0
#define LA_UTCINFO_CLIENT_IPADDR 1
#define LA_UTCINFO_CLIENT_LOCATION 2
#define LA_UTCINFO_CLIENT_OTHERINFO 3
#define LA_UTCINFO_CLIENT_INFO_LAST 3
#define LA_UTCINFO_PROP_NAME 0
#define LA_UTCINFO_PROP_VALUE 1
struct VBOXLACONTEXT
{
const VBOXSERVICEENV *pEnv;
bool fLogEnabled;
bool fDetachOnDisconnect;
struct /* Information about the client, which properties are monitored. */
{
char *pszLastName;
char *pszPropName; /* The actual Client/%ID%/Name property name with client id. */
char *pszPropIPAddr; /* The actual Client/%ID%/IPAddr property name with client id. */
char *pszPropLocation; /* The actual Client/%ID%/Location property name with client id. */
char *pszPropOtherInfo; /* The actual Client/%ID%/OtherInfo property name with client id. */
char *pszPropAttach; /* The actual Client/%ID%/Attach property name with client id. */
char *pszPropWaitPattern; /* Which properties are monitored. */
} activeClient;
};
typedef struct ACTIONENTRY
{
} ACTIONENTRY;
static VBOXLACONTEXT gCtx = {0};
static const char *g_pszPropActiveClient = "/VirtualBox/HostInfo/VRDP/ActiveClient";
static const char *g_pszPropAttachTemplate = "/VirtualBox/HostInfo/VRDP/Client/%u/Attach";
static const char *g_pszVolatileEnvironment = "Volatile Environment";
static const WCHAR *g_pwszUTCINFOClientInfo[] = {
L"UTCINFO_CLIENTNAME",
L"UTCINFO_CLIENTIPA",
L"UTCINFO_CLIENTLOCATION",
L"UTCINFO_CLIENTOTHERINFO"
};
static const char *g_pszPropInfoTemplates[] = {
};
#ifdef RT_ARCH_AMD64
const WCHAR *g_pwszRegKeyDisconnectActions = L"Software\\Wow6432Node\\Oracle\\Sun Ray\\ClientInfoAgent\\DisconnectActions";
const WCHAR *g_pwszRegKeyReconnectActions = L"Software\\Wow6432Node\\Oracle\\Sun Ray\\ClientInfoAgent\\ReconnectActions";
#else
const WCHAR *g_pwszRegKeyDisconnectActions = L"Software\\Oracle\\Sun Ray\\ClientInfoAgent\\DisconnectActions";
const WCHAR *g_pwszRegKeyReconnectActions = L"Software\\Oracle\\Sun Ray\\ClientInfoAgent\\ReconnectActions";
#endif /* !RT_ARCH_AMD64 */
const char g_szCommandPrefix[] = "Command";
{
0,
&hKey);
if (lErr != ERROR_SUCCESS)
{
LALOGFORCE(("LA: RegOpenKeyExW: failed [%ls]\n",
pwszRegKey));
return FALSE;
}
NULL,
&dwType,
&nRegData);
if (lErr != ERROR_SUCCESS)
{
LALOGFORCE(("LA: RegQueryValueExW: failed [%ls/%ls]\n",
pwszRegKey, pwszName));
return FALSE;
}
{
LALOGFORCE(("LA: buffer overflow reg %d, [%ls]\n",
nRegData, pwszRegKey));
return FALSE;
}
{
LALOGFORCE(("LA: wrong type %d, [%ls/%ls]\n",
return FALSE;
}
if (lErr != ERROR_SUCCESS)
{
return FALSE;
}
return TRUE;
}
{
{
}
}
{
0,
&hKey);
if (dwErr != ERROR_SUCCESS)
{
LALOG(("LA: Can't open registry key [%ls], error %d\n",
pwszRegKey, dwErr));
return FALSE;
}
for (;;)
{
dwIndex++,
NULL,
&type,
&cbData);
if (dwRet == ERROR_NO_MORE_ITEMS)
{
LALOG(("LA: Enumeration exhausted\n"));
break;
}
else if (dwRet != ERROR_SUCCESS)
{
LALOG(("LA: Enumeration failed, error %d\n",
dwRet));
break;
}
{
LALOG(("LA: skipped type %d\n",
type));
continue;
}
char szName[256];
if (RT_FAILURE(rc))
{
LALOG(("LA: RTUtf16ToUtf8Ex for [%ls] rc %Rrc\n",
wszValueName, rc));
continue;
}
/* Check if the name starts with "Command" */
{
LALOG(("LA: skipped prefix %s\n",
szName));
continue;
}
if (nIndex == 0)
{
LALOG(("LA: skipped index %s\n",
szName));
continue;
}
/* Allocate with terminating nul after data. */
if (!pEntry)
{
LALOG(("LA: RTMemAlloc failed\n"));
break;
}
/* Insert the new entry to the list. Sort by index. */
if (RTListIsEmpty(listActions))
{
}
else
{
bool fAdded = false;
{
{
fAdded = true;
break;
}
}
if (!fAdded)
{
}
}
LALOG(("LA: added %d %ls\n",
}
#ifdef LOG_ENABLED
{
LALOG(("LA: [%u]: [%ls]\n",
}
#endif
if (!bRet)
{
}
LALOG(("LA: action enum %d\n",
bRet));
return bRet;
}
{
LALOG(("LA: ExecuteActions\n"));
{
LALOG(("LA: [%u]: [%ls]\n",
NULL, // lpProcessAttributes
NULL, // lpThreadAttributes
FALSE, // bInheritHandles
0, // dwCreationFlags
NULL, // lpEnvironment
NULL, // lpCurrentDirectory
&si, // lpStartupInfo
&pi)) // lpProcessInformation
{
LALOG(("LA: Executing [%ls] failed, error %d\n",
}
else
{
LALOG(("LA: Executing [%ls] succeeded\n",
pIter->wszCommandLine));
/* Don't care about waiting on the new process, so close these. */
}
}
LALOG(("LA: ExecuteActions leave\n"));
}
{
char szRegKey[REG_KEY_LEN];
/* Attempt to open HKCU\Volatile Environment\<session ID> first. */
{
"%s\\%d",
0,
&hKey);
if (lErr == ERROR_SUCCESS)
{
}
}
if (!fFound)
{
/* Fall back to HKCU\Volatile Environment. */
"%s",
0,
&hKey);
if (lErr == ERROR_SUCCESS)
{
}
}
if (fFound)
{
/* Convert szRegKey to Utf16 string. */
if (RT_FAILURE(rc))
{
}
else
{
}
}
else
{
LALOG(("LA: GetVolatileEnvironmentKey: not found\n"));
}
return fFound;
}
{
{
return FALSE;
}
0,
&hKey);
if (lErr != ERROR_SUCCESS)
{
LALOG(("LA: RegOpenKeyExW: failed [%ls]\n",
wszRegKey));
return FALSE;
}
NULL,
&dwType,
NULL,
&nRegData);
if (lErr != ERROR_SUCCESS)
{
LALOG(("LA: RegQueryValueExW: failed [%ls]\n",
wszRegKey));
return FALSE;
}
if (nRegData >= cbClientName)
{
LALOG(("LA: buffer overflow reg %d, buffer %d, [%ls]\n",
return FALSE;
}
{
LALOG(("LA: wrong type %d, [%ls]\n",
return FALSE;
}
NULL,
NULL,
(BYTE *)pwszClientName,
&nRegData);
if (lErr != ERROR_SUCCESS)
{
return FALSE;
}
return TRUE;
}
{
{
return FALSE;
}
0,
&hKey);
if (lErr != ERROR_SUCCESS)
{
return FALSE;
}
0,
if (lErr != ERROR_SUCCESS)
{
return FALSE;
}
return TRUE;
}
static void laBroadcastSettingChange(void)
{
NULL,
(LPARAM)"Environment",
5000,
&dwResult) == 0)
{
}
}
{
{
{
}
}
}
{
/*
* Write the client location info to:
* HKCU\Volatile Environment\<CLIENT_LOCATION_INFO> or
* HKCU\Volatile Environment\<SessionID>\<CLIENT_LOCATION_INFO>
* depending on whether this is a Terminal Services or desktop session
* respectively.
* The client location info are: Name, IPAddr, Location, OtherInfo
*/
unsigned int idx;
{
LALOG(("LA: Failed to get 'Volatile Environment' registry key\n"));
return;
}
/* Now write the client name under the appropriate key. */
0,
&hKey);
if (lRet != ERROR_SUCCESS)
{
LALOG(("LA: Failed to open key [%ls], error %lu\n",
return;
}
{
break;
/* pszClientInfo is UTF8, make an Unicode copy for registry. */
if (RT_FAILURE(rc))
{
break;
}
0,
if (lRet != ERROR_SUCCESS)
{
}
}
/* Also, write these info (Name, IPAddr, Location and Other Info) to the environment of this process, as it
* doesn't listen for WM_SETTINGCHANGE messages.
*/
{
break;
}
}
{
LALOG(("LA: laDoAttach\n"));
/* Hardcoded action. */
/* Process configured actions. */
}
{
LALOG(("LA: laDoDetach\n"));
/* Process configured actions. */
}
static int laGetProperty(uint32_t u32GuestPropHandle, const char *pszName, uint64_t *pu64Timestamp, char **ppszValue)
{
int rc = VINF_SUCCESS;
/* The buffer for storing the data and its initial size. We leave a bit
* of space here in case the maximum values are raised.
*/
/* Because there is a race condition between our reading the size of a
* property and the guest updating it, we loop a few times here and
* hope. Actually this should never go wrong, as we are generous
* enough with buffer space.
*/
unsigned i;
for (i = 0; i < 3; ++i)
{
{
rc = VERR_NO_MEMORY;
break;
}
&cbBuf);
if (rc != VERR_BUFFER_OVERFLOW)
{
break;
}
cbBuf += 1024;
}
if (RT_SUCCESS(rc))
{
LALOG(("LA: laGetProperty: [%s]\n"
" value: [%s]\n"
" timestamp: %lld ns\n",
}
else if (rc == VERR_NOT_FOUND)
{
}
else
{
}
return rc;
}
const char *pszPatterns,
{
int rc = VINF_SUCCESS;
/* The buffer for storing the data and its initial size. We leave a bit
* of space here in case the maximum values are raised.
*/
/* Because there is a race condition between our reading the size of a
* property and the guest updating it, we loop a few times here and
* hope. Actually this should never go wrong, as we are generous
* enough with buffer space.
*/
unsigned i;
for (i = 0; i < 3; ++i)
{
{
rc = VERR_NO_MEMORY;
break;
}
NULL /* ppszName */,
NULL /* ppszValue */,
NULL /* ppszFlags */,
&cbBuf);
if (rc != VERR_BUFFER_OVERFLOW)
{
break;
}
cbBuf += 1024;
}
return rc;
}
static int laGetUint32(uint32_t u32GuestPropHandle, const char *pszName, uint64_t *pu64Timestamp, uint32_t *pu32Value)
{
uint64_t u64Timestamp = 0;
&pszValue);
if (RT_SUCCESS(rc))
{
{
if (RT_SUCCESS(rc))
{
}
}
else
{
}
}
if (pszValue)
{
}
LALOG(("LA: laGetUint32: rc = %Rrc, [%s]\n",
return rc;
}
static int laGetString(uint32_t u32GuestPropHandle, const char *pszName, uint64_t *pu64Timestamp, char **ppszValue)
{
LALOG(("LA: laGetString: rc = %Rrc, [%s]\n",
return rc;
}
{
return rc;
}
static int laUpdateCurrentState(VBOXLACONTEXT *pCtx, uint32_t u32ActiveClientId, uint64_t u64ActiveClientTS)
{
/* Prepare the current state for the active client.
* If u32ActiveClientId is 0, then there is no connected clients.
*/
LALOG(("LA: laUpdateCurrentState: %u %lld\n",
int rc = VINF_SUCCESS;
int l;
};
{
}
unsigned int idx;
{
if (*pClientInfoMap[idx])
{
}
if (u32ActiveClientId != 0)
{
if (l == -1)
{
rc = VERR_NO_MEMORY;
break;
}
}
}
if (RT_SUCCESS(rc))
{
{
}
if (u32ActiveClientId != 0)
{
if (l == -1)
{
rc = VERR_NO_MEMORY;
}
}
}
if (RT_SUCCESS(rc))
{
{
}
if (u32ActiveClientId != 0)
{
"%s|%s|%s|%s|%s",
if (l == -1)
{
rc = VERR_NO_MEMORY;
}
}
}
if (RT_SUCCESS(rc))
{
}
else
{
}
LALOG(("LA: laUpdateCurrentState rc = %Rrc\n",
rc));
return rc;
}
{
LALOG(("LA: laWait [%s]\n",
LALOG(("LA: laWait rc %Rrc\n",
rc));
return rc;
}
{
/* Check if the name was changed. */
/* Get the name string and check if it was changed since last time.
* Write Client name, IPAddr, Location and Other Info to the registry if the name has changed.
*/
uint64_t u64Timestamp = 0;
int rc = VINF_SUCCESS;
unsigned int idx;
char *pClientInfoMap[][2] = {
};
{
LALOG(("LA: laProcessClientInfo: read [%s], at %lld\n",
if (RT_FAILURE(rc))
{
break;
}
}
{
{
}
}
{
{
}
}
}
{
/* Check if the attach was changed. */
uint64_t u64Timestamp = 0;
&u32Attach);
if (RT_SUCCESS(rc))
{
LALOG(("LA: laProcessAttach: read %d, at %lld\n",
{
{
LALOG(("LA: laProcessAttach: changed\n"));
/* Just do the last action. */
}
else
{
LALOG(("LA: laProcessAttach: same\n"));
/* The property has changed but the value is the same,
* which means that it was changed and restored.
*/
}
}
}
LALOG(("LA: laProcessAttach: action %d\n",
}
{
/* Check if the attach was changed.
*
* Caller assumes that this function will filter double actions.
* That is two or more LA_DO_ATTACH will do just one LA_DO_ATTACH.
*/
LALOG(("LA: laDoActions: action %d, prev %d\n",
{
case LA_DO_ATTACH:
{
{
}
} break;
case LA_DO_DETACH:
{
{
}
} break;
case LA_DO_DETACH_AND_ATTACH:
{
{
}
} break;
case LA_DO_ATTACH_AND_DETACH:
{
{
}
} break;
case LA_DO_NOTHING:
default:
break;
}
LALOG(("LA: laDoActions: leave\n"));
}
{
&& (dwValue & 0x10) != 0)
{
gCtx.fLogEnabled = true;
}
else
{
gCtx.fLogEnabled = false;
}
LALOG(("VBoxTray: VBoxLAInit\n"));
/* DetachOnDisconnect is enabled by default. */
dwValue = 0x02;
&& (dwValue & 0x02) == 0)
{
gCtx.fDetachOnDisconnect = false;
}
else
{
gCtx.fDetachOnDisconnect = true;
}
if (RT_FAILURE(rc))
{
return rc;
}
*(void **)&gCtx.pfnProcessIdToSessionId = RTLdrGetSystemSymbol("kernel32.dll", "ProcessIdToSessionId");
*pfStartThread = true;
*ppInstance = &gCtx;
return VINF_SUCCESS;
}
{
LALOG(("VBoxTray: VBoxLADestroy\n"));
if (pCtx->u32GuestPropHandle != 0)
{
}
}
/*
* Thread function to wait for and process property changes
*/
{
LALOG(("VBoxTray: VBoxLAThread: Started.\n"));
/*
* On name change event (/VirtualBox/HostInfo/VRDP/Client/%ID%/Name)
* Store the name in the registry (HKCU\Volatile Environment\UTCINFO_CLIENTNAME).
* On a client attach event (/VirtualBox/HostInfo/VRDP/Client/%ID%/Attach -> 1):
* Execute ReconnectActions
* On a client detach event (/VirtualBox/HostInfo/VRDP/Client/%ID%/Attach -> 0):
* Execute DisconnectActions
*
* The active connected client id is /VirtualBox/HostInfo/VRDP/ActiveClientClient.
*/
{
}
{
}
/* A non zero timestamp in the past. */
/* Start at Detached state. */
for (;;)
{
/* Query current ActiveClient.
* if it differs from the current active client
* rebuild the context;
* wait with timeout for properties change since the active client was changed;
* if 'Name' was changed
* update the name;
* if 'Attach' was changed
* do respective actions.
* remember the query timestamp;
*/
uint64_t u64Timestamp = 0;
if (RT_SUCCESS(rc))
{
if (fClientIdChanged)
{
}
if (RT_SUCCESS(rc))
{
{
if (RT_SUCCESS(rc))
{
}
}
else
{
/* If the client has been disconnected, do the detach actions. */
if ( pCtx->fDetachOnDisconnect
&& fClientIdChanged)
{
LALOG(("LA: client disconnected\n"));
/* laDoActions will prevent a repeated detach action. So if there
* was a detach already, then this detach will be ignored.
*/
}
}
}
}
/* Check if it is time to exit.
* If the code above failed, wait a bit until repeating to avoid a loop.
* Otherwise just check if the stop event was signalled.
*/
if ( rc == VERR_NOT_FOUND
{
/* No connections, wait longer. */
u32Wait = 5000;
}
else if (RT_FAILURE(rc))
{
u32Wait = 1000;
}
else
{
u32Wait = 0;
}
{
break;
}
}
LALOG(("VBoxTray: VBoxLAThread: Finished.\n"));
return 0;
}