summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Presence/Presence.hs1045
-rw-r--r--Presence/Util.hs59
-rwxr-xr-xg11
-rw-r--r--xmppServer.hs1042
4 files changed, 1117 insertions, 1040 deletions
diff --git a/Presence/Presence.hs b/Presence/Presence.hs
new file mode 100644
index 00000000..2344fb75
--- /dev/null
+++ b/Presence/Presence.hs
@@ -0,0 +1,1045 @@
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE TupleSections #-}
3{-# LANGUAGE LambdaCase #-}
4module Presence where
5
6import System.Environment
7import System.Posix.Signals
8import Control.Concurrent (threadDelay,forkIO,forkOS,killThread,throwTo)
9import Control.Concurrent.STM
10import Control.Concurrent.STM.TMVar
11import Control.Monad.Trans.Resource (runResourceT)
12import Control.Monad.Trans
13import Control.Monad.IO.Class (MonadIO, liftIO)
14import Network.Socket ( SockAddr(..) )
15import System.Endian (fromBE32)
16import Data.List (nub, (\\), intersect, groupBy, sort, sortBy )
17import Data.Ord (comparing )
18import Data.Monoid ( (<>), Sum(..), getSum )
19import qualified Data.Text as Text
20import qualified Data.Text.IO as Text
21import qualified Data.Text.Encoding as Text
22import Control.Monad
23import Control.Monad.Fix
24import qualified Network.BSD as BSD
25import qualified Data.Text as Text
26import Data.Text (Text)
27import qualified Data.Map as Map
28import Data.Map (Map)
29import Control.Exception ({-evaluate,-}handle,SomeException(..),bracketOnError,ErrorCall(..))
30import System.IO.Error (isDoesNotExistError)
31import System.Posix.User (getUserEntryForID,userName)
32import qualified Data.ByteString.Lazy.Char8 as L
33import qualified ConfigFiles
34import Data.Maybe (maybeToList,listToMaybe,mapMaybe)
35import Data.Bits
36import Data.Int (Int8)
37import Data.XML.Types (Event)
38import System.Posix.Types (UserID,CPid)
39import Control.Applicative
40
41import LockedChan (LockedChan)
42import TraversableT
43import UTmp (ProcessID,users)
44import LocalPeerCred
45import XMPPServer
46import PeerResolve
47import ConsoleWriter
48import ClientState
49import Util
50
51isPeerKey :: ConnectionKey -> Bool
52isPeerKey k = case k of { PeerKey {} -> True ; _ -> False }
53
54isClientKey :: ConnectionKey -> Bool
55isClientKey k = case k of { ClientKey {} -> True ; _ -> False }
56
57localJID :: Text -> Text -> IO Text
58localJID user resource = do
59 hostname <- textHostName
60 return $ user <> "@" <> hostname <> "/" <> resource
61
62newPresenceState cw = atomically $ do
63 clients <- newTVar Map.empty
64 clientsByUser <- newTVar Map.empty
65 remotesByPeer <- newTVar Map.empty
66 associatedPeers <- newTVar Map.empty
67 xmpp <- newEmptyTMVar
68 keyToChan <- newTVar Map.empty
69 return PresenceState
70 { clients = clients
71 , clientsByUser = clientsByUser
72 , remotesByPeer = remotesByPeer
73 , associatedPeers = associatedPeers
74 , keyToChan = keyToChan
75 , server = xmpp
76 , consoleWriter = cw
77 }
78
79
80presenceHooks state verbosity = XMPPServerParameters
81 { xmppChooseResourceName = chooseResourceName state
82 , xmppTellClientHisName = tellClientHisName state
83 , xmppTellMyNameToClient = textHostName
84 , xmppTellMyNameToPeer = \addr -> return $ addrToText addr
85 , xmppTellPeerHisName = return . peerKeyToText
86 , xmppTellClientNameOfPeer = flip peerKeyToResolvedName
87 , xmppNewConnection = newConn state
88 , xmppEOF = eofConn state
89 , xmppRosterBuddies = rosterGetBuddies state
90 , xmppRosterSubscribers = rosterGetSubscribers state
91 , xmppRosterSolicited = rosterGetSolicited state
92 , xmppRosterOthers = rosterGetOthers state
93 , xmppSubscribeToRoster = informSentRoster state
94 , xmppDeliverMessage = deliverMessage state
95 , xmppInformClientPresence = informClientPresence state
96 , xmppInformPeerPresence = informPeerPresence state
97 , xmppAnswerProbe = \k stanza chan -> answerProbe state (stanzaTo stanza) k chan
98 , xmppClientSubscriptionRequest = clientSubscriptionRequest state
99 , xmppPeerSubscriptionRequest = peerSubscriptionRequest state
100 , xmppClientInformSubscription = clientInformSubscription state
101 , xmppPeerInformSubscription = peerInformSubscription state
102 , xmppVerbosity = return verbosity
103 }
104
105
106data LocalPresence = LocalPresence
107 { networkClients :: Map ConnectionKey ClientState
108 -- TODO: loginClients
109 }
110
111data RemotePresence = RemotePresence
112 { resources :: Map Text Stanza
113 -- , localSubscribers :: Map Text ()
114 -- ^ subset of clientsByUser who should be
115 -- notified about this presence.
116 }
117
118
119
120pcSingletonNetworkClient :: ConnectionKey
121 -> ClientState -> LocalPresence
122pcSingletonNetworkClient key client =
123 LocalPresence
124 { networkClients = Map.singleton key client
125 }
126
127pcInsertNetworkClient :: ConnectionKey -> ClientState -> LocalPresence -> LocalPresence
128pcInsertNetworkClient key client pc =
129 pc { networkClients = Map.insert key client (networkClients pc) }
130
131pcRemoveNewtworkClient :: ConnectionKey
132 -> LocalPresence -> Maybe LocalPresence
133pcRemoveNewtworkClient key pc = if pcIsEmpty pc' then Nothing
134 else Just pc'
135 where
136 pc' = pc { networkClients = Map.delete key (networkClients pc) }
137
138pcIsEmpty :: LocalPresence -> Bool
139pcIsEmpty pc = Map.null (networkClients pc)
140
141
142data PresenceState = PresenceState
143 { clients :: TVar (Map ConnectionKey ClientState)
144 , clientsByUser :: TVar (Map Text LocalPresence)
145 , remotesByPeer :: TVar (Map ConnectionKey
146 (Map UserName
147 RemotePresence))
148 , associatedPeers :: TVar (Map SockAddr ())
149 , server :: TMVar XMPPServer
150 , keyToChan :: TVar (Map ConnectionKey Conn)
151 , consoleWriter :: ConsoleWriter
152 }
153
154
155
156getConsolePids :: PresenceState -> IO [(Text,ProcessID)]
157getConsolePids state = do
158 us <- UTmp.users
159 return $ map (\(_,tty,pid)->(lazyByteStringToText tty,pid)) us
160
161identifyTTY' :: [(Text, ProcessID)]
162 -> System.Posix.Types.UserID
163 -> L.ByteString
164 -> IO (Maybe Text, Maybe System.Posix.Types.CPid)
165identifyTTY' ttypids uid inode = ttypid
166 where ttypids' = map (\(tty,pid)->(L.fromChunks [Text.encodeUtf8 tty], pid)) ttypids
167 ttypid = fmap textify $ identifyTTY ttypids' uid inode
168 textify (tty,pid) = (fmap lazyByteStringToText tty, pid)
169
170chooseResourceName :: PresenceState
171 -> ConnectionKey -> SockAddr -> t -> IO Text
172chooseResourceName state k addr desired = do
173 muid <- getLocalPeerCred' addr
174 (mtty,pid) <- getTTYandPID muid
175 user <- getJabberUserForId muid
176 status <- atomically $ newTVar Nothing
177 flgs <- atomically $ newTVar 0
178 let client = ClientState { clientResource = maybe "fallback" id mtty
179 , clientUser = user
180 , clientPid = pid
181 , clientStatus = status
182 , clientFlags = flgs }
183
184 do -- forward-lookup of the buddies so that it is cached for reversing.
185 buds <- configText ConfigFiles.getBuddies (clientUser client)
186 forM_ buds $ \bud -> do
187 let (_,h,_) = splitJID bud
188 forkIO $ void $ resolvePeer h
189
190 atomically $ do
191 modifyTVar' (clients state) $ Map.insert k client
192 modifyTVar' (clientsByUser state) $ flip Map.alter (clientUser client)
193 $ \mb -> Just $ maybe (pcSingletonNetworkClient k client)
194 (pcInsertNetworkClient k client)
195 mb
196
197 localJID (clientUser client) (clientResource client)
198
199 where
200 getTTYandPID muid = do
201 -- us <- fmap (map (second fst) . Map.toList) . readTVarIO $ activeUsers state
202 ttypids <- getConsolePids state
203 -- let tailOf3 ((_,a),b) = (a,b)
204 (t,pid) <- case muid of
205 Just (uid,inode) -> identifyTTY' ttypids uid inode
206 Nothing -> return (Nothing,Nothing)
207 let rsc = t `mplus` fmap ( ("pid."<>) . Text.pack . show ) pid
208 return (rsc,pid)
209
210 getJabberUserForId muid =
211 maybe (return "nobody")
212 (\(uid,_) ->
213 handle (\(SomeException _) ->
214 return . (<> "uid.") . Text.pack . show $ uid)
215 $ do
216 user <- fmap userName $ getUserEntryForID uid
217 return (Text.pack user)
218 )
219 muid
220
221forClient :: PresenceState
222 -> ConnectionKey -> IO b -> (ClientState -> IO b) -> IO b
223forClient state k fallback f = do
224 mclient <- atomically $ do
225 cs <- readTVar (clients state)
226 return $ Map.lookup k cs
227 maybe fallback f mclient
228
229tellClientHisName :: PresenceState -> ConnectionKey -> IO Text
230tellClientHisName state k = forClient state k fallback go
231 where
232 fallback = localJID "nobody" "fallback"
233 go client = localJID (clientUser client) (clientResource client)
234
235toMapUnit :: Ord k => [k] -> Map k ()
236toMapUnit xs = Map.fromList $ map (,()) xs
237
238resolveAllPeers :: [Text] -> IO (Map SockAddr ())
239resolveAllPeers hosts = fmap (toMapUnit . concat) $ Prelude.mapM (fmap (take 1) . resolvePeer) hosts
240
241
242rosterGetStuff
243 :: (L.ByteString -> IO [L.ByteString])
244 -> PresenceState -> ConnectionKey -> IO [Text]
245rosterGetStuff what state k = forClient state k (return [])
246 $ \client -> do
247 jids <- configText what (clientUser client)
248 let hosts = map ((\(_,h,_)->h) . splitJID) jids
249 addrs <- resolveAllPeers hosts
250 peers <- atomically $ readTVar (associatedPeers state)
251 addrs <- return $ addrs `Map.difference` peers
252 sv <- atomically $ takeTMVar $ server state
253 -- Grok peers to associate with from the roster:
254 forM_ (Map.keys addrs) $ \addr -> do
255 putStrLn $ "new addr: "++show addr
256 addPeer sv addr
257 -- Update local set of associated peers
258 atomically $ do
259 writeTVar (associatedPeers state) (addrs `Map.union` peers)
260 putTMVar (server state) sv
261 return jids
262
263rosterGetBuddies :: PresenceState -> ConnectionKey -> IO [Text]
264rosterGetBuddies state k = rosterGetStuff ConfigFiles.getBuddies state k
265
266rosterGetSolicited :: PresenceState -> ConnectionKey -> IO [Text]
267rosterGetSolicited = rosterGetStuff ConfigFiles.getSolicited
268
269rosterGetOthers :: PresenceState -> ConnectionKey -> IO [Text]
270rosterGetOthers = rosterGetStuff ConfigFiles.getOthers
271
272rosterGetSubscribers :: PresenceState -> ConnectionKey -> IO [Text]
273rosterGetSubscribers = rosterGetStuff ConfigFiles.getSubscribers
274
275data Conn = Conn { connChan :: TChan Stanza
276 , auxAddr :: SockAddr }
277
278configText :: Functor f =>
279 (L.ByteString -> f [L.ByteString]) -> Text -> f [Text]
280configText what u = fmap (map lazyByteStringToText)
281 $ what (textToLazyByteString u)
282
283getBuddies' :: Text -> IO [Text]
284getBuddies' = configText ConfigFiles.getBuddies
285getSolicited' :: Text -> IO [Text]
286getSolicited' = configText ConfigFiles.getSolicited
287
288sendProbesAndSolicitations :: PresenceState
289 -> ConnectionKey -> SockAddr -> TChan Stanza -> IO ()
290sendProbesAndSolicitations state k laddr chan = do
291 -- get all buddies & solicited matching k for all users
292 xs <- runTraversableT $ do
293 cbu <- lift $ atomically $ readTVar $ clientsByUser state
294 user <- liftT $ Map.keys cbu
295 (isbud,getter) <- liftT [(True ,getBuddies' )
296 ,(False,getSolicited')]
297 bud <- liftMT $ getter user
298 let (u,h,r) = splitJID bud
299 addr <- liftMT $ nub `fmap` resolvePeer h
300 liftT $ guard (PeerKey addr == k)
301 -- Note: Earlier I was tempted to do all the IO
302 -- within the TraversableT monad. That apparently
303 -- is a bad idea. Perhaps due to laziness and an
304 -- unforced list? Instead, we will return a list
305 -- of (Bool,Text) for processing outside.
306 return (isbud,u,if isbud then "" else user)
307 -- XXX: The following O(n²) nub may be a little
308 -- too onerous.
309 forM_ (nub xs) $ \(isbud,u,user) -> do
310 let make = if isbud then presenceProbe
311 else presenceSolicitation
312 toh = peerKeyToText k
313 jid = unsplitJID (u,toh,Nothing)
314 me = addrToText laddr
315 from = if isbud then me -- probe from server
316 else -- solicitation from particular user
317 unsplitJID (Just user,me,Nothing)
318 stanza <- make from jid
319 -- send probes for buddies, solicitations for solicited.
320 putStrLn $ "probing "++show k++" for: " ++ show (isbud,jid)
321 atomically $ writeTChan chan stanza
322 -- reverse xs `seq` return ()
323
324newConn :: PresenceState -> ConnectionKey -> SockAddr -> TChan Stanza -> IO ()
325newConn state k addr outchan = do
326 atomically $ modifyTVar' (keyToChan state)
327 $ Map.insert k Conn { connChan = outchan
328 , auxAddr = addr }
329 when (isPeerKey k)
330 $ sendProbesAndSolicitations state k addr outchan
331
332delclient :: (Alternative m, Monad m) =>
333 ConnectionKey -> m LocalPresence -> m LocalPresence
334delclient k mlp = do
335 lp <- mlp
336 let nc = Map.delete k $ networkClients lp
337 guard $ not (Map.null nc)
338 return $ lp { networkClients = nc }
339
340eofConn :: PresenceState -> ConnectionKey -> IO ()
341eofConn state k = do
342 atomically $ modifyTVar' (keyToChan state) $ Map.delete k
343 case k of
344 ClientKey {} -> do
345 forClient state k (return ()) $ \client -> do
346 stanza <- makePresenceStanza "jabber:server" Nothing Offline
347 informClientPresence state k stanza
348 atomically $ do
349 modifyTVar' (clientsByUser state)
350 $ Map.alter (delclient k) (clientUser client)
351 PeerKey {} -> do
352 let h = peerKeyToText k
353 jids <- atomically $ do
354 rbp <- readTVar (remotesByPeer state)
355 return $ do
356 umap <- maybeToList $ Map.lookup k rbp
357 (u,rp) <- Map.toList umap
358 r <- Map.keys (resources rp)
359 return $ unsplitJID (Just u, h, Just r)
360 forM_ jids $ \jid -> do
361 stanza <- makePresenceStanza "jabber:client" (Just jid) Offline
362 informPeerPresence state k stanza
363
364{-
365rewriteJIDForClient1:: Text -> IO (Maybe ((Maybe Text,Text,Maybe Text),SockAddr))
366rewriteJIDForClient1 jid = do
367 let (n,h,r) = splitJID jid
368 maddr <- fmap listToMaybe $ resolvePeer h
369 flip (maybe $ return Nothing) maddr $ \addr -> do
370 h' <- peerKeyToResolvedName (PeerKey addr)
371 return $ Just ((n,h',r), addr)
372-}
373
374-- | The given address is taken to be the local address for the socket this JID
375-- came in on. The returned JID parts are suitable for unsplitJID to create a
376-- valid JID for communicating to a client. The returned Bool is True when the
377-- host part refers to this local host (i.e. it equals the given SockAddr).
378-- If there are multiple results, it will prefer one which is a member of the
379-- given list in the last argument.
380rewriteJIDForClient :: SockAddr -> Text -> [Text] -> IO (Bool,(Maybe Text,Text,Maybe Text))
381rewriteJIDForClient laddr jid buds = do
382 let (n,h,r) = splitJID jid
383 maddr <- parseAddress (strip_brackets h)
384 flip (maybe $ return (False,(n,ip6literal h,r))) maddr $ \addr -> do
385 let mine = laddr `withPort` 0 == addr `withPort` 0
386 h' <- if mine then textHostName
387 else peerKeyToResolvedName buds (PeerKey addr)
388 return (mine,(n,h',r))
389
390peerKeyToResolvedName :: [Text] -> ConnectionKey -> IO Text
391peerKeyToResolvedName buds k@(ClientKey { localAddress=addr }) = return "ErrorClIeNt1"
392peerKeyToResolvedName buds pk = do
393 ns <- peerKeyToResolvedNames pk
394 let hs = map (\jid -> let (_,h,_)=splitJID jid in h) buds
395 ns' = sortBy (comparing $ not . flip elem hs) ns
396 return $ maybe (peerKeyToText pk) id (listToMaybe ns')
397
398
399multiplyJIDForClient :: SockAddr -> Text -> IO (Bool,[(Maybe Text,Text,Maybe Text)])
400multiplyJIDForClient laddr jid = do
401 let (n,h,r) = splitJID jid
402 maddr <- parseAddress (strip_brackets h)
403 flip (maybe $ return (False,[(n,ip6literal h,r)])) maddr $ \addr -> do
404 let mine = sameAddress laddr addr
405 names <- if mine then fmap (:[]) textHostName
406 else peerKeyToResolvedNames (PeerKey addr)
407 return (mine,map (\h' -> (n,h',r)) names)
408
409
410addrTextToKey :: Text -> IO (Maybe ConnectionKey)
411addrTextToKey h = do
412 maddr <- parseAddress (strip_brackets h)
413 return (fmap PeerKey maddr)
414
415guardPortStrippedAddress :: Text -> SockAddr -> IO (Maybe ())
416guardPortStrippedAddress h laddr = do
417 maddr <- fmap (fmap (`withPort` 0)) $ parseAddress (strip_brackets h)
418 let laddr' = laddr `withPort` 0
419 return $ maddr >>= guard . (==laddr')
420
421
422-- | Accepts a textual representation of a domainname
423-- JID suitable for client connections, and returns the
424-- coresponding ipv6 address JID suitable for peers paired
425-- with a SockAddr with the address part of that JID in
426-- binary form. If no suitable address could be resolved
427-- for the given name, Nothing is returned.
428rewriteJIDForPeer :: Text -> IO (Maybe (Text,SockAddr))
429rewriteJIDForPeer jid = do
430 let (n,h,r) = splitJID jid
431 maddr <- fmap listToMaybe $ resolvePeer h
432 return $ flip fmap maddr $ \addr ->
433 let h' = addrToText addr
434 to' = unsplitJID (n,h',r)
435 in (to',addr)
436
437deliverToConsole :: PresenceState -> IO () -> Stanza -> IO ()
438deliverToConsole state fail msg = do
439 putStrLn $ "TODO: deliver to console"
440 did1 <- writeActiveTTY (consoleWriter state) msg
441 did2 <- writeAllPty (consoleWriter state) msg
442 if not (did1 || did2) then fail else return ()
443
444-- | deliver <message/> or error stanza
445deliverMessage :: PresenceState
446 -> IO ()
447 -> StanzaWrap (LockedChan Event)
448 -> IO ()
449deliverMessage state fail msg =
450 case stanzaOrigin msg of
451 NetworkOrigin senderk@(ClientKey {}) _ -> do
452 -- Case 1. Client -> Peer
453 mto <- do
454 flip (maybe $ return Nothing) (stanzaTo msg) $ \to -> do
455 rewriteJIDForPeer to
456 flip (maybe fail {- reverse lookup failure -})
457 mto
458 $ \(to',addr) -> do
459 let k = PeerKey addr
460 chans <- atomically $ readTVar (keyToChan state)
461 flip (maybe fail) (Map.lookup k chans) $ \(Conn { connChan=chan
462 , auxAddr=laddr }) -> do
463 (n,r) <- forClient state senderk (return (Nothing,Nothing))
464 $ \c -> return (Just (clientUser c), Just (clientResource c))
465 -- original 'from' address is discarded.
466 let from' = unsplitJID (n,addrToText laddr,r)
467 -- dup <- atomically $ cloneStanza (msg { stanzaTo=Just to', stanzaFrom=Just from' })
468 let dup = (msg { stanzaTo=Just to', stanzaFrom=Just from' })
469 sendModifiedStanzaToPeer dup chan
470 NetworkOrigin senderk@(PeerKey {}) _ -> do
471 key_to_chan <- atomically $ readTVar (keyToChan state)
472 flip (maybe fail) (Map.lookup senderk key_to_chan)
473 $ \(Conn { connChan=sender_chan
474 , auxAddr=laddr }) -> do
475 flip (maybe fail) (stanzaTo msg) $ \to -> do
476 (mine,(n,h,r)) <- rewriteJIDForClient laddr to []
477 if not mine then fail else do
478 let to' = unsplitJID (n,h,r)
479 cmap <- atomically . readTVar $ clientsByUser state
480 (from',chans,ks) <- do
481 flip (maybe $ return (Nothing,[],[])) n $ \n -> do
482 buds <- configText ConfigFiles.getBuddies n
483 from' <- do
484 flip (maybe $ return Nothing) (stanzaFrom msg) $ \from -> do
485 (_,trip) <- rewriteJIDForClient laddr from buds
486 return . Just $ unsplitJID trip
487 let nope = return (from',[],[])
488 flip (maybe nope) (Map.lookup n cmap) $ \presence_container -> do
489 let ks = Map.keys (networkClients presence_container)
490 chans = mapMaybe (flip Map.lookup key_to_chan) ks
491 return (from',chans,ks)
492 putStrLn $ "chan count: " ++ show (length chans)
493 let msg' = msg { stanzaTo=Just to'
494 , stanzaFrom=from' }
495 if null chans then deliverToConsole state fail msg' else do
496 forM_ chans $ \Conn { connChan=chan} -> do
497 putStrLn $ "sending "++show (stanzaId msg)++" to clients "++show ks
498 -- TODO: Cloning isn't really neccessary unless there are multiple
499 -- destinations and we should probably transition to minimal cloning,
500 -- or else we should distinguish between announcable stanzas and
501 -- consumable stanzas and announcables use write-only broadcast
502 -- channels that must be cloned in order to be consumed.
503 -- For now, we are doing redundant cloning.
504 dup <- cloneStanza msg'
505 sendModifiedStanzaToClient dup
506 chan
507
508
509setClientFlag :: PresenceState -> ConnectionKey -> Int8 -> IO ()
510setClientFlag state k flag =
511 atomically $ do
512 cmap <- readTVar (clients state)
513 flip (maybe $ return ()) (Map.lookup k cmap) $ \client -> do
514 setClientFlag0 client flag
515
516setClientFlag0 :: ClientState -> Int8 -> STM ()
517setClientFlag0 client flag =
518 modifyTVar' (clientFlags client) (\flgs -> flgs .|. flag)
519
520informSentRoster :: PresenceState -> ConnectionKey -> IO ()
521informSentRoster state k = do
522 setClientFlag state k cf_interested
523
524
525subscribedPeers :: Text -> IO [SockAddr]
526subscribedPeers user = do
527 jids <- configText ConfigFiles.getSubscribers user
528 let hosts = map ((\(_,h,_)->h) . splitJID) jids
529 fmap Map.keys $ resolveAllPeers hosts
530
531-- | this JID is suitable for peers, not clients.
532clientJID :: Conn -> ClientState -> Text
533clientJID con client = unsplitJID ( Just $ clientUser client
534 , addrToText $ auxAddr con
535 , Just $ clientResource client)
536
537-- | Send presence notification to subscribed peers.
538-- Note that a full JID from address will be added to the
539-- stanza if it is not present.
540informClientPresence :: PresenceState
541 -> ConnectionKey -> StanzaWrap (LockedChan Event) -> IO ()
542informClientPresence state k stanza = do
543 forClient state k (return ()) $ \client -> do
544 informClientPresence0 state (Just k) client stanza
545
546informClientPresence0 :: PresenceState
547 -> Maybe ConnectionKey
548 -> ClientState
549 -> StanzaWrap (LockedChan Event)
550 -> IO ()
551informClientPresence0 state mbk client stanza = do
552 dup <- cloneStanza stanza
553 atomically $ writeTVar (clientStatus client) $ Just dup
554 is_avail <- atomically $ clientIsAvailable client
555 when (not is_avail) $ do
556 atomically $ setClientFlag0 client cf_available
557 maybe (return ()) (sendCachedPresence state) mbk
558 addrs <- subscribedPeers (clientUser client)
559 ktc <- atomically $ readTVar (keyToChan state)
560 let connected = mapMaybe (flip Map.lookup ktc . PeerKey) addrs
561 forM_ connected $ \con -> do
562 let from' = clientJID con client
563 mto <- runTraversableT $ do
564 to <- liftT $ stanzaTo stanza
565 (to',_) <- liftMT $ rewriteJIDForPeer to
566 return to'
567 dup <- cloneStanza stanza
568 sendModifiedStanzaToPeer dup { stanzaFrom = Just from'
569 , stanzaTo = mto }
570 (connChan con)
571
572informPeerPresence :: PresenceState
573 -> ConnectionKey
574 -> StanzaWrap (LockedChan Event)
575 -> IO ()
576informPeerPresence state k stanza = do
577 -- Presence must indicate full JID with resource...
578 putStrLn $ "xmppInformPeerPresence checking from address..."
579 flip (maybe $ return ()) (stanzaFrom stanza) $ \from -> do
580 let (muser,h,mresource) = splitJID from
581 putStrLn $ "xmppInformPeerPresence from = " ++ show from
582 -- flip (maybe $ return ()) mresource $ \resource -> do
583 flip (maybe $ return ()) muser $ \user -> do
584
585 clients <- atomically $ do
586
587 -- Update remotesByPeer...
588 rbp <- readTVar (remotesByPeer state)
589 let umap = maybe Map.empty id $ Map.lookup k rbp
590 rp = case (presenceShow $ stanzaType stanza) of
591 Offline ->
592 maybe Map.empty
593 (\resource ->
594 maybe (Map.empty)
595 (Map.delete resource . resources)
596 $ Map.lookup user umap)
597 mresource
598
599 _ ->maybe Map.empty
600 (\resource ->
601 maybe (Map.singleton resource stanza)
602 (Map.insert resource stanza . resources )
603 $ Map.lookup user umap)
604 mresource
605 umap' = Map.insert user (RemotePresence rp) umap
606
607 flip (maybe $ return []) (case presenceShow $ stanzaType stanza of
608 Offline -> Just ()
609 _ -> mresource >> Just ())
610 $ \_ -> do
611 writeTVar (remotesByPeer state) $ Map.insert k umap' rbp
612 -- TODO: Store or delete the stanza (remotesByPeer)
613
614 -- all clients, we'll filter available/authorized later
615
616 ktc <- readTVar (keyToChan state)
617 runTraversableT $ do
618 (ck,client) <- liftMT $ fmap Map.toList $ readTVar (clients state)
619 con <- liftMaybe $ Map.lookup ck ktc
620 return (ck,con,client)
621 putStrLn $ "xmppInformPeerPresence (length clients="++show (length clients)++")"
622 forM_ clients $ \(ck,con,client) -> do
623 -- (TODO: appropriately authorized clients only.)
624 -- For now, all "available" clients (available = sent initial presence)
625 is_avail <- atomically $ clientIsAvailable client
626 when is_avail $ do
627 putStrLn $ "reversing for client: " ++ show from
628 froms <- do -- flip (maybe $ return [from]) k . const $ do
629 let ClientKey laddr = ck
630 (_,trip) <- multiplyJIDForClient laddr from
631 return (map unsplitJID trip)
632
633 putStrLn $ "sending to client: " ++ show (stanzaType stanza,froms)
634 forM_ froms $ \from' -> do
635 dup <- cloneStanza stanza
636 sendModifiedStanzaToClient (dup { stanzaFrom=Just from' })
637 (connChan con)
638
639answerProbe :: PresenceState
640 -> Maybe Text -> ConnectionKey -> TChan Stanza -> IO ()
641answerProbe state mto k chan = do
642 -- putStrLn $ "answerProbe! " ++ show (stanzaType stanza)
643 ktc <- atomically $ readTVar (keyToChan state)
644 muser <- runTraversableT $ do
645 to <- liftT $ mto
646 conn <- liftT $ Map.lookup k ktc
647 let (mu,h,_) = splitJID to -- TODO: currently resource-id is ignored on presence
648 -- probes. Is this correct? Check the spec.
649 liftMT $ guardPortStrippedAddress h (auxAddr conn)
650 u <- liftT mu
651 let ch = addrToText (auxAddr conn)
652 return (u,conn,ch)
653
654 flip (maybe $ return ()) muser $ \(u,conn,ch) -> do
655
656 resolved_subs <- resolvedFromRoster ConfigFiles.getSubscribers u
657 let gaddrs = groupBy (\a b -> snd a == snd b) (sort resolved_subs)
658 whitelist = do
659 xs <- gaddrs
660 x <- take 1 xs
661 guard $ snd x==k
662 mapMaybe fst xs
663
664 -- -- only subscribed peers should get probe replies
665 -- addrs <- subscribedPeers u
666
667 -- TODO: notify remote peer that they are unsubscribed?
668 -- reply <- makeInformSubscription "jabber:server" to from False
669 when (not $ null whitelist) $ do
670
671 replies <- runTraversableT $ do
672 cbu <- lift . atomically $ readTVar (clientsByUser state)
673 let lpres = maybeToList $ Map.lookup u cbu
674 cw <- lift . atomically $ readTVar (cwClients $ consoleWriter state)
675 clientState <- liftT $ (lpres >>= Map.elems . networkClients)
676 ++ Map.elems cw
677 stanza <- liftIOMaybe $ atomically (readTVar (clientStatus clientState))
678 stanza <- lift $ cloneStanza stanza
679 let jid = unsplitJID (Just $ clientUser clientState
680 , ch
681 ,Just $ clientResource clientState)
682 return stanza { stanzaFrom = Just jid
683 , stanzaType = (stanzaType stanza)
684 { presenceWhiteList = whitelist }
685 }
686
687 forM_ replies $ \reply -> do
688 sendModifiedStanzaToPeer reply chan
689
690 -- if no presence, send offline message
691 when (null replies) $ do
692 let jid = unsplitJID (Just u,ch,Nothing)
693 pstanza <- makePresenceStanza "jabber:server" (Just jid) Offline
694 atomically $ writeTChan (connChan conn) pstanza
695
696sendCachedPresence :: PresenceState -> ConnectionKey -> IO ()
697sendCachedPresence state k = do
698 forClient state k (return ()) $ \client -> do
699 rbp <- atomically $ readTVar (remotesByPeer state)
700 jids <- configText ConfigFiles.getBuddies (clientUser client)
701 let hosts = map ((\(_,h,_)->h) . splitJID) jids
702 addrs <- resolveAllPeers hosts
703 let onlines = rbp `Map.intersection` Map.mapKeys PeerKey addrs
704 ClientKey laddr = k
705 mcon <- atomically $ do ktc <- readTVar (keyToChan state)
706 return $ Map.lookup k ktc
707 flip (maybe $ return ()) mcon $ \con -> do
708 -- me <- textHostName
709 forM_ (Map.toList onlines) $ \(pk, umap) -> do
710 forM_ (Map.toList umap) $ \(user,rp) -> do
711 let h = peerKeyToText pk
712 forM_ (Map.toList $ resources rp) $ \(resource,stanza) -> do
713 let jid = unsplitJID (Just user,h,Just resource)
714 (mine,js) <- multiplyJIDForClient laddr jid
715 forM_ js $ \jid -> do
716 let from' = unsplitJID jid
717 dup <- cloneStanza stanza
718 sendModifiedStanzaToClient (dup { stanzaFrom=Just from' })
719 (connChan con)
720
721 pending <- configText ConfigFiles.getPending (clientUser client)
722 hostname <- textHostName
723 forM_ pending $ \pending_jid -> do
724 let cjid = unsplitJID ( Just $ clientUser client
725 , hostname
726 , Nothing )
727 ask <- presenceSolicitation pending_jid cjid
728 sendModifiedStanzaToClient ask (connChan con)
729
730 -- Note: relying on self peer connection to send
731 -- send local buddies.
732 return ()
733
734addToRosterFile :: (MonadPlus t, Traversable t) =>
735 (L.ByteString -> (L.ByteString -> IO (t L.ByteString))
736 -> Maybe L.ByteString
737 -> t1)
738 -> Text -> Text -> [SockAddr] -> t1
739addToRosterFile doit whose to addrs =
740 modifyRosterFile doit whose to addrs True
741
742removeFromRosterFile :: (MonadPlus t, Traversable t) =>
743 (L.ByteString -> (L.ByteString -> IO (t L.ByteString))
744 -> Maybe L.ByteString
745 -> t1)
746 -> Text -> Text -> [SockAddr] -> t1
747removeFromRosterFile doit whose to addrs =
748 modifyRosterFile doit whose to addrs False
749
750modifyRosterFile :: (Traversable t, MonadPlus t) =>
751 (L.ByteString -> (L.ByteString -> IO (t L.ByteString))
752 -> Maybe L.ByteString
753 -> t1)
754 -> Text -> Text -> [SockAddr] -> Bool -> t1
755modifyRosterFile doit whose to addrs bAdd = do
756 let (mu,_,_) = splitJID to
757 cmp jid = runTraversableT $ do
758 let (msu,stored_h,mr) = splitJID (lazyByteStringToText jid)
759 -- Delete from file if a resource is present in file
760 (\f -> maybe f (const mzero) mr) $ do
761 -- Delete from file if no user is present in file
762 flip (maybe mzero) msu $ \stored_u -> do
763 -- do not delete anything if no user was specified
764 flip (maybe $ return jid) mu $ \u -> do
765 -- do not delete if stored user is same as specified
766 if stored_u /= u then return jid else do
767 stored_addrs <- lift $ resolvePeer stored_h
768 -- do not delete if failed to resolve
769 if null stored_addrs then return jid else do
770 -- delete if specified address matches stored
771 if null (stored_addrs \\ addrs) then mzero else do
772 -- keep
773 return jid
774 doit (textToLazyByteString whose)
775 cmp
776 (guard bAdd >> Just (textToLazyByteString to))
777
778clientSubscriptionRequest :: PresenceState -> IO () -> ConnectionKey -> Stanza -> TChan Stanza -> IO ()
779clientSubscriptionRequest state fail k stanza chan = do
780 forClient state k fail $ \client -> do
781 flip (maybe fail) (stanzaTo stanza) $ \to -> do
782 putStrLn $ "Forwarding solictation to peer"
783 let (mu,h,_) = splitJID to
784 to <- return $ unsplitJID (mu,h,Nothing) -- delete resource
785 flip (maybe fail) mu $ \u -> do
786 addrs <- resolvePeer h
787 if null addrs then fail else do
788 -- add to-address to from's solicited
789 addToRosterFile ConfigFiles.modifySolicited (clientUser client) to addrs
790 removeFromRosterFile ConfigFiles.modifyBuddies (clientUser client) to addrs
791 resolved_subs <- resolvedFromRoster ConfigFiles.getSubscribers (clientUser client)
792 let is_subscribed = not . null $ intersect (map ((mu,).PeerKey) addrs) resolved_subs
793 -- subscribers: "from"
794 -- buddies: "to"
795
796 (ktc,ap) <- atomically $
797 liftM2 (,) (readTVar $ keyToChan state)
798 (readTVar $ associatedPeers state)
799
800 case stanzaType stanza of
801 PresenceRequestSubscription True -> do
802 hostname <- textHostName
803 let cjid = unsplitJID (Just $ clientUser client, hostname,Nothing)
804 chans <- clientCons state ktc (clientUser client)
805 forM_ chans $ \( Conn { connChan=chan }, client ) -> do
806 -- roster update ask="subscribe"
807 update <- makeRosterUpdate cjid to
808 [ ("ask","subscribe")
809 , if is_subscribed then ("subscription","from")
810 else ("subscription","none")
811 ]
812 sendModifiedStanzaToClient update chan
813 _ -> return ()
814
815 let dsts = Map.fromList $ map ((,()) . PeerKey) addrs
816 cdsts = ktc `Map.intersection` dsts
817 forM_ (Map.toList cdsts) $ \(pk,con) -> do
818 -- if already connected, send solicitation ...
819 -- let from = clientJID con client
820 let from = unsplitJID ( Just $ clientUser client
821 , addrToText $ auxAddr con
822 , Nothing )
823 mb <- rewriteJIDForPeer to
824 flip (maybe $ return ()) mb $ \(to',addr) -> do
825 dup <- cloneStanza stanza
826 sendModifiedStanzaToPeer (dup { stanzaTo = Just to'
827 , stanzaFrom = Just from })
828 (connChan con)
829 let addrm = Map.fromList (map (,()) addrs)
830 when (not . Map.null $ addrm Map.\\ ap) $ do
831 -- Add peer if we are not already associated ...
832 sv <- atomically $ takeTMVar $ server state
833 addPeer sv (head addrs)
834 atomically $ putTMVar (server state) sv
835
836
837resolvedFromRoster
838 :: (L.ByteString -> IO [L.ByteString])
839 -> UserName -> IO [(Maybe UserName, ConnectionKey)]
840resolvedFromRoster doit u = do
841 subs <- configText doit u
842 runTraversableT $ do
843 (mu,h,_) <- liftT $ splitJID `fmap` subs
844 addr <- liftMT $ fmap nub $ resolvePeer h
845 return (mu,PeerKey addr)
846
847clientCons :: PresenceState
848 -> Map ConnectionKey t -> Text -> IO [(t, ClientState)]
849clientCons state ktc u = do
850 mlp <- atomically $ do
851 cmap <- readTVar $ clientsByUser state
852 return $ Map.lookup u cmap
853 let ks = do lp <- maybeToList mlp
854 Map.toList (networkClients lp)
855 doit (k,client) = do
856 con <- Map.lookup k ktc
857 return (con,client)
858 return $ mapMaybe doit ks
859
860peerSubscriptionRequest :: PresenceState -> IO () -> ConnectionKey -> Stanza -> TChan Stanza -> IO ()
861peerSubscriptionRequest state fail k stanza chan = do
862 putStrLn $ "Handling pending subscription from remote"
863 flip (maybe fail) (stanzaFrom stanza) $ \from -> do
864 flip (maybe fail) (stanzaTo stanza) $ \to -> do
865 let (mto_u,h,_) = splitJID to
866 (mfrom_u,from_h,_) = splitJID from
867 to <- return $ unsplitJID (mto_u,h,Nothing) -- delete resource
868 from <- return $ unsplitJID (mfrom_u,from_h,Nothing) -- delete resource
869 ktc <- atomically . readTVar $ keyToChan state
870 flip (maybe fail) (Map.lookup k ktc)
871 $ \Conn { auxAddr=laddr } -> do
872 (mine,totup) <- rewriteJIDForClient laddr to []
873 if not mine then fail else do
874 (_,fromtup) <- rewriteJIDForClient laddr from []
875 flip (maybe fail) mto_u $ \u -> do
876 flip (maybe fail) mfrom_u $ \from_u -> do
877 resolved_subs <- resolvedFromRoster ConfigFiles.getSubscribers u
878 let already_subscribed = elem (mfrom_u,k) resolved_subs
879 is_wanted = case stanzaType stanza of
880 PresenceRequestSubscription b -> b
881 _ -> False -- Shouldn't happen.
882 -- Section 8 says (for presence of type "subscribe", the server MUST
883 -- adhere to the rules defined under Section 3 and summarized under
884 -- see Appendix A. (pariticularly Appendex A.3.1)
885 if already_subscribed == is_wanted
886 then do
887 -- contact ∈ subscribers --> SHOULD NOT, already handled
888 -- already subscribed, reply and quit
889 -- (note: swapping to and from for reply)
890 reply <- makeInformSubscription "jabber:server" to from is_wanted
891 sendModifiedStanzaToPeer reply chan
892 answerProbe state (Just to) k chan
893 else do
894
895 -- TODO: if peer-connection is to self, then auto-approve local user.
896
897 -- add from-address to to's pending
898 addrs <- resolvePeer from_h
899
900 -- Catch exception in case the user does not exist
901 if null addrs then fail else do
902
903 let from' = unsplitJID fromtup
904
905 already_pending <-
906 if is_wanted then
907 addToRosterFile ConfigFiles.modifyPending u from' addrs
908 else do
909 removeFromRosterFile ConfigFiles.modifySubscribers u from' addrs
910 reply <- makeInformSubscription "jabber:server" to from is_wanted
911 sendModifiedStanzaToPeer reply chan
912 return False
913
914 -- contact ∉ subscribers & contact ∈ pending --> SHOULD NOT
915 when (not already_pending) $ do
916 -- contact ∉ subscribers & contact ∉ pending --> MUST
917
918 chans <- clientCons state ktc u
919 forM_ chans $ \( Conn { connChan=chan }, client ) -> do
920 -- send to clients
921 -- TODO: interested/available clients only?
922 dup <- cloneStanza stanza
923 sendModifiedStanzaToClient dup { stanzaFrom = Just $ from'
924 , stanzaTo = Just $ unsplitJID totup }
925 chan
926
927
928clientInformSubscription :: PresenceState
929 -> IO ()
930 -> ConnectionKey
931 -> StanzaWrap (LockedChan Event)
932 -> IO ()
933clientInformSubscription state fail k stanza = do
934 forClient state k fail $ \client -> do
935 flip (maybe fail) (stanzaTo stanza) $ \to -> do
936 putStrLn $ "clientInformSubscription"
937 let (mu,h,mr) = splitJID to
938 addrs <- resolvePeer h
939 -- remove from pending
940 buds <- resolvedFromRoster ConfigFiles.getBuddies (clientUser client)
941 let is_buddy = not . null $ map ((mu,) . PeerKey) addrs `intersect` buds
942 removeFromRosterFile ConfigFiles.modifyPending (clientUser client) to addrs
943 let (relationship,addf,remf) =
944 case stanzaType stanza of
945 PresenceInformSubscription True ->
946 ( ("subscription", if is_buddy then "both"
947 else "from" )
948 , ConfigFiles.modifySubscribers
949 , ConfigFiles.modifyOthers )
950 _ -> ( ("subscription", if is_buddy then "to"
951 else "none" )
952 , ConfigFiles.modifyOthers
953 , ConfigFiles.modifySubscribers )
954 addToRosterFile addf (clientUser client) to addrs
955 removeFromRosterFile remf (clientUser client) to addrs
956
957 do
958 cbu <- atomically $ readTVar (clientsByUser state)
959 putStrLn $ "cbu = " ++ show (fmap (fmap clientPid . networkClients) cbu)
960
961 -- send roster update to clients
962 (clients,ktc) <- atomically $ do
963 cbu <- readTVar (clientsByUser state)
964 let mlp = Map.lookup (clientUser client) cbu
965 let cs = maybe [] (Map.toList . networkClients) mlp
966 ktc <- readTVar (keyToChan state)
967 return (cs,ktc)
968 forM_ clients $ \(ck, client) -> do
969 is_intereseted <- atomically $ clientIsInterested client
970 putStrLn $ "clientIsInterested: "++show is_intereseted
971 is_intereseted <- atomically $ clientIsInterested client
972 when is_intereseted $ do
973 flip (maybe $ return ()) (Map.lookup ck ktc) $ \con -> do
974 hostname <- textHostName
975 -- TODO: Should cjid include the resource?
976 let cjid = unsplitJID (mu, hostname, Nothing)
977 update <- makeRosterUpdate cjid to [relationship]
978 sendModifiedStanzaToClient update (connChan con)
979
980 -- notify peer
981 let dsts = Map.fromList $ map ((,()) . PeerKey) addrs
982 cdsts = ktc `Map.intersection` dsts
983 forM_ (Map.toList cdsts) $ \(pk,con) -> do
984 let from = clientJID con client
985 to' = unsplitJID (mu, peerKeyToText pk, Nothing)
986 dup <- cloneStanza stanza
987 sendModifiedStanzaToPeer (dup { stanzaTo = Just $ to'
988 , stanzaFrom = Just from })
989 (connChan con)
990 answerProbe state (Just from) pk (connChan con)
991
992peerInformSubscription :: PresenceState
993 -> IO ()
994 -> ConnectionKey
995 -> StanzaWrap (LockedChan Event)
996 -> IO ()
997peerInformSubscription state fail k stanza = do
998 putStrLn $ "TODO: peerInformSubscription"
999 -- remove from solicited
1000 flip (maybe fail) (stanzaFrom stanza) $ \from -> do
1001 ktc <- atomically $ readTVar (keyToChan state)
1002 flip (maybe fail) (Map.lookup k ktc)
1003 $ \(Conn { connChan=sender_chan
1004 , auxAddr=laddr }) -> do
1005 (_,(from_u,from_h,_)) <- rewriteJIDForClient laddr from []
1006 let from'' = unsplitJID (from_u,from_h,Nothing)
1007 muser = do
1008 to <- stanzaTo stanza
1009 let (mu,to_h,to_r) = splitJID to
1010 mu
1011 -- TODO muser = Nothing when wanted=False
1012 -- should probably mean unsubscribed for all users.
1013 -- This would allow us to answer anonymous probes with 'unsubscribed'.
1014 flip (maybe fail) muser $ \user -> do
1015 addrs <- resolvePeer from_h
1016 was_solicited <- removeFromRosterFile ConfigFiles.modifySolicited user from'' addrs
1017 subs <- resolvedFromRoster ConfigFiles.getSubscribers user
1018 let is_sub = not . null $ map ((from_u,) . PeerKey) addrs `intersect` subs
1019 let (relationship,addf,remf) =
1020 case stanzaType stanza of
1021 PresenceInformSubscription True ->
1022 ( ("subscription", if is_sub then "both"
1023 else "to" )
1024 , ConfigFiles.modifyBuddies
1025 , ConfigFiles.modifyOthers )
1026 _ -> ( ("subscription", if is_sub then "from"
1027 else "none")
1028 , ConfigFiles.modifyOthers
1029 , ConfigFiles.modifyBuddies )
1030 addToRosterFile addf user from'' addrs
1031 removeFromRosterFile remf user from'' addrs
1032
1033 hostname <- textHostName
1034 let to' = unsplitJID (Just user, hostname, Nothing)
1035 chans <- clientCons state ktc user
1036 forM_ chans $ \(Conn { connChan=chan }, client) -> do
1037 update <- makeRosterUpdate to' from'' [relationship]
1038 is_intereseted <- atomically $ clientIsInterested client
1039 when is_intereseted $ do
1040 sendModifiedStanzaToClient update chan
1041 -- TODO: interested/availabe clients only?
1042 dup <- cloneStanza stanza
1043 sendModifiedStanzaToClient dup { stanzaFrom = Just $ from''
1044 , stanzaTo = Just to' }
1045 chan
diff --git a/Presence/Util.hs b/Presence/Util.hs
new file mode 100644
index 00000000..8d9a9494
--- /dev/null
+++ b/Presence/Util.hs
@@ -0,0 +1,59 @@
1{-# LANGUAGE OverloadedStrings #-}
2module Util where
3
4import qualified Data.ByteString.Lazy as L
5import Data.Monoid
6import qualified Data.Text as Text
7 ;import Data.Text (Text)
8import qualified Data.Text.Encoding as Text
9import qualified Network.BSD as BSD
10import Network.Socket
11
12import Network.Address (setPort)
13
14type UserName = Text
15type ResourceName = Text
16
17
18unsplitJID :: (Maybe UserName,Text,Maybe ResourceName) -> Text
19unsplitJID (n,h,r) = username <> h <> resource
20 where
21 username = maybe "" (<>"@") n
22 resource = maybe "" ("/"<>) r
23
24splitJID :: Text -> (Maybe UserName,Text,Maybe ResourceName)
25splitJID bjid =
26 let xs = splitAll '@' bjid
27 ys = splitAll '/' (last xs)
28 splitAll c bjid = take 1 xs0 ++ map (Text.drop 1) (drop 1 xs0)
29 where xs0 = Text.groupBy (\x y-> y/=c) bjid
30 server = head ys
31 name = case xs of
32 (n:s:_) -> Just n
33 (s:_) -> Nothing
34 rsrc = case ys of
35 (s:_:_) -> Just $ last ys
36 _ -> Nothing
37 in (name,server,rsrc)
38
39
40textHostName :: IO Text
41textHostName = fmap Text.pack BSD.getHostName
42
43textToLazyByteString :: Text -> L.ByteString
44textToLazyByteString s = L.fromChunks [Text.encodeUtf8 s]
45
46lazyByteStringToText :: L.ByteString -> Text
47lazyByteStringToText = (foldr1 (<>) . map Text.decodeUtf8 . L.toChunks)
48
49-- | for example: 2001-db8-85a3-8d3-1319-8a2e-370-7348.ipv6-literal.net
50ip6literal :: Text -> Text
51ip6literal addr = Text.map dash addr <> ".ipv6-literal.net"
52 where
53 dash ':' = '-'
54 dash x = x
55
56sameAddress :: SockAddr -> SockAddr -> Bool
57sameAddress laddr addr = setPort 0 laddr == setPort 0 addr
58
59
diff --git a/g b/g
index 736b2463..cab1b16e 100755
--- a/g
+++ b/g
@@ -1,5 +1,8 @@
1#!/bin/bash 1#!/bin/bash
2args="-fwarn-unused-imports -O2" 2warn="-freverse-errors -fwarn-unused-imports -Wmissing-signatures -fdefer-typed-holes"
3exts="-XOverloadedStrings -XRecordWildCards"
4defs="-DBENCODE_AESON -DTHREAD_DEBUG"
5hide="-hide-package crypto-random -hide-package crypto-api -hide-package crypto-numbers -hide-package cryptohash -hide-package prettyclass"
3 6
4root=${0%/*} 7root=${0%/*}
5cd "$root" 8cd "$root"
@@ -7,9 +10,13 @@ cd "$root"
7me=${0##*/} 10me=${0##*/}
8me=${me%.*} 11me=${me%.*}
9ghc \ 12ghc \
13 $hide \
14 $exts \
15 $defs \
10 -hidir build/$me -odir build/$me \ 16 -hidir build/$me -odir build/$me \
11 -iPresence \ 17 -iPresence \
12 -iArchive \ 18 -iArchive \
19 -isrc \
13 build/b/Presence/monitortty.o \ 20 build/b/Presence/monitortty.o \
14 $args \ 21 $warn \
15 "$@" 22 "$@"
diff --git a/xmppServer.hs b/xmppServer.hs
index 803b4324..01246f64 100644
--- a/xmppServer.hs
+++ b/xmppServer.hs
@@ -44,1002 +44,7 @@ import XMPPServer
44import PeerResolve 44import PeerResolve
45import ConsoleWriter 45import ConsoleWriter
46import ClientState 46import ClientState
47 47import Presence
48type UserName = Text
49type ResourceName = Text
50
51unsplitJID :: (Maybe UserName,Text,Maybe ResourceName) -> Text
52unsplitJID (n,h,r) = username <> h <> resource
53 where
54 username = maybe "" (<>"@") n
55 resource = maybe "" ("/"<>) r
56
57splitJID :: Text -> (Maybe UserName,Text,Maybe ResourceName)
58splitJID bjid =
59 let xs = splitAll '@' bjid
60 ys = splitAll '/' (last xs)
61 splitAll c bjid = take 1 xs0 ++ map (Text.drop 1) (drop 1 xs0)
62 where xs0 = Text.groupBy (\x y-> y/=c) bjid
63 server = head ys
64 name = case xs of
65 (n:s:_) -> Just n
66 (s:_) -> Nothing
67 rsrc = case ys of
68 (s:_:_) -> Just $ last ys
69 _ -> Nothing
70 in (name,server,rsrc)
71
72isPeerKey :: ConnectionKey -> Bool
73isPeerKey k = case k of { PeerKey {} -> True ; _ -> False }
74
75isClientKey :: ConnectionKey -> Bool
76isClientKey k = case k of { ClientKey {} -> True ; _ -> False }
77
78textHostName :: IO Text
79textHostName = fmap Text.pack BSD.getHostName
80
81localJID :: Text -> Text -> IO Text
82localJID user resource = do
83 hostname <- textHostName
84 return $ user <> "@" <> hostname <> "/" <> resource
85
86
87data LocalPresence = LocalPresence
88 { networkClients :: Map ConnectionKey ClientState
89 -- TODO: loginClients
90 }
91
92data RemotePresence = RemotePresence
93 { resources :: Map Text Stanza
94 -- , localSubscribers :: Map Text ()
95 -- ^ subset of clientsByUser who should be
96 -- notified about this presence.
97 }
98
99
100
101pcSingletonNetworkClient :: ConnectionKey
102 -> ClientState -> LocalPresence
103pcSingletonNetworkClient key client =
104 LocalPresence
105 { networkClients = Map.singleton key client
106 }
107
108pcInsertNetworkClient :: ConnectionKey -> ClientState -> LocalPresence -> LocalPresence
109pcInsertNetworkClient key client pc =
110 pc { networkClients = Map.insert key client (networkClients pc) }
111
112pcRemoveNewtworkClient :: ConnectionKey
113 -> LocalPresence -> Maybe LocalPresence
114pcRemoveNewtworkClient key pc = if pcIsEmpty pc' then Nothing
115 else Just pc'
116 where
117 pc' = pc { networkClients = Map.delete key (networkClients pc) }
118
119pcIsEmpty :: LocalPresence -> Bool
120pcIsEmpty pc = Map.null (networkClients pc)
121
122
123data PresenceState = PresenceState
124 { clients :: TVar (Map ConnectionKey ClientState)
125 , clientsByUser :: TVar (Map Text LocalPresence)
126 , remotesByPeer :: TVar (Map ConnectionKey
127 (Map UserName
128 RemotePresence))
129 , associatedPeers :: TVar (Map SockAddr ())
130 , server :: TMVar XMPPServer
131 , keyToChan :: TVar (Map ConnectionKey Conn)
132 , consoleWriter :: ConsoleWriter
133 }
134
135
136
137getConsolePids :: PresenceState -> IO [(Text,ProcessID)]
138getConsolePids state = do
139 us <- UTmp.users
140 return $ map (\(_,tty,pid)->(lazyByteStringToText tty,pid)) us
141
142lazyByteStringToText :: L.ByteString -> Text
143lazyByteStringToText = (foldr1 (<>) . map Text.decodeUtf8 . L.toChunks)
144
145textToLazyByteString :: Text -> L.ByteString
146textToLazyByteString s = L.fromChunks [Text.encodeUtf8 s]
147
148identifyTTY' :: [(Text, ProcessID)]
149 -> System.Posix.Types.UserID
150 -> L.ByteString
151 -> IO (Maybe Text, Maybe System.Posix.Types.CPid)
152identifyTTY' ttypids uid inode = ttypid
153 where ttypids' = map (\(tty,pid)->(L.fromChunks [Text.encodeUtf8 tty], pid)) ttypids
154 ttypid = fmap textify $ identifyTTY ttypids' uid inode
155 textify (tty,pid) = (fmap lazyByteStringToText tty, pid)
156
157chooseResourceName :: PresenceState
158 -> ConnectionKey -> SockAddr -> t -> IO Text
159chooseResourceName state k addr desired = do
160 muid <- getLocalPeerCred' addr
161 (mtty,pid) <- getTTYandPID muid
162 user <- getJabberUserForId muid
163 status <- atomically $ newTVar Nothing
164 flgs <- atomically $ newTVar 0
165 let client = ClientState { clientResource = maybe "fallback" id mtty
166 , clientUser = user
167 , clientPid = pid
168 , clientStatus = status
169 , clientFlags = flgs }
170
171 do -- forward-lookup of the buddies so that it is cached for reversing.
172 buds <- configText ConfigFiles.getBuddies (clientUser client)
173 forM_ buds $ \bud -> do
174 let (_,h,_) = splitJID bud
175 forkIO $ void $ resolvePeer h
176
177 atomically $ do
178 modifyTVar' (clients state) $ Map.insert k client
179 modifyTVar' (clientsByUser state) $ flip Map.alter (clientUser client)
180 $ \mb -> Just $ maybe (pcSingletonNetworkClient k client)
181 (pcInsertNetworkClient k client)
182 mb
183
184 localJID (clientUser client) (clientResource client)
185
186 where
187 getTTYandPID muid = do
188 -- us <- fmap (map (second fst) . Map.toList) . readTVarIO $ activeUsers state
189 ttypids <- getConsolePids state
190 -- let tailOf3 ((_,a),b) = (a,b)
191 (t,pid) <- case muid of
192 Just (uid,inode) -> identifyTTY' ttypids uid inode
193 Nothing -> return (Nothing,Nothing)
194 let rsc = t `mplus` fmap ( ("pid."<>) . Text.pack . show ) pid
195 return (rsc,pid)
196
197 getJabberUserForId muid =
198 maybe (return "nobody")
199 (\(uid,_) ->
200 handle (\(SomeException _) ->
201 return . (<> "uid.") . Text.pack . show $ uid)
202 $ do
203 user <- fmap userName $ getUserEntryForID uid
204 return (Text.pack user)
205 )
206 muid
207
208forClient :: PresenceState
209 -> ConnectionKey -> IO b -> (ClientState -> IO b) -> IO b
210forClient state k fallback f = do
211 mclient <- atomically $ do
212 cs <- readTVar (clients state)
213 return $ Map.lookup k cs
214 maybe fallback f mclient
215
216tellClientHisName :: PresenceState -> ConnectionKey -> IO Text
217tellClientHisName state k = forClient state k fallback go
218 where
219 fallback = localJID "nobody" "fallback"
220 go client = localJID (clientUser client) (clientResource client)
221
222toMapUnit :: Ord k => [k] -> Map k ()
223toMapUnit xs = Map.fromList $ map (,()) xs
224
225resolveAllPeers :: [Text] -> IO (Map SockAddr ())
226resolveAllPeers hosts = fmap (toMapUnit . concat) $ Prelude.mapM (fmap (take 1) . resolvePeer) hosts
227
228
229rosterGetStuff
230 :: (L.ByteString -> IO [L.ByteString])
231 -> PresenceState -> ConnectionKey -> IO [Text]
232rosterGetStuff what state k = forClient state k (return [])
233 $ \client -> do
234 jids <- configText what (clientUser client)
235 let hosts = map ((\(_,h,_)->h) . splitJID) jids
236 addrs <- resolveAllPeers hosts
237 peers <- atomically $ readTVar (associatedPeers state)
238 addrs <- return $ addrs `Map.difference` peers
239 sv <- atomically $ takeTMVar $ server state
240 -- Grok peers to associate with from the roster:
241 forM_ (Map.keys addrs) $ \addr -> do
242 putStrLn $ "new addr: "++show addr
243 addPeer sv addr
244 -- Update local set of associated peers
245 atomically $ do
246 writeTVar (associatedPeers state) (addrs `Map.union` peers)
247 putTMVar (server state) sv
248 return jids
249
250rosterGetBuddies :: PresenceState -> ConnectionKey -> IO [Text]
251rosterGetBuddies state k = rosterGetStuff ConfigFiles.getBuddies state k
252
253rosterGetSolicited :: PresenceState -> ConnectionKey -> IO [Text]
254rosterGetSolicited = rosterGetStuff ConfigFiles.getSolicited
255
256rosterGetOthers :: PresenceState -> ConnectionKey -> IO [Text]
257rosterGetOthers = rosterGetStuff ConfigFiles.getOthers
258
259rosterGetSubscribers :: PresenceState -> ConnectionKey -> IO [Text]
260rosterGetSubscribers = rosterGetStuff ConfigFiles.getSubscribers
261
262data Conn = Conn { connChan :: TChan Stanza
263 , auxAddr :: SockAddr }
264
265configText :: Functor f =>
266 (L.ByteString -> f [L.ByteString]) -> Text -> f [Text]
267configText what u = fmap (map lazyByteStringToText)
268 $ what (textToLazyByteString u)
269
270getBuddies' :: Text -> IO [Text]
271getBuddies' = configText ConfigFiles.getBuddies
272getSolicited' :: Text -> IO [Text]
273getSolicited' = configText ConfigFiles.getSolicited
274
275sendProbesAndSolicitations :: PresenceState
276 -> ConnectionKey -> SockAddr -> TChan Stanza -> IO ()
277sendProbesAndSolicitations state k laddr chan = do
278 -- get all buddies & solicited matching k for all users
279 xs <- runTraversableT $ do
280 cbu <- lift $ atomically $ readTVar $ clientsByUser state
281 user <- liftT $ Map.keys cbu
282 (isbud,getter) <- liftT [(True ,getBuddies' )
283 ,(False,getSolicited')]
284 bud <- liftMT $ getter user
285 let (u,h,r) = splitJID bud
286 addr <- liftMT $ nub `fmap` resolvePeer h
287 liftT $ guard (PeerKey addr == k)
288 -- Note: Earlier I was tempted to do all the IO
289 -- within the TraversableT monad. That apparently
290 -- is a bad idea. Perhaps due to laziness and an
291 -- unforced list? Instead, we will return a list
292 -- of (Bool,Text) for processing outside.
293 return (isbud,u,if isbud then "" else user)
294 -- XXX: The following O(n²) nub may be a little
295 -- too onerous.
296 forM_ (nub xs) $ \(isbud,u,user) -> do
297 let make = if isbud then presenceProbe
298 else presenceSolicitation
299 toh = peerKeyToText k
300 jid = unsplitJID (u,toh,Nothing)
301 me = addrToText laddr
302 from = if isbud then me -- probe from server
303 else -- solicitation from particular user
304 unsplitJID (Just user,me,Nothing)
305 stanza <- make from jid
306 -- send probes for buddies, solicitations for solicited.
307 putStrLn $ "probing "++show k++" for: " ++ show (isbud,jid)
308 atomically $ writeTChan chan stanza
309 -- reverse xs `seq` return ()
310
311newConn :: PresenceState -> ConnectionKey -> SockAddr -> TChan Stanza -> IO ()
312newConn state k addr outchan = do
313 atomically $ modifyTVar' (keyToChan state)
314 $ Map.insert k Conn { connChan = outchan
315 , auxAddr = addr }
316 when (isPeerKey k)
317 $ sendProbesAndSolicitations state k addr outchan
318
319delclient :: (Alternative m, Monad m) =>
320 ConnectionKey -> m LocalPresence -> m LocalPresence
321delclient k mlp = do
322 lp <- mlp
323 let nc = Map.delete k $ networkClients lp
324 guard $ not (Map.null nc)
325 return $ lp { networkClients = nc }
326
327eofConn :: PresenceState -> ConnectionKey -> IO ()
328eofConn state k = do
329 atomically $ modifyTVar' (keyToChan state) $ Map.delete k
330 case k of
331 ClientKey {} -> do
332 forClient state k (return ()) $ \client -> do
333 stanza <- makePresenceStanza "jabber:server" Nothing Offline
334 informClientPresence state k stanza
335 atomically $ do
336 modifyTVar' (clientsByUser state)
337 $ Map.alter (delclient k) (clientUser client)
338 PeerKey {} -> do
339 let h = peerKeyToText k
340 jids <- atomically $ do
341 rbp <- readTVar (remotesByPeer state)
342 return $ do
343 umap <- maybeToList $ Map.lookup k rbp
344 (u,rp) <- Map.toList umap
345 r <- Map.keys (resources rp)
346 return $ unsplitJID (Just u, h, Just r)
347 forM_ jids $ \jid -> do
348 stanza <- makePresenceStanza "jabber:client" (Just jid) Offline
349 informPeerPresence state k stanza
350
351{-
352rewriteJIDForClient1:: Text -> IO (Maybe ((Maybe Text,Text,Maybe Text),SockAddr))
353rewriteJIDForClient1 jid = do
354 let (n,h,r) = splitJID jid
355 maddr <- fmap listToMaybe $ resolvePeer h
356 flip (maybe $ return Nothing) maddr $ \addr -> do
357 h' <- peerKeyToResolvedName (PeerKey addr)
358 return $ Just ((n,h',r), addr)
359-}
360
361-- | for example: 2001-db8-85a3-8d3-1319-8a2e-370-7348.ipv6-literal.net
362ip6literal :: Text -> Text
363ip6literal addr = Text.map dash addr <> ".ipv6-literal.net"
364 where
365 dash ':' = '-'
366 dash x = x
367
368-- | The given address is taken to be the local address for the socket this JID
369-- came in on. The returned JID parts are suitable for unsplitJID to create a
370-- valid JID for communicating to a client. The returned Bool is True when the
371-- host part refers to this local host (i.e. it equals the given SockAddr).
372-- If there are multiple results, it will prefer one which is a member of the
373-- given list in the last argument.
374rewriteJIDForClient :: SockAddr -> Text -> [Text] -> IO (Bool,(Maybe Text,Text,Maybe Text))
375rewriteJIDForClient laddr jid buds = do
376 let (n,h,r) = splitJID jid
377 maddr <- parseAddress (strip_brackets h)
378 flip (maybe $ return (False,(n,ip6literal h,r))) maddr $ \addr -> do
379 let mine = laddr `withPort` 0 == addr `withPort` 0
380 h' <- if mine then textHostName
381 else peerKeyToResolvedName buds (PeerKey addr)
382 return (mine,(n,h',r))
383
384sameAddress :: SockAddr -> SockAddr -> Bool
385sameAddress laddr addr = laddr `withPort` 0 == addr `withPort` 0
386
387peerKeyToResolvedName :: [Text] -> ConnectionKey -> IO Text
388peerKeyToResolvedName buds k@(ClientKey { localAddress=addr }) = return "ErrorClIeNt1"
389peerKeyToResolvedName buds pk = do
390 ns <- peerKeyToResolvedNames pk
391 let hs = map (\jid -> let (_,h,_)=splitJID jid in h) buds
392 ns' = sortBy (comparing $ not . flip elem hs) ns
393 return $ maybe (peerKeyToText pk) id (listToMaybe ns')
394
395
396multiplyJIDForClient :: SockAddr -> Text -> IO (Bool,[(Maybe Text,Text,Maybe Text)])
397multiplyJIDForClient laddr jid = do
398 let (n,h,r) = splitJID jid
399 maddr <- parseAddress (strip_brackets h)
400 flip (maybe $ return (False,[(n,ip6literal h,r)])) maddr $ \addr -> do
401 let mine = sameAddress laddr addr
402 names <- if mine then fmap (:[]) textHostName
403 else peerKeyToResolvedNames (PeerKey addr)
404 return (mine,map (\h' -> (n,h',r)) names)
405
406
407addrTextToKey :: Text -> IO (Maybe ConnectionKey)
408addrTextToKey h = do
409 maddr <- parseAddress (strip_brackets h)
410 return (fmap PeerKey maddr)
411
412guardPortStrippedAddress :: Text -> SockAddr -> IO (Maybe ())
413guardPortStrippedAddress h laddr = do
414 maddr <- fmap (fmap (`withPort` 0)) $ parseAddress (strip_brackets h)
415 let laddr' = laddr `withPort` 0
416 return $ maddr >>= guard . (==laddr')
417
418
419-- | Accepts a textual representation of a domainname
420-- JID suitable for client connections, and returns the
421-- coresponding ipv6 address JID suitable for peers paired
422-- with a SockAddr with the address part of that JID in
423-- binary form. If no suitable address could be resolved
424-- for the given name, Nothing is returned.
425rewriteJIDForPeer :: Text -> IO (Maybe (Text,SockAddr))
426rewriteJIDForPeer jid = do
427 let (n,h,r) = splitJID jid
428 maddr <- fmap listToMaybe $ resolvePeer h
429 return $ flip fmap maddr $ \addr ->
430 let h' = addrToText addr
431 to' = unsplitJID (n,h',r)
432 in (to',addr)
433
434deliverToConsole :: PresenceState -> IO () -> Stanza -> IO ()
435deliverToConsole state fail msg = do
436 putStrLn $ "TODO: deliver to console"
437 did1 <- writeActiveTTY (consoleWriter state) msg
438 did2 <- writeAllPty (consoleWriter state) msg
439 if not (did1 || did2) then fail else return ()
440
441-- | deliver <message/> or error stanza
442deliverMessage :: PresenceState
443 -> IO ()
444 -> StanzaWrap (LockedChan Event)
445 -> IO ()
446deliverMessage state fail msg =
447 case stanzaOrigin msg of
448 NetworkOrigin senderk@(ClientKey {}) _ -> do
449 -- Case 1. Client -> Peer
450 mto <- do
451 flip (maybe $ return Nothing) (stanzaTo msg) $ \to -> do
452 rewriteJIDForPeer to
453 flip (maybe fail {- reverse lookup failure -})
454 mto
455 $ \(to',addr) -> do
456 let k = PeerKey addr
457 chans <- atomically $ readTVar (keyToChan state)
458 flip (maybe fail) (Map.lookup k chans) $ \(Conn { connChan=chan
459 , auxAddr=laddr }) -> do
460 (n,r) <- forClient state senderk (return (Nothing,Nothing))
461 $ \c -> return (Just (clientUser c), Just (clientResource c))
462 -- original 'from' address is discarded.
463 let from' = unsplitJID (n,addrToText laddr,r)
464 -- dup <- atomically $ cloneStanza (msg { stanzaTo=Just to', stanzaFrom=Just from' })
465 let dup = (msg { stanzaTo=Just to', stanzaFrom=Just from' })
466 sendModifiedStanzaToPeer dup chan
467 NetworkOrigin senderk@(PeerKey {}) _ -> do
468 key_to_chan <- atomically $ readTVar (keyToChan state)
469 flip (maybe fail) (Map.lookup senderk key_to_chan)
470 $ \(Conn { connChan=sender_chan
471 , auxAddr=laddr }) -> do
472 flip (maybe fail) (stanzaTo msg) $ \to -> do
473 (mine,(n,h,r)) <- rewriteJIDForClient laddr to []
474 if not mine then fail else do
475 let to' = unsplitJID (n,h,r)
476 cmap <- atomically . readTVar $ clientsByUser state
477 (from',chans,ks) <- do
478 flip (maybe $ return (Nothing,[],[])) n $ \n -> do
479 buds <- configText ConfigFiles.getBuddies n
480 from' <- do
481 flip (maybe $ return Nothing) (stanzaFrom msg) $ \from -> do
482 (_,trip) <- rewriteJIDForClient laddr from buds
483 return . Just $ unsplitJID trip
484 let nope = return (from',[],[])
485 flip (maybe nope) (Map.lookup n cmap) $ \presence_container -> do
486 let ks = Map.keys (networkClients presence_container)
487 chans = mapMaybe (flip Map.lookup key_to_chan) ks
488 return (from',chans,ks)
489 putStrLn $ "chan count: " ++ show (length chans)
490 let msg' = msg { stanzaTo=Just to'
491 , stanzaFrom=from' }
492 if null chans then deliverToConsole state fail msg' else do
493 forM_ chans $ \Conn { connChan=chan} -> do
494 putStrLn $ "sending "++show (stanzaId msg)++" to clients "++show ks
495 -- TODO: Cloning isn't really neccessary unless there are multiple
496 -- destinations and we should probably transition to minimal cloning,
497 -- or else we should distinguish between announcable stanzas and
498 -- consumable stanzas and announcables use write-only broadcast
499 -- channels that must be cloned in order to be consumed.
500 -- For now, we are doing redundant cloning.
501 dup <- cloneStanza msg'
502 sendModifiedStanzaToClient dup
503 chan
504
505
506setClientFlag :: PresenceState -> ConnectionKey -> Int8 -> IO ()
507setClientFlag state k flag =
508 atomically $ do
509 cmap <- readTVar (clients state)
510 flip (maybe $ return ()) (Map.lookup k cmap) $ \client -> do
511 setClientFlag0 client flag
512
513setClientFlag0 :: ClientState -> Int8 -> STM ()
514setClientFlag0 client flag =
515 modifyTVar' (clientFlags client) (\flgs -> flgs .|. flag)
516
517informSentRoster :: PresenceState -> ConnectionKey -> IO ()
518informSentRoster state k = do
519 setClientFlag state k cf_interested
520
521
522subscribedPeers :: Text -> IO [SockAddr]
523subscribedPeers user = do
524 jids <- configText ConfigFiles.getSubscribers user
525 let hosts = map ((\(_,h,_)->h) . splitJID) jids
526 fmap Map.keys $ resolveAllPeers hosts
527
528-- | this JID is suitable for peers, not clients.
529clientJID :: Conn -> ClientState -> Text
530clientJID con client = unsplitJID ( Just $ clientUser client
531 , addrToText $ auxAddr con
532 , Just $ clientResource client)
533
534-- | Send presence notification to subscribed peers.
535-- Note that a full JID from address will be added to the
536-- stanza if it is not present.
537informClientPresence :: PresenceState
538 -> ConnectionKey -> StanzaWrap (LockedChan Event) -> IO ()
539informClientPresence state k stanza = do
540 forClient state k (return ()) $ \client -> do
541 informClientPresence0 state (Just k) client stanza
542
543informClientPresence0 :: PresenceState
544 -> Maybe ConnectionKey
545 -> ClientState
546 -> StanzaWrap (LockedChan Event)
547 -> IO ()
548informClientPresence0 state mbk client stanza = do
549 dup <- cloneStanza stanza
550 atomically $ writeTVar (clientStatus client) $ Just dup
551 is_avail <- atomically $ clientIsAvailable client
552 when (not is_avail) $ do
553 atomically $ setClientFlag0 client cf_available
554 maybe (return ()) (sendCachedPresence state) mbk
555 addrs <- subscribedPeers (clientUser client)
556 ktc <- atomically $ readTVar (keyToChan state)
557 let connected = mapMaybe (flip Map.lookup ktc . PeerKey) addrs
558 forM_ connected $ \con -> do
559 let from' = clientJID con client
560 mto <- runTraversableT $ do
561 to <- liftT $ stanzaTo stanza
562 (to',_) <- liftMT $ rewriteJIDForPeer to
563 return to'
564 dup <- cloneStanza stanza
565 sendModifiedStanzaToPeer dup { stanzaFrom = Just from'
566 , stanzaTo = mto }
567 (connChan con)
568
569informPeerPresence :: PresenceState
570 -> ConnectionKey
571 -> StanzaWrap (LockedChan Event)
572 -> IO ()
573informPeerPresence state k stanza = do
574 -- Presence must indicate full JID with resource...
575 putStrLn $ "xmppInformPeerPresence checking from address..."
576 flip (maybe $ return ()) (stanzaFrom stanza) $ \from -> do
577 let (muser,h,mresource) = splitJID from
578 putStrLn $ "xmppInformPeerPresence from = " ++ show from
579 -- flip (maybe $ return ()) mresource $ \resource -> do
580 flip (maybe $ return ()) muser $ \user -> do
581
582 clients <- atomically $ do
583
584 -- Update remotesByPeer...
585 rbp <- readTVar (remotesByPeer state)
586 let umap = maybe Map.empty id $ Map.lookup k rbp
587 rp = case (presenceShow $ stanzaType stanza) of
588 Offline ->
589 maybe Map.empty
590 (\resource ->
591 maybe (Map.empty)
592 (Map.delete resource . resources)
593 $ Map.lookup user umap)
594 mresource
595
596 _ ->maybe Map.empty
597 (\resource ->
598 maybe (Map.singleton resource stanza)
599 (Map.insert resource stanza . resources )
600 $ Map.lookup user umap)
601 mresource
602 umap' = Map.insert user (RemotePresence rp) umap
603
604 flip (maybe $ return []) (case presenceShow $ stanzaType stanza of
605 Offline -> Just ()
606 _ -> mresource >> Just ())
607 $ \_ -> do
608 writeTVar (remotesByPeer state) $ Map.insert k umap' rbp
609 -- TODO: Store or delete the stanza (remotesByPeer)
610
611 -- all clients, we'll filter available/authorized later
612
613 ktc <- readTVar (keyToChan state)
614 runTraversableT $ do
615 (ck,client) <- liftMT $ fmap Map.toList $ readTVar (clients state)
616 con <- liftMaybe $ Map.lookup ck ktc
617 return (ck,con,client)
618 putStrLn $ "xmppInformPeerPresence (length clients="++show (length clients)++")"
619 forM_ clients $ \(ck,con,client) -> do
620 -- (TODO: appropriately authorized clients only.)
621 -- For now, all "available" clients (available = sent initial presence)
622 is_avail <- atomically $ clientIsAvailable client
623 when is_avail $ do
624 putStrLn $ "reversing for client: " ++ show from
625 froms <- do -- flip (maybe $ return [from]) k . const $ do
626 let ClientKey laddr = ck
627 (_,trip) <- multiplyJIDForClient laddr from
628 return (map unsplitJID trip)
629
630 putStrLn $ "sending to client: " ++ show (stanzaType stanza,froms)
631 forM_ froms $ \from' -> do
632 dup <- cloneStanza stanza
633 sendModifiedStanzaToClient (dup { stanzaFrom=Just from' })
634 (connChan con)
635
636answerProbe :: PresenceState
637 -> Maybe Text -> ConnectionKey -> TChan Stanza -> IO ()
638answerProbe state mto k chan = do
639 -- putStrLn $ "answerProbe! " ++ show (stanzaType stanza)
640 ktc <- atomically $ readTVar (keyToChan state)
641 muser <- runTraversableT $ do
642 to <- liftT $ mto
643 conn <- liftT $ Map.lookup k ktc
644 let (mu,h,_) = splitJID to -- TODO: currently resource-id is ignored on presence
645 -- probes. Is this correct? Check the spec.
646 liftMT $ guardPortStrippedAddress h (auxAddr conn)
647 u <- liftT mu
648 let ch = addrToText (auxAddr conn)
649 return (u,conn,ch)
650
651 flip (maybe $ return ()) muser $ \(u,conn,ch) -> do
652
653 resolved_subs <- resolvedFromRoster ConfigFiles.getSubscribers u
654 let gaddrs = groupBy (\a b -> snd a == snd b) (sort resolved_subs)
655 whitelist = do
656 xs <- gaddrs
657 x <- take 1 xs
658 guard $ snd x==k
659 mapMaybe fst xs
660
661 -- -- only subscribed peers should get probe replies
662 -- addrs <- subscribedPeers u
663
664 -- TODO: notify remote peer that they are unsubscribed?
665 -- reply <- makeInformSubscription "jabber:server" to from False
666 when (not $ null whitelist) $ do
667
668 replies <- runTraversableT $ do
669 cbu <- lift . atomically $ readTVar (clientsByUser state)
670 let lpres = maybeToList $ Map.lookup u cbu
671 cw <- lift . atomically $ readTVar (cwClients $ consoleWriter state)
672 clientState <- liftT $ (lpres >>= Map.elems . networkClients)
673 ++ Map.elems cw
674 stanza <- liftIOMaybe $ atomically (readTVar (clientStatus clientState))
675 stanza <- lift $ cloneStanza stanza
676 let jid = unsplitJID (Just $ clientUser clientState
677 , ch
678 ,Just $ clientResource clientState)
679 return stanza { stanzaFrom = Just jid
680 , stanzaType = (stanzaType stanza)
681 { presenceWhiteList = whitelist }
682 }
683
684 forM_ replies $ \reply -> do
685 sendModifiedStanzaToPeer reply chan
686
687 -- if no presence, send offline message
688 when (null replies) $ do
689 let jid = unsplitJID (Just u,ch,Nothing)
690 pstanza <- makePresenceStanza "jabber:server" (Just jid) Offline
691 atomically $ writeTChan (connChan conn) pstanza
692
693sendCachedPresence :: PresenceState -> ConnectionKey -> IO ()
694sendCachedPresence state k = do
695 forClient state k (return ()) $ \client -> do
696 rbp <- atomically $ readTVar (remotesByPeer state)
697 jids <- configText ConfigFiles.getBuddies (clientUser client)
698 let hosts = map ((\(_,h,_)->h) . splitJID) jids
699 addrs <- resolveAllPeers hosts
700 let onlines = rbp `Map.intersection` Map.mapKeys PeerKey addrs
701 ClientKey laddr = k
702 mcon <- atomically $ do ktc <- readTVar (keyToChan state)
703 return $ Map.lookup k ktc
704 flip (maybe $ return ()) mcon $ \con -> do
705 -- me <- textHostName
706 forM_ (Map.toList onlines) $ \(pk, umap) -> do
707 forM_ (Map.toList umap) $ \(user,rp) -> do
708 let h = peerKeyToText pk
709 forM_ (Map.toList $ resources rp) $ \(resource,stanza) -> do
710 let jid = unsplitJID (Just user,h,Just resource)
711 (mine,js) <- multiplyJIDForClient laddr jid
712 forM_ js $ \jid -> do
713 let from' = unsplitJID jid
714 dup <- cloneStanza stanza
715 sendModifiedStanzaToClient (dup { stanzaFrom=Just from' })
716 (connChan con)
717
718 pending <- configText ConfigFiles.getPending (clientUser client)
719 hostname <- textHostName
720 forM_ pending $ \pending_jid -> do
721 let cjid = unsplitJID ( Just $ clientUser client
722 , hostname
723 , Nothing )
724 ask <- presenceSolicitation pending_jid cjid
725 sendModifiedStanzaToClient ask (connChan con)
726
727 -- Note: relying on self peer connection to send
728 -- send local buddies.
729 return ()
730
731addToRosterFile :: (MonadPlus t, Traversable t) =>
732 (L.ByteString -> (L.ByteString -> IO (t L.ByteString))
733 -> Maybe L.ByteString
734 -> t1)
735 -> Text -> Text -> [SockAddr] -> t1
736addToRosterFile doit whose to addrs =
737 modifyRosterFile doit whose to addrs True
738
739removeFromRosterFile :: (MonadPlus t, Traversable t) =>
740 (L.ByteString -> (L.ByteString -> IO (t L.ByteString))
741 -> Maybe L.ByteString
742 -> t1)
743 -> Text -> Text -> [SockAddr] -> t1
744removeFromRosterFile doit whose to addrs =
745 modifyRosterFile doit whose to addrs False
746
747modifyRosterFile :: (Traversable t, MonadPlus t) =>
748 (L.ByteString -> (L.ByteString -> IO (t L.ByteString))
749 -> Maybe L.ByteString
750 -> t1)
751 -> Text -> Text -> [SockAddr] -> Bool -> t1
752modifyRosterFile doit whose to addrs bAdd = do
753 let (mu,_,_) = splitJID to
754 cmp jid = runTraversableT $ do
755 let (msu,stored_h,mr) = splitJID (lazyByteStringToText jid)
756 -- Delete from file if a resource is present in file
757 (\f -> maybe f (const mzero) mr) $ do
758 -- Delete from file if no user is present in file
759 flip (maybe mzero) msu $ \stored_u -> do
760 -- do not delete anything if no user was specified
761 flip (maybe $ return jid) mu $ \u -> do
762 -- do not delete if stored user is same as specified
763 if stored_u /= u then return jid else do
764 stored_addrs <- lift $ resolvePeer stored_h
765 -- do not delete if failed to resolve
766 if null stored_addrs then return jid else do
767 -- delete if specified address matches stored
768 if null (stored_addrs \\ addrs) then mzero else do
769 -- keep
770 return jid
771 doit (textToLazyByteString whose)
772 cmp
773 (guard bAdd >> Just (textToLazyByteString to))
774
775clientSubscriptionRequest :: PresenceState -> IO () -> ConnectionKey -> Stanza -> TChan Stanza -> IO ()
776clientSubscriptionRequest state fail k stanza chan = do
777 forClient state k fail $ \client -> do
778 flip (maybe fail) (stanzaTo stanza) $ \to -> do
779 putStrLn $ "Forwarding solictation to peer"
780 let (mu,h,_) = splitJID to
781 to <- return $ unsplitJID (mu,h,Nothing) -- delete resource
782 flip (maybe fail) mu $ \u -> do
783 addrs <- resolvePeer h
784 if null addrs then fail else do
785 -- add to-address to from's solicited
786 addToRosterFile ConfigFiles.modifySolicited (clientUser client) to addrs
787 removeFromRosterFile ConfigFiles.modifyBuddies (clientUser client) to addrs
788 resolved_subs <- resolvedFromRoster ConfigFiles.getSubscribers (clientUser client)
789 let is_subscribed = not . null $ intersect (map ((mu,).PeerKey) addrs) resolved_subs
790 -- subscribers: "from"
791 -- buddies: "to"
792
793 (ktc,ap) <- atomically $
794 liftM2 (,) (readTVar $ keyToChan state)
795 (readTVar $ associatedPeers state)
796
797 case stanzaType stanza of
798 PresenceRequestSubscription True -> do
799 hostname <- textHostName
800 let cjid = unsplitJID (Just $ clientUser client, hostname,Nothing)
801 chans <- clientCons state ktc (clientUser client)
802 forM_ chans $ \( Conn { connChan=chan }, client ) -> do
803 -- roster update ask="subscribe"
804 update <- makeRosterUpdate cjid to
805 [ ("ask","subscribe")
806 , if is_subscribed then ("subscription","from")
807 else ("subscription","none")
808 ]
809 sendModifiedStanzaToClient update chan
810 _ -> return ()
811
812 let dsts = Map.fromList $ map ((,()) . PeerKey) addrs
813 cdsts = ktc `Map.intersection` dsts
814 forM_ (Map.toList cdsts) $ \(pk,con) -> do
815 -- if already connected, send solicitation ...
816 -- let from = clientJID con client
817 let from = unsplitJID ( Just $ clientUser client
818 , addrToText $ auxAddr con
819 , Nothing )
820 mb <- rewriteJIDForPeer to
821 flip (maybe $ return ()) mb $ \(to',addr) -> do
822 dup <- cloneStanza stanza
823 sendModifiedStanzaToPeer (dup { stanzaTo = Just to'
824 , stanzaFrom = Just from })
825 (connChan con)
826 let addrm = Map.fromList (map (,()) addrs)
827 when (not . Map.null $ addrm Map.\\ ap) $ do
828 -- Add peer if we are not already associated ...
829 sv <- atomically $ takeTMVar $ server state
830 addPeer sv (head addrs)
831 atomically $ putTMVar (server state) sv
832
833
834resolvedFromRoster
835 :: (L.ByteString -> IO [L.ByteString])
836 -> UserName -> IO [(Maybe UserName, ConnectionKey)]
837resolvedFromRoster doit u = do
838 subs <- configText doit u
839 runTraversableT $ do
840 (mu,h,_) <- liftT $ splitJID `fmap` subs
841 addr <- liftMT $ fmap nub $ resolvePeer h
842 return (mu,PeerKey addr)
843
844clientCons :: PresenceState
845 -> Map ConnectionKey t -> Text -> IO [(t, ClientState)]
846clientCons state ktc u = do
847 mlp <- atomically $ do
848 cmap <- readTVar $ clientsByUser state
849 return $ Map.lookup u cmap
850 let ks = do lp <- maybeToList mlp
851 Map.toList (networkClients lp)
852 doit (k,client) = do
853 con <- Map.lookup k ktc
854 return (con,client)
855 return $ mapMaybe doit ks
856
857peerSubscriptionRequest :: PresenceState -> IO () -> ConnectionKey -> Stanza -> TChan Stanza -> IO ()
858peerSubscriptionRequest state fail k stanza chan = do
859 putStrLn $ "Handling pending subscription from remote"
860 flip (maybe fail) (stanzaFrom stanza) $ \from -> do
861 flip (maybe fail) (stanzaTo stanza) $ \to -> do
862 let (mto_u,h,_) = splitJID to
863 (mfrom_u,from_h,_) = splitJID from
864 to <- return $ unsplitJID (mto_u,h,Nothing) -- delete resource
865 from <- return $ unsplitJID (mfrom_u,from_h,Nothing) -- delete resource
866 ktc <- atomically . readTVar $ keyToChan state
867 flip (maybe fail) (Map.lookup k ktc)
868 $ \Conn { auxAddr=laddr } -> do
869 (mine,totup) <- rewriteJIDForClient laddr to []
870 if not mine then fail else do
871 (_,fromtup) <- rewriteJIDForClient laddr from []
872 flip (maybe fail) mto_u $ \u -> do
873 flip (maybe fail) mfrom_u $ \from_u -> do
874 resolved_subs <- resolvedFromRoster ConfigFiles.getSubscribers u
875 let already_subscribed = elem (mfrom_u,k) resolved_subs
876 is_wanted = case stanzaType stanza of
877 PresenceRequestSubscription b -> b
878 _ -> False -- Shouldn't happen.
879 -- Section 8 says (for presence of type "subscribe", the server MUST
880 -- adhere to the rules defined under Section 3 and summarized under
881 -- see Appendix A. (pariticularly Appendex A.3.1)
882 if already_subscribed == is_wanted
883 then do
884 -- contact ∈ subscribers --> SHOULD NOT, already handled
885 -- already subscribed, reply and quit
886 -- (note: swapping to and from for reply)
887 reply <- makeInformSubscription "jabber:server" to from is_wanted
888 sendModifiedStanzaToPeer reply chan
889 answerProbe state (Just to) k chan
890 else do
891
892 -- TODO: if peer-connection is to self, then auto-approve local user.
893
894 -- add from-address to to's pending
895 addrs <- resolvePeer from_h
896
897 -- Catch exception in case the user does not exist
898 if null addrs then fail else do
899
900 let from' = unsplitJID fromtup
901
902 already_pending <-
903 if is_wanted then
904 addToRosterFile ConfigFiles.modifyPending u from' addrs
905 else do
906 removeFromRosterFile ConfigFiles.modifySubscribers u from' addrs
907 reply <- makeInformSubscription "jabber:server" to from is_wanted
908 sendModifiedStanzaToPeer reply chan
909 return False
910
911 -- contact ∉ subscribers & contact ∈ pending --> SHOULD NOT
912 when (not already_pending) $ do
913 -- contact ∉ subscribers & contact ∉ pending --> MUST
914
915 chans <- clientCons state ktc u
916 forM_ chans $ \( Conn { connChan=chan }, client ) -> do
917 -- send to clients
918 -- TODO: interested/available clients only?
919 dup <- cloneStanza stanza
920 sendModifiedStanzaToClient dup { stanzaFrom = Just $ from'
921 , stanzaTo = Just $ unsplitJID totup }
922 chan
923
924
925clientInformSubscription :: PresenceState
926 -> IO ()
927 -> ConnectionKey
928 -> StanzaWrap (LockedChan Event)
929 -> IO ()
930clientInformSubscription state fail k stanza = do
931 forClient state k fail $ \client -> do
932 flip (maybe fail) (stanzaTo stanza) $ \to -> do
933 putStrLn $ "clientInformSubscription"
934 let (mu,h,mr) = splitJID to
935 addrs <- resolvePeer h
936 -- remove from pending
937 buds <- resolvedFromRoster ConfigFiles.getBuddies (clientUser client)
938 let is_buddy = not . null $ map ((mu,) . PeerKey) addrs `intersect` buds
939 removeFromRosterFile ConfigFiles.modifyPending (clientUser client) to addrs
940 let (relationship,addf,remf) =
941 case stanzaType stanza of
942 PresenceInformSubscription True ->
943 ( ("subscription", if is_buddy then "both"
944 else "from" )
945 , ConfigFiles.modifySubscribers
946 , ConfigFiles.modifyOthers )
947 _ -> ( ("subscription", if is_buddy then "to"
948 else "none" )
949 , ConfigFiles.modifyOthers
950 , ConfigFiles.modifySubscribers )
951 addToRosterFile addf (clientUser client) to addrs
952 removeFromRosterFile remf (clientUser client) to addrs
953
954 do
955 cbu <- atomically $ readTVar (clientsByUser state)
956 putStrLn $ "cbu = " ++ show (fmap (fmap clientPid . networkClients) cbu)
957
958 -- send roster update to clients
959 (clients,ktc) <- atomically $ do
960 cbu <- readTVar (clientsByUser state)
961 let mlp = Map.lookup (clientUser client) cbu
962 let cs = maybe [] (Map.toList . networkClients) mlp
963 ktc <- readTVar (keyToChan state)
964 return (cs,ktc)
965 forM_ clients $ \(ck, client) -> do
966 is_intereseted <- atomically $ clientIsInterested client
967 putStrLn $ "clientIsInterested: "++show is_intereseted
968 is_intereseted <- atomically $ clientIsInterested client
969 when is_intereseted $ do
970 flip (maybe $ return ()) (Map.lookup ck ktc) $ \con -> do
971 hostname <- textHostName
972 -- TODO: Should cjid include the resource?
973 let cjid = unsplitJID (mu, hostname, Nothing)
974 update <- makeRosterUpdate cjid to [relationship]
975 sendModifiedStanzaToClient update (connChan con)
976
977 -- notify peer
978 let dsts = Map.fromList $ map ((,()) . PeerKey) addrs
979 cdsts = ktc `Map.intersection` dsts
980 forM_ (Map.toList cdsts) $ \(pk,con) -> do
981 let from = clientJID con client
982 to' = unsplitJID (mu, peerKeyToText pk, Nothing)
983 dup <- cloneStanza stanza
984 sendModifiedStanzaToPeer (dup { stanzaTo = Just $ to'
985 , stanzaFrom = Just from })
986 (connChan con)
987 answerProbe state (Just from) pk (connChan con)
988
989peerInformSubscription :: PresenceState
990 -> IO ()
991 -> ConnectionKey
992 -> StanzaWrap (LockedChan Event)
993 -> IO ()
994peerInformSubscription state fail k stanza = do
995 putStrLn $ "TODO: peerInformSubscription"
996 -- remove from solicited
997 flip (maybe fail) (stanzaFrom stanza) $ \from -> do
998 ktc <- atomically $ readTVar (keyToChan state)
999 flip (maybe fail) (Map.lookup k ktc)
1000 $ \(Conn { connChan=sender_chan
1001 , auxAddr=laddr }) -> do
1002 (_,(from_u,from_h,_)) <- rewriteJIDForClient laddr from []
1003 let from'' = unsplitJID (from_u,from_h,Nothing)
1004 muser = do
1005 to <- stanzaTo stanza
1006 let (mu,to_h,to_r) = splitJID to
1007 mu
1008 -- TODO muser = Nothing when wanted=False
1009 -- should probably mean unsubscribed for all users.
1010 -- This would allow us to answer anonymous probes with 'unsubscribed'.
1011 flip (maybe fail) muser $ \user -> do
1012 addrs <- resolvePeer from_h
1013 was_solicited <- removeFromRosterFile ConfigFiles.modifySolicited user from'' addrs
1014 subs <- resolvedFromRoster ConfigFiles.getSubscribers user
1015 let is_sub = not . null $ map ((from_u,) . PeerKey) addrs `intersect` subs
1016 let (relationship,addf,remf) =
1017 case stanzaType stanza of
1018 PresenceInformSubscription True ->
1019 ( ("subscription", if is_sub then "both"
1020 else "to" )
1021 , ConfigFiles.modifyBuddies
1022 , ConfigFiles.modifyOthers )
1023 _ -> ( ("subscription", if is_sub then "from"
1024 else "none")
1025 , ConfigFiles.modifyOthers
1026 , ConfigFiles.modifyBuddies )
1027 addToRosterFile addf user from'' addrs
1028 removeFromRosterFile remf user from'' addrs
1029
1030 hostname <- textHostName
1031 let to' = unsplitJID (Just user, hostname, Nothing)
1032 chans <- clientCons state ktc user
1033 forM_ chans $ \(Conn { connChan=chan }, client) -> do
1034 update <- makeRosterUpdate to' from'' [relationship]
1035 is_intereseted <- atomically $ clientIsInterested client
1036 when is_intereseted $ do
1037 sendModifiedStanzaToClient update chan
1038 -- TODO: interested/availabe clients only?
1039 dup <- cloneStanza stanza
1040 sendModifiedStanzaToClient dup { stanzaFrom = Just $ from''
1041 , stanzaTo = Just to' }
1042 chan
1043 48
1044main :: IO () 49main :: IO ()
1045main = runResourceT $ do 50main = runResourceT $ do
@@ -1047,48 +52,9 @@ main = runResourceT $ do
1047 let verbosity = getSum $ flip foldMap args $ \case 52 let verbosity = getSum $ flip foldMap args $ \case
1048 ('-':xs) -> Sum $ length (filter (=='-') xs) 53 ('-':xs) -> Sum $ length (filter (=='-') xs)
1049 _ -> mempty 54 _ -> mempty
1050 cw <- liftIO newConsoleWriter 55 cw <- liftIO newConsoleWriter
1051 state <- liftIO . atomically $ do 56 state <- liftIO $ newPresenceState cw
1052 clients <- newTVar Map.empty 57 sv <- xmppServer (presenceHooks state verbosity)
1053 clientsByUser <- newTVar Map.empty
1054 remotesByPeer <- newTVar Map.empty
1055 associatedPeers <- newTVar Map.empty
1056 xmpp <- newEmptyTMVar
1057 keyToChan <- newTVar Map.empty
1058 return PresenceState
1059 { clients = clients
1060 , clientsByUser = clientsByUser
1061 , remotesByPeer = remotesByPeer
1062 , associatedPeers = associatedPeers
1063 , keyToChan = keyToChan
1064 , server = xmpp
1065 , consoleWriter = cw
1066 }
1067 sv <- xmppServer
1068 XMPPServerParameters
1069 { xmppChooseResourceName = chooseResourceName state
1070 , xmppTellClientHisName = tellClientHisName state
1071 , xmppTellMyNameToClient = textHostName
1072 , xmppTellMyNameToPeer = \addr -> return $ addrToText addr
1073 , xmppTellPeerHisName = return . peerKeyToText
1074 , xmppTellClientNameOfPeer = flip peerKeyToResolvedName
1075 , xmppNewConnection = newConn state
1076 , xmppEOF = eofConn state
1077 , xmppRosterBuddies = rosterGetBuddies state
1078 , xmppRosterSubscribers = rosterGetSubscribers state
1079 , xmppRosterSolicited = rosterGetSolicited state
1080 , xmppRosterOthers = rosterGetOthers state
1081 , xmppSubscribeToRoster = informSentRoster state
1082 , xmppDeliverMessage = deliverMessage state
1083 , xmppInformClientPresence = informClientPresence state
1084 , xmppInformPeerPresence = informPeerPresence state
1085 , xmppAnswerProbe = \k stanza chan -> answerProbe state (stanzaTo stanza) k chan
1086 , xmppClientSubscriptionRequest = clientSubscriptionRequest state
1087 , xmppPeerSubscriptionRequest = peerSubscriptionRequest state
1088 , xmppClientInformSubscription = clientInformSubscription state
1089 , xmppPeerInformSubscription = peerInformSubscription state
1090 , xmppVerbosity = return verbosity
1091 }
1092 liftIO $ do 58 liftIO $ do
1093 atomically $ putTMVar (server state) sv 59 atomically $ putTMVar (server state) sv
1094 60