summaryrefslogtreecommitdiff
path: root/Command/EnableTor.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Command/EnableTor.hs')
-rw-r--r--Command/EnableTor.hs130
1 files changed, 130 insertions, 0 deletions
diff --git a/Command/EnableTor.hs b/Command/EnableTor.hs
new file mode 100644
index 000000000..6f145413d
--- /dev/null
+++ b/Command/EnableTor.hs
@@ -0,0 +1,130 @@
+{- git-annex command
+ -
+ - Copyright 2016 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+{-# LANGUAGE CPP #-}
+
+module Command.EnableTor where
+
+import Command
+import qualified Annex
+import P2P.Address
+import Utility.Tor
+import Annex.UUID
+import Config.Files
+import P2P.IO
+import qualified P2P.Protocol as P2P
+import Utility.ThreadScheduler
+
+import Control.Concurrent.Async
+import qualified Network.Socket as S
+#ifndef mingw32_HOST_OS
+import Utility.Su
+import System.Posix.User
+#endif
+
+cmd :: Command
+cmd = noCommit $ dontCheck repoExists $
+ command "enable-tor" SectionSetup "enable tor hidden service"
+ "uid" (withParams seek)
+
+seek :: CmdParams -> CommandSeek
+seek = withWords start
+
+-- This runs as root, so avoid making any commits or initializing
+-- git-annex, or doing other things that create root-owned files.
+start :: [String] -> CommandStart
+start os = do
+ uuid <- getUUID
+ when (uuid == NoUUID) $
+ giveup "This can only be run in a git-annex repository."
+#ifndef mingw32_HOST_OS
+ curruserid <- liftIO getEffectiveUserID
+ if curruserid == 0
+ then case readish =<< headMaybe os of
+ Nothing -> giveup "Need user-id parameter."
+ Just userid -> go uuid userid
+ else do
+ showStart "enable-tor" ""
+ showLongNote "Need root access to enable tor..."
+ gitannex <- liftIO readProgramFile
+ let ps = [Param (cmdname cmd), Param (show curruserid)]
+ ifM (liftIO $ runAsRoot gitannex ps)
+ ( next $ next checkHiddenService
+ , giveup $ unwords $
+ [ "Failed to run as root:" , gitannex ] ++ toCommand ps
+ )
+#else
+ go uuid 0
+#endif
+ where
+ go uuid userid = do
+ (onionaddr, onionport) <- liftIO $
+ addHiddenService torAppName userid (fromUUID uuid)
+ storeP2PAddress $ TorAnnex onionaddr onionport
+ stop
+
+checkHiddenService :: CommandCleanup
+checkHiddenService = bracket setup cleanup go
+ where
+ setup = do
+ showLongNote "Tor hidden service is configured. Checking connection to it. This may take a few minutes."
+ startlistener
+
+ cleanup = liftIO . cancel
+
+ go _ = check (150 :: Int) =<< filter istoraddr <$> loadP2PAddresses
+
+ istoraddr (TorAnnex _ _) = True
+
+ check 0 _ = giveup "Still unable to connect to hidden service. It might not yet be usable by others. Please check Tor's logs for details."
+ check _ [] = giveup "Somehow didn't get an onion address."
+ check n addrs@(addr:_) = do
+ g <- Annex.gitRepo
+ -- Connect but don't bother trying to auth,
+ -- we just want to know if the tor circuit works.
+ cv <- liftIO $ tryNonAsync $ connectPeer g addr
+ case cv of
+ Left e -> do
+ warning $ "Unable to connect to hidden service. It may not yet have propigated to the Tor network. (" ++ show e ++ ") Will retry.."
+ liftIO $ threadDelaySeconds (Seconds 2)
+ check (n-1) addrs
+ Right conn -> do
+ liftIO $ closeConnection conn
+ showLongNote "Tor hidden service is working."
+ return True
+
+ -- Unless the remotedaemon is already listening on the hidden
+ -- service's socket, start a listener. This is only run during the
+ -- check, and it refuses all auth attempts.
+ startlistener = do
+ r <- Annex.gitRepo
+ u <- getUUID
+ uid <- liftIO getRealUserID
+ let ident = fromUUID u
+ v <- liftIO $ getHiddenServiceSocketFile torAppName uid ident
+ case v of
+ Just sockfile -> ifM (liftIO $ haslistener sockfile)
+ ( liftIO $ async $ return ()
+ , liftIO $ async $ runlistener sockfile u r
+ )
+ Nothing -> giveup "Could not find socket file in Tor configuration!"
+
+ runlistener sockfile u r = serveUnixSocket sockfile $ \h -> do
+ let conn = P2PConnection
+ { connRepo = r
+ , connCheckAuth = const False
+ , connIhdl = h
+ , connOhdl = h
+ }
+ void $ runNetProto conn $ P2P.serveAuth u
+ hClose h
+
+ haslistener sockfile = catchBoolIO $ do
+ soc <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
+ S.connect soc (S.SockAddrUnix sockfile)
+ S.close soc
+ return True