pipes-network-tls-0.1.0.0: src/Control/Proxy/TCP/TLS.hs
-- | This module exports functions that allow you to use TLS-secured
-- TCP connections as streams, as well as utilities to connect to a
-- TLS-enabled TCP server or running your own.
--
-- If you need to safely connect to a TLS-enabled TCP server or run your own
-- /within/ a pipes pipeline, then you /must/ use the functions exported from
-- the module "Control.Proxy.TCP.TLS.Safe" instead.
--
-- This module re-exports many functions and types from "Network.Simple.TCP.TLS"
-- module in the @network-simple@ package. You might refer to that module for
-- more documentation.
module Control.Proxy.TCP.TLS (
-- * Client side
-- $client-side
S.connect
, S.ClientSettings
, S.getDefaultClientSettings
, S.makeClientSettings
-- * Server side
-- $server-side
, S.serve
, S.ServerSettings
, S.makeServerSettings
-- ** Listening
, S.listen
-- ** Accepting
, S.accept
, S.acceptFork
-- * TLS context streams
-- $socket-streaming
, contextReadS
, contextWriteD
-- ** Timeouts
-- $socket-streaming-timeout
, contextReadTimeoutS
, contextWriteTimeoutD
-- * Exports
, S.HostPreference(..)
, S.Credential(..)
, Timeout(..)
) where
import Control.Monad.Trans.Class
import qualified Control.Proxy as P
import Control.Proxy.TCP (Timeout(..))
import qualified Control.Proxy.Trans.Either as PE
import qualified Data.ByteString as B
import Data.Monoid
import qualified Network.Simple.TCP.TLS as S
import qualified Network.TLS as T
import System.Timeout (timeout)
--------------------------------------------------------------------------------
-- $client-side
--
-- Here's how you could run a simple TLS-secured TCP client:
--
-- > import Control.Proxy.TCP.TLS
-- >
-- > settings <- getDefaultClientSettings
-- > connect settings "www.example.org" "443" $ \(tlsCtx, remoteAddr) -> do
-- > putStrLn $ "Secure connection established to " ++ show remoteAddr
-- > -- now you may use tlsCtx as you please within this scope, possibly with
-- > -- the contextReadS or contextWriteD proxies explained below.
--------------------------------------------------------------------------------
-- $server-side
--
-- Here's how you could run a simple TLS-secured TCP server that handles in
-- different threads each incoming connection to port @4433@ at hostname
-- @example.org@. You will need a X509 certificate and a private key appropiate
-- to be used at that hostname.
--
-- > import Control.Proxy.TCP.TLS
-- > import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
-- >
-- > cert <- fileReadCertificate "~/example.org.crt"
-- > pkey <- fileReadPrivateKey "~/example.org.key"
-- > let cred = Credential cert pkey []
-- > settings = makeServerSettings cred Nothing
-- >
-- > serve settings (Host "example.org") "4433" $ \(tlsCtx, remoteAddr) -> do
-- > putStrLn $ "Secure connection established from " ++ show remoteAddr
-- > -- now you may use tlsCtx as you please within this scope, possibly with
-- > -- the contextReadS or contextWriteD proxies explained below.
--
-- If you need more control on the way your server runs, then you can use more
-- advanced functions such as 'listen', 'accept' and 'acceptFork'.
--------------------------------------------------------------------------------
-- $socket-streaming
--
-- Once you have an established TLS connection 'T.Context', then you can use the
-- following 'P.Proxy's to interact with the other connection end using streams.
-- | Receives decrypted bytes from the remote end, sending them downstream.
--
-- Up to @16384@ decrypted bytes will be received at once. The TLS connection is
-- automatically renegotiated if a /ClientHello/ message is received.
--
-- If the remote peer closes its side of the connection or EOF is reached,
-- this proxy returns.
contextReadS
:: P.Proxy p
=> T.Context -- ^Established TLS connection context.
-> () -> P.Producer p B.ByteString IO ()
contextReadS ctx = P.runIdentityK loop where
loop () = do
mbs <- lift (S.recv ctx)
case mbs of
Just bs -> P.respond bs >>= loop
Nothing -> return ()
{-# INLINABLE contextReadS #-}
-- | Encrypts and sends to the remote end the bytes received from upstream,
-- then forwards such same bytes downstream.
--
-- If the remote peer closes its side of the connection, this proxy returns.
--
-- Requests from downstream are forwarded upstream.
contextWriteD
:: P.Proxy p
=> T.Context -- ^Established TLS connection context.
-> x -> p x B.ByteString x B.ByteString IO r
contextWriteD ctx = P.runIdentityK loop where
loop x = do
a <- P.request x
lift (S.send ctx a)
P.respond a >>= loop
{-# INLINABLE contextWriteD #-}
--------------------------------------------------------------------------------
-- $socket-streaming-timeout
--
-- These proxies behave like the similarly named ones above, except they support
-- timing out the interaction with the remote end.
-- | Like 'contextReadS', except it throws a 'Timeout' exception in the
-- 'PE.EitherP' proxy transformer if receiving data from the remote end takes
-- more time than specified.
contextReadTimeoutS
:: P.Proxy p
=> Int -- ^Timeout in microseconds (1/10^6 seconds).
-> T.Context -- ^Established TLS connection context.
-> () -> P.Producer (PE.EitherP Timeout p) B.ByteString IO ()
contextReadTimeoutS wait ctx = loop where
loop () = do
mmbs <- lift (timeout wait (S.recv ctx))
case mmbs of
Just (Just bs) -> P.respond bs >>= loop
Just Nothing -> return ()
Nothing -> PE.throw ex
ex = Timeout $ "contextReadTimeoutS: " <> show wait <> " microseconds."
{-# INLINABLE contextReadTimeoutS #-}
-- | Like 'contextWriteD', except it throws a 'Timeout' exception in the
-- 'PE.EitherP' proxy transformer if sending data to the remote end takes
-- more time than specified.
contextWriteTimeoutD
:: P.Proxy p
=> Int -- ^Timeout in microseconds (1/10^6 seconds).
-> T.Context -- ^Established TLS connection context.
-> x -> (PE.EitherP Timeout p) x B.ByteString x B.ByteString IO r
contextWriteTimeoutD wait ctx = loop where
loop x = do
a <- P.request x
m <- lift (timeout wait (S.send ctx a))
case m of
Just () -> P.respond a >>= loop
Nothing -> PE.throw ex
ex = Timeout $ "contextWriteTimeoutD: " <> show wait <> " microseconds."
{-# INLINABLE contextWriteTimeoutD #-}