module Jingle.Socks5Server (start) where
import Prelude ()
import BasicPrelude
import System.IO (IOMode(ReadMode))
import Network.Socks5.Types
(SocksReply(SocksReplySuccess), SocksHostAddress(SocksAddrDomainName))
import Network.Socks5.Lowlevel
(socksListen, requestDstAddr, sendSerialized, SocksResponse(..))
import UnexceptionalIO (UIO, Unexceptional)
import qualified UnexceptionalIO as UIO
import qualified Data.ByteString as ByteString
import qualified Network.Socket as Socket
import Jingle.StoreChunks
import Util
start :: (Unexceptional m) =>
FilePath
-> [Socket.SockAddr]
-> (String -> UIO ())
-> (JingleTSID -> UIO (Maybe Int))
-> (JingleTSID -> FilePath -> UIO ())
-> m (Either IOError ())
start storePath ports logger getSize transferFinished = UIO.fromIO' (userError . show) $
forM_ ports $ \port -> do
Just family <- return $ addrToFamily port
socket <- Socket.socket family Socket.Stream 0
Socket.setSocketOption socket Socket.ReuseAddr 1
Socket.bind socket port
Socket.listen socket 5
void $ UIO.fork (threadLoop port socket)
where
addrToFamily Socket.SockAddrInet {} = Just Socket.AF_INET
addrToFamily Socket.SockAddrInet6 {} = Just Socket.AF_INET6
addrToFamily _ = Nothing
exceptionLogger :: IOError -> UIO ()
exceptionLogger = logger . show
threadLoop port socket =
afterAccept port socket =<<
UIO.fromIO' (error.show) (Socket.accept socket)
afterAccept port socket (Right (conn, _)) = do
void $ UIO.forkFinally
(UIO.fromIO' (userError . show) $ processConnection conn)
afterAfterAccept
threadLoop port socket
afterAccept port socket (Left e) = do
exceptionLogger e
UIO.unsafeFromIO $ Socket.close socket
-- Should these just get logged, or should the process die if we
-- cannot restart?
either exceptionLogger return =<<
start storePath [port] logger getSize transferFinished
afterAfterAccept (Right (Right ())) = return ()
afterAfterAccept (Right (Left e)) = exceptionLogger e
afterAfterAccept (Left e) = logger $ show e
processConnection conn = do
request <- socksListen conn
let SocksAddrDomainName addr = requestDstAddr request
let taddr = decodeUtf8 addr
let saddr = textToString taddr
size <- UIO.run $ getSize (JingleTSID taddr)
sendSerialized conn $ SocksResponse
SocksReplySuccess (SocksAddrDomainName addr) 0
h <- Socket.socketToHandle conn ReadMode
(path, _, _, _) <- UIO.runEitherIO $ storeChunks size storePath saddr
(fromIO_ $ Just <$> ByteString.hGetSome h 4096)
UIO.run $ transferFinished (JingleTSID taddr) path