{- Copyright (C) 2009 John Millikin <jmillikin@gmail.com>
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 <http://www.gnu.org/licenses/>.
-}
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