VBoxCredProvProvider.cpp revision 352bb6b9d2fa1f7df7797f50c58e297ac37059a2
/* $Id$ */
/** @file
* VBoxCredProvProvider - The actual credential provider class.
*/
/*
* 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;
* you can redistribute it and/or modify it under the terms of the GNU
* 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.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <credentialprovider.h>
#include <iprt/err.h>
#include <VBox/VBoxGuestLib.h>
#include "VBoxCredentialProvider.h"
#include "VBoxCredProvProvider.h"
#include "VBoxCredProvCredential.h"
VBoxCredProvProvider::VBoxCredProvProvider(void) :
m_cRefs(1),
m_pPoller(NULL),
m_pCred(NULL),
m_pEvents(NULL),
m_fHandleRemoteSessions(false)
{
VBoxCredentialProviderAcquire();
VBoxCredProvReportStatus(VBoxGuestFacilityStatus_Init);
}
VBoxCredProvProvider::~VBoxCredProvProvider(void)
{
VBoxCredProvVerbose(0, "VBoxCredProv: Destroying\n");
if (m_pCred)
{
m_pCred->Release();
m_pCred = NULL;
}
if (m_pPoller)
{
m_pPoller->Shutdown();
delete m_pPoller;
m_pPoller = NULL;
}
VBoxCredProvReportStatus(VBoxGuestFacilityStatus_Terminated);
VBoxCredentialProviderRelease();
}
/* IUnknown overrides. */
ULONG
VBoxCredProvProvider::AddRef(void)
{
LONG cRefs = InterlockedIncrement(&m_cRefs);
VBoxCredProvVerbose(0, "VBoxCredProv: AddRef: Returning refcount=%ld\n",
cRefs);
return cRefs;
}
ULONG
VBoxCredProvProvider::Release(void)
{
LONG cRefs = InterlockedDecrement(&m_cRefs);
VBoxCredProvVerbose(0, "VBoxCredProv: Release: Returning refcount=%ld\n",
cRefs);
if (!cRefs)
{
VBoxCredProvVerbose(0, "VBoxCredProv: Calling destructor\n");
delete this;
}
return cRefs;
}
HRESULT
VBoxCredProvProvider::QueryInterface(REFIID interfaceID, void **ppvInterface)
{
HRESULT hr = S_OK;
if (ppvInterface)
{
if ( IID_IUnknown == interfaceID
|| IID_ICredentialProvider == interfaceID)
{
*ppvInterface = static_cast<IUnknown*>(this);
reinterpret_cast<IUnknown*>(*ppvInterface)->AddRef();
}
else
{
*ppvInterface = NULL;
hr = E_NOINTERFACE;
}
}
else
hr = E_INVALIDARG;
return hr;
}
/**
* Loads the global configuration from registry.
*
* @return DWORD Windows error code.
*/
DWORD
VBoxCredProvProvider::LoadConfiguration(void)
{
HKEY hKey;
/** @todo Add some registry wrapper function(s) as soon as we got more values to retrieve. */
DWORD dwRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Oracle\\VirtualBox Guest Additions\\AutoLogon",
0L, KEY_QUERY_VALUE, &hKey);
if (dwRet == ERROR_SUCCESS)
{
DWORD dwValue;
DWORD dwType = REG_DWORD;
DWORD dwSize = sizeof(DWORD);
dwRet = RegQueryValueEx(hKey, L"HandleRemoteSessions", NULL, &dwType, (LPBYTE)&dwValue, &dwSize);
if ( dwRet == ERROR_SUCCESS
&& dwType == REG_DWORD
&& dwSize == sizeof(DWORD))
{
m_fHandleRemoteSessions = true;
}
dwRet = RegQueryValueEx(hKey, L"LoggingEnabled", NULL, &dwType, (LPBYTE)&dwValue, &dwSize);
if ( dwRet == ERROR_SUCCESS
&& dwType == REG_DWORD
&& dwSize == sizeof(DWORD))
{
g_dwVerbosity = 1; /* Default logging level. */
}
if (g_dwVerbosity) /* Do we want logging at all? */
{
dwRet = RegQueryValueEx(hKey, L"LoggingLevel", NULL, &dwType, (LPBYTE)&dwValue, &dwSize);
if ( dwRet == ERROR_SUCCESS
&& dwType == REG_DWORD
&& dwSize == sizeof(DWORD))
{
g_dwVerbosity = dwValue;
}
}
RegCloseKey(hKey);
}
/* Do not report back an error here yet. */
return ERROR_SUCCESS;
}
/**
* Determines whether we should handle the current session or not.
*
* @return bool true if we should handle this session, false if not.
*/
bool
VBoxCredProvProvider::HandleCurrentSession(void)
{
/* Load global configuration from registry. */
int rc = LoadConfiguration();
if (RT_FAILURE(rc))
VBoxCredProvVerbose(0, "VBoxCredProv: Error loading global configuration, rc=%Rrc\n",
rc);
bool fHandle = false;
if (VbglR3AutoLogonIsRemoteSession())
{
if (m_fHandleRemoteSessions) /* Force remote session handling. */
fHandle = true;
}
else /* No remote session. */
fHandle = true;
VBoxCredProvVerbose(3, "VBoxCredProv: Handling current session=%RTbool\n", fHandle);
return fHandle;
}
/**
* Tells this provider the current usage scenario.
*
* @return HRESULT
* @param enmUsageScenario Current usage scenario this provider will be
* used in.
* @param dwFlags Optional flags for the usage scenario.
*/
HRESULT
VBoxCredProvProvider::SetUsageScenario(CREDENTIAL_PROVIDER_USAGE_SCENARIO enmUsageScenario, DWORD dwFlags)
{
HRESULT hr = S_OK;
DWORD dwErr;
VBoxCredProvVerbose(0, "VBoxCredProv::SetUsageScenario: enmUsageScenario=%d, dwFlags=%ld\n",
enmUsageScenario, dwFlags);
m_enmUsageScenario = enmUsageScenario;
switch (m_enmUsageScenario)
{
case CPUS_LOGON:
case CPUS_UNLOCK_WORKSTATION:
{
VBoxCredProvReportStatus(VBoxGuestFacilityStatus_Active);
dwErr = LoadConfiguration();
if (dwErr != ERROR_SUCCESS)
VBoxCredProvVerbose(0, "VBoxCredProv: Error while loading configuration, error=%ld\n", dwErr);
/* Do not stop running on a misconfigured system. */
/*
* If we're told to not handle the current session just bail out and let the
* user know.
*/
if (!HandleCurrentSession())
break;
if (!m_pPoller)
{
/** @todo try catch please. */
m_pPoller = new VBoxCredProvPoller();
AssertPtr(m_pPoller);
int rc = m_pPoller->Initialize(this);
if (RT_FAILURE(rc))
VBoxCredProvVerbose(0, "VBoxCredProv::SetUsageScenario: Error initializing poller thread, rc=%Rrc\n", rc);
}
if (!m_pCred)
{
/** @todo try catch please. */
m_pCred = new VBoxCredProvCredential();
if (m_pCred)
hr = m_pCred->Initialize(m_enmUsageScenario);
else
hr = E_OUTOFMEMORY;
}
else
{
/* All set up already! Nothing to do here right now. */
}
/* If we failed, do some cleanup. */
if (FAILED(hr))
{
if (m_pCred != NULL)
{
m_pCred->Release();
m_pCred = NULL;
}
}
break;
}
case CPUS_CHANGE_PASSWORD:
case CPUS_CREDUI:
case CPUS_PLAP:
hr = E_NOTIMPL;
break;
default:
hr = E_INVALIDARG;
break;
}
VBoxCredProvVerbose(0, "VBoxCredProv::SetUsageScenario returned hr=0x%08x\n", hr);
return hr;
}
/**
* Tells this provider how the serialization will be handled. Currently not used.
*
* @return STDMETHODIMP
* @param pcpCredentialSerialization Credentials serialization.
*/
STDMETHODIMP
VBoxCredProvProvider::SetSerialization(const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpCredentialSerialization)
{
NOREF(pcpCredentialSerialization);
return E_NOTIMPL;
}
/**
* Initializes the communication with LogonUI through callbacks events which we can later
* use to start re-enumeration of credentials.
*
* @return HRESULT
* @param pcpEvents Pointer to event interface.
* @param upAdviseContext The current advise context.
*/
HRESULT
VBoxCredProvProvider::Advise(ICredentialProviderEvents *pcpEvents, UINT_PTR upAdviseContext)
{
VBoxCredProvVerbose(0, "VBoxCredProv::Advise, pcpEvents=0x%p, upAdviseContext=%u\n",
pcpEvents, upAdviseContext);
if (m_pEvents)
{
m_pEvents->Release();
m_pEvents = NULL;
}
m_pEvents = pcpEvents;
if (m_pEvents)
m_pEvents->AddRef();
/*
* Save advice context for later use when binding to
* certain ICredentialProviderEvents events.
*/
m_upAdviseContext = upAdviseContext;
return S_OK;
}
/**
* Uninitializes the callback events so that they're no longer valid.
*
* @return HRESULT
*/
HRESULT
VBoxCredProvProvider::UnAdvise(void)
{
VBoxCredProvVerbose(0, "VBoxCredProv::UnAdvise: pEvents=0x%p\n",
m_pEvents);
if (m_pEvents)
{
m_pEvents->Release();
m_pEvents = NULL;
}
return S_OK;
}
/**
* Retrieves the total count of fields we're handling (needed for field enumeration
* through LogonUI).
*
* @return HRESULT
* @param pdwCount Receives total count of fields.
*/
HRESULT
VBoxCredProvProvider::GetFieldDescriptorCount(DWORD *pdwCount)
{
if (pdwCount)
{
*pdwCount = VBOXCREDPROV_NUM_FIELDS;
VBoxCredProvVerbose(0, "VBoxCredProv::GetFieldDescriptorCount: %ld\n", *pdwCount);
}
return S_OK;
}
/**
* Retrieves a descriptor of a specified field.
*
* @return HRESULT
* @param dwIndex ID of field to retrieve descriptor for.
* @param ppFieldDescriptor Pointer which receives the allocated field
* descriptor.
*/
HRESULT
VBoxCredProvProvider::GetFieldDescriptorAt(DWORD dwIndex, CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR **ppFieldDescriptor)
{
HRESULT hr = S_OK;
if ( dwIndex < VBOXCREDPROV_NUM_FIELDS
&& ppFieldDescriptor)
{
PCREDENTIAL_PROVIDER_FIELD_DESCRIPTOR pcpFieldDesc =
(PCREDENTIAL_PROVIDER_FIELD_DESCRIPTOR)CoTaskMemAlloc(sizeof(CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR));
if (pcpFieldDesc)
{
const VBOXCREDPROV_FIELD &field = s_VBoxCredProvFields[dwIndex];
RT_BZERO(pcpFieldDesc, sizeof(CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR));
pcpFieldDesc->dwFieldID = field.desc.dwFieldID;
pcpFieldDesc->cpft = field.desc.cpft;
if (field.desc.pszLabel)
hr = SHStrDupW(field.desc.pszLabel, &pcpFieldDesc->pszLabel);
}
else
hr = E_OUTOFMEMORY;
if (SUCCEEDED(hr))
*ppFieldDescriptor = pcpFieldDesc;
else
CoTaskMemFree(pcpFieldDesc);
}
else
hr = E_INVALIDARG;
VBoxCredProvVerbose(0, "VBoxCredProv::GetFieldDescriptorAt: dwIndex=%ld, ppDesc=0x%p, hr=0x%08x\n",
dwIndex, ppFieldDescriptor, hr);
return hr;
}
/**
* Retrieves the total number of credentials this provider can offer at the current time and
* if a logon attempt should be made.
*
* @return HRESULT
* @param pdwCount Receives number of credentials to serve.
* @param pdwDefault Receives the credentials index to try
* logging on if there is more than one
* credential provided. 0 is default.
* @param pfAutoLogonWithDefault Receives a flag indicating whether a
* logon attempt using the default
* credential should be made or not.
*/
HRESULT
VBoxCredProvProvider::GetCredentialCount(DWORD *pdwCount, DWORD *pdwDefault, BOOL *pfAutoLogonWithDefault)
{
AssertPtr(pdwCount);
AssertPtr(pdwDefault);
AssertPtr(pfAutoLogonWithDefault);
bool fHasCredentials = false;
/* Do we have credentials? */
if (m_pCred)
{
int rc = m_pCred->RetrieveCredentials();
fHasCredentials = rc == VINF_SUCCESS;
}
if (fHasCredentials)
{
*pdwCount = 1; /* This provider always has the same number of credentials (1). */
*pdwDefault = 0; /* The credential we provide is *always* at index 0! */
*pfAutoLogonWithDefault = TRUE; /* We always at least try to auto-login (if password is correct). */
}
else
{
*pdwCount = 0;
*pdwDefault = CREDENTIAL_PROVIDER_NO_DEFAULT;
*pfAutoLogonWithDefault = FALSE;
}
VBoxCredProvVerbose(0, "VBoxCredProv::GetCredentialCount: *pdwCount=%ld, *pdwDefault=%ld, *pfAutoLogonWithDefault=%s\n",
*pdwCount, *pdwDefault, *pfAutoLogonWithDefault ? "true" : "false");
return S_OK;
}
/**
* Called by Winlogon to retrieve the interface of our current ICredentialProviderCredential interface.
*
* @return HRESULT
* @param dwIndex Index of credential (in case there is more than one credential at a time) to
* retrieve the interface for.
* @param ppCredProvCredential Pointer that receives the credential interface.
*/
HRESULT
VBoxCredProvProvider::GetCredentialAt(DWORD dwIndex, ICredentialProviderCredential **ppCredProvCredential)
{
VBoxCredProvVerbose(0, "VBoxCredProv::GetCredentialAt: Index=%ld, ppCredProvCredential=0x%p\n",
dwIndex, ppCredProvCredential);
if (!m_pCred)
{
VBoxCredProvVerbose(0, "VBoxCredProv::GetCredentialAt: No credentials available\n");
return E_INVALIDARG;
}
HRESULT hr;
if ( dwIndex == 0
&& ppCredProvCredential)
{
hr = m_pCred->QueryInterface(IID_ICredentialProviderCredential,
reinterpret_cast<void**>(ppCredProvCredential));
}
else
{
VBoxCredProvVerbose(0, "VBoxCredProv::GetCredentialAt: More than one credential not supported!\n");
hr = E_INVALIDARG;
}
return hr;
}
/**
* Triggers a credential re-enumeration -- will be called by our poller thread. This then invokes
* GetCredentialCount() and GetCredentialAt() called by Winlogon.
*/
void
VBoxCredProvProvider::OnCredentialsProvided(void)
{
VBoxCredProvVerbose(0, "VBoxCredProv::OnCredentialsProvided\n");
if (m_pEvents)
m_pEvents->CredentialsChanged(m_upAdviseContext);
}
/**
* Creates our provider. This happens *before* CTRL-ALT-DEL was pressed!
*/
HRESULT
VBoxCredProvProviderCreate(REFIID interfaceID, void **ppvInterface)
{
HRESULT hr;
/** @todo try-catch. */
VBoxCredProvProvider *pProvider = new VBoxCredProvProvider();
if (pProvider)
{
hr = pProvider->QueryInterface(interfaceID, ppvInterface);
pProvider->Release();
}
else
hr = E_OUTOFMEMORY;
return hr;
}