summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CmdLine/GitAnnex.hs2
-rw-r--r--Command/EnableTor.hs34
-rw-r--r--Utility/Tor.hs82
-rw-r--r--doc/git-annex-enable-tor.mdwn25
-rw-r--r--git-annex.cabal1
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