aboutsummaryrefslogtreecommitdiff
path: root/Assistant/Pairing.hs
blob: 4f8a8bb118ad58d535db455ffa33576b4d88ef99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
{- git-annex assistant repo pairing
 -
 - Copyright 2012 Joey Hess <joey@kitenet.net>
 -
 - Licensed under the GNU GPL version 3 or higher.
 -}

module Assistant.Pairing where

import Common
import Utility.Verifiable
import Utility.ThreadScheduler
import Utility.Network

import Network.Multicast
import Network.Info
import Network.Socket
import Control.Concurrent
import Control.Exception (bracket)
import qualified Data.Map as M

{- "I'll pair with anybody who shares the secret that can be used to verify
 - this request." -}
data PairReq = PairReq (Verifiable PairData)
	deriving (Eq, Read, Show)

{- "I've verified your request, and you can verify mine to see that I know
 - the secret. I set up your ssh key already. Here's mine for you to set up." -}
data PairAck = PairAck (Verifiable PairData)
	deriving (Eq, Read, Show)

fromPairReq :: PairReq -> Verifiable PairData
fromPairReq (PairReq v) = v

fromPairAck :: PairAck -> Verifiable PairData
fromPairAck (PairAck v) = v

data PairMsg
	= PairReqM PairReq
	| PairAckM PairAck
	deriving (Eq, Read, Show)

data PairData = PairData
	{ remoteHostName :: HostName
	, remoteUserName :: UserName
	, sshPubKey :: SshPubKey
	}
	deriving (Eq, Read, Show)

type SshPubKey = String
type UserName = String

{- A pairing that is in progress has a secret, and a thread that is
 - broadcasting pairing requests. -}
data PairingInProgress = PairingInProgress Secret ThreadId

{- This is an arbitrary port in the dynamic port range, that could
 - conceivably be used for some other broadcast messages.
 - If so, hope they ignore the garbage from us; we'll certianly
 - ignore garbage from them. Wild wild west. -}
pairingPort :: PortNumber
pairingPort = 55556

{- This is the All Hosts multicast group, which should reach all hosts
 - on the same network segment. -}
multicastAddress :: SomeAddr -> HostName
multicastAddress (IPv4Addr _) = "224.0.0.1"
multicastAddress (IPv6Addr _) = "ff02::1"

type MkPairMsg = HostName -> PairMsg

{- Multicasts a message repeatedly on all interfaces until its thread
 - is killed, with a 2 second delay between each transmission.
 -
 - The remoteHostName is set to the best host name that can be found for
 - each interface's IP address. When possible, that's a .local name.
 - If not, it's whatever is found in the DNS for the address, or failing
 - that, the IP address.
 -
 - Note that new sockets are opened each time. This is hardly efficient,
 - but it allows new network interfaces to be used as they come up.
 - On the other hand, the expensive DNS lookups are cached.
 -}
multicastPairMsg :: MkPairMsg -> IO ThreadId
multicastPairMsg mkmsg = forkIO $ go =<< initMsgCache mkmsg
	where
		go cache = do
			addrs <- activeNetworkAddresses
			cache' <- updateMsgCache mkmsg cache addrs
			mapM_ (sendinterface cache') addrs
			threadDelaySeconds (Seconds 2)
			go cache'
		sendinterface cache i = void $ catchMaybeIO $
			withSocketsDo $ bracket
				(multicastSender (multicastAddress i) pairingPort)
				(sClose . fst)
				(\(sock, addr) -> do
					setInterface sock (show i)
					maybe noop (\s -> void $ sendTo sock s addr)
						(M.lookup i cache)
				)

{- A cache of serialized messages. -}
type MsgCache = M.Map SomeAddr String

{- Ensures that the cache has messages for each address. -}
updateMsgCache :: MkPairMsg -> MsgCache -> [SomeAddr] -> IO MsgCache
updateMsgCache _ m [] = return m
updateMsgCache mkmsg m (v:vs)
	| M.member v m = updateMsgCache mkmsg m vs
	| otherwise = do
		let sockaddr = case v of
			IPv4Addr (IPv4 a) -> SockAddrInet (PortNum 0) a
			IPv6Addr (IPv6 o1 o2 o3 o4) -> SockAddrInet6 (PortNum 0) 0 (o1, o2, o3, o4) 0
		mhostname <- catchDefaultIO (fst <$> getNameInfo [] True False sockaddr) Nothing
		let cache' = M.insert v (show $ mkmsg $ fromMaybe (show v) mhostname) m
		updateMsgCache mkmsg cache' vs

{- An initial message cache. Look up hostname.local, and if found, 
 - put it in the cache. -}
initMsgCache :: MkPairMsg -> IO MsgCache
initMsgCache mkmsg = go =<< getHostname
	where
		go Nothing = return M.empty
		go (Just n) = do
			let localname = n ++ ".local"
			addrs <- catchDefaultIO (getAddrInfo Nothing (Just localname) Nothing) []
			case headMaybe addrs of
				Nothing -> return M.empty
				Just addr -> case addrAddress addr of
					SockAddrInet _ a ->
						use localname $
							IPv4Addr $ IPv4 a
					SockAddrInet6 _ _ (o1, o2, o3, o4) _ ->
						use localname $	
							IPv6Addr $ IPv6 o1 o2 o3 o4
					_ -> return M.empty
		use hostname addr = return $ M.fromList [(addr, show $ mkmsg hostname)]

data SomeAddr = IPv4Addr IPv4 | IPv6Addr IPv6
	deriving (Ord, Eq)

instance Show SomeAddr where
	show (IPv4Addr x) = show x
	show (IPv6Addr x) = show x

activeNetworkAddresses :: IO [SomeAddr]
activeNetworkAddresses = filter (not . all (`elem` "0.:") . show)
	. concat . map (\ni -> [IPv4Addr $ ipv4 ni, IPv6Addr $ ipv6 ni])
	<$> getNetworkInterfaces