//+--------------------------------------------------------------------------
// File:        officer.cpp
// Contents:    officer rights implementation
//---------------------------------------------------------------------------
#include <pch.cpp>
#include <certsd.h>
#include <certacl.h>
#include <sid.h>

using namespace CertSrv;

HRESULT
COfficerRightsSD::Merge(
        PSECURITY_DESCRIPTOR pOfficerSD,
        PSECURITY_DESCRIPTOR pCASD)
{

    HRESULT hr;
    PACL pCAAcl; // no free
    PACL pOfficerAcl; // no free
    PACL pNewOfficerAcl = NULL;
    ACL_SIZE_INFORMATION CAAclInfo, OfficerAclInfo;
    PBOOL pCAFound = NULL, pOfficerFound = NULL;
    DWORD cCAAce, cOfficerAce;
    PACCESS_ALLOWED_ACE pCAAce;
    PACCESS_ALLOWED_CALLBACK_ACE pOfficerAce;
    PACCESS_ALLOWED_CALLBACK_ACE pNewAce = NULL;
    DWORD dwNewAclSize = sizeof(ACL);
    PSID pSidEveryone = NULL, pSidBuiltinAdministrators = NULL;
    SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_WORLD_SID_AUTHORITY;
    DWORD dwSidEveryoneSize, dwAceSize, dwSidSize;
    PSID_LIST pSidList;
    PSECURITY_DESCRIPTOR pNewOfficerSD = NULL;
    ACL EmptyAcl;
    SECURITY_DESCRIPTOR EmptySD;

    CSASSERT(NULL==pOfficerSD || IsValidSecurityDescriptor(pOfficerSD));
    CSASSERT(IsValidSecurityDescriptor(pCASD));

    // allow NULL officer SD, in that case build an empty SD and use it
    if(NULL==pOfficerSD)
    {
        if(!InitializeAcl(&EmptyAcl, sizeof(ACL), ACL_REVISION))
        {
            hr = myHLastError();
            _JumpError(hr, error, "InitializeAcl");
        }
   
        if (!InitializeSecurityDescriptor(&EmptySD, SECURITY_DESCRIPTOR_REVISION))
        {
            hr = myHLastError();
            _JumpError(hr, error, "InitializeSecurityDescriptor");
        }

        if(!SetSecurityDescriptorDacl(
            &EmptySD,
            TRUE, // DACL present
            &EmptyAcl,
            FALSE)) // DACL defaulted
        {
            hr = myHLastError();
            _JumpError(hr, error, "SetSecurityDescriptorControl");
        }

        pOfficerSD = &EmptySD;
    }

    hr = myGetSecurityDescriptorDacl(pCASD, &pCAAcl);
    _JumpIfError(hr, error, "myGetSecurityDescriptorDacl");

    hr = myGetSecurityDescriptorDacl(pOfficerSD, &pOfficerAcl);
    _JumpIfError(hr, error, "myGetSecurityDescriptorDacl");

    // allocate a bool array for each DACL

    if(!GetAclInformation(pCAAcl,
                          &CAAclInfo,
                          sizeof(CAAclInfo),
                          AclSizeInformation))
    {
        hr = myHLastError();
        _JumpError(hr, error, "GetAclInformation");
    }
    if(!GetAclInformation(pOfficerAcl,
                          &OfficerAclInfo,
                          sizeof(OfficerAclInfo),
                          AclSizeInformation))
    {
        hr = myHLastError();
        _JumpError(hr, error, "GetAclInformation");
    }

    pCAFound = (PBOOL)LocalAlloc(LMEM_FIXED, sizeof(BOOL)*CAAclInfo.AceCount);
    if(!pCAFound)
    {
        hr = E_OUTOFMEMORY;
        _JumpError(hr, error, "LocalAlloc");
    }
    ZeroMemory(pCAFound, sizeof(BOOL)*CAAclInfo.AceCount);

    pOfficerFound = (PBOOL)LocalAlloc(LMEM_FIXED, sizeof(BOOL)*OfficerAclInfo.AceCount);
    if(!pOfficerFound)
    {
        hr = E_OUTOFMEMORY;
        _JumpError(hr, error, "LocalAlloc");
    }
    ZeroMemory(pOfficerFound, sizeof(BOOL)*OfficerAclInfo.AceCount);

    hr = GetEveryoneSID(&pSidEveryone);
    _JumpIfError(hr, error, "GetEveryoneSID");

    dwSidEveryoneSize = GetLengthSid(pSidEveryone);

    // mark in the bool arrays each ace whose SID is found in both DACLs;
    // also mark CA ACEs we are not interested in (denied ACEs and 
    // non-officer ACEs)

    for(cCAAce=0; cCAAce<CAAclInfo.AceCount; cCAAce++)
    {
        if(!GetAce(pCAAcl, cCAAce, (PVOID*)&pCAAce))
        {
            hr = myHLastError();
            _JumpError(hr, error, "GetAce");
        }
        
        // process only officer allow aces
        if(0==(pCAAce->Mask & CA_ACCESS_OFFICER))
        {
            pCAFound[cCAAce] = TRUE;
            continue;
        }

        // compare SIDs in each officer ace with current CA ace and mark 
        // corresponding bool in arrays if equal
        for(cOfficerAce=0; cOfficerAce<OfficerAclInfo.AceCount; cOfficerAce++)
        {
            if(!GetAce(pOfficerAcl, cOfficerAce, (PVOID*)&pOfficerAce))
            {
                hr = myHLastError();
                _JumpError(hr, error, "GetAce");
            }
            if(EqualSid((PSID)&pOfficerAce->SidStart,
                (PSID)&pCAAce->SidStart))
            {
                pCAFound[cCAAce] = TRUE;
                pOfficerFound[cOfficerAce] = TRUE;
            }
        }
        
        // if the officer is found in the CA ACL but not in the officer ACL,
        // we will add a new ACE allowing him to manage certs for Everyone
        if(!pCAFound[cCAAce])
        {
            dwNewAclSize += sizeof(ACCESS_ALLOWED_CALLBACK_ACE)+
                GetLengthSid((PSID)&pCAAce->SidStart)+
                dwSidEveryoneSize;
        }
    }

    // Calculate the size of the new officer ACL; we already added in the header
    // size and the size of the new ACEs to be added. Now we add the ACEs to be
    // copied over from the old ACL
    for(cOfficerAce=0; cOfficerAce<OfficerAclInfo.AceCount; cOfficerAce++)
    {
        if(pOfficerFound[cOfficerAce])
        {
            if(!GetAce(pOfficerAcl, cOfficerAce, (PVOID*)&pOfficerAce))
            {
                hr = myHLastError();
                _JumpError(hr, error, "GetAce");
            }
            dwNewAclSize += pOfficerAce->Header.AceSize;
        }
    }

    // allocate a new DACL

    pNewOfficerAcl = (PACL)LocalAlloc(LMEM_FIXED, dwNewAclSize);
    if(!pNewOfficerAcl)
    {
        hr = E_OUTOFMEMORY;
        _JumpError(hr, error, "LocalAlloc");
    }

#ifdef _DEBUG
    ZeroMemory(pNewOfficerAcl, dwNewAclSize);
#endif 

    if(!InitializeAcl(pNewOfficerAcl, dwNewAclSize, ACL_REVISION))
    {
        hr = myHLastError();
        _JumpError(hr, error, "InitializeAcl");
    }

    // build the new DACL

    // traverse officer DACL and add only marked ACEs (whose SID was found
    // in the CA DACL, ie principal is an officer)
    for(cOfficerAce=0; cOfficerAce<OfficerAclInfo.AceCount; cOfficerAce++)
    {
        if(pOfficerFound[cOfficerAce])
        {
            if(!GetAce(pOfficerAcl, cOfficerAce, (PVOID*)&pOfficerAce))
            {
                hr = myHLastError();
                _JumpError(hr, error, "GetAce");
            }

            if(!AddAce(
                pNewOfficerAcl,
                ACL_REVISION,
                MAXDWORD,
                pOfficerAce,
                pOfficerAce->Header.AceSize))
            {
                hr = myHLastError();
                _JumpError(hr, error, "GetAce");
            }
        }
    }

    CSASSERT(IsValidAcl(pNewOfficerAcl));

    hr = GetBuiltinAdministratorsSID(&pSidBuiltinAdministrators);
    _JumpIfError(hr, error, "GetBuiltinAdministratorsSID");
    
    // traverse the CA DACL and add a new officer to list, allowed to manage
    // Everyone
    for(cCAAce=0; cCAAce<CAAclInfo.AceCount; cCAAce++)
    {
        if(pCAFound[cCAAce])
            continue;

        if(!GetAce(pCAAcl, cCAAce, (PVOID*)&pCAAce))
        {
            hr = myHLastError();
            _JumpError(hr, error, "GetAce");
        }

        // create a new ACE
        dwSidSize = GetLengthSid((PSID)&pCAAce->SidStart);

        dwAceSize = sizeof(ACCESS_ALLOWED_CALLBACK_ACE)+
            dwSidEveryoneSize+dwSidSize;

        pNewAce = (ACCESS_ALLOWED_CALLBACK_ACE*) LocalAlloc(LMEM_FIXED, dwAceSize);
        if(!pNewAce)
        {
            hr = E_OUTOFMEMORY;
            _JumpError(hr, error, "LocalAlloc");
        }
#ifdef _DEBUG
        ZeroMemory(pNewAce, dwAceSize);
#endif 

        pNewAce->Header.AceType = ACCESS_ALLOWED_CALLBACK_ACE_TYPE;
        pNewAce->Header.AceFlags= 0;
        pNewAce->Header.AceSize = (USHORT)dwAceSize;
        pNewAce->Mask = DELETE;
        CopySid(dwSidSize,
            (PSID)&pNewAce->SidStart,
            (PSID)&pCAAce->SidStart);
        pSidList = (PSID_LIST)(((BYTE*)&pNewAce->SidStart)+dwSidSize);
        pSidList->dwSidCount = 1;
        
        CopySid(dwSidEveryoneSize,
            (PSID)&pSidList->SidListStart,
            pSidEveryone);

        CSASSERT(IsValidSid((PSID)&pNewAce->SidStart));
        
        if(!AddAce(
            pNewOfficerAcl,
            ACL_REVISION,
            MAXDWORD,
            pNewAce,
            dwAceSize))
        {
            hr = myHLastError();
            _JumpError(hr, error, "AddAce");
        }

        LocalFree(pNewAce);
        pNewAce = NULL;
    }

    CSASSERT(IsValidAcl(pNewOfficerAcl));

    // setup the new security descriptor
    
    pNewOfficerSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LMEM_FIXED,
                                      SECURITY_DESCRIPTOR_MIN_LENGTH);
    if (pNewOfficerSD == NULL)
    {
        hr = E_OUTOFMEMORY;
        _JumpError(hr, error, "LocalAlloc");
    }
#ifdef _DEBUG
    ZeroMemory(pNewOfficerSD, SECURITY_DESCRIPTOR_MIN_LENGTH);
#endif 

    if (!InitializeSecurityDescriptor(pNewOfficerSD, SECURITY_DESCRIPTOR_REVISION))
    {
        hr = myHLastError();
        _JumpError(hr, error, "InitializeSecurityDescriptor");
    }

    if(!SetSecurityDescriptorOwner(
        pNewOfficerSD,
        pSidBuiltinAdministrators,
        FALSE))
    {
        hr = myHLastError();
        _JumpError(hr, error, "SetSecurityDescriptorControl");
    }

    if(!SetSecurityDescriptorDacl(
        pNewOfficerSD,
        TRUE, // DACL present
        pNewOfficerAcl,
        FALSE)) // DACL defaulted
    {
        hr = myHLastError();
        _JumpError(hr, error, "SetSecurityDescriptorDacl");
    }

    CSASSERT(IsValidSecurityDescriptor(pNewOfficerSD));

    hr = Set(pNewOfficerSD);
    _JumpIfError(hr, error, "CProtectedSecurityDescriptor::Set");

error:
    if(pNewAce)
    {
        LocalFree(pNewAce);
    }

    if(pSidEveryone)
    {
        FreeSid(pSidEveryone);
    }

    if(pSidBuiltinAdministrators)
    {
        FreeSid(pSidBuiltinAdministrators);
    }

    if(pNewOfficerAcl)
    {
        LocalFree(pNewOfficerAcl);
    }

    if(pNewOfficerSD)
    {
        LocalFree(pNewOfficerSD);
    }

    return hr;
}

HRESULT 
COfficerRightsSD::Adjust(PSECURITY_DESCRIPTOR pCASD)
{
    return Merge(Get(), pCASD);
}

HRESULT
COfficerRightsSD::InitializeEmpty()
{
    HRESULT hr = S_OK;
    ACL Acl;
    SECURITY_DESCRIPTOR SD;

    hr = Init(NULL);
    _JumpIfError(hr, error, "CProtectedSecurityDescriptor::Init");

    if(!InitializeAcl(&Acl, sizeof(ACL), ACL_REVISION))
    {
        hr = myHLastError();
        _JumpError(hr, error, "InitializeAcl");
    }
   
    if (!InitializeSecurityDescriptor(&SD, SECURITY_DESCRIPTOR_REVISION))
    {
        hr = myHLastError();
        _JumpError(hr, error, "InitializeSecurityDescriptor");
    }

    if(!SetSecurityDescriptorDacl(
        &SD,
        TRUE, // DACL present
        &Acl,
        FALSE)) // DACL defaulted
    {
        hr = myHLastError();
        _JumpError(hr, error, "SetSecurityDescriptorControl");
    }

    m_fInitialized = true;

    hr = Set(&SD);
    _JumpIfError(hr, error, "CProtectedSecurityDescriptor::Set");

error:
    return hr;
}

HRESULT COfficerRightsSD::Save()
{
    HRESULT hr = S_OK;

    if(IsEnabled())
    {
        hr = CProtectedSecurityDescriptor::Save();
        _JumpIfError(hr, error, "CProtectedSecurityDescriptor::Save");
    }
    else
    {
        hr = CProtectedSecurityDescriptor::Delete();
        _JumpIfError(hr, error, "CProtectedSecurityDescriptor::Delete");
    }

error:
    return hr;
}

HRESULT COfficerRightsSD::Load()
{
    HRESULT hr;
    
    hr = CProtectedSecurityDescriptor::Load();
    if(S_OK==hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)==hr)
    {
        SetEnable(S_OK==hr);
        hr = S_OK;
    }
    _JumpIfError(hr, error, "CProtectedSecurityDescriptor::Save");

error:
    return hr;
}

HRESULT COfficerRightsSD::Initialize(LPCWSTR pwszSanitizedName)
{
    HRESULT hr;
    
    hr = CProtectedSecurityDescriptor::Initialize(pwszSanitizedName);
    _JumpIfError(hr, error, "CProtectedSecurityDescriptor::Save");

    SetEnable(NULL!=m_pSD);

error:
    return hr;
}

HRESULT COfficerRightsSD::ConvertToString(
    IN PSECURITY_DESCRIPTOR pSD,
    OUT LPWSTR& rpwszSD)
{
    HRESULT hr = S_OK;
    LPCWSTR pcwszHeader = L"\n"; // start with a new line
    DWORD dwBufSize = sizeof(WCHAR)*(wcslen(pcwszHeader)+1);
    ACL_SIZE_INFORMATION AclInfo;
    DWORD dwIndex;
    PACCESS_ALLOWED_CALLBACK_ACE pAce; // no free
    PACL pDacl; // no free
    LPWSTR pwszAce; // no free

    CSASSERT(IsValidSecurityDescriptor(pSD));
    
    rpwszSD = NULL;

    // get acl
    hr = myGetSecurityDescriptorDacl(
             pSD,
             &pDacl);
    _JumpIfError(hr, error, "myGetDaclFromInfoSecurityDescriptor");

    if(!GetAclInformation(pDacl,
                          &AclInfo,
                          sizeof(ACL_SIZE_INFORMATION),
                          AclSizeInformation))   
    {
        hr = myHLastError();
        _JumpError(hr, error, "GetAclInformation");
    }
    
    
    // calculate text size

    for(dwIndex = 0;  dwIndex < AclInfo.AceCount; dwIndex++) 
    {
        DWORD dwAceSize;
        if(!GetAce(pDacl, dwIndex, (LPVOID*)&pAce))
        {
            hr = myHLastError();
            _JumpError(hr, error, "GetAce");
        }

        hr = ConvertAceToString(
            pAce,
            &dwAceSize,
            NULL);
        _JumpIfError(hr, error, "ConvertAceToString");

        dwBufSize += dwAceSize;
    }

    rpwszSD = (LPWSTR)LocalAlloc(LMEM_FIXED, dwBufSize);
    _JumpIfAllocFailed(rpwszSD, error);

    // build the output string
    wcscpy(rpwszSD, pcwszHeader);
    
    pwszAce = rpwszSD + wcslen(pcwszHeader);

    for(dwIndex = 0;  dwIndex < AclInfo.AceCount; dwIndex++) 
    {
        DWORD dwAceSize;
        if(!GetAce(pDacl, dwIndex, (LPVOID*)&pAce))
        {
            hr = myHLastError();
            _JumpError(hr, error, "GetAce");
        }

        hr = ConvertAceToString(
            pAce,
            &dwAceSize,
            pwszAce);
        _JumpIfError(hr, error, "ConvertAceToString");

        pwszAce += dwAceSize/sizeof(WCHAR);
    }

error:
    return hr;
}

// Returned string has the following format:
//
// [Allow|Deny]\t[OfficerName|SID]\n
// \t[Client1Name|SID]\n
// \t[Client2Name|SID]\n
// ...
//
// Example:
//
// Allow    OfficerGroup1
//      ClientGroup1
//      ClientGroup2
//
// If SID cannot be converted to friendly name it is displayed
// as a string SID
//
HRESULT COfficerRightsSD::ConvertAceToString(
    IN PACCESS_ALLOWED_CALLBACK_ACE pAce,
    OUT OPTIONAL PDWORD pdwSize,
    IN OUT OPTIONAL LPWSTR pwszSD)
{
    HRESULT hr = S_OK;
    DWORD dwSize = 1; // trailing '\0'
    CSid sid((PSID)(&pAce->SidStart));
    PSID_LIST pSidList = (PSID_LIST) (((BYTE*)&pAce->SidStart)+
        GetLengthSid(&pAce->SidStart));
    PSID pSid;
    DWORD cSids;
    
    LPCWSTR pcwszAllow      = m_pcwszResources[0];
    LPCWSTR pcwszDeny       = m_pcwszResources[1];

    LPCWSTR pcwszPermissionType = 
        (ACCESS_ALLOWED_CALLBACK_ACE_TYPE==pAce->Header.AceType)?
        pcwszAllow:pcwszDeny;
    LPCWSTR pcwszSid; // no free

    // asked for size and/or ace string
    CSASSERT(pdwSize || pwszSD);

    if(pAce->Header.AceType != ACCESS_ALLOWED_CALLBACK_ACE_TYPE &&
       pAce->Header.AceType != ACCESS_DENIED_CALLBACK_ACE_TYPE)
    {
        return E_INVALIDARG;
    }

    pcwszSid = sid.GetName();
    if(!pcwszSid)
    {
        return E_OUTOFMEMORY;
    }
    
    dwSize = wcslen(pcwszSid);

    dwSize += wcslen(pcwszPermissionType);
    
    dwSize += 2; // '\t' between sid an permission and a '\n' after

    if(pwszSD)
    {
        wcscat(pwszSD, pcwszPermissionType);
        wcscat(pwszSD, L"\t");
        wcscat(pwszSD, pcwszSid);
        wcscat(pwszSD, L"\n");
    }

    for(pSid=(PSID)&pSidList->SidListStart, cSids=0; cSids<pSidList->dwSidCount;
        cSids++, pSid = (PSID)(((BYTE*)pSid)+GetLengthSid(pSid)))
    {
        CSASSERT(IsValidSid(pSid));

        CSid sidClient(pSid);
        LPCWSTR pcwszSidClient;
        
        pcwszSidClient = sidClient.GetName();
        if(!pcwszSidClient)
        {
            return E_OUTOFMEMORY;
        }

        dwSize += wcslen(pcwszSidClient) + 2; // \tClientNameOrSid\n
        
        if(pwszSD)
        {
            wcscat(pwszSD, L"\t");
            wcscat(pwszSD, pcwszSidClient);
            wcscat(pwszSD, L"\n");
        }
    }

    dwSize *= sizeof(WCHAR);

    if(pdwSize)
    {
        *pdwSize = dwSize;
    }

    return hr;
}

HRESULT 
CertSrv::GetWellKnownSID(
    PSID *ppSid,
    SID_IDENTIFIER_AUTHORITY *pAuth,
    BYTE  SubauthorityCount,
    DWORD SubAuthority1,
    DWORD SubAuthority2,
    DWORD SubAuthority3,
    DWORD SubAuthority4,
    DWORD SubAuthority5,
    DWORD SubAuthority6,
    DWORD SubAuthority7,
    DWORD SubAuthority8)
{
    HRESULT hr = S_OK;

    // build Everyone SID
    if(!AllocateAndInitializeSid(
            pAuth,
            SubauthorityCount,
            SubAuthority1,
            SubAuthority2,
            SubAuthority3,
            SubAuthority4,
            SubAuthority5,
            SubAuthority6,
            SubAuthority7,
            SubAuthority8,
            ppSid))
    {
        hr = myHLastError();
        _JumpError(hr, error, "AllocateAndInitializeSid");
    }

error:
    return hr;
}

// caller is responsible for LocalFree'ing PSID
HRESULT CertSrv::GetEveryoneSID(PSID *ppSid)
{
    SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_WORLD_SID_AUTHORITY;
    return GetWellKnownSID(
        ppSid,
        &SIDAuth,
        1,
        SECURITY_WORLD_RID);
    return S_OK;
}
// caller is responsible for LocalFree'ing PSID
HRESULT CertSrv::GetLocalSystemSID(PSID *ppSid)
{
    SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
    return GetWellKnownSID(
        ppSid,
        &SIDAuth,
        1,
        SECURITY_LOCAL_SYSTEM_RID);
}

// caller is responsible for LocalFree'ing PSID
HRESULT CertSrv::GetBuiltinAdministratorsSID(PSID *ppSid)
{
    SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
    return GetWellKnownSID(
        ppSid,
        &SIDAuth,
        2,
        SECURITY_BUILTIN_DOMAIN_RID,
        DOMAIN_ALIAS_RID_ADMINS);
}