module System.Posix.ACL
( Permset(..)
, emptyPermset
, fullPermset
, unionPermsets
, intersectPermsets
, ACL(..)
, longTextForm
, shortTextFrom
, getACL
, getDefaultACL
, fdGetACL
, setACL
, setDefaultACL
, fdSetACL
, deleteDefaultACL
) where
import Control.Monad (foldM, replicateM_, when)
import Data.Bits (Bits, (.&.))
import Data.Map
import System.Posix.Types (UserID, GroupID, Fd)
import System.Posix.ACL.Acl_h (cAclRead, cAclWrite, cAclExecute)
import System.Posix.ACL.Internals hiding (ACL, Permset)
import qualified System.Posix.ACL.Internals as I
import Text.ParserCombinators.ReadP
import Text.Read hiding ((+++), (<++))
data Permset = Permset { hasRead :: Bool
, hasWrite :: Bool
, hasExecute :: Bool
} deriving Eq
toPermset :: (Bits a, Integral a) => a -> Permset
toPermset a =
Permset (hasCPerm cAclRead) (hasCPerm cAclWrite) (hasCPerm cAclExecute)
where hasCPerm x = if x .&. (fromIntegral a) == x then True else False
emptyPermset :: Permset
emptyPermset = Permset False False False
fullPermset :: Permset
fullPermset = Permset True True True
unionPermsets :: Permset -> Permset -> Permset
unionPermsets p q = Permset (if hasRead p || hasRead q then True else False)
(if hasWrite p || hasWrite q then True else False)
(if hasExecute p || hasExecute q then True else False)
intersectPermsets :: Permset -> Permset -> Permset
intersectPermsets p q = Permset (if hasRead p && hasRead q then True else False)
(if hasWrite p && hasWrite q then True else False)
(if hasExecute p && hasExecute q then True else False)
instance Show Permset where
showsPrec = showsPermset
showsPermset :: Int -> Permset -> ShowS
showsPermset _ (Permset r w x) = (if r then ('r':) else ('-':)) .
(if w then ('w':) else ('-':)) .
(if x then ('x':) else ('-':))
showsPermsetShort :: Int -> Permset -> ShowS
showsPermsetShort _ (Permset r w x) = (if r then ('r':) else id) .
(if w then ('w':) else id) .
(if x then ('x':) else id)
parseRead :: ReadP Permset
parseRead = do _ <- char 'r'
return (Permset True False False)
parseWrite :: ReadP Permset
parseWrite = do _ <- char 'w'
return (Permset False True False)
parseExecute :: ReadP Permset
parseExecute = do _ <- char 'x'
return (Permset False False True)
parseDash :: ReadP Permset
parseDash = do _ <- satisfy (== '-')
return emptyPermset
parseLongTextPermset :: ReadP Permset
parseLongTextPermset = do
skipSpaces
r <- parseRead +++ parseDash
w <- parseWrite +++ parseDash
x <- parseExecute +++ parseDash
return (unionPermsets r (unionPermsets w x))
parseShortTextPermset :: ReadP Permset
parseShortTextPermset = do
skipSpaces
r <- parseRead <++ return emptyPermset
w <- parseWrite <++ return emptyPermset
x <- parseExecute <++ return emptyPermset
return (unionPermsets r (unionPermsets w x))
parsePermset :: ReadP Permset
parsePermset = parseLongTextPermset +++ parseShortTextPermset
instance Read Permset where
readPrec = lift parsePermset
data ACL = MinimumACL { ownerPerms :: Permset
, owningGroupPerms :: Permset
, otherPerms :: Permset
}
| ExtendedACL { ownerPerms :: Permset
, usersPerms :: Map UserID Permset
, owningGroupPerms :: Permset
, groupsPerms :: Map GroupID Permset
, mask :: Permset
, otherPerms :: Permset
}
deriving Eq
instance Show ACL where
showsPrec = showsLongText
longTextForm :: ACL -> String
longTextForm acl = showsLongText 0 acl ""
showsLongText :: Int -> ACL -> ShowS
showsLongText n (MinimumACL ow og ot) = ("user::" ++) . showsPrec n ow .
("\ngroup::" ++) . showsPrec n og .
("\nother::" ++) . showsPrec n ot .
('\n' :)
showsLongText n (ExtendedACL ow us og gr m ot) =
("user::" ++) . showsPrec n ow .
foldlWithKey showsNamedUser id us .
("\ngroup::" ++) . showsPrec n og . showsEffective og .
foldlWithKey showsNamedGroup id gr .
("\nmask::" ++) . showsPrec n m .
("\nother::" ++) . showsPrec n ot .
('\n' :)
where showsNamed iD perm = showsPrec n iD . (':' :) . showsPrec n perm .
showsEffective perm
showsNamedUser sh uid perm = sh . ("\nuser:" ++) . showsNamed uid perm
showsNamedGroup sh gid perm = sh . ("\ngroup:" ++) .
showsNamed gid perm
showsEffective perm = let int = intersectPermsets m perm
in if int == perm
then id
else ("\t#effective:" ++) . showsPrec n int
shortTextFrom :: ACL -> String
shortTextFrom acl = showsShortText 0 acl ""
showsShortText :: Int -> ACL -> ShowS
showsShortText n (MinimumACL ow og ot) = ("u::" ++) . showsPermsetShort n ow .
(",g::" ++) . showsPermsetShort n og .
(",o::" ++) . showsPermsetShort n ot
showsShortText n (ExtendedACL ow us og gr m ot) =
("u::" ++) .
showsPermsetShort n ow .
foldlWithKey showsNamedUser id us .
(",g::" ++) . showsPermsetShort n og .
foldlWithKey showsNamedGroup id gr .
(",m::" ++) . showsPermsetShort n m .
(",o::" ++) . showsPermsetShort n ot
where showsNamed ident perm = showsPrec n ident . (':' :)
. showsPermsetShort n perm
showsNamedUser sh uid perm = sh . (",u:" ++) . showsNamed uid perm
showsNamedGroup sh gid perm = sh . (",g:" ++) . showsNamed gid perm
instance Read ACL where
readPrec = lift $ do skipSpaces
parseLongTextFrom +++ parseShortTextForm
parseLongTextFrom :: ReadP ACL
parseLongTextFrom = parseMinLongTextFrom +++ parseExtLongTextFrom
parseMinLongTextFrom :: ReadP ACL
parseMinLongTextFrom = do
_ <- string "user::"
ow <- parseLongTextPermset
_ <- string "\ngroup::"
og <- parseLongTextPermset
_ <- string "\nother::"
ot <- parseLongTextPermset
return $ MinimumACL ow og ot
parseExtLongTextFrom :: ReadP ACL
parseExtLongTextFrom = do
_ <- string "user::"
ow <- parseLongTextPermset
us <- many $ do _ <- string "\nuser:"
uid <- readPrec_to_P readPrec 0
_ <- char ':'
p1 <- parseLongTextPermset
_ <- option p1 $ do skipSpaces
_ <- string "#effective:"
parseLongTextPermset
return (uid,p1)
_ <- string "\ngroup::"
og <- parseLongTextPermset
_ <- option og $ do skipSpaces
_ <- string "#effective:"
parseLongTextPermset
gs <- many $ do _ <- string "\ngroup:"
gid <- readPrec_to_P readPrec 0
_ <- char ':'
p2 <- parseLongTextPermset
_ <- option p2 $ do skipSpaces
_ <- string "#effective:"
parseLongTextPermset
return (gid,p2)
_ <- string "\nmask::"
m <- parseLongTextPermset
_ <- string "\nother::"
ot <- parseLongTextPermset
return $ ExtendedACL ow (fromListWith unionPermsets us)
og (fromListWith unionPermsets gs) m ot
parseShortTextForm :: ReadP ACL
parseShortTextForm = parseMinShortTextForm +++ parseExtShortTextForm
parseMinShortTextForm :: ReadP ACL
parseMinShortTextForm = do
_ <- string "u::"
ow <- parseShortTextPermset
_ <- string ",g::"
og <- parseShortTextPermset
_ <- string ",o::"
ot <- parseShortTextPermset
return $ MinimumACL ow og ot
parseExtShortTextForm :: ReadP ACL
parseExtShortTextForm = do
_ <- string "u::"
ow <- parseShortTextPermset
us <- many $ do _ <- string ",u:"
uid <- readPrec_to_P readPrec 0
_ <- char ':'
p1 <- parseShortTextPermset
return (uid,p1)
_ <- string ",g::"
og <- parseShortTextPermset
gs <- many $ do _ <- string ",g:"
gid <- readPrec_to_P readPrec 0
_ <- char ':'
p2 <- parseShortTextPermset
return (gid,p2)
_ <- string ",m::"
m <- parseShortTextPermset
_ <- string ",o::"
ot <- parseShortTextPermset
return $ ExtendedACL ow (fromListWith unionPermsets us)
og (fromListWith unionPermsets gs) m ot
pokeCPermset :: I.Permset -> Permset -> IO ()
pokeCPermset cperms perms = do
when (hasRead perms) (addPerm cperms Read)
when (hasWrite perms) (addPerm cperms Write)
when (hasExecute perms) (addPerm cperms Execute)
toCACL :: ACL -> IO I.ACL
toCACL (MinimumACL ow og ot) = do cacl <- newACL 3
replicateM_ 3 (createEntry cacl)
ents <- getEntries cacl
setUserObjEnt ow (ents!!0)
setGroupObjEnt og (ents!!1)
setOtherEnt ot (ents!!2)
return cacl
toCACL (ExtendedACL ow us og gr m ot) = do
cacl <- newACL (4 + size us + size gr)
replicateM_ (4 + size us + size gr) (createEntry cacl)
ents <- getEntries cacl
setUserObjEnt ow (ents!!0)
mapM_ setUserEnt (zip (userSubStr ents) (toList us))
setGroupObjEnt og (groupElem ents)
mapM_ setGroupEnt (zip (groupSubStr ents) (toList gr))
setTagType (maskElem ents) Mask
m_p <- getPermset (maskElem ents)
pokeCPermset m_p m
setOtherEnt ot (otherElem ents)
return cacl
where userSubStr xs = take (size us) $ drop 1 xs
groupElem xs = xs!!(1 + size us)
groupSubStr xs = take (size gr) $ drop (2 + size us) xs
maskElem xs = xs!!(2 + size us + size gr)
otherElem xs = xs!!(3 + size us + size gr)
setUserEnt (e,(u,p)) = do setTagType e User
setQualifier e (UserID u)
s <- getPermset e
pokeCPermset s p
setGroupEnt (e,(g,p)) = do setTagType e Group
setQualifier e (GroupID g)
s <- getPermset e
pokeCPermset s p
setUserObjEnt :: Permset -> Entry -> IO ()
setUserObjEnt p e = do setTagType e UserObj
s <- getPermset e
pokeCPermset s p
setGroupObjEnt :: Permset -> Entry -> IO ()
setGroupObjEnt p e = do setTagType e GroupObj
s <- getPermset e
pokeCPermset s p
setOtherEnt :: Permset -> Entry -> IO ()
setOtherEnt p e = do setTagType e Other
s <- getPermset e
pokeCPermset s p
setACL :: FilePath -> ACL -> IO ()
setACL path acl = toCACL acl >>= setFileACL path Access
setDefaultACL :: FilePath -> ACL -> IO ()
setDefaultACL path acl = toCACL acl >>= setFileACL path Default
fdSetACL :: Fd -> ACL -> IO ()
fdSetACL fd acl = toCACL acl >>= setFdACL fd
getACL :: FilePath -> IO (ACL)
getACL path = getFileACL path Access >>= peekCACL
getDefaultACL :: FilePath -> IO (ACL)
getDefaultACL path = getFileACL path Default >>= peekCACL
fdGetACL :: Fd -> IO (ACL)
fdGetACL fd = getFdACL fd >>= peekCACL
peekCACL :: I.ACL -> IO ACL
peekCACL cacl = do
ents <- getEntries cacl
foldM addCEntry (MinimumACL emptyPermset emptyPermset emptyPermset) ents
addCEntry :: ACL -> I.Entry -> IO ACL
addCEntry acl ent = do
tag <- getTagType ent
perms <- getPermset ent
n <- permsetToIntegral perms
addPermsetWithTag tag ent acl (toPermset (n::Int))
where addPermsetWithTag t e a p =
case t of
User -> do Just (UserID uid) <- getQualifier e
return $ addUserPermset uid p a
Group -> do Just (GroupID gid) <- getQualifier e
return $ addGroupPermset gid p a
UserObj -> return $ addUserObjPermset p a
GroupObj -> return $ addGroupObjPermset p a
Other -> return $ addOtherPermset p a
Mask -> return $ setMaskPermset p a
Undefined -> return undefined
addUserPermset :: UserID -> Permset -> ACL -> ACL
addUserPermset uid p (MinimumACL ow og ot) =
ExtendedACL ow (singleton uid p) og empty emptyPermset ot
addUserPermset uid p acl =
acl { usersPerms = insertWith unionPermsets uid p (usersPerms acl) }
addGroupPermset :: GroupID -> Permset -> ACL -> ACL
addGroupPermset gid p (MinimumACL ow og ot) =
ExtendedACL ow empty og (singleton gid p) emptyPermset ot
addGroupPermset gid p acl =
acl { groupsPerms = insertWith unionPermsets gid p (groupsPerms acl) }
addUserObjPermset :: Permset -> ACL -> ACL
addUserObjPermset p acl = acl { ownerPerms = unionPermsets p (ownerPerms acl) }
addGroupObjPermset :: Permset -> ACL -> ACL
addGroupObjPermset p acl =
acl { owningGroupPerms = unionPermsets p (owningGroupPerms acl) }
setMaskPermset :: Permset -> ACL -> ACL
setMaskPermset p (MinimumACL ow og ot) = ExtendedACL ow empty og empty p ot
setMaskPermset p acl = acl { mask = unionPermsets p (mask acl) }
addOtherPermset :: Permset -> ACL -> ACL
addOtherPermset p acl = acl { otherPerms = unionPermsets p (otherPerms acl) }