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 +27/−0
- Network/Connection.hs +190/−0
- Network/Connection/Types.hs +87/−0
- README.md +81/−0
- Setup.hs +2/−0
- connection.cabal +44/−0
+ 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