doc-review-0.7: src/Privilege.hs
module Privilege
( tryDropPrivilege
)
where
import Control.Applicative ( (<$>) )
import Data.IORef ( newIORef, readIORef , writeIORef )
import System.Posix.User ( getUserEntryForName, setUserID
, userID, getRealUserID )
import Control.Monad ( join, unless )
-- Obtain an action that will attempt to drop privileges if the
-- environment and configuration allow it. Ideally, we would be able
-- to drop privileges after binding to the port and before doing
-- anything else. This is a compromise that provides a modicum of
-- safety if the request can co-opt the server, but no protection from
-- any local attacks. In particular, all of the server start-up stuff
-- happened before this, including creating the database. This could
-- potentially cause permission errors.
tryDropPrivilege :: String -> IO (IO ())
tryDropPrivilege username = do
-- Figure out what user id we need to switch to (if any)
uid <- getRealUserID
targetUID <-
do t <- userID <$> getUserEntryForName username
case uid of
-- Root:
0 ->
return $ Just t
-- We already are the target user:
_ | t == uid ->
return Nothing
-- Anyone else:
_ ->
error $ "Only root can change users \
\(trying to run as " ++ username ++ ")"
case targetUID of
Nothing ->
-- If we have no target UID, then just return a no-op
return $ return ()
Just target ->
-- Build and return an action that will ensure that we are
-- running as the target user before proceeding
do
-- The variable that holds the action that ensures that we
-- have dropped privileges
startRef <- newIORef $ return ()
-- Put the privilege dropping action in
writeIORef startRef $
do putStrLn $
"Dropping privileges: switching to user " ++ show target
setUserID target `catch` \e ->
-- Check that we didn't lose a race
-- trying to drop privileges. If we lost,
-- then everything's OK because the
-- privileges are already dropped.
do newUID <- getRealUserID
-- If it wasn't losing the race,
-- raise it again here
unless (newUID == target) $ ioError e
finalUID <- getRealUserID
if finalUID == target
-- replace the action with a no-op
then writeIORef startRef $ return ()
else error "Failed to drop privileges"
return $ join $ readIORef startRef