/* $Id: VBoxCredProvCredential.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */ /** @file * VBoxCredProvCredential - Class for keeping and handling the passed credentials. */ /* * Copyright (C) 2012-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #ifndef WIN32_NO_STATUS # include # define WIN32_NO_STATUS #endif #include #include "VBoxCredentialProvider.h" #include "VBoxCredProvProvider.h" #include "VBoxCredProvCredential.h" #include "VBoxCredProvUtils.h" #include #include #include #include #include VBoxCredProvCredential::VBoxCredProvCredential(void) : m_cRefs(1) , m_enmUsageScenario(CPUS_INVALID) , m_pEvents(NULL) , m_fHaveCreds(false) { VBoxCredProvVerbose(0, "VBoxCredProvCredential: Created\n"); VBoxCredentialProviderAcquire(); for (unsigned i = 0; i < VBOXCREDPROV_NUM_FIELDS; i++) { const VBOXCREDPROV_FIELD *pField = &s_VBoxCredProvDefaultFields[i]; m_apwszFields[i] = RTUtf16Dup(pField->desc.pszLabel ? pField->desc.pszLabel : L""); AssertPtr(m_apwszFields[i]); } } VBoxCredProvCredential::~VBoxCredProvCredential(void) { VBoxCredProvVerbose(0, "VBoxCredProvCredential: Destroying\n"); Reset(); for (unsigned i = 0; i < VBOXCREDPROV_NUM_FIELDS; i++) { if (m_apwszFields[i]) { RTUtf16Free(m_apwszFields[i]); m_apwszFields[i] = NULL; } } VBoxCredentialProviderRelease(); } ULONG VBoxCredProvCredential::AddRef(void) { LONG cRefs = InterlockedIncrement(&m_cRefs); VBoxCredProvVerbose(0, "VBoxCredProvCredential::AddRef: Returning refcount=%ld\n", cRefs); return cRefs; } ULONG VBoxCredProvCredential::Release(void) { LONG cRefs = InterlockedDecrement(&m_cRefs); VBoxCredProvVerbose(0, "VBoxCredProvCredential::Release: Returning refcount=%ld\n", cRefs); if (!cRefs) { VBoxCredProvVerbose(0, "VBoxCredProvCredential: Calling destructor\n"); delete this; } return cRefs; } HRESULT VBoxCredProvCredential::QueryInterface(REFIID interfaceID, void **ppvInterface) { HRESULT hr = S_OK;; if (ppvInterface) { if ( IID_IUnknown == interfaceID || IID_ICredentialProviderCredential == interfaceID) { *ppvInterface = static_cast(this); reinterpret_cast(*ppvInterface)->AddRef(); } else { *ppvInterface = NULL; hr = E_NOINTERFACE; } } else hr = E_INVALIDARG; return hr; } /** * Assigns or copies a RTUTF16 string to a UNICODE_STRING. * * When fCopy is false, this does *not* copy its contents * and only assigns its code points to the destination! * When fCopy is true, the actual string buffer gets copied. * * Does not take terminating \0 into account. * * @return HRESULT * @param pUnicodeDest Unicode string assigning the UTF16 string to. * @param pwszSource UTF16 string to assign. * @param fCopy Whether to just assign or copy the actual buffer * contents from source -> dest. */ HRESULT VBoxCredProvCredential::RTUTF16ToUnicode(PUNICODE_STRING pUnicodeDest, PRTUTF16 pwszSource, bool fCopy) { AssertPtrReturn(pUnicodeDest, E_POINTER); AssertPtrReturn(pwszSource, E_POINTER); size_t cbLen = RTUtf16Len(pwszSource) * sizeof(RTUTF16); AssertReturn(cbLen <= USHORT_MAX, E_INVALIDARG); HRESULT hr; if (fCopy) { if (cbLen <= pUnicodeDest->MaximumLength) { memcpy(pUnicodeDest->Buffer, pwszSource, cbLen); pUnicodeDest->Length = (USHORT)cbLen; hr = S_OK; } else hr = E_INVALIDARG; } else /* Just assign the buffer. */ { pUnicodeDest->Buffer = pwszSource; pUnicodeDest->Length = (USHORT)cbLen; hr = S_OK; } return hr; } /** * Copies an UTF16 string into a PUNICODE_STRING by allocating space for it. * * @return HRESULT * @param pUnicodeDest Where to store the copied (allocated) unicode string. * @param pwszSource UTF16 string to copy. */ HRESULT VBoxCredProvCredential::RTUTF16ToUnicodeA(PUNICODE_STRING pUnicodeDest, PRTUTF16 pwszSource) { AssertPtrReturn(pUnicodeDest, E_POINTER); AssertPtrReturn(pwszSource, E_POINTER); size_t cbLen = RTUtf16Len(pwszSource) * sizeof(RTUTF16); pUnicodeDest->Buffer = (LPWSTR)CoTaskMemAlloc(cbLen); if (!pUnicodeDest->Buffer) return E_OUTOFMEMORY; pUnicodeDest->MaximumLength = (USHORT)cbLen; pUnicodeDest->Length = 0; return RTUTF16ToUnicode(pUnicodeDest, pwszSource, true /* fCopy */); } /** * Frees a formerly allocated PUNICODE_STRING. * * @param pUnicode String to free. */ void VBoxCredProvCredential::UnicodeStringFree(PUNICODE_STRING pUnicode) { if (!pUnicode) return; if (pUnicode->Buffer) { Assert(pUnicode->MaximumLength); /* Make sure to wipe contents before free'ing. */ RTMemWipeThoroughly(pUnicode->Buffer, pUnicode->MaximumLength /* MaximumLength is bytes! */, 3 /* Passes */); CoTaskMemFree(pUnicode->Buffer); pUnicode->Buffer = NULL; } pUnicode->Length = 0; pUnicode->MaximumLength = 0; } /** * Creates a KERB_INTERACTIVE_LOGON structure with the given parameters. * Must be destroyed with kerberosLogonDestroy(). * * @return HRESULT * @param pLogon Structure to create. * @param enmUsage Intended usage of the structure. * @param pwszUser User name to use. * @param pwszPassword Password to use. * @param pwszDomain Domain to use. Optional and can be NULL. */ HRESULT VBoxCredProvCredential::kerberosLogonCreate(KERB_INTERACTIVE_LOGON *pLogon, CREDENTIAL_PROVIDER_USAGE_SCENARIO enmUsage, PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszDomain) { AssertPtrReturn(pLogon, E_INVALIDARG); AssertPtrReturn(pwszUser, E_INVALIDARG); AssertPtrReturn(pwszPassword, E_INVALIDARG); /* pwszDomain is optional. */ HRESULT hr; /* Do we have a domain name set? */ if ( pwszDomain && RTUtf16Len(pwszDomain)) { hr = RTUTF16ToUnicodeA(&pLogon->LogonDomainName, pwszDomain); } else /* No domain (FQDN) given, try local computer name. */ { WCHAR wszComputerName[MAX_COMPUTERNAME_LENGTH + 1]; DWORD cch = ARRAYSIZE(wszComputerName); if (GetComputerNameW(wszComputerName, &cch)) { /* Is a domain name missing? Then use the name of the local computer. */ hr = RTUTF16ToUnicodeA(&pLogon->LogonDomainName, wszComputerName); VBoxCredProvVerbose(0, "VBoxCredProvCredential::kerberosLogonInit: Local computer name=%ls\n", wszComputerName); } else hr = HRESULT_FROM_WIN32(GetLastError()); } /* Fill in the username and password. */ if (SUCCEEDED(hr)) { hr = RTUTF16ToUnicodeA(&pLogon->UserName, pwszUser); if (SUCCEEDED(hr)) { hr = RTUTF16ToUnicodeA(&pLogon->Password, pwszPassword); if (SUCCEEDED(hr)) { /* Set credential type according to current usage scenario. */ switch (enmUsage) { case CPUS_UNLOCK_WORKSTATION: pLogon->MessageType = KerbWorkstationUnlockLogon; break; case CPUS_LOGON: pLogon->MessageType = KerbInteractiveLogon; break; case CPUS_CREDUI: pLogon->MessageType = (KERB_LOGON_SUBMIT_TYPE)0; /* No message type required here. */ break; default: VBoxCredProvVerbose(0, "VBoxCredProvCredential::kerberosLogonInit: Unknown usage scenario=%ld\n", enmUsage); hr = E_FAIL; break; } } } } return hr; } /** * Destroys a formerly created KERB_INTERACTIVE_LOGON structure. * * @param pLogon Structure to destroy. */ void VBoxCredProvCredential::kerberosLogonDestroy(KERB_INTERACTIVE_LOGON *pLogon) { if (!pLogon) return; UnicodeStringFree(&pLogon->UserName); UnicodeStringFree(&pLogon->Password); UnicodeStringFree(&pLogon->LogonDomainName); } HRESULT VBoxCredProvCredential::kerberosLogonSerialize(const KERB_INTERACTIVE_LOGON *pLogonIn, PBYTE *ppPackage, DWORD *pcbPackage) { AssertPtrReturn(pLogonIn, E_INVALIDARG); AssertPtrReturn(ppPackage, E_INVALIDARG); AssertPtrReturn(pcbPackage, E_INVALIDARG); /* * First, allocate enough space for the logon structure itself and separate * string buffers right after it to store the actual user, password and domain * credentials. */ DWORD cbLogon = sizeof(KERB_INTERACTIVE_UNLOCK_LOGON) + pLogonIn->LogonDomainName.Length + pLogonIn->UserName.Length + pLogonIn->Password.Length; #ifdef DEBUG /* Do not reveal any hints to credential data in release mode. */ VBoxCredProvVerbose(1, "VBoxCredProvCredential::AllocateLogonPackage: Allocating %ld bytes (%zu bytes credentials)\n", cbLogon, cbLogon - sizeof(KERB_INTERACTIVE_UNLOCK_LOGON)); #endif KERB_INTERACTIVE_UNLOCK_LOGON *pLogon = (KERB_INTERACTIVE_UNLOCK_LOGON*)CoTaskMemAlloc(cbLogon); if (!pLogon) return E_OUTOFMEMORY; /* Make sure to zero everything first. */ RT_BZERO(pLogon, cbLogon); /* Let our byte buffer point to the end of our allocated structure so that it can * be used to store the credential data sequentially in a binary blob * (without terminating \0). */ PBYTE pbBuffer = (PBYTE)pLogon + sizeof(KERB_INTERACTIVE_UNLOCK_LOGON); /* The buffer of the packed destination string does not contain the actual * string content but a relative offset starting at the given * KERB_INTERACTIVE_UNLOCK_LOGON structure. */ #define KERB_CRED_INIT_PACKED(StringDst, StringSrc, LogonOffset) \ StringDst.Length = StringSrc.Length; \ StringDst.MaximumLength = StringSrc.Length; \ if (StringDst.Length) \ { \ StringDst.Buffer = (PWSTR)pbBuffer; \ memcpy(StringDst.Buffer, StringSrc.Buffer, StringDst.Length); \ StringDst.Buffer = (PWSTR)(pbBuffer - (PBYTE)LogonOffset); \ pbBuffer += StringDst.Length; \ } KERB_INTERACTIVE_LOGON *pLogonOut = &pLogon->Logon; pLogonOut->MessageType = pLogonIn->MessageType; KERB_CRED_INIT_PACKED(pLogonOut->LogonDomainName, pLogonIn->LogonDomainName, pLogon); KERB_CRED_INIT_PACKED(pLogonOut->UserName , pLogonIn->UserName, pLogon); KERB_CRED_INIT_PACKED(pLogonOut->Password , pLogonIn->Password, pLogon); *ppPackage = (PBYTE)pLogon; *pcbPackage = cbLogon; #undef KERB_CRED_INIT_PACKED return S_OK; } /** * Returns the current value of a specific credential provider field. * * @return Pointer (const) to the credential provider field requested, or NULL if not found / invalid. * @param dwFieldID Field ID of the credential provider field to get. */ PCRTUTF16 VBoxCredProvCredential::getField(DWORD dwFieldID) { if (dwFieldID >= VBOXCREDPROV_NUM_FIELDS) return NULL; /* Paranoia: Don't ever reveal passwords. */ if (dwFieldID == VBOXCREDPROV_FIELDID_PASSWORD) return NULL; return m_apwszFields[dwFieldID]; } /** * Sets a credential provider field by first zero'ing out its current content in a (hopefully) secure manner, * then applying either the field's default or a new value. * * @return HRESULT * @param dwFieldID Field ID of the credential provider field to reset. * @param pcwszString String to set for the given field. Specify NULL for setting the provider's default value. * @param fNotifyUI Whether to notify the LogonUI about the reset. */ HRESULT VBoxCredProvCredential::setField(DWORD dwFieldID, const PRTUTF16 pcwszString, bool fNotifyUI) { if (dwFieldID >= VBOXCREDPROV_NUM_FIELDS) return E_INVALIDARG; HRESULT hr = S_OK; PRTUTF16 pwszField = m_apwszFields[dwFieldID]; if (pwszField) { /* First, wipe the existing value thoroughly. */ RTMemWipeThoroughly(pwszField, (RTUtf16Len(pwszField) + 1) * sizeof(RTUTF16), 3 /* Passes */); /* Second, free the string. */ RTUtf16Free(pwszField); } /* Either fill in the default value or the one specified in pcwszString. */ pwszField = RTUtf16Dup(pcwszString ? pcwszString : s_VBoxCredProvDefaultFields[dwFieldID].desc.pszLabel); if (pwszField) { m_apwszFields[dwFieldID] = pwszField; /* Update the pointer. */ if ( m_pEvents && fNotifyUI) /* Let the logon UI know if wanted. */ { hr = m_pEvents->SetFieldString(this, dwFieldID, pwszField); } } else hr = E_OUTOFMEMORY; VBoxCredProvVerbose(0, "VBoxCredProvCredential::setField: Setting field dwFieldID=%ld to '%ls', fNotifyUI=%RTbool, hr=0x%08x\n", dwFieldID, #ifdef DEBUG pwszField, #else /* Don't show any passwords in release mode. */ dwFieldID == VBOXCREDPROV_FIELDID_PASSWORD ? L"XXX" : pwszField, #endif fNotifyUI, hr); return hr; } /** * Resets (wipes) stored credentials. * * @return HRESULT */ HRESULT VBoxCredProvCredential::Reset(void) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::Reset: Wiping credentials user=%ls, pw=%ls, domain=%ls\n", m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME] ? m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME] : L"", #ifdef DEBUG m_apwszFields[VBOXCREDPROV_FIELDID_PASSWORD] ? m_apwszFields[VBOXCREDPROV_FIELDID_PASSWORD] : L"", #else L"XXX" /* Don't show any passwords in release mode. */, #endif m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME] ? m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME] : L""); /* Note: Do not reset the user name and domain name here, * as they could still being queried (again) by LogonUI on failed login attempts. */ HRESULT hr = setField(VBOXCREDPROV_FIELDID_PASSWORD, NULL /* Use default value */, true /* fNotifyUI */); m_fIsSelected = false; VBoxCredProvVerbose(0, "VBoxCredProvCredential::Reset\n"); return hr; } /** * Checks and retrieves credentials provided by the host + does account lookup on eventually * renamed user accounts. * * @return IPRT status code. */ int VBoxCredProvCredential::RetrieveCredentials(void) { PRTUTF16 pwszUser = NULL; PRTUTF16 pwszPassword = NULL; PRTUTF16 pwszDomain = NULL; int rc = VbglR3CredentialsQueryAvailability(); if (RT_SUCCESS(rc)) { /* * Set status to "terminating" to let the host know this module now * tries to receive and use passed credentials so that credentials from * the host won't be sent twice. */ VBoxCredProvReportStatus(VBoxGuestFacilityStatus_Terminating); rc = VbglR3CredentialsRetrieveUtf16(&pwszUser, &pwszPassword, &pwszDomain); VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Retrieved credentials with rc=%Rrc\n", rc); } if (RT_SUCCESS(rc)) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Received credentials for user '%ls'\n", pwszUser); /* * In case we got a "display name" (e.g. "John Doe") * instead of the real user name (e.g. "jdoe") we have * to translate the data first ... */ PWSTR pwszExtractedName = NULL; if ( TranslateAccountName(pwszUser, &pwszExtractedName) && pwszExtractedName) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Translated account name '%ls' -> '%ls'\n", pwszUser, pwszExtractedName); RTMemWipeThoroughly(pwszUser, (RTUtf16Len(pwszUser) + 1) * sizeof(RTUTF16), 3 /* Passes */); RTUtf16Free(pwszUser); pwszUser = RTUtf16Dup(pwszExtractedName); CoTaskMemFree(pwszExtractedName); pwszExtractedName = NULL; } else { /* * Okay, no display name, but maybe it's a * principal name from which we have to extract the domain from? * (jdoe@my-domain.sub.net.com -> jdoe in domain my-domain.sub.net.com.) */ PWSTR pwszExtractedDomain = NULL; if (ExtractAccountData(pwszUser, &pwszExtractedName, &pwszExtractedDomain)) { /* Update user name. */ if (pwszExtractedName) { if (pwszUser) { RTMemWipeThoroughly(pwszUser, (RTUtf16Len(pwszUser) + 1) * sizeof(RTUTF16), 3 /* Passes */); RTUtf16Free(pwszUser); } pwszUser = RTUtf16Dup(pwszExtractedName); CoTaskMemFree(pwszExtractedName); pwszExtractedName = NULL; } /* Update domain. */ if (pwszExtractedDomain) { if (pwszDomain) { RTMemWipeThoroughly(pwszDomain, (RTUtf16Len(pwszDomain) + 1) * sizeof(RTUTF16), 3 /* Passes */); RTUtf16Free(pwszDomain); } pwszDomain = RTUtf16Dup(pwszExtractedDomain); CoTaskMemFree(pwszExtractedDomain); pwszExtractedDomain = NULL; } VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Extracted account name '%ls' + domain '%ls'\n", pwszUser ? pwszUser : L"", pwszDomain ? pwszDomain : L""); } } m_fHaveCreds = true; } if (m_fHaveCreds) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Setting fields\n"); setField(VBOXCREDPROV_FIELDID_USERNAME, pwszUser, true /* fNotifyUI */); setField(VBOXCREDPROV_FIELDID_PASSWORD, pwszPassword, true /* fNotifyUI */); setField(VBOXCREDPROV_FIELDID_DOMAINNAME, pwszDomain, true /* fNotifyUI */); } VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Wiping ...\n"); VbglR3CredentialsDestroyUtf16(pwszUser, pwszPassword, pwszDomain, 3 /* cPasses */); VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Returned rc=%Rrc\n", rc); return rc; } /** * Initializes this credential with the current credential provider * usage scenario. */ HRESULT VBoxCredProvCredential::Initialize(CREDENTIAL_PROVIDER_USAGE_SCENARIO enmUsageScenario) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::Initialize: enmUsageScenario=%ld\n", enmUsageScenario); m_enmUsageScenario = enmUsageScenario; return S_OK; } /** * Called by LogonUI when it needs this credential's advice. * * At the moment we only grab the credential provider events so that we can * trigger a re-enumeration of the credentials later. */ HRESULT VBoxCredProvCredential::Advise(ICredentialProviderCredentialEvents *pEvents) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::Advise: pEvents=0x%p\n", pEvents); if (m_pEvents) { m_pEvents->Release(); m_pEvents = NULL; } return pEvents->QueryInterface(IID_PPV_ARGS(&m_pEvents)); } /** * Called by LogonUI when it's finished with handling this credential. * * We only need to release the credential provider events, if any. */ HRESULT VBoxCredProvCredential::UnAdvise(void) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::UnAdvise\n"); if (m_pEvents) { m_pEvents->Release(); m_pEvents = NULL; } return S_OK; } /** * Called by LogonUI when a user profile (tile) has been selected. * * As we don't want Winlogon to try logging in immediately we set pfAutoLogon * to FALSE (if set). */ HRESULT VBoxCredProvCredential::SetSelected(PBOOL pfAutoLogon) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::SetSelected\n"); /* * Don't do auto logon here because it would retry too often with * every credential field (user name, password, domain, ...) which makes * winlogon wait before new login attempts can be made. */ if (pfAutoLogon) *pfAutoLogon = FALSE; m_fIsSelected = true; return S_OK; } /** * Called by LogonUI when a user profile (tile) has been unselected again. */ HRESULT VBoxCredProvCredential::SetDeselected(void) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::SetDeselected\n"); Reset(); return S_OK; } /** * Called by LogonUI to retrieve the (interactive) state of a UI field. */ HRESULT VBoxCredProvCredential::GetFieldState(DWORD dwFieldID, CREDENTIAL_PROVIDER_FIELD_STATE *pFieldState, CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE *pFieldstateInteractive) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetFieldState: dwFieldID=%ld\n", dwFieldID); HRESULT hr = S_OK; if (dwFieldID < VBOXCREDPROV_NUM_FIELDS) { if (pFieldState) *pFieldState = s_VBoxCredProvDefaultFields[dwFieldID].state; if (pFieldstateInteractive) *pFieldstateInteractive = s_VBoxCredProvDefaultFields[dwFieldID].stateInteractive; } else hr = E_INVALIDARG; return hr; } /** * Searches the account name based on a display (real) name (e.g. "John Doe" -> "jdoe"). * * @return TRUE if translation of the account name was successful, FALSE if not. * @param pwszDisplayName Display name to extract account name from. * @param ppwszAccoutName Where to store the extracted account name on success. * Needs to be free'd with CoTaskMemFree(). */ BOOL VBoxCredProvCredential::TranslateAccountName(PWSTR pwszDisplayName, PWSTR *ppwszAccoutName) { AssertPtrReturn(pwszDisplayName, FALSE); VBoxCredProvVerbose(0, "VBoxCredProvCredential::TranslateAccountName: Getting account name for \"%ls\" ...\n", pwszDisplayName); /** @todo Do we need ADS support (e.g. TranslateNameW) here? */ BOOL fFound = FALSE; /* Did we find the desired user? */ NET_API_STATUS rcStatus; DWORD dwLevel = 2; /* Detailed information about user accounts. */ DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH; DWORD dwEntriesRead = 0; DWORD dwTotalEntries = 0; DWORD dwResumeHandle = 0; LPUSER_INFO_2 pBuf = NULL; LPUSER_INFO_2 pCurBuf = NULL; do { rcStatus = NetUserEnum(NULL, /* Server name, NULL for localhost. */ dwLevel, FILTER_NORMAL_ACCOUNT, (LPBYTE*)&pBuf, dwPrefMaxLen, &dwEntriesRead, &dwTotalEntries, &dwResumeHandle); if ( rcStatus == NERR_Success || rcStatus == ERROR_MORE_DATA) { if ((pCurBuf = pBuf) != NULL) { for (DWORD i = 0; i < dwEntriesRead; i++) { /* * Search for the "display name" - that might be * "John Doe" or something similar the user recognizes easier * and may not the same as the "account" name (e.g. "jdoe"). */ if ( pCurBuf && pCurBuf->usri2_full_name && StrCmpI(pwszDisplayName, pCurBuf->usri2_full_name) == 0) { /* * Copy the real user name (e.g. "jdoe") to our * output buffer. */ LPWSTR pwszTemp; HRESULT hr = SHStrDupW(pCurBuf->usri2_name, &pwszTemp); if (hr == S_OK) { *ppwszAccoutName = pwszTemp; fFound = TRUE; } else VBoxCredProvVerbose(0, "VBoxCredProvCredential::TranslateAccountName: Error copying data, hr=%08x\n", hr); break; } pCurBuf++; } } if (pBuf != NULL) { NetApiBufferFree(pBuf); pBuf = NULL; } } } while (rcStatus == ERROR_MORE_DATA && !fFound); if (pBuf != NULL) { NetApiBufferFree(pBuf); pBuf = NULL; } VBoxCredProvVerbose(0, "VBoxCredProvCredential::TranslateAccountName returned rcStatus=%ld, fFound=%RTbool\n", rcStatus, fFound); return fFound; #if 0 DWORD dwErr = NO_ERROR; ULONG cbLen = 0; if ( TranslateNameW(pwszName, NameUnknown, NameUserPrincipal, NULL, &cbLen) && cbLen > 0) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetAccountName: Translated ADS name has %u characters\n", cbLen)); ppwszAccoutName = (PWSTR)RTMemAlloc(cbLen * sizeof(WCHAR)); AssertPtrReturn(pwszName, FALSE); if (TranslateNameW(pwszName, NameUnknown, NameUserPrincipal, ppwszAccoutName, &cbLen)) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetAccountName: Real ADS account name of '%ls' is '%ls'\n", pwszName, ppwszAccoutName)); } else { RTMemFree(ppwszAccoutName); dwErr = GetLastError(); } } else dwErr = GetLastError(); /* The above method for looking up in ADS failed, try another one. */ if (dwErr != NO_ERROR) { dwErr = NO_ERROR; } #endif } /** * Extracts the actual account name & domain from a (raw) account data string. * * This might be a principal or FQDN string. * * @return success indicator. Will fail if input not in a user\@domain format. * @param pwszAccountData Raw account data string to extract data from. * @param ppwszAccountName Where to store the extracted account name on * success. Needs to be freed with CoTaskMemFree(). * @param ppwszDomain Where to store the extracted domain name on * success. Needs to be freed with CoTaskMemFree(). */ /*static*/ bool VBoxCredProvCredential::ExtractAccountData(PWSTR pwszAccountData, PWSTR *ppwszAccountName, PWSTR *ppwszDomain) { AssertPtrReturn(pwszAccountData, FALSE); VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccoutData: Getting account name for \"%ls\" ...\n", pwszAccountData); /** @todo r=bird: The original code seemed a little confused about whether * the domain stuff was optional or not, as it declared pwszDomain * very early and freed it in the error path. Not entirely sure what * to make of that... */ /* Try to figure out whether this is a principal name (user@domain). */ LPWSTR const pwszAt = StrChrW(pwszAccountData, L'@'); if (pwszAt && pwszAt != pwszAccountData) { if (pwszAt[1]) { size_t cwcUser = (size_t)(pwszAt - pwszAccountData) + 1; LPWSTR pwszName = (LPWSTR)CoTaskMemAlloc(cwcUser * sizeof(WCHAR)); if (pwszName) { int rc = RTUtf16CopyEx(pwszName, cwcUser, pwszAccountData, cwcUser - 1); if (RT_SUCCESS(rc)) { LPWSTR pwszDomain = NULL; HRESULT hr = SHStrDupW(&pwszAt[1], &pwszDomain); if (SUCCEEDED(hr)) { *ppwszAccountName = pwszName; *ppwszDomain = pwszDomain; return true; } VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: Error copying domain data, hr=%08x\n", hr); } else VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: Error copying account data, rc=%Rrc\n", rc); CoTaskMemFree(pwszName); } else VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: allocation failure.\n"); } else VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: No domain name found!\n"); } else VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccountData: No valid principal account name found!\n"); return false; } /** * Returns the current value of a specified LogonUI field. * * @return IPRT status code. * @param dwFieldID Field ID to get value for. * @param ppwszString Pointer that receives the actual value of the specified field. */ HRESULT VBoxCredProvCredential::GetStringValue(DWORD dwFieldID, PWSTR *ppwszString) { HRESULT hr; PWSTR pwszString = NULL; if (dwFieldID < VBOXCREDPROV_NUM_FIELDS) { switch (dwFieldID) { case VBOXCREDPROV_FIELDID_SUBMIT_BUTTON: { /* Fill in standard value to make Winlogon happy. */ hr = SHStrDupW(L"Submit", &pwszString); break; } default: { if ( m_apwszFields[dwFieldID] && m_apwszFields[dwFieldID][0]) hr = SHStrDupW(m_apwszFields[dwFieldID], &pwszString); else /* Fill in an empty value. */ hr = SHStrDupW(L"", &pwszString); break; } } } else hr = E_INVALIDARG; VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetStringValue: m_fIsSelected=%RTbool, dwFieldID=%ld, pwszString=%ls, hr=%Rhrc\n", m_fIsSelected, dwFieldID, #ifdef DEBUG pwszString ? pwszString : L"", #else /* Don't show any passwords in release mode. */ dwFieldID == VBOXCREDPROV_FIELDID_PASSWORD ? L"XXX" : (pwszString ? pwszString : L""), #endif hr); if (ppwszString) *ppwszString = pwszString; else if (pwszString) CoTaskMemFree(pwszString); return hr; } /** * Returns back the field ID of which the submit button should be put next to. * * We always want to be the password field put next to the submit button * currently. * * @return HRESULT * @param dwFieldID Field ID of the submit button. * @param pdwAdjacentTo Field ID where to put the submit button next to. */ HRESULT VBoxCredProvCredential::GetSubmitButtonValue(DWORD dwFieldID, DWORD *pdwAdjacentTo) { VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetSubmitButtonValue: dwFieldID=%ld\n", dwFieldID); HRESULT hr = S_OK; /* Validate parameters. */ if ( dwFieldID == VBOXCREDPROV_FIELDID_SUBMIT_BUTTON && pdwAdjacentTo) { /* pdwAdjacentTo is a pointer to the fieldID you want the submit button to appear next to. */ *pdwAdjacentTo = VBOXCREDPROV_FIELDID_PASSWORD; VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetSubmitButtonValue: dwFieldID=%ld, *pdwAdjacentTo=%ld\n", dwFieldID, *pdwAdjacentTo); } else hr = E_INVALIDARG; return hr; } /** * Sets the value of a specified field. Currently not used. * * @return HRESULT * @param dwFieldID Field to set value for. * @param pwszValue Actual value to set. */ HRESULT VBoxCredProvCredential::SetStringValue(DWORD dwFieldID, PCWSTR pwszValue) { RT_NOREF(dwFieldID, pwszValue); /* Do more things here later. */ HRESULT hr = S_OK; VBoxCredProvVerbose(0, "VBoxCredProvCredential::SetStringValue: dwFieldID=%ld, pcwzString=%ls, hr=%Rhrc\n", dwFieldID, #ifdef DEBUG pwszValue ? pwszValue : L"", #else /* Never show any (sensitive) data in release mode! */ L"XXX", #endif hr); return hr; } HRESULT VBoxCredProvCredential::GetBitmapValue(DWORD dwFieldID, HBITMAP *phBitmap) { NOREF(dwFieldID); NOREF(phBitmap); /* We don't do own bitmaps. */ return E_NOTIMPL; } HRESULT VBoxCredProvCredential::GetCheckboxValue(DWORD dwFieldID, BOOL *pfChecked, PWSTR *ppwszLabel) { NOREF(dwFieldID); NOREF(pfChecked); NOREF(ppwszLabel); return E_NOTIMPL; } HRESULT VBoxCredProvCredential::GetComboBoxValueCount(DWORD dwFieldID, DWORD *pcItems, DWORD *pdwSelectedItem) { NOREF(dwFieldID); NOREF(pcItems); NOREF(pdwSelectedItem); return E_NOTIMPL; } HRESULT VBoxCredProvCredential::GetComboBoxValueAt(DWORD dwFieldID, DWORD dwItem, PWSTR *ppwszItem) { NOREF(dwFieldID); NOREF(dwItem); NOREF(ppwszItem); return E_NOTIMPL; } HRESULT VBoxCredProvCredential::SetCheckboxValue(DWORD dwFieldID, BOOL fChecked) { NOREF(dwFieldID); NOREF(fChecked); return E_NOTIMPL; } HRESULT VBoxCredProvCredential::SetComboBoxSelectedValue(DWORD dwFieldId, DWORD dwSelectedItem) { NOREF(dwFieldId); NOREF(dwSelectedItem); return E_NOTIMPL; } HRESULT VBoxCredProvCredential::CommandLinkClicked(DWORD dwFieldID) { NOREF(dwFieldID); return E_NOTIMPL; } /** * Does the actual authentication stuff to attempt a login. * * @return HRESULT * @param pcpGetSerializationResponse Credential serialization response. * @param pcpCredentialSerialization Details about the current credential. * @param ppwszOptionalStatusText Text to set. Optional. * @param pcpsiOptionalStatusIcon Status icon to set. Optional. */ HRESULT VBoxCredProvCredential::GetSerialization(CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *pcpGetSerializationResponse, CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpCredentialSerialization, PWSTR *ppwszOptionalStatusText, CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon) { NOREF(ppwszOptionalStatusText); NOREF(pcpsiOptionalStatusIcon); KERB_INTERACTIVE_UNLOCK_LOGON KerberosUnlockLogon; RT_BZERO(&KerberosUnlockLogon, sizeof(KerberosUnlockLogon)); /* Save a pointer to the interactive logon struct. */ KERB_INTERACTIVE_LOGON *pLogon = &KerberosUnlockLogon.Logon; #ifdef DEBUG /* Note: NEVER print this in release mode! */ VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetSerialization: Username=%ls, Password=%ls, Domain=%ls\n", m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME], m_apwszFields[VBOXCREDPROV_FIELDID_PASSWORD], m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME]); #endif HRESULT hr = kerberosLogonCreate(pLogon, m_enmUsageScenario, m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME], m_apwszFields[VBOXCREDPROV_FIELDID_PASSWORD], m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME]); if (SUCCEEDED(hr)) { hr = kerberosLogonSerialize(pLogon, &pcpCredentialSerialization->rgbSerialization, &pcpCredentialSerialization->cbSerialization); if (SUCCEEDED(hr)) { HANDLE hLSA; NTSTATUS s = LsaConnectUntrusted(&hLSA); hr = HRESULT_FROM_NT(s); if (SUCCEEDED(hr)) { #if 0 /* eeek. leaving this as an example of how not to handle a string constant. */ size_t cchKerberosName; hr = StringCchLengthA(NEGOSSP_NAME_A, USHORT_MAX, &cchKerberosName); if (SUCCEEDED(hr)) { USHORT usLength; hr = SizeTToUShort(cchKerberosName, &usLength); if (SUCCEEDED(hr)) #endif { LSA_STRING lsaszKerberosName; lsaszKerberosName.Buffer = (PCHAR)NEGOSSP_NAME_A; lsaszKerberosName.Length = sizeof(NEGOSSP_NAME_A) - 1; lsaszKerberosName.MaximumLength = sizeof(NEGOSSP_NAME_A); ULONG ulAuthPackage = 0; s = LsaLookupAuthenticationPackage(hLSA, &lsaszKerberosName, &ulAuthPackage); hr = HRESULT_FROM_NT(s); if (SUCCEEDED(hr)) { pcpCredentialSerialization->ulAuthenticationPackage = ulAuthPackage; pcpCredentialSerialization->clsidCredentialProvider = CLSID_VBoxCredProvider; /* We're done -- let the logon UI know. */ *pcpGetSerializationResponse = CPGSR_RETURN_CREDENTIAL_FINISHED; VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: Finished for user '%ls' (domain '%ls')\n", m_apwszFields[VBOXCREDPROV_FIELDID_USERNAME], m_apwszFields[VBOXCREDPROV_FIELDID_DOMAINNAME]); } else VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: LsaLookupAuthenticationPackage failed with ntStatus=%ld\n", s); } #if 0 } #endif LsaDeregisterLogonProcess(hLSA); } else VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: LsaConnectUntrusted failed with ntStatus=%ld\n", s); } else VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: kerberosLogonSerialize failed with hr=0x%08x\n", hr); kerberosLogonDestroy(pLogon); pLogon = NULL; } else VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization: kerberosLogonCreate failed with hr=0x%08x\n", hr); VBoxCredProvVerbose(1, "VBoxCredProvCredential::GetSerialization returned hr=0x%08x\n", hr); return hr; } /** * Called by LogonUI after a logon attempt was made -- here we could set an additional status * text and/or icon. * * Currently not used. * * @return HRESULT * @param ntStatus NT status of logon attempt reported by Winlogon. * @param ntSubStatus NT substatus of logon attempt reported by Winlogon. * @param ppwszOptionalStatusText Pointer that receives the optional status text. * @param pcpsiOptionalStatusIcon Pointer that receives the optional status icon. */ HRESULT VBoxCredProvCredential::ReportResult(NTSTATUS ntStatus, NTSTATUS ntSubStatus, PWSTR *ppwszOptionalStatusText, CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon) { RT_NOREF(ntStatus, ntSubStatus, ppwszOptionalStatusText, pcpsiOptionalStatusIcon); VBoxCredProvVerbose(0, "VBoxCredProvCredential::ReportResult: ntStatus=%ld, ntSubStatus=%ld\n", ntStatus, ntSubStatus); return E_NOTIMPL; }