{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE NoMonomorphismRestriction, OverloadedStrings #-} -- -- Submodule for functionality related to SASL negotation: -- authentication functions, SASL functionality, bind functionality, -- and the legacy `{urn:ietf:params:xml:ns:xmpp-session}session' -- functionality. module Network.Xmpp.Sasl ( xmppSasl , digestMd5 , scramSha1 , plain , auth ) where import Control.Monad.Error import Control.Monad.State.Strict import Data.Text (Text) import Data.XML.Pickle import Data.XML.Types import Network.Xmpp.Marshal import Network.Xmpp.Sasl.Mechanisms import Network.Xmpp.Sasl.Types import Network.Xmpp.Stream import Network.Xmpp.Types import System.Log.Logger (debugM, errorM, infoM) -- | Uses the first supported mechanism to authenticate, if any. Updates the -- state with non-password credentials and restarts the stream upon -- success. Returns `Nothing' on success, an `AuthFailure' if -- authentication fails, or an `XmppFailure' if anything else fails. xmppSasl :: [SaslHandler] -- ^ Acceptable authentication mechanisms and their -- corresponding handlers -> Stream -> IO (Either XmppFailure (Maybe AuthFailure)) xmppSasl handlers stream = do debugM "Pontarius.Xmpp" "xmppSasl: Attempts to authenticate..." flip withStream stream $ do -- Chooses the first mechanism that is acceptable by both the client and the -- server. mechanisms <- gets $ streamSaslMechanisms . streamFeatures case (filter (\(name, _) -> name `elem` mechanisms)) handlers of [] -> return $ Right $ Just $ AuthNoAcceptableMechanism mechanisms (_name, handler):_ -> do cs <- gets streamConnectionState case cs of Closed -> do lift $ errorM "Pontarius.Xmpp" "xmppSasl: Stream state closed." return . Left $ XmppNoStream _ -> runErrorT $ do -- TODO: Log details about handler? SaslHandler "show" instance? lift $ lift $ debugM "Pontarius.Xmpp" "xmppSasl: Performing handler..." r <- ErrorT handler case r of Just ae -> do lift $ lift $ errorM "Pontarius.Xmpp" $ "xmppSasl: AuthFailure encountered: " ++ show ae return $ Just ae Nothing -> do lift $ lift $ debugM "Pontarius.Xmpp" "xmppSasl: Authentication successful, restarting stream." _ <- ErrorT restartStream lift $ lift $ debugM "Pontarius.Xmpp" "xmppSasl: Stream restarted." return Nothing -- | Authenticate to the server using the first matching method and bind a -- resource. auth :: [SaslHandler] -> Maybe Text -> Stream -> IO (Either XmppFailure (Maybe AuthFailure)) auth mechanisms resource con = runErrorT $ do mbAuthFail <- ErrorT $ xmppSasl mechanisms con case mbAuthFail of Nothing -> do _jid <- ErrorT $ xmppBind resource con ErrorT $ flip withStream' con $ do s <- get case establishSession $ streamConfiguration s of False -> return $ Right Nothing True -> do _ <-liftIO $ startSession con return $ Right Nothing f -> return f -- Produces a `bind' element, optionally wrapping a resource. bindBody :: Maybe Text -> Element bindBody = pickleElem $ -- Pickler to produce a -- "" -- element, with a possible "[JID]" -- child. xpBind . xpOption $ xpElemNodes "resource" (xpContent xpId) -- Sends a (synchronous) IQ set request for a (`Just') given or server-generated -- resource and extract the JID from the non-error response. xmppBind :: Maybe Text -> Stream -> IO (Either XmppFailure Jid) xmppBind rsrc c = runErrorT $ do lift $ debugM "Pontarius.Xmpp" "Attempts to bind..." answer <- ErrorT $ pushIQ "bind" Nothing Set Nothing (bindBody rsrc) c case answer of Right IQResult{iqResultPayload = Just b} -> do lift $ debugM "Pontarius.Xmpp" "xmppBind: IQ result received; unpickling JID..." let jid = unpickleElem xpJid' b case jid of Right jid' -> do lift $ infoM "Pontarius.Xmpp" $ "Bound JID: " ++ show jid' _ <- lift $ withStream ( do modify $ \s -> s{streamJid = Just jid'}) c return jid' _ -> do lift $ errorM "Pontarius.Xmpp" $ "xmppBind: JID could not be unpickled from: " ++ show b throwError $ XmppOtherFailure _ -> do lift $ errorM "Pontarius.XMPP" "xmppBind: IQ error received." throwError XmppOtherFailure where -- Extracts the character data in the `jid' element. xpJid' :: PU [Node] Jid xpJid' = xpBind $ xpElemNodes jidName (xpContent xpJid) jidName = "{urn:ietf:params:xml:ns:xmpp-bind}jid" -- A `bind' element pickler. xpBind :: PU [Node] b -> PU [Node] b xpBind c = xpElemNodes "{urn:ietf:params:xml:ns:xmpp-bind}bind" c sessionXml :: Element sessionXml = pickleElem (xpElemBlank "{urn:ietf:params:xml:ns:xmpp-session}session") () -- 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 :: Stream -> IO Bool startSession con = do debugM "Pontarius.XMPP" "startSession: Pushing `session' IQ set stanza..." answer <- pushIQ "session" Nothing Set Nothing sessionXml con case answer of Left e -> do errorM "Pontarius.XMPP" $ "startSession: Error stanza received (" ++ (show e) ++ ")" return False Right _ -> do debugM "Pontarius.XMPP" "startSession: Result stanza received." return True