2016-05-16 21:55:06 +00:00
{- # LANGUAGE BangPatterns # -}
2016-05-14 23:50:16 +00:00
{- # LANGUAGE DeriveDataTypeable # -}
2016-10-09 18:54:08 +00:00
{- # LANGUAGE OverloadedStrings # -}
2016-05-14 23:50:16 +00:00
{- # OPTIONS_GHC - fno - cse # -}
2016-05-11 21:39:02 +00:00
module Main where
2016-10-09 18:54:08 +00:00
import ClassyPrelude hiding ( getArgs , head )
2016-05-14 23:50:16 +00:00
import qualified Data.ByteString.Char8 as BC
2016-10-09 18:54:08 +00:00
import Data.List ( head , ( !! ) )
2016-05-16 21:33:00 +00:00
import Data.Maybe ( fromMaybe )
2016-05-14 23:50:16 +00:00
import System.Console.CmdArgs
import System.Environment ( getArgs , withArgs )
2016-08-27 16:42:38 +00:00
import qualified Logger
2016-08-27 16:31:32 +00:00
import Tunnel
import Types
2016-05-14 23:50:16 +00:00
data WsTunnel = WsTunnel
2016-08-24 13:26:25 +00:00
{ localToRemote :: String
2016-05-28 20:32:52 +00:00
-- , remoteToLocal :: String
2016-08-24 13:26:25 +00:00
, dynamicToRemote :: String
, wsTunnelServer :: String
, udpMode :: Bool
, proxy :: String
, serverMode :: Bool
, restrictTo :: String
, verbose :: Bool
, quiet :: Bool
2018-12-26 20:26:48 +00:00
, pathPrefix :: String
2016-05-14 23:50:16 +00:00
} deriving ( Show , Data , Typeable )
2016-05-15 00:09:18 +00:00
data WsServerInfo = WsServerInfo
{ useTls :: ! Bool
, host :: ! String
, port :: ! Int
} deriving ( Show )
data TunnelInfo = TunnelInfo
{ localHost :: ! String
, localPort :: ! Int
, remoteHost :: ! String
, remotePort :: ! Int
} deriving ( Show )
2016-05-14 23:50:16 +00:00
2016-05-15 23:09:56 +00:00
2016-05-14 23:50:16 +00:00
cmdLine :: WsTunnel
cmdLine = WsTunnel
2016-05-15 00:09:18 +00:00
{ localToRemote = def &= explicit &= name " L " &= name " localToRemote " &= typ " [BIND:]PORT:HOST:PORT "
2016-08-24 13:26:25 +00:00
&= help " Listen on local and forwards traffic from remote " &= groupname " Client options "
2016-05-28 20:32:52 +00:00
-- , remoteToLocal = def &= explicit &= name "R" &= name "RemoteToLocal" &= typ "[BIND:]PORT:HOST:PORT"
-- &= help "Listen on remote and forward traffic from local"
2016-08-24 13:26:25 +00:00
, dynamicToRemote = def &= explicit &= name " D " &= name " dynamicToRemote " &= typ " [BIND:]PORT "
&= help " Listen on local and dynamically (with socks5 proxy) forwards traffic from remote " &= groupname " Client options "
2016-05-15 00:09:18 +00:00
, udpMode = def &= explicit &= name " u " &= name " udp " &= help " forward UDP traffic instead of TCP "
2018-12-26 20:26:48 +00:00
, pathPrefix = def &= explicit &= name " upgradePathPrefix "
&= help " Use a specific prefix that will show up in the http path in the upgrade request. Useful if you need to route requests server side but don't have vhosts "
&= typ " String " &= groupname " Client options "
2016-05-28 19:17:48 +00:00
, proxy = def &= explicit &= name " p " &= name " httpProxy "
2016-06-05 20:13:09 +00:00
&= help " If set, will use this proxy to connect to the server " &= typ " USER:PASS@HOST:PORT "
2016-05-28 20:32:52 +00:00
, wsTunnelServer = def &= argPos 0 &= typ " ws[s]://wstunnelServer[:port] "
2016-05-14 23:50:16 +00:00
2016-05-15 00:09:18 +00:00
, serverMode = def &= explicit &= name " server "
&= help " Start a server that will forward traffic for you " &= groupname " Server options "
, restrictTo = def &= explicit &= name " r " &= name " restrictTo "
&= help " Accept traffic to be forwarded only to this service " &= typ " HOST:PORT "
2016-06-01 14:24:16 +00:00
, verbose = def &= groupname " Common options " &= help " Print debug information "
2016-06-01 20:01:23 +00:00
, quiet = def &= help " Print only errors "
2016-05-15 00:09:18 +00:00
} &= summary ( " Use the websockets protocol to tunnel {TCP,UDP} traffic \ n "
2016-05-14 23:50:16 +00:00
++ " wsTunnelClient <---> wsTunnelServer <---> RemoteHost \ n "
++ " Use secure connection (wss://) to bypass proxies "
)
&= helpArg [ explicit , name " help " , name " h " ]
toPort :: String -> Int
2018-12-28 08:48:50 +00:00
toPort " stdio " = 0
2016-05-14 23:50:16 +00:00
toPort str = case readMay str of
Just por -> por
2016-10-09 18:54:08 +00:00
Nothing -> error $ " Invalid port number ` " ++ str ++ " ` "
2016-05-14 23:50:16 +00:00
parseServerInfo :: WsServerInfo -> String -> WsServerInfo
2016-05-15 00:09:18 +00:00
parseServerInfo server [] = server
2016-06-05 20:13:09 +00:00
parseServerInfo server ( 'w' : 's' : ':' : '/' : '/' : xs ) = parseServerInfo ( server { Main . useTls = False , Main . port = 80 } ) xs
parseServerInfo server ( 'w' : 's' : 's' : ':' : '/' : '/' : xs ) = parseServerInfo ( server { Main . useTls = True , Main . port = 443 } ) xs
parseServerInfo server ( ':' : prt ) = server { Main . port = toPort prt }
parseServerInfo server hostPath = parseServerInfo ( server { Main . host = takeWhile ( /= ':' ) hostPath } ) ( dropWhile ( /= ':' ) hostPath )
2016-05-14 23:50:16 +00:00
parseTunnelInfo :: String -> TunnelInfo
parseTunnelInfo str = mk $ BC . unpack <$> BC . split ':' ( BC . pack str )
where
2016-05-28 13:14:55 +00:00
mk [ lPort , host , rPort ] = TunnelInfo { localHost = " 127.0.0.1 " , Main . localPort = toPort lPort , remoteHost = host , remotePort = toPort rPort }
mk [ bind , lPort , host , rPort ] = TunnelInfo { localHost = bind , Main . localPort = toPort lPort , remoteHost = host , remotePort = toPort rPort }
2016-05-15 00:09:18 +00:00
mk _ = error $ " Invalid tunneling information ` " ++ str ++ " `, please use format [BIND:]PORT:HOST:PORT "
2016-05-14 23:50:16 +00:00
2016-05-28 19:17:48 +00:00
parseRestrictTo :: String -> ( ( ByteString , Int ) -> Bool )
2016-05-16 21:33:00 +00:00
parseRestrictTo " " = const True
2016-05-16 21:55:06 +00:00
parseRestrictTo str = let ( ! h , ! p ) = fromMaybe ( error " Invalid Parameter restart " ) parse
in ( \ ( ! hst , ! port ) -> hst == h && port == p )
2016-05-16 21:33:00 +00:00
where
parse = do
let ret = BC . unpack <$> BC . split ':' ( BC . pack str )
guard ( length ret == 2 )
portNumber <- readMay $ ret !! 1 :: Maybe Int
2016-05-16 21:55:06 +00:00
return ( BC . pack ( head ret ) , portNumber )
2016-05-16 21:33:00 +00:00
2016-06-05 20:13:09 +00:00
parseProxyInfo :: String -> Maybe ProxySettings
2016-05-28 19:17:48 +00:00
parseProxyInfo str = do
2016-06-05 20:13:09 +00:00
let ret = BC . split ':' ( BC . pack str )
guard ( length ret >= 2 )
if length ret == 3
then do
portNumber <- readMay $ BC . unpack $ ret !! 2 :: Maybe Int
let cred = ( head ret , head ( BC . split '@' ( ret !! 1 ) ) )
let h = BC . split '@' ( ret !! 1 ) !! 1
return $ ProxySettings ( BC . unpack h ) ( fromIntegral portNumber ) ( Just cred )
else if length ret == 2
then do
portNumber <- readMay . BC . unpack $ ret !! 1 :: Maybe Int
return $ ProxySettings ( BC . unpack $ head ret ) ( fromIntegral portNumber ) Nothing
else Nothing
2016-05-28 19:17:48 +00:00
2016-05-11 21:39:02 +00:00
main :: IO ()
2016-05-14 23:50:16 +00:00
main = do
args <- getArgs
2018-12-26 20:26:48 +00:00
cfg' <- if null args then withArgs [ " --help " ] ( cmdArgs cmdLine ) else cmdArgs cmdLine
let cfg = cfg' { pathPrefix = if pathPrefix cfg' == mempty then " wstunnel " else pathPrefix cfg' }
2016-05-14 23:50:16 +00:00
let serverInfo = parseServerInfo ( WsServerInfo False " " 0 ) ( wsTunnelServer cfg )
2016-08-27 16:42:38 +00:00
Logger . init ( if quiet cfg then Logger . QUIET
else if verbose cfg
then Logger . VERBOSE
else Logger . NORMAL )
2016-05-31 16:35:04 +00:00
2016-05-14 23:50:16 +00:00
if serverMode cfg
2016-10-09 18:54:08 +00:00
then putStrLn ( " Starting server with opts " <> tshow serverInfo )
2016-06-05 20:13:09 +00:00
>> runServer ( Main . useTls serverInfo ) ( Main . host serverInfo , fromIntegral $ Main . port serverInfo ) ( parseRestrictTo $ restrictTo cfg )
2016-08-24 13:26:25 +00:00
else if not $ null ( localToRemote cfg )
then let ( TunnelInfo lHost lPort rHost rPort ) = parseTunnelInfo ( localToRemote cfg )
in runClient TunnelSettings { localBind = lHost
2016-08-27 16:31:32 +00:00
, Types . localPort = fromIntegral lPort
2016-08-24 13:26:25 +00:00
, serverHost = Main . host serverInfo
, serverPort = fromIntegral $ Main . port serverInfo
, destHost = rHost
, destPort = fromIntegral rPort
2016-08-27 16:31:32 +00:00
, Types . useTls = Main . useTls serverInfo
2018-12-28 08:48:50 +00:00
, protocol = if lPort == 0 then STDIO else if udpMode cfg then UDP else TCP
2016-08-24 13:26:25 +00:00
, proxySetting = parseProxyInfo ( proxy cfg )
, useSocks = False
2018-12-26 20:26:48 +00:00
, upgradePrefix = pathPrefix cfg
2016-08-24 13:26:25 +00:00
}
2016-08-24 20:49:33 +00:00
else if not $ null ( dynamicToRemote cfg )
then let ( TunnelInfo lHost lPort _ _ ) = parseTunnelInfo $ ( dynamicToRemote cfg ) ++ " :127.0.0.1:1212 "
2016-08-24 13:26:25 +00:00
in runClient TunnelSettings { localBind = lHost
2016-08-27 16:31:32 +00:00
, Types . localPort = fromIntegral lPort
2016-08-24 13:26:25 +00:00
, serverHost = Main . host serverInfo
, serverPort = fromIntegral $ Main . port serverInfo
, destHost = " "
2016-08-24 20:49:33 +00:00
, destPort = 0
2016-08-27 16:31:32 +00:00
, Types . useTls = Main . useTls serverInfo
2016-08-24 20:49:33 +00:00
, protocol = SOCKS5
2016-08-24 13:26:25 +00:00
, proxySetting = parseProxyInfo ( proxy cfg )
, useSocks = True
2018-12-26 20:26:48 +00:00
, upgradePrefix = pathPrefix cfg
2016-08-24 13:26:25 +00:00
}
else return ()
2016-05-14 23:50:16 +00:00
putStrLn " Goodbye ! "
return ()