Win32-security-0.1: src/System/Win32/Security/Sid.hsc
{-# LANGUAGE CPP, ForeignFunctionInterface, OverloadedStrings, RankNTypes #-}
-- | This module is a place for functions listed in MSDN "Authorization functions" section that work with security
-- identifiers.
--
-- Partially borrowed from Win32-extras package
module System.Win32.Security.Sid
( Sid (..)
, SID_NAME_USE (..)
, sidTypeUser
, sidTypeGroup
, sidTypeDomain
, sidTypeAlias
, sidTypeWellKnownGroup
, sidTypeDeletedAccount
, sidTypeInvalid
, sidTypeUnknown
, sidTypeComputer
, LookedUpAccount (..)
-- * Functions
, isValidSid
, getLengthSid
, lookupAccountName
, lookupAccountSid
, convertSidToStringSid
, getProcessUserSid
, getCurrentProcess
) where
import Data.Maybe (fromJust)
import Foreign
import Foreign.C
import System.IO.Unsafe
import System.Win32.Process
import System.Win32.Security
import System.Win32.Types
import qualified Data.Text as T
import qualified Data.Text.Foreign as T
import qualified System.Win32.Error as E
import qualified System.Win32.Error.Foreign as E
import qualified System.Win32.Security.MarshalText as T
#include <windows.h>
-- The data type encapsulates a function that allows anyone to perform an operation with the
-- SID pointer. This is done to fit both explicitly allocated SIDs (via closure's reference to
-- foreign ptr) and ones that are parts of larger structures, such as security descriptors.
newtype Sid = Sid { withSidPtr :: forall a. (PSID -> IO a) -> IO a }
-- | Converts SID from binary to a textual representation.
convertSidToStringSid :: Sid -> IO T.Text
convertSidToStringSid sid =
withSidPtr sid $ \pSid ->
with nullPtr $ \ pStringSid -> do
E.failIfFalse_ "ConvertSidToStringSid" $
c_ConvertSidToStringSid pSid pStringSid
str <- peek pStringSid
result <- T.fromPtr0 str
_ <- localFree str
return result
foreign import WINDOWS_CCONV unsafe "windows.h ConvertSidToStringSidW"
c_ConvertSidToStringSid
:: PSID
-> Ptr LPWSTR
-> IO BOOL
-- | Checks the given SID for a validity. Return False if revision number is outside a known range
-- or if the number of subauthorities is more that maximum.
isValidSid :: Sid -> Bool
isValidSid sid = unsafePerformIO $ withSidPtr sid c_IsValidSid
foreign import WINDOWS_CCONV unsafe "windows.h IsValidSid"
c_IsValidSid
:: PSID
-> IO BOOL
-- | Gets the length, in bytes, of given valid Sid. If Sid is not valid, the return value is undefined.
getLengthSid :: Sid -> Int
getLengthSid sid = fromIntegral . unsafePerformIO $ withSidPtr sid c_GetLengthSid
foreign import WINDOWS_CCONV unsafe "windows.h GetLengthSid"
c_GetLengthSid
:: PSID
-> IO DWORD
newtype SID_NAME_USE = SID_NAME_USE { sidNameUseValue :: #{type SID_NAME_USE} }
deriving (Eq)
instance Storable SID_NAME_USE where
sizeOf _ = #{size SID_NAME_USE}
alignment _ = 4
peek p = fmap SID_NAME_USE (peek $ castPtr p)
poke p (SID_NAME_USE x) = poke (castPtr p) x
type PSID_NAME_USE = Ptr SID_NAME_USE
#{enum SID_NAME_USE, SID_NAME_USE
, sidTypeUser = SidTypeUser
, sidTypeGroup = SidTypeGroup
, sidTypeDomain = SidTypeDomain
, sidTypeAlias = SidTypeAlias
, sidTypeWellKnownGroup = SidTypeWellKnownGroup
, sidTypeDeletedAccount = SidTypeDeletedAccount
, sidTypeInvalid = SidTypeInvalid
, sidTypeUnknown = SidTypeUnknown
, sidTypeComputer = SidTypeComputer
}
-- | A table used to lookup SID constant names for the Show instance.
sidNameUseTable :: [(SID_NAME_USE, String)]
sidNameUseTable =
[ (sidTypeUser, "sidTypeUser")
, (sidTypeGroup, "sidTypeGroup")
, (sidTypeDomain, "sidTypeDomain")
, (sidTypeAlias, "sidTypeAlias")
, (sidTypeWellKnownGroup, "sidTypeWellKnownGroup")
, (sidTypeDeletedAccount, "sidTypeDeletedAccount")
, (sidTypeInvalid, "sidTypeInvalid")
, (sidTypeUnknown, "sidTypeUnknown")
, (sidTypeComputer, "sidTypeComputer")
]
instance Show SID_NAME_USE where
show x@(SID_NAME_USE v) = case lookup x sidNameUseTable of
Just name -> name
Nothing -> "SID_NAME_USE " ++ show v
data LookedUpAccount = LookedUpAccount
{ lookedUpSid :: Sid
, lookedUpAccountName :: T.Text
, lookedUpReferencedDomainName :: T.Text
, lookedUpUse :: SID_NAME_USE
}
lookupAccountName :: Maybe T.Text -> T.Text -> IO (Maybe LookedUpAccount)
lookupAccountName systemName accountName =
maybe ($ nullPtr) T.useAsPtr0 systemName $ \lpSystemName ->
T.useAsPtr0 accountName $ \lpAccountName ->
with 0 $ \lpcbSid ->
with 0 $ \lpcchReferencedDomainName ->
with (SID_NAME_USE 0) $ \lppeUse -> do
nullPSid <- newForeignPtr_ nullPtr
go lpSystemName lpAccountName nullPSid lpcbSid nullPtr lpcchReferencedDomainName lppeUse
where
go lpSystemName lpAccountName sid lpcbSid rdnBuffer lpcchReferencedDomainName lppeUse = do
cbSid <- peek lpcbSid
cchReferencedDomainName <- peek lpcchReferencedDomainName
withForeignPtr sid $ \pSid -> do
r <- c_LookupAccountNameW lpSystemName lpAccountName pSid lpcbSid rdnBuffer lpcchReferencedDomainName lppeUse
if r
then do
rdn <- T.fromPtr (castPtr rdnBuffer) (fromIntegral cchReferencedDomainName - 1) -- -1 is for terminating null
use <- peek lppeUse
return . Just $ LookedUpAccount (Sid $ withForeignPtr sid) accountName rdn use
else do
err_code <- getLastError
case err_code of
#{const ERROR_NONE_MAPPED} -> return Nothing
#{const ERROR_INSUFFICIENT_BUFFER} -> do
newCbSid <- peek lpcbSid
newCchReferencedDomainName <- peek lpcchReferencedDomainName
newSid <- mallocForeignPtrBytes (fromIntegral newCbSid)
allocaBytes ((fromIntegral newCchReferencedDomainName) * sizeOf (undefined :: CWchar)) $ \newRdnBuffer ->
go lpSystemName lpAccountName newSid lpcbSid newRdnBuffer lpcchReferencedDomainName lppeUse
_ -> E.failWith "LookupAccountNameW" $ E.fromDWORD err_code
foreign import WINDOWS_CCONV unsafe "windows.h LookupAccountNameW"
c_LookupAccountNameW
:: LPCWSTR -- lpSystemName
-> LPCWSTR -- lpAccountName
-> PSID -- Sid
-> LPDWORD -- cbSid
-> LPWSTR -- ReferencedDomainName
-> LPDWORD -- cchReferencedDomainName
-> PSID_NAME_USE -- peUse
-> IO BOOL
lookupAccountSid :: Maybe T.Text -> Sid -> IO (Maybe LookedUpAccount)
lookupAccountSid systemName sid =
maybe ($ nullPtr) T.useAsPtr0 systemName $ \lpSystemName ->
withSidPtr sid $ \pSid ->
with 0 $ \lpcchName ->
with 0 $ \lpcchReferencedDomainName ->
with (SID_NAME_USE 0) $ \lppeUse ->
go lpSystemName pSid nullPtr lpcchName nullPtr lpcchReferencedDomainName lppeUse
where
go lpSystemName pSid nameBuffer lpcchName rdnBuffer lpcchRdn lppeUse = do
cchName <- peek lpcchName
cchRdn <- peek lpcchRdn
r <- c_LookupAccountSidW lpSystemName pSid nameBuffer lpcchName rdnBuffer lpcchRdn lppeUse
if r
then do
-- -1s are for terminating nulls
name <- T.fromPtr (castPtr nameBuffer) (fromIntegral cchName - 1)
rdn <- T.fromPtr (castPtr rdnBuffer) (fromIntegral cchRdn - 1)
use <- peek lppeUse
return . Just $ LookedUpAccount sid name rdn use
else do
err_code <- getLastError
case err_code of
#{const ERROR_NONE_MAPPED} -> return Nothing
#{const ERROR_INSUFFICIENT_BUFFER} -> do
newCchName <- peek lpcchName
newCchRdn <- peek lpcchRdn
allocaBytes ((fromIntegral newCchName) * sizeOf (undefined :: CWchar)) $ \newNameBuffer ->
allocaBytes ((fromIntegral newCchRdn) * sizeOf (undefined :: CWchar)) $ \newRdnBuffer ->
go lpSystemName pSid newNameBuffer lpcchName newRdnBuffer lpcchRdn lppeUse
_ -> E.failWith "LookupAccountSidW" $ E.fromDWORD err_code
foreign import WINDOWS_CCONV unsafe "windows.h LookupAccountSidW"
c_LookupAccountSidW
:: LPCWSTR -- lpSystemName
-> PSID -- lpSid
-> LPWSTR -- lpName
-> LPDWORD -- cchName
-> LPWSTR -- lpReferencedDomainName
-> LPDWORD -- cchReferencedDomainName
-> PSID_NAME_USE -- peUse
-> IO BOOL
openProcessToken :: ProcessHandle -> IO HANDLE
openProcessToken handle = alloca $ \pToken -> do
E.failIfFalse_ "OpenProcessToken" $ c_OpenProcessToken handle #{const TOKEN_QUERY} pToken
peek pToken
foreign import WINDOWS_CCONV unsafe "windows.h OpenProcessToken"
c_OpenProcessToken
:: HANDLE -- ProcessHandle
-> DWORD -- DesiredAccess
-> Ptr HANDLE -- TokenHandle
-> IO BOOL
getProcessUserSid :: ProcessHandle -> IO Sid
getProcessUserSid handle = do
token <- openProcessToken handle
go token Nothing 0
where
go handle maybeBuf bufSize =
with 0 $ \pReturnLength ->
maybe ($ nullPtr) withForeignPtr maybeBuf $ \pBuf -> do
ret <- c_GetTokenInformation handle #{const TokenUser} pBuf bufSize pReturnLength
if ret
then unmarshalAndReturn (fromJust maybeBuf)
else do
errValue <- getLastError
case errValue of
#{const ERROR_INSUFFICIENT_BUFFER} -> do
properBufferSize <- peek pReturnLength
newBuffer <- mallocForeignPtrBytes $ fromIntegral properBufferSize
go handle (Just newBuffer) properBufferSize
x ->
E.failWith "GetTokenInformation" $ E.fromDWORD x
unmarshalAndReturn buf = return $ Sid $ \act -> withForeignPtr buf $ \pBuf -> do
pSid <- peek $ pBuf `plusPtr` #{offset TOKEN_USER, User} `plusPtr` #{offset SID_AND_ATTRIBUTES, Sid}
act pSid
{-# CFILES cbits/HsWin32.c #-}
foreign import ccall "HsWin32.h &CloseHandleFinaliser"
c_CloseHandleFinaliser :: FunPtr (Ptr a -> IO ())
foreign import WINDOWS_CCONV unsafe "windows.h GetTokenInformation"
c_GetTokenInformation
:: HANDLE -- TokenHandle
-> BYTE -- TokenInformationClass
-> Ptr () -- TokenInformation
-> DWORD -- TokenInformationLength
-> LPDWORD -- ReturnLength
-> IO BOOL
-- | Wrapper for API GetCurrentProcess() function. This would look better in a
-- System.Win32.Process module, but that one is located in win32 package, which
-- is rather hard to change.
getCurrentProcess :: IO ProcessHandle
getCurrentProcess = c_GetCurrentProcess
foreign import WINDOWS_CCONV unsafe "window.h GetCurrentProcess"
c_GetCurrentProcess
:: IO HANDLE