Index: nselib/rpc.lua =================================================================== --- nselib/rpc.lua (revision 17184) +++ nselib/rpc.lua (working copy) @@ -13,8 +13,12 @@ -- Overview -- -------- -- The library contains the following classes: +-- o RPC +-- - Static container for constants -- o Comm +-- - Handles socket connections -- - Handles low-level packet sending, recieving, decoding and encoding +-- - Stores rpc programs info: socket, protocol, program name, id and version -- - Used by Mount, NFS, RPC and Portmap -- o Mount -- - Handles communication with the mount RPC program @@ -24,8 +28,6 @@ -- - Provides easy access to common RPC functions -- - Implemented as a static class where most functions accept host -- and port parameters --- o RPC --- - Static container for constants -- o Portmap -- - Handles communication with the portmap RPC program -- o Util @@ -94,31 +96,127 @@ module(... or "rpc", package.seeall) require("datafiles") +-- RPC info using the nmap.registry +RPC_registry = { + ["rpcbind"] = { proto = 'rpc.protocol' }, + ["nfs"] = { ver = 'nfs.version' }, + ["mountd"] = { ver = 'mount.version' }, +} --- Defines the order in which to try to connect to the RPC programs --- TCP appears to be more stable than UDP in most cases, so try it first -local RPC_PROTOCOLS = ( nmap.registry.args and nmap.registry.args['rpc.protocol'] and type(nmap.registry.args['rpc.protocol']) == 'table') and nmap.registry.args['rpc.protocol'] or { "tcp", "udp" } - --- used to cache the contents of the rpc datafile -local RPC_PROGRAMS - -- Supported protocol versions -Version = { +RPC_version = { + ["rpcbind"] = { min=2, max=2 }, ["nfs"] = { min=1, max=3 }, ["mountd"] = { min=1, max=3 }, } +-- used to cache the contents of the rpc datafile +local RPC_PROGRAMS + +-- Defines the order in which to try to connect to the RPC programs +-- TCP appears to be more stable than UDP in most cases, so try it first +local RPC_PROTOCOLS = (nmap.registry.args and nmap.registry.args[RPC_registry['rpcbind'].proto] and + type(nmap.registry.args[RPC_registry['rpcbind'].proto]) == 'table') and + nmap.registry.args[RPC_registry['rpcbind'].proto] or { "tcp", "udp" } + math.randomseed( os.time() ) +--- Container class for RPC constants +RPC = +{ + AuthType = + { + Null = 0 + }, + + MessageType = + { + Call = 0, + Reply = 1 + }, + + Procedure = + { + [2] = + { + GETPORT = 3, + DUMP = 4, + }, + + }, + +} + -- Low-level communication class Comm = { - new = function(self,o) + --- creats a new Comm object + -- + -- @param program making the connect socket call + -- @param version number containing the program version to use + -- @return a new Comm object on success + new = function(self, o, program, version) o = o or {} - setmetatable(o, self) - self.__index = self + setmetatable(o, self) + self.__index = self + local try = nmap.new_try(function() return end) + try(o:set_rpcinfo()) return o - end, + end, + + --- makes a connection and return the socket + -- + -- @param host table + -- @param port table + -- @return status boolean true on success, false on failure + -- @return string containing error message (if status is false) + connect = function(self, host, port) + local socket = nmap.new_socket() + local status, err = socket:connect(host.ip, port.number, port.protocol) + if (status == false) then + return false, string.format("%s connect error: %s", self.program, err) + else + self.socket = socket + self.proto = port.protocol + end + return status, nil + end; + + --- Disconnects from the remote program + -- + -- @return status boolean true on success, false on failure + -- @return string containing error message (if status is false) + disconnect = function(self) + local status, err = self.socket:close() + if (status == false) then + return false, string.format("%s disconnect error: %s", self.program, err) + end + self.socket=nil + return status, nil + end; + + --- set/check the rpc program information: version and program id + -- + -- @param version number containing the program version to use + -- @return status boolean true on success, false on failure + -- @return string containing error message (if status is false) + set_rpcinfo = function(self, version) + if (RPC_version[self.program]) then + if (RPC_registry[self.program] and nmap.registry.args and + nmap.registry.args[RPC_registry[self.program].ver]) then + self.version = tonumber(nmap.registry.args[RPC_registry[self.program].ver]) + elseif (not(self.version) and version) then + self.version = version + end + if ( self.version > RPC_version[self.program].max or self.version < RPC_version[self.program].min ) then + return false, string.format("RPC library does not support: %s version %d",self.program,self.version) + end + else + return false, string.format("RPC library does not support: %s",self.program) + end + self.program_id = Util.ProgNameToNumber(self.program) + return true, nil + end; --- Checks if data contains enough bytes to read the needed amount -- If it doesn't it attempts to read the remaining amount of bytes from the socket @@ -129,31 +227,27 @@ -- @return status success or failure -- @return data string containing the data passed to the function and the additional data appended to it GetAdditionalBytes = function( self, data, pos, needed ) - - local status = true - local tmp - + local status, tmp if data:len() - pos + 1 < needed then local toread = needed - ( data:len() - pos + 1 ) status, tmp = self.socket:receive_bytes( toread ) - if status then + if (status) then data = data .. tmp else return false, string.format("getAdditionalBytes() failed to read: %d bytes from the socket", needed - ( data:len() - pos ) ) end end - return status, data + return true, data end, --- Creates a RPC header -- -- @param xid number - -- @param program_id number containing the program_id to connect to - -- @param program_version number containing the version to query -- @param procedure number containing the procedure to call -- @param auth table containing the authentication data to use - -- @return string of bytes - CreateHeader = function( self, xid, program_id, program_version, procedure, auth ) + -- @return status boolean true on success, false on failure + -- @return string of bytes on success or error message + CreateHeader = function( self, xid, procedure, auth ) local RPC_VERSION = 2 local packet @@ -161,10 +255,10 @@ xid = math.random(1234567890) end if not auth or auth.type ~= RPC.AuthType.Null then - return false, "No or invalid authentication type specified" + return false, "Comm.CreateHeader: No or invalid authentication type specified" end - packet = bin.pack( ">IIIIII", xid, RPC.MessageType.Call, RPC_VERSION, program_id, program_version, procedure ) + packet = bin.pack( ">IIIIII", xid, RPC.MessageType.Call, RPC_VERSION, self.program_id, self.version, procedure ) if auth.type == RPC.AuthType.Null then packet = packet .. bin.pack( "IIII", 0, 0, 0, 0 ) end @@ -179,17 +273,16 @@ -- @return header table containing xid, type, state, -- verifier and accept_state DecodeHeader = function( self, data, pos ) + local status local header = {} - local status - local HEADER_LEN = 20 header.verifier = {} - if ( data:len() - pos < HEADER_LEN ) then local tmp status, tmp = self:GetAdditionalBytes( data, pos, HEADER_LEN - ( data:len() - pos ) ) if not status then + stdnse.print_debug("Comm.DecodeHeader: Failed to call GetAdditionalBytes") return -1, nil end data = data .. tmp @@ -202,6 +295,7 @@ if header.verifier.length - 8 > 0 then status, data = self:GetAdditionalBytes( data, pos, header.verifier.length - 8 ) if not status then + stdnse.print_debug("Comm.DecodeHeader: Failed to call GetAdditionalBytes") return -1, nil end pos, header.verifier.data = bin.unpack("A" .. header.verifier.length - 8, data, pos ) @@ -212,10 +306,11 @@ --- Reads the response from the socket -- + -- @return status true on success -- @return data string containing the raw response ReceivePacket = function( self ) local status - + if ( self.proto == "udp" ) then -- There's not much we can do in here to check if we received all data -- as the packet contains no length field. It's up to each decoding function @@ -229,9 +324,9 @@ lastfragment = false status, data = self:GetAdditionalBytes( data, pos, 4 ) if ( not(status) ) then - return false, "rpc.Comm.ReceivePacket: failed to call GetAdditionalBytes" + return false, "Comm.ReceivePacket: failed to call GetAdditionalBytes" end - + pos, tmp = bin.unpack(">i", data, pos ) length = bit.band( tmp, 0x7FFFFFFF ) @@ -241,7 +336,7 @@ status, data = self:GetAdditionalBytes( data, pos, length ) if ( not(status) ) then - return false, "rpc.Comm.ReceivePacket: failed to call GetAdditionalBytes" + return false, "Comm.ReceivePacket: failed to call GetAdditionalBytes" end -- @@ -278,20 +373,18 @@ --- Encodes a RPC packet -- -- @param xid number containing the transaction ID - -- @param prog number containing the program id + -- @param procedure number containing the procedure to call -- @param auth table containing authentication information -- @param data string containing the packet data -- @return packet string containing the encoded packet data - EncodePacket = function( self, xid, prog, auth, data ) - local status, packet = self:CreateHeader( xid, prog.id, prog.version, prog.proc, auth ) + EncodePacket = function( self, xid, proc, auth, data ) + local status, packet = self:CreateHeader( xid, proc, auth ) local len - if ( not(status) ) then return end packet = packet .. ( data or "" ) - if ( self.proto == "udp") then return packet else @@ -302,7 +395,7 @@ end, SendPacket = function( self, packet ) - return self.socket:send( packet ) + return self.socket:send(packet) end, } @@ -314,6 +407,31 @@ -- Mount = { + stat_msg = { + [1] = "Not owner.", + [2] = "No such file or directory.", + [5] = "I/O error.", + [13] = "Permission denied.", + [20] = "Not a directory.", + [22] = "Invalid argument.", + [63] = "Filename too long.", + [10004] = "Operation not supported.", + [10006] = "A failure on the server.", + }, + + stat_code = { + MNT_OK = 0, + MNTERR_PERM = 1, + MNTERR_NOENT = 2, + MNTERR_IO = 5, + MNTERR_ACCES = 13, + MNTERR_NOTDIR = 20, + MNTERR_INVAL = 22, + MNTERR_NAMETOOLONG = 63, + MNTERR_NOTSUPP = 10004, + MNTERR_SERVERFAULT = 10006, + }, + Procedure = { MOUNT = 1, @@ -329,56 +447,15 @@ self.__index = self return o end, - - --- Connects to the mountd program - -- - -- @param host table - -- @param port table - -- @param version number containing the program version to use - -- @return status boolean true on success, false on failure - -- @return result string containing error message (if status is false) - Connect = function( self, host, port, version ) - local socket = nmap.new_socket() - local status, result = socket:connect(host.ip, port.number, port.protocol) - if ( status ) then - self.socket = socket - self.proto = port.protocol - self.comm = Comm:new( { socket = socket, proto=port.protocol} ) - self.version = ( nmap.registry.args and nmap.registry.args['mount.version'] ) and tonumber(nmap.registry.args['mount.version']) or version - - if ( self.version > Version["mountd"].max or self.version < Version["mountd"].min ) then - return false, "Library does not support mountd version: " .. self.version - end - end - - return status, result - end, - - --- Disconnects from the mountd program - -- - -- @return status boolean true on success, false on failure - -- @return result string containing error message (if status is false) - Disconnect = function( self ) - local status, result = self.socket:close() - if ( status ) then - self.proto = nil - self.socket = nil - self.comm = nil - end - return status, result - end, - --- Requests a list of NFS export from the remote server -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @return status success or failure -- @return entries table containing a list of share names (strings) - Export = function( self ) - - local catch = function() socket:close() end - local try = nmap.new_try(catch) + Export = function(self, comm) local msg_type = 0 - local prg_mount = Util.ProgNameToNumber("mountd") local packet local pos = 1 local header = {} @@ -388,35 +465,39 @@ local REPLY_ACCEPTED, SUCCESS, PROC_EXPORT = 0, 0, 5 - if self.proto ~= "tcp" and self.proto ~= "udp" then - return false, "Protocol should be either udp or tcp" + if comm.proto ~= "tcp" and comm.proto ~= "udp" then + return false, "Mount.Export: Protocol should be either udp or tcp" end - packet = self.comm:EncodePacket( nil, { id=prg_mount, version=self.version, proc=Mount.Procedure.EXPORT }, { type=RPC.AuthType.Null }, nil ) - try( self.comm:SendPacket( packet ) ) + packet = comm:EncodePacket(nil, Mount.Procedure.EXPORT, { type=RPC.AuthType.Null }, nil ) + if (not(comm:SendPacket( packet ))) then + return false, "Mount.Export: Failed to send data" + end - status, data = self.comm:ReceivePacket() + status, data = comm:ReceivePacket() if ( not(status) ) then - return false, "mountExportCall: Failed to read data from socket" + return false, "Mount.Export: Failed to read data from socket" end -- make sure we have atleast 24 bytes to unpack the header - data = try( self.comm:GetAdditionalBytes( data, pos, 24 ) ) - pos, header = self.comm:DecodeHeader( data, pos ) - + status, data = comm:GetAdditionalBytes( data, pos, 24 ) + if (not(status)) then + return false, "Mount.Export: Failed to call GetAdditionalBytes" + end + pos, header = comm:DecodeHeader( data, pos ) if not header then - return false, "Failed to decode header" + return false, "Mount.Export: Failed to decode header" end if header.type ~= RPC.MessageType.Reply then - return false, string.format("Packet was not a reply") + return false, "Mount.Export: packet was not a reply" end if header.state ~= REPLY_ACCEPTED then - return false, string.format("Reply state was not Accepted(0) as expected") + return false, string.format("Mount.Export: Reply state was not Accepted(0) as expected") end if header.accept_state ~= SUCCESS then - return false, string.format("Accept State was not Successful") + return false, string.format("Mount.Export: Accept State was not Successful") end --- @@ -437,7 +518,10 @@ --- while true do -- make sure we have atleast 4 more bytes to check for value follows - data = try( self.comm:GetAdditionalBytes( data, pos, 4 ) ) + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + return false, "Mount.Export: Failed to call GetAdditionalBytes" + end local data_follows pos, data_follows = bin.unpack( ">I", data, pos ) @@ -451,10 +535,16 @@ local len -- make sure we have atleast 4 more bytes to get the length - data = try( self.comm:GetAdditionalBytes( data, pos, 4 ) ) + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + return false, "Mount.Export: Failed to call GetAdditionalBytes" + end pos, len = bin.unpack(">I", data, pos ) - data = try( self.comm:GetAdditionalBytes( data, pos, len ) ) + status, data = comm:GetAdditionalBytes( data, pos, len ) + if (not(status)) then + return false, "Mount.Export: Failed to call GetAdditionalBytes" + end pos, entry.name = bin.unpack("A" .. len, data, pos ) pos = pos + Util.CalcFillBytes( len ) @@ -462,16 +552,25 @@ while true do local group - data = try( self.comm:GetAdditionalBytes( data, pos, 4 ) ) + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + return false, "Mount.Export: Failed to call GetAdditionalBytes" + end pos, data_follows = bin.unpack( ">I", data, pos ) if data_follows ~= 1 then break end - data = try( self.comm:GetAdditionalBytes( data, pos, 4 ) ) + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + return false, "Mount.Export: Failed to call GetAdditionalBytes" + end pos, len = bin.unpack( ">I", data, pos ) - data = try( self.comm:GetAdditionalBytes( data, pos, len ) ) + status, data = comm:GetAdditionalBytes( data, pos, len ) + if (not(status)) then + return false, "Mount.Export: Failed to call GetAdditionalBytes" + end pos, group = bin.unpack( "A" .. len, data, pos ) table.insert( entry, group ) @@ -482,73 +581,83 @@ return true, entries end, - --- Attempts to mount a remote export in order to get the filehandle -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @param path string containing the path to mount -- @return status success or failure -- @return fhandle string containing the filehandle of the remote export - Mount = function( self, path ) - - local catch = function() socket:close() end - local try = nmap.new_try(catch) + Mount = function(self, comm, path) local packet, data - local prog_id = Util.ProgNameToNumber("mountd") local _, pos, data, header, fhandle = "", 1, "", "", {} local status, len - local REPLY_ACCEPTED, SUCCESS, MOUNT_OK = 0, 0, 0 + local REPLY_ACCEPTED, SUCCESS = 0, 0 data = bin.pack(">IA", path:len(), path) for i=1, Util.CalcFillBytes( path:len() ) do data = data .. string.char( 0x00 ) end + packet = comm:EncodePacket( nil, Mount.Procedure.MOUNT, { type=RPC.AuthType.Null }, data ) + if (not(comm:SendPacket(packet))) then + return false, "Mount: Failed to send data" + end - packet = self.comm:EncodePacket( nil, { id=prog_id, version=self.version, proc=Mount.Procedure.MOUNT }, { type=RPC.AuthType.Null }, data ) - try( self.comm:SendPacket( packet ) ) - - status, data = self.comm:ReceivePacket() + status, data = comm:ReceivePacket() if ( not(status) ) then - return false, "mountCall: Failed to read data from socket" + return false, "Mount: Failed to read data from socket" end - pos, header = self.comm:DecodeHeader( data, pos ) + pos, header = comm:DecodeHeader( data, pos ) if not header then - return false, "Failed to decode header" + return false, "Mount: Failed to decode header" end if header.type ~= RPC.MessageType.Reply then - return false, string.format("Packet was not a reply") + return false, string.format("Mount: Packet was not a reply") end if header.state ~= REPLY_ACCEPTED then - return false, string.format("Reply state was not Accepted(0) as expected") + return false, string.format("Mount: Reply state was not Accepted(0) as expected") end if header.accept_state ~= SUCCESS then - return false, string.format(3, "mountCall: Accept State was not Successful", path) + return false, string.format(3, "Mount: Accept State was not Successful", path) end local mount_status - data = try( self.comm:GetAdditionalBytes( data, pos, 4 ) ) + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + return false, "Mount: Failed to call GetAdditionalBytes" + end pos, mount_status = bin.unpack(">I", data, pos ) - if mount_status ~= MOUNT_OK then - if ( mount_status == 13 ) then - return false, "Access Denied" + if (mount_status ~= Mount.stat_code.MNT_OK) then + if (Mount.stat_msg[mount_status]) then + return false, string.format("Mount query failed: %s",Mount.stat_msg[mount_status]) else - return false, string.format("Mount failed: %d", mount_status) + return false, string.format("Mount query failed: code %d", mount_status) end end - if ( self.version == 3 ) then - data = try( self.comm:GetAdditionalBytes( data, pos, 4 ) ) + if ( comm.version == 3 ) then + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + return false, "Mount: Failed to call GetAdditionalBytes" + end _, len = bin.unpack(">I", data, pos ) - data = try( self.comm:GetAdditionalBytes( data, pos, len + 4 ) ) + status, data = comm:GetAdditionalBytes( data, pos, len + 4 ) + if (not(status)) then + return false, "Mount: Failed to call GetAdditionalBytes" + end pos, fhandle = bin.unpack( "A" .. len + 4, data, pos ) - elseif ( self.version < 3 ) then - data = try( self.comm:GetAdditionalBytes( data, pos, 32 ) ) + elseif ( comm.version < 3 ) then + status, data = comm:GetAdditionalBytes( data, pos, 32 ) + if (not(status)) then + return false, "Mount: Failed to call GetAdditionalBytes" + end pos, fhandle = bin.unpack( "A32", data, pos ) else return false, "Mount failed" @@ -559,49 +668,48 @@ --- Attempts to unmount a remote export in order to get the filehandle -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @param path string containing the path to mount -- @return status success or failure -- @return error string containing error if status is false - Unmount = function( self, path ) - - local catch = function() socket:close() end - local try = nmap.new_try(catch) + Unmount = function(self, comm, path) local packet, data - local prog_id = Util.ProgNameToNumber("mountd") local _, pos, data, header, fhandle = "", 1, "", "", {} local status - local REPLY_ACCEPTED, SUCCESS, MOUNT_OK = 0, 0, 0 - + local REPLY_ACCEPTED, SUCCESS = 0, 0 data = bin.pack(">IA", path:len(), path) for i=1, Util.CalcFillBytes( path:len() ) do data = data .. string.char( 0x00 ) end - packet = self.comm:EncodePacket( nil, { id=prog_id, version=self.version, proc=Mount.Procedure.UMNT }, { type=RPC.AuthType.Null }, data ) - try( self.comm:SendPacket( packet ) ) + packet = comm:EncodePacket( nil, Mount.Procedure.UMNT, { type=RPC.AuthType.Null }, data ) + if (not(comm:SendPacket(packet))) then + return false, "Unmount: Failed to send data" + end - status, data = self.comm:ReceivePacket( ) + status, data = comm:ReceivePacket( ) if ( not(status) ) then - return false, "mountCall: Failed to read data from socket" + return false, "Unmount: Failed to read data from socket" end - pos, header = self.comm:DecodeHeader( data, pos ) + pos, header = comm:DecodeHeader( data, pos ) if not header then - return false, "Failed to decode header" + return false, "Unmount: Failed to decode header" end if header.type ~= RPC.MessageType.Reply then - return false, string.format("Packet was not a reply") + return false, string.format("Unmount: Packet was not a reply") end if header.state ~= REPLY_ACCEPTED then - return false, string.format("Reply state was not Accepted(0) as expected") + return false, string.format("Unmount: Reply state was not Accepted(0) as expected") end if header.accept_state ~= SUCCESS then - return false, string.format(3, "mountCall: Accept State was not Successful", path) + return false, string.format(3, "Unmount: Accept State was not Successful", path) end return true, "" @@ -616,6 +724,119 @@ -- NFS = { + -- NFS error msg v2 and v3 + stat_msg = { + [1] = "Not owner.", + [2] = "No such file or directory.", + [5] = "I/O error.", + [6] = "I/O error. No such device or address.", + [13] = "Permission denied.", + [17] = "File exists.", + [18] = "Attempt to do a cross-device hard link.", + [19] = "No such device.", + [20] = "Not a directory.", + [21] = "Is a directory.", + [22] = "Invalid argument or unsupported argument for an operation.", + [27] = "File too large.", + [28] = "No space left on device.", + [30] = "Read-only file system.", + [31] = "Too many hard links.", + [63] = "The filename in an operation was too long.", + [66] = "An attempt was made to remove a directory that was not empty.", + [69] = "Resource (quota) hard limit exceeded.", + [70] = "Invalid file handle.", + [71] = "Too many levels of remote in path.", + [99] = "The server's write cache used in the \"WRITECACHE\" call got flushed to disk.", + [10001] = "Illegal NFS file handle.", + [10002] = "Update synchronization mismatch was detected during a SETATTR operation.", + [10003] = "READDIR or READDIRPLUS cookie is stale.", + [10004] = "Operation is not supported.", + [10005] = "Buffer or request is too small.", + --[10006] = "", + --[10007] = "", + --[10008] = "", + }, + + stat_code = { + + -- NFS Version 1 + [1] = { + NFS_OK = 0, + NFSERR_PERM = 1, + NFSERR_NOENT = 2, + NFSERR_IO = 5, + NFSERR_NXIO = 6, + NFSERR_ACCES = 13, + NFSERR_EXIST = 17, + NFSERR_NODEV = 19, + NFSERR_NOTDIR = 20, + NFSERR_ISDIR = 21, + NFSERR_FBIG = 27, + NFSERR_NOSPC = 28, + NFSERR_ROFS = 30, + NFSERR_NAMETOOLONG = 63, + NFSERR_NOTEMPTY = 66, + NFSERR_DQUOT = 69, + NFSERR_STALE = 70, + NFSERR_WFLUSH = 99, + }, + + -- NFS Version 2 + [2] = { + NFS_OK = 0, + NFSERR_PERM = 1, + NFSERR_NOENT = 2, + NFSERR_IO = 5, + NFSERR_NXIO = 6, + NFSERR_ACCES = 13, + NFSERR_EXIST = 17, + NFSERR_NODEV = 19, + NFSERR_NOTDIR = 20, + NFSERR_ISDIR = 21, + NFSERR_FBIG = 27, + NFSERR_NOSPC = 28, + NFSERR_ROFS = 30, + NFSERR_NAMETOOLONG = 63, + NFSERR_NOTEMPTY = 66, + NFSERR_DQUOT = 69, + NFSERR_STALE = 70, + NFSERR_WFLUSH = 99, + }, + + -- NFS Version 3 + [3] = { + NFS_OK = 0, + NFSERR_PERM = 1, + NFSERR_NOENT = 2, + NFSERR_IO = 5, + NFSERR_NXIO = 6, + NFSERR_ACCES = 13, + NFSERR_EXIST = 17, + NFSERR_XDEV = 18, + NFSERR_NODEV = 19, + NFSERR_NOTDIR = 20, + NFSERR_ISDIR = 21, + NFSERR_INVAL = 22, + NFSERR_FBIG = 27, + NFSERR_NOSPC = 28, + NFSERR_ROFS = 30, + NFSERR_MLINK = 31, + NFSERR_NAMETOOLONG = 63, + NFSERR_NOTEMPTY = 66, + NFSERR_DQUOT = 69, + NFSERR_STALE = 70, + NFSERR_REMOTE = 71, + NFSERR_BADHANDLE = 10001, + NFSERR_NOT_SYNC = 10002, + NFSERR_BAD_COOKIE = 10003, + NFSERR_NOTSUPP = 10004, + NFSERR_TOOSMALL = 10005, + --NFS3ERR_SERVERFAULT = 10006, + --NFS3ERR_BADTYPE = 10007, + --NFS3ERR_JUKEBOX = 10008, + }, + }, + -- Unfortunately the NFS procedure numbers differ in between versions Procedure = { @@ -665,48 +886,10 @@ return o end, - --- Connects to the nfsd program + --- Decodes the READDIR section of a NFS ReadDir response -- - -- @param host table - -- @param port table - -- @param version number containing the program version to use - -- @return status boolean true on success, false on failure - -- @return result string containing error message (if status is false) - Connect = function( self, host, port, version ) - local socket = nmap.new_socket() - local status, result = socket:connect(host.ip, port.number, port.protocol) - - if ( status ) then - self.socket = socket - self.proto = port.protocol - self.version = ( nmap.registry.args and nmap.registry.args['nfs.version'] ) and tonumber(nmap.registry.args['nfs.version']) or version - - if ( self.version > Version["nfs"].max or self.version < Version["nfs"].min ) then - return false, "Library does not support nfsd version: " .. self.version - end - - self.comm = Comm:new( { socket = socket, proto=port.protocol} ) - end - - return status, result - end, - - --- Disconnects from the nfsd program - -- - -- @return status boolean true on success, false on failure - -- @return result string containing error message (if status is false) - Disconnect = function( self ) - local status, result = self.socket:close() - if ( status ) then - self.proto = nil - self.socket = nil - self.comm = nil - end - return status, result - end, - - --- Decodes the READDIR section of a NFS ReadDir response - -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @param data string containing the buffer of bytes read so far -- @param pos number containing the current offset into data -- @return pos number containing the offset after the decoding @@ -716,47 +899,54 @@ -- table for each file/directory entry. It has the following fields -- file_id, name and cookie -- - ReadDirDecode = function( self, data, pos ) - + ReadDirDecode = function( self, comm, data, pos ) local entry, response = {}, {} local value_follows local status, _ - local NFS_OK = 0 - - status, data = self.comm:GetAdditionalBytes( data, pos, 4 ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, status = bin.unpack(">I", data, pos) - if status ~= NFS_OK then + if (status ~= NFS.stat_code[comm.version].NFS_OK) then + if (NFS.stat_msg[status]) then + stdnse.print_debug(string.format("READDIR query failed: %s", NFS.stat_msg[status])) + else + stdnse.print_debug(string.format("READDIR query failed: code %d", status)) + end return -1, nil end - if ( 3 == self.version ) then + if ( 3 == comm.version ) then local attrib = {} response.attributes = {} - status, data = self.comm:GetAdditionalBytes( data, pos, 4 ) - if( not(status) ) then - return false, "NFS.ReadDirDecode failed to get additional bytes from socket" + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end + pos, value_follows = bin.unpack(">I", data, pos) if value_follows == 0 then return -1, nil end - status, data = self.comm:GetAdditionalBytes( data, pos, 84 ) - if( not(status) ) then - return false, "NFS.ReadDirDecode failed to get additional bytes from socket" + status, data = comm:GetAdditionalBytes( data, pos, 84 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, attrib.type, attrib.mode, attrib.nlink, attrib.uid, attrib.gid, attrib.size, attrib.used, attrib.rdev, attrib.fsid, attrib.fileid, attrib.atime, attrib.mtime, attrib.ctime = bin.unpack(">IIIIILLLLLLLL", data, pos) table.insert(response.attributes, attrib) -- opaque data - status, data = self.comm:GetAdditionalBytes( data, pos, 8 ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + status, data = comm:GetAdditionalBytes( data, pos, 8 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, _ = bin.unpack(">L", data, pos) end @@ -764,9 +954,10 @@ response.entries = {} while true do entry = {} - status, data = self.comm:GetAdditionalBytes( data, pos, 4 ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, value_follows = bin.unpack(">I", data, pos) @@ -775,145 +966,150 @@ break end - if ( 3 == self.version ) then - status, data = self.comm:GetAdditionalBytes( data, pos, 8 ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + if ( 3 == comm.version ) then + status, data = comm:GetAdditionalBytes( data, pos, 8 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, entry.fileid = bin.unpack(">L", data, pos ) else - status, data = self.comm:GetAdditionalBytes( data, pos, 4 ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, entry.fileid = bin.unpack(">I", data, pos ) end - status, data = self.comm:GetAdditionalBytes( data, pos, 4 ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, entry.length = bin.unpack(">I", data, pos) - status, data = self.comm:GetAdditionalBytes( data, pos, entry.length ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + status, data = comm:GetAdditionalBytes( data, pos, entry.length ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, entry.name = bin.unpack("A" .. entry.length, data, pos) pos = pos + Util.CalcFillBytes( entry.length ) - if ( 3 == self.version ) then - status, data = self.comm:GetAdditionalBytes( data, pos, 8 ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + if ( 3 == comm.version ) then + status, data = comm:GetAdditionalBytes( data, pos, 8 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end - pos, entry.cookie = bin.unpack(">L", data, pos) else - status, data = self.comm:GetAdditionalBytes( data, pos, 4 ) - if ( not(status) ) then - return false, "ReadDirDecode failed" + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + stdnse.print_debug("NFS.ReadDirDecode: Failed to call GetAdditionalBytes") + return -1, nil end - pos, entry.cookie = bin.unpack(">I", data, pos) end table.insert( response.entries, entry ) end return pos, response end, - - + --- Reads the contents inside a NFS directory -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @param file_handle string containing the filehandle to query -- @return status true on success, false on failure -- @return table of file table entries as described in decodeReadDir - ReadDir = function( self, file_handle ) - + ReadDir = function( self, comm, file_handle ) local status, packet local cookie, count = 0, 8192 local pos, data, _ = 1, "", "" local header, response = {}, {} if ( not(file_handle) ) then - return false, "No filehandle received" + return false, "ReadDir: No filehandle received" end - if ( self.version == 3 ) then + if ( comm.version == 3 ) then local opaque_data = 0 data = bin.pack("A>L>L>I", file_handle, cookie, opaque_data, count) else data = bin.pack("A>I>I", file_handle, cookie, count) end - packet = self.comm:EncodePacket( nil, { id=Util.ProgNameToNumber("nfs"), version=self.version, proc=NFS.Procedure[self.version].READDIR }, { type=RPC.AuthType.Null }, data ) - status = self.comm:SendPacket( packet ) + packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].READDIR, { type=RPC.AuthType.Null }, data ) + if(not(comm:SendPacket( packet ))) then + return false, "ReadDir: Failed to send data" + end + status, data = comm:ReceivePacket() if ( not(status) ) then - return false, "nfsReadDir: Failed to write to socket" - end - - status, data = self.comm:ReceivePacket() - if ( not(status) ) then - return false, "nfsReadDir: Failed to read data from socket" + return false, "ReadDir: Failed to read data from socket" end - pos, header = self.comm:DecodeHeader( data, pos ) - + pos, header = comm:DecodeHeader( data, pos ) if not header then - return false, "Failed to decode header" + return false, "ReadDir: Failed to decode header" end - pos, response = self:ReadDirDecode( data, pos ) + pos, response = self:ReadDirDecode( comm, data, pos ) + if (not(response)) then + return false, "ReadDir: Failed to decode the READDIR section" + end return true, response end, --- Gets filesystem stats (Total Blocks, Free Blocks and Available block) on a remote NFS share -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @param file_handle string containing the filehandle to query -- @return status true on success, false on failure -- @return statfs table with the fields transfer_size, block_size, -- total_blocks, free_blocks and available_blocks -- @return errormsg if status is false - StatFs = function( self, file_handle ) - + StatFs = function( self, comm, file_handle ) local status, packet local pos, data, _ = 1, "", "" local header, statfs = {}, {} - if ( self.version > 2 ) then - return false, ("Version %d not supported"):format(self.version) + if ( comm.version > 2 ) then + return false, ("StatFs: Version %d not supported"):format(comm.version) end if ( not(file_handle) or file_handle:len() ~= 32 ) then - return false, "Incorrect filehandle received" + return false, "StatFs: Incorrect filehandle received" end data = bin.pack("A", file_handle ) - packet = self.comm:EncodePacket( nil, { id=Util.ProgNameToNumber("nfs"), version=self.version, proc=NFS.Procedure[self.version].STATFS }, { type=RPC.AuthType.Null }, data ) - status = self.comm:SendPacket( packet ) - if ( not(status) ) then - return false, "nfsStatFs: Failed to write to socket" - end + packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].STATFS, { type=RPC.AuthType.Null }, data ) + if (not(comm:SendPacket( packet ))) then + return false, "StatFS: Failed to send data" + end - status, data = self.comm:ReceivePacket( ) + status, data = comm:ReceivePacket( ) if ( not(status) ) then - return false, "nfsStatFs: Failed to read data from socket" + return false, "StatFs: Failed to read data from socket" end - pos, header = self.comm:DecodeHeader( data, pos ) - + pos, header = comm:DecodeHeader( data, pos ) if not header then - return false, "Failed to decode header" + return false, "StatFs: Failed to decode header" end - pos, statfs = self:StatFsDecode( data, pos ) + pos, statfs = self:StatFsDecode( comm, data, pos ) if not statfs then - return false, "Failed to decode statfs structure" + return false, "StatFs: Failed to decode statfs structure" end return true, statfs end, --- Attempts to decode the attributes section of the reply -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @param data string containing the full statfs reply -- @param pos number pointing to the statfs section of the reply -- @return pos number containing the offset after decoding @@ -922,117 +1118,130 @@ -- blocksize, rdev, blocks, fsid, -- fileid, atime, mtime and ctime -- - GetAttrDecode = function( self, data, pos ) + GetAttrDecode = function( self, comm, data, pos ) + local status local attrib = {} - local catch = function() socket:close() end - local try = nmap.new_try(catch) - local NFS_OK = 0 - local status - status, data = self.comm:GetAdditionalBytes( data, pos, 4 ) - if ( not(status) ) then - return false, "GetAttrDecode: GetAdditionalBytes failed" + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + stdnse.print_debug("GetAttrDecode: Failed to call GetAdditionalBytes") + return -1, nil end - pos, attrib.status = bin.unpack(">I", data, pos) - if attrib.status ~= NFS_OK then + if (attrib.status ~= NFS.stat_code[comm.version].NFS_OK) then + if (NFS.stat_msg[attrib.status]) then + stdnse.print_debug(string.format("GETATTR query failed: %s", NFS.stat_msg[attrib.status])) + else + stdnse.print_debug(string.format("GETATTR query failed: code %d", attrib.status)) + end return -1, nil end - if ( self.version < 3 ) then - status, data = self.comm:GetAdditionalBytes( data, pos, 64 ) - if ( not(status) ) then - return false, "GetAttrDecode: GetAdditionalBytes failed" + if ( comm.version < 3 ) then + status, data = comm:GetAdditionalBytes( data, pos, 64 ) + if (not(status)) then + stdnse.print_debug("GetAttrDecode: Failed to call GetAdditionalBytes") + return -1, nil end pos, attrib.type, attrib.mode, attrib.nlink, attrib.uid, attrib.gid, attrib.size, attrib.blocksize, attrib.rdev, attrib.blocks, attrib.fsid, attrib.fileid, attrib.atime, attrib.mtime, attrib.ctime = bin.unpack( ">IIIIIIIIIILLL", data, pos ) - elseif ( self.version == 3 ) then - status, data = self.comm:GetAdditionalBytes( data, pos, 84 ) - if ( not(status) ) then - return false, "GetAttrDecode: GetAdditionalBytes failed" - end - pos, attrib.type, attrib.mode, attrib.nlink, attrib.uid, - attrib.gid, attrib.size, attrib.used, attrib.rdev, - attrib.fsid, attrib.fileid, attrib.atime, attrib.mtime, - attrib.ctime = bin.unpack(">IIIIILLLLLLLL", data, pos) - else - return -1, "Unsupported version" + elseif ( comm.version == 3 ) then + status, data = comm:GetAdditionalBytes( data, pos, 84 ) + if (not(status)) then + stdnse.print_debug("GetAttrDecode: Failed to call GetAdditionalBytes") + return -1, nil end - return pos, attrib - end, + pos, attrib.type, attrib.mode, attrib.nlink, attrib.uid, + attrib.gid, attrib.size, attrib.used, attrib.rdev, + attrib.fsid, attrib.fileid, attrib.atime, attrib.mtime, + attrib.ctime = bin.unpack(">IIIIILLLLLLLL", data, pos) + else + stdnse.print_debug("GetAttrDecode: Unsupported version") + return -1, nil + end + return pos, attrib + end, - --- Gets mount attributes (uid, gid, mode, etc ..) from a remote NFS share - -- - -- @param file_handle string containing the filehandle to query - -- @return status true on success, false on failure - -- @return attribs table with the fields type, mode, - -- nlink, uid, gid, size, - -- blocksize, rdev, blocks, fsid, - -- fileid, atime, mtime and ctime - -- @return errormsg if status is false - GetAttr = function( self, file_handle ) - local data, packet, status, attribs, pos, header + --- Gets mount attributes (uid, gid, mode, etc ..) from a remote NFS share + -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation + -- @param file_handle string containing the filehandle to query + -- @return status true on success, false on failure + -- @return attribs table with the fields type, mode, + -- nlink, uid, gid, size, + -- blocksize, rdev, blocks, fsid, + -- fileid, atime, mtime and ctime + -- @return errormsg if status is false + GetAttr = function( self, comm, file_handle ) + local data, packet, status, attribs, pos, header - data = bin.pack("A", file_handle) - packet = self.comm:EncodePacket( nil, { id=Util.ProgNameToNumber("nfs"), version=self.version, proc=NFS.Procedure[self.version].GETATTR }, { type=RPC.AuthType.Null }, data ) - status = self.comm:SendPacket(packet) - if ( not(status) ) then - return false, "nfsGetAttribs: Failed to send data to socket" - end + data = bin.pack("A", file_handle) + packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].GETATTR, { type=RPC.AuthType.Null }, data ) + if(not(comm:SendPacket(packet))) then + return false, "GetAttr: Failed to send data" + end - status, data = self.comm:ReceivePacket() - if ( not(status) ) then - return false, "nfsGetAttribs: Failed to read data from socket" - end + status, data = comm:ReceivePacket() + if ( not(status) ) then + return false, "GetAttr: Failed to read data from socket" + end - pos, header = self.comm:DecodeHeader( data, 1 ) + pos, header = comm:DecodeHeader( data, 1 ) + if not header then + return false, "GetAttr: Failed to decode header" + end - if not header then - return false, "Failed to decode header" - end + pos, attribs = self:GetAttrDecode(comm, data, pos ) + if not attribs then + return false, "GetAttr: Failed to decode attrib structure" + end - pos, attribs = self:GetAttrDecode( data, pos ) + return true, attribs + end, - if not attribs then - return false, "Failed to decode attrib structure" - end + --- Attempts to decode the StatFS section of the reply + -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation + -- @param data string containing the full statfs reply + -- @param pos number pointing to the statfs section of the reply + -- @return pos number containing the offset after decoding + -- @return statfs table with the following fields: transfer_size, block_size, + -- total_blocks, free_blocks and available_blocks + StatFsDecode = function( self, comm, data, pos ) + local status + local statfs = {} - return true, attribs - end, + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if (not(status)) then + stdnse.print_debug("StatFsDecode: Failed to call GetAdditionalBytes") + return -1, nil + end + pos, statfs.status = bin.unpack(">I", data, pos) - --- Attempts to decode the StatFS section of the reply - -- - -- @param data string containing the full statfs reply - -- @param pos number pointing to the statfs section of the reply - -- @return pos number containing the offset after decoding - -- @return statfs table with the following fields: transfer_size, block_size, - -- total_blocks, free_blocks and available_blocks - -- - StatFsDecode = function( self, data, pos ) - local catch = function() socket:close() end - local try = nmap.new_try(catch) - local statfs = {} - local NFS_OK, NSFERR_ACCESS = 0, 13 - - data = try( self.comm:GetAdditionalBytes( data, pos, 4 ) ) - pos, statfs.status = bin.unpack(">I", data, pos) - - if statfs.status ~= NFS_OK then - if statfs.status == NSFERR_ACCESS then - stdnse.print_debug("STATFS query received NSFERR_ACCESS") - end - return -1, nil + if (statfs.status ~= NFS.stat_code[comm.version].NFS_OK) then + if (NFS.stat_msg[statfs.status]) then + stdnse.print_debug(string.format("STATFS query failed: %s", NFS.stat_msg[statfs.status])) + else + stdnse.print_debug(string.format("STATFS query failed: code %d", statfs.status)) end + return -1, nil + end - data = try( self.comm:GetAdditionalBytes( data, pos, 20 ) ) - pos, statfs.transfer_size, statfs.block_size, - statfs.total_blocks, statfs.free_blocks, - statfs.available_blocks = bin.unpack(">IIIII", data, pos ) - return pos, statfs - end, + status, data = comm:GetAdditionalBytes( data, pos, 20 ) + if (not(status)) then + stdnse.print_debug("StatFsDecode: Failed to call GetAdditionalBytes") + return -1, nil + end + pos, statfs.transfer_size, statfs.block_size, + statfs.total_blocks, statfs.free_blocks, + statfs.available_blocks = bin.unpack(">IIIII", data, pos ) + return pos, statfs + end, } Helper = { @@ -1045,33 +1254,28 @@ -- @return status true on success, false on failure -- @return result table of string entries or error message on failure ShowMounts = function( host, port ) - - local data, prog_tbl = {}, {} - local status, result, mounts, response - local socket = nmap.new_socket() - local mountd - local ver - local mnt = Mount:new() + local status, result, mounts + local mountd, mnt_comm + local mnt = Mount:new() local portmap = Portmap:new() - local portmap_table, proginfo status, mountd = Helper.GetProgramInfo( host, port, "mountd") - if ( not(status) ) then - return false, "Failed to retrieve rpc information for mountd" + return false, string.format("rpc.Helper.ShowMounts: %s", mountd) end - - status, result = mnt:Connect( host, mountd.port, mountd.version ) + + mnt_comm = Comm:new({program='mountd', version=mountd.version}) + status, result = mnt_comm:connect(host, mountd.port) if ( not(status) ) then - stdnse.print_debug(3, result) - return false, result + return false, string.format("rpc.Helper.ShowMounts: %s",result) end - - status, mounts = mnt:Export() - - mnt:Disconnect() - - return status, mounts + status, mounts = mnt:Export(mnt_comm) + mnt_comm:disconnect() + if (status == false) then + return status, string.format("rpc.Helper.ShowMounts: %s", mounts) + else + return status, mounts + end end, --- Retrieves NFS storage statistics @@ -1083,53 +1287,55 @@ -- @return statfs table with the fields transfer_size, block_size, -- total_blocks, free_blocks and available_blocks ExportStats = function( host, port, path ) - local fhandle local stats, status, result + local mnt_comm, nfs_comm local mountd, nfsd = {}, {} local mnt, nfs = Mount:new(), NFS:new() - + status, mountd = Helper.GetProgramInfo( host, port, "mountd", 2) if ( not(status) ) then - return false, "Failed to retrieve rpc information for mountd" + return false, string.format("rpc.Helper.ExportStats: %s", mountd) end status, nfsd = Helper.GetProgramInfo( host, port, "nfs", 2) if ( not(status) ) then - return false, "Failed to retrieve rpc information for nfsd" + return false, string.format("rpc.Helper.ExportStats: %s", nfsd) end + mnt_comm = Comm:new({program = 'mountd', version = mountd.version}) + nfs_comm = Comm:new({program = 'nfs', version = nfsd.version}) - status, result = mnt:Connect( host, mountd.port, mountd.version ) + if (nfs_comm.version ~= mnt_comm.version and nfs_comm.version ~= 3) then + return false, string.format("rpc.Helper.ExportStats: nfs and mount versions mismatch") + end + status, result = mnt_comm:connect(host, mountd.port) if ( not(status) ) then - return false, "Failed to connect to mountd program" + return false, string.format("rpc.Helper.ExportStats: %s",result) end - - status, result = nfs:Connect( host, nfsd.port, nfsd.version ) + status, result = nfs_comm:connect(host, nfsd.port) if ( not(status) ) then - mnt:Disconnect() - return false, "Failed to connect to nfsd program" + return false, string.format("rpc.Helper.ExportStats: %s",result) end - status, fhandle = mnt:Mount( path ) + status, fhandle = mnt:Mount(mnt_comm, path) if ( not(status) ) then - mnt:Disconnect() - nfs:Disconnect() - stdnse.print_debug("rpc.Helper.ExportStats: mount failed") - return false, "Mount failed" + mnt_comm:disconnect() + nfs_comm:disconnect() + return false, string.format("rpc.Helper.ExportStats: %s", fhandle) end - - status, stats = nfs:StatFs( fhandle ) + status, stats = nfs:StatFs(nfs_comm, fhandle) if ( not(status) ) then - mnt:Disconnect() - nfs:Disconnect() - return false, stats + mnt_comm:disconnect() + nfs_comm:disconnect() + return false, string.format("rpc.Helper.ExportStats: %s", stats) end - status, fhandle = mnt:Unmount( path ) - - mnt:Disconnect() - nfs:Disconnect() - + status, fhandle = mnt:Unmount(mnt_comm, path) + mnt_comm:disconnect() + nfs_comm:disconnect() + if ( not(status) ) then + return false, string.format("rpc.Helper.ExportStats: %s", fhandle) + end return true, stats end, @@ -1145,54 +1351,56 @@ local fhandle local dirs, status, result local mountd, nfsd = {}, {} + local mnt_comm, nfs_comm local mnt, nfs = Mount:new(), NFS:new() status, mountd = Helper.GetProgramInfo( host, port, "mountd") if ( not(status) ) then - return false, "Failed to retrieve rpc information for mountd" + return false, string.format("rpc.Helper.Dir: %s", mountd) end status, nfsd = Helper.GetProgramInfo( host, port, "nfs") if ( not(status) ) then - return false, "Failed to retrieve rpc information for nfsd" + return false, string.format("rpc.Helper.Dir: %s", nfsd) end - status, result = mnt:Connect( host, mountd.port, mountd.version ) + mnt_comm = Comm:new({program = 'mountd', version = mountd.version}) + nfs_comm = Comm:new({program = 'nfs', version = nfsd.version}) + + if (nfs_comm.version ~= mnt_comm.version and nfs_comm.version ~= 3) then + return false, string.format("rpc.Helper.ExportStats: nfs and mount versions mismatch") + end + status, result = mnt_comm:connect(host, mountd.port) if ( not(status) ) then - return false, "Failed to connect to mountd program" + return false, string.format("rpc.Helper.Dir: %s",result) end - status, result = nfs:Connect( host, nfsd.port, nfsd.version ) + status, result = nfs_comm:connect(host, nfsd.port) if ( not(status) ) then - mnt:Disconnect() - return false, "Failed to connect to nfsd program" + return false, string.format("rpc.Helper.Dir: %s",result) end - status, fhandle = mnt:Mount( path ) + status, fhandle = mnt:Mount(mnt_comm, path ) if ( not(status) ) then - mnt:Disconnect() - nfs:Disconnect() - return false, "rpc.Helper.Dir: mount failed" + mnt_comm:disconnect() + nfs_comm:disconnect() + return false, string.format("rpc.Helper.Dir: %s",fhandle) end - status, dirs = nfs:ReadDir( fhandle ) + status, dirs = nfs:ReadDir(nfs_comm, fhandle ) if ( not(status) ) then - mnt:Disconnect() - nfs:Disconnect() - return false, "rpc.Helper.Dir: statfs failed" + mnt_comm:disconnect() + nfs_comm:disconnect() + return false, string.format("rpc.Helper.Dir: %s", dirs) end - status, fhandle = mnt:Unmount( path ) - - mnt:Disconnect() - nfs:Disconnect() - + status, fhandle = mnt:Unmount(mnt_comm, path) + mnt_comm:disconnect() + nfs_comm:disconnect() if ( not(status) ) then - return false, "rpc.Helper.Dir: mount failed" + return false, string.format("rpc.Helper.Dir: %s", fhandle) end - return true, dirs - end, --- Retrieves NFS Attributes @@ -1206,51 +1414,58 @@ GetAttributes = function( host, port, path ) local fhandle local attribs, status, result + local mnt_comm, nfs_comm local mountd, nfsd = {}, {} local mnt, nfs = Mount:new(), NFS:new() status, mountd = Helper.GetProgramInfo( host, port, "mountd") if ( not(status) ) then - return false, "Failed to retrieve rpc information for mountd" + return false, string.format("rpc.Helper.GetAttributes: %s", mountd) end status, nfsd = Helper.GetProgramInfo( host, port, "nfs") if ( not(status) ) then - return false, "Failed to retrieve rpc information for nfsd" + return false, string.format("rpc.Helper.GetAttributes: %s", nfsd) end + + mnt_comm = Comm:new({program = 'mountd', version = mountd.version}) + nfs_comm = Comm:new({program = 'nfs', version = nfsd.version}) - status, result = mnt:Connect( host, mountd.port, mountd.version ) + if (nfs_comm.version ~= mnt_comm.version and nfs_comm.version ~= 3) then + return false, string.format("rpc.Helper.ExportStats: nfs and mount versions mismatch") + end + + status, result = mnt_comm:connect(host, mountd.port) if ( not(status) ) then - return false, "Failed to connect to mountd program" + return false, string.format("rpc.Helper.GetAttributes: %s",result) end - status, result = nfs:Connect( host, nfsd.port, nfsd.version ) + status, result = nfs_comm:connect(host, nfsd.port) if ( not(status) ) then - mnt:Disconnect() - return false, "Failed to connect to nfsd program" + mnt_comm:disconnect() + return false, string.format("rpc.Helper.GetAttributes: %s",result) end - status, fhandle = mnt:Mount( path ) + status, fhandle = mnt:Mount(mnt_comm, path) if ( not(status) ) then - mnt:Disconnect() - nfs:Disconnect() - return false, "rpc.Helper.GetAttributes: mount failed" + mnt_comm:disconnect() + nfs_comm:disconnect() + return false, string.format("rpc.Helper.GetAttributes: %s", fhandle) end - status, attribs = nfs:GetAttr( fhandle ) + status, attribs = nfs:GetAttr(nfs_comm, fhandle) if ( not(status) ) then - mnt:Disconnect() - nfs:Disconnect() - return false, "rpc.Helper.GetAttributes: GetAttr failed" + mnt_comm:disconnect() + nfs_comm:disconnect() + return false, string.format("rpc.Helper.GetAttributes: %s", attribs) end - status, fhandle = mnt:Unmount( path ) + status, fhandle = mnt:Unmount(mnt_comm, path) - mnt:Disconnect() - nfs:Disconnect() - + mnt_comm:disconnect() + nfs_comm:disconnect() if ( not(status) ) then - return false, "rpc.Helper.ExportStats: mount failed" + return false, string.format("rpc.Helper.GetAttributes: %s", fhandle) end return true, attribs @@ -1264,20 +1479,21 @@ -- @return table containing the portmapper information as returned by -- Portmap.Dump RpcInfo = function( host, port ) + local comm = Comm:new({program='rpcbind', version=2}) local portmap = Portmap:new() - local status = Portmap:Connect(host, port) - local result - - if ( not(status) ) then - return + local status, result = comm:connect(host, port) + if (status == false) then + return false, string.format("rpc.Helper.RpcInfo: %s", result) end + status, result = portmap:Dump(comm) + comm:disconnect() + if (status == false) then + return status, string.format("rpc.Helper.RpcInfo: %s", result) + else + return status, result + end + end, - status, result = portmap:Dump() - portmap:Disconnect() - - return status, result - end, - --- Queries the portmapper for a port for the specified RPC program -- -- @param host table @@ -1288,18 +1504,21 @@ -- @return table containing the portmapper information as returned by -- Portmap.Dump GetPortForProgram = function( host, port, program_id, protocol ) + local comm = Comm:new({program='rpcbind', version=2}) local portmap = Portmap:new() - local status = Portmap:Connect(host, port) - local result + + local status, result = comm:connect(host, port) + if (status == false) then + return false, string.format("rpc.Helper.GetPortForProgram: %s", result) + end - if ( not(status) ) then - return + status, result = portmap:GetPort(comm, program_id, protocol, 1 ) + comm:disconnect() + if (status == false) then + return status, string.format("rpc.Helper.GetPortFroProgram: %s", result) + else + return status, result end - - status, result = portmap:GetPort( program_id, protocol, 1 ) - portmap:Disconnect() - - return status, result end, --- Get RPC program information @@ -1312,24 +1531,12 @@ -- @return info table containing port, port.number -- port.protocol and version GetProgramInfo = function( host, port, program, max_version ) - - local status, response - local portmap_table, info - local portmap = Portmap:new() + local info - status, response = portmap:Connect( host, port ) + local status, portmap_table = Helper.RpcInfo(host, port) if ( not(status) ) then - return false, "rpc.Helper.ShowMounts: Failed to connect to portmap" + return false, portmap_table end - status, portmap_table = portmap:Dump() - if ( not(status) ) then - portmap:Disconnect() - return false, "rpc.Helper.ShowMounts: Failed to GetProgramVersions" - end - status = portmap:Disconnect() - if ( not(status) ) then - return false, "rpc.Helper.ShowMounts: Failed to disconnect from portmap" - end -- assume failure status = false @@ -1343,12 +1550,12 @@ info.port.number = tmp[p].port info.port.protocol = p -- choose the highest version available - if ( not(Version[program]) ) then + if ( not(RPC_version[program]) ) then info.version = tmp[p].version[#tmp[p].version] status = true else for i=#tmp[p].version, 1, -1 do - if ( Version[program].max >= tmp[p].version[i] ) then + if ( RPC_version[program].max >= tmp[p].version[i] ) then if ( not(max_version) ) then info.version = tmp[p].version[i] status = true @@ -1372,32 +1579,6 @@ } ---- Container class for RPC constants -RPC = -{ - AuthType = - { - Null = 0 - }, - - MessageType = - { - Call = 0, - Reply = 1 - }, - - Procedure = - { - [2] = - { - GETPORT = 3, - DUMP = 4, - }, - - }, - -} - --- Portmap class Portmap = { @@ -1412,41 +1593,11 @@ self.__index = self return o end, - - --- Connects to the Portmapper - -- - -- @param host table - -- @param port table - -- @param version number containing the program version to use - -- @return status boolean true on success, false on failure - -- @return result string containing error message (if status is false) - Connect = function( self, host, port, version ) - local socket = nmap.new_socket() - local status, result = socket:connect(host.ip, port.number, port.protocol) - if ( status ) then - self.socket = socket - self.version = version or 2 - self.protocol = port.protocol - end - - return status, result - end, - - --- Disconnects from the portmapper program - -- - -- @return status boolean true on success, false on failure - -- @return result string containing error message (if status is false) - Disconnect = function( self ) - local status, result = self.socket:close() - if ( status ) then - self.socket = nil - end - return status, result - end, - --- Dumps a list of RCP programs from the portmapper -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @return status boolean true on success, false on failure -- @return result table containing RPC program information or error message -- on failure. The table has the following format: @@ -1460,20 +1611,16 @@ -- o program_id is the number associated with the program -- o protocol is either "tcp" or "udp" -- - Dump = function( self ) + Dump = function(self, comm) local status, data, packet, response, pos, header - - local prog_id = Util.ProgNameToNumber("rpcbind") - local prog_proc = RPC.Procedure[self.version].DUMP - local comm - if ( self.program_table ) then return true, self.program_table end - comm = Comm:new( { socket=self.socket, proto=self.protocol } ) - packet = comm:EncodePacket( nil, { id=prog_id, version=self.version, proc=prog_proc }, { type=RPC.AuthType.Null }, data ) - status, response = comm:SendPacket( packet ) + packet = comm:EncodePacket( nil, RPC.Procedure[comm.version].DUMP, { type=RPC.AuthType.Null }, data ) + if (not(comm:SendPacket(packet))) then + return false, "Portmap.Dump: Failed to send data" + end status, data = comm:ReceivePacket() if ( not(status) ) then return false, "Portmap.Dump: Failed to read data from socket" @@ -1481,10 +1628,10 @@ pos, header = comm:DecodeHeader( data, 1 ) if ( not(header) ) then - return false, "Failed to decode RPC header" + return false, "Portmap.Dump: Failed to decode RPC header" end if header.accept_state ~= 0 then - return false, string.format("RPC Accept State was not Successful") + return false, "RPC Accept State was not Successful" end self.program_table = {} @@ -1493,15 +1640,16 @@ local vfollows local program, version, protocol, port - status, data = comm:GetAdditionalBytes( data, pos, 4 ) - pos, vfollows = bin.unpack( ">I", data, pos ) - + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if ( not(status) ) then + return false, "Portmap.Dump: Failed to call GetAdditionalBytes" + end + pos, vfollows = bin.unpack( ">I", data, pos ) if ( vfollows == 0 ) then break end pos, program, version, protocol, port = bin.unpack(">IIII", data, pos) - if ( protocol == Portmap.PROTOCOLS.tcp ) then protocol = "tcp" elseif ( protocol == Portmap.PROTOCOLS.udp ) then @@ -1525,51 +1673,52 @@ --- Queries the portmapper for the port of the selected program, -- protocol and version -- + -- @param Comm object handles rpc program information and + -- low-level packet manipulation -- @param program string name of the program -- @param protocol string containing either "tcp" or "udp" -- @param version number containing the version of the queried program -- @return number containing the port number - GetPort = function( self, program, protocol, version ) + GetPort = function( self, comm, program, protocol, version ) local status, data, response, header, pos, packet local xid - local prog_id = Util.ProgNameToNumber("rpcbind") -- RPC Portmap - local prog_proc = RPC.Procedure[self.version].GETPORT - local comm if ( not( Portmap.PROTOCOLS[protocol] ) ) then - return false, ("Protocol %s not supported"):format(protocol) + return false, ("Portmap.GetPort: Protocol %s not supported"):format(protocol) end - if ( Util.ProgNameToNumber( program ) == nil ) then - return false, ("Unknown program name: %s"):format(program) + if ( Util.ProgNameToNumber(program) == nil ) then + return false, ("Portmap.GetPort: Unknown program name: %s"):format(program) end - comm = Comm:new( { socket=self.socket, proto=self.protocol } ) data = bin.pack( ">I>I>I>I", Util.ProgNameToNumber(program), version, Portmap.PROTOCOLS[protocol], 0 ) - packet = comm:EncodePacket( xid, { id=prog_id, version=self.version, proc=prog_proc }, { type=RPC.AuthType.Null }, data ) + packet = comm:EncodePacket( xid, RPC.Procedure[comm.version].GETPORT, { type=RPC.AuthType.Null }, data ) - status = comm:SendPacket(packet) + if (not(comm:SendPacket(packet))) then + return false, "Portmap.GetPort: Failed to send data" + end + data = "" - status, data = comm:ReceivePacket() if ( not(status) ) then - return false, "GetPort: Failed to read data from socket" + return false, "Portmap.GetPort: Failed to read data from socket" end - pos, header = comm:DecodeHeader( data, 1 ) - + pos, header = comm:DecodeHeader( data, 1 ) if ( not(header) ) then - return false, "Failed to decode RPC header" + return false, "Portmap.GetPort: Failed to decode RPC header" end if header.accept_state ~= 0 then - return false, string.format("RPC Accept State was not Successful") + return false, "RPC Accept State was not Successful" end - status, data = comm:GetAdditionalBytes( data, pos, 4 ) - return true, select(2, bin.unpack(">I", data, pos ) ) - + status, data = comm:GetAdditionalBytes( data, pos, 4 ) + if ( not(status) ) then + return false, "Portmap.GetPort: Failed to call GetAdditionalBytes" + end + return true, select(2, bin.unpack(">I", data, pos ) ) end, - + } --- Static class containing mostly conversion functions