crypton-connection (empty) → 0.3.1
raw patch · 7 files changed
+690/−0 lines, 7 filesdep +basedep +basementdep +bytestringsetup-changed
Dependencies added: base, basement, bytestring, containers, crypton-x509, crypton-x509-store, crypton-x509-system, crypton-x509-validation, data-default-class, network, socks, tls
Files
- CHANGELOG.md +7/−0
- LICENSE +27/−0
- Network/Connection.hs +427/−0
- Network/Connection/Types.hs +100/−0
- README.md +83/−0
- Setup.hs +2/−0
- crypton-connection.cabal +44/−0
+ CHANGELOG.md view
@@ -0,0 +1,7 @@+## Version 0.2.1 (16 April 2014)++- Fix a difference between TLSSettings and TLSSettingsSimple,+ where connection would override the connection hostname and port in+ the simple case, but leave the field as is with TLSSettings.+ TLSSettings can now be used properly as template, and will be+ correctly overriden at the identification level only.
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2012-2019 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,427 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- 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(..)+ , ProxySettings(..)+ , SockSettings++ -- * Exceptions+ , LineTooLong(..)+ , HostNotResolved(..)+ , HostCannotConnect(..)++ -- * Library initialization+ , initConnectionContext+ , ConnectionContext++ -- * Connection operation+ , connectFromHandle+ , connectFromSocket+ , connectTo+ , connectionClose++ -- * Sending and receiving data+ , connectionGet+ , connectionGetExact+ , connectionGetChunk+ , connectionGetChunk'+ , connectionGetLine+ , connectionWaitForInput+ , connectionPut++ -- * TLS related operations+ , connectionSetSecure+ , connectionIsSecure+ , connectionSessionManager+ ) where++import Control.Concurrent.MVar+import Control.Monad (join)+import qualified Control.Exception as E+import qualified System.IO.Error as E (mkIOError, eofErrorType)++import qualified Network.TLS as TLS+import qualified Network.TLS.Extra as TLS++import System.X509 (getSystemCertificateStore)++import Network.Socks5 (defaultSocksConf, socksConnectWithSocket, SocksAddress(..), SocksHostAddress(..))+import Network.Socket+import qualified Network.Socket.ByteString as N++import Data.Tuple (swap)+import Data.Default.Class+import Data.Data+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC+import qualified Data.ByteString.Lazy as L++import System.Environment+import System.Timeout+import System.IO+import qualified Data.Map as M++import Network.Connection.Types++type Manager = MVar (M.Map TLS.SessionID TLS.SessionData)++-- | This is the exception raised if we reached the user specified limit for+-- the line in ConnectionGetLine.+data LineTooLong = LineTooLong deriving (Show,Typeable)++-- | Exception raised when there's no resolution for a specific host+data HostNotResolved = HostNotResolved String deriving (Show,Typeable)++-- | Exception raised when the connect failed+data HostCannotConnect = HostCannotConnect String [E.IOException] deriving (Show,Typeable)++instance E.Exception LineTooLong+instance E.Exception HostNotResolved+instance E.Exception HostCannotConnect++connectionSessionManager :: Manager -> TLS.SessionManager+connectionSessionManager mvar = TLS.SessionManager+ { TLS.sessionResume = \sessionID -> withMVar mvar (return . M.lookup sessionID)+ , TLS.sessionEstablish = \sessionID sessionData ->+ modifyMVar_ mvar (return . M.insert sessionID sessionData)+ , TLS.sessionInvalidate = \sessionID -> modifyMVar_ mvar (return . M.delete sessionID)+#if MIN_VERSION_tls(1,5,0)+ , TLS.sessionResumeOnlyOnce = \sessionID ->+ modifyMVar mvar (pure . swap . M.updateLookupWithKey (\_ _ -> Nothing) sessionID)+#endif+ }++-- | Initialize the library with shared parameters between connection.+initConnectionContext :: IO ConnectionContext+initConnectionContext = ConnectionContext <$> getSystemCertificateStore++-- | Create a final TLS 'ClientParams' according to the destination and the+-- TLSSettings.+makeTLSParams :: ConnectionContext -> ConnectionID -> TLSSettings -> TLS.ClientParams+makeTLSParams cg cid ts@(TLSSettingsSimple {}) =+ (TLS.defaultParamsClient (fst cid) portString)+ { TLS.clientSupported = def { TLS.supportedCiphers = TLS.ciphersuite_default }+ , TLS.clientShared = def+ { TLS.sharedCAStore = globalCertificateStore cg+ , TLS.sharedValidationCache = validationCache+ -- , TLS.sharedSessionManager = connectionSessionManager+ }+ }+ where validationCache+ | settingDisableCertificateValidation ts =+ TLS.ValidationCache (\_ _ _ -> return TLS.ValidationCachePass)+ (\_ _ _ -> return ())+ | otherwise = def+ portString = BC.pack $ show $ snd cid+makeTLSParams _ cid (TLSSettings p) =+ p { TLS.clientServerIdentification = (fst cid, portString) }+ where portString = BC.pack $ show $ snd cid++withBackend :: (ConnectionBackend -> IO a) -> Connection -> IO a+withBackend f conn = readMVar (connectionBackend conn) >>= f++connectionNew :: ConnectionID -> ConnectionBackend -> IO Connection+connectionNew cid backend =+ Connection <$> newMVar backend+ <*> newMVar (Just B.empty)+ <*> pure cid++-- | 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 cid $ ConnectionStream h+ withSecurity (Just tlsSettings) = tlsEstablish h (makeTLSParams cg cid tlsSettings) >>= connectionNew cid . ConnectionTLS+ cid = (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+connectFromSocket :: ConnectionContext+ -> Socket+ -> ConnectionParams+ -> IO Connection+connectFromSocket cg sock p = withSecurity (connectionUseSecure p)+ where withSecurity Nothing = connectionNew cid $ ConnectionSocket sock+ withSecurity (Just tlsSettings) = tlsEstablish sock (makeTLSParams cg cid tlsSettings) >>= connectionNew cid . ConnectionTLS+ cid = (connectionHostname p, connectionPort p)++-- | 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+ let conFct = doConnect (connectionUseSocks cParams)+ (connectionHostname cParams)+ (connectionPort cParams)+ E.bracketOnError conFct (close . fst) $ \(h, _) ->+ connectFromSocket cg h cParams+ where+ sockConnect sockHost sockPort h p = do+ (sockServ, servAddr) <- resolve' sockHost sockPort+ let sockConf = defaultSocksConf servAddr+ let destAddr = SocksAddress (SocksAddrDomainName $ BC.pack h) p+ (dest, _) <- socksConnectWithSocket sockServ sockConf destAddr+ case dest of+ SocksAddrIPV4 h4 -> return (sockServ, SockAddrInet p h4)+ SocksAddrIPV6 h6 -> return (sockServ, SockAddrInet6 p 0 h6 0)+ SocksAddrDomainName _ -> error "internal error: socks connect return a resolved address as domain name"+++ doConnect proxy h p =+ case proxy of+ Nothing -> resolve' h p+ Just (OtherProxy proxyHost proxyPort) -> resolve' proxyHost proxyPort+ Just (SockSettingsSimple sockHost sockPort) ->+ sockConnect sockHost sockPort h p+ Just (SockSettingsEnvironment envName) -> do+ -- if we can't get the environment variable or that the string cannot be parsed+ -- we connect directly.+ let name = maybe "SOCKS_SERVER" id envName+ evar <- E.try (getEnv name)+ case evar of+ Left (_ :: E.IOException) -> resolve' h p+ Right var ->+ case parseSocks var of+ Nothing -> resolve' h p+ Just (sockHost, sockPort) -> sockConnect sockHost sockPort h p++ -- Try to parse "host:port" or "host"+ -- if port is omitted then the default SOCKS port (1080) is assumed+ parseSocks :: String -> Maybe (String, PortNumber)+ parseSocks s =+ case break (== ':') s of+ (sHost, "") -> Just (sHost, 1080)+ (sHost, ':':portS) ->+ case reads portS of+ [(sPort,"")] -> Just (sHost, sPort)+ _ -> Nothing+ _ -> Nothing++ -- Try to resolve the host/port to an address (zero to many of them), then+ -- try to connect from the first address to the last, returning the first one that+ -- succeeds+ resolve' :: String -> PortNumber -> IO (Socket, SockAddr)+ resolve' host port = do+ let hints = defaultHints { addrFlags = [AI_ADDRCONFIG], addrSocketType = Stream }+ addrs <- getAddrInfo (Just hints) (Just host) (Just $ show port)+ firstSuccessful $ map tryToConnect addrs+ where+ tryToConnect addr =+ E.bracketOnError+ (socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr))+ (close)+ (\sock -> connect sock (addrAddress addr) >> return (sock, addrAddress addr))+ firstSuccessful = go []+ where+ go :: [E.IOException] -> [IO a] -> IO a+ go [] [] = E.throwIO $ HostNotResolved host+ go l@(_:_) [] = E.throwIO $ HostCannotConnect host l+ go acc (act:followingActs) = do+ er <- E.try act+ case er of+ Left err -> go (err:acc) followingActs+ Right r -> return r++-- | 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 (ConnectionSocket s) = N.sendAll s content+ doWrite (ConnectionTLS ctx) = TLS.sendData ctx $ L.fromChunks [content]++-- | Get exact count of bytes from a connection.+--+-- The size argument is the exact amount that must be returned to the user.+-- The call will wait until all data is available. Hence, it behaves like+-- 'B.hGet'.+--+-- On end of input, 'connectionGetExact' will throw an 'E.isEOFError'+-- exception.+connectionGetExact :: Connection -> Int -> IO ByteString+connectionGetExact conn x = loop B.empty 0+ where loop bs y+ | y == x = return bs+ | otherwise = do+ next <- connectionGet conn (x - y)+ loop (B.append bs next) (y + (B.length next))++-- | Get some bytes from a connection.+--+-- The size argument is just the maximum that could be returned to the user.+-- The call will return as soon as there's data, even if there's less+-- than requested. Hence, it behaves like 'B.hGetSome'.+--+-- On end of input, 'connectionGet' returns 0, but subsequent calls will throw+-- an 'E.isEOFError' exception.+connectionGet :: Connection -> Int -> IO ByteString+connectionGet conn size+ | size < 0 = fail "Network.Connection.connectionGet: size < 0"+ | size == 0 = return B.empty+ | otherwise = connectionGetChunkBase "connectionGet" conn $ B.splitAt size++-- | Get the next block of data from the connection.+connectionGetChunk :: Connection -> IO ByteString+connectionGetChunk conn =+ connectionGetChunkBase "connectionGetChunk" conn $ \s -> (s, B.empty)++-- | Like 'connectionGetChunk', but return the unused portion to the buffer,+-- where it will be the next chunk read.+connectionGetChunk' :: Connection -> (ByteString -> (a, ByteString)) -> IO a+connectionGetChunk' = connectionGetChunkBase "connectionGetChunk'"++-- | Wait for input to become available on a connection.+--+-- As with 'hWaitForInput', the timeout value is given in milliseconds. If the+-- timeout value is less than zero, then 'connectionWaitForInput' waits+-- indefinitely.+--+-- Unlike 'hWaitForInput', this function does not do any decoding, so it+-- returns true when there is /any/ available input, not just full characters.+connectionWaitForInput :: Connection -> Int -> IO Bool+connectionWaitForInput conn timeout_ms = maybe False (const True) <$> timeout timeout_ns tryGetChunk+ where tryGetChunk = connectionGetChunkBase "connectionWaitForInput" conn $ \buf -> ((), buf)+ timeout_ns = timeout_ms * 1000++connectionGetChunkBase :: String -> Connection -> (ByteString -> (a, ByteString)) -> IO a+connectionGetChunkBase loc conn f =+ modifyMVar (connectionBuffer conn) $ \m ->+ case m of+ Nothing -> throwEOF conn loc+ Just buf+ | B.null buf -> do+ chunk <- withBackend getMoreData conn+ if B.null chunk+ then closeBuf chunk+ else updateBuf chunk+ | otherwise ->+ updateBuf buf+ where+ getMoreData (ConnectionTLS tlsctx) = TLS.recvData tlsctx+ getMoreData (ConnectionSocket sock) = N.recv sock 1500+ getMoreData (ConnectionStream h) = B.hGetSome h (16 * 1024)++ updateBuf buf = case f buf of (a, !buf') -> return (Just buf', a)+ closeBuf buf = case f buf of (a, _buf') -> return (Nothing, a)++-- | Get the next line, using ASCII LF as the line terminator.+--+-- This throws an 'isEOFError' exception on end of input, and LineTooLong when+-- the number of bytes gathered is over the limit without a line terminator.+--+-- The actual line returned can be bigger than the limit specified, provided+-- that the last chunk returned by the underlaying backend contains a LF.+-- Put another way: Only when we need more input and limit is reached that the+-- LineTooLong exception will be raised.+--+-- An end of file will be considered as a line terminator too, if the line is+-- not empty.+connectionGetLine :: Int -- ^ Maximum number of bytes before raising a LineTooLong exception+ -> Connection -- ^ Connection+ -> IO ByteString -- ^ The received line with the LF trimmed+connectionGetLine limit conn = more (throwEOF conn loc) 0 id+ where+ loc = "connectionGetLine"+ lineTooLong = E.throwIO LineTooLong++ -- Accumulate chunks using a difference list and concatenate them+ -- when an end-of-line indicator is reached.+ more eofK !currentSz !dl =+ getChunk (\s -> let len = B.length s+ in if currentSz + len > limit+ then lineTooLong+ else more eofK (currentSz + len) (dl . (s:)))+ (\s -> done (dl . (s:)))+ (done dl)++ done :: ([ByteString] -> [ByteString]) -> IO ByteString+ done dl = return $! B.concat $ dl []++ -- Get another chunk and call one of the continuations+ getChunk :: (ByteString -> IO r) -- moreK: need more input+ -> (ByteString -> IO r) -- doneK: end of line (line terminator found)+ -> IO r -- eofK: end of file+ -> IO r+ getChunk moreK doneK eofK =+ join $ connectionGetChunkBase loc conn $ \s ->+ if B.null s+ then (eofK, B.empty)+ else case B.break (== 10) s of+ (a, b)+ | B.null b -> (moreK a, B.empty)+ | otherwise -> (doneK a, B.tail b)++throwEOF :: Connection -> String -> IO a+throwEOF conn loc =+ E.throwIO $ E.mkIOError E.eofErrorType loc' Nothing (Just path)+ where+ loc' = "Network.Connection." ++ loc+ path = let (host, port) = connectionID conn+ in host ++ ":" ++ show port++-- | Close a connection.+connectionClose :: Connection -> IO ()+connectionClose = withBackend backendClose+ where backendClose (ConnectionTLS ctx) = ignoreIOExc (TLS.bye ctx) `E.finally` TLS.contextClose ctx+ backendClose (ConnectionSocket sock) = close sock+ backendClose (ConnectionStream h) = hClose h++ ignoreIOExc action = action `E.catch` \(_ :: E.IOException) -> return ()++-- | Activate secure layer using the parameters specified.+--+-- This is typically used to negotiate a TLS channel on an already+-- established channel, e.g., supporting a STARTTLS command. It also+-- flushes 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 (connectionID connection) params)+ return (ConnectionTLS ctx, Just B.empty)+ (ConnectionSocket s) -> do ctx <- tlsEstablish s (makeTLSParams cg (connectionID connection) params)+ return (ConnectionTLS ctx, Just 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 (ConnectionSocket _) = return False+ isSecure (ConnectionTLS _) = return True++tlsEstablish :: TLS.HasBackend backend => backend -> TLS.ClientParams -> IO TLS.Context+tlsEstablish handle tlsParams = do+ ctx <- TLS.contextNew handle tlsParams+ TLS.handshake ctx+ return ctx
+ Network/Connection/Types.hs view
@@ -0,0 +1,100 @@+-- |+-- 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.Class+import Data.X509.CertificateStore+import Data.ByteString (ByteString)++import Network.Socket (PortNumber, Socket)+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+ | ConnectionSocket Socket+ | ConnectionTLS TLS.Context+++-- | Hostname This could either be a name string (punycode encoded) or an ipv4/ipv6+type HostName = String++-- | 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 ProxySettings -- ^ optional Proxy/Socks configuration.+ }++-- | Proxy settings for the connection.+--+-- OtherProxy handles specific application-level proxies like HTTP proxies.+--+-- The simple SOCKS settings is just the hostname and portnumber of the SOCKS proxy server.+--+-- That's for now the only settings in the SOCKS package,+-- socks password, or any sort of other authentications is not yet implemented.+data ProxySettings =+ SockSettingsSimple HostName PortNumber+ | SockSettingsEnvironment (Maybe String)+ | OtherProxy HostName PortNumber++type SockSettings = ProxySettings++-- | 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.ClientParams -- ^ full blown TLS Settings directly using TLS.Params. for power users.+ deriving (Show)++instance Default TLSSettings where+ def = TLSSettingsSimple False False False++type ConnectionID = (HostName, PortNumber)++-- | This opaque type represent a connection to a destination.+data Connection = Connection+ { connectionBackend :: MVar ConnectionBackend+ , connectionBuffer :: MVar (Maybe ByteString) -- ^ this is set to 'Nothing' on EOF+ , connectionID :: ConnectionID -- ^ 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,83 @@+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:+```haskell+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 = 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:+```haskell+con <- connectTo ctx $ ConnectionParams+ { connectionHostname = "www.example.com"+ , connectionPort = 4567+ , connectionUseSecure = Nothing+ , connectionUseSocks = Just $ SockSettingsSimple "localhost" 1080+ }+```+Connecting to a SSL style socket is equally easy, and need to set the UseSecure fields in ConnectionParams:+```haskell+con <- connectTo ctx $ ConnectionParams+ { connectionHostname = "www.example.com"+ , connectionPort = 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):++```haskell+{-# 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 = 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
+ crypton-connection.cabal view
@@ -0,0 +1,44 @@+Name: crypton-connection+Version: 0.3.1+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: Kazu Yamamoto <kazu@iij.ad.jp>+Synopsis: Simple and easy network connections API+Build-Type: Simple+Category: Network+stability: experimental+Cabal-Version: >=1.10+Homepage: https://github.com/kazu-yamamoto/crypton-connection+extra-source-files: README.md+ CHANGELOG.md++Library+ Default-Language: Haskell2010+ Build-Depends: base >= 3 && < 5+ , basement+ , bytestring+ , containers+ , data-default-class+ , network >= 2.6.3+ , tls >= 1.7+ , socks >= 0.6+ , crypton-x509 >= 1.5+ , crypton-x509-store >= 1.5+ , crypton-x509-system >= 1.5+ , crypton-x509-validation >= 1.5+ Exposed-modules: Network.Connection+ Other-modules: Network.Connection.Types+ ghc-options: -Wall++source-repository head+ type: git+ location: https://github.com/kazu-yamamoto/crypton-connection