cacophony-0.8.0: examples/echo-server/Main.hs
{-# LANGUAGE RecordWildCards, OverloadedStrings #-}
module Main where
import Control.Monad (forM_)
import Data.ByteString (writeFile, readFile)
import qualified Data.ByteString.Base64 as B64 (encode, decodeLenient)
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import Prelude hiding (writeFile, readFile)
import System.Console.GetOpt
import System.Directory (doesFileExist)
import System.Environment
import System.IO (stderr, hPutStr, hPutStrLn)
import Crypto.Noise.DH
import Data.ByteArray.Extend
import Server
import Types
data Options =
Options { optShowHelp :: Bool
, optLogFile :: Maybe FilePath
}
defaultOptions :: Options
defaultOptions =
Options { optShowHelp = False
, optLogFile = Nothing
}
options :: [OptDescr (Options -> Options)]
options =
[ Option ['h'] ["help"]
(NoArg (\o -> o { optShowHelp = True }))
"show help"
, Option ['l'] []
(ReqArg (\f o -> o { optLogFile = Just f }) "FILE")
"log output to file (default: stdout)"
]
parseOptions :: [String] -> Either [String] (Options, [String])
parseOptions argv =
case getOpt Permute options argv of
(o, n, []) -> Right (foldl (flip id) defaultOptions o, n)
(_, _, errs) -> Left errs
usageHeader :: String
usageHeader = "Usage: echo-server [OPTION] PORT PSK_FILE"
processOptions :: (Options, [String]) -> IO ()
processOptions (Options{..}, [port, pskFile]) = do
local25519 <- processPrivateKey "server_key_25519"
local448 <- processPrivateKey "server_key_448"
remote25519 <- readPublicKey "client_key_25519.pub"
remote448 <- readPublicKey "client_key_448.pub"
psk <- readPSK pskFile
startServer ServerOpts { soLogFile = optLogFile
, soPort = port
, soPSK = psk
, soLocal25519 = local25519
, soRemote25519 = remote25519
, soLocal448 = local448
, soRemote448 = remote448
}
processOptions _ = do
hPutStrLn stderr "error: a port and base64 encoded PSK file must be specified"
hPutStr stderr $ usageInfo usageHeader options
readPrivateKey :: DH d => FilePath -> IO (KeyPair d)
readPrivateKey f = fromMaybe (error $ "error importing " <> f) . dhBytesToPair . convert <$> readFile f
readPublicKey :: DH d => FilePath -> IO (PublicKey d)
readPublicKey f = (fromMaybe (error $ "error importing " <> f) . dhBytesToPub . convert . B64.decodeLenient) <$> readFile f
readPSK :: FilePath -> IO ScrubbedBytes
readPSK f = convert . B64.decodeLenient <$> readFile f
genAndWriteKey :: DH d => FilePath -> IO (KeyPair d)
genAndWriteKey f = do
pair@(sec, pub) <- dhGenKey
writeFile f $ (convert . dhSecToBytes) sec
writeFile (f <> ".pub") $ (B64.encode . convert . dhPubToBytes) pub
return pair
processPrivateKey :: DH d => FilePath -> IO (KeyPair d)
processPrivateKey f = do
exists <- doesFileExist f
if exists
then readPrivateKey f
else genAndWriteKey f
main :: IO ()
main = do
argv <- getArgs
let opts = parseOptions argv
case opts of
Left errs -> do
forM_ errs (hPutStr stderr)
hPutStr stderr $ usageInfo usageHeader options
Right o ->
if optShowHelp . fst $ o
then putStr $ usageInfo usageHeader options
else processOptions o