diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,7 @@
+Copyright (c) 2015 Anton Dessiatov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Win32-security.cabal b/Win32-security.cabal
new file mode 100644
--- /dev/null
+++ b/Win32-security.cabal
@@ -0,0 +1,82 @@
+name:             Win32-security
+version:          0.1
+synopsis:         Haskell bindings to a security-related functions of the Windows API
+license:          MIT
+license-file:     LICENSE
+author:           Anton Dessiatov
+maintainer:       anton.dessiatov@gmail.com
+copyright:        Anton Dessiatov, 2015
+category:         System
+build-type:       Simple
+cabal-version:    >=1.16
+homepage:         https://github.com/anton-dessiatov/Win32-security
+bug-reports:      https://github.com/anton-dessiatov/Win32-security/issues
+description:
+    This package contains bindings for security-related functions of the Windows API.
+    Its main features are account name/SID lookup and editing securable objects access control lists.
+
+Flag build-tests
+    Description:
+        Build all the little test executables
+    Default:
+        False
+
+library
+  default-language: Haskell2010
+  build-depends:
+      base >= 4 && < 5
+    , text >= 1
+    , Win32 >= 2
+    , Win32-errors >= 0.2.1
+  exposed-modules:
+      System.Win32.Security.AccessControl
+      System.Win32.Security.Sid
+      System.Win32.Security.SecurityInfo
+  other-modules:
+      System.Win32.Security.MarshalText
+  hs-source-dirs: src
+  include-dirs: cbits
+  c-sources:
+    cbits/Win32Security.c
+  if os(windows) && arch(i386)
+    cpp-options: "-DWINDOWS_CCONV=stdcall"
+  else
+    cpp-options: "-DWINDOWS_CCONV=ccall"
+
+executable win32-security-sid-lookup
+  default-language: Haskell2010
+  hs-source-dirs: tests/sid-lookup
+  main-is: Main.hs
+  if flag (build-tests)
+    buildable: True
+    build-depends:
+        base
+      , text
+      , Win32-security
+  else
+    buildable: False
+
+executable win32-security-file-security
+  default-language: Haskell2010
+  hs-source-dirs: tests/file-security
+  main-is: Main.hs
+  if flag (build-tests)
+    buildable: True
+    build-depends:
+        base
+      , text
+      , Win32-security
+  else
+    buildable: False
+
+executable win32-security-get-process-sid
+  default-language: Haskell2010
+  hs-source-dirs: tests/get-process-sid
+  main-is: Main.hs
+  if flag (build-tests)
+    buildable: True
+    build-depends:
+        base
+      , Win32-security
+  else
+    buildable: False
diff --git a/cbits/Win32Security.c b/cbits/Win32Security.c
new file mode 100644
--- /dev/null
+++ b/cbits/Win32Security.c
@@ -0,0 +1,9 @@
+#include "Win32Security.h"
+
+void LocalFreeFinaliser(void* p) {
+  LocalFree(p);
+}
+
+void CloseHandleFinaliser(HANDLE h) {
+  CloseHandle(h);
+}
diff --git a/src/System/Win32/Security/AccessControl.hsc b/src/System/Win32/Security/AccessControl.hsc
new file mode 100644
--- /dev/null
+++ b/src/System/Win32/Security/AccessControl.hsc
@@ -0,0 +1,163 @@
+{-# 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}
diff --git a/src/System/Win32/Security/MarshalText.hs b/src/System/Win32/Security/MarshalText.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Win32/Security/MarshalText.hs
@@ -0,0 +1,29 @@
+-- | Helper functions to marshal the text back and forth to null-terminated CWchar.
+-- Borrowed from a Win32-junction-point package
+module System.Win32.Security.MarshalText
+  ( useAsPtr0
+  , fromPtr0
+  ) where
+
+import Data.Char (chr)
+import Foreign
+import Foreign.C
+import Foreign.Marshal.Array (lengthArray0)
+import qualified Data.Text as T
+import qualified Data.Text.Foreign as T
+
+-- | useAsPtr returns a length and byte buffer, but all the win32 functions
+-- rely on null termination.
+useAsPtr0 :: T.Text -> (Ptr CWchar -> IO a) -> IO a
+useAsPtr0 t f = T.useAsPtr (T.snoc t (chr 0x0)) $ \ str _ -> f  (castPtr str)
+
+-- This traverses the string twice. Is there a faster way?
+fromPtr0 :: Ptr CWchar -> IO T.Text
+fromPtr0 ptr = do
+    -- length in 16-bit words.
+    len <- lengthArray0 0x0000 ptr'
+    -- no loss of precision here. I16 is a newtype wrapper around Int.
+    T.fromPtr ptr' $ fromIntegral len
+  where
+    ptr' :: Ptr Word16
+    ptr' = castPtr ptr
diff --git a/src/System/Win32/Security/SecurityInfo.hsc b/src/System/Win32/Security/SecurityInfo.hsc
new file mode 100644
--- /dev/null
+++ b/src/System/Win32/Security/SecurityInfo.hsc
@@ -0,0 +1,194 @@
+{-# LANGUAGE CPP, ForeignFunctionInterface, GeneralizedNewtypeDeriving, OverloadedStrings, RankNTypes #-}
+module System.Win32.Security.SecurityInfo
+  ( SecurityDescriptor
+
+  , SecurityObjectType
+  , securityObjectUnknown
+  , securityObjectFile
+  , securityObjectService
+  , securityObjectPrinter
+  , securityObjectRegistryKey
+  , securityObjectLMShare
+  , securityObjectKernelObject
+  , securityObjectDSObject
+  , securityObjectDSObjectAll
+  , securityObjectProviderDefined
+  , securityObjectWMIGuid
+  , securityObjectRegistryWow6432Key
+
+  , SecurityInformation
+  , securityInformationOwner
+  , securityInformationGroup
+  , securityInformationDacl
+  , securityInformationSacl
+  , securityInformationAll
+
+  , GetSecurityInfoResult (..)
+  , getNamedSecurityInfo
+  , SetSecurityInfoAcl (..)
+  , setNamedSecurityInfo
+  ) where
+
+import Foreign
+import System.Win32.Types
+import System.Win32.Security
+import System.Win32.Security.Sid
+import System.Win32.Security.AccessControl
+import qualified Data.Text as T
+import qualified System.Win32.Error.Foreign as E
+import qualified System.Win32.Security.MarshalText as T
+
+#include <windows.h>
+#include <AccCtrl.h>
+
+newtype SecurityDescriptor = SecurityDescriptor (ForeignPtr SECURITY_DESCRIPTOR)
+
+-- | newtype wrapper around Windows SDK SE_OBJECT_TYPE enumeration
+newtype SecurityObjectType = SecurityObjectType BYTE
+#{enum SecurityObjectType, SecurityObjectType
+ , securityObjectUnknown            = SE_UNKNOWN_OBJECT_TYPE
+ , securityObjectFile               = SE_FILE_OBJECT
+ , securityObjectService            = SE_SERVICE
+ , securityObjectPrinter            = SE_PRINTER
+ , securityObjectRegistryKey        = SE_REGISTRY_KEY
+ , securityObjectLMShare            = SE_LMSHARE
+ , securityObjectKernelObject       = SE_KERNEL_OBJECT
+ , securityObjectDSObject           = SE_DS_OBJECT
+ , securityObjectDSObjectAll        = SE_DS_OBJECT_ALL
+ , securityObjectProviderDefined    = SE_PROVIDER_DEFINED_OBJECT
+ , securityObjectWMIGuid            = SE_WMIGUID_OBJECT
+ , securityObjectRegistryWow6432Key = SE_REGISTRY_WOW64_32KEY
+ }
+
+newtype SecurityInformation = SecurityInformation DWORD
+  deriving (Bits, Eq)
+
+securityInformationOwner :: SecurityInformation
+securityInformationOwner = SecurityInformation oWNER_SECURITY_INFORMATION
+
+securityInformationGroup :: SecurityInformation
+securityInformationGroup = SecurityInformation gROUP_SECURITY_INFORMATION
+
+securityInformationDacl :: SecurityInformation
+securityInformationDacl = SecurityInformation dACL_SECURITY_INFORMATION
+
+securityInformationSacl :: SecurityInformation
+securityInformationSacl = SecurityInformation sACL_SECURITY_INFORMATION
+
+securityInformationAll :: SecurityInformation
+securityInformationAll = SecurityInformation $
+  oWNER_SECURITY_INFORMATION .|. gROUP_SECURITY_INFORMATION .|.
+  dACL_SECURITY_INFORMATION .|. sACL_SECURITY_INFORMATION
+
+foreign import WINDOWS_CCONV unsafe "windows.h GetNamedSecurityInfoW"
+  c_GetNamedSecurityInfoW
+    :: LPWSTR -- pObjectName
+    -> BYTE -- ObjectType
+    -> DWORD -- SecurityInformation
+    -> Ptr PSID -- ppsidOwner
+    -> Ptr PSID -- ppsidGroup
+    -> Ptr PACL -- ppDacl
+    -> Ptr PACL -- ppSacl
+    -> Ptr (Ptr SECURITY_DESCRIPTOR) -- ppSecurityDescriptor
+    -> IO DWORD
+
+data GetSecurityInfoResult = GetSecurityInfoResult
+  { securityInfoOwner      :: Maybe Sid
+  , securityInfoGroup      :: Maybe Sid
+  , securityInfoDacl       :: Maybe Acl
+  , securityInfoSacl       :: Maybe Acl
+  , securityInfoDescriptor :: SecurityDescriptor
+  }
+
+getNamedSecurityInfo :: T.Text -> SecurityObjectType -> SecurityInformation -> IO GetSecurityInfoResult
+getNamedSecurityInfo objectName (SecurityObjectType objectType) (SecurityInformation securityInfo) =
+  T.useAsPtr0 objectName $ \pObjectName ->
+  alloca $ \ppSidOwner ->
+  alloca $ \ppSidGroup ->
+  alloca $ \ppDacl ->
+  alloca $ \ppSacl ->
+  alloca $ \ppSecurityDescriptor -> do
+    E.failUnlessSuccess "GetNamedSecurityInfoW" $
+      c_GetNamedSecurityInfoW pObjectName objectType securityInfo ppSidOwner ppSidGroup ppDacl ppSacl ppSecurityDescriptor
+    sdPtr <- peek ppSecurityDescriptor
+    sd <- newForeignPtr localFreeFinaliser sdPtr
+    ownerSid <- if securityInfo .&. oWNER_SECURITY_INFORMATION /= 0
+      then do
+        pSidOwner <- peek ppSidOwner
+        return . Just $ Sid $ \act -> withForeignPtr sd $ \_ -> act pSidOwner
+      else
+        return Nothing
+    groupSid <- if securityInfo .&. gROUP_SECURITY_INFORMATION /= 0
+      then do
+        pSidGroup <- peek ppSidGroup
+        return . Just $ Sid $ \act -> withForeignPtr sd $ \_ -> act pSidGroup
+      else
+        return Nothing
+    dacl <- if securityInfo .&. dACL_SECURITY_INFORMATION /= 0
+      then do
+        pDacl <- peek ppDacl
+        return . Just $ Acl $ \act -> withForeignPtr sd $ \_ -> act pDacl
+      else
+        return Nothing
+    sacl <- if securityInfo .&. sACL_SECURITY_INFORMATION /= 0
+      then do
+        pSacl <- peek ppSacl
+        return . Just $ Acl $ \act -> withForeignPtr sd $ \_ -> act pSacl
+      else
+        return Nothing
+    return GetSecurityInfoResult
+      { securityInfoOwner = ownerSid
+      , securityInfoGroup = groupSid
+      , securityInfoDacl = dacl
+      , securityInfoSacl = sacl
+      , securityInfoDescriptor = SecurityDescriptor sd
+      }
+
+{-# CFILES cbits/Win32Security.c #-}
+foreign import ccall "Win32Security.h &LocalFreeFinaliser"
+  localFreeFinaliser :: FunPtr (Ptr a -> IO ())
+
+data SetSecurityInfoAcl
+  = DontSetAcl
+  -- | Set ACL and prevent inheritable ACEs from propagating
+  | ProtectedAcl Acl
+  -- | Set ACL and allow inheritable ACEs to propagate
+  | UnprotectedAcl Acl
+
+setNamedSecurityInfo :: T.Text -> SecurityObjectType -> Maybe Sid -> Maybe Sid -> SetSecurityInfoAcl -> SetSecurityInfoAcl -> IO ()
+setNamedSecurityInfo objectName (SecurityObjectType objectType) maybeOwner maybeGroup ssiDacl ssiSacl =
+    T.useAsPtr0 objectName $ \pObjectName ->
+    maybe ($ nullPtr) withSidPtr maybeOwner $ \psidOwner ->
+    maybe ($ nullPtr) withSidPtr maybeGroup $ \psidGroup ->
+    withSecurityInfoAcl ssiDacl $ \pDacl ->
+    withSecurityInfoAcl ssiSacl $ \pSacl ->
+      let securityInfo = 0
+            .|. if psidOwner /= nullPtr then oWNER_SECURITY_INFORMATION else 0
+            .|. if psidGroup /= nullPtr then gROUP_SECURITY_INFORMATION else  0
+            .|. case ssiDacl of
+                  DontSetAcl -> 0
+                  ProtectedAcl _ -> dACL_SECURITY_INFORMATION .|. #{const PROTECTED_DACL_SECURITY_INFORMATION}
+                  UnprotectedAcl _ -> dACL_SECURITY_INFORMATION
+            .|. case ssiSacl of
+                  DontSetAcl -> 0
+                  ProtectedAcl _ -> sACL_SECURITY_INFORMATION .|. #{const PROTECTED_SACL_SECURITY_INFORMATION}
+                  UnprotectedAcl _ -> sACL_SECURITY_INFORMATION
+      in E.failUnlessSuccess "SetNamedSecurityInfoW" $
+           c_SetNamedSecurityInfoW pObjectName objectType securityInfo psidOwner psidGroup pDacl pSacl
+  where
+    withSecurityInfoAcl :: SetSecurityInfoAcl -> (Ptr ACL -> IO a) -> IO a
+    withSecurityInfoAcl ssia act = case ssia of
+      DontSetAcl -> act $ nullPtr
+      ProtectedAcl x -> withAclPtr x act
+      UnprotectedAcl x -> withAclPtr x act
+
+foreign import WINDOWS_CCONV "windows.h SetNamedSecurityInfoW"
+  c_SetNamedSecurityInfoW
+    :: LPWSTR -- pObjectName
+    -> BYTE -- ObjectType
+    -> DWORD -- SecurityInfo
+    -> PSID -- psidOwner
+    -> PSID -- psidGroup
+    -> PACL -- pDacl
+    -> PACL -- pSacl
+    -> IO DWORD
diff --git a/src/System/Win32/Security/Sid.hsc b/src/System/Win32/Security/Sid.hsc
new file mode 100644
--- /dev/null
+++ b/src/System/Win32/Security/Sid.hsc
@@ -0,0 +1,281 @@
+{-# 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
diff --git a/tests/file-security/Main.hs b/tests/file-security/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/file-security/Main.hs
@@ -0,0 +1,81 @@
+module Main (main) where
+
+import Control.Monad (join)
+import Data.Bits
+import Data.Maybe
+import System.Environment
+import System.Win32.Security.AccessControl
+import System.Win32.Security.SecurityInfo
+import System.Win32.Security.Sid
+import qualified Data.Text as T
+import qualified Data.Traversable as T
+
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    act:fileName:[] -> performAction act fileName
+    _ -> putStrLn "2 arguments are expected: action (either \"read\" or \"modify\") and a file name"
+
+performAction :: String -> FilePath -> IO ()
+performAction "read" fileName = do
+  sinfo <- getNamedSecurityInfo (T.pack fileName) securityObjectFile
+    (securityInformationOwner .|. securityInformationGroup .|. securityInformationDacl)
+  printSecurityInfo sinfo
+performAction "modify" fileName = do
+  let textFileName = T.pack fileName
+  sinfo <- getNamedSecurityInfo textFileName securityObjectFile securityInformationDacl
+  let oldDacl = fromJust $ securityInfoDacl sinfo
+      newDacl = aclFromList . tail $ aclToList oldDacl
+  putStrLn "Old DACL was:"
+  printAcl oldDacl
+  putStrLn "New DACL will be:"
+  printAcl newDacl
+  setNamedSecurityInfo textFileName securityObjectFile Nothing Nothing (UnprotectedAcl newDacl) DontSetAcl
+
+printSecurityInfo :: GetSecurityInfoResult -> IO ()
+printSecurityInfo gsir = do
+  putStrLn "Owner:"
+  maybeOwnerAcct <- T.forM (securityInfoOwner gsir) $ lookupAccountSid Nothing
+  printMaybeAcct $ join maybeOwnerAcct
+  putStrLn "Group:"
+  maybeGroupAcct <- T.forM (securityInfoGroup gsir) $ lookupAccountSid Nothing
+  printMaybeAcct $ join maybeGroupAcct
+  putStrLn "DACL:"
+  let dacl = securityInfoDacl gsir
+  case dacl of
+    Just x  -> printAcl x
+    Nothing -> putStrLn "Missing"
+
+printMaybeAcct :: Maybe LookedUpAccount -> IO ()
+printMaybeAcct maybel = case maybel of
+  Just l -> do
+    print (lookedUpAccountName l)
+    print (lookedUpReferencedDomainName l)
+    print (lookedUpUse l)
+    s <- convertSidToStringSid (lookedUpSid l)
+    print s
+  Nothing ->
+    putStrLn "Unknown"
+
+printAcl :: Acl -> IO ()
+printAcl acl = do
+    putStrLn $ concat [ "ACL Entries count: ", show $ aclEntriesCount acl ]
+    mapM_ printAce $ aclToList acl
+  where
+    printAce ace = case ace of
+      AceAccessAllowed ga -> do
+        putStrLn "ACCESS_ALLOWED_ACE"
+        printGenericAce ga
+      AceAccessDenied ga -> do
+        putStrLn "ACCESS_DENIED_ACE"
+        printGenericAce ga
+    printGenericAce ga = do
+      putStrLn $ concat [ "ACE Flags: ", show $ genericAceFlags ga ]
+      putStrLn $ concat [ "ACE AccessMask: ", show $ genericAceAccessMask ga ]
+      sidString <- convertSidToStringSid $ genericAceSid ga
+      putStrLn $ concat [ "ACE Sid: ", show sidString ]
+      putStrLn "Sid lookup: "
+      sidLookup <- lookupAccountSid Nothing $ genericAceSid ga
+      printMaybeAcct sidLookup
+
diff --git a/tests/get-process-sid/Main.hs b/tests/get-process-sid/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/get-process-sid/Main.hs
@@ -0,0 +1,18 @@
+module Main (main) where
+
+import System.Win32.Security.Sid
+
+main :: IO ()
+main =
+  getCurrentProcess >>= getProcessUserSid >>= lookupAccountSid Nothing >>= printMaybeAcct
+
+printMaybeAcct :: Maybe LookedUpAccount -> IO ()
+printMaybeAcct maybel = case maybel of
+  Just l -> do
+    print (lookedUpAccountName l)
+    print (lookedUpReferencedDomainName l)
+    print (lookedUpUse l)
+    s <- convertSidToStringSid (lookedUpSid l)
+    print s
+  Nothing ->
+    putStrLn "Nothing found"
diff --git a/tests/sid-lookup/Main.hs b/tests/sid-lookup/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/sid-lookup/Main.hs
@@ -0,0 +1,27 @@
+module Main (main) where
+
+import System.Environment
+import System.Win32.Security.Sid
+import qualified Data.Text as T
+
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    domainName:accountName:[] ->
+      lookupAccountName (Just $ T.pack domainName) (T.pack accountName) >>= printMaybeAcct
+    accountName:[] ->
+      lookupAccountName Nothing (T.pack accountName) >>= printMaybeAcct
+    _ ->
+      putStrLn "Either 2 args (domain name and account name) or 1 (account name) are expected"
+
+printMaybeAcct :: Maybe LookedUpAccount -> IO ()
+printMaybeAcct maybel = case maybel of
+  Just l -> do
+    print (lookedUpAccountName l)
+    print (lookedUpReferencedDomainName l)
+    print (lookedUpUse l)
+    s <- convertSidToStringSid (lookedUpSid l)
+    print s
+  Nothing ->
+    putStrLn "Nothing found"
