packages feed

connection (empty) → 0.1.0

raw patch · 6 files changed

+431/−0 lines, 6 filesdep +basedep +bytestringdep +certificatesetup-changed

Dependencies added: base, bytestring, certificate, containers, cprng-aes, data-default, network, socks, tls, tls-extra

Files

+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2012 Vincent Hanquez <vincent@snarc.org>++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ Network/Connection.hs view
@@ -0,0 +1,190 @@+-- |+-- Module      : Network.Connection+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : portable+--+-- Simple connection abstraction+--+module Network.Connection+    (+    -- * Type for a connection+      Connection+    , connectionID+    , ConnectionParams(..)+    , TLSSettings(..)+    , SockSettings(..)++    -- * Library initialization+    , initConnectionContext+    , ConnectionContext++    -- * Connection operation+    , connectFromHandle+    , connectTo+    , connectionClose++    -- * Sending and receiving data+    , connectionGet+    , connectionGetChunk+    , connectionPut++    -- * TLS related operation+    , connectionSetSecure+    , connectionIsSecure+    ) where++import Control.Applicative+import Control.Concurrent.MVar++import qualified Network.TLS as TLS+import qualified Network.TLS.Extra as TLS++import System.Certificate.X509 (getSystemCertificateStore)++import Network.Socks5+import qualified Network as N++import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L++import qualified Crypto.Random.AESCtr as RNG++import System.IO+import qualified Data.Map as M++import Network.Connection.Types++type Manager = MVar (M.Map TLS.SessionID TLS.SessionData)+data ConnectionSessionManager = ConnectionSessionManager Manager++instance TLS.SessionManager ConnectionSessionManager where+    sessionResume (ConnectionSessionManager mvar) sessionID =+        withMVar mvar (return . M.lookup sessionID)+    sessionEstablish (ConnectionSessionManager mvar) sessionID sessionData =+        modifyMVar_ mvar (return . M.insert sessionID sessionData)+    sessionInvalidate (ConnectionSessionManager mvar) sessionID =+        modifyMVar_ mvar (return . M.delete sessionID)++-- | Initialize the library with shared parameters between connection.+initConnectionContext :: IO ConnectionContext+initConnectionContext = ConnectionContext <$> getSystemCertificateStore++makeTLSParams :: ConnectionContext -> TLSSettings -> TLS.Params+makeTLSParams cg ts@(TLSSettingsSimple {}) =+    TLS.defaultParamsClient+        { TLS.pConnectVersion    = TLS.TLS11+        , TLS.pAllowedVersions   = [TLS.TLS10,TLS.TLS11,TLS.TLS12]+        , TLS.pCiphers           = TLS.ciphersuite_all+        , TLS.pCertificates      = []+        , TLS.onCertificatesRecv = if settingDisableCertificateValidation ts+                                       then const $ return TLS.CertificateUsageAccept+                                       else TLS.certificateVerifyChain (globalCertificateStore cg)+        }+makeTLSParams _ (TLSSettings p) = p++withBackend :: (ConnectionBackend -> IO a) -> Connection -> IO a+withBackend f conn = modifyMVar (connectionBackend conn) (\b -> f b >>= \a -> return (b,a))++withBuffer :: (ByteString -> IO (ByteString, b)) -> Connection -> IO b+withBuffer f conn = modifyMVar (connectionBuffer conn) f++connectionNew :: ConnectionParams -> ConnectionBackend -> IO Connection+connectionNew p backend = Connection <$> newMVar backend <*> newMVar B.empty <*> pure (connectionHostname p, connectionPort p)++-- | Use an already established handle to create a connection object.+--+-- if the TLS Settings is set, it will do the handshake with the server.+-- The SOCKS settings have no impact here, as the handle is already established+connectFromHandle :: ConnectionContext+                  -> Handle+                  -> ConnectionParams+                  -> IO Connection+connectFromHandle cg h p = withSecurity (connectionUseSecure p)+    where withSecurity Nothing            = connectionNew p $ ConnectionStream h+          withSecurity (Just tlsSettings) = tlsEstablish h (makeTLSParams cg tlsSettings) >>= connectionNew p . ConnectionTLS++-- | connect to a destination using the parameter+connectTo :: ConnectionContext -- ^ The global context of this connection.+          -> ConnectionParams  -- ^ The parameters for this connection (where to connect, and such).+          -> IO Connection     -- ^ The new established connection on success.+connectTo cg cParams = do+        h <- conFct (connectionHostname cParams) (N.PortNumber $ connectionPort cParams)        +        connectFromHandle cg h cParams+    where+        conFct = case connectionUseSocks cParams of+                      Nothing                       -> N.connectTo+                      Just (SockSettingsSimple h p) -> socksConnectTo h (N.PortNumber p)++-- | Put a block of data in the connection.+connectionPut :: Connection -> ByteString -> IO ()+connectionPut connection content = withBackend doWrite connection+    where doWrite (ConnectionStream h) = B.hPut h content >> hFlush h+          doWrite (ConnectionTLS ctx)  = TLS.sendData ctx $ L.fromChunks [content]++-- | Get some bytes from a connection.+--+-- The size argument is just the maximum that could be returned to the user,+-- however the call will returns as soon as there's data, even if there's less+-- data than expected.+connectionGet :: Connection -> Int -> IO ByteString+connectionGet con size = withBuffer getData con+    where getData buf+                | B.null buf           = do chunk <- withBackend getMoreData con+                                            let (ret, remain) = B.splitAt size chunk+                                            return (remain, ret)+                | B.length buf >= size = let (ret, remain) = B.splitAt size buf+                                          in return (remain, ret)+                | otherwise            = return (B.empty, buf)+          getMoreData (ConnectionTLS tlsctx) = TLS.recvData tlsctx+          getMoreData (ConnectionStream h)   = hWaitForInput h (-1) >> B.hGetNonBlocking h (16 * 1024)++-- | Get the next block of data from the connection.+connectionGetChunk :: Connection -> IO ByteString+connectionGetChunk con = withBuffer getData con+    where getData buf+                | B.null buf = withBackend getMoreData con >>= \chunk -> return (B.empty, chunk)+                | otherwise  = return (B.empty, buf)+          getMoreData (ConnectionTLS tlsctx) = TLS.recvData tlsctx+          getMoreData (ConnectionStream h)   = hWaitForInput h (-1) >> B.hGetNonBlocking h (16 * 1024)++-- | Close a connection.+connectionClose :: Connection -> IO ()+connectionClose = withBackend backendClose+    where backendClose (ConnectionTLS ctx)  = TLS.bye ctx >> TLS.contextClose ctx+          backendClose (ConnectionStream h) = hClose h++-- | Activate secure layer using the parameters specified.+-- +-- This is typically used to negociate a TLS channel on an already+-- establish channel, e.g. supporting a STARTTLS command. it also+-- flush the received buffer to prevent application confusing+-- received data before and after the setSecure call.+-- +-- If the connection is already using TLS, nothing else happens.+connectionSetSecure :: ConnectionContext+                    -> Connection+                    -> TLSSettings+                    -> IO ()+connectionSetSecure cg connection params =+    modifyMVar_ (connectionBuffer connection) $ \b ->+    modifyMVar (connectionBackend connection) $ \backend ->+        case backend of+            (ConnectionStream h) -> do ctx <- tlsEstablish h (makeTLSParams cg params)+                                       return (ConnectionTLS ctx, B.empty)+            (ConnectionTLS _)    -> return (backend, b)++-- | Returns if the connection is establish securely or not.+connectionIsSecure :: Connection -> IO Bool+connectionIsSecure conn = withBackend isSecure conn+    where isSecure (ConnectionStream _) = return False+          isSecure (ConnectionTLS _)    = return True++tlsEstablish :: Handle -> TLS.TLSParams -> IO TLS.Context+tlsEstablish handle tlsParams = do+    rng <- RNG.makeSystem+    ctx <- TLS.contextNewOnHandle handle tlsParams rng+    TLS.handshake ctx+    return ctx
+ Network/Connection/Types.hs view
@@ -0,0 +1,87 @@+-- |+-- Module      : Network.Connection.Types+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : portable+--+-- connection types+--+module Network.Connection.Types+    where++import Control.Concurrent.MVar (MVar)++import Data.Default+import Data.CertificateStore+import Data.ByteString (ByteString)++import Network.BSD (HostName)+import Network.Socket (PortNumber)+import qualified Network.TLS as TLS++import System.IO (Handle)++-- | Simple backend enumeration, either using a raw connection or a tls connection.+data ConnectionBackend = ConnectionStream Handle+                       | ConnectionTLS TLS.Context++-- | Connection Parameters to establish a Connection.+--+-- The strict minimum is an hostname and the port.+--+-- If you need to establish a TLS connection, you should make sure+-- connectionUseSecure is correctly set.+--+-- If you need to connect through a SOCKS, you should make sure+-- connectionUseSocks is correctly set.+data ConnectionParams = ConnectionParams+    { connectionHostname   :: HostName           -- ^ host name to connect to.+    , connectionPort       :: PortNumber         -- ^ port number to connect to.+    , connectionUseSecure  :: Maybe TLSSettings  -- ^ optional TLS parameters.+    , connectionUseSocks   :: Maybe SockSettings -- ^ optional Socks configuration.+    }++-- | Socks settings for the connection.+--+-- The simple settings is just the hostname and portnumber of the proxy server.+--+-- That's for now the only settings in the SOCKS package,+-- socks password, or authentication is not yet implemented.+data SockSettings = SockSettingsSimple HostName PortNumber++-- | TLS Settings that can be either expressed as simple settings,+-- or as full blown TLS.Params settings.+--+-- Unless you need access to parameters that are not accessible through the+-- simple settings, you should use TLSSettingsSimple.+data TLSSettings+    = TLSSettingsSimple+             { settingDisableCertificateValidation :: Bool -- ^ Disable certificate verification completely,+                                                           --   this make TLS/SSL vulnerable to a MITM attack.+                                                           --   not recommended to use, but for testing.+             , settingDisableSession               :: Bool -- ^ Disable session management. TLS/SSL connections+                                                           --   will always re-established their context.+                                                           --   Not Implemented Yet.+             , settingUseServerName                :: Bool -- ^ Use server name extension. Not Implemented Yet.+             } -- ^ Simple TLS settings. recommended to use.+    | TLSSettings TLS.Params -- ^ full blown TLS Settings directly using TLS.Params. for power users.+    deriving (Show)++instance Default TLSSettings where+    def = TLSSettingsSimple False False False++-- | This opaque type represent a connection to a destination.+data Connection = Connection+    { connectionBackend :: MVar ConnectionBackend+    , connectionBuffer  :: MVar ByteString+    , connectionID      :: (HostName, PortNumber)  -- ^ return a simple tuple of the port and hostname that we're connected to.+    }++-- | Shared values (certificate store, sessions, ..) between connections+--+-- At the moment, this is only strictly needed to shared sessions and certificates+-- when using a TLS enabled connection.+data ConnectionContext = ConnectionContext+    { globalCertificateStore :: !CertificateStore+    }
+ README.md view
@@ -0,0 +1,81 @@+haskell Connection library+==========================++Simple network library for all your connection need.++Features:++- Really simple to use+- SSL/TLS+- SOCKS++Usage+-----++Connect to www.example.com on port 4567 (without socks or tls), then send a+byte, receive a single byte, print it, and close the connection:++    import qualified Data.ByteString as B+    import Network.Connection+    import Data.Default++    main = do+        ctx <- initConnectionContext+        con <- connectTo ctx $ ConnectionParams+                                  { connectionHostname  = "www.example.com"+                                  , connectionPort      = fromIntegral 4567+                                  , connectionUseSecure = Nothing+                                  , connectionUseSocks  = Nothing+                                  }+        connectionPut con (B.singleton 0xa)+        r <- connectionGet con 1+        putStrLn $ show r+        connectionClose con++Using a socks proxy is easy, we just need replacing the connectionSocks+parameter, for example connecting to the same host, but using a socks+proxy at localhost:1080:++    con <- connectTo ctx $ ConnectionParams+                           { connectionHostname  = "www.example.com"+                           , connectionPort      = fromIntegral 4567+                           , connectionUseSecure = Nothing+                           , connectionUseSocks  = Just $ SockSettingsSimple "localhost" (fromIntegral 1080)+                           }++Connecting to a SSL style socket is equally easy, and need to set the UseSecure fields in ConnectionParams:++    con <- connectTo ctx $ ConnectionParams+                           { connectionHostname  = "www.example.com"+                           , connectionPort      = fromIntegral 4567+                           , connectionUseSecure = Just def+                           , connectionUseSocks  = Nothing+                           }++And finally, you can start TLS in the middle of an insecure connection. This is great for+protocol using STARTTLS (e.g. IMAP, SMTP):++    {-# LANGUAGE OverloadedStrings #-}+    import qualified Data.ByteString as B+    import Data.ByteString.Char8 ()+    import Network.Connection+    import Data.Default++    main = do+        ctx <- initConnectionContext+        con <- connectTo ctx $ ConnectionParams+                                  { connectionHostname  = "www.example.com"+                                  , connectionPort      = fromIntegral 4567+                                  , connectionUseSecure = Nothing+                                  , connectionUseSocks  = Nothing+                                  }+        -- talk to the other side with no TLS: says hello and starttls+        connectionPut con "HELLO\n"+        connectionPut con "STARTTLS\n"++        -- switch to TLS+        connectionSetSecure ctx con def++        -- the connection is from now on using TLS, we can send secret for example+        connectionPut con "PASSWORD 123\n"+        connectionClose con
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ connection.cabal view
@@ -0,0 +1,44 @@+Name:                connection+Version:             0.1.0+Description:+    Simple network library for all your connection need.+    .+    Features: Really simple to use, SSL/TLS, SOCKS+    .+    This library provides a very simple api to create sockets+    to a destination with the choice of SSL/TLS, and SOCKS.+License:             BSD3+License-file:        LICENSE+Copyright:           Vincent Hanquez <vincent@snarc.org>+Author:              Vincent Hanquez <vincent@snarc.org>+Maintainer:          Vincent Hanquez <vincent@snarc.org>+Synopsis:            Simple and easy network connections API+Build-Type:          Simple+Category:            Network+stability:           experimental+Cabal-Version:       >=1.6+Homepage:            http://github.com/vincenthz/hs-connection+data-files:          README.md++Flag test+  Description:       Build unit test+  Default:           False++Library+  Build-Depends:     base >= 3 && < 5+                   , bytestring+                   , containers+                   , data-default+                   , network >= 2.3+                   , tls >= 1.0+                   , tls-extra >= 0.5+                   , cprng-aes+                   , socks >= 0.4+                   , certificate >= 1.3.0 && < 1.4.0+  Exposed-modules:   Network.Connection+  Other-modules:     Network.Connection.Types+  ghc-options:       -Wall++source-repository head+  type: git+  location: git://github.com/vincenthz/hs-connection