git-annex/Remote/Hook.hs

153 lines
4.4 KiB
Haskell
Raw Normal View History

{- A remote that provides hooks to run shell commands.
-
- Copyright 2011 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Remote.Hook (remote) where
2011-10-05 20:02:51 +00:00
import Common.Annex
import Types.Remote
import Types.Key
import Types.Creds
import qualified Git
import Config
import Config.Cost
import Annex.UUID
2011-08-17 00:49:54 +00:00
import Remote.Helper.Special
2015-08-17 14:42:14 +00:00
import Remote.Helper.Messages
import Utility.Env
import Messages.Progress
import qualified Data.Map as M
type Action = String
type HookName = String
2011-12-31 08:11:39 +00:00
remote :: RemoteType
remote = RemoteType {
typename = "hook",
enumerate = const (findSpecialRemotes "hooktype"),
generate = gen,
setup = hookSetup
}
gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex (Maybe Remote)
gen r u c gc = do
cst <- remoteCost gc expensiveRemoteCost
return $ Just $ specialRemote c
2014-08-02 21:25:16 +00:00
(simplyPrepare $ store hooktype)
(simplyPrepare $ retrieve hooktype)
(simplyPrepare $ remove hooktype)
(simplyPrepare $ checkKey r hooktype)
2014-12-16 19:26:13 +00:00
Remote
{ uuid = u
, cost = cst
, name = Git.repoDescribe r
, storeKey = storeKeyDummy
, retrieveKeyFile = retreiveKeyFileDummy
, retrieveKeyFileCheap = retrieveCheap hooktype
, removeKey = removeKeyDummy
, lockContent = Nothing
2014-12-16 19:26:13 +00:00
, checkPresent = checkPresentDummy
, checkPresentCheap = False
, whereisKey = Nothing
, remoteFsck = Nothing
, repairRepo = Nothing
, config = c
, localpath = Nothing
, repo = r
, gitconfig = gc
, readonly = False
, availability = GloballyAvailable
, remotetype = remote
, mkUnavailable = gen r u c $
gc { remoteAnnexHookType = Just "!dne!" }
, getInfo = return [("hooktype", hooktype)]
, claimUrl = Nothing
, checkUrl = Nothing
}
where
hooktype = fromMaybe (error "missing hooktype") $ remoteAnnexHookType gc
hookSetup :: Maybe UUID -> Maybe CredPair -> RemoteConfig -> Annex (RemoteConfig, UUID)
hookSetup mu _ c = do
u <- maybe (liftIO genUUID) return mu
2011-07-15 16:47:14 +00:00
let hooktype = fromMaybe (error "Specify hooktype=") $
2011-05-15 06:49:43 +00:00
M.lookup "hooktype" c
glacier, S3: Fix bug that caused embedded creds to not be encypted using the remote's key. encryptionSetup must be called before setRemoteCredPair. Otherwise, the RemoteConfig doesn't have the cipher in it, and so no cipher is used to encrypt the embedded creds. This is a security fix for non-shared encryption methods! For encryption=shared, there's no security problem, just an inconsistentency in whether the embedded creds are encrypted. This is very important to get right, so used some types to help ensure that setRemoteCredPair is only run after encryptionSetup. Note that the external special remote bypasses the type safety, since creds can be set after the initial remote config, if the external special remote program requests it. Also note that IA remotes never use encryption, so encryptionSetup is not run for them at all, and again the type safety is bypassed. This leaves two open questions: 1. What to do about S3 and glacier remotes that were set up using encryption=pubkey/hybrid with embedcreds? Such a git repo has a security hole embedded in it, and this needs to be communicated to the user. Is the changelog enough? 2. enableremote won't work in such a repo, because git-annex will try to decrypt the embedded creds, which are not encrypted, so fails. This needs to be dealt with, especially for ecryption=shared repos, which are not really broken, just inconsistently configured. Noticing that problem for encryption=shared is what led to commit fbdeeeed5fa276d94be587c8916d725eddcaf546, which tried to fix the problem by not decrypting the embedded creds. This commit was sponsored by Josh Taylor.
2014-09-18 21:07:17 +00:00
(c', _encsetup) <- encryptionSetup c
gitConfigSpecialRemote u c' "hooktype" hooktype
return (c', u)
hookEnv :: Action -> Key -> Maybe FilePath -> IO (Maybe [(String, String)])
hookEnv action k f = Just <$> mergeenv (fileenv f ++ keyenv)
2012-11-11 04:51:07 +00:00
where
mergeenv l = addEntries l <$> getEnvironment
envvar s v = ("ANNEX_" ++ s, v)
2012-11-11 04:51:07 +00:00
keyenv = catMaybes
[ Just $ envvar "KEY" (key2file k)
, Just $ envvar "ACTION" action
, envvar "HASH_1" <$> headMaybe hashbits
, envvar "HASH_2" <$> headMaybe (drop 1 hashbits)
2012-11-11 04:51:07 +00:00
]
fileenv Nothing = []
fileenv (Just file) = [envvar "FILE" file]
hashbits = map takeDirectory $ splitPath $ hashDirMixed def k
lookupHook :: HookName -> Action -> Annex (Maybe String)
lookupHook hookname action = do
command <- getConfig (annexConfig hook) ""
if null command
then do
2013-09-26 03:19:01 +00:00
fallback <- getConfig (annexConfig hookfallback) ""
if null fallback
then do
warning $ "missing configuration for " ++ hook ++ " or " ++ hookfallback
return Nothing
else return $ Just fallback
else return $ Just command
2012-11-11 04:51:07 +00:00
where
hook = hookname ++ "-" ++ action ++ "-hook"
hookfallback = hookname ++ "-hook"
runHook :: HookName -> Action -> Key -> Maybe FilePath -> Annex Bool -> Annex Bool
runHook hook action k f a = maybe (return False) run =<< lookupHook hook action
2012-11-11 04:51:07 +00:00
where
run command = do
showOutput -- make way for hook output
ifM (progressCommandEnv "sh" [Param "-c", Param command] =<< liftIO (hookEnv action k f))
2012-11-11 04:51:07 +00:00
( a
, do
warning $ hook ++ " hook exited nonzero!"
return False
)
2014-08-02 21:25:16 +00:00
store :: HookName -> Storer
store h = fileStorer $ \k src _p ->
runHook h "store" k (Just src) $ return True
2014-08-02 21:25:16 +00:00
retrieve :: HookName -> Retriever
retrieve h = fileRetriever $ \d k _p ->
unlessM (runHook h "retrieve" k (Just d) $ return True) $
error "failed to retrieve content"
retrieveCheap :: HookName -> Key -> AssociatedFile -> FilePath -> Annex Bool
retrieveCheap _ _ _ _ = return False
remove :: HookName -> Remover
2011-07-15 16:47:14 +00:00
remove h k = runHook h "remove" k Nothing $ return True
checkKey :: Git.Repo -> HookName -> CheckPresent
checkKey r h k = do
2015-08-17 14:42:14 +00:00
showChecking r
v <- lookupHook h action
liftIO $ check v
2012-11-11 04:51:07 +00:00
where
action = "checkpresent"
2012-11-11 04:51:07 +00:00
findkey s = key2file k `elem` lines s
check Nothing = error $ action ++ " hook misconfigured"
2012-11-11 04:51:07 +00:00
check (Just hook) = do
environ <- hookEnv action k Nothing
findkey <$> readProcessEnv "sh" ["-c", hook] environ