/nselib/socks.lua
Lua | 359 lines | 214 code | 55 blank | 90 comment | 28 complexity | 617dd3dc2c836bc672f4184efcc4ab09 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-2.0, LGPL-2.1
- ---
- -- A smallish SOCKS version 5 proxy protocol implementation
- --
- -- @author "Patrik Karlsson <patrik@cqure.net>"
- --
- local bin = require "bin"
- local nmap = require "nmap"
- local stdnse = require "stdnse"
- local string = require "string"
- _ENV = stdnse.module("socks", stdnse.seeall)
- -- SOCKS Authentication methods
- AuthMethod = {
- NONE = 0,
- GSSAPI = 1,
- USERPASS = 2,
- }
- Request = {
-
- -- Class that handles the connection request to the server
- Connect = {
-
- -- Creates a new instance of the class
- -- @param auth_method table of requested authentication methods
- -- @return o instance on success, nil on failure
- new = function(self, auth_method)
- local o = {
- version = 5,
- auth_method = ( "table" ~= type(auth_method) and { auth_method } or auth_method )
- }
- setmetatable(o, self)
- self.__index = self
- return o
- end,
-
- -- Converts the instance to string, so that it can be sent to the
- -- server.
- -- @return string containing the raw request
- __tostring = function(self)
- local methods = ""
- for _, m in ipairs(self.auth_method) do
- methods = methods .. string.char(m)
- end
- return bin.pack("Cp", self.version, methods)
- end,
- },
-
- -- Class that handles the authentication request to the server
- Authenticate = {
-
- -- Creates a new instance of the class
- -- @param auth_method number with the requested authentication method
- -- @param creds method specific table of credentials
- -- currently only user and pass authentication is supported
- -- this method requires two fields to be present
- -- <code>username</code> and <code>password</code>
- -- @return o instance on success, nil on failure
- new = function(self, auth_method, creds)
- local o = {
- auth_method = auth_method,
- creds = creds
- }
- setmetatable(o, self)
- self.__index = self
- if ( auth_method == 2 ) then
- return o
- end
- end,
-
- -- Converts the instance to string, so that it can be sent to the
- -- server.
- -- @return string containing the raw request
- __tostring = function(self)
- -- we really don't support anything but 2, but let's pretend that
- -- we actually do
- if ( 2 == self.auth_method ) then
- local version = 1
- local username= self.creds.username or ""
- local password= self.creds.password or ""
- username = (username == "") and "\0" or username
- password = (password == "") and "\0" or password
- return bin.pack("Cpp", version, username, password)
- end
- end,
-
- }
-
- }
- Response = {
-
- -- Class that handles the connection response
- Connect = {
-
- -- Creates a new instance of the class
- -- @param data string containing the data as received over the socket
- -- @return o instance on success, nil on failure
- new = function(self, data)
- local o = { data = data }
- setmetatable(o, self)
- self.__index = self
- if ( o:parse() ) then
- return o
- end
- end,
-
- -- Parses the received data and populates member variables
- -- @return true on success, false on failure
- parse = function(self)
- if ( #self.data ~= 2 ) then
- return
- end
- local pos
- pos, self.version, self.method = bin.unpack("CC", self.data)
- return true
- end
-
- },
-
- -- Class that handles the authentication response
- Authenticate = {
-
- Status = {
- SUCCESS = 0,
- -- could be anything but zero
- FAIL = 1,
- },
-
- -- Creates a new instance of the class
- -- @param data string containing the data as received over the socket
- -- @return o instance on success, nil on failure
- new = function(self, data)
- local o = { data = data }
- setmetatable(o, self)
- self.__index = self
- if ( o:parse() ) then
- return o
- end
- end,
-
- -- Parses the received data and populates member variables
- -- @return true on success, false on failure
- parse = function(self)
- if ( #self.data ~= 2 ) then
- return
- end
- local pos
- pos, self.version, self.status = bin.unpack("CC", self.data)
- return true
- end,
-
- -- checks if the authentication was successful or not
- -- @return true on success, false on failure
- isSuccess = function(self)
- return ( self.status == self.Status.SUCCESS )
- end,
-
- }
-
- }
- -- A buffered socket implementation
- Socket =
- {
- retries = 3,
-
- -- Creates a new socket instance
- -- @param host table containing the host table
- -- @param port table containing the port table
- -- @param options table containing options, currenlty supports:
- -- <code>timeout</code> - socket timeout in ms
- -- @return o new instance of Socket
- new = function(self, host, port, options)
- local o = {
- host = host,
- port = port,
- options = options or {}
- }
- setmetatable(o, self)
- self.__index = self
- o.Socket = nmap.new_socket()
- o.Buffer = nil
- return o
- end,
-
- -- Connects the socket to the server
- -- @return status true on success false on failure
- -- @return err string containing error message on failure
- connect = function( self )
- self.Socket:set_timeout(self.options.timeout or 10000)
- return self.Socket:connect( self.host, self.port )
- end,
-
- -- Closes an open connection.
- --
- -- @return Status (true or false).
- -- @return Error code (if status is false).
- close = function( self )
- return self.Socket:close()
- end,
-
- -- Opposed to the <code>socket:receive_bytes</code> function, that returns
- -- at least x bytes, this function returns the amount of bytes requested.
- --
- -- @param count of bytes to read
- -- @return true on success, false on failure
- -- @return data containing bytes read from the socket
- -- err containing error message if status is false
- recv = function( self, count )
- local status, data
-
- self.Buffer = self.Buffer or ""
-
- if ( #self.Buffer < count ) then
- status, data = self.Socket:receive_bytes( count - #self.Buffer )
- if ( not(status) or #data < count - #self.Buffer ) then
- return false, data
- end
- self.Buffer = self.Buffer .. data
- end
-
- data = self.Buffer:sub( 1, count )
- self.Buffer = self.Buffer:sub( count + 1)
-
- return true, data
- end,
-
- -- Sends data over the socket
- --
- -- @return Status (true or false).
- -- @return Error code (if status is false).
- send = function( self, data )
- return self.Socket:send( data )
- end,
- }
- -- The main script interface
- Helper = {
-
- -- Create a new instance of the class
- -- @param host table containing the host table
- -- @param port table containing the port table
- -- @param options table containing library options, currenlty:
- -- <code>timeout</code> - socket timeout in ms
- -- @return o instance of Helper
- new = function(self, host, port, options)
- local o = { host = host, port = port, options = options }
- setmetatable(o, self)
- self.__index = self
- return o
- end,
-
- -- Get the authentication method name by number
- -- @param method number containing the authentication method
- -- @return string containing the method name or Unknown
- authNameByNumber = function(self, method)
- local methods = {
- [0] = "No authentication",
- [1] = "GSSAPI",
- [2] = "Username and password",
- }
- return methods[method] or ("Unknown method (%d)"):format(method)
- end,
-
- -- Connects to the SOCKS server
- -- @param auth_method table containing the auth. methods to request
- -- @return status true on success, false on failure
- -- @return response table containing the respons or err string on failure
- connect = function(self, auth_method)
- self.socket = Socket:new(self.host, self.port, self.options)
- local status, err = self.socket:connect()
- if ( not(status) ) then
- return status, err
- end
-
- auth_method = auth_method or {AuthMethod.NONE, AuthMethod.GSSAPI, AuthMethod.USERPASS}
- status = self.socket:send( tostring(Request.Connect:new(auth_method)) )
- if ( not(status) ) then
- self.socket:close()
- return false, "Failed to send connection request to server"
- end
-
- local status, data = self.socket:recv(2)
- if ( not(status) ) then
- self.socket:close()
- return false, "Failed to receive connection response from server"
- end
-
- local response = Response.Connect:new(data)
- if ( not(response) ) then
- return false, "Failed to parse response from server"
- end
- if ( response.version ~= 5 ) then
- return false, ("Unsupported SOCKS version (%d)"):format(response.version)
- end
- if ( response.method == 0xFF ) then
- return false, "No acceptable authentication methods"
- end
-
- -- store the method so authenticate knows what to use
- self.auth_method = response.method
- return true, response
- end,
-
- -- Authenticates to the SOCKS server
- -- @param creds table containing authentication method specific fields
- -- currently only authentication method 2 (username and pass) is
- -- implemented. That method requires the following fields:
- -- <code>username</code> - containing the username
- -- <code>password</code> - containing the password
- -- @return status true on success, false on failure
- -- @return err string containing the error message
- authenticate = function(self, creds)
- if ( self.auth_method ~= 2 ) then
- return false, "Authentication method not supported"
- end
- local req = Request.Authenticate:new(self.auth_method, creds)
- if ( not(req) ) then
- return false, "Failed to create authentication request"
- end
-
- local status = self.socket:send(tostring(req))
- if ( not(status) ) then
- return false, "Failed to send authentication request"
- end
-
- if ( 2 == self.auth_method ) then
- local status, data = self.socket:recv(2)
- local auth = Response.Authenticate:new(data)
-
- if ( not(auth) ) then
- return false, "Failed to parse authentication response"
- end
-
- if ( auth:isSuccess() ) then
- return true, "Authentication was successfull"
- else
- return false, "Authentication failed"
- end
- end
- return false, "Unsupported authentication method"
- end,
- -- closes the connection to the server
- close = function(self)
- return self.socket:close()
- end,
-
- }
- return _ENV;