/* $Id: VBoxCredProvCredential.cpp $ */
/** @file
 * VBoxCredProvCredential - Class for keeping and handling the passed credentials.
 */

/*
 * 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                                                               *
*******************************************************************************/
#ifndef WIN32_NO_STATUS
# include <ntstatus.h>
# define WIN32_NO_STATUS
#endif

#include "VBoxCredentialProvider.h"

#include "VBoxCredProvProvider.h"
#include "VBoxCredProvCredential.h"
#include "VBoxCredProvUtils.h"

#include <lm.h>

#include <iprt/initterm.h>
#include <iprt/mem.h>
#include <iprt/string.h>



VBoxCredProvCredential::VBoxCredProvCredential(void) :
    m_enmUsageScenario(CPUS_INVALID),
    m_cRefs(1),
    m_pEvents(NULL)
{
    VBoxCredProvVerbose(0, "VBoxCredProvCredential: Created\n");
    VBoxCredentialProviderAcquire();
    RT_BZERO(m_apwszCredentials, sizeof(PRTUTF16) * VBOXCREDPROV_NUM_FIELDS);
}


VBoxCredProvCredential::~VBoxCredProvCredential(void)
{
    VBoxCredProvVerbose(0, "VBoxCredProvCredential: Destroying\n");
    Reset();
    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<IUnknown*>(this);
            reinterpret_cast<IUnknown*>(*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, VERR_INVALID_POINTER);
    AssertPtrReturn(pwszSource, VERR_INVALID_POINTER);

    size_t cbLen = RTUtf16Len(pwszSource) * sizeof(RTUTF16);

    pUnicodeDest->Length        = cbLen;
    pUnicodeDest->MaximumLength = pUnicodeDest->Length;

    if (fCopy)
        memcpy(pUnicodeDest->Buffer, pwszSource, cbLen);
    else /* Just assign the buffer. */
        pUnicodeDest->Buffer    = pwszSource;

    return S_OK;
}


HRESULT
VBoxCredProvCredential::AllocateLogonPackage(const KERB_INTERACTIVE_UNLOCK_LOGON &rUnlockLogon, PBYTE *ppPackage, DWORD *pcbPackage)
{
    AssertPtrReturn(ppPackage, E_INVALIDARG);
    AssertPtrReturn(pcbPackage, E_INVALIDARG);

    const KERB_INTERACTIVE_LOGON *pLogonIn = &rUnlockLogon.Logon;

    /*
     * 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
    VBoxCredProvVerbose(3, "VBoxCredProvCredential::AllocateLogonPackage: Allocating %ld bytes (%d 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;

    /* 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;                         \
    StringDst.Buffer         = (PWSTR)pbBuffer;                          \
    memcpy(StringDst.Buffer, StringSrc.Buffer, StringDst.Length);        \
    StringDst.Buffer         = (PWSTR)(pbBuffer - (PBYTE)LogonOffset);   \
    pbBuffer                += StringDst.Length;

    RT_BZERO(&pLogon->LogonId, sizeof(LUID));

    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;
}


/**
 * Resets (wipes) stored credentials.
 *
 * @return  HRESULT
 */
HRESULT
VBoxCredProvCredential::Reset(void)
{

    VBoxCredProvVerbose(0, "VBoxCredProvCredential::Reset: Wiping credentials user=%ls, pw=%ls, domain=%ls\n",
                        m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
#ifdef DEBUG
                        m_apwszCredentials[VBOXCREDPROV_FIELDID_PASSWORD],
#else
                        L"XXX" /* Don't show any passwords in release mode. */,
#endif
                        m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]);

    VbglR3CredentialsDestroyUtf16(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
                                  m_apwszCredentials[VBOXCREDPROV_FIELDID_PASSWORD],
                                  m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME],
                                  3 /* Passes */);
    HRESULT hr = S_OK;
    if (m_pEvents)
    {
        /* Note: On Windows 8, set "this" to "nullptr". */
        HRESULT hr2 = m_pEvents->SetFieldString(this, VBOXCREDPROV_FIELDID_USERNAME, L"");
        if (SUCCEEDED(hr))
            hr = hr2;
        hr2 = m_pEvents->SetFieldString(this, VBOXCREDPROV_FIELDID_PASSWORD, L"");
        if (SUCCEEDED(hr))
            hr = hr2;
        hr2 = m_pEvents->SetFieldString(this, VBOXCREDPROV_FIELDID_DOMAINNAME, L"");
        if (SUCCEEDED(hr))
            hr = hr2;
    }

    VBoxCredProvVerbose(0, "VBoxCredProvCredential::Reset: Returned hr=%08x\n", hr);
    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)
{
    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(&m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
                                            &m_apwszCredentials[VBOXCREDPROV_FIELDID_PASSWORD],
                                            &m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]);

        VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Retrieved credentials with rc=%Rrc\n", rc);
    }

    if (RT_SUCCESS(rc))
    {
        VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: User=%ls, Password=%ls, Domain=%ls\n",
                            m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
#ifdef DEBUG
                            m_apwszCredentials[VBOXCREDPROV_FIELDID_PASSWORD],
#else
                            L"XXX" /* Don't show any passwords in release mode. */,
#endif
                            m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]);

        /*
         * 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 pwszAcount;
        if (TranslateAccountName(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME], &pwszAcount))
        {
            VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Translated account name %ls -> %ls\n",
                                m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME], pwszAcount);

            if (m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME])
            {
                RTMemWipeThoroughly(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
                                    RTUtf16Len(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME]) + sizeof(RTUTF16),
                                    3 /* Passes */);
                RTUtf16Free(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME]);
            }
            m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME] = pwszAcount;
        }
        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 pwszDomain;
            if (ExtractAccoutData(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
                                  &pwszAcount, &pwszDomain))
            {
                /* Update user name. */
                if (m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME])
                {
                    RTMemWipeThoroughly(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
                                        RTUtf16Len(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME]) + sizeof(RTUTF16),
                                        3 /* Passes */);
                    RTUtf16Free(m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME]);
                }
                m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME] = pwszAcount;

                /* Update domain. */
                if (m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME])
                {
                    RTMemWipeThoroughly(m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME],
                                        RTUtf16Len(m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]) + sizeof(RTUTF16),
                                        3 /* Passes */);
                    RTUtf16Free(m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]);
                }
                m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME] = pwszDomain;

                VBoxCredProvVerbose(0, "VBoxCredProvCredential::RetrieveCredentials: Extracted account data pwszAccount=%ls, pwszDomain=%ls\n",
                                    m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
                                    m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]);
            }
        }
    }

    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;
    }

    m_pEvents = pEvents;
    if (m_pEvents)
        m_pEvents->AddRef();

    return S_OK;
}


/**
 * 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;
    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();

    if (m_pEvents)
        m_pEvents->SetFieldString(this, VBOXCREDPROV_FIELDID_PASSWORD, L"");

    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)
        && pFieldState
        && pFieldstateInteractive)
    {
        *pFieldState            = s_VBoxCredProvFields[dwFieldID].state;
        *pFieldstateInteractive = s_VBoxCredProvFields[dwFieldID].stateInteractive;
    }
    else
        hr = E_INVALIDARG;

    return hr;
}


/**
 * Searches the account name based on a display (real) name (e.g. "John Doe" -> "jdoe").
 * Result "ppwszAccoutName" needs to be freed 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.
 */
BOOL
VBoxCredProvCredential::ExtractAccoutData(PWSTR pwszAccountData, PWSTR *ppwszAccoutName, PWSTR *ppwszDomain)
{
    AssertPtrReturn(pwszAccountData, FALSE);
    VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccoutData: Getting account name for \"%ls\" ...\n",
                        pwszAccountData);
    HRESULT hr = E_FAIL;

    /* Try to figure out whether this is a principal name (user@domain). */
    LPWSTR pPos = NULL;
    if (   (pPos  = StrChrW(pwszAccountData, L'@')) != NULL
        &&  pPos != pwszAccountData)
    {
        DWORD cbSize = (pPos - pwszAccountData) * sizeof(WCHAR);
        LPWSTR pwszName = (LPWSTR)CoTaskMemAlloc(cbSize + sizeof(WCHAR)); /* Space for terminating zero. */
        LPWSTR pwszDomain = NULL;
        AssertPtr(pwszName);
        hr = StringCbCopyN(pwszName, cbSize + sizeof(WCHAR), pwszAccountData, cbSize);
        if (SUCCEEDED(hr))
        {
            *ppwszAccoutName = pwszName;
            *pPos++; /* Skip @, point to domain name (if any). */
            if (    pPos != NULL
                && *pPos != L'\0')
            {
                hr = SHStrDupW(pPos, &pwszDomain);
                if (SUCCEEDED(hr))
                {
                    *ppwszDomain = pwszDomain;
                }
                else
                    VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccoutData: Error copying domain data, hr=%08x\n", hr);
            }
            else
            {
                hr = E_FAIL;
                VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccoutData: No domain name found!\n");
            }
        }
        else
            VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccoutData: Error copying account data, hr=%08x\n", hr);

        if (hr != S_OK)
        {
            CoTaskMemFree(pwszName);
            if (pwszDomain)
                CoTaskMemFree(pwszDomain);
        }
    }
    else
        VBoxCredProvVerbose(0, "VBoxCredProvCredential::ExtractAccoutData: No valid principal account name found!\n");

    return (hr == S_OK);
}


/**
 * Returns the 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;
    if (   dwFieldID < VBOXCREDPROV_NUM_FIELDS
        && ppwszString)
    {
        switch (dwFieldID)
        {
            case VBOXCREDPROV_FIELDID_SUBMIT_BUTTON:
                /* Fill in standard value to make Winlogon happy. */
                hr = SHStrDupW(L"Submit", ppwszString);
                break;

            default:
                if (   m_apwszCredentials[dwFieldID]
                    && RTUtf16Len(m_apwszCredentials[dwFieldID]))
                    hr = SHStrDupW(m_apwszCredentials[dwFieldID], ppwszString);
                else /* Fill in an empty value. */
                    hr = SHStrDupW(L"", ppwszString);
                break;
        }
#ifdef DEBUG
        if (SUCCEEDED(hr))
            VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetStringValue: dwFieldID=%ld, ppwszString=%ls\n",
                                dwFieldID, *ppwszString);
#endif
    }
    else
        hr = E_INVALIDARG;
    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   pcwzString              Actual value to set.
 */
HRESULT
VBoxCredProvCredential::SetStringValue(DWORD dwFieldID, PCWSTR pcwzString)
{
#ifdef DEBUG
    VBoxCredProvVerbose(0, "VBoxCredProvCredential::SetStringValue: dwFieldID=%ld, pcwzString=%ls\n",
                        dwFieldID, pcwzString);
#endif

    /* Do more things here later. */
    HRESULT hr = S_OK;

    VBoxCredProvVerbose(0, "VBoxCredProvCredential::SetStringValue returned with hr=%08x\n", 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 *pKerberosLogon = &KerberosUnlockLogon.Logon;
    AssertPtr(pKerberosLogon);

    HRESULT hr;

#ifdef DEBUG
    VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetSerialization: Username=%ls, Password=%ls, Domain=%ls\n",
                        m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
                        m_apwszCredentials[VBOXCREDPROV_FIELDID_PASSWORD],
                        m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]);
#endif

    /* Do we have a domain name set? */
    if (   m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]
        && RTUtf16Len(m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME]))
    {
        hr = RTUTF16ToUnicode(&pKerberosLogon->LogonDomainName,
                              m_apwszCredentials[VBOXCREDPROV_FIELDID_DOMAINNAME],
                              false /* Just assign, no copy */);
    }
    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 = RTUTF16ToUnicode(&pKerberosLogon->LogonDomainName,
                                  wszComputerName,
                                  false /* Just assign, no copy */);

            VBoxCredProvVerbose(0, "VBoxCredProvCredential::GetSerialization: Local computer name=%ls\n",
                                wszComputerName);
        }
        else
            hr = HRESULT_FROM_WIN32(GetLastError());
    }

    if (SUCCEEDED(hr))
    {
        /* Fill in the username and password. */
        if (SUCCEEDED(hr))
        {
            hr = RTUTF16ToUnicode(&pKerberosLogon->UserName,
                                  m_apwszCredentials[VBOXCREDPROV_FIELDID_USERNAME],
                                  false /* Just assign, no copy */);
            if (SUCCEEDED(hr))
            {
                hr = RTUTF16ToUnicode(&pKerberosLogon->Password,
                                      m_apwszCredentials[VBOXCREDPROV_FIELDID_PASSWORD],
                                      false /* Just assign, no copy */);
                if (SUCCEEDED(hr))
                {
                    /* Set credential type according to current usage scenario. */
                    AssertPtr(pKerberosLogon);
                    switch (m_enmUsageScenario)
                    {
                        case CPUS_UNLOCK_WORKSTATION:
                            pKerberosLogon->MessageType = KerbWorkstationUnlockLogon;
                            break;

                        case CPUS_LOGON:
                            pKerberosLogon->MessageType = KerbInteractiveLogon;
                            break;

                        case CPUS_CREDUI:
                            pKerberosLogon->MessageType = (KERB_LOGON_SUBMIT_TYPE)0; /* No message type required here. */
                            break;

                        default:
                            hr = E_FAIL;
                            break;
                    }

                    if (SUCCEEDED(hr)) /* Build the logon package. */
                        hr = AllocateLogonPackage(KerberosUnlockLogon,
                                                  &pcpCredentialSerialization->rgbSerialization,
                                                  &pcpCredentialSerialization->cbSerialization);

                    if (SUCCEEDED(hr))
                    {
                        ULONG ulAuthPackage;

                        HANDLE hLsa;
                        NTSTATUS s = LsaConnectUntrusted(&hLsa);
                        if (SUCCEEDED(HRESULT_FROM_NT(s)))
                        {
                            LSA_STRING lsaszKerberosName;
                            size_t cchKerberosName;
                            hr = StringCchLengthA(NEGOSSP_NAME_A, USHORT_MAX, &cchKerberosName);
                            if (SUCCEEDED(hr))
                            {
                                USHORT usLength;
                                hr = SizeTToUShort(cchKerberosName, &usLength);
                                if (SUCCEEDED(hr))
                                {
                                    lsaszKerberosName.Buffer        = (PCHAR)NEGOSSP_NAME_A;
                                    lsaszKerberosName.Length        = usLength;
                                    lsaszKerberosName.MaximumLength = lsaszKerberosName.Length + 1;

                                }
                            }

                            if (SUCCEEDED(hr))
                            {
                                s = LsaLookupAuthenticationPackage(hLsa, &lsaszKerberosName,
                                                                   &ulAuthPackage);
                                if (FAILED(HRESULT_FROM_NT(s)))
                                    hr = HRESULT_FROM_NT(s);
                            }

                            LsaDeregisterLogonProcess(hLsa);
                        }

                        if (SUCCEEDED(hr))
                        {
                            pcpCredentialSerialization->ulAuthenticationPackage = ulAuthPackage;
                            pcpCredentialSerialization->clsidCredentialProvider = CLSID_VBoxCredProvider;

                            /* We're done -- let the logon UI know. */
                            *pcpGetSerializationResponse = CPGSR_RETURN_CREDENTIAL_FINISHED;
                        }
                    }
                }
            }
        }
    }

    VBoxCredProvVerbose(0, "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)
{
    VBoxCredProvVerbose(0, "VBoxCredProvCredential::ReportResult: ntStatus=%ld, ntSubStatus=%ld\n",
                        ntStatus, ntSubStatus);
    return S_OK;
}

