285 lines
8.6 KiB
Lua
285 lines
8.6 KiB
Lua
---
|
|
-- 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;
|