Blame Network/Socks5/Command.hs

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