module Propellor.Ssh where import Propellor.Base import Utility.UserInfo import Utility.FileSystemEncoding import System.PosixCompat import Data.Time.Clock.POSIX import Data.Hashable -- Parameters can be passed to both ssh and scp, to enable a ssh connection -- caching socket. -- -- If the socket already exists, check if its mtime is older than 10 -- minutes, and if so stop that ssh process, in order to not try to -- use an old stale connection. (atime would be nicer, but there's -- a good chance a laptop uses noatime) sshCachingParams :: HostName -> IO [CommandParam] sshCachingParams hn = do home <- myHomeDir let socketfile = socketFile home hn createDirectoryIfMissing False (takeDirectory socketfile) let ps = [ Param "-o" , Param ("ControlPath=" ++ socketfile) , Param "-o", Param "ControlMaster=auto" , Param "-o", Param "ControlPersist=yes" ] maybe noop (expireold ps socketfile) =<< catchMaybeIO (getFileStatus socketfile) return ps where expireold ps f s = do now <- truncate <$> getPOSIXTime :: IO Integer if modificationTime s > fromIntegral now - tenminutes then touchFile f else do void $ boolSystem "ssh" $ [ Param "-O", Param "stop" ] ++ ps ++ [ Param "localhost" ] nukeFile f tenminutes = 600 -- Generate a socket filename inside the home directory. -- -- There's a limit in the size of unix domain sockets, of approximately -- 100 bytes. Try to never construct a filename longer than that. -- -- When space allows, include the full hostname in the socket filename. -- Otherwise, a checksum of the hostname is included in the name, to -- avoid using the same socket file for multiple hosts. socketFile :: FilePath -> HostName -> FilePath socketFile home hn = selectSocketFile [ sshdir hn ++ ".sock" , sshdir hn , sshdir take 10 hn ++ "-" ++ checksum , sshdir checksum ] (home ".propellor-" ++ checksum) where sshdir = home ".ssh" "propellor" checksum = take 9 $ show $ abs $ hash hn selectSocketFile :: [FilePath] -> FilePath -> FilePath selectSocketFile [] d = d selectSocketFile (f:fs) d | valid_unix_socket_path f = f | otherwise = selectSocketFile fs d valid_unix_socket_path :: FilePath -> Bool valid_unix_socket_path f = length (decodeW8 f) < 100 - reservedbyssh where -- ssh tacks on 17 or so characters when making a socket reservedbyssh = 18