diff options
-rw-r--r-- | CmdLine/GitAnnex.hs | 2 | ||||
-rw-r--r-- | Command/EnableTor.hs | 34 | ||||
-rw-r--r-- | Utility/Tor.hs | 82 | ||||
-rw-r--r-- | doc/git-annex-enable-tor.mdwn | 25 | ||||
-rw-r--r-- | git-annex.cabal | 1 |
5 files changed, 144 insertions, 0 deletions
diff --git a/CmdLine/GitAnnex.hs b/CmdLine/GitAnnex.hs index e989f3f43..0049ecb3c 100644 --- a/CmdLine/GitAnnex.hs +++ b/CmdLine/GitAnnex.hs @@ -52,6 +52,7 @@ import qualified Command.Init import qualified Command.Describe import qualified Command.InitRemote import qualified Command.EnableRemote +import qualified Command.EnableTor import qualified Command.Expire import qualified Command.Repair import qualified Command.Unused @@ -142,6 +143,7 @@ cmds testoptparser testrunner = , Command.Describe.cmd , Command.InitRemote.cmd , Command.EnableRemote.cmd + , Command.EnableTor.cmd , Command.Reinject.cmd , Command.Unannex.cmd , Command.Uninit.cmd diff --git a/Command/EnableTor.hs b/Command/EnableTor.hs new file mode 100644 index 000000000..1a54c6c5d --- /dev/null +++ b/Command/EnableTor.hs @@ -0,0 +1,34 @@ +{- git-annex command + - + - Copyright 2016 Joey Hess <id@joeyh.name> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Command.EnableTor where + +import Command +import Utility.Tor + +-- This runs as root, so avoid making any commits or initializing +-- git-annex, as that would create root-owned files. +cmd :: Command +cmd = noCommit $ dontCheck repoExists $ + command "enable-tor" SectionPlumbing "" + "userid uuid" (withParams seek) + +seek :: CmdParams -> CommandSeek +seek = withWords start + +start :: CmdParams -> CommandStart +start (suserid:uuid:[]) = case readish suserid of + Nothing -> error "Bad userid" + Just userid -> do + (onionaddr, onionport, onionsocket) <- liftIO $ + addHiddenService userid uuid + liftIO $ putStrLn $ + onionaddr ++ ":" ++ + show onionport ++ " " ++ + show onionsocket + stop +start _ = error "Bad params" diff --git a/Utility/Tor.hs b/Utility/Tor.hs new file mode 100644 index 000000000..a0a609008 --- /dev/null +++ b/Utility/Tor.hs @@ -0,0 +1,82 @@ +{- tor interface + - + - Copyright 2016 Joey Hess <id@joeyh.name> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Utility.Tor where + +import Common +import Utility.ThreadScheduler +import System.PosixCompat.Types +import Data.Char + +type OnionPort = Int +type OnionAddress = String +type OnionSocket = FilePath + +-- | Adds a hidden service connecting to localhost, using some kind +-- of unique identifier. +-- +-- This will only work if run as root, and tor has to already be running. +-- +-- Picks a port number for the hidden service that is not used by any +-- other hidden service (and is >= 1024). Returns the hidden service's +-- onion address, port, and the unix socket file to use. +-- +-- If there is already a hidden service for the specified unique +-- identifier, returns its information without making any changes. +addHiddenService :: UserID -> String -> IO (OnionAddress, OnionPort, OnionSocket) +addHiddenService uid ident = do + ls <- lines <$> readFile torrc + let portssocks = mapMaybe (parseportsock . separate isSpace) ls + case filter (\(_, s) -> s == sockfile) portssocks of + ((p, _s):_) -> waithiddenservice 1 p + _ -> do + let newport = Prelude.head $ + filter (`notElem` map fst portssocks) [1024..] + writeFile torrc $ unlines $ + ls ++ + [ "" + , "HiddenServiceDir " ++ hsdir + , "HiddenServicePort " ++ show newport ++ + " unix:" ++ sockfile + ] + -- Reload tor, so it will see the new hidden + -- service and generate the hostname file for it. + reloaded <- anyM (uncurry boolSystem) + [ ("systemctl", [Param "reload", Param "tor"]) + , ("sefvice", [Param "tor", Param "reload"]) + ] + unless reloaded $ + error "failed to reload tor, perhaps the tor service is not running" + waithiddenservice 120 newport + where + parseportsock ("HiddenServicePort", l) = do + p <- readish $ takeWhile (not . isSpace) l + return (p, drop 1 (dropWhile (/= ':') l)) + parseportsock _ = Nothing + + hsdir = libDir </> "hidden_service_" ++ show uid ++ "_" ++ ident + sockfile = runDir uid </> ident ++ ".sock" + + waithiddenservice :: Int -> OnionPort -> IO (OnionAddress, OnionPort, OnionSocket) + waithiddenservice 0 _ = error "tor failed to create hidden service, perhaps the tor service is not running" + waithiddenservice n p = do + v <- tryIO $ readFile (hsdir </> "hostname") + case v of + Right s | ".onion\n" `isSuffixOf` s -> + return (takeWhile (/= '\n') s, p, sockfile) + _ -> do + threadDelaySeconds (Seconds 1) + waithiddenservice (n-1) p + +torrc :: FilePath +torrc = "/etc/tor/torrc" + +libDir :: FilePath +libDir = "/var/lib/tor" + +runDir :: UserID -> FilePath +runDir uid = "/var/run/user" </> show uid diff --git a/doc/git-annex-enable-tor.mdwn b/doc/git-annex-enable-tor.mdwn new file mode 100644 index 000000000..b44cf817c --- /dev/null +++ b/doc/git-annex-enable-tor.mdwn @@ -0,0 +1,25 @@ +# NAME + +git-annex enable-tor - enable tor hidden service + +# SYNOPSIS + +git annex enable-tor userid uuid + +# DESCRIPTION + +This plumbing-level command enables a tor hidden service for git-annex, +using the specified repository uuid and userid. +It outputs to stdout a line of the form "address.onion:onionport socketfile" + +This command has to be run by root, since it modifies `/etc/tor/torrc`. + +# SEE ALSO + +[[git-annex]](1) + +# AUTHOR + +Joey Hess <id@joeyh.name> + +Warning: Automatically converted into a man page by mdwn2man. Edit with care. diff --git a/git-annex.cabal b/git-annex.cabal index 46b08d22d..eb819463b 100644 --- a/git-annex.cabal +++ b/git-annex.cabal @@ -1060,6 +1060,7 @@ Executable git-annex Utility.ThreadLock Utility.ThreadScheduler Utility.Tmp + Utility.Tor Utility.Touch Utility.Url Utility.UserInfo |