// 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"


/********************************************************************
 Helper functions to maintain a list of file shares to create / remove

********************************************************************/
SCA_SMB* NewSmb()
{
    SCA_SMB* pss = (SCA_SMB*)MemAlloc(sizeof(SCA_SMB), TRUE);
    Assert(pss);
    return pss;
}


SCA_SMB_EX_USER_PERMS* NewExUserPermsSmb()
{
    SCA_SMB_EX_USER_PERMS* pExUserPerms = (SCA_SMB_EX_USER_PERMS*)MemAlloc(sizeof(SCA_SMB_EX_USER_PERMS), TRUE);
    Assert(pExUserPerms);
    return pExUserPerms;
}


SCA_SMB* AddSmbToList(SCA_SMB* pssList, SCA_SMB* pss)
{
    if (pssList)
    {
        SCA_SMB* pssT = pssList;
        while (pssT->pssNext)
        {
            pssT  = pssT->pssNext;
        }

        pssT->pssNext = pss;
    }
    else
    {
        pssList = pss;
    }

    return pssList;
}


SCA_SMB_EX_USER_PERMS* AddExUserPermsSmbToList(
    SCA_SMB_EX_USER_PERMS* pExUserPermsList, 
    SCA_SMB_EX_USER_PERMS* pExUserPerms
    )
{
    SCA_SMB_EX_USER_PERMS* pExUserPermsTemp = pExUserPermsList;
    if (pExUserPermsList)
    {
        while (pExUserPermsTemp->pExUserPermsNext)
        {
            pExUserPermsTemp = pExUserPermsTemp->pExUserPermsNext;
        }

        pExUserPermsTemp->pExUserPermsNext = pExUserPerms;
    }
    else
    {
        pExUserPermsList = pExUserPerms;
    }

    return pExUserPermsList;
}

void ScaSmbFreeList(SCA_SMB* pssList)
{
    SCA_SMB* pssDelete = pssList;
    while (pssList)
    {
        pssDelete = pssList;
        pssList = pssList->pssNext;

        MemFree(pssDelete);
    }
}

void ScaExUserPermsSmbFreeList(SCA_SMB_EX_USER_PERMS* pExUserPermsList)
{
    SCA_SMB_EX_USER_PERMS* pExUserPermsDelete = pExUserPermsList;
    while (pExUserPermsList)
    {
        pExUserPermsDelete = pExUserPermsList;
        pExUserPermsList = pExUserPermsList->pExUserPermsNext;

        MemFree(pExUserPermsDelete);
    }
}

// sql query constants
LPCWSTR vcsSmbQuery = L"SELECT `FileShare`, `ShareName`, `Component_`, `Description`, `Directory_` FROM `Wix4FileShare`";

enum eSmbQuery {
    ssqFileShare = 1,
    ssqShareName,
    ssqComponent,
    ssqDescription,
    ssqDirectory,
    };


/********************************************************************
 ScaSmbRead - read all of the information from the msi tables and 
              return a list of file share jobs to be done.

********************************************************************/
HRESULT ScaSmbRead(SCA_SMB** ppssList)
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;
    PMSIHANDLE hView, hRec;

    LPWSTR pwzData = NULL;

    SCA_SMB* pss = NULL;
    BOOL bUserPermissionsTableExists = FALSE;

    if (S_OK != WcaTableExists(L"Wix4FileShare"))
    {
        WcaLog(LOGMSG_VERBOSE, "Skipping ScaSmbCreateShare() - Wix4FileShare table not present");
        ExitFunction1(hr = S_FALSE);
    }

    if (S_OK == WcaTableExists(L"Wix4FileSharePermissions"))
    {
        bUserPermissionsTableExists = TRUE;
    }
    else
    {
        WcaLog(LOGMSG_VERBOSE, "No Additional Permissions - Wix4FileSharePermissions table not present");
    }

    WcaLog(LOGMSG_VERBOSE, "Reading File Share Tables");

    // loop through all the fileshares
    hr = WcaOpenExecuteView(vcsSmbQuery, &hView);
    ExitOnFailure(hr, "Failed to open view on Wix4FileShare table");
    while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
    {
        pss = NewSmb();
        if (!pss)
        {
            hr = E_OUTOFMEMORY;
            break;
        }
        Assert(pss);
        ::ZeroMemory(pss, sizeof(*pss));

        hr = WcaGetRecordString(hRec, ssqFileShare, &pwzData);
        ExitOnFailure(hr, "Failed to get Wix4FileShare.FileShare");
        hr = ::StringCchCopyW(pss->wzId, countof(pss->wzId), pwzData);
        ExitOnFailure(hr, "Failed to copy ID string to smb object");

        hr = WcaGetRecordFormattedString(hRec, ssqShareName, &pwzData);
        ExitOnFailure(hr, "Failed to get Wix4FileShare.ShareName");
        hr = ::StringCchCopyW(pss->wzShareName, countof(pss->wzShareName), pwzData);
        ExitOnFailure(hr, "Failed to copy share name string to smb object");

        hr = WcaGetRecordString(hRec, ssqComponent, &pwzData);
        ExitOnFailure(hr, "Failed to get Component for Wix4FileShare: '%ls'", pss->wzShareName);
        hr = ::StringCchCopyW(pss->wzComponent, countof(pss->wzComponent), pwzData);
        ExitOnFailure(hr, "Failed to copy component string to smb object");

        hr = WcaGetRecordFormattedString(hRec, ssqDescription, &pwzData);
        ExitOnFailure(hr, "Failed to get Share Description for Wix4FileShare: '%ls'", pss->wzShareName);
        hr = ::StringCchCopyW(pss->wzDescription, countof(pss->wzDescription), pwzData);
        ExitOnFailure(hr, "Failed to copy description string to smb object");

        // get component install state
        er = ::MsiGetComponentStateW(WcaGetInstallHandle(), pss->wzComponent, &pss->isInstalled, &pss->isAction);
        hr = HRESULT_FROM_WIN32(er);
        ExitOnFailure(hr, "Failed to get Component state for Wix4FileShare");

        // get the share's directory
        hr = WcaGetRecordString(hRec, ssqDirectory, &pwzData);
        ExitOnFailure(hr, "Failed to get directory for Wix4FileShare: '%ls'", pss->wzShareName);

        WCHAR wzPath[MAX_PATH];
        DWORD dwLen;
        dwLen = countof(wzPath);
        // review: relevant for file shares?
        if (INSTALLSTATE_SOURCE == pss->isAction)
        {
            er = ::MsiGetSourcePathW(WcaGetInstallHandle(), pwzData, wzPath, &dwLen);
        }
        else
        {
            er = ::MsiGetTargetPathW(WcaGetInstallHandle(), pwzData, wzPath, &dwLen);
        }
        hr = HRESULT_FROM_WIN32(er);
        ExitOnFailure(hr, "Failed to get Source/TargetPath for Directory");

        // If the path is to the root of a drive, then it needs a trailing backslash.
        // Otherwise, it can't have a trailing backslash.
        if (3 < dwLen)
        {
            if (wzPath[dwLen - 1] == L'\\')
            {
                wzPath[dwLen - 1] = 0;
            }
        }
        else if (2 == dwLen && wzPath[1] == L':')
        {
            wzPath[2] = L'\\';
            wzPath[3] = 0;
        }

        hr = ::StringCchCopyW(pss->wzDirectory, countof(pss->wzDirectory), wzPath);
        ExitOnFailure(hr, "Failed to copy directory string to smb object");

        // Check to see if additional user & permissions are specified for this share
        if (bUserPermissionsTableExists)
        {
            hr = ScaSmbExPermsRead(pss);
            ExitOnFailure(hr, "Failed to get Additional File Share Permissions");
        }

        *ppssList = AddSmbToList(*ppssList, pss);
        pss = NULL; // set the smb NULL so it doesn't accidentally get freed below
    }

    if (E_NOMOREITEMS == hr)
    {
        hr = S_OK;
    }
    ExitOnFailure(hr, "Failure occured while processing Wix4FileShare table");

LExit:
    // if anything was left over after an error clean it all up
    if (pss)
    {
        ScaSmbFreeList(pss);
    }

    ReleaseStr(pwzData);

    return hr;
}


/********************************************************************
 RetrieveSMBShareUserPermList - retrieve SMB Share's user permission list

********************************************************************/
HRESULT RetrieveFileShareUserPerm(SCA_SMB* pss, SCA_SMB_EX_USER_PERMS** ppExUserPermsList, DWORD *pUserPermsCount)
{
    HRESULT hr = S_OK;
    SHARE_INFO_502* psi = NULL;
    NET_API_STATUS s;
    BOOL bValid, bDaclDefaulted;
    PACL acl = NULL;
    PEXPLICIT_ACCESSW pEA = NULL;
    ULONG nCount = 0;
    DWORD er = ERROR_SUCCESS;
    PSID pSID = NULL;
    DWORD nUserNameSize = MAX_DARWIN_COLUMN;
    DWORD nDomainNameSize = MAX_DARWIN_COLUMN;
    SID_NAME_USE peUse;
    DWORD dwCounter = 0;
    SCA_SMB_EX_USER_PERMS* pExUserPermsList = NULL;
    DWORD dwUserPermsCount = 0;

    *pUserPermsCount = 0;
    s = ::NetShareGetInfo(NULL, pss->wzShareName, 502, (LPBYTE*)&psi);
    WcaLog(LOGMSG_VERBOSE, "retrieving permissions on existing file share.");
    if (NERR_NetNameNotFound == s)
    {
        WcaLog(LOGMSG_VERBOSE, "File share has already been removed.");
        ExitFunction1(hr = S_OK);
    }
    else if (NERR_Success != s || psi == NULL)
    {
        hr = E_FAIL;
        ExitOnFailure(hr, "Failed to get share information with return code: %d", s);
    }
    if (!::GetSecurityDescriptorDacl(psi->shi502_security_descriptor, &bValid, &acl, &bDaclDefaulted) || !bValid)
    {
        ExitOnLastError(hr, "Failed to get acl from security descriptor");
    }

    er = ::GetExplicitEntriesFromAclW(acl, &nCount, &pEA);
    hr = HRESULT_FROM_WIN32(er);
    ExitOnFailure(hr, "Failed to get  access entries from acl for file share %ls", pss->wzShareName);
    for (dwCounter = 0; dwCounter < nCount; ++dwCounter)
    {
        if (TRUSTEE_IS_SID == pEA[dwCounter].Trustee.TrusteeForm)
        {
            SCA_SMB_EX_USER_PERMS* pExUserPerms = NewExUserPermsSmb();
            ::ZeroMemory(pExUserPerms, sizeof(*pExUserPerms));
            pExUserPermsList = AddExUserPermsSmbToList(pExUserPermsList, pExUserPerms);
            pSID = (PSID)(pEA[dwCounter].Trustee.ptstrName);
            if (!::LookupAccountSidW(NULL, pSID, pExUserPerms->scau.wzName, &nUserNameSize, pExUserPerms->scau.wzDomain, &nDomainNameSize, &peUse))
            {
                hr = E_FAIL;
                ExitOnFailure(hr, "Failed to get account name from SID");
            }
            pExUserPerms->nPermissions = pEA[dwCounter].grfAccessPermissions;
            pExUserPerms->accessMode = pEA[dwCounter].grfAccessMode;
            ++dwUserPermsCount;
            nUserNameSize = MAX_DARWIN_COLUMN;
            nDomainNameSize = MAX_DARWIN_COLUMN;
        }
    }
    *ppExUserPermsList = pExUserPermsList;
    *pUserPermsCount = dwUserPermsCount;

LExit:
    if (psi)
    {
        ::NetApiBufferFree(psi);
    }

    if (pEA)
    {
        ::LocalFree(pEA);
    }

    return hr;
}


/********************************************************************
 SchedCreateSmb - schedule one instance of a file share creation

********************************************************************/
HRESULT SchedCreateSmb(SCA_SMB* pss)
{
    HRESULT hr = S_OK;

    WCHAR wzDomainUser[255]; // "domain\user"
    SCA_SMB_EX_USER_PERMS* pExUserPermsList = NULL;
    int nCounter = 0;
    WCHAR* pwzRollbackCustomActionData = NULL;
    WCHAR* pwzCustomActionData = NULL;

    hr = WcaWriteStringToCaData(pss->wzShareName, &pwzRollbackCustomActionData);
    ExitOnFailure(hr, "failed to add ShareName to CustomActionData");

    hr = WcaWriteStringToCaData(pss->wzShareName, &pwzCustomActionData);
    ExitOnFailure(hr, "failed to add ShareName to CustomActionData");

    hr = WcaWriteStringToCaData(pss->wzDescription, &pwzCustomActionData);
    ExitOnFailure(hr, "Failed to add server name to CustomActionData");

    hr = WcaWriteStringToCaData(pss->wzDirectory, &pwzCustomActionData);
    ExitOnFailure(hr, "Failed to add full path instance to CustomActionData");

    hr = WcaWriteStringToCaData(pss->fUseIntegratedAuth ? L"1" : L"0", &pwzCustomActionData);
    ExitOnFailure(hr, "Failed to add server name to CustomActionData");

    hr = WcaWriteIntegerToCaData(pss->nUserPermissionCount, &pwzCustomActionData);
    ExitOnFailure(hr, "Failed to add additional user permission count to CustomActionData");

    if (pss->nUserPermissionCount > 0)
    {
        nCounter = 0;
        for (pExUserPermsList = pss->pExUserPerms; pExUserPermsList; pExUserPermsList = pExUserPermsList->pExUserPermsNext)
        {
            Assert(nCounter < pss->nUserPermissionCount);

            hr = UserBuildDomainUserName(wzDomainUser, countof(wzDomainUser), pExUserPermsList->scau.wzName, pExUserPermsList->scau.wzDomain);
            ExitOnFailure(hr, "Failed to build user and domain name for CustomActionData");
            hr = WcaWriteStringToCaData(wzDomainUser, &pwzCustomActionData);
            ExitOnFailure(hr, "Failed to add server Domain\\UserName to CustomActionData");

            hr = WcaWriteIntegerToCaData((int)pExUserPermsList->accessMode, &pwzCustomActionData);
            ExitOnFailure(hr, "Failed to add access mode to CustomActionData");
            
            hr = WcaWriteIntegerToCaData(pExUserPermsList->nPermissions, &pwzCustomActionData);
            ExitOnFailure(hr, "Failed to add permissions to CustomActionData");
            ++nCounter;
        }
        Assert(nCounter == pss->nUserPermissionCount);
    }

    // Schedule the rollback first
    hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CreateSmbRollback"), pwzRollbackCustomActionData, COST_SMB_DROPSMB);
    ExitOnFailure(hr, "Failed to schedule DropSmb action");

    hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CreateSmb"), pwzCustomActionData, COST_SMB_CREATESMB);
    ExitOnFailure(hr, "Failed to schedule CreateSmb action");

LExit:
    ReleaseStr(pwzRollbackCustomActionData);
    ReleaseStr(pwzCustomActionData);

    if (pExUserPermsList)
    {
        ScaExUserPermsSmbFreeList(pExUserPermsList);
    }

    return hr;
}


/********************************************************************
 ScaSmbInstall - for every file share, schedule the create custom action

********************************************************************/
HRESULT ScaSmbInstall(SCA_SMB* pssList)
{
    HRESULT hr = S_FALSE; // assume nothing will be done
    SCA_SMB* pss = NULL;

    for (pss = pssList; pss; pss = pss->pssNext)
    {
        // if installing this component
        if (WcaIsInstalling(pss->isInstalled, pss->isAction) )
        {
            hr = SchedCreateSmb(pss);
            ExitOnFailure(hr, "Failed to schedule the creation of the fileshare: %ls", pss->wzShareName);
        }
    }

LExit:
    return hr;
}


/********************************************************************
 SchedDropSmb - schedule one instance of a file share removal

********************************************************************/
HRESULT SchedDropSmb(SCA_SMB* pss)
{
    HRESULT hr = S_OK;

    WCHAR* pwzCustomActionData = NULL;
    WCHAR* pwzRollbackCustomActionData = NULL;
    SCA_SMB_EX_USER_PERMS *pExUserPermsList = NULL;
    SCA_SMB_EX_USER_PERMS *pExUserPerm = NULL;
    WCHAR wzDomainUser[255]; // "domain\user"
    DWORD dwUserPermsCount = 0;
    
    // roll back DropSmb
    hr = WcaWriteStringToCaData(pss->wzShareName, &pwzRollbackCustomActionData);
    ExitOnFailure(hr, "failed to add ShareName to CustomActionData");

    hr = WcaWriteStringToCaData(pss->wzDescription, &pwzRollbackCustomActionData);
    ExitOnFailure(hr, "Failed to add server name to CustomActionData");

    hr = WcaWriteStringToCaData(pss->wzDirectory, &pwzRollbackCustomActionData);
    ExitOnFailure(hr, "Failed to add full path instance to CustomActionData");

    hr = WcaWriteStringToCaData(L"1", &pwzRollbackCustomActionData);
    ExitOnFailure(hr, "Failed to add useintegrated flag  to CustomActionData");

    hr = RetrieveFileShareUserPerm(pss, &pExUserPermsList, &dwUserPermsCount);
    ExitOnFailure(hr, "Failed to retrieve SMBShare's user permissions");

    hr = WcaWriteIntegerToCaData((int)dwUserPermsCount, &pwzRollbackCustomActionData);
    ExitOnFailure(hr, "Failed to add additional user permission count to CustomActionData");

    for (pExUserPerm = pExUserPermsList; pExUserPerm; pExUserPerm = pExUserPerm->pExUserPermsNext)
    {
        hr = UserBuildDomainUserName(wzDomainUser, countof(wzDomainUser), pExUserPerm->scau.wzName, pExUserPerm->scau.wzDomain);
        ExitOnFailure(hr, "Failed to build user and domain name for CustomActionData");
        hr = WcaWriteStringToCaData(wzDomainUser, &pwzRollbackCustomActionData);
        ExitOnFailure(hr, "Failed to add server Domain\\UserName to CustomActionData");

        hr = WcaWriteIntegerToCaData((int)pExUserPerm->accessMode, &pwzRollbackCustomActionData);
        ExitOnFailure(hr, "Failed to add access mode to CustomActionData");
        
        hr = WcaWriteIntegerToCaData(pExUserPerm->nPermissions, &pwzRollbackCustomActionData);
        ExitOnFailure(hr, "Failed to add permissions to CustomActionData");
    }

    hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"DropSmbRollback"), pwzRollbackCustomActionData, COST_SMB_CREATESMB);
    ExitOnFailure(hr, "Failed to schedule DropSmbRollback action");

    // DropSMB
    hr = WcaWriteStringToCaData(pss->wzShareName, &pwzCustomActionData);
    ExitOnFailure(hr, "failed to add ShareName to CustomActionData");

    hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"DropSmb"), pwzCustomActionData, COST_SMB_DROPSMB);
    ExitOnFailure(hr, "Failed to schedule DropSmb action");

LExit:
    ReleaseStr(pwzCustomActionData);

    if (pExUserPermsList)
    {
        ScaExUserPermsSmbFreeList(pExUserPermsList);
    }
    
    return hr;

}


/********************************************************************
 ScaSmbUninstall - for every file share, schedule the drop custom action

********************************************************************/
HRESULT ScaSmbUninstall(SCA_SMB* pssList)
{
    HRESULT hr = S_FALSE; // assume nothing will be done
    SCA_SMB* pss = NULL;

    for (pss = pssList; pss; pss = pss->pssNext)
    {
        // if uninstalling this component
        if (WcaIsUninstalling(pss->isInstalled, pss->isAction) )
        {
            hr = SchedDropSmb(pss);
            ExitOnFailure(hr, "Failed to remove file share %ls", pss->wzShareName);
        }
    }

LExit:
    return hr;
}

LPCWSTR vcsSmbExUserPermsQuery = L"SELECT `FileShare_`,`User_`,`Permissions` "
    L"FROM `Wix4FileSharePermissions` WHERE `FileShare_`=?";

enum  eSmbUserPermsQuery {
    ssupqFileShare = 1,
    ssupqUser,
    ssupqPermissions

};


/********************************************************************
 ScaSmbExPermsRead - for Every entry in File Permissions table add a 
                     User Name & Permissions structure to the List

********************************************************************/
HRESULT ScaSmbExPermsRead(SCA_SMB* pss)
{
    HRESULT hr = S_OK;
    PMSIHANDLE hView, hRec;

    LPWSTR pwzData = NULL;
    SCA_SMB_EX_USER_PERMS* pExUserPermsList = pss->pExUserPerms;
    SCA_SMB_EX_USER_PERMS* pExUserPerms = NULL;
    int nCounter = 0;

    hRec = ::MsiCreateRecord(1);
    hr = WcaSetRecordString(hRec, 1, pss->wzId);
    ExitOnFailure(hr, "Failed to look up FileShare");

    hr = WcaOpenView(vcsSmbExUserPermsQuery, &hView);
    ExitOnFailure(hr, "Failed to open view on Wix4FileSharePermissions table");
    hr = WcaExecuteView(hView, hRec);
    ExitOnFailure(hr, "Failed to execute view on Wix4FileSharePermissions table");

    // loop through all User/Permissions paris returned
    while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
    {
        pExUserPerms = NewExUserPermsSmb();
        if (!pExUserPerms)
        {
            hr = E_OUTOFMEMORY;
            break;
        }
        Assert(pExUserPerms);
        ::ZeroMemory(pExUserPerms, sizeof(*pExUserPerms));

        hr = WcaGetRecordString(hRec, ssupqUser, &pwzData);
        ExitOnFailure(hr, "Failed to get Wix4FileSharePermissions.User");
        hr = ScaGetUser(pwzData, &pExUserPerms->scau);
        ExitOnFailure(hr, "Failed to get user information for fileshare: '%ls'", pss->wzShareName);

        hr = WcaGetRecordInteger(hRec, ssupqPermissions, &pExUserPerms->nPermissions);
        ExitOnFailure(hr, "Failed to get Wix4FileSharePermissions.Permissions");
        pExUserPerms->accessMode = SET_ACCESS;  // we only support SET_ACCESS here

        pExUserPermsList = AddExUserPermsSmbToList(pExUserPermsList, pExUserPerms);
        ++nCounter;
        pExUserPerms = NULL; // set the smb NULL so it doesn't accidentally get freed below
    }

    if (E_NOMOREITEMS == hr)
    {
        hr = S_OK;
        pss->pExUserPerms = pExUserPermsList;
        pss->nUserPermissionCount = nCounter;
    }
    ExitOnFailure(hr, "Failure occured while processing FileShare table");

LExit:
    // if anything was left over after an error clean it all up
    if (pExUserPerms)
    {
        ScaExUserPermsSmbFreeList(pExUserPerms);
    }

    ReleaseStr(pwzData);

    return hr;
}