summaryrefslogtreecommitdiffstats
path: root/nselib/versant.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/versant.lua')
-rw-r--r--nselib/versant.lua284
1 files changed, 284 insertions, 0 deletions
diff --git a/nselib/versant.lua b/nselib/versant.lua
new file mode 100644
index 0000000..27252a5
--- /dev/null
+++ b/nselib/versant.lua
@@ -0,0 +1,284 @@
+---
+-- A tiny library allowing some basic information enumeration from
+-- Versant object database software (see
+-- http://en.wikipedia.org/wiki/Versant_Corporation). The code is
+-- entirely based on packet dumps captured when using the Versant
+-- Management Center administration application.
+--
+-- @author Patrik Karlsson <patrik@cqure.net>
+--
+
+local stdnse = require "stdnse"
+local match = require "match"
+local nmap = require "nmap"
+local string = require "string"
+local table = require "table"
+_ENV = stdnse.module("versant", stdnse.seeall)
+
+Versant = {
+
+ -- fallback to these constants when version and user are not given
+ USER = "nmap",
+ VERSION = "8.0.2",
+
+ -- Creates an instance of the Versant class
+ -- @param host table
+ -- @param port table
+ -- @return o new instance of Versant
+ new = function(self, host, port)
+ local o = { host = host, port = port, socket = nmap.new_socket() }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Connects a socket to the Versant server
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message if status is false
+ connect = function(self)
+ return self.socket:connect(self.host, self.port)
+ end,
+
+ -- Closes the socket
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message if status is false
+ close = function(self)
+ return self.socket:close()
+ end,
+
+ -- Sends command to the server
+ -- @param cmd string containing the command to run
+ -- @param arg string containing any arguments
+ -- @param user [optional] string containing the user name
+ -- @param ver [optional] string containing the version number
+ -- @return status true on success, false on failure
+ -- @return data opaque string containing the response
+ sendCommand = function(self, cmd, arg, user, ver)
+
+ user = user or Versant.USER
+ ver = ver or Versant.VERSION
+ arg = arg or ""
+
+ local data = stdnse.fromhex("000100000000000000020002000000010000000000000000000000000000000000010000")
+ .. string.pack("zzz",
+ cmd,
+ user,
+ ver
+ )
+ -- align to even 4 bytes
+ data = data .. string.rep("\0", 4 - ((#data % 4) or 0))
+
+ data = data .. stdnse.fromhex("0000000b000001000000000000000000")
+ .. string.pack("zxxxxxxxxxxz",
+ ("%s:%d"):format(self.host.ip, self.port.number),
+ arg
+ )
+
+ data = data .. string.rep("\0", 2048 - #data)
+
+ local status, err = self.socket:send(data)
+ if ( not(status) ) then
+ return false, "Failed to send request to server"
+ end
+
+ local status, data = self.socket:receive_buf(match.numbytes(2048), true)
+ if ( not(status) ) then
+ return false, "Failed to read response from server"
+ end
+
+ return status, data
+ end,
+
+ -- Get database node information
+ -- @return status true on success, false on failure
+ -- @return result table containing an entry for each database. Each entry
+ -- contains a table with the following fields:
+ -- <code>name</code> - the database name
+ -- <code>owner</code> - the database owner
+ -- <code>created</code> - the date when the database was created
+ -- <code>version</code> - the database version
+ getNodeInfo = function(self)
+ local status, data = self:sendCommand("o_getnodeinfo", "-nodeinfo")
+ if ( not(status) ) then
+ return false, data
+ end
+
+ status, data = self.socket:receive_buf(match.numbytes(4), true)
+ if ( not(status) ) then
+ return false, "Failed to read response from server"
+ end
+
+ local db_count = string.unpack(">I4", data)
+ if ( db_count == 0 ) then
+ return false, "Database count was zero"
+ end
+
+ status, data = self.socket:receive_buf(match.numbytes(4), true)
+ if ( not(status) ) then
+ return false, "Failed to read response from server"
+ end
+
+ local buf_size = string.unpack(">I4", data)
+ local dbs = {}
+
+ for i=1, db_count do
+ status, data = self.socket:receive_buf(match.numbytes(buf_size), true)
+ local db = {}
+
+ db.name = string.unpack("z", data, 23)
+ db.owner = string.unpack("z", data, 599)
+ db.created= string.unpack("z", data, 631)
+ db.version= string.unpack("z", data, 663)
+
+ -- remove trailing line-feed
+ db.created = db.created:match("^(.-)\n*$")
+
+ table.insert(dbs, db)
+ end
+ return true, dbs
+ end,
+
+ -- Gets the database OBE port, this port is dynamically allocated once this
+ -- command completes.
+ --
+ -- @return status true on success, false on failure
+ -- @return port table containing the OBE port
+ getObePort = function(self)
+
+ local status, data = self:sendCommand("o_oscp", "-utility")
+ if ( not(status) ) then
+ return false, data
+ end
+
+ status, data = self.socket:receive_buf(match.numbytes(256), true)
+ if ( not(status) ) then
+ return false, "Failed to read response from server"
+ end
+
+ local success, pos = string.unpack(">I4", data)
+ if ( success ~= 0 ) then
+ return false, "Response contained invalid data"
+ end
+
+ local port = { protocol = "tcp" }
+ port.number, pos = string.unpack(">I2", data, pos)
+
+ return true, port
+ end,
+
+
+ -- Gets the XML license file from the database
+ -- @return status true on success, false on failure
+ -- @return data string containing the XML license file
+ getLicense = function(self)
+
+ local status, data = self:sendCommand("o_licfile", "-license")
+ if ( not(status) ) then
+ return false, data
+ end
+
+ status, data = self.socket:receive_buf(match.numbytes(4), true)
+ if ( not(status) ) then
+ return false, "Failed to read response from server"
+ end
+
+ local len = string.unpack(">I4", data)
+ if ( len == 0 ) then
+ return false, "Failed to retrieve license file"
+ end
+
+ status, data = self.socket:receive_buf(match.numbytes(len), true)
+ if ( not(status) ) then
+ return false, "Failed to read response from server"
+ end
+
+ return true, data
+ end,
+
+ -- Gets the TCP port for a given database
+ -- @param db string containing the database name
+ -- @return status true on success, false on failure
+ -- @return port table containing the database port
+ getDbPort = function(self, db)
+ local status, data = self:sendCommand(db, "")
+ if ( not(status) ) then
+ return false, data
+ end
+
+ if ( not(status) ) then
+ return false, "Failed to connect to database"
+ end
+
+ local port = { protocol = "tcp" }
+ port.number = string.unpack(">I4", data, 27)
+ if ( port == 0 ) then
+ return false, "Failed to determine database port"
+ end
+ return true, port
+ end,
+}
+
+Versant.OBE = {
+
+ -- Creates a new versant OBE instance
+ -- @param host table
+ -- @param port table
+ -- @return o new instance of Versant OBE
+ new = function(self, host, port)
+ local o = { host = host, port = port, socket = nmap.new_socket() }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Connects a socket to the Versant server
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message if status is false
+ connect = function(self)
+ return self.socket:connect(self.host, self.port)
+ end,
+
+ -- Closes the socket
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message if status is false
+ close = function(self)
+ return self.socket:close()
+ end,
+
+ -- Get database information including file paths and hostname
+ -- @return status true on success false on failure
+ -- @return result table containing the fields:
+ -- <code>root_path</code> - the database root directory
+ -- <code>db_path</code> - the database directory
+ -- <code>lib_path</code> - the library directory
+ -- <code>hostname</code> - the database host name
+ getVODInfo = function(self)
+ local data = stdnse.fromhex("1002005d00000000000100000000000d000000000000000000000000") --28
+ .. "-noprint -i " --12
+ .. string.rep("\0", 216) -- 256 - (28 + 12)
+
+ self.socket:send(data)
+ local status, data = self.socket:receive_buf(match.numbytes(256), true)
+ if ( not(status) ) then
+ return false, "Failed to read response from server"
+ end
+
+ local len = string.unpack(">I4", data, 13)
+ status, data = self.socket:receive_buf(match.numbytes(len), true)
+ if ( not(status) ) then
+ return false, "Failed to read response from server"
+ end
+
+ local result = {}
+ local offset = 13
+ result.version = string.unpack("z", data)
+
+ for _, item in ipairs({"root_path", "db_path", "lib_path", "hostname"}) do
+ result[item] = string.unpack("z", data, offset)
+ offset = offset + 256
+ end
+ return true, result
+ end,
+}
+
+return _ENV;