socks (empty) → 0.1.0
raw patch · 9 files changed
+466/−0 lines, 9 filesdep +basedep +bytestringdep +cerealsetup-changed
Dependencies added: base, bytestring, cereal, network
Files
- Example.hs +42/−0
- LICENSE +27/−0
- Network/Socks5.hs +44/−0
- Network/Socks5/Command.hs +84/−0
- Network/Socks5/Types.hs +114/−0
- Network/Socks5/Wire.hs +106/−0
- README.md +21/−0
- Setup.hs +2/−0
- socks.cabal +26/−0
+ Example.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE OverloadedStrings #-}+import Network.Socks5+import Network.Socket hiding (recv)+import Network.Socket.ByteString+import Network.BSD+import Data.ByteString.Char8 ()++import System.Environment (getArgs)++main = do+ args <- getArgs+ let destinationName = case args of+ [] -> "www.google.com"+ (x:_) -> x+ -- socks server is expected to be running on localhost port 1080+ he <- getHostByName "localhost"+ let socksServerAddr = SockAddrInet 1080 (head $ hostAddresses he)++ example1 socksServerAddr destinationName+ example2 socksServerAddr destinationName+ where+ -- connect to @destName on port 80 through the socks server+ -- www.google.com get resolve on the client here and then the sockaddr is+ -- passed to socksConnectAddr+ example1 socksServerAddr destName = do+ socket <- socket AF_INET Stream defaultProtocol+ + gHost <- getHostByName destName+ let destinationAddr = SockAddrInet 80 (head $ hostAddresses gHost)+ socksConnectAddr socket socksServerAddr destinationAddr++ sendAll socket "GET / HTTP/1.0\r\n\r\n"+ recv socket 4096 >>= putStrLn . show+ sClose socket+ -- connect to @destName on port 80 through the socks server+ -- the server is doing the resolution itself+ example2 socksServerAddr destName = do+ socket <- socket AF_INET Stream defaultProtocol+ socksConnectName socket socksServerAddr destName 80+ sendAll socket "GET / HTTP/1.0\r\n\r\n"+ recv socket 4096 >>= putStrLn . show+ sClose socket
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2010-2011 Vincent Hanquez <vincent@snarc.org>++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ Network/Socks5.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ViewPatterns #-}+-- |+-- Module : Network.Socks5+-- License : BSD-style+-- Maintainer : Vincent Hanquez <vincent@snarc.org>+-- Stability : experimental+-- Portability : unknown+--+module Network.Socks5+ ( socksConnectAddr+ , socksConnectName+ ) where++import Control.Monad+import Network.Socket (Socket, SockAddr(..), PortNumber, connect)+import Network.Socks5.Command+import Network.Socks5.Types++withSocks socket sockaddr f = do+ connect socket sockaddr+ r <- socks5Establish socket [SocksMethodNone]+ when (r == SocksMethodNotAcceptable) $ error "cannot connect with no socks method of authentication"+ f++-- | connect a new socket to the socks server, and connect the stream on the server side+-- to the sockaddr specified. the sockaddr need to be SockAddrInet or SockAddrInet6.+--+-- a unix sockaddr will raises an exception.+--+-- |socket|-----sockServer----->|server|----destAddr----->|destination|+socksConnectAddr :: Socket -> SockAddr -> SockAddr -> IO ()+socksConnectAddr socket sockserver destaddr = withSocks socket sockserver $ do+ case destaddr of+ SockAddrInet p h -> socks5ConnectIPV4 socket h p >> return ()+ SockAddrInet6 p _ h _ -> socks5ConnectIPV6 socket h p >> return ()+ SockAddrUnix _ -> error "unsupported unix sockaddr type"++-- | connect a new socket to the socks server, and connect the stream to a FQDN+-- resolved on the server side.+socksConnectName :: Socket -> SockAddr -> String -> PortNumber -> IO ()+socksConnectName socket sockserver destination port = withSocks socket sockserver $ do+ _ <- socks5ConnectDomainName socket destination port+ return ()
+ Network/Socks5/Command.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ViewPatterns #-}+-- |+-- Module : Network.Socks5.Command+-- License : BSD-style+-- Maintainer : Vincent Hanquez <vincent@snarc.org>+-- Stability : experimental+-- Portability : unknown+--+module Network.Socks5.Command+ ( socks5Establish+ , socks5ConnectIPV4+ , socks5ConnectIPV6+ , socks5ConnectDomainName+ -- * lowlevel interface+ , socks5Rpc+ ) where++import Control.Applicative+import Control.Exception+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC+import Data.Serialize++import Network.Socket (Socket, PortNumber, HostAddress, HostAddress6)+import Network.Socket.ByteString++import Network.Socks5.Types+import Network.Socks5.Wire++socks5Establish :: Socket -> [SocksMethod] -> IO SocksMethod+socks5Establish socket methods = do+ sendAll socket (encode $ SocksHello methods)+ getSocksHelloResponseMethod <$> runGetDone get (recv socket 4096)++socks5ConnectIPV4 :: Socket -> HostAddress -> PortNumber -> IO (HostAddress, PortNumber)+socks5ConnectIPV4 socket hostaddr port = onReply <$> socks5Rpc socket request+ where+ request = SocksRequest+ { requestCommand = SocksCommandConnect+ , requestDstAddr = SocksAddrIPV4 hostaddr+ , requestDstPort = fromIntegral port+ }++ onReply (SocksAddrIPV4 h, p) = (h, p)+ onReply _ = error "ipv4 requested, got something different"++socks5ConnectIPV6 :: Socket -> HostAddress6 -> PortNumber -> IO (HostAddress6, PortNumber)+socks5ConnectIPV6 socket hostaddr6 port = onReply <$> socks5Rpc socket request+ where+ request = SocksRequest+ { requestCommand = SocksCommandConnect+ , requestDstAddr = SocksAddrIPV6 hostaddr6+ , requestDstPort = fromIntegral port+ }+ onReply (SocksAddrIPV6 h, p) = (h, p)+ onReply _ = error "ipv6 requested, got something different"++-- TODO: FQDN should only be ascii, maybe putting a "fqdn" data type+-- in front to make sure and make the BC.pack safe.+socks5ConnectDomainName :: Socket -> String -> PortNumber -> IO (SocksAddr, PortNumber)+socks5ConnectDomainName socket fqdn port = socks5Rpc socket $ SocksRequest+ { requestCommand = SocksCommandConnect+ , requestDstAddr = SocksAddrDomainName $ BC.pack fqdn+ , requestDstPort = fromIntegral port+ }++socks5Rpc :: Socket -> SocksRequest -> IO (SocksAddr, PortNumber)+socks5Rpc socket req = do+ sendAll socket (encode req)+ onReply <$> runGetDone get (recv socket 4096)+ where onReply res@(responseReply -> reply)+ | reply /= SocksReplySuccess = throw $ SocksError reply+ | otherwise =+ (responseBindAddr res, fromIntegral $ responseBindPort res)++runGetDone :: Show a => Get a -> IO ByteString -> IO a+runGetDone getter ioget = ioget >>= return . runGetPartial getter >>= r where+ r (Fail s) = error s+ r (Partial cont) = ioget >>= r . cont+ r (Done a b)+ | not $ B.null b = error "got too many bytes while receiving data"+ | otherwise = putStrLn (show a) >> return a
+ Network/Socks5/Types.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE DeriveDataTypeable #-}+-- |+-- Module : Network.Socks5.Types+-- License : BSD-style+-- Maintainer : Vincent Hanquez <vincent@snarc.org>+-- Stability : experimental+-- Portability : unknown+module Network.Socks5.Types+ ( SocksCommand(..)+ , SocksMethod(..)+ , SocksAddr(..)+ , SocksReply(..)+ , SocksVersionNotSupported(..)+ , SocksError(..)+ ) where++import Data.ByteString (ByteString)+import Data.Word+import Data.Data+import Network.Socket (HostAddress, HostAddress6)+import Control.Exception++data SocksCommand =+ SocksCommandConnect+ | SocksCommandBind+ | SocksCommandUdpAssociate+ | SocksCommandOther Word8+ deriving (Show,Eq,Ord)++data SocksMethod =+ SocksMethodNone+ | SocksMethodGSSAPI+ | SocksMethodUsernamePassword+ | SocksMethodOther Word8+ | SocksMethodNotAcceptable+ deriving (Show,Eq,Ord)++data SocksAddr =+ SocksAddrIPV4 HostAddress+ | SocksAddrDomainName ByteString+ | SocksAddrIPV6 HostAddress6+ deriving (Show,Eq)++data SocksReply =+ SocksReplySuccess+ | SocksReplyGeneralServerFailure+ | SocksReplyConnectionNotAllowedByRule+ | SocksReplyNetworkUnreachable+ | SocksReplyHostUnreachable+ | SocksReplyConnectionRefused+ | SocksReplyTTLExpired+ | SocksReplyCommandNotSupported+ | SocksReplyAddrTypeNotSupported+ | SocksReplyOther Word8+ deriving (Show,Eq,Ord,Data,Typeable)++data SocksVersionNotSupported = SocksVersionNotSupported+ deriving (Show,Data,Typeable)++data SocksError = SocksError SocksReply+ deriving (Show,Eq,Data,Typeable)++instance Exception SocksError+instance Exception SocksVersionNotSupported++instance Enum SocksCommand where+ toEnum 1 = SocksCommandConnect+ toEnum 2 = SocksCommandBind+ toEnum 3 = SocksCommandUdpAssociate+ toEnum w+ | w < 256 = SocksCommandOther $ fromIntegral w+ | otherwise = error "socks command is only 8 bits"+ fromEnum SocksCommandConnect = 1+ fromEnum SocksCommandBind = 2+ fromEnum SocksCommandUdpAssociate = 3+ fromEnum (SocksCommandOther w) = fromIntegral w++instance Enum SocksMethod where+ toEnum 0 = SocksMethodNone+ toEnum 1 = SocksMethodGSSAPI+ toEnum 2 = SocksMethodUsernamePassword+ toEnum 0xff = SocksMethodNotAcceptable+ toEnum w+ | w < 256 = SocksMethodOther $ fromIntegral w+ | otherwise = error "socks method is only 8 bits"+ fromEnum SocksMethodNone = 0+ fromEnum SocksMethodGSSAPI = 1+ fromEnum SocksMethodUsernamePassword = 2+ fromEnum (SocksMethodOther w) = fromIntegral w+ fromEnum SocksMethodNotAcceptable = 0xff++instance Enum SocksReply where+ fromEnum SocksReplySuccess = 0+ fromEnum SocksReplyGeneralServerFailure = 1+ fromEnum SocksReplyConnectionNotAllowedByRule = 2+ fromEnum SocksReplyNetworkUnreachable = 3+ fromEnum SocksReplyHostUnreachable = 4+ fromEnum SocksReplyConnectionRefused = 5+ fromEnum SocksReplyTTLExpired = 6+ fromEnum SocksReplyCommandNotSupported = 7+ fromEnum SocksReplyAddrTypeNotSupported = 8+ fromEnum (SocksReplyOther w) = fromIntegral w+ toEnum 0 = SocksReplySuccess+ toEnum 1 = SocksReplyGeneralServerFailure+ toEnum 2 = SocksReplyConnectionNotAllowedByRule+ toEnum 3 = SocksReplyNetworkUnreachable+ toEnum 4 = SocksReplyHostUnreachable+ toEnum 5 = SocksReplyConnectionRefused+ toEnum 6 = SocksReplyTTLExpired+ toEnum 7 = SocksReplyCommandNotSupported+ toEnum 8 = SocksReplyAddrTypeNotSupported+ toEnum w+ | w < 256 = SocksReplyOther $ fromIntegral w+ | otherwise = error "sock reply is only 8 bits"
+ Network/Socks5/Wire.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE DeriveDataTypeable #-}+-- |+-- Module : Network.Socks5.Wire+-- License : BSD-style+-- Maintainer : Vincent Hanquez <vincent@snarc.org>+-- Stability : experimental+-- Portability : unknown+module Network.Socks5.Wire+ ( SocksHello(..)+ , SocksHelloResponse(..)+ , SocksRequest(..)+ , SocksResponse(..)+ ) where++import Control.Applicative+import Control.Monad+import qualified Data.ByteString as B+import Data.Serialize++import Network.Socket (PortNumber)++import Network.Socks5.Types++data SocksHello = SocksHello { getSocksHelloMethods :: [SocksMethod] }+ deriving (Show,Eq)++data SocksHelloResponse = SocksHelloResponse { getSocksHelloResponseMethod :: SocksMethod }+ deriving (Show,Eq)++data SocksRequest = SocksRequest+ { requestCommand :: SocksCommand+ , requestDstAddr :: SocksAddr+ , requestDstPort :: PortNumber+ } deriving (Show,Eq)++data SocksResponse = SocksResponse+ { responseReply :: SocksReply+ , responseBindAddr :: SocksAddr+ , responseBindPort :: PortNumber+ } deriving (Show,Eq)++getAddr 1 = SocksAddrIPV4 <$> getWord32be+getAddr 3 = SocksAddrDomainName <$> (getWord8 >>= getByteString . fromIntegral)+getAddr 4 = SocksAddrIPV6 <$> (liftM4 (,,,) getWord32le getWord32le getWord32le getWord32le)+getAddr n = error ("cannot get unknown socket address type: " ++ show n)++putAddr (SocksAddrIPV4 h) = putWord8 1 >> putWord32host h+putAddr (SocksAddrDomainName b) = putWord8 3 >> putWord8 (fromIntegral $ B.length b) >> putByteString b+putAddr (SocksAddrIPV6 (a,b,c,d)) = putWord8 4 >> mapM_ putWord32host [a,b,c,d]++getSocksRequest 5 = do+ cmd <- toEnum . fromIntegral <$> getWord8+ _ <- getWord8+ addr <- getWord8 >>= getAddr+ port <- fromIntegral <$> getWord16be+ return $ SocksRequest cmd addr port+getSocksRequest v =+ error ("unsupported version of the protocol " ++ show v)++getSocksResponse 5 = do+ reply <- toEnum . fromIntegral <$> getWord8+ _ <- getWord8+ addr <- getWord8 >>= getAddr+ port <- fromIntegral <$> getWord16be+ return $ SocksResponse reply addr port+getSocksResponse v =+ error ("unsupported version of the protocol " ++ show v)++instance Serialize SocksHello where+ put (SocksHello ms) = do+ putWord8 5+ putWord8 $ fromIntegral $ length ms+ mapM_ (putWord8 . fromIntegral . fromEnum) ms+ get = do+ v <- getWord8+ case v of+ 5 -> getWord8 >>= flip replicateM (toEnum . fromIntegral <$> getWord8) . fromIntegral >>= return . SocksHello+ _ -> error "unsupported sock hello version"++instance Serialize SocksHelloResponse where+ put (SocksHelloResponse m) = putWord8 5 >> putWord8 (fromIntegral $ fromEnum $ m)+ get = do+ v <- getWord8+ case v of+ 5 -> SocksHelloResponse . toEnum . fromIntegral <$> getWord8+ _ -> error "unsupported sock hello response version"++instance Serialize SocksRequest where+ put req = do+ putWord8 5+ putWord8 $ fromIntegral $ fromEnum $ requestCommand req+ putWord8 0+ putAddr $ requestDstAddr req+ putWord16be $ fromIntegral $ requestDstPort req+ + get = getWord8 >>= getSocksRequest++instance Serialize SocksResponse where+ put req = do+ putWord8 5+ putWord8 $ fromIntegral $ fromEnum $ responseReply req+ putWord8 0+ putAddr $ responseBindAddr req+ putWord16be $ fromIntegral $ responseBindPort req+ get = getWord8 >>= getSocksResponse+
+ README.md view
@@ -0,0 +1,21 @@+Socks+=====++Haskell library implementation of the SOCKS 5 protocol.++TODO+----++ * more socks authentification methods: only no authentification is supported for now.+ * support of socks' bind for server to client connection (like FTP).+ * add socks4a and socks4 support.++Usage+-----++See Example.hs for really simple and straighforward example. The main api is only 2 calls:++ * socksConnectAddr which connect to a SockAddr (SockAddrInet or SockAddrInet6).+ The name resolution is left on client side.+ * socksConnectName which connect to a fully qualified domain name "www.example.com".+ The proxy server will do the name resolution.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ socks.cabal view
@@ -0,0 +1,26 @@+Name: socks+Version: 0.1.0+Description: Socks proxy (version 5) implementation.+License: BSD3+License-file: LICENSE+Copyright: Vincent Hanquez <vincent@snarc.org>+Author: Vincent Hanquez <vincent@snarc.org>+Maintainer: Vincent Hanquez <vincent@snarc.org>+Synopsis: Socks proxy (version 5) implementation.+Build-Type: Simple+Category: Network+stability: experimental+Cabal-Version: >=1.6+Homepage: http://github.com/vincenthz/hs-socks+data-files: README.md, Example.hs++Library+ Build-Depends: base >= 3 && < 5+ , bytestring+ , cereal >= 0.3+ , network >= 2.3+ Exposed-modules: Network.Socks5+ Other-modules: Network.Socks5.Wire+ Network.Socks5.Types+ Network.Socks5.Command+ ghc-options: -Wall -fno-warn-missing-signatures