VBoxLA.cpp revision e0791f3e14768aaf0020eb06cbb0ada32c52f3ce
/* $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 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
struct VBOXLACONTEXT
{
const VBOXSERVICEENV *pEnv;
struct /* Information about the client, which properties are monitored. */
{
char *pszLastName;
char *pszPropName; /* The actual Client/%ID%/Name 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_pszPropNameTemplate = "/VirtualBox/HostInfo/VRDP/Client/%u/Name";
static const char *g_pszPropAttachTemplate = "/VirtualBox/HostInfo/VRDP/Client/%u/Attach";
static const char *g_pszVolatileEnvironment = "Volatile Environment";
const WCHAR *g_pwszRegKeyDisconnectActions = L"Software\\Oracle\\Sun Ray\\ClientInfoAgent\\DisconnectActions";
const WCHAR *g_pwszRegKeyReconnectActions = L"Software\\Oracle\\Sun Ray\\ClientInfoAgent\\ReconnectActions";
const char g_szCommandPrefix[] = "Command";
{
{
}
}
{
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)
{
}
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)
{
}
}
{
{
{
}
}
}
static void laOnClientName(const char *pszClientName)
{
/* pszClientName is UTF8, make an Unicode copy for registry. */
size_t cchUnicodeName = 0;
&putf16UnicodeName, 0, &cchUnicodeName);
if (RT_FAILURE(rc))
{
return;
}
/*
* Write the client name to:
* HKCU\Volatile Environment\UTCINFO_CLIENTNAME or
* HKCU\Volatile Environment\<SessionID>\UTCINFO_CLIENTNAME
* depending on whether this is a Terminal Services or desktop session
* respectively.
*/
{
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;
}
0,
if (lRet != ERROR_SUCCESS)
{
}
/* Also, write the client name to the environment of this process, as it
* doesn't listen for WM_SETTINGCHANGE messages.
*/
}
{
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)
{
LALOG(("LA: laGetProperty: not found [%s]\n"));
}
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;
{
}
{
}
if (u32ActiveClientId != 0)
{
if (l == -1)
{
rc = VERR_NO_MEMORY;
}
}
if (RT_SUCCESS(rc))
{
{
}
if (u32ActiveClientId != 0)
{
if (l == -1)
{
rc = VERR_NO_MEMORY;
}
}
}
if (RT_SUCCESS(rc))
{
{
}
if (u32ActiveClientId != 0)
{
"%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 the name to the registry if changed.
*/
uint64_t u64Timestamp = 0;
&pszName);
if (RT_SUCCESS(rc))
{
LALOG(("LA: laProcessName: read [%s], at %lld\n",
pszName, u64Timestamp));
{
}
}
if (pszName)
{
}
}
{
/* 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\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"));
}
{
LALOG(("VBoxTray: VBoxLAInit\n"));
if (RT_FAILURE(rc))
{
return rc;
}
if (gCtx.hModuleKernel32)
{
*(uintptr_t *)&gCtx.pfnProcessIdToSessionId = (uintptr_t)GetProcAddress(gCtx.hModuleKernel32, "ProcessIdToSessionId");
}
else
{
}
*pfStartThread = true;
*ppInstance = &gCtx;
return VINF_SUCCESS;
}
{
LALOG(("VBoxTray: VBoxLADestroy\n"));
if (pCtx->u32GuestPropHandle != 0)
{
}
if (pCtx->hModuleKernel32)
{
}
}
/*
* 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 (RT_SUCCESS(rc))
{
{
if (RT_SUCCESS(rc))
{
}
}
}
}
/* 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 = 10000;
}
else if (RT_FAILURE(rc))
{
u32Wait = 1000;
}
else
{
u32Wait = 0;
}
{
break;
}
}
LALOG(("VBoxTray: VBoxLAThread: Finished.\n"));
return 0;
}