{- Copyright (C) 2009 John Millikin This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} module Network.Protocol.XMPP.Client ( ConnectedClient ,Client ,clientConnect ,clientAuthenticate ,clientBind ,clientJID ,clientServerJID ,putTree ,getTree ) where import Codec.Binary.Base64.String (encode) import Network (HostName, PortID, connectTo) import Text.XML.HXT.Arrow ((>>>)) import qualified Text.XML.HXT.Arrow as A import Text.XML.HXT.DOM.TypeDefs (XmlTree) import qualified Text.XML.HXT.DOM.XmlNode as XN import Network.Protocol.XMPP.JID (JID, jidParse) import Network.Protocol.XMPP.SASL (Mechanism, bestMechanism) import qualified Network.Protocol.XMPP.Stream as S import Network.Protocol.XMPP.Util (mkElement, mkQName) data ConnectedClient = ConnectedClient JID S.Stream data Client = Client { clientJID :: JID ,clientServerJID :: JID ,clientStream :: S.Stream } type Username = String type Password = String clientConnect :: JID -> HostName -> PortID -> IO ConnectedClient clientConnect jid host port = do handle <- connectTo host port stream <- S.beginStream jid handle return $ ConnectedClient jid stream clientAuthenticate :: ConnectedClient -> JID -> Username -> Password -> IO Client clientAuthenticate (ConnectedClient serverJID stream) jid username password = do let mechanisms = (advertisedMechanisms . S.streamFeatures) stream let saslMechanism = case bestMechanism mechanisms of Nothing -> error "No supported SASL mechanism" Just m -> m -- TODO: use detected mechanism let saslText = concat [(show jid), "\x00", username, "\x00", password] let b64Text = encode saslText S.putTree stream $ mkElement ("", "auth") [ ("", "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl") ,("", "mechanism", "PLAIN")] [XN.mkText b64Text] response <- S.getTree stream -- TODO: check if response is success or failure newStream <- S.restartStream stream return $ Client serverJID jid newStream clientBind :: Client -> IO JID clientBind c = do -- Bind -- TODO: request specific resource -- TODO: set ID to random value, and check bind result for JID -- TODO: return JID from server putTree c $ mkElement ("", "iq") [("", "type", "set")] [ mkElement ("", "bind") [("", "xmlns", "urn:ietf:params:xml:ns:xmpp-bind")] []] bindResult <- getTree c let [rawJID] = A.runLA ( A.deep (A.hasQName (mkQName "urn:ietf:params:xml:ns:xmpp-bind" "jid")) >>> A.getChildren >>> A.getText) bindResult let jid = case jidParse rawJID of Just x -> x _ -> error "Couldn't parse server's returned JID" -- Session putTree c $ mkElement ("", "iq") [("", "type", "set")] [mkElement ("", "session") [("", "xmlns", "urn:ietf:params:xml:ns:xmpp-session")] []] getTree c putTree c $ mkElement ("", "presence") [] [] getTree c return jid advertisedMechanisms :: [S.StreamFeature] -> [Mechanism] advertisedMechanisms [] = [] advertisedMechanisms (f:fs) = case f of (S.FeatureSASL ms) -> ms _ -> advertisedMechanisms fs ------------------------------------------------------------------------------- putTree :: Client -> XmlTree -> IO () putTree = S.putTree . clientStream getTree :: Client -> IO XmlTree getTree = S.getTree . clientStream