Dmitry & others,

Attached is a diff implementing TLS support for haskell-xmpp, and correcting a build failure.

The support is rough but it seems to work so far.

-- John
diff -durN -x '*~' -x Test.hs orig/haskell-xmpp-1.0/haskell-xmpp.cabal haskell-xmpp-1.0/haskell-xmpp.cabal
--- orig/haskell-xmpp-1.0/haskell-xmpp.cabal	2011-01-15 16:50:14.000000000 -0600
+++ haskell-xmpp-1.0/haskell-xmpp.cabal	2012-04-16 21:03:49.125224872 -0500
@@ -32,7 +32,7 @@
 
 library
   Hs-Source-Dirs: ./src
-  Build-Depends: base > 3 && <=5, random, pretty, array, HaXml >= 1.20.2, mtl >= 1.0, network, html, polyparse, regex-compat, stm, utf8-string
+  Build-Depends: base > 3 && <=5, random, pretty, array, HaXml >= 1.20.2 && < 1.21, mtl >= 1.0, network, html, polyparse, regex-compat, stm, utf8-string, gnutls, bytestring
   Exposed-modules: Network.XMPP
                 , Network.XMPP.Sasl
                 , Network.XMPP.Core
diff -durN -x '*~' -x Test.hs orig/haskell-xmpp-1.0/src/Network/XMPP/Concurrent.hs haskell-xmpp-1.0/src/Network/XMPP/Concurrent.hs
--- orig/haskell-xmpp-1.0/src/Network/XMPP/Concurrent.hs	2011-01-15 16:50:14.000000000 -0600
+++ haskell-xmpp-1.0/src/Network/XMPP/Concurrent.hs	2012-04-16 21:46:04.405285127 -0500
@@ -36,6 +36,8 @@
 import Network.XMPP.XEP.Version 
 import Network.XMPP.IM.Presence    
 import Network.XMPP.Utils
+import qualified Network.Protocol.TLS.GNU as TLS
+import qualified Data.ByteString.Lazy.Char8 as BC8
 
 import System.IO
     
@@ -54,7 +56,7 @@
   liftIO $ forkIO $ runReaderT a (Thread in' out')
   s <- get
   liftIO $ forkIO $ loopWrite s out'
-  liftIO $ forkIO $ connPersist (handle s)
+  liftIO $ forkIO $ connPersist (transport s)
   loopRead in' 
     where 
       loopRead in' = loop $ 
@@ -105,10 +107,20 @@
     else do
       waitFor f
 
-connPersist :: Handle -> IO ()
-connPersist h = do
+connPersist :: Transport -> IO ()
+connPersist (HandleTransport h) = do
   hPutStr h " "
   putStrLn "<space added>"
   threadDelay 30000000
-  connPersist h
-    
+  connPersist (HandleTransport h)
+connPersist (TLSTransport t) = do
+  r <- TLS.runTLS t $ do
+    TLS.putBytes (BC8.pack " ")
+  case r of
+    Left x -> fail (show x)
+    Right _ -> return ()
+  putStrLn "<space added>"
+  threadDelay 30000000
+  connPersist (TLSTransport t)
+  
+
diff -durN -x '*~' -x Test.hs orig/haskell-xmpp-1.0/src/Network/XMPP/Core.hs haskell-xmpp-1.0/src/Network/XMPP/Core.hs
--- orig/haskell-xmpp-1.0/src/Network/XMPP/Core.hs	2011-01-15 16:50:14.000000000 -0600
+++ haskell-xmpp-1.0/src/Network/XMPP/Core.hs	2012-04-16 21:33:28.101267152 -0500
@@ -26,6 +26,9 @@
 import Network.XMPP.JID
 import Network.XMPP.IQ
 import Network.XMPP.Utils
+import Text.XML.HaXml.Xtract.Parse (xtract)
+import qualified Network.Protocol.TLS.GNU as TLS
+
 
 -- | Open connection to specified server and return `Stream' coming from it
 initiateStream :: Handle
@@ -50,7 +53,48 @@
      
      debug "Stream started"
      --debug $ "Observing: " ++ render (P.content m)
-     m <- xtractM "/stream:features/mechanisms/mechanism/-"
+     
+     -- First, we read stream:features
+     sf_ <- xtractM "/stream:features"
+     let sf = case sf_ of
+               [] -> error "Did not get stream:features"
+               [x] -> x
+               x -> error "Got many stream:features"
+               
+     -- Now, we look for starttls.
+     newsf <- case xtract id "/stream:features/starttls" sf of
+           [] -> do debug "Did not see starttls in features"
+                    return sf
+           x -> do debug $ "Saw starttls: " ++ show x
+                   out $ toContent $ ptag "starttls" 
+                     [xmlns "urn:ietf:params:xml:ns:xmpp-tls"]
+                     []
+                   nm <- nextM
+                   case xtract id "/proceed" nm of
+                     [] -> fail "Did not get proceed after starttls"
+                     _ -> return ()
+                   debug $ "Starting TLS"
+                   session <- liftIO $ TLS.runClient (TLS.handleTransport h) $ do
+                     TLS.setPriority [TLS.X509]
+                     TLS.setCredentials =<< TLS.certificateCredentials
+                     TLS.handshake
+                     TLS.getSession
+                   case session of
+                     Left x -> fail (show x)
+                     Right x -> do 
+                       debug $ "Got TLS session"
+                       resetStreamTLS h x
+                       debug $ "Stream reset"
+                       out $ toContent $ stream Client server
+                       startM
+                       tlssf_ <- xtractM "/stream:features"
+                       debug $ "Got new features"
+                       return $ case tlssf_ of
+                             [] -> error "Did not get stream:features"
+                             [x] -> x
+                             x -> error "Got many stream:features"
+               
+     let m = xtract id "/stream:features/mechanisms/mechanism/-" newsf
      let mechs = map getText m
      debug $ "Mechanisms: " ++ show mechs
 
diff -durN -x '*~' -x Test.hs orig/haskell-xmpp-1.0/src/Network/XMPP/Stream.hs haskell-xmpp-1.0/src/Network/XMPP/Stream.hs
--- orig/haskell-xmpp-1.0/src/Network/XMPP/Stream.hs	2011-01-15 16:50:14.000000000 -0600
+++ haskell-xmpp-1.0/src/Network/XMPP/Stream.hs	2012-04-16 21:50:42.369291734 -0500
@@ -24,6 +24,7 @@
   , withNewStream
   , withStream
   , resetStreamHandle
+  , resetStreamTLS
   , getText
   , getText_
   , loopWithPlugins
@@ -49,18 +50,29 @@
 import Network.XMPP.Types
 import Network.XMPP.UTF8
 
+import qualified Network.Protocol.TLS.GNU as TLS
+import System.IO.Unsafe(unsafeInterleaveIO)
+import qualified Data.ByteString.Lazy.Char8 as BC8
+
 -- For a definition of 'Stream' see Network.XMPP.Types.
 
 -- In the beginning, all Stream buffers are empty, and by default it is bound to stdin.
 -- Functions like 'openStreamTo' or 'openStreamViaProxyTo' could override this.
 newStream :: Stream 
-newStream = Stream { handle=stdin, idx=0, lexemes=[] }
+newStream = Stream { transport=HandleTransport stdin, idx=0, lexemes=[] }
 
 -- Main 'workhorses' for Stream are 'out', 'nextM', 'peekM' and 'selectM':
 -- | Sends message into Stream
 out :: XmppMessage -> XmppStateT ()
-out xmpp = do h <- gets handle
-              liftIO $ hPutXmpp h xmpp
+out xmpp = do h <- gets transport
+              case h of
+                HandleTransport h -> liftIO $ hPutXmpp h xmpp
+                TLSTransport t -> do let str = renderXmpp xmpp
+                                     liftIO $ putStrLn $ "TLS Sending: " ++ str
+                                     r <- liftIO $ TLS.runTLS t $ TLS.putBytes (BC8.pack . toUTF8 $ str)
+                                     case r of
+                                       Left x -> fail (show x)
+                                       Right _ -> return ()
 
 -- | Selects next messages from stream
 nextM :: XmppStateT XmppMessage
@@ -125,8 +137,49 @@
 -- | Replaces contents of the Stream with the contents
 --   coming from given handle.
 resetStreamHandle h =
-  do c <- liftIO $ hGetContents h
-     modify (\stream -> stream { handle=h , lexemes = xmlLex "stream" (fromUTF8 c) })
+  do c <- liftIO $ hGetContentsNoClose h
+     modify (\stream -> stream { transport=HandleTransport h , lexemes = xmlLex "stream" (fromUTF8 c) })
+
+-- This is inefficient, but the moment we get to TLS, we ditch it.
+hGetContentsNoClose h =
+  unsafeInterleaveIO $ do
+    c <- hGetChar h
+    next <- hGetContentsNoClose h
+    return (c:next)
+    
+
+-- | Replaces contents of the Stream with the contents
+-- coming from the TLS stream.
+resetStreamTLS h t =
+  do c <- liftIO $ getTLSStream t
+     modify (\stream -> stream { transport=TLSTransport t , lexemes = xmlLex "stream" (fromUTF8 c) })
+  where getTLSStream :: TLS.Session -> IO String
+        getTLSStream s = unsafeInterleaveIO (runner s)
+        waitForData =
+          do -- liftIO $ putStrLn "before first checkpending"
+             pending <- TLS.checkPending
+             if pending == 0
+                {-
+                then do liftIO $ putStrLn "pending was 0"
+                        liftIO (hWaitForInput h (- 1) >> return ())
+                        liftIO $ putStrLn "after wait"
+                        waitForData -}
+                then return 1
+                else return pending
+        runner :: TLS.Session -> IO String
+        runner s = 
+          do r <- TLS.runTLS s $ do
+               -- liftIO $ putStrLn "before waitForData"
+               pendingBytes <- waitForData
+               -- liftIO $ putStrLn "before getBytes"
+               lazy <- TLS.getBytes pendingBytes
+               -- liftIO $ putStrLn $ "got bytes"
+               return $ BC8.unpack lazy
+             case r of
+               Left x -> fail (show x)
+               Right y -> do remainder <- getTLSStream s
+                             return (y ++ remainder)
+               
 
 -------------------------------
 -- Basic plugin support
diff -durN -x '*~' -x Test.hs orig/haskell-xmpp-1.0/src/Network/XMPP/Types.hs haskell-xmpp-1.0/src/Network/XMPP/Types.hs
--- orig/haskell-xmpp-1.0/src/Network/XMPP/Types.hs	2011-01-15 16:50:14.000000000 -0600
+++ haskell-xmpp-1.0/src/Network/XMPP/Types.hs	2012-04-16 21:38:57.201274975 -0500
@@ -12,6 +12,7 @@
 module Network.XMPP.Types
   ( XmppMessage
   , XmppStateT
+  , Transport(..)
   , Stream(..)
   , StreamType(..)
   , Stanza(..)
@@ -29,14 +30,17 @@
     
 import Text.PrettyPrint.HughesPJ    (render, hcat)
 import qualified Text.XML.HaXml.Pretty as P (content)
+import qualified Network.Protocol.TLS.GNU as TLS
 
 import Network.XMPP.JID
 
 -- | XMPP message in the parsed form
 type XmppMessage = Content Posn    
 
+data Transport = HandleTransport Handle | TLSTransport TLS.Session
+
 -- | XMPP stream, used as a state in XmppStateT state transformer
-data Stream = Stream { handle::Handle 
+data Stream = Stream { transport :: Transport
                      -- ^ IO handle to the underlying file or socket
                      , idx :: !Int
                      -- ^ id of the next message (if needed)
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to