packages feed

Win32-security-0.1: src/System/Win32/Security/AccessControl.hsc

{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, OverloadedStrings, RankNTypes, ScopedTypeVariables #-}
module System.Win32.Security.AccessControl
  ( Acl (..)
  , aclEntriesCount
  , AceFlags (..)
  , aceFlagContainerInherit
  , aceFlagFailedAccess
  , aceFlagInheritOnly
  , aceFlagInherited
  , aceFlagNoPropagateInherit
  , aceFlagObjectInherit
  , aceFlagSuccessfulAccess
  , Ace (..)
  , GenericAce (..)
  , aclToList
  , aclFromList
  ) where

import Control.Applicative ((<$>))
import Control.Monad (foldM_)
import Data.Bits
import Foreign
-- I have to use GHC internal ForeignPtr module because that one exports mallocForeignPtrAlignedBytes
-- function, and I have to make ACL buffers DWORD-aligned.
import GHC.ForeignPtr
import System.Win32.File
import System.Win32.Security
import System.Win32.Security.Sid
import System.Win32.Types
import System.IO.Unsafe
import qualified System.Win32.Error.Foreign as E

#include <windows.h>

-- | Access control list.
newtype Acl = Acl { withAclPtr :: forall a. (PACL -> IO a) -> IO a }

aclEntriesCount :: Acl -> Int
aclEntriesCount acl = fromIntegral . unsafePerformIO $ withAclPtr acl peekAceCount
  where peekAceCount :: PACL -> IO WORD
        peekAceCount = #{peek ACL, AceCount}

newtype AceFlags = AceFlags { aceFlagsGetValue :: BYTE }
  deriving (Eq, Bits, Show)

#{enum AceFlags, AceFlags
 , aceFlagContainerInherit   = CONTAINER_INHERIT_ACE
 , aceFlagFailedAccess       = FAILED_ACCESS_ACE_FLAG
 , aceFlagInheritOnly        = INHERIT_ONLY_ACE
 , aceFlagInherited          = INHERITED_ACE
 , aceFlagNoPropagateInherit = NO_PROPAGATE_INHERIT_ACE
 , aceFlagObjectInherit      = OBJECT_INHERIT_ACE
 , aceFlagSuccessfulAccess   = SUCCESSFUL_ACCESS_ACE_FLAG
 }

-- | Not all ACE types are currently supported. Exotic ones like ACCESS_ALLOWED_CALLBACK_OBJECT_ACE are
-- not implemented. Feel free to contact me if you REALLY need it.
data Ace
  = AceAccessAllowed GenericAce
  | AceAccessDenied GenericAce
  | AceUnknown

data GenericAce = GenericAce
  { genericAceFlags      :: AceFlags
  , genericAceAccessMask :: AccessMode
  , genericAceSid        :: Sid
  }

aclToList :: Acl -> [Ace]
aclToList acl = reverse . unsafePerformIO $ go [] (aclEntriesCount acl) #{size ACL}
  where
    -- This one accumulates Ace entries in reverse order (reverse is to avoid unnecessary list
    -- traversals with (++))
    go :: [Ace] -> Int -> Int -> IO [Ace]
    go result 0 _ = return result
    go currentList remainingAces currentOffset = withAclPtr acl $ \pAcl -> do
      let currentPtr = pAcl `plusPtr` currentOffset
      (headerType :: BYTE) <- #{peek ACE_HEADER, AceType} currentPtr
      (headerFlags :: BYTE) <- #{peek ACE_HEADER, AceFlags} currentPtr
      (headerSize :: WORD) <- #{peek ACE_HEADER, AceSize} currentPtr
      newAce <- case headerType of
        #{const ACCESS_ALLOWED_ACE_TYPE} -> AceAccessAllowed <$>
          parseGenericAce (AceFlags headerFlags) (fromIntegral headerSize) currentOffset
        #{const ACCESS_DENIED_ACE_TYPE} -> AceAccessDenied <$>
          parseGenericAce (AceFlags headerFlags) (fromIntegral headerSize) currentOffset
        _ -> return AceUnknown
      go (newAce:currentList) (remainingAces - 1) (currentOffset + fromIntegral headerSize)

    parseGenericAce :: AceFlags -> Int -> Int -> IO GenericAce
    parseGenericAce flags size currentOffset = withAclPtr acl $ \pAcl -> do
      let currentPtr = pAcl `plusPtr` currentOffset
      (mask :: DWORD) <- #{peek ACCESS_ALLOWED_ACE, Mask} currentPtr
      -- All this black magic is to avoid copying the SID and instead refer to it using a
      -- withAclPtr function (to prevent it from being consumed by GC)
      let sid = Sid $ \act -> withAclPtr acl $ \pAcl -> act (pAcl `plusPtr` currentOffset `plusPtr` #{offset ACCESS_ALLOWED_ACE, SidStart})
      return $ GenericAce flags mask sid

-- | Calculates amount of memory required by a given ACE
aceSize :: Ace -> Int
aceSize ace = case ace of
  AceAccessAllowed ga -> #{size ACCESS_ALLOWED_ACE} + getLengthSid (genericAceSid ga) - #{size DWORD}
  AceAccessDenied ga  -> #{size ACCESS_DENIED_ACE} + getLengthSid (genericAceSid ga) - #{size DWORD}

-- | Serializes given ACE to a given buffer. Buffer should have at least 'aceSize' bytes.
serializeAce :: Ace -> Ptr () -> IO ()
serializeAce ace dest = do
    #{poke ACE_HEADER, AceSize} dest (fromIntegral $ aceSize ace :: WORD)
    case ace of
      AceAccessAllowed ga -> do
        #{poke ACE_HEADER, AceType} dest (#{const ACCESS_ALLOWED_ACE_TYPE} :: BYTE)
        serializeGenericAce ga
      AceAccessDenied ga -> do
        #{poke ACE_HEADER, AceType} dest (#{const ACCESS_DENIED_ACE_TYPE} :: BYTE)
        serializeGenericAce ga
      AceUnknown -> error "Adding AceUnknown to ACL is not supported"
  where
    serializeGenericAce :: GenericAce -> IO ()
    serializeGenericAce ga = do
      #{poke ACE_HEADER, AceFlags} dest (aceFlagsGetValue $ genericAceFlags ga)
      #{poke ACCESS_ALLOWED_ACE, Mask} dest $ genericAceAccessMask ga
      let sid = genericAceSid ga
          sidLength = getLengthSid sid
          aceSidPtr = dest `plusPtr` #{offset ACCESS_ALLOWED_ACE, SidStart}
      withSidPtr sid $ \pSid ->
        copyBytes aceSidPtr pSid sidLength

-- | Creates an Acl from a list of access control entries. ACL revision is assumed to be ACL_REVISION because
-- ACL_REVISION_DS is not supported yet.
aclFromList :: [Ace] -> Acl
aclFromList aces =
  let acesAndSizes = map (\ace -> (ace, aceSize ace)) aces
      aclSize = #{size ACL} + (sum $ map snd acesAndSizes)
  in unsafePerformIO $ do
    aclData <- mallocForeignPtrAlignedBytes aclSize #{size DWORD}
    withForeignPtr aclData $ \pAcl -> do
      E.failIfFalse_ "InitializeAcl" $
        c_InitializeAcl pAcl (fromIntegral aclSize) #{const ACL_REVISION}
      #{poke ACL, AceCount} pAcl (fromIntegral $ length aces :: WORD)
      foldM_
        (\ptr (ace, size) -> serializeAce ace ptr >> return (ptr `plusPtr` size))
        (pAcl `plusPtr` #{size ACL})
        acesAndSizes
    return $ Acl $ withForeignPtr aclData

foreign import WINDOWS_CCONV unsafe "windows.h InitializeAcl"
  c_InitializeAcl
    :: PACL -- pAcl
    -> DWORD -- nAclLength
    -> DWORD -- dwAclRevision
    -> IO BOOL

-- | Creates a copy of a given Acl structure. This is mostly used internally to establish an immutable data
-- interface.
aclCopy :: Acl -> IO Acl
aclCopy acl = withAclPtr acl $ \pAcl -> do
    size <- fromIntegral <$> peekAceSize pAcl
    newAcl <- mallocForeignPtrBytes size
    withForeignPtr newAcl $ \pNewAcl ->
      copyBytes pNewAcl pAcl size
    return $ Acl $ withForeignPtr newAcl
  where
    peekAceSize :: PACL -> IO WORD
    peekAceSize = #{peek ACL, AclSize}