You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

116 lines
4.6 KiB

{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.Xmpp.Session where
import qualified Control.Exception as Ex
import Control.Monad.Error
import Data.Text as Text
import Data.XML.Pickle
import Data.XML.Types(Element)
import Network
import qualified Network.TLS as TLS
import Network.Xmpp.Bind
import Network.Xmpp.Concurrent.Types
import Network.Xmpp.Concurrent
import Network.Xmpp.Connection_
import Network.Xmpp.Marshal
import Network.Xmpp.Pickle
import Network.Xmpp.Sasl
import Network.Xmpp.Sasl.Mechanisms
import Network.Xmpp.Sasl.Types
import Network.Xmpp.Stream
import Network.Xmpp.Tls
import Network.Xmpp.Types
import Control.Concurrent.STM.TMVar
import Data.Maybe
-- | Creates a 'Session' object by setting up a connection with an XMPP server.
--
-- Will connect to the specified host. If the fourth parameters is a 'Just'
-- value, @session@ will attempt to secure the connection with TLS. If the fifth
-- parameters is a 'Just' value, @session@ will attempt to authenticate and
-- acquire an XMPP resource.
session :: HostName -- ^ Host to connect to
-> Text -- ^ The realm host name (to
-- distinguish the XMPP service)
-> PortID -- ^ Port to connect to
-> Maybe TLS.TLSParams -- ^ TLS settings, if securing the
-- connection to the server is
-- desired
-> Maybe ([SaslHandler], Maybe Text) -- ^ SASL handlers and the desired
-- JID resource (or Nothing to let
-- the server decide)
-> IO (Either XmppFailure (Session, Maybe AuthFailure))
session hostname realm port tls sasl = runErrorT $ do
con <- ErrorT $ connect hostname port realm
case tls of
Just tls' -> ErrorT $ startTls tls' con
Nothing -> return ()
aut <- case sasl of
Just sasl' -> ErrorT $ auth (fst sasl) (snd sasl) con
Nothing -> return Nothing
ses <- ErrorT $ newSession con
return (ses, aut)
-- | Connects to the XMPP server and opens the XMPP stream against the given
-- host name, port, and realm.
connect :: HostName -> PortID -> Text -> IO (Either XmppFailure (TMVar Connection))
connect address port hostname = do
con <- connectTcp address port hostname
case con of
Right con' -> do
result <- withConnection startStream con'
return $ Right con'
Left e -> do
return $ Left e
sessionXml :: Element
sessionXml = pickleElem
(xpElemBlank "{urn:ietf:params:xml:ns:xmpp-session}session")
()
sessionIQ :: Stanza
sessionIQ = IQRequestS $ IQRequest { iqRequestID = "sess"
, iqRequestFrom = Nothing
, iqRequestTo = Nothing
, iqRequestLangTag = Nothing
, iqRequestType = Set
, iqRequestPayload = sessionXml
}
-- Sends the session IQ set element and waits for an answer. Throws an error if
-- if an IQ error stanza is returned from the server.
startSession :: TMVar Connection -> IO ()
startSession con = do
answer <- pushIQ' "session" Nothing Set Nothing sessionXml con
case answer of
Left e -> error $ show e
Right _ -> return ()
-- | Authenticate to the server using the first matching method and bind a
-- resource.
auth :: [SaslHandler]
-> Maybe Text
-> TMVar Connection
-> IO (Either XmppFailure (Maybe AuthFailure))
auth mechanisms resource con = runErrorT $ do
ErrorT $ xmppSasl mechanisms con
jid <- lift $ xmppBind resource con
lift $ startSession con
return Nothing
-- | Authenticate to the server with the given username and password
-- and bind a resource.
--
-- Prefers SCRAM-SHA1 over DIGEST-MD5.
simpleAuth :: Text.Text -- ^ The username
-> Text.Text -- ^ The password
-> Maybe Text -- ^ The desired resource or 'Nothing' to let the
-- server assign one
-> TMVar Connection
-> IO (Either XmppFailure (Maybe AuthFailure))
simpleAuth username passwd resource = flip auth resource $
[ -- TODO: scramSha1Plus
scramSha1 username Nothing passwd
, digestMd5 username Nothing passwd
]