summaryrefslogtreecommitdiffstats
path: root/nselib/ndmp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/ndmp.lua')
-rw-r--r--nselib/ndmp.lua423
1 files changed, 423 insertions, 0 deletions
diff --git a/nselib/ndmp.lua b/nselib/ndmp.lua
new file mode 100644
index 0000000..8339037
--- /dev/null
+++ b/nselib/ndmp.lua
@@ -0,0 +1,423 @@
+---
+-- A minimalistic NDMP (Network Data Management Protocol) library
+--
+-- @author Patrik Karlsson <patrik@cqure.net>
+--
+
+local match = require "match"
+local nmap = require "nmap"
+local os = require "os"
+local stdnse = require "stdnse"
+local string = require "string"
+local table = require "table"
+_ENV = stdnse.module("ndmp", stdnse.seeall)
+
+NDMP = {
+
+ -- Message types
+ MessageType = {
+ CONFIG_GET_HOST_INFO = 0x00000100,
+ CONFIG_GET_FS_INFO = 0x00000105,
+ CONFIG_GET_AUTH_ATTR = 0x00000103,
+ CONFIG_GET_SERVER_INFO = 0x00000108,
+ CONNECT_CLIENT_AUTH = 0x00000901,
+ },
+
+ -- Error types
+ ErrorType = {
+ NOT_AUTHORIZED_ERROR = 0x00000004
+ },
+
+ -- The fragment header, 4 bytes where the highest bit is used to determine
+ -- whether the fragment is the last or not.
+ FragmentHeader = {
+ size = 4,
+
+ -- Creates a new instance of fragment header
+ -- @return o instance of Class
+ new = function(self)
+ local o = {
+ last = true,
+ length = 24,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Parse data stream and create a new instance of this class
+ -- @param data opaque string
+ -- @return fh new instance of FragmentHeader class
+ parse = function(data)
+ local fh = NDMP.FragmentHeader:new()
+ local tmp = string.unpack(">I4", data)
+ fh.length = (tmp & 0x7fffffff)
+ fh.last= (tmp >> 31)
+ return fh
+ end,
+
+ -- Serializes the instance to an opaque string
+ -- @return str string containing the serialized class
+ __tostring = function(self)
+ local tmp = 0
+ if ( self.last ) then
+ tmp = 0x80000000
+ end
+ tmp = tmp + self.length
+ return string.pack(">I4", tmp)
+ end,
+
+ },
+
+ -- The ndmp 24 byte header
+ Header = {
+ size = 24,
+
+ -- creates a new instance of Header
+ -- @return o instance of Header
+ new = function(self)
+ local o = {
+ seq = 0,
+ time = os.time(),
+ type = 0,
+ msg = 0x00000108,
+ reply_seq = 0,
+ error = 0,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Create a Header instance from opaque data string
+ -- @param data opaque string
+ -- @return hdr new instance of Header
+ parse = function(data)
+ local hdr = NDMP.Header:new()
+ hdr.seq, hdr.time, hdr.type, hdr.msg, hdr.reply_seq, hdr.error = string.unpack(">I4I4I4I4I4I4", data)
+ return hdr
+ end,
+
+ -- Serializes the instance to an opaque string
+ -- @return str string containing the serialized class instance
+ __tostring = function(self)
+ return string.pack(">I4I4I4I4I4I4", self.seq, self.time, self.type, self.msg, self.reply_seq, self.error)
+ end,
+
+ },
+}
+
+NDMP.Message = {}
+
+NDMP.Message.ConfigGetServerInfo = {
+
+ -- Creates a Config Server Info instance
+ -- @return o new instance of Class
+ new = function(self)
+ local o = {
+ frag_header = NDMP.FragmentHeader:new(),
+ header = NDMP.Header:new(),
+ data = nil,
+ }
+ o.header.msg = NDMP.MessageType.CONFIG_GET_SERVER_INFO
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Create a ConfigGetServerInfo instance from opaque data string
+ -- @param data opaque string
+ -- @return msg new instance of ConfigGetServerInfo
+ parse = function(data)
+ local msg = NDMP.Message.ConfigGetServerInfo:new()
+ msg.frag_header = NDMP.FragmentHeader.parse(data)
+ data = data:sub(NDMP.FragmentHeader.size + 1)
+ msg.header = NDMP.Header.parse(data)
+ msg.data = data:sub(NDMP.Header.size + 1)
+
+ msg.serverinfo = {}
+ local err, pos = string.unpack(">I4", msg.data)
+ pos, msg.serverinfo.vendor = Util.parseString(msg.data, pos)
+ pos, msg.serverinfo.product = Util.parseString(msg.data, pos)
+ pos, msg.serverinfo.version = Util.parseString(msg.data, pos)
+ return msg
+ end,
+
+ -- Serializes the instance to an opaque string
+ -- @return str string containing the serialized class instance
+ __tostring = function(self)
+ return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "")
+ end,
+
+}
+
+NDMP.Message.ConfigGetHostInfo = {
+ new = function(self)
+ local o = {
+ frag_header = NDMP.FragmentHeader:new(),
+ header = NDMP.Header:new(),
+ data = nil,
+ }
+ o.header.msg = NDMP.MessageType.CONFIG_GET_HOST_INFO
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ parse = function(data)
+ local msg = NDMP.Message.ConfigGetServerInfo:new()
+ msg.frag_header = NDMP.FragmentHeader.parse(data)
+ data = data:sub(NDMP.FragmentHeader.size + 1)
+ msg.header = NDMP.Header.parse(data)
+ if ( msg.header.error == NDMP.ErrorType.NOT_AUTHORIZED_ERROR ) then
+ -- no data to parse
+ return msg
+ end
+ msg.data = data:sub(NDMP.Header.size + 1)
+
+ msg.hostinfo = {}
+ local err, pos = string.unpack(">I4", msg.data)
+ pos, msg.hostinfo.hostname = Util.parseString(msg.data, pos)
+ pos, msg.hostinfo.ostype = Util.parseString(msg.data, pos)
+ pos, msg.hostinfo.osver = Util.parseString(msg.data, pos)
+ pos, msg.hostinfo.hostid = Util.parseString(msg.data, pos)
+ return msg
+ end,
+
+ __tostring = function(self)
+ return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "")
+ end,
+}
+
+NDMP.Message.ConfigGetFsInfo = {
+
+ new = function(self)
+ local o = {
+ frag_header = NDMP.FragmentHeader:new(),
+ header = NDMP.Header:new(),
+ data = nil,
+ fsinfo = {},
+ }
+ o.header.msg = NDMP.MessageType.CONFIG_GET_FS_INFO
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ parse = function(data)
+ local msg = NDMP.Message.ConfigGetFsInfo:new()
+ msg.frag_header = NDMP.FragmentHeader.parse(data)
+ data = data:sub(NDMP.FragmentHeader.size + 1)
+ msg.header = NDMP.Header.parse(data)
+ if ( msg.header.error == NDMP.ErrorType.NOT_AUTHORIZED_ERROR ) then
+ -- no data to parse
+ return msg
+ end
+ msg.data = data:sub(NDMP.Header.size + 1)
+
+ local err, count, pos = string.unpack(">I4I4", msg.data)
+ for i=1, count do
+ local item = {}
+ item.invalid, pos = string.unpack(">I4", msg.data, pos)
+ pos, item.fs_type = Util.parseString(msg.data, pos)
+ pos, item.fs_logical_device = Util.parseString(msg.data, pos)
+ pos, item.fs_physical_device = Util.parseString(msg.data, pos)
+ item.total_size,
+ item.used_size,
+ item.avail_size,
+ item.total_inodes,
+ item.used_inodes, pos = string.unpack(">I8I8I8I8I8", msg.data, pos)
+ pos, item.fs_env = Util.parseString(msg.data, pos)
+ pos, item.fs_status = Util.parseString(msg.data, pos)
+ table.insert(msg.fsinfo, item)
+ end
+ return msg
+ end,
+
+ __tostring = function(self)
+ return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "")
+ end,
+}
+
+NDMP.Message.UnhandledMessage = {
+
+ new = function(self)
+ local o = {
+ frag_header = NDMP.FragmentHeader:new(),
+ header = NDMP.Header:new(),
+ data = nil,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ parse = function(data)
+ local msg = NDMP.Message.ConfigGetFsInfo:new()
+ msg.frag_header = NDMP.FragmentHeader.parse(data)
+ data = data:sub(NDMP.FragmentHeader.size + 1)
+ msg.header = NDMP.Header.parse(data)
+ msg.data = data:sub(NDMP.Header.size + 1)
+ return msg
+ end,
+
+ __tostring = function(self)
+ return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "")
+ end
+
+}
+
+Util = {
+
+ parseString = function(data, pos)
+ local str, pos = string.unpack(">s4", data, pos)
+ local pad = ( 4 - ( #str % 4 ) ~= 4 ) and 4 - ( #str % 4 ) or 0
+ return pos + pad, str
+
+ end,
+
+}
+
+NDMP.TypeToMessage = {
+ [NDMP.MessageType.CONFIG_GET_SERVER_INFO] = NDMP.Message.ConfigGetServerInfo,
+ [NDMP.MessageType.CONFIG_GET_HOST_INFO] = NDMP.Message.ConfigGetHostInfo,
+ [NDMP.MessageType.CONFIG_GET_FS_INFO] = NDMP.Message.ConfigGetFsInfo,
+}
+
+-- Handles the communication with the NDMP service
+Comm = {
+
+ -- Creates new Comm instance
+ -- @param host table as received by the action method
+ -- @param port table as received by the action method
+ -- @return o new instance of Comm
+ new = function(self, host, port)
+ local o = {
+ host = host,
+ port = port,
+ socket = nmap.new_socket(),
+ seq = 0,
+ in_queue = {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Connects to the NDMP server
+ -- @return status true on success, false on failure
+ connect = function(self)
+ -- some servers seem to take their time, so leave this as 10s for now
+ self.socket:set_timeout(10000)
+ return self.socket:connect(self.host, self.port)
+ end,
+
+ -- Receives a message from the server
+ -- @return status true on success, false on failure
+ -- @return msg NDMP message when a parser exists, otherwise nil
+ sock_recv = function(self)
+ local status, frag_data = self.socket:receive_buf(match.numbytes(4), true)
+ if ( not(status) ) then
+ return false, "Failed to read NDMP 4-byte fragment header"
+ end
+ local frag_header = NDMP.FragmentHeader.parse(frag_data)
+
+ local status, header_data = self.socket:receive_buf(match.numbytes(24), true)
+ if ( not(status) ) then
+ return false, "Failed to read NDMP 24-byte header"
+ end
+ local header = NDMP.Header.parse(header_data)
+
+ local status, data = self.socket:receive_buf(match.numbytes(frag_header.length - 24), true)
+ if ( not(status) ) then
+ return false, "Failed to read NDMP data"
+ end
+
+ if ( NDMP.TypeToMessage[header.msg] ) then
+ return true, NDMP.TypeToMessage[header.msg].parse(frag_data .. header_data .. data)
+ end
+ return true, NDMP.Message.UnhandledMessage.parse(frag_data .. header_data .. data)
+ end,
+
+ recv = function(self)
+ if ( #self.in_queue > 0 ) then
+ return true, table.remove(self.in_queue, 1)
+ end
+ return self:sock_recv()
+ end,
+
+ -- Sends a message to the server
+ -- @param msg NDMP message
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message when status is false
+ send = function(self, msg)
+ self.seq = self.seq + 1
+ msg.header.seq = self.seq
+ return self.socket:send(tostring(msg))
+ end,
+
+
+ exch = function(self, msg)
+ local status, err = self:send(msg)
+ if ( not(status) ) then
+ return false, "Failed to send ndmp Message to server"
+ end
+ local s_seq = msg.header.seq
+
+ for k, v in ipairs(self.in_queue) do
+ if ( v.reply_seq == s_seq ) then
+ return true, table.remove(self.in_queue, k)
+ end
+ end
+
+ while(true) do
+ local reply
+ status, reply = self:sock_recv()
+ if ( not(status) ) then
+ return false, "Failed to receive msg from server"
+ elseif ( reply and reply.header and reply.header.reply_seq == s_seq ) then
+ return true, reply
+ else
+ table.insert(self.in_queue, reply)
+ end
+ end
+ end,
+
+ close = function(self) return self.socket:close() end,
+
+}
+
+
+Helper = {
+
+ new = function(self, host, port)
+ local o = { comm = Comm:new(host, port) }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ connect = function(self)
+ return self.comm:connect()
+ end,
+
+ getFsInfo = function(self)
+ return self.comm:exch(NDMP.Message.ConfigGetFsInfo:new())
+ end,
+
+ getHostInfo = function(self)
+ return self.comm:exch(NDMP.Message.ConfigGetHostInfo:new())
+ end,
+
+ getServerInfo = function(self)
+ return self.comm:exch(NDMP.Message.ConfigGetServerInfo:new())
+ end,
+
+ close = function(self)
+ return self.comm:close()
+
+ end
+
+}
+
+return _ENV;