// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"


// function definitions

extern "C" HRESULT ApprovedExesParseFromXml(
    __in BURN_APPROVED_EXES* pApprovedExes,
    __in IXMLDOMNode* pixnBundle
    )
{
    HRESULT hr = S_OK;
    IXMLDOMNodeList* pixnNodes = NULL;
    IXMLDOMNode* pixnNode = NULL;
    DWORD cNodes = 0;
    LPWSTR scz = NULL;

    // select approved exe nodes
    hr = XmlSelectNodes(pixnBundle, L"ApprovedExeForElevation", &pixnNodes);
    ExitOnFailure(hr, "Failed to select approved exe nodes.");

    // get approved exe node count
    hr = pixnNodes->get_length((long*)&cNodes);
    ExitOnFailure(hr, "Failed to get approved exe node count.");

    if (!cNodes)
    {
        ExitFunction();
    }

    // allocate memory for approved exes
    pApprovedExes->rgApprovedExes = (BURN_APPROVED_EXE*)MemAlloc(sizeof(BURN_APPROVED_EXE) * cNodes, TRUE);
    ExitOnNull(pApprovedExes->rgApprovedExes, hr, E_OUTOFMEMORY, "Failed to allocate memory for approved exe structs.");

    pApprovedExes->cApprovedExes = cNodes;

    // parse approved exe elements
    for (DWORD i = 0; i < cNodes; ++i)
    {
        BURN_APPROVED_EXE* pApprovedExe = &pApprovedExes->rgApprovedExes[i];

        hr = XmlNextElement(pixnNodes, &pixnNode, NULL);
        ExitOnFailure(hr, "Failed to get next node.");

        // @Id
        hr = XmlGetAttributeEx(pixnNode, L"Id", &pApprovedExe->sczId);
        ExitOnFailure(hr, "Failed to get @Id.");

        // @Key
        hr = XmlGetAttributeEx(pixnNode, L"Key", &pApprovedExe->sczKey);
        ExitOnFailure(hr, "Failed to get @Key.");

        // @ValueName
        hr = XmlGetAttributeEx(pixnNode, L"ValueName", &pApprovedExe->sczValueName);
        if (E_NOTFOUND != hr)
        {
            ExitOnFailure(hr, "Failed to get @ValueName.");
        }

        // @Win64
        hr = XmlGetYesNoAttribute(pixnNode, L"Win64", &pApprovedExe->fWin64);
        if (E_NOTFOUND != hr)
        {
            ExitOnFailure(hr, "Failed to get @Win64.");
        }

        // prepare next iteration
        ReleaseNullObject(pixnNode);
        ReleaseNullStr(scz);
    }

    hr = S_OK;

LExit:
    ReleaseObject(pixnNodes);
    ReleaseObject(pixnNode);
    ReleaseStr(scz);
    return hr;
}

extern "C" void ApprovedExesUninitialize(
    __in BURN_APPROVED_EXES* pApprovedExes
    )
{
    if (pApprovedExes->rgApprovedExes)
    {
        for (DWORD i = 0; i < pApprovedExes->cApprovedExes; ++i)
        {
            BURN_APPROVED_EXE* pApprovedExe = &pApprovedExes->rgApprovedExes[i];

            ReleaseStr(pApprovedExe->sczId);
            ReleaseStr(pApprovedExe->sczKey);
            ReleaseStr(pApprovedExe->sczValueName);
        }
        MemFree(pApprovedExes->rgApprovedExes);
    }
}

extern "C" void ApprovedExesUninitializeLaunch(
    __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe
    )
{
    if (pLaunchApprovedExe)
    {
        ReleaseStr(pLaunchApprovedExe->sczArguments);
        ReleaseStr(pLaunchApprovedExe->sczExecutablePath);
        ReleaseStr(pLaunchApprovedExe->sczId);
    }
}

extern "C" HRESULT ApprovedExesFindById(
    __in BURN_APPROVED_EXES* pApprovedExes,
    __in_z LPCWSTR wzId,
    __out BURN_APPROVED_EXE** ppApprovedExe
    )
{
    HRESULT hr = S_OK;
    BURN_APPROVED_EXE* pApprovedExe = NULL;

    for (DWORD i = 0; i < pApprovedExes->cApprovedExes; ++i)
    {
        pApprovedExe = &pApprovedExes->rgApprovedExes[i];

        if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pApprovedExe->sczId, -1, wzId, -1))
        {
            *ppApprovedExe = pApprovedExe;
            ExitFunction1(hr = S_OK);
        }
    }

    hr = E_NOTFOUND;

LExit:
    return hr;
}

extern "C" HRESULT ApprovedExesLaunch(
    __in BURN_VARIABLES* pVariables,
    __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe,
    __out DWORD* pdwProcessId
    )
{
    HRESULT hr = S_OK;
    LPWSTR sczArgumentsFormatted = NULL;
    LPWSTR sczArgumentsObfuscated = NULL;
    LPWSTR sczCommand = NULL;
    LPWSTR sczCommandObfuscated = NULL;
    LPWSTR sczExecutableDirectory = NULL;
    size_t cchExecutableDirectory = 0;
    STARTUPINFOW si = { };
    PROCESS_INFORMATION pi = { };

    // build command
    if (pLaunchApprovedExe->sczArguments && *pLaunchApprovedExe->sczArguments)
    {
        hr = VariableFormatString(pVariables, pLaunchApprovedExe->sczArguments, &sczArgumentsFormatted, NULL);
        ExitOnFailure(hr, "Failed to format argument string.");

        hr = StrAllocFormattedSecure(&sczCommand, L"\"%ls\" %s", pLaunchApprovedExe->sczExecutablePath, sczArgumentsFormatted);
        ExitOnFailure(hr, "Failed to create executable command.");

        hr = VariableFormatStringObfuscated(pVariables, pLaunchApprovedExe->sczArguments, &sczArgumentsObfuscated, NULL);
        ExitOnFailure(hr, "Failed to format obfuscated argument string.");

        hr = StrAllocFormatted(&sczCommandObfuscated, L"\"%ls\" %s", pLaunchApprovedExe->sczExecutablePath, sczArgumentsObfuscated);
    }
    else
    {
        hr = StrAllocFormatted(&sczCommand, L"\"%ls\"", pLaunchApprovedExe->sczExecutablePath);
        ExitOnFailure(hr, "Failed to create executable command.");

        hr = StrAllocFormatted(&sczCommandObfuscated, L"\"%ls\"", pLaunchApprovedExe->sczExecutablePath);
    }
    ExitOnFailure(hr, "Failed to create obfuscated executable command.");

    // Try to get the directory of the executable so we can set the current directory of the process to help those executables
    // that expect stuff to be relative to them.  Best effort only.
    hr = PathGetDirectory(pLaunchApprovedExe->sczExecutablePath, &sczExecutableDirectory);
    if (SUCCEEDED(hr))
    {
        // CreateProcessW has undocumented MAX_PATH restriction for lpCurrentDirectory even when long path support is enabled.
        hr = ::StringCchLengthW(sczExecutableDirectory, MAX_PATH - 1, &cchExecutableDirectory);
    }

    if (FAILED(hr))
    {
        ReleaseNullStr(sczExecutableDirectory);

        hr = S_OK;
    }

    LogId(REPORT_STANDARD, MSG_LAUNCHING_APPROVED_EXE, pLaunchApprovedExe->sczExecutablePath, sczCommandObfuscated);

    si.cb = sizeof(si);
    if (!::CreateProcessW(pLaunchApprovedExe->sczExecutablePath, sczCommand, NULL, NULL, FALSE, CREATE_NEW_PROCESS_GROUP, NULL, sczExecutableDirectory, &si, &pi))
    {
        ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", pLaunchApprovedExe->sczExecutablePath);
    }

    *pdwProcessId = pi.dwProcessId;

    if (pLaunchApprovedExe->dwWaitForInputIdleTimeout)
    {
        ::WaitForInputIdle(pi.hProcess, pLaunchApprovedExe->dwWaitForInputIdleTimeout);
    }

LExit:
    StrSecureZeroFreeString(sczArgumentsFormatted);
    ReleaseStr(sczArgumentsObfuscated);
    StrSecureZeroFreeString(sczCommand);
    ReleaseStr(sczCommandObfuscated);
    ReleaseStr(sczExecutableDirectory);

    ReleaseHandle(pi.hThread);
    ReleaseHandle(pi.hProcess);

    return hr;
}

extern "C" HRESULT ApprovedExesVerifySecureLocation(
    __in BURN_CACHE* pCache,
    __in BURN_VARIABLES* pVariables,
    __in LPCWSTR wzExecutablePath
    )
{
    HRESULT hr = S_OK;
    LPWSTR scz = NULL;
    LPWSTR sczSecondary = NULL;

    const LPCWSTR vrgSecureFolderVariables[] = {
        L"ProgramFiles64Folder",
        L"ProgramFilesFolder",
    };

    for (DWORD i = 0; i < countof(vrgSecureFolderVariables); ++i)
    {
        LPCWSTR wzSecureFolderVariable = vrgSecureFolderVariables[i];

        hr = VariableGetString(pVariables, wzSecureFolderVariable, &scz);
        if (SUCCEEDED(hr))
        {
            hr = PathDirectoryContainsPath(scz, wzExecutablePath);
            if (S_OK == hr)
            {
                ExitFunction();
            }
        }
        else if (E_NOTFOUND != hr)
        {
            ExitOnFailure(hr, "Failed to get the variable: %ls", wzSecureFolderVariable);
        }
    }

    // The problem with using a Variable for the root package cache folder is that it might not have been secured yet.
    // Getting it through CacheGetPerMachineRootCompletedPath makes sure it has been secured.
    hr = CacheGetPerMachineRootCompletedPath(pCache, &scz, &sczSecondary);
    ExitOnFailure(hr, "Failed to get the root package cache folder.");

    // If the package cache is redirected, hr is S_FALSE.
    if (S_FALSE == hr)
    {
        hr = PathDirectoryContainsPath(sczSecondary, wzExecutablePath);
        if (S_OK == hr)
        {
            ExitFunction();
        }
    }

    hr = PathDirectoryContainsPath(scz, wzExecutablePath);
    if (S_OK == hr)
    {
        ExitFunction();
    }

    hr = S_FALSE;

LExit:
    ReleaseStr(scz);
    ReleaseStr(sczSecondary);

    return hr;
}