packages feed

socks5-0.6.0.0: server/Main.hs

import Data.Default
import Data.Text.Lazy qualified as LT
import Network.SOCKS5.Server
import Network.TLS
import Network.TLS.Extra.Cipher
import Options.Applicative

data Args = Args
  { host :: String,
    port :: String,
    useTLS :: Bool,
    cert :: FilePath,
    key :: FilePath,
    userPass :: [String]
  }

argsParser :: Parser Args
argsParser =
  Args
    <$> strOption
      ( long "host"
          <> short 'H'
          <> metavar "HOSTNAME"
          <> value "0.0.0.0"
          <> showDefault
          <> help "The hostname to bind the server to."
      )
    <*> strOption
      ( long "port"
          <> short 'P'
          <> metavar "PORT"
          <> value "1080"
          <> showDefault
          <> help "The port to listen on."
      )
    <*> switch
      ( long "tls"
          <> help "Enable TLS encryption. Requires --cert and --key."
      )
    <*> strOption
      ( long "cert"
          <> metavar "CERT_FILE"
          <> value "cert.pem"
          <> showDefault
          <> help "The TLS certificate file."
      )
    <*> strOption
      ( long "key"
          <> metavar "KEY_FILE"
          <> value "key.pem"
          <> showDefault
          <> help "The TLS private key file."
      )
    <*> many
      ( strOption
          ( long "user-pass"
              <> short 'u'
              <> metavar "USERNAME:PASSWORD"
              <> help "User/password pair for authentication. Can be specified multiple times."
          )
      )

parseUserPass :: String -> (LT.Text, LT.Text)
parseUserPass s = case break (== ':') s of
  (user, ':' : pass) | not (null user) && not (null pass) -> (LT.pack user, LT.pack pass)
  _ -> error $ "Invalid format for user-pass. Expected USERNAME:PASSWORD, but got: " ++ s

main :: IO ()
main = do
  args <-
    execParser $
      info
        (argsParser <**> helper)
        ( fullDesc
            <> progDesc "Run a SOCKS5 proxy server."
            <> header "socks5-server - A simple SOCKS5 proxy"
        )

  let users = map parseUserPass (userPass args)
  let serverConfig =
        ServerConfig
          { serverHost = host args,
            serverPort = port args,
            users = users
          }
  if useTLS args
    then do
      params <- credential (cert args) (key args)
      runSOCKS5ServerTLS serverConfig params
    else runSOCKS5Server serverConfig

credential :: FilePath -> FilePath -> IO ServerParams
credential cert key = do
  cred <- either error id <$> credentialLoadX509 cert key
  return
    (defaultParamsServer :: ServerParams)
      { serverShared =
          def
            { sharedCredentials = Credentials [cred]
            },
        serverSupported =
          def
            { supportedCiphers = ciphersuite_strong
            }
      }