summaryrefslogtreecommitdiffstats
path: root/nselib/socks.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/socks.lua')
-rw-r--r--nselib/socks.lua283
1 files changed, 283 insertions, 0 deletions
diff --git a/nselib/socks.lua b/nselib/socks.lua
new file mode 100644
index 0000000..fdf588f
--- /dev/null
+++ b/nselib/socks.lua
@@ -0,0 +1,283 @@
+---
+-- A smallish SOCKS version 5 proxy protocol implementation
+--
+-- @author Patrik Karlsson <patrik@cqure.net>
+--
+
+local match = require "match"
+local nmap = require "nmap"
+local stdnse = require "stdnse"
+local string = require "string"
+local table = require "table"
+_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)
+ return string.pack("Bs1", self.version,
+ string.pack(("B"):rep(#self.auth_method), table.unpack(self.auth_method)))
+ 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 string.pack("Bs1s1", 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
+ self.version, self.method, pos = string.unpack("BB", 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
+ self.version, self.status, pos = string.unpack("BB", 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,
+
+ }
+
+}
+
+-- 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, currently:
+ -- <code>timeout</code> - socket timeout in ms
+ -- @return o instance of Helper
+ new = function(self, host, port, options)
+ options = options or {}
+ 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 response or err string on failure
+ connect = function(self, auth_method, socket)
+ self.socket = socket or nmap.new_socket()
+ self.socket:set_timeout(self.options.timeout or 10000)
+ local status, err = self.socket:connect(self.host, self.port)
+ 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:receive_buf(match.numbytes(2), true)
+ 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:receive_buf(match.numbytes(2), true)
+ 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 successful"
+ 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;