{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} ------------------------------------------------------------------------------ module Snap.Internal.Http.Server.TLS ( TLSException , initTLS , stopTLS , bindHttps , freePort , createSession , endSession , recv , send ) where ------------------------------------------------------------------------------ import Control.Exception import Data.ByteString.Char8 (ByteString) import Data.Dynamic import Foreign.C ------------------------------------------------------------------------------ #ifdef OPENSSL import Control.Monad import qualified Data.ByteString.Char8 as S import qualified Network.Socket as Socket import Network.Socket hiding ( accept , shutdown , recv , recvLen , send , socket ) import OpenSSL import OpenSSL.Session import qualified OpenSSL.Session as SSL import Prelude hiding (catch) import Unsafe.Coerce import Snap.Internal.Http.Server.Address #endif ------------------------------------------------------------------------------ import Snap.Internal.Http.Server.Backend ------------------------------------------------------------------------------ data TLSException = TLSException String deriving (Show, Typeable) instance Exception TLSException #ifndef OPENSSL ------------------------------------------------------------------------------ initTLS :: IO () initTLS = throwIO $ TLSException "TLS is not supported" ------------------------------------------------------------------------------ stopTLS :: IO () stopTLS = return () ------------------------------------------------------------------------------ bindHttps :: ByteString -> Int -> FilePath -> FilePath -> IO ListenSocket bindHttps _ _ _ _ = throwIO $ TLSException "TLS is not supported" ------------------------------------------------------------------------------ freePort :: ListenSocket -> IO () freePort _ = return () ------------------------------------------------------------------------------ createSession :: ListenSocket -> Int -> CInt -> IO () -> IO NetworkSession createSession _ _ _ _ = throwIO $ TLSException "TLS is not supported" ------------------------------------------------------------------------------ endSession :: NetworkSession -> IO () endSession _ = return () ------------------------------------------------------------------------------ send :: IO () -> IO () -> NetworkSession -> ByteString -> IO () send _ _ _ _ = return () ------------------------------------------------------------------------------ recv :: IO b -> NetworkSession -> IO (Maybe ByteString) recv _ _ = throwIO $ TLSException "TLS is not supported" #else ------------------------------------------------------------------------------ initTLS :: IO () initTLS = withOpenSSL $ return () ------------------------------------------------------------------------------ stopTLS :: IO () stopTLS = return () ------------------------------------------------------------------------------ bindHttps :: ByteString -> Int -> FilePath -> FilePath -> IO ListenSocket bindHttps bindAddress bindPort cert key = do (family, addr) <- getSockAddr bindPort bindAddress sock <- Socket.socket family Socket.Stream 0 Socket.setSocketOption sock Socket.ReuseAddr 1 Socket.bindSocket sock addr Socket.listen sock 150 ctx <- context contextSetPrivateKeyFile ctx key contextSetCertificateFile ctx cert contextSetDefaultCiphers ctx certOK <- contextCheckPrivateKey ctx when (not certOK) $ throwIO $ TLSException certificateError return $! ListenHttps sock ctx where certificateError = "OpenSSL says that the certificate " ++ "doesn't match the private key!" ------------------------------------------------------------------------------ freePort :: ListenSocket -> IO () freePort (ListenHttps sock _) = Socket.sClose sock freePort _ = return () ------------------------------------------------------------------------------ createSession :: ListenSocket -> Int -> CInt -> IO () -> IO NetworkSession createSession (ListenHttps _ ctx) recvSize socket _ = do csock <- mkSocket socket AF_INET Stream defaultProtocol Connected handle (\(e::SomeException) -> Socket.sClose csock >> throwIO e) $ do ssl <- connection ctx csock accept ssl return $! NetworkSession socket (unsafeCoerce ssl) recvSize createSession _ _ _ _ = error "can't call createSession on a ListenHttp" ------------------------------------------------------------------------------ endSession :: NetworkSession -> IO () endSession (NetworkSession _ aSSL _) = shutdown (unsafeCoerce aSSL) Unidirectional ------------------------------------------------------------------------------ send :: IO () -> IO () -> NetworkSession -> ByteString -> IO () send tickleTimeout _ (NetworkSession _ aSSL sz) bs = go bs where ssl = unsafeCoerce aSSL -- I think we have to chop the data into chunks here because HsOpenSSL -- won't; of course, blaze-builder may already be doing this for us, but I -- don't want to risk it. go !s = if S.null s then return () else do SSL.write ssl a tickleTimeout go b where (a,b) = S.splitAt sz s ------------------------------------------------------------------------------ recv :: IO b -> NetworkSession -> IO (Maybe ByteString) recv _ (NetworkSession _ aSSL recvLen) = do b <- SSL.read ssl recvLen return $! if S.null b then Nothing else Just b where ssl = unsafeCoerce aSSL #endif