diff options
Diffstat (limited to 'nselib/ndmp.lua')
-rw-r--r-- | nselib/ndmp.lua | 423 |
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; |