Index: nselib/rpc.lua =================================================================== --- nselib/rpc.lua (revision 17337) +++ nselib/rpc.lua (working copy) @@ -12,7 +12,9 @@ -- -------- -- The library contains the following classes: -- o Comm +-- - Handles network 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 @@ -96,15 +98,26 @@ -- +-- RPC args using the nmap.registry.args +RPC_args = { + ["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" } +local RPC_PROTOCOLS = (nmap.registry.args and nmap.registry.args[RPC_args['rpcbind'].proto] and + type(nmap.registry.args[RPC_args['rpcbind'].proto]) == 'table') and + nmap.registry.args[RPC_args['rpcbind'].proto] 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 }, } @@ -114,13 +127,98 @@ -- Low-level communication class Comm = { - new = function(self,o) - o = o or {} - setmetatable(o, self) - self.__index = self + --- Creats a new rpc Comm object + -- + -- @param program name string + -- @param version number containing the program version to use + -- @return a new Comm object + new = function(self, program, version) + local o = {} + setmetatable(o, self) + self.__index = self + o.program = program + o.program_id = Util.ProgNameToNumber(program) + o:SetVersion(version) return o - end, - + end, + + --- Connects to the remote program + -- + -- @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 status, err, socket + status, err = self:ChkProgram() + if (not(status)) then + return status, err + end + status, err = self:ChkVersion() + if (not(status)) then + return status, err + end + socket = nmap.new_socket() + status, err = socket:connect(host.ip, port.number, port.protocol) + if (not(status)) then + return status, string.format("%s connect error: %s", self.program, err) + else + self.socket = socket + self.ip = host.ip + self.port = port.number + self.proto = port.protocol + return status, nil + end + 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 (not(status)) then + return status, string.format("%s disconnect error: %s", self.program, err) + end + self.socket=nil + return status, nil + end, + + --- Checks if the rpc program is supported + -- + -- @return status boolean true on success, false on failure + -- @return string containing error message (if status is false) + ChkProgram = function(self) + if (not(RPC_version[self.program])) then + return false, string.format("RPC library does not support: %s protocol", self.program) + end + return true, nil + end, + + --- Checks if the rpc program version is supported + -- + -- @return status boolean true on success, false on failure + -- @return string containing error message (if status is false) + ChkVersion = function(self) + 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 + return true, nil + end, + + --- Sets the rpc program version + -- + -- @return status boolean true + SetVersion = function(self, version) + if (RPC_version[self.program] and RPC_args[self.program] and + nmap.registry.args and nmap.registry.args[RPC_args[self.program].ver]) then + self.version = tonumber(nmap.registry.args[RPC_args[self.program].ver]) + elseif (not(self.version) and version) then + self.version = version + end + 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 -- @@ -128,11 +226,10 @@ -- @param pos number containing the current offset into the buffer -- @param needed number containing the number of bytes needed to be available -- @return status success or failure - -- @return data string containing the data passed to the function and the additional data appended to it + -- @return data string containing the data passed to the function and the additional data appended to it or error message on failure 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 ) @@ -143,18 +240,17 @@ 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, error message on failure + CreateHeader = function( self, xid, procedure, auth ) local RPC_VERSION = 2 local packet @@ -162,10 +258,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 @@ -191,6 +287,7 @@ local tmp status, tmp = self:GetAdditionalBytes( data, pos, HEADER_LEN - ( data:len() - pos ) ) if not status then + stdnse.print_debug(string.format("Comm.ReceivePacket: failed to call GetAdditionalBytes")) return -1, nil end data = data .. tmp @@ -203,6 +300,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(string.format("Comm.ReceivePacket: failed to call GetAdditionalBytes")) return -1, nil end pos, header.verifier.data = bin.unpack("A" .. header.verifier.length - 8, data, pos ) @@ -213,10 +311,11 @@ --- Reads the response from the socket -- - -- @return data string containing the raw response + -- @return status true on success, false on failure + -- @return data string containing the raw response or error message on failure 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 @@ -230,9 +329,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 ) @@ -242,7 +341,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 -- @@ -279,20 +378,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 @@ -305,8 +402,9 @@ SendPacket = function( self, packet ) return self.socket:send( packet ) end, + +} -} --- Mount class handling communication with the mountd program -- @@ -315,6 +413,31 @@ -- Mount = { + StatMsg = { + [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.", + }, + + StatCode = { + 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,57 +452,17 @@ setmetatable(o, self) 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 ) + Export = function(self, comm) - local catch = function() self.socket:close() end - local try = nmap.new_try(catch) local msg_type = 0 - local prg_mount = Util.ProgNameToNumber("mountd") local packet local pos = 1 local header = {} @@ -389,35 +472,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, "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, "Mount.Export: Accept State was not Successful" end --- @@ -438,7 +525,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 ) @@ -452,10 +542,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 ) @@ -463,16 +559,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 ) @@ -483,22 +588,19 @@ 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() self.socket:close() end - local try = nmap.new_try(catch) - local packet, data - local prog_id = Util.ProgNameToNumber("mountd") + Mount = function(self, comm, path) + local packet, mount_status 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) @@ -506,50 +608,63 @@ data = data .. string.char( 0x00 ) 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 ) ) + 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 - 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, "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, "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.StatCode.MNT_OK) then + if (Mount.StatMsg[mount_status]) then + return false, string.format("Mount query failed: %s",Mount.StatMsg[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" @@ -560,19 +675,16 @@ --- 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() self.socket:close() end - local try = nmap.new_try(catch) - local packet, data - local prog_id = Util.ProgNameToNumber("mountd") + Unmount = function(self, comm, path) + local packet, status 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) @@ -580,29 +692,31 @@ 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, "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, "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, "" @@ -617,6 +731,118 @@ -- NFS = { + -- NFS error msg v2 and v3 + StatMsg = { + [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] = "An error occurred on the server which does not map to any of the legal NFS version 3 protocol error values.", + [10007] = "An attempt was made to create an object of a type not supported by the server.", + [10008] = "The server initiated the request, but was not able to complete it in a timely fashion.", + }, + + StatCode = { + -- 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, + NFSERR_SERVERFAULT = 10006, + NFSERR_BADTYPE = 10007, + NFSERR_JUKEBOX = 10008, + }, + }, + -- Unfortunately the NFS procedure numbers differ in between versions Procedure = { @@ -664,50 +890,12 @@ setmetatable(o, self) self.__index = self return o - end, + 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 @@ -717,47 +905,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.StatCode[comm.version].NFS_OK) then + if (NFS.StatMsg[status]) then + stdnse.print_debug(string.format("READDIR query failed: %s", NFS.StatMsg[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 @@ -765,9 +960,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) @@ -776,61 +972,66 @@ 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 @@ -838,83 +1039,88 @@ 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 @@ -923,117 +1129,134 @@ -- blocksize, rdev, blocks, fsid, -- fileid, atime, mtime and ctime -- - GetAttrDecode = function( self, data, pos ) + GetAttrDecode = function( self, comm, data, pos ) local attrib = {} - local catch = function() self.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.StatCode[comm.version].NFS_OK) then + if (NFS.StatMsg[attrib.status]) then + stdnse.print_debug(string.format("GETATTR query failed: %s", NFS.StatMsg[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 ( comm.version < 3 ) then + status, data = comm:GetAdditionalBytes( data, pos, 64 ) if ( not(status) ) then - return false, "GetAttrDecode: GetAdditionalBytes failed" + 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) - --- 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 + else + stdnse.print_debug("GetAttrDecode: Unsupported version") + return -1, nil + end - 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 + return pos, attrib + end, - status, data = self.comm:ReceivePacket() - if ( not(status) ) then - return false, "nfsGetAttribs: Failed to read data from socket" - end + --- 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 - pos, header = self.comm:DecodeHeader( data, 1 ) + 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 - if not header then - return false, "Failed to decode header" - end + status, data = comm:ReceivePacket() + if ( not(status) ) then + return false, "GetAttr: Failed to read data from socket" + end - pos, attribs = self:GetAttrDecode( data, pos ) + pos, header = comm:DecodeHeader( data, 1 ) + if not header then + return false, "GetAttr: Failed to decode header" + end - if not attribs then - return false, "Failed to decode attrib structure" - end + pos, attribs = self:GetAttrDecode(comm, data, pos ) + if not attribs then + return false, "GetAttr: Failed to decode attrib structure" + end - return true, attribs - end, + return true, attribs + end, - --- 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() self.socket:close() end - local try = nmap.new_try(catch) - local statfs = {} - local NFS_OK, NSFERR_ACCESS = 0, 13 + --- 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 = {} - data = try( self.comm:GetAdditionalBytes( data, pos, 4 ) ) - pos, statfs.status = bin.unpack(">I", data, pos) + 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) - 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.StatCode[comm.version].NFS_OK) then + if (NFS.StatMsg[statfs.status]) then + stdnse.print_debug(string.format("STATFS query failed: %s", NFS.StatMsg[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 = { @@ -1046,33 +1269,29 @@ -- @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 status, string.format("rpc.Helper.ShowMounts: %s", mountd) + end + mnt_comm = Comm:new('mountd', mountd.version) + status, result = mnt_comm:Connect(host, mountd.port) if ( not(status) ) then - return false, "Failed to retrieve rpc information for mountd" + return false, string.format("rpc.Helper.ShowMounts: %s", result) end - - status, result = mnt:Connect( host, mountd.port, mountd.version ) + status, mounts = mnt:Export(mnt_comm) + mnt_comm:Disconnect() if ( not(status) ) then - stdnse.print_debug(3, result) - return false, result + return status, string.format("rpc.Helper.ShowMounts: %s", mounts) + else + return status, mounts end - - status, mounts = mnt:Export() - - mnt:Disconnect() - - return status, mounts end, --- Retrieves NFS storage statistics @@ -1084,53 +1303,58 @@ -- @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 status, 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 status, string.format("rpc.Helper.ExportStats: %s", nfsd) end + mnt_comm = Comm:new('mountd', mountd.version) + nfs_comm = Comm:new('nfs', nfsd.version) - status, result = mnt:Connect( host, mountd.port, mountd.version ) + -- TODO: recheck the version mismatch when adding NFSv4 + if (nfs_comm.version <= 2 and mnt_comm.version > 2) then + return status, string.format("rpc.Helper.ExportStats: versions mismatch, nfs v%d - mount v%d", + nfs_comm.version, mnt_comm.version) + end + status, result = mnt_comm:Connect(host, mountd.port) if ( not(status) ) then - return false, "Failed to connect to mountd program" + return status, 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" + mnt_comm:Disconnect() + return status, 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 status, 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 status, 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 status, string.format("rpc.Helper.ExportStats: %s", fhandle) + end return true, stats end, @@ -1142,58 +1366,62 @@ -- @return status true on success, false on failure -- @return table of file table entries as described in decodeReadDir Dir = function( host, port, path ) - 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 status, 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 status, string.format("rpc.Helper.Dir: %s", nfsd) end - status, result = mnt:Connect( host, mountd.port, mountd.version ) + mnt_comm = Comm:new('mountd', mountd.version) + nfs_comm = Comm:new('nfs', nfsd.version) + + -- TODO: recheck the version mismatch when adding NFSv4 + if (nfs_comm.version <= 2 and mnt_comm.version > 2) then + return status, string.format("rpc.Helper.Dir: versions mismatch, nfs v%d - mount v%d", + nfs_comm.version, mnt_comm.version) + end + status, result = mnt_comm:Connect(host, mountd.port) if ( not(status) ) then - return false, "Failed to connect to mountd program" + return status, 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" + mnt_comm:Disconnect() + return status, 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 status, 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 status, 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 status, string.format("rpc.Helper.Dir: %s", fhandle) end - return true, dirs - end, --- Retrieves NFS Attributes @@ -1207,51 +1435,60 @@ 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 status, 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 status, string.format("rpc.Helper.GetAttributes: %s", nfsd) end + + mnt_comm, result = Comm:new('mountd', mountd.version) + nfs_comm, result = Comm:new('nfs', nfsd.version) - status, result = mnt:Connect( host, mountd.port, mountd.version ) + -- TODO: recheck the version mismatch when adding NFSv4 + if (nfs_comm.version <= 2 and mnt_comm.version > 2) then + return status, string.format("rpc.Helper.GetAttributes: versions mismatch, nfs v%d - mount v%d", + nfs_comm.version, mnt_comm.version) + end + + status, result = mnt_comm:Connect(host, mountd.port) if ( not(status) ) then - return false, "Failed to connect to mountd program" + return status, 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 status, 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 status, 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 status, 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 status, string.format("rpc.Helper.GetAttributes: %s", fhandle) end return true, attribs @@ -1265,20 +1502,23 @@ -- @return table containing the portmapper information as returned by -- Portmap.Dump RpcInfo = function( host, port ) + local status, result local portmap = Portmap:new() - local status = Portmap:Connect(host, port) - local result + local comm = Comm:new('rpcbind', 2) - if ( not(status) ) then - return + status, result = comm:Connect(host, port) + if (not(status)) then + return status, string.format("rpc.Helper.RpcInfo: %s", result) end + status, result = portmap:Dump(comm) + comm:Disconnect() + if (not(status)) then + return status, string.format("rpc.Helper.RpcInfo: %s", result) + 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 @@ -1289,17 +1529,21 @@ -- @return table containing the portmapper information as returned by -- Portmap.Dump GetPortForProgram = function( host, port, program_id, protocol ) + local status, result local portmap = Portmap:new() - local status = Portmap:Connect(host, port) - local result + local comm = Comm:new('rpcbind', 2) + + status, result = comm:Connect(host, port) + if (not(status)) then + return status, 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 (not(status)) then + return status, string.format("rpc.Helper.GetPortForProgram: %s", result) end - status, result = portmap:GetPort( program_id, protocol, 1 ) - portmap:Disconnect() - return status, result end, @@ -1313,24 +1557,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 status, 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 @@ -1344,12 +1576,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 @@ -1412,42 +1644,12 @@ setmetatable(o, self) 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) + end, - 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: @@ -1461,20 +1663,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" @@ -1482,10 +1680,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, "Portmap.Dump: RPC Accept State was not Successful" end self.program_table = {} @@ -1494,15 +1692,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 @@ -1526,51 +1725,53 @@ --- 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 ) 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, "Portmap.GetPort: 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