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