2016-11-14 17:26:34 +00:00
|
|
|
{- 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
|
2016-11-29 17:02:19 +00:00
|
|
|
import Utility.FileMode
|
|
|
|
|
2016-11-14 20:35:45 +00:00
|
|
|
import System.PosixCompat.Types
|
2016-11-14 17:26:34 +00:00
|
|
|
import Data.Char
|
2016-11-21 21:27:38 +00:00
|
|
|
import Network.Socket
|
|
|
|
import Network.Socks5
|
|
|
|
import qualified Data.ByteString.UTF8 as BU8
|
|
|
|
import qualified System.Random as R
|
2016-11-14 17:26:34 +00:00
|
|
|
|
|
|
|
type OnionPort = Int
|
2016-11-21 23:24:55 +00:00
|
|
|
|
2016-11-21 21:27:38 +00:00
|
|
|
newtype OnionAddress = OnionAddress String
|
2016-11-29 21:30:27 +00:00
|
|
|
deriving (Show, Eq)
|
2016-11-21 23:24:55 +00:00
|
|
|
|
2016-11-14 20:35:45 +00:00
|
|
|
type OnionSocket = FilePath
|
2016-11-21 23:24:55 +00:00
|
|
|
|
2016-11-20 19:45:01 +00:00
|
|
|
type UniqueIdent = String
|
2016-11-14 17:26:34 +00:00
|
|
|
|
2016-11-21 21:27:38 +00:00
|
|
|
connectHiddenService :: OnionAddress -> OnionPort -> IO Socket
|
|
|
|
connectHiddenService (OnionAddress address) port = do
|
|
|
|
(s, _) <- socksConnect torsockconf socksaddr
|
|
|
|
return s
|
|
|
|
where
|
|
|
|
torsocksport = 9050
|
|
|
|
torsockconf = defaultSocksConf "127.0.0.1" torsocksport
|
|
|
|
socksdomain = SocksAddrDomainName (BU8.fromString address)
|
|
|
|
socksaddr = SocksAddress socksdomain (fromIntegral port)
|
|
|
|
|
2016-11-14 20:35:45 +00:00
|
|
|
-- | Adds a hidden service connecting to localhost, using some kind
|
|
|
|
-- of unique identifier.
|
|
|
|
--
|
2016-11-14 17:26:34 +00:00
|
|
|
-- This will only work if run as root, and tor has to already be running.
|
|
|
|
--
|
2016-11-21 21:27:38 +00:00
|
|
|
-- Picks a random high port number for the hidden service that is not
|
|
|
|
-- used by any other hidden service. Returns the hidden service's
|
2016-11-14 20:35:45 +00:00
|
|
|
-- 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.
|
2016-11-21 21:27:38 +00:00
|
|
|
addHiddenService :: UserID -> UniqueIdent -> IO (OnionAddress, OnionPort)
|
2016-11-14 20:35:45 +00:00
|
|
|
addHiddenService uid ident = do
|
2016-11-29 17:02:19 +00:00
|
|
|
prepHiddenServiceSocketDir uid ident
|
2016-11-14 20:35:45 +00:00
|
|
|
ls <- lines <$> readFile torrc
|
|
|
|
let portssocks = mapMaybe (parseportsock . separate isSpace) ls
|
|
|
|
case filter (\(_, s) -> s == sockfile) portssocks of
|
|
|
|
((p, _s):_) -> waithiddenservice 1 p
|
|
|
|
_ -> do
|
2016-11-29 21:30:27 +00:00
|
|
|
highports <- R.getStdRandom mkhighports
|
2016-11-14 20:35:45 +00:00
|
|
|
let newport = Prelude.head $
|
2016-11-21 21:27:38 +00:00
|
|
|
filter (`notElem` map fst portssocks) highports
|
2016-11-14 17:26:34 +00:00
|
|
|
writeFile torrc $ unlines $
|
2016-11-14 20:35:45 +00:00
|
|
|
ls ++
|
2016-11-14 17:26:34 +00:00
|
|
|
[ ""
|
2016-11-20 19:45:01 +00:00
|
|
|
, "HiddenServiceDir " ++ hiddenServiceDir uid ident
|
2016-11-14 17:26:34 +00:00
|
|
|
, "HiddenServicePort " ++ show newport ++
|
2016-11-14 20:35:45 +00:00
|
|
|
" unix:" ++ sockfile
|
2016-11-14 17:26:34 +00:00
|
|
|
]
|
|
|
|
-- 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"])
|
2016-12-18 21:13:06 +00:00
|
|
|
, ("service", [Param "tor", Param "reload"])
|
2016-11-14 17:26:34 +00:00
|
|
|
]
|
|
|
|
unless reloaded $
|
2016-11-29 21:30:27 +00:00
|
|
|
giveup "failed to reload tor, perhaps the tor service is not running"
|
2016-11-14 20:35:45 +00:00
|
|
|
waithiddenservice 120 newport
|
2016-11-14 17:26:34 +00:00
|
|
|
where
|
2016-11-14 20:35:45 +00:00
|
|
|
parseportsock ("HiddenServicePort", l) = do
|
|
|
|
p <- readish $ takeWhile (not . isSpace) l
|
|
|
|
return (p, drop 1 (dropWhile (/= ':') l))
|
|
|
|
parseportsock _ = Nothing
|
|
|
|
|
2016-11-29 17:02:19 +00:00
|
|
|
sockfile = hiddenServiceSocketFile uid ident
|
2016-11-14 20:35:45 +00:00
|
|
|
|
2016-11-21 21:27:38 +00:00
|
|
|
-- An infinite random list of high ports.
|
2016-11-29 21:30:27 +00:00
|
|
|
mkhighports g =
|
2016-11-21 21:27:38 +00:00
|
|
|
let (g1, g2) = R.split g
|
|
|
|
in (R.randomRs (1025, 65534) g1, g2)
|
|
|
|
|
|
|
|
waithiddenservice :: Int -> OnionPort -> IO (OnionAddress, OnionPort)
|
2016-11-29 21:30:27 +00:00
|
|
|
waithiddenservice 0 _ = giveup "tor failed to create hidden service, perhaps the tor service is not running"
|
2016-11-14 20:35:45 +00:00
|
|
|
waithiddenservice n p = do
|
2016-11-20 19:45:01 +00:00
|
|
|
v <- tryIO $ readFile $ hiddenServiceHostnameFile uid ident
|
2016-11-14 17:26:34 +00:00
|
|
|
case v of
|
2016-11-29 17:02:19 +00:00
|
|
|
Right s | ".onion\n" `isSuffixOf` s ->
|
2016-11-21 21:27:38 +00:00
|
|
|
return (OnionAddress (takeWhile (/= '\n') s), p)
|
2016-11-14 17:26:34 +00:00
|
|
|
_ -> do
|
|
|
|
threadDelaySeconds (Seconds 1)
|
2016-11-14 20:35:45 +00:00
|
|
|
waithiddenservice (n-1) p
|
2016-11-14 17:26:34 +00:00
|
|
|
|
2016-11-29 17:02:19 +00:00
|
|
|
-- | A hidden service directory to use.
|
|
|
|
--
|
|
|
|
-- The "hs" is used in the name to prevent too long a path name,
|
|
|
|
-- which could present problems for socketFile.
|
|
|
|
hiddenServiceDir :: UserID -> UniqueIdent -> FilePath
|
|
|
|
hiddenServiceDir uid ident = libDir </> "hs_" ++ show uid ++ "_" ++ ident
|
|
|
|
|
|
|
|
hiddenServiceHostnameFile :: UserID -> UniqueIdent -> FilePath
|
|
|
|
hiddenServiceHostnameFile uid ident = hiddenServiceDir uid ident </> "hostname"
|
|
|
|
|
|
|
|
-- | Location of the socket for a hidden service.
|
|
|
|
--
|
|
|
|
-- This has to be a location that tor can read from, and that the user
|
|
|
|
-- can write to. Tor is often prevented by apparmor from reading
|
|
|
|
-- from many locations. Putting it in /etc is a FHS violation, but it's the
|
|
|
|
-- simplest and most robust choice until http://bugs.debian.org/846275 is
|
|
|
|
-- dealt with.
|
|
|
|
--
|
|
|
|
-- Note that some unix systems limit socket paths to 92 bytes long.
|
|
|
|
-- That should not be a problem if the UniqueIdent is around the length of
|
|
|
|
-- a UUID.
|
|
|
|
hiddenServiceSocketFile :: UserID -> UniqueIdent -> FilePath
|
2016-11-29 21:52:46 +00:00
|
|
|
hiddenServiceSocketFile uid ident = etcDir </> "hidden_services" </> show uid ++ "_" ++ ident </> "s"
|
2016-11-29 17:02:19 +00:00
|
|
|
|
|
|
|
-- | Sets up the directory for the socketFile, with appropriate
|
|
|
|
-- permissions. Must run as root.
|
|
|
|
prepHiddenServiceSocketDir :: UserID -> UniqueIdent -> IO ()
|
|
|
|
prepHiddenServiceSocketDir uid ident = do
|
|
|
|
createDirectoryIfMissing True d
|
|
|
|
setOwnerAndGroup d uid (-1)
|
|
|
|
modifyFileMode d $
|
|
|
|
addModes [ownerReadMode, ownerExecuteMode, ownerWriteMode]
|
|
|
|
where
|
|
|
|
d = takeDirectory $ hiddenServiceSocketFile uid ident
|
|
|
|
|
2016-11-14 17:26:34 +00:00
|
|
|
torrc :: FilePath
|
|
|
|
torrc = "/etc/tor/torrc"
|
|
|
|
|
|
|
|
libDir :: FilePath
|
|
|
|
libDir = "/var/lib/tor"
|
2016-11-14 20:35:45 +00:00
|
|
|
|
2016-11-29 17:02:19 +00:00
|
|
|
etcDir :: FilePath
|
|
|
|
etcDir = "/etc/tor"
|