//+-------------------------------------------------------------------
//
// Copyright (C) 1995, Microsoft Corporation.
//
//  File:        daclwrap.cxx
//
//  Contents:    class encapsulating file security.
//
//  Classes:     CDaclWrap
//
//  History:     Nov-93        Created         DaveMont
//
//--------------------------------------------------------------------
#include <t2.hxx>
#include <daclwrap.hxx>

#if DBG
extern ULONG Debug;
#endif
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap::CDaclWrap, public
//
//  Synopsis:   initialize data members, constructor will not throw
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
CDaclWrap::CDaclWrap()
      : _ccaa(0)
{
}
//+---------------------------------------------------------------------------
//
//  Member:     Dtor, public
//
//  Synopsis:   cleanup allocated data
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
CDaclWrap::~CDaclWrap()
{
    for (ULONG j = 0; j < _ccaa; j++)
        delete _aaa[j].pcaa;
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap::SetAccess, public
//
//  Synopsis:   caches data for a new ACE
//
//  Arguments:  IN [option] - rePlace, Revoke, Grant, Deny
//              IN [Name] - principal (username)
//              IN [System] - server/machine where Name is defined
//              IN [access] - access mode (Read Change None All)
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::SetAccess(ULONG option, WCHAR *Name, WCHAR *System, ULONG access)
{
    ULONG ret;

    // sorry, static number of ACCESSes can be set at one time

    if (_ccaa >= CMAXACES)
        return(ERROR_BUFFER_OVERFLOW);

    // allocate a new account access class

    if (NULL == (_aaa[_ccaa].pcaa = new CAccountAccess(Name, System)))
    {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    // to fix the bug where someone asks to both grant and deny under
    // the /p option (the deny is thru access = N)

    if ((GENERIC_NONE == access) && (OPTION_REPLACE == option))
    {
	_aaa[_ccaa].option = OPTION_DENY;
    } else
    {
	_aaa[_ccaa].option = option;
    }

    SID *psid;

    if (ERROR_SUCCESS == ( ret = _aaa[_ccaa].pcaa->Init(access)))
    {
        // get the sid to make sure the username is valid

        if (ERROR_SUCCESS == ( ret =_aaa[_ccaa].pcaa->Sid(&psid)))
        {
            // loop thru the existing sids, making sure the new one is not a duplicate

            SID *poldsid;
            for (ULONG check = 0;check < _ccaa ; check++)
            {
                if (ERROR_SUCCESS == ( ret =_aaa[check].pcaa->Sid(&poldsid)))
                {
                    if (EqualSid(psid,poldsid))
                    {
                        VERBOSE((stderr, "SetAccess found matching new sids\n"))
                        return(ERROR_BAD_ARGUMENTS);
                    }
                }
            }
            _ccaa++;
        }
    }
    return(ret);
}

//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:BuildAcl, public
//
//  Synopsis:   merges cached new aces with the input ACL
//
//  Arguments:  OUT [pnewdacl] - Address of new ACL to build
//              IN  [poldacl]  - (OPTIONAL) old ACL that is to be merged
//              IN  [revision] - ACL revision
//              IN  [fdir]     - True = directory
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::BuildAcl(ACL **pnewdacl, ACL *poldacl, UCHAR revision, BOOL fdir)
{
    ULONG ret, caclsize;

    // get the size of the new ACL we are going to create

    if (ERROR_SUCCESS == (ret =  _GetNewAclSize(&caclsize, poldacl, fdir)))
    {
        // allocate the new ACL

        if (ERROR_SUCCESS == (ret =  _AllocateNewAcl(pnewdacl, caclsize, revision)))
        {
            // and fill it up

            if (ERROR_SUCCESS != (ret =  _FillNewAcl(*pnewdacl, poldacl, fdir)))
            {
                // free the buffer if we failed

                LocalFree(*pnewdacl);
            }

        }
    }
    return(ret);
}

//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_GetNewAclSize, private
//
//  Synopsis:   returns the size need to merge the new ACEs with the old ACL,
//              this is an ugly algorithm:
//
//if (old aces exist)
//   for (new aces)
//      if (new ace option == GRANT)
//         for (old aces)
//            if (new ace SID == old ace SID)
//               do inheritance check
//               found = true
//               if (old ace type == ALLOWED)
//                  old ace mask |= new ace mask
//               else
//                  old ace mask &= ~new ace mask
//         if (!found)
//            add size of new ace
//         else
//            new ace mask = 0
//      else
//         add size of new ace
//
//   for (old aces)
//      for (new aces)
//         if (new ace option == DENY, REPLACE, REVOKE)
//            if (new ace SID == old ace SID)
//               found = true
//               break
//      if (!found)
//         add size of old ace
//      else
//         old ace mask = 0
//else
//   for (new aces)
//      add size of new ace
//
//
//  Arguments:  OUT [caclsize] - returns size
//              IN  [poldacl]  - (OPTIONAL) old ACL that is to be merged
//              IN  [fdir]     - True = directory
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_GetNewAclSize(ULONG *caclsize, ACL *poldacl, BOOL fdir)
{
    ULONG ret;

    // the size for the ACL header

    *caclsize = sizeof(ACL);

    // initialize the access requests
    for (ULONG j = 0; j < _ccaa; j++)
       _aaa[j].pcaa->ReInit();

    // if we are merging, calculate the merge size

    if (poldacl)
    {
        // first the grant options

        for (j = 0; j < _ccaa; j++)
        {
            SID *psid;
            if (OPTION_GRANT == _aaa[j].option)
            {
                BOOL ffound = FALSE;
                ACE_HEADER *pah = (ACE_HEADER *)Add2Ptr(poldacl, sizeof(ACL));

                for (ULONG cace = 0; cace < poldacl->AceCount;
                     cace++, pah = (ACE_HEADER *)Add2Ptr(pah, pah->AceSize))
                {
                    if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                    {
                        if (EqualSid(psid,
                                     (SID *)&((ACCESS_ALLOWED_ACE *)
                                     pah)->SidStart) )
                        {
                            // if old and new types are the same, just and with the old

                            if (fdir && (pah->AceType == _aaa[j].pcaa->AceType()))
                            {
                                // make sure that we can handle the inheritance
                                _aaa[j].pcaa->AddInheritance(pah->AceFlags);

                                ffound = TRUE;
                            } else if (pah->AceType == _aaa[j].pcaa->AceType())
                            {
                                ffound = TRUE;
                            }
                            
                            if (ACCESS_ALLOWED_ACE_TYPE == pah->AceType)
                            {
                                (ACCESS_MASK) ((ACCESS_ALLOWED_ACE *)
                                pah)->Mask |= _aaa[j].pcaa->AccessMask();
                            } else if (ACCESS_DENIED_ACE_TYPE == pah->AceType)
                            {
                                (ACCESS_MASK) ((ACCESS_ALLOWED_ACE *)
                                pah)->Mask &= ~_aaa[j].pcaa->AccessMask();
                            } else
                            {
                                VERBOSE((stderr, "_GetNewAclSize found an ace that was not allowed or denied\n"))
                                return(ERROR_INVALID_DATA);
                            }
                        }
                    } else
                    {
                        return(ret);
                    }
                }
                if (!ffound)
                {
                    // bugbug allowed/denied sizes currently the same
                    
                    *caclsize += sizeof(ACCESS_ALLOWED_ACE) -
                                 sizeof(DWORD) +
                                 GetLengthSid(psid);
                    
                    SIZE((stderr, "adding on size of an new ACE (to the new ACL) = %d\n",*caclsize))
                } else
                {
                    if (fdir && (ERROR_SUCCESS != (ret = _aaa[j].pcaa->TestInheritance())))
                        return(ret);
                    _aaa[j].pcaa->ClearAccessMask();
                }
            } else if ( (OPTION_REPLACE == _aaa[j].option) ||
                        (OPTION_DENY == _aaa[j].option) )
            {
                if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                {
                    // bugbug allowed/denied sizes currently the same
                
                    *caclsize += sizeof(ACCESS_ALLOWED_ACE) -
                                 sizeof(DWORD) +
                                 GetLengthSid(psid);
                
                    SIZE((stderr, "adding on size of an new ACE (to the new ACL) = %d\n",*caclsize))
                } else
                    return(ret);
            }
        }
        // now for the deny, replace & revoke options

        ACE_HEADER *pah = (ACE_HEADER *)Add2Ptr(poldacl, sizeof(ACL));
        SID *psid;

        // loop thru the old ACL

        for (ULONG cace = 0; cace < poldacl->AceCount;
            cace++, pah = (ACE_HEADER *)Add2Ptr(pah, pah->AceSize))
        {
            BOOL ffound = FALSE;

            // and thru the new ACEs looking for matching SIDs

            for (ULONG j = 0; j < _ccaa; j++)
            {
                if ( (_aaa[j].option & OPTION_DENY ) ||
                     (_aaa[j].option & OPTION_REPLACE ) ||
                     (_aaa[j].option & OPTION_REVOKE ) )
                {
                    if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                    {
                        if (EqualSid(psid,
                                     (SID *)&((ACCESS_ALLOWED_ACE *)
                                     pah)->SidStart) )
                        {
                            ffound = TRUE;
                        }
                    } else
                        return(ret);
                }
            }
            if (!ffound)
            {
                // if we did not find a match, add the size of the old ACE

                *caclsize += ((ACE_HEADER *)pah)->AceSize;

                SIZE((stderr, "adding on size of an old ACE (to the new ACL) = %d\n",*caclsize))
            } else
            {
                (ACCESS_MASK) ((ACCESS_ALLOWED_ACE *)pah)->Mask = 0;
            }
        }
        SIZE((stderr, "final size for new ACL = %d\n",*caclsize))
    } else
    {
        // no old ACL, just add up the sizes of the new aces

        for (j = 0; j < _ccaa; j++)
        {
            // need to know the size of the sid
    
            SID *psid;
            if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
            {
                // bugbug allowed/denied sizes currently the same
    
                *caclsize += sizeof(ACCESS_ALLOWED_ACE) -
                             sizeof(DWORD) +
                             GetLengthSid(psid);
    
                SIZE((stderr, "adding on size of an new ACE (to the new ACL) = %d\n",*caclsize))
            } else
            {
                return(ret);
            }
        }
        SIZE((stderr, "final size for new ACL = %d\n",*caclsize))
    }
    return(ERROR_SUCCESS);
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_AllocateNewAcl, private
//
//  Synopsis:   allocates and initializes the new ACL
//
//  Arguments:  OUT [pnewdacl] - address of new ACL to allocate
//              IN  [caclsize] - size to allocate for the new ACL
//              IN  [revision] - revision of the new ACL
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_AllocateNewAcl(ACL **pnewdacl, ULONG caclsize, ULONG revision)
{
    if (NULL == (*pnewdacl = (ACL *) LocalAlloc(LMEM_FIXED, caclsize)))
    {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    if (!InitializeAcl(*pnewdacl,caclsize, revision))
    {
        ULONG ret = GetLastError();
        LocalFree(*pnewdacl);
        return(ret);

    }

    return(ERROR_SUCCESS);
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_SetAllowedAce, private
//
//  Synopsis:   appends an allowed ACE to the input ACL
//
//  Arguments:  IN [dacl] - ACL to add the ACE to
//              IN [mask] - access mask to add
//              IN [psid] - SID to add
//              IN [fdir] - if a Dir add inherit ACE as well
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_SetAllowedAce(ACL *dacl, ACCESS_MASK mask, SID *psid, BOOL fdir)
{
    ULONG ret = ERROR_SUCCESS;

    // compute the size of the ACE we are making

    USHORT acesize = sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(psid);

    SIZE((stderr, "adding allowed ace, size = %d\n",fdir ? acesize*2 : acesize))

    // static buffer in the hopes we won't have to allocate memory

    BYTE buf[1024];

    // allocator either uses buf or allocates a new buffer if size is not enough

    FastAllocator fa(buf, 1024);

    // get the buffer for the ACE

    ACCESS_ALLOWED_ACE *paaa = (ACCESS_ALLOWED_ACE *)fa.GetBuf(acesize);

    // fill in the ACE

    memcpy(&paaa->SidStart,psid,GetLengthSid(psid));
    paaa->Mask = mask;

    paaa->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;
    paaa->Header.AceFlags = fdir ? CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE : 0;
    paaa->Header.AceSize = acesize;

    // put the ACE into the ACL

    if (!AddAce(dacl,
                dacl->AclRevision,
                0xffffffff,
                paaa,
                paaa->Header.AceSize))
        ret = GetLastError();
    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_SetDeniedAce, private
//
//  Synopsis:   appends a denied ACE to the input ACL
//
//  Arguments:  IN [dacl] - ACL to add the ACE to
//              IN [mask] - access mask to add
//              IN [psid] - SID to add
//              IN [fdir] - if a Dir add inherit ACE as well
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_SetDeniedAce(ACL *dacl, ACCESS_MASK mask, SID *psid, BOOL fdir)
{
    ULONG ret = ERROR_SUCCESS;

    // compute the size of the ACE we are making

    USHORT acesize = sizeof(ACCESS_DENIED_ACE) -
                   sizeof(DWORD) +
                   GetLengthSid(psid);

    SIZE((stderr, "adding denied ace, size = %d\n",acesize))

    // static buffer in the hopes we won't have to allocate memory

    BYTE buf[1024];

    // allocator either uses buf or allocates a new buffer if size is not enough

    FastAllocator fa(buf, 1024);

    // get the buffer for the ACE

    ACCESS_DENIED_ACE *paaa = (ACCESS_DENIED_ACE *)fa.GetBuf(acesize);

    // fill in the ACE

    memcpy(&paaa->SidStart,psid,GetLengthSid(psid));
    paaa->Mask = mask;

    paaa->Header.AceType = ACCESS_DENIED_ACE_TYPE;
    paaa->Header.AceFlags = fdir ? CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE : 0;
    paaa->Header.AceSize = acesize;

    // put the ACE into the ACL

    if (!AddAce(dacl,
                dacl->AclRevision,
                0xffffffff,
                paaa,
                paaa->Header.AceSize))
        ret = GetLastError();
    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_FillNewAcl, private
//
//  Synopsis:   The worker routine that actually fills the ACL, it adds the
//              new denied ACEs, then if the new ACEs are being merged with
//              an existing ACL, the existing ACL's ACE's (that don't
//              conflict) are added, finally the new allowed ACEs are added.
//              another ugly algorithm:
//
//for (new aces)
//   if (new ace option == DENY)
//      add new ace
//
//if (old aces)
//   for (old aces)
//      if (old ace mask != 0)
//         add old ace
//
//   for (new aces)
//      if (new ace option != DENY)
//         if ( new ace option != REVOKE)
//            if (new ace mask != 0
//                add new ace
//
//else
//   for (new aces)
//      if (new ace option != DENY)
//         add new ace
//
//  Arguments:  IN [pnewdacl] - the new ACL to be filled
//              IN [poldacl]  - (OPTIONAL) old ACL that is to be merged
//              IN [fdir]     - TRUE = directory
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_FillNewAcl(ACL *pnewdacl, ACL *poldacl, BOOL fdir)
{
    SID *psid;
    ULONG ret;

    // set new denied aces

    VERBOSE((stderr, "start addr of new ACL %0lx\n",pnewdacl))

    for (ULONG j = 0; j < _ccaa; j++)
    {
        if (_aaa[j].option & OPTION_DENY)
        {
            if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
            {
                if (ERROR_SUCCESS != (ret = _SetDeniedAce(pnewdacl,
                                                           _aaa[j].pcaa->AccessMask(),
                                                           psid,
                                                           fdir )))
                    return(ret);
            } else
                return(ret);
        }
    }

    // check and see if the ACL from from the file is in correct format

    if (poldacl)
    {
        SIZE((stderr, "old ACL size = %d, acecount = %d\n",poldacl->AclSize,
              poldacl->AceCount))

        ACE_HEADER *pah = (ACE_HEADER *)Add2Ptr(poldacl, sizeof(ACL));

        // loop thru the old ACL, looking for matches with the new ACEs

        BOOL fallowedacefound = FALSE;
        for (ULONG cace = 0; cace < poldacl->AceCount;
            cace++, pah = (ACE_HEADER *)Add2Ptr(pah, pah->AceSize))
        {
            // error exit if the old ACL is incorrectly formated

            if (pah->AceType == ACCESS_DENIED_ACE_TYPE && fallowedacefound)
            {
                VERBOSE((stderr, "_FillNewAcl found an denied ACE after an allowed ACE\n"))
                return(ERROR_INVALID_DATA);
            }
            else if (pah->AceType == ACCESS_ALLOWED_ACE_TYPE)
                fallowedacefound = TRUE;

            // add the old ace to the new ACL if the old ace's mask is not zero

            if ( 0 != (ACCESS_MASK)((ACCESS_ALLOWED_ACE *)pah)->Mask)
            {
                // add the old ace
                if (!AddAce(pnewdacl,
                            pnewdacl->AclRevision,
                            0xffffffff,
                            pah,
                            pah->AceSize))
                    return(GetLastError());
            }
        }
        // now for the new aces

        for (ULONG j = 0; j < _ccaa; j++)
        {
            if ( (_aaa[j].option != OPTION_DENY) && 
                 (_aaa[j].option != OPTION_REVOKE) &&
                 (_aaa[j].pcaa->AccessMask() != 0) )
            {
                if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                {
                    if (ERROR_SUCCESS != (ret = _SetAllowedAce(pnewdacl,
                                                               _aaa[j].pcaa->AccessMask(),
                                                               psid,
                                                               fdir )))
                        return(ret);
                } else
                    return(ret);
            }
            
        }
    } else
    {
        // no old acl, just add the (rest) of the new aces
        for (ULONG j = 0; j < _ccaa; j++)
        {
            if (_aaa[j].option != OPTION_DENY)
            {
                if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                {
                    if (ERROR_SUCCESS != (ret = _SetAllowedAce(pnewdacl,
                                                               _aaa[j].pcaa->AccessMask(),
                                                               psid,
                                                               fdir )))
                        return(ret);
                } else
                    return(ret);
            }
        }
    }

    return(ERROR_SUCCESS);
}
