#include "pch.h"
#include "dsrole.h"
#pragma hdrstop


/*-----------------------------------------------------------------------------
/ Display specifier helpers/cache functions
/----------------------------------------------------------------------------*/

#define DEFAULT_LANGUAGE      0x409

#define DISPLAY_SPECIFIERS    L"CN=displaySpecifiers"
#define SPECIFIER_PREFIX      L"CN="
#define SPECIFIER_POSTFIX     L"-Display"
#define DEFAULT_SPECIFIER     L"default"


/*-----------------------------------------------------------------------------
/ GetDisplaySpecifier
/ -------------------
/   Get the specified display specifier (sic), given it an LANGID etc.
/
/ In:
/   pccgi -> CLASSCACHEGETINFO structure.
/   riid = interface
/   ppvObject = object requested
/
/ Out:
    HRESULT
/----------------------------------------------------------------------------*/

HRESULT _GetServerConfigPath(LPWSTR *ppszServerConfigPath, LPCLASSCACHEGETINFO pccgi)
{
    HRESULT hres;
    IADs* padsRootDSE = NULL;
    BSTR bstrConfigContainer = NULL;
    VARIANT variant;
    INT cchString;
    LPWSTR pszServer = pccgi->pServer;
    LPWSTR pszMachineServer = NULL;
    USES_CONVERSION;

    *ppszServerConfigPath = NULL;
    VariantInit(&variant);

    //
    // open the RootDSE for the server we are interested in, if we are using the default
    // server then lets just use the cached version.
    //

    hres = GetCacheInfoRootDSE(pccgi, &padsRootDSE);
    if ( (hres == HRESULT_FROM_WIN32(ERROR_NO_SUCH_DOMAIN)) && !pccgi->pServer )
    {
        TraceMsg("Failed to get the RootDSE from the server - not found");

        DSROLE_PRIMARY_DOMAIN_INFO_BASIC *pInfo;
        if ( DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (BYTE**)&pInfo) == WN_SUCCESS )
        {
            if ( pInfo->DomainNameDns )
            {
                Trace(TEXT("Machine domain is: %s"), W2T(pInfo->DomainNameDns));

                CLASSCACHEGETINFO ccgi = *pccgi;
                ccgi.pServer = pInfo->DomainNameDns;

                hres = GetCacheInfoRootDSE(&ccgi, &padsRootDSE);
                if ( SUCCEEDED(hres) )
                {
                    hres = LocalAllocStringW(&pszMachineServer, pInfo->DomainNameDns);
                    pszServer = pszMachineServer;
                }
            }

            DsRoleFreeMemory(pInfo);
        }
    }
    FailGracefully(hres, "Failed to get the IADs for the RootDSE");

    //
    // we now have the RootDSE, so lets read the config container path and compose
    // a string that the outside world cna use
    //

    hres = padsRootDSE->Get(L"configurationNamingContext", &variant);
    FailGracefully(hres, "Failed to get the 'configurationNamingContext' property");

    if ( V_VT(&variant) != VT_BSTR )
        ExitGracefully(hres, E_FAIL, "configurationNamingContext is not a BSTR");

    cchString = lstrlenW(L"LDAP://") + lstrlenW(V_BSTR(&variant));
    
    if ( pszServer )
        cchString += lstrlenW(pszServer) + 1;   // NB: +1 for '/'

    //
    // allocate the buffer we want to use, and fill it
    //

    hres = LocalAllocStringLenW(ppszServerConfigPath, cchString);
    FailGracefully(hres, "Failed to allocate buffer for server path");

    StrCpyW(*ppszServerConfigPath, L"LDAP://");
    
    if ( pszServer )
    {
        StrCatW(*ppszServerConfigPath, pszServer);
        StrCatW(*ppszServerConfigPath, L"/");
    }

    StrCatW(*ppszServerConfigPath, V_BSTR(&variant)); 

    Trace(TEXT("Server config path is: %s"), W2T(*ppszServerConfigPath));
    hres = S_OK;                    // success

exit_gracefully:

    DoRelease(padsRootDSE);
    SysFreeString(bstrConfigContainer);
    LocalFreeStringW(&pszMachineServer);
    VariantClear(&variant);

    return hres;
}

HRESULT _ComposeSpecifierPath(LPWSTR pSpecifier, LANGID langid, LPWSTR pConfigPath, IADsPathname* pDsPathname, BSTR *pbstrDisplaySpecifier)
{
    TCHAR szLANGID[16];
    WCHAR szSpecifierFull[MAX_PATH];
    USES_CONVERSION;
    
    pDsPathname->Set(pConfigPath, ADS_SETTYPE_FULL);
    pDsPathname->AddLeafElement(DISPLAY_SPECIFIERS);

    if ( !langid )
        langid = GetUserDefaultUILanguage();

    wsprintf(szLANGID, TEXT("CN=%x"), langid);
    pDsPathname->AddLeafElement(T2W(szLANGID));

    if ( pSpecifier )
    {
        StrCpyW(szSpecifierFull, SPECIFIER_PREFIX);
        StrCatW(szSpecifierFull, pSpecifier);
        StrCatW(szSpecifierFull, SPECIFIER_POSTFIX);

        Trace(TEXT("szSpecifierFull: %s"), W2T(szSpecifierFull));
        pDsPathname->AddLeafElement(szSpecifierFull);           // add to the name we are dealing with
    }

    return pDsPathname->Retrieve(ADS_FORMAT_WINDOWS, pbstrDisplaySpecifier);
}

HRESULT GetDisplaySpecifier(LPCLASSCACHEGETINFO pccgi, REFIID riid, LPVOID* ppvObject)
{
    HRESULT hr;
    IADsPathname* pDsPathname = NULL;
    BSTR bstrDisplaySpecifier = NULL;
    LPWSTR pszServerConfigPath = NULL;
    USES_CONVERSION;

    TraceEnter(TRACE_CACHE, "GetDisplaySpecifier");
    Trace(TEXT("Display specifier %s, LANGID %x"), W2T(pccgi->pObjectClass), pccgi->langid);

    // When dealing with the local case lets ensure that we enable/disable the flags
    // accordingly.

    if ( !(pccgi->dwFlags & CLASSCACHE_DSAVAILABLE) && !ShowDirectoryUI() )
    {
        ExitGracefully(hr, HRESULT_FROM_WIN32(ERROR_DS_NO_SUCH_OBJECT), "ShowDirectoryUI returned FALSE, and the CLASSCAHCE_DSAVAILABLE flag is not set");
    }
    
    hr = CoCreateInstance(CLSID_Pathname, NULL, CLSCTX_INPROC_SERVER, IID_IADsPathname, (LPVOID*)&pDsPathname);
    FailGracefully(hr, "Failed to get the IADsPathname interface");

    // check to see if we have a valid server config path

    pszServerConfigPath = pccgi->pServerConfigPath;

    if ( !pszServerConfigPath )
    {
        hr = _GetServerConfigPath(&pszServerConfigPath, pccgi);
        FailGracefully(hr, "Failed to allocate server config path");
    }

    hr = _ComposeSpecifierPath(pccgi->pObjectClass, pccgi->langid, pszServerConfigPath, pDsPathname, &bstrDisplaySpecifier);
    FailGracefully(hr, "Failed to retrieve the display specifier path");

    // attempt to bind to the display specifier object, if we fail to find the object
    // then try defaults.

    Trace(TEXT("Calling GetObject on: %s"), W2T(bstrDisplaySpecifier));

    hr = ADsOpenObject(bstrDisplaySpecifier, 
                       pccgi->pUserName, pccgi->pPassword, 
                       pccgi->dwFlags & CLASSCACHE_SIMPLEAUTHENTICATE ? 0:ADS_SECURE_AUTHENTICATION, 
                       riid, ppvObject);

    SysFreeString(bstrDisplaySpecifier);
    if ( hr == HRESULT_FROM_WIN32(ERROR_DS_NO_SUCH_OBJECT) )
    {
        // Display specifier not found. Try the default specifier in the
        // caller's locale. The default specifier is the catch-all for classes
        // that don't have their own specifier.

        hr = _ComposeSpecifierPath(DEFAULT_SPECIFIER, pccgi->langid, pszServerConfigPath, pDsPathname, &bstrDisplaySpecifier);
        FailGracefully(hr, "Failed to retrieve the display specifier path");
        Trace(TEXT("Calling GetObject on: %s"), W2T(bstrDisplaySpecifier));

        hr = ADsOpenObject(bstrDisplaySpecifier, 
                           pccgi->pUserName, pccgi->pPassword, 
                           pccgi->dwFlags & CLASSCACHE_SIMPLEAUTHENTICATE ? 0:ADS_SECURE_AUTHENTICATION, 
                           riid, ppvObject);

        SysFreeString(bstrDisplaySpecifier);
        if ((hr == HRESULT_FROM_WIN32(ERROR_DS_NO_SUCH_OBJECT)) && (pccgi->langid != DEFAULT_LANGUAGE))
        {
            // Now try the object's specifier in the default locale.

            hr = _ComposeSpecifierPath(pccgi->pObjectClass, DEFAULT_LANGUAGE, pszServerConfigPath, pDsPathname, &bstrDisplaySpecifier);
            FailGracefully(hr, "Failed to retrieve the display specifier path");
            Trace(TEXT("Calling GetObject on: %s"), W2T(bstrDisplaySpecifier));

            hr = ADsOpenObject(bstrDisplaySpecifier, 
                               pccgi->pUserName, pccgi->pPassword, 
                               pccgi->dwFlags & CLASSCACHE_SIMPLEAUTHENTICATE ? 0:ADS_SECURE_AUTHENTICATION, 
                               riid, ppvObject);

            SysFreeString(bstrDisplaySpecifier);
            if (hr == HRESULT_FROM_WIN32(ERROR_DS_NO_SUCH_OBJECT))
            {
                // Finally try the default specifier in the default locale.

                hr = _ComposeSpecifierPath(DEFAULT_SPECIFIER, DEFAULT_LANGUAGE, pszServerConfigPath, pDsPathname, &bstrDisplaySpecifier);
                FailGracefully(hr, "Failed to retrieve the display specifier path");
                Trace(TEXT("Calling GetObject on: %s"), W2T(bstrDisplaySpecifier));

                hr = ADsOpenObject(bstrDisplaySpecifier, 
                                   pccgi->pUserName, pccgi->pPassword, 
                                   pccgi->dwFlags & CLASSCACHE_SIMPLEAUTHENTICATE ? 0:ADS_SECURE_AUTHENTICATION, 
                                   riid, ppvObject);

                SysFreeString(bstrDisplaySpecifier);
            }
        }
    }

    FailGracefully(hr, "Failed in ADsOpenObject for display specifier");

    // hr = S_OK;                   // success

exit_gracefully:

    DoRelease(pDsPathname);

    if ( !pccgi->pServerConfigPath )
        LocalFreeStringW(&pszServerConfigPath);

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ GetServerAndCredentails
/ -----------------------
/   Read the server and credentails information from the IDataObject.
/
/ In:
/   pccgi -> CLASSCACHEGETINFO structure to be filled
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT GetServerAndCredentails(CLASSCACHEGETINFO *pccgi)
{
    HRESULT hres;
    STGMEDIUM medium = { TYMED_NULL };
    FORMATETC fmte = {g_cfDsDispSpecOptions, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    USES_CONVERSION;

    TraceEnter(TRACE_UI, "GetServerAndCredentails");

    // we can only get this information if we have a pDataObject to call.

    pccgi->pUserName = NULL;
    pccgi->pPassword = NULL;
    pccgi->pServer = NULL;
    pccgi->pServerConfigPath = NULL;

    if ( pccgi->pDataObject )
    {
        if ( SUCCEEDED(pccgi->pDataObject->GetData(&fmte, &medium)) )
        {
            DSDISPLAYSPECOPTIONS *pdso = (DSDISPLAYSPECOPTIONS*)medium.hGlobal;          
            TraceAssert(pdso);

            // mirror the flags into the CCGI structure

            if ( pdso->dwFlags & DSDSOF_SIMPLEAUTHENTICATE )
            {
                TraceMsg("Setting simple authentication");
                pccgi->dwFlags |= CLASSCACHE_SIMPLEAUTHENTICATE;
            }

            if ( pdso->dwFlags & DSDSOF_DSAVAILABLE )
            {
                TraceMsg("Setting 'DS is available' flags");
                pccgi->dwFlags |= CLASSCACHE_DSAVAILABLE;
            }

            // if we have credentail information that should be copied then lets grab
            // that and put it into the structure.

            if ( pdso->dwFlags & DSDSOF_HASUSERANDSERVERINFO )
            {
                if ( pdso->offsetUserName )
                {
                    LPCWSTR pszUserName = (LPCWSTR)ByteOffset(pdso, pdso->offsetUserName);
                    hres = LocalAllocStringW(&pccgi->pUserName, pszUserName);
                    FailGracefully(hres, "Failed to copy the user name");
                }

                if ( pdso->offsetPassword )
                {
                    LPCWSTR pszPassword = (LPCWSTR)ByteOffset(pdso, pdso->offsetPassword);
                    hres = LocalAllocStringW(&pccgi->pPassword, pszPassword);
                    FailGracefully(hres, "Failed to copy the password");
                }

                if ( pdso->offsetServer )
                {
                    LPCWSTR pszServer = (LPCWSTR)ByteOffset(pdso, pdso->offsetServer);
                    hres = LocalAllocStringW(&pccgi->pServer, pszServer);
                    FailGracefully(hres, "Failed to copy the server");
                }

                if ( pdso->offsetServerConfigPath )
                {
                    LPCWSTR pszServerConfigPath = (LPCWSTR)ByteOffset(pdso, pdso->offsetServerConfigPath);
                    hres = LocalAllocStringW(&pccgi->pServerConfigPath, pszServerConfigPath);
                    FailGracefully(hres, "Failed to copy the server config path");
                }
            }
        }
    }

    hres = S_OK;            // success

exit_gracefully:
    
    if ( FAILED(hres) )
    {
        LocalFreeStringW(&pccgi->pUserName);
        LocalFreeStringW(&pccgi->pPassword);
        LocalFreeStringW(&pccgi->pServer);
        LocalFreeStringW(&pccgi->pServerConfigPath);
    }

    ReleaseStgMedium(&medium);
    
    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ GetAttributePrefix
/ ------------------
/   Get the attribtue prefix we must use to pick up information from the
/   cache / DS.  This is part of the IDataObject we are given, if not then
/   we default to shell behaviour.
/
/ In:
/   ppAttributePrefix -> receives the attribute prefix string
/   pDataObject = IDataObject to query against.
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT GetAttributePrefix(LPWSTR* ppAttributePrefix, IDataObject* pDataObject)
{   
    HRESULT hr;
    STGMEDIUM medium = { TYMED_NULL };
    FORMATETC fmte = {g_cfDsDispSpecOptions, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    PDSDISPLAYSPECOPTIONS pOptions;
    LPWSTR pPrefix = NULL;
    USES_CONVERSION;

    TraceEnter(TRACE_UI, "GetAttributePrefix");

    if ( (SUCCEEDED(pDataObject->GetData(&fmte, &medium))) && (medium.tymed == TYMED_HGLOBAL) )
    {
        pOptions = (PDSDISPLAYSPECOPTIONS)medium.hGlobal;
        pPrefix = (LPWSTR)ByteOffset(pOptions, pOptions->offsetAttribPrefix);

        Trace(TEXT("pOptions->dwSize %d"), pOptions->dwSize);
        Trace(TEXT("pOptions->dwFlags %08x"), pOptions->dwFlags);
        Trace(TEXT("pOptions->offsetAttribPrefix %d (%s)"), pOptions->offsetAttribPrefix, W2T(pPrefix));

        hr = LocalAllocStringW(ppAttributePrefix, pPrefix);
        FailGracefully(hr, "Failed when copying prefix from StgMedium");
    }
    else
    {
        hr = LocalAllocStringW(ppAttributePrefix, DS_PROP_SHELL_PREFIX);
        FailGracefully(hr, "Failed when defaulting the attribute prefix string");
    }

    Trace(TEXT("Resulting prefix: %s"), W2T(*ppAttributePrefix));

    // hr = S_OK;                       // success
       
exit_gracefully:

    ReleaseStgMedium(&medium);

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ GetRootDSE
/ ----------
/   Get the RootDSE given an CLASSCACHEGETINFO structure
/
/ In:
/   pccgi -> CLASSCACHEGETINFO structure.
/   pads -> IADs* interface
/
/ Out:
    HRESULT
/----------------------------------------------------------------------------*/
HRESULT GetRootDSE(LPCWSTR pszUserName, LPCWSTR pszPassword, LPCWSTR pszServer, BOOL fNotSecure, IADs **ppads)
{
    HRESULT hres;
    LPWSTR pszRootDSE = L"/RootDSE";
    WCHAR szBuffer[MAX_PATH];
    USES_CONVERSION;

    TraceEnter(TRACE_CACHE, "GetRootDSE");

    StrCpyW(szBuffer, L"LDAP://");

    if ( pszServer )
        StrCatW(szBuffer, pszServer);
    else
        pszRootDSE++;

    StrCatW(szBuffer, pszRootDSE);

    Trace(TEXT("RootDSE path is: %s"), W2T(szBuffer));

    hres = ADsOpenObject(szBuffer, 
                         (LPWSTR)pszUserName, (LPWSTR)pszPassword, 
                         fNotSecure ? 0:ADS_SECURE_AUTHENTICATION, 
                         IID_IADs, (void **)ppads);
    
    TraceLeaveResult(hres);
}

HRESULT GetCacheInfoRootDSE(LPCLASSCACHEGETINFO pccgi, IADs **ppads)
{
    return GetRootDSE(pccgi->pUserName, pccgi->pPassword, pccgi->pServer,
                      (pccgi->dwFlags & CLASSCACHE_SIMPLEAUTHENTICATE),
                      ppads);
}

