cacophony-0.7.0: examples/echo-server/Server.hs
{-# LANGUAGE OverloadedStrings, RecordWildCards, GADTs,
ScopedTypeVariables #-}
module Server
( startServer
) where
import Control.AutoUpdate (mkAutoUpdate, defaultUpdateSettings, updateAction)
import Control.Exception (handle)
import Control.Monad (void)
import Data.Bits ((.&.), shiftR)
import Data.ByteString (ByteString, pack, length)
import Data.ByteString.Base16 (encode)
import qualified Data.ByteString.Char8 as C8 (pack)
import Data.IORef
import Data.Monoid ((<>))
import Network.Simple.TCP
import Prelude hiding (log, length)
import System.Timeout
import Crypto.Noise
import Crypto.Noise.Cipher
import Crypto.Noise.DH
import Crypto.Noise.Hash
import Data.ByteArray.Extend hiding (length)
import Handshakes
import Log
import Parse
import Types
mkHandshakeKeys :: forall d. DH d
=> ScrubbedBytes
-> Bool
-> ServerOpts
-> DHType d
-> IO (HandshakeKeys d)
mkHandshakeKeys pro psk ServerOpts{..} DTCurve25519 = do
lek <- dhGenKey :: IO (KeyPair d)
return
HandshakeKeys { hkPrologue = pro
, hkPSK = if psk then Just soPSK else Nothing
, hkLocalStatic = soLocal25519
, hkLocalEphemeral = lek
, hkRemoteStatic = soRemote25519
}
mkHandshakeKeys pro psk ServerOpts{..} DTCurve448 = do
lek <- dhGenKey :: IO (KeyPair d)
return
HandshakeKeys { hkPrologue = pro
, hkPSK = if psk then Just soPSK else Nothing
, hkLocalStatic = soLocal448
, hkLocalEphemeral = lek
, hkRemoteStatic = soRemote448
}
prependLength :: ByteString
-> ByteString
prependLength msg = pack w16len <> msg
where
len = length msg
w16len = fmap fromIntegral [(len .&. 0xFF00) `shiftR` 8, len .&. 0xFF]
writeSocket :: Socket
-> ByteString
-> IO ()
writeSocket s msg = send s $ prependLength msg
mkProtocolName :: ServerOpts
-> Header
-> String
mkProtocolName ServerOpts{..} (psk, hp, WrapCipherType cipherT, WrapDHType curveT, WrapHashType hashT) =
p <> show hp <> "_" <> show curveT <> "_" <> show cipherT <> "_" <> show hashT
where
p = if psk then "NoisePSK_" else "Noise_"
messageLoop :: (Cipher c, DH d, Hash h)
=> IORef ByteString
-> Socket
-> NoiseState c d h
-> IO ()
messageLoop bufRef sock = loop
where
loop ns = do
maybeMessage <- parseSocket bufRef sock messageParser
case maybeMessage of
Nothing -> return ()
Just m -> do
let (pt, ns') = either (error . show) id $ readMessage ns m
(ct, ns'') = either (error . show) id $ writeMessage ns' pt
writeSocket sock ct
loop ns''
startServer :: ServerOpts
-> IO ()
startServer opts@ServerOpts{..} = do
logHandle <- maybe openStdOut openLog soLogFile
au <- mkAutoUpdate defaultUpdateSettings { updateAction = getDateTime }
let logEx = logException logHandle au
log = logMsg logHandle au
serve HostAny soPort $ \(sock, ip) -> handle (logEx ip) $ do
log ip "connection established"
leftoverBufRef <- newIORef ""
th <- timeout 60000000 $ parseSocket leftoverBufRef sock headerParser
case th of
Nothing -> log ip "timeout waiting for header"
Just maybeHeader ->
case maybeHeader of
Nothing -> do
send sock "failed to parse header\n"
log ip "failed to parse header"
Just h@(psk, hp, WrapCipherType cipherT, WrapDHType curveT, WrapHashType hashT) -> do
log ip $ "client selected " <> C8.pack (mkProtocolName opts h)
hk <- mkHandshakeKeys (convert . serializeHeader $ h) psk opts curveT
log ip $ "private ephemeral: " <> (encode . convert . dhSecToBytes . fst . hkLocalEphemeral) hk
let ns = mkNoiseState hk hp ResponderRole cipherT hashT
void . timeout 60000000 $ messageLoop leftoverBufRef sock ns
log ip "connection closed"