    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