|
Packit |
fc2124 |
{-# LANGUAGE DeriveDataTypeable #-}
|
|
Packit |
fc2124 |
{-# LANGUAGE ViewPatterns #-}
|
|
Packit |
fc2124 |
{-# LANGUAGE CPP #-}
|
|
Packit |
fc2124 |
-- |
|
|
Packit |
fc2124 |
-- Module : Network.Socks5.Command
|
|
Packit |
fc2124 |
-- License : BSD-style
|
|
Packit |
fc2124 |
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
|
|
Packit |
fc2124 |
-- Stability : experimental
|
|
Packit |
fc2124 |
-- Portability : unknown
|
|
Packit |
fc2124 |
--
|
|
Packit |
fc2124 |
module Network.Socks5.Command
|
|
Packit |
fc2124 |
( establish
|
|
Packit |
fc2124 |
, Connect(..)
|
|
Packit |
fc2124 |
, Command(..)
|
|
Packit |
fc2124 |
, connectIPV4
|
|
Packit |
fc2124 |
, connectIPV6
|
|
Packit |
fc2124 |
, connectDomainName
|
|
Packit |
fc2124 |
-- * lowlevel interface
|
|
Packit |
fc2124 |
, rpc
|
|
Packit |
fc2124 |
, rpc_
|
|
Packit |
fc2124 |
, sendSerialized
|
|
Packit |
fc2124 |
, waitSerialized
|
|
Packit |
fc2124 |
) where
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
import Control.Applicative
|
|
Packit |
fc2124 |
import Control.Exception
|
|
Packit |
fc2124 |
import Data.ByteString (ByteString)
|
|
Packit |
fc2124 |
import qualified Data.ByteString as B
|
|
Packit |
fc2124 |
import qualified Data.ByteString.Char8 as BC
|
|
Packit |
fc2124 |
import Data.Serialize
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
import Network.Socket (Socket, PortNumber, HostAddress, HostAddress6)
|
|
Packit |
fc2124 |
import Network.Socket.ByteString
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
import Network.Socks5.Types
|
|
Packit |
fc2124 |
import Network.Socks5.Wire
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
establish :: Socket -> [SocksMethod] -> IO SocksMethod
|
|
Packit |
fc2124 |
establish socket methods = do
|
|
Packit |
fc2124 |
sendAll socket (encode $ SocksHello methods)
|
|
Packit |
fc2124 |
getSocksHelloResponseMethod <$> runGetDone get (recv socket 4096)
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
newtype Connect = Connect SocksAddress deriving (Show,Eq,Ord)
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
class Command a where
|
|
Packit |
fc2124 |
toRequest :: a -> SocksRequest
|
|
Packit |
fc2124 |
fromRequest :: SocksRequest -> Maybe a
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
instance Command SocksRequest where
|
|
Packit |
fc2124 |
toRequest = id
|
|
Packit |
fc2124 |
fromRequest = Just
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
instance Command Connect where
|
|
Packit |
fc2124 |
toRequest (Connect (SocksAddress ha port)) = SocksRequest
|
|
Packit |
fc2124 |
{ requestCommand = SocksCommandConnect
|
|
Packit |
fc2124 |
, requestDstAddr = ha
|
|
Packit |
fc2124 |
, requestDstPort = fromIntegral port
|
|
Packit |
fc2124 |
}
|
|
Packit |
fc2124 |
fromRequest req
|
|
Packit |
fc2124 |
| requestCommand req /= SocksCommandConnect = Nothing
|
|
Packit |
fc2124 |
| otherwise = Just $ Connect $ SocksAddress (requestDstAddr req) (requestDstPort req)
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
connectIPV4 :: Socket -> HostAddress -> PortNumber -> IO (HostAddress, PortNumber)
|
|
Packit |
fc2124 |
connectIPV4 socket hostaddr port = onReply <$> rpc_ socket (Connect $ SocksAddress (SocksAddrIPV4 hostaddr) port)
|
|
Packit |
fc2124 |
where onReply (SocksAddrIPV4 h, p) = (h, p)
|
|
Packit |
fc2124 |
onReply _ = error "ipv4 requested, got something different"
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
connectIPV6 :: Socket -> HostAddress6 -> PortNumber -> IO (HostAddress6, PortNumber)
|
|
Packit |
fc2124 |
connectIPV6 socket hostaddr6 port = onReply <$> rpc_ socket (Connect $ SocksAddress (SocksAddrIPV6 hostaddr6) port)
|
|
Packit |
fc2124 |
where onReply (SocksAddrIPV6 h, p) = (h, p)
|
|
Packit |
fc2124 |
onReply _ = error "ipv6 requested, got something different"
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
-- TODO: FQDN should only be ascii, maybe putting a "fqdn" data type
|
|
Packit |
fc2124 |
-- in front to make sure and make the BC.pack safe.
|
|
Packit |
fc2124 |
connectDomainName :: Socket -> String -> PortNumber -> IO (SocksHostAddress, PortNumber)
|
|
Packit |
fc2124 |
connectDomainName socket fqdn port = rpc_ socket $ Connect $ SocksAddress (SocksAddrDomainName $ BC.pack fqdn) port
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
sendSerialized :: Serialize a => Socket -> a -> IO ()
|
|
Packit |
fc2124 |
sendSerialized sock a = sendAll sock $ encode a
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
waitSerialized :: Serialize a => Socket -> IO a
|
|
Packit |
fc2124 |
waitSerialized sock = runGetDone get (getMore sock)
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
rpc :: Command a => Socket -> a -> IO (Either SocksError (SocksHostAddress, PortNumber))
|
|
Packit |
fc2124 |
rpc socket req = do
|
|
Packit |
fc2124 |
sendSerialized socket (toRequest req)
|
|
Packit |
fc2124 |
onReply <$> runGetDone get (getMore socket)
|
|
Packit |
fc2124 |
where onReply res@(responseReply -> reply) =
|
|
Packit |
fc2124 |
case reply of
|
|
Packit |
fc2124 |
SocksReplySuccess -> Right (responseBindAddr res, fromIntegral $ responseBindPort res)
|
|
Packit |
fc2124 |
SocksReplyError e -> Left e
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
rpc_ :: Command a => Socket -> a -> IO (SocksHostAddress, PortNumber)
|
|
Packit |
fc2124 |
rpc_ socket req = rpc socket req >>= either throwIO return
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
-- this function expect all the data to be consumed. this is fine for intertwined message,
|
|
Packit |
fc2124 |
-- but might not be a good idea for multi messages from one party.
|
|
Packit |
fc2124 |
runGetDone :: Serialize a => Get a -> IO ByteString -> IO a
|
|
Packit |
fc2124 |
runGetDone getter ioget = ioget >>= return . runGetPartial getter >>= r where
|
|
Packit |
fc2124 |
#if MIN_VERSION_cereal(0,4,0)
|
|
Packit |
fc2124 |
r (Fail s _) = error s
|
|
Packit |
fc2124 |
#else
|
|
Packit |
fc2124 |
r (Fail s) = error s
|
|
Packit |
fc2124 |
#endif
|
|
Packit |
fc2124 |
r (Partial cont) = ioget >>= r . cont
|
|
Packit |
fc2124 |
r (Done a b)
|
|
Packit |
fc2124 |
| not $ B.null b = error "got too many bytes while receiving data"
|
|
Packit |
fc2124 |
| otherwise = return a
|
|
Packit |
fc2124 |
|
|
Packit |
fc2124 |
getMore :: Socket -> IO ByteString
|
|
Packit |
fc2124 |
getMore socket = recv socket 4096
|