hstorchat-0.1.0.0: src/Network/HSTorChat/Client.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.HSTorChat.Client (newConnectionRequest
) where
import Control.Concurrent
import Control.Exception
import Data.Attoparsec.Text hiding (take)
import qualified Data.Map as M
import qualified Data.Text as T
import Data.Proxy
import Graphics.QML
import System.IO
import System.IO.Error
import System.Random
import Network.HSTorChat.Protocol
import Network.HSTorChat.GUI
-- | This loop handles a new Buddy connection request.
newConnectionRequest :: ObjRef TorChat -> Handle -> IO ()
newConnectionRequest tc iHdl = do
bs <- buds
txt <- hGetLine iHdl
case parseOnly parsePingPong (T.pack txt) of
Left e -> putStr "Error parsing incoming connection: " >> print e
Right (Ping onion cky)
-- Can this interrupt a legitimate connection?
| buddyonline (M.lookup onion bs) -> putStrLn $ "Already a connection to: " ++ T.unpack onion
| otherwise -> pending >>= initiateConn onion cky . filter ((== onion) . _ponion)
-- When a Pong is received an attempt is made
-- to authenticate using the cookie we sent.
Right (Pong cke) -> pending >>= authorizePendingConnection tc iHdl . filter ((== cke) . _pcookie)
_ -> putStrLn "Buddy is not authenticated yet. Ignoring message."
where
-- | Initiate a new connection from scratch.
initiateConn o c [] = do oHdl <- hstorchatOutConn $ o `T.append` ".onion"
gen <- getStdGen
let cky = gencookie gen
case oHdl of
(Just hdl) -> reply (PendingConnection cky o hdl) $ Ping myonion cky : stdrply c
Nothing -> putStrLn $ "Error connecting to " ++ T.unpack o
-- | Complete an existing pending connection.
initiateConn _ c (pconn:_) = reply pconn $ stdrply c
reply p msgs = do mapM_ (hPutStrLn (_pouthandle p) . formatMsg) msgs
modifyMVar_ (_pending $ fromObjRef tc)
$ \ps -> return $ p : filter ((/= _ponion p) . _ponion) ps
newConnectionRequest tc iHdl
buddyonline Nothing = False
buddyonline (Just bud) = _status (fromObjRef bud) /= Offline
gettc = fromObjRef tc
pending = readMVar $ _pending gettc
buds = readMVar $ _buddies gettc
myonion = _myonion gettc
stdrply k = [ Pong k
, Client "HSTorChat"
, Version "0.1.0.0"
, AddMe
, Status Available ]
authorizePendingConnection :: ObjRef TorChat -> Handle -> [PendingConnection] -> IO ()
authorizePendingConnection _ _ [] = putStrLn "Security Warning: Attempted connection with unidentified cookie."
-- | A pending connection exists. Verify and start the buddy
authorizePendingConnection tc iHdl (PendingConnection cke o oHdl : _) = do
let tc' = fromObjRef tc
bs <- readMVar $ _buddies tc'
bud <- constructBuddy $ M.lookup o bs
-- Filter this connection.
modifyMVar_ (_pending tc') $ \ps -> return $ filter ((/= cke) . _pcookie) ps
modifyMVar_ (_buddies tc') $ \buds -> return $ M.insert o bud buds
fireSignal (Proxy :: Proxy BuddiesChanged) tc
runBuddyConnection tc bud
where
constructBuddy :: Maybe (ObjRef Buddy) -> IO (ObjRef Buddy)
constructBuddy Nothing = do ms <- newMVar []
newObjectDC $ Buddy o iHdl oHdl cke Handshake ms
constructBuddy (Just b) = let b' = fromObjRef b in
newObjectDC $ b' { _inConn = iHdl
, _outConn = oHdl
, _cookie = cke
, _status = Handshake
, _msgs = _msgs b' }
runBuddyConnection :: ObjRef TorChat -> ObjRef Buddy -> IO ()
runBuddyConnection tc objb = do
let b = fromObjRef objb
iHdl = _inConn b
oHdl = _outConn b
oni = _onion b
txt <- hGetLine iHdl `catch` errorHandler
case parseOnly parseResponse (T.pack txt) of
Left e -> print ("Error parsing incoming message: " ++ e) >>
runBuddyConnection tc objb
Right (Message msg) -> do
cmsg <- newObjectDC $ ChatMsg msg (_onion b) False
modifyMVar_ (_msgs b) (\ms -> return (cmsg:ms))
fireSignal (Proxy :: Proxy NewChatMsg) objb
runBuddyConnection tc objb
Right (Status Offline) -> do
nb <- newObjectDC $ b { _status = Offline }
modifyMVar_ (_buddies $ fromObjRef tc)
$ \bs -> return $ M.insert oni nb bs
fireSignal (Proxy :: Proxy BuddiesChanged) tc
-- Cleanup handles.
hClose iHdl
hClose oHdl
Right (Status st) -> do
nb <- newObjectDC $ b { _status = st }
modifyMVar_ (_buddies $ fromObjRef tc)
$ \bs -> return $ M.insert oni nb bs
fireSignal (Proxy :: Proxy BuddiesChanged) tc
-- Run the new buddy loop.
runBuddyConnection tc nb
Right p -> print (T.unpack oni ++ ": " ++ show p) >> runBuddyConnection tc objb
where
errorHandler e
| isEOFError e = return "status offline"
| otherwise = ioError e