summaryrefslogtreecommitdiff
path: root/xmppServer.hs
diff options
context:
space:
mode:
Diffstat (limited to 'xmppServer.hs')
-rw-r--r--xmppServer.hs1113
1 files changed, 1113 insertions, 0 deletions
diff --git a/xmppServer.hs b/xmppServer.hs
new file mode 100644
index 00000000..803b4324
--- /dev/null
+++ b/xmppServer.hs
@@ -0,0 +1,1113 @@
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE TupleSections #-}
3{-# LANGUAGE LambdaCase #-}
4import System.Environment
5import System.Posix.Signals
6import Control.Concurrent (threadDelay,forkIO,forkOS,killThread,throwTo)
7import Control.Concurrent.STM
8import Control.Concurrent.STM.TMVar
9import Control.Monad.Trans.Resource (runResourceT)
10import Control.Monad.Trans
11import Control.Monad.IO.Class (MonadIO, liftIO)
12import Network.Socket ( SockAddr(..) )
13import System.Endian (fromBE32)
14import Data.List (nub, (\\), intersect, groupBy, sort, sortBy )
15import Data.Ord (comparing )
16import Data.Monoid ( (<>), Sum(..), getSum )
17import qualified Data.Text as Text
18import qualified Data.Text.IO as Text
19import qualified Data.Text.Encoding as Text
20import Control.Monad
21import Control.Monad.Fix
22import qualified Network.BSD as BSD
23import qualified Data.Text as Text
24import Data.Text (Text)
25import qualified Data.Map as Map
26import Data.Map (Map)
27import Control.Exception ({-evaluate,-}handle,SomeException(..),bracketOnError,ErrorCall(..))
28import System.IO.Error (isDoesNotExistError)
29import System.Posix.User (getUserEntryForID,userName)
30import qualified Data.ByteString.Lazy.Char8 as L
31import qualified ConfigFiles
32import Data.Maybe (maybeToList,listToMaybe,mapMaybe)
33import Data.Bits
34import Data.Int (Int8)
35import Data.XML.Types (Event)
36import System.Posix.Types (UserID,CPid)
37import Control.Applicative
38
39import LockedChan (LockedChan)
40import TraversableT
41import UTmp (ProcessID,users)
42import LocalPeerCred
43import XMPPServer
44import PeerResolve
45import ConsoleWriter
46import ClientState
47
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
1044main :: IO ()
1045main = runResourceT $ do
1046 args <- liftIO getArgs
1047 let verbosity = getSum $ flip foldMap args $ \case
1048 ('-':xs) -> Sum $ length (filter (=='-') xs)
1049 _ -> mempty
1050 cw <- liftIO newConsoleWriter
1051 state <- liftIO . atomically $ do
1052 clients <- newTVar Map.empty
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
1093 atomically $ putTMVar (server state) sv
1094
1095 quitVar <- newEmptyTMVarIO
1096 installHandler sigTERM (CatchOnce (atomically $ putTMVar quitVar True)) Nothing
1097 installHandler sigINT (CatchOnce (atomically $ putTMVar quitVar True)) Nothing
1098
1099 forkIO $ do
1100 let console = cwPresenceChan $ consoleWriter state
1101 fix $ \loop -> do
1102 what <- atomically
1103 $ orElse (do (client,stanza) <- takeTMVar console
1104 return $ do informClientPresence0 state Nothing client stanza
1105 loop)
1106 (do readTMVar quitVar
1107 return $ return ())
1108 what
1109
1110 quitMessage <- atomically $ takeTMVar quitVar
1111
1112 putStrLn "goodbye."
1113 return ()