module System.Unix.Chroot
( fchroot
, useEnv
) where
import Control.Exception (evaluate)
import Control.Monad.Catch (MonadMask, finally)
import Control.Monad.Trans (MonadIO, liftIO)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as L
import Foreign.C.Error
import Foreign.C.String
import System.Directory (createDirectoryIfMissing)
import System.Exit (ExitCode(ExitSuccess))
import System.FilePath (dropTrailingPathSeparator, dropFileName)
import System.IO (hPutStr, stderr)
import System.Posix.Env (getEnv)
import System.Posix.IO
import System.Posix.Directory
import System.Process (readProcessWithExitCode, showCommandForUser)
foreign import ccall unsafe "chroot" c_chroot :: CString -> IO Int
forceList = undefined
forceList' = undefined
chroot :: FilePath -> IO ()
chroot fp = withCString fp $ \cfp -> throwErrnoIfMinus1_ "chroot" (c_chroot cfp)
fchroot :: (MonadIO m, MonadMask m) => FilePath -> m a -> m a
fchroot path action =
do origWd <- liftIO $ getWorkingDirectory
rootFd <- liftIO $ openFd "/" ReadOnly Nothing defaultFileFlags
liftIO $ chroot path
liftIO $ changeWorkingDirectory "/"
action `finally` (liftIO $ breakFree origWd rootFd)
where
breakFree origWd rootFd =
do changeWorkingDirectoryFd rootFd
closeFd rootFd
chroot "."
changeWorkingDirectory origWd
useEnv :: (MonadIO m, MonadMask m) => FilePath -> (a -> m a) -> m a -> m a
useEnv rootPath force action =
do
sockPath <- liftIO $ getEnv "SSH_AUTH_SOCK"
home <- liftIO $ getEnv "HOME"
liftIO $ copySSH home
withSock sockPath . fchroot rootPath $ (action >>= force)
where
copySSH Nothing = return ()
copySSH (Just home) =
createDirectoryIfMissing True (rootPath ++ "/root") >>
run "/usr/bin/rsync" ["-rlptgDHxS", "--delete", home ++ "/.ssh/", rootPath ++ "/root/.ssh"]
withSock :: (MonadIO m, MonadMask m) => Maybe FilePath -> m a -> m a
withSock Nothing action = action
withSock (Just sockPath) action =
withMountBind dir (rootPath ++ dir) action
where dir = dropTrailingPathSeparator (dropFileName sockPath)
withMountBind :: (MonadIO m, MonadMask m) => FilePath -> FilePath -> m a -> m a
withMountBind toMount mountPoint action =
(do liftIO $ createDirectoryIfMissing True mountPoint
liftIO $ run "/bin/mount" ["--bind", escapePathForMount toMount, escapePathForMount mountPoint]
action) `finally` (liftIO $ run "/bin/umount" [escapePathForMount mountPoint])
escapePathForMount = id
run cmd args =
do (code, out, err) <- readProcessWithExitCode cmd args ""
case code of
ExitSuccess -> return ()
_ -> error ("Exception in System.Unix.Chroot.useEnv: " ++ showCommandForUser cmd args ++ " -> " ++ show code ++
"\n\nstdout:\n " ++ prefix "> " out ++ "\n\nstderr:\n" ++ prefix "> " err)
prefix pre s = unlines (map (pre ++) (lines s))