{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ViewPatterns #-}
module Network.Socks5
    (
    
      SocksAddress(..)
    , SocksHostAddress(..)
    , SocksReply(..)
    , SocksError(..)
    
    , module Network.Socks5.Conf
    
    , socksConnectWithSocket
    , socksConnect
    
    , socksConnectAddr
    , socksConnectName
    , socksConnectTo
    , socksConnectWith
    ) where
import Control.Monad
import Control.Exception
import qualified Data.ByteString.Char8 as BC
import Network.Socket ( sClose, Socket, SocketType(..), SockAddr(..), Family(..)
                      , socket, socketToHandle, connect)
import Network.BSD
import Network (PortID(..))
import qualified Network.Socks5.Command as Cmd
import Network.Socks5.Conf
import Network.Socks5.Types
import Network.Socks5.Lowlevel
import System.IO
socksConnectWithSocket :: Socket       
                       -> SocksConf    
                       -> SocksAddress 
                       -> IO (SocksHostAddress, PortNumber)
socksConnectWithSocket sock serverConf destAddr = do
    serverAddr <- resolveToSockAddr (socksServer serverConf)
    connect sock serverAddr
    r <- Cmd.establish sock [SocksMethodNone]
    when (r == SocksMethodNotAcceptable) $ error "cannot connect with no socks method of authentication"
    Cmd.rpc_ sock (Connect destAddr)
socksConnect :: SocksConf    
             -> SocksAddress 
             -> IO (Socket, (SocksHostAddress, PortNumber))
socksConnect serverConf destAddr =
    getProtocolNumber "tcp" >>= \proto ->
    bracketOnError (socket AF_INET Stream proto) sClose $ \sock -> do
        ret <- socksConnectWithSocket sock serverConf destAddr
        return (sock, ret)
{-# DEPRECATED socksConnectAddr "use socksConnectWithSocket" #-}
socksConnectAddr :: Socket -> SockAddr -> SockAddr -> IO ()
socksConnectAddr sock sockserver destaddr =
    socksConnectWithSocket sock
                           (defaultSocksConfFromSockAddr sockserver)
                           (socksServer $ defaultSocksConfFromSockAddr destaddr) >>
    return ()
socksConnectName :: Socket -> SockAddr -> String -> PortNumber -> IO ()
socksConnectName sock sockserver destination port = do
    socksConnectWithSocket sock
                           (defaultSocksConfFromSockAddr sockserver)
                           (SocksAddress (SocksAddrDomainName $ BC.pack destination) port)
    >> return ()
socksConnectWith :: SocksConf 
                 -> String    
                 -> PortID    
                 -> IO Socket
socksConnectWith socksConf desthost destport = do
    dport <- resolvePortID destport
    proto <- getProtocolNumber "tcp"
    bracketOnError (socket AF_INET Stream proto) sClose $ \sock -> do
        sockaddr <- resolveToSockAddr (socksServer socksConf)
        socksConnectName sock sockaddr desthost dport
        return sock
socksConnectTo :: String -> PortID -> String -> PortID -> IO Handle
socksConnectTo sockshost socksport desthost destport = do
    sport <- resolvePortID socksport
    let socksConf = defaultSocksConf sockshost sport
    sock <- socksConnectWith socksConf desthost destport
    socketToHandle sock ReadWriteMode
resolvePortID (Service serv) = getServicePortNumber serv
resolvePortID (PortNumber n) = return n
resolvePortID _              = error "unsupported unix PortID"