3493 lines
108 KiB
Lua
3493 lines
108 KiB
Lua
---
|
|
-- RPC Library supporting a very limited subset of operations.
|
|
--
|
|
-- The library works over both the UDP and TCP protocols. A subset of nfs and
|
|
-- mountd procedures are supported. The nfs and mountd programs support
|
|
-- versions 1 through 3. Authentication is supported using the NULL RPC
|
|
-- Authentication protocol
|
|
--
|
|
-- The library contains the following classes:
|
|
-- * <code>Comm </code>
|
|
-- ** Handles network connections.
|
|
-- ** Handles low-level packet sending, receiving, decoding and encoding.
|
|
-- ** Stores rpc programs info: socket, protocol, program name, id and version.
|
|
-- ** Used by Mount, NFS, RPC and Portmap.
|
|
-- * <code>Portmap</code>
|
|
-- ** Contains RPC constants.
|
|
-- ** Handles communication with the portmap RPC program.
|
|
-- * <code>Mount</code>
|
|
-- ** Handles communication with the mount RPC program.
|
|
-- * <code>NFS</code>
|
|
-- ** Handles communication with the nfs RPC program.
|
|
-- * <code>Helper</code>
|
|
-- ** Provides easy access to common RPC functions.
|
|
-- ** Implemented as a static class where most functions accept host and port parameters.
|
|
-- * <code>Util</code>
|
|
-- ** Mostly static conversion routines.
|
|
--
|
|
-- The portmapper dynamically allocates TCP/UDP ports to RPC programs. So in
|
|
-- in order to request a list of NFS shares from the server we need to:
|
|
-- * Make sure that we can talk to the portmapper on port 111 TCP or UDP.
|
|
-- * Query the portmapper for the ports allocated to the NFS program.
|
|
-- * Query the NFS program for a list of shares on the ports returned by the portmap program.
|
|
--
|
|
-- The Helper class contains functions that facilitate access to common
|
|
-- RPC program procedures through static class methods. Most functions accept
|
|
-- host and port parameters. As the Helper functions query the portmapper to
|
|
-- get the correct RPC program port, the port supplied to these functions
|
|
-- should be the rpcbind port 111/tcp or 111/udp.
|
|
--
|
|
-- The following sample code illustrates how scripts can use the <code>Helper</code> class
|
|
-- to interface the library:
|
|
--
|
|
-- <code>
|
|
-- -- retrieve a list of NFS export
|
|
-- status, mounts = rpc.Helper.ShowMounts( host, port )
|
|
--
|
|
-- -- iterate over every share
|
|
-- for _, mount in ipairs( mounts ) do
|
|
--
|
|
-- -- get the NFS attributes for the share
|
|
-- status, attribs = rpc.Helper.GetAttributes( host, port, mount.name )
|
|
-- .... process NFS attributes here ....
|
|
-- end
|
|
-- </code>
|
|
--
|
|
-- RPC transaction IDs (XID) are not properly implemented as a random ID is
|
|
-- generated for each client call. The library makes no attempt to verify
|
|
-- whether the returned XID is valid or not.
|
|
--
|
|
-- Therefore TCP is the preferred method of communication and the library
|
|
-- always attempts to connect to the TCP port of the RPC program first.
|
|
-- This behaviour can be overridden by setting the rpc.protocol argument.
|
|
-- The portmap service is always queried over the protocol specified in the
|
|
-- port information used to call the Helper function from the script.
|
|
--
|
|
-- When multiple versions exists for a specific RPC program the library
|
|
-- always attempts to connect using the highest available version.
|
|
--
|
|
-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
|
|
--
|
|
-- @author Patrik Karlsson <patrik@cqure.net>
|
|
--
|
|
-- @args nfs.version number If set overrides the detected version of nfs
|
|
-- @args mount.version number If set overrides the detected version of mountd
|
|
-- @args rpc.protocol table If set overrides the preferred order in which
|
|
-- protocols are tested. (ie. "tcp", "udp")
|
|
|
|
local datafiles = require "datafiles"
|
|
local datetime = require "datetime"
|
|
local math = require "math"
|
|
local nmap = require "nmap"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
_ENV = stdnse.module("rpc", stdnse.seeall)
|
|
|
|
-- Version 0.3
|
|
--
|
|
-- Created 01/24/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
|
-- Revised 02/22/2010 - v0.2 - cleanup, revised the way TCP/UDP are handled fo
|
|
-- encoding an decoding
|
|
-- Revised 03/13/2010 - v0.3 - re-worked library to be OO
|
|
-- Revised 04/18/2010 - v0.4 - Applied patch from Djalal Harouni with improved
|
|
-- error checking and re-designed Comm class. see:
|
|
-- http://seclists.org/nmap-dev/2010/q2/232
|
|
-- Revised 06/02/2010 - v0.5 - added code to the Util class to check for file
|
|
-- types and permissions.
|
|
-- Revised 06/04/2010 - v0.6 - combined Portmap and RPC classes in the
|
|
-- same Portmap class.
|
|
--
|
|
|
|
|
|
-- 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_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
|
|
|
|
-- local mutex to synchronize I/O operations on nmap.registry[host.ip]['portmapper']
|
|
local mutex = nmap.mutex("rpc")
|
|
|
|
-- Supported protocol versions
|
|
RPC_version = {
|
|
["rpcbind"] = { min=2, max=4 },
|
|
["nfs"] = { min=1, max=3 },
|
|
["mountd"] = { min=1, max=3 },
|
|
}
|
|
|
|
-- Low-level communication class
|
|
Comm = {
|
|
|
|
--- Creates 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.checkprogver = true
|
|
o:SetVersion(version)
|
|
return o
|
|
end,
|
|
|
|
--- Connects to the remote program
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @param timeout [optional] socket timeout in ms
|
|
-- @return status boolean true on success, false on failure
|
|
-- @return string containing error message (if status is false)
|
|
Connect = function(self, host, port, timeout)
|
|
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
|
|
timeout = timeout or stdnse.get_timeout(host, 10000)
|
|
local new_socket = function(...)
|
|
local socket = nmap.new_socket(...)
|
|
socket:set_timeout(timeout)
|
|
return socket
|
|
end
|
|
if ( port.protocol == "tcp" ) then
|
|
if nmap.is_privileged() then
|
|
-- Try to bind to a reserved port
|
|
for i = 1, 10, 1 do
|
|
local resvport = math.random(512, 1023)
|
|
socket = new_socket()
|
|
status, err = socket:bind(nil, resvport)
|
|
if status then
|
|
status, err = socket:connect(host, port)
|
|
if status or err == "TIMEOUT" then break end
|
|
socket:close()
|
|
end
|
|
end
|
|
else
|
|
socket = new_socket()
|
|
status, err = socket:connect(host, port)
|
|
end
|
|
else
|
|
if nmap.is_privileged() then
|
|
-- Try to bind to a reserved port
|
|
for i = 1, 10, 1 do
|
|
local resvport = math.random(512, 1023)
|
|
socket = new_socket("udp")
|
|
status, err = socket:bind(nil, resvport)
|
|
if status then
|
|
status, err = socket:connect(host, port)
|
|
if status or err == "TIMEOUT" then break end
|
|
socket:close()
|
|
end
|
|
end
|
|
else
|
|
socket = new_socket("udp")
|
|
status, err = socket:connect(host, port)
|
|
end
|
|
end
|
|
if (not(status)) then
|
|
return status, string.format("%s connect error: %s",
|
|
self.program, err)
|
|
else
|
|
self.socket = socket
|
|
self.host = host
|
|
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 not self.checkprogver then return true 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
|
|
return true, nil
|
|
end,
|
|
|
|
--- Sets the rpc program version
|
|
--
|
|
-- @return status boolean true
|
|
SetVersion = function(self, version)
|
|
if self.checkprogver then
|
|
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
|
|
else
|
|
self.version = version
|
|
end
|
|
return true, nil
|
|
end,
|
|
|
|
--- Sets the verification of the specified program and version support
|
|
-- before trying to connecting.
|
|
-- @param check boolean to enable or disable checking of program and version support.
|
|
SetCheckProgVer = function(self, check)
|
|
self.checkprogver = check
|
|
end,
|
|
|
|
--- Sets the RPC program ID to use.
|
|
-- @param progid number Program ID to set.
|
|
SetProgID = function(self, progid)
|
|
self.program_id = progid
|
|
end,
|
|
|
|
--- Checks if <code>data</code> contains enough bytes to read the <code>needed</code> amount
|
|
--
|
|
-- If it doesn't it attempts to read the remaining amount of bytes from the
|
|
-- socket. Unlike <code>socket.receive_bytes</code>, reading less than
|
|
-- <code>needed</code> is treated as an error.
|
|
--
|
|
-- @param data string containing the current buffer
|
|
-- @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 or error message on failure
|
|
GetAdditionalBytes = function( self, data, pos, needed )
|
|
local toread = needed - ( data:len() - pos + 1 )
|
|
-- Do the loop ourselves instead of receive_bytes. Pathological case:
|
|
-- * read less than needed and timeout
|
|
-- * receive_bytes returns short but we don't know if it's eof or timeout
|
|
-- * Try again. If it was timeout, we've doubled the timeout waiting for bytes that aren't coming.
|
|
while toread > 0 do
|
|
local status, tmp = self.socket:receive()
|
|
if status then
|
|
toread = toread - #tmp
|
|
data = data .. tmp
|
|
else
|
|
return false, string.format("getAdditionalBytes read %d bytes before error: %s",
|
|
needed - toread, tmp)
|
|
end
|
|
end
|
|
return true, data
|
|
end,
|
|
|
|
--- Creates a RPC header
|
|
--
|
|
-- @param xid number. If no xid was provided, a random one will be used.
|
|
-- @param procedure number containing the procedure to call. Defaults to <code>0</code>.
|
|
-- @param auth table containing the authentication data to use. Defaults to NULL authentication.
|
|
-- @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
|
|
-- Defaulting to NULL Authentication
|
|
local auth = auth or {type = Portmap.AuthType.NULL}
|
|
local xid = xid or math.random(1234567890)
|
|
local procedure = procedure or 0
|
|
|
|
packet = string.pack( ">I4 I4 I4 I4 I4 I4", xid, Portmap.MessageType.CALL, RPC_VERSION,
|
|
self.program_id, self.version, procedure )
|
|
if auth.type == Portmap.AuthType.NULL then
|
|
packet = packet .. string.pack( ">I4 I4 I4 I4", 0, 0, 0, 0 )
|
|
elseif auth.type == Portmap.AuthType.UNIX then
|
|
packet = packet .. Util.marshall_int32(auth.type)
|
|
local blob = (
|
|
Util.marshall_int32(math.floor(nmap.clock())) --time
|
|
.. Util.marshall_vopaque(auth.hostname or 'localhost')
|
|
.. Util.marshall_int32(auth.uid or 0)
|
|
.. Util.marshall_int32(auth.gid or 0)
|
|
)
|
|
if auth.gids then --len prefix gid list
|
|
blob = blob .. Util.marshall_int32(#auth.gids)
|
|
for _,gid in ipairs(auth.gids) do
|
|
blob = blob .. Util.marshall_int32(gid)
|
|
end
|
|
else
|
|
blob = blob .. Util.marshall_int32(0)
|
|
end
|
|
packet = (packet .. Util.marshall_vopaque(blob)
|
|
.. string.pack( ">I4 I4", 0, 0 ) --AUTH_NULL verf
|
|
)
|
|
else
|
|
return false, "Comm.CreateHeader: invalid authentication type specified"
|
|
end
|
|
return true, packet
|
|
end,
|
|
|
|
--- Decodes the RPC header (without the leading 4 bytes as received over TCP)
|
|
--
|
|
-- @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
|
|
-- @return header table containing <code>xid</code>, <code>type</code>, <code>state</code>,
|
|
-- <code>verifier</code> and ( <code>accept_state</code> or <code>denied_state</code> )
|
|
DecodeHeader = function( self, data, pos )
|
|
local header = {}
|
|
local status
|
|
|
|
local HEADER_LEN = 20
|
|
|
|
header.verifier = {}
|
|
|
|
pos = pos or 1
|
|
if ( data:len() - pos + 1 < HEADER_LEN ) then
|
|
local tmp
|
|
status, tmp = self:GetAdditionalBytes( data, pos, HEADER_LEN - ( data:len() - pos ) )
|
|
if not status then
|
|
stdnse.debug4("Comm.DecodeHeader: failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
data = data .. tmp
|
|
end
|
|
|
|
header.xid, header.type, header.state, pos = string.unpack(">I4 I4 I4", data, pos)
|
|
|
|
if ( header.state == Portmap.State.MSG_DENIED ) then
|
|
header.denied_state, pos = string.unpack(">I4", data, pos )
|
|
return pos, header
|
|
end
|
|
|
|
header.verifier.flavor, pos = string.unpack(">I4", data, pos)
|
|
header.verifier.length, pos = string.unpack(">I4", data, pos)
|
|
|
|
if header.verifier.length - 8 > 0 then
|
|
status, data = self:GetAdditionalBytes( data, pos, header.verifier.length - 8 )
|
|
if not status then
|
|
stdnse.debug4("Comm.DecodeHeader: failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
header.verifier.data, pos = string.unpack("c" .. header.verifier.length - 8, data, pos )
|
|
end
|
|
header.accept_state, pos = string.unpack(">I4", data, pos )
|
|
|
|
return pos, header
|
|
end,
|
|
|
|
--- Reads the response from the socket
|
|
--
|
|
-- @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
|
|
-- to do appropriate checks
|
|
return self.socket:receive_bytes(1)
|
|
else
|
|
local tmp, lastfragment, length
|
|
local data, pos = "", 1
|
|
|
|
-- Maximum number of allowed attempts to parse the received bytes. This
|
|
-- prevents the code from looping endlessly on invalid content.
|
|
local retries = 400
|
|
|
|
repeat
|
|
retries = retries - 1
|
|
lastfragment = false
|
|
status, data = self:GetAdditionalBytes( data, pos, 4 )
|
|
if ( not(status) ) then
|
|
return false, "Comm.ReceivePacket: failed to call GetAdditionalBytes"
|
|
end
|
|
|
|
tmp, pos = string.unpack(">I4", data, pos )
|
|
length = tmp & 0x7FFFFFFF
|
|
|
|
if (tmp & 0x80000000) == 0x80000000 then
|
|
lastfragment = true
|
|
end
|
|
|
|
status, data = self:GetAdditionalBytes( data, pos, length )
|
|
if ( not(status) ) then
|
|
return false, "Comm.ReceivePacket: failed to call GetAdditionalBytes"
|
|
end
|
|
|
|
--
|
|
-- When multiple packets are received they look like this
|
|
-- H = Header data
|
|
-- D = Data
|
|
--
|
|
-- We don't want the Header
|
|
--
|
|
-- HHHHDDDDDDDDDDDDDDHHHHDDDDDDDDDDD
|
|
-- ^ ^ ^ ^
|
|
-- 1 5 18 22
|
|
--
|
|
-- eg. we want
|
|
-- data:sub(5, 18) and data:sub(22)
|
|
--
|
|
|
|
local bufcopy = data:sub(pos)
|
|
|
|
if 1 ~= pos - 4 then
|
|
bufcopy = data:sub(1, pos - 5) .. bufcopy
|
|
pos = pos - 4
|
|
else
|
|
pos = 1
|
|
end
|
|
|
|
pos = pos + length
|
|
data = bufcopy
|
|
until (lastfragment == true) or (retries == 0)
|
|
|
|
if retries == 0 then
|
|
return false, "Aborted after too many retries"
|
|
end
|
|
return true, data
|
|
end
|
|
end,
|
|
|
|
--- Encodes a RPC packet
|
|
--
|
|
-- @param xid number containing the transaction ID
|
|
-- @param proc 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, 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
|
|
-- set the high bit as this is our last fragment
|
|
len = 0x80000000 + packet:len()
|
|
return string.pack(">I4", len) .. packet
|
|
end
|
|
end,
|
|
|
|
SendPacket = function( self, packet )
|
|
if ( self.host and self.port ) then
|
|
return self.socket:sendto(self.host, self.port, packet)
|
|
else
|
|
return self.socket:send( packet )
|
|
end
|
|
end,
|
|
|
|
GetSocketInfo = function(self)
|
|
return self.socket:get_info()
|
|
end,
|
|
|
|
}
|
|
|
|
--- Portmap (rpcbind) class
|
|
Portmap =
|
|
{
|
|
PROTOCOLS = {
|
|
['tcp'] = 6,
|
|
['udp'] = 17,
|
|
},
|
|
|
|
-- TODO: add more Authentication Protocols
|
|
AuthType =
|
|
{
|
|
NULL = 0,
|
|
UNIX = 1,
|
|
},
|
|
|
|
-- TODO: complete Authentication stats and error messages
|
|
AuthState =
|
|
{
|
|
AUTH_OK = 0,
|
|
AUTH_BADCRED = 1,
|
|
AUTH_REJECTEDCRED = 2,
|
|
AUTH_BADVERF = 3,
|
|
AUTH_REJECTEDVERF = 4,
|
|
AUTH_TOOWEAK = 5,
|
|
AUTH_INVALIDRESP = 6,
|
|
AUTH_FAILED = 7,
|
|
},
|
|
|
|
AuthMsg =
|
|
{
|
|
[0] = "Success.",
|
|
[1] = "bad credential (seal broken).",
|
|
[2] = "client must begin new session.",
|
|
[3] = "bad verifier (seal broken).",
|
|
[4] = "verifier expired or replayed.",
|
|
[5] = "rejected for security reasons.",
|
|
[6] = "bogus response verifier.",
|
|
[7] = "reason unknown.",
|
|
},
|
|
|
|
MessageType =
|
|
{
|
|
CALL = 0,
|
|
REPLY = 1
|
|
},
|
|
|
|
Procedure =
|
|
{
|
|
[2] =
|
|
{
|
|
GETPORT = 3,
|
|
DUMP = 4,
|
|
CALLIT = 5,
|
|
},
|
|
|
|
[3] =
|
|
{
|
|
DUMP = 4,
|
|
},
|
|
|
|
[4] =
|
|
{
|
|
DUMP = 4,
|
|
},
|
|
|
|
},
|
|
|
|
State =
|
|
{
|
|
MSG_ACCEPTED = 0,
|
|
MSG_DENIED = 1,
|
|
},
|
|
|
|
AcceptState =
|
|
{
|
|
SUCCESS = 0,
|
|
PROG_UNAVAIL = 1,
|
|
PROG_MISMATCH = 2,
|
|
PROC_UNAVAIL = 3,
|
|
GARBAGE_ARGS = 4,
|
|
SYSTEM_ERR = 5,
|
|
},
|
|
|
|
AcceptMsg =
|
|
{
|
|
[0] = "RPC executed successfully.",
|
|
[1] = "remote hasn't exported program.",
|
|
[2] = "remote can't support version.",
|
|
[3] = "program can't support procedure.",
|
|
[4] = "procedure can't decode params.",
|
|
[5] = "errors like memory allocation failure.",
|
|
},
|
|
|
|
RejectState =
|
|
{
|
|
RPC_MISMATCH = 0,
|
|
AUTH_ERROR = 1,
|
|
},
|
|
|
|
RejectMsg =
|
|
{
|
|
[0] = "RPC version number != 2.",
|
|
[1] = "remote can't authenticate caller.",
|
|
},
|
|
|
|
new = function(self,o)
|
|
o = o or {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
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:
|
|
--
|
|
-- <code>
|
|
-- table[program_id][protocol]["port"] = <port number>
|
|
-- table[program_id][protocol]["version"] = <table of versions>
|
|
-- table[program_id][protocol]["addr"] = <IP address, for RPCv3 and higher>
|
|
-- </code>
|
|
--
|
|
-- Where
|
|
-- o program_id is the number associated with the program
|
|
-- o protocol is one of "tcp", "udp", "tcp6", or "udp6", or another netid
|
|
-- reported by the system.
|
|
--
|
|
Dump = function(self, comm)
|
|
local status, data, packet, response, pos, header
|
|
local program_table = setmetatable({}, { __mode = 'v' })
|
|
|
|
packet = comm:EncodePacket( nil, Portmap.Procedure[comm.version].DUMP,
|
|
{ type=Portmap.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"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader( data, 1 )
|
|
if ( not(header) ) then
|
|
return false, "Portmap.Dump: Failed to decode RPC header"
|
|
end
|
|
|
|
if header.type ~= Portmap.MessageType.REPLY then
|
|
return false, "Portmap.Dump: Packet was not a reply"
|
|
end
|
|
|
|
if header.state ~= Portmap.State.MSG_ACCEPTED then
|
|
if (Portmap.RejectMsg[header.denied_state]) then
|
|
return false,
|
|
string.format("Portmap.Dump: RPC call failed: %s",
|
|
Portmap.RejectMsg[header.denied_state])
|
|
else
|
|
return false,
|
|
string.format("Portmap.Dump: RPC call failed: code %d",
|
|
header.state)
|
|
end
|
|
end
|
|
|
|
if header.accept_state ~= Portmap.AcceptState.SUCCESS then
|
|
if (Portmap.AcceptMsg[header.accept_state]) then
|
|
return false,
|
|
string.format("Portmap.Dump: RPC accepted state: %s",
|
|
Portmap.AcceptMsg[header.accept_state])
|
|
else
|
|
return false,
|
|
string.format("Portmap.Dump: RPC accepted state code %d",
|
|
header.accept_state)
|
|
end
|
|
end
|
|
|
|
while true do
|
|
local vfollows
|
|
local program, version, protocol, port
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if ( not(status) ) then
|
|
return false, "Portmap.Dump: Failed to call GetAdditionalBytes"
|
|
end
|
|
vfollows, pos = string.unpack(">I4", data, pos)
|
|
if ( vfollows == 0 ) then
|
|
break
|
|
end
|
|
|
|
program, version, pos = string.unpack(">I4 I4", data, pos)
|
|
local addr, owner
|
|
if comm.version > 2 then
|
|
local len
|
|
len, pos = string.unpack(">I4", data, pos)
|
|
pos, protocol = Util.unmarshall_vopaque(len, data, pos)
|
|
-- workaround for NetApp 5.0: trim trailing null bytes
|
|
protocol = protocol:match("[^\0]*")
|
|
len, pos = string.unpack(">I4", data, pos)
|
|
pos, addr = Util.unmarshall_vopaque(len, data, pos)
|
|
len, pos = string.unpack(">I4", data, pos)
|
|
pos, owner = Util.unmarshall_vopaque(len, data, pos)
|
|
if protocol:match("^[tu][cd]p6?$") then
|
|
-- RFC 5665
|
|
local upper, lower
|
|
addr, upper, lower = addr:match("^(.-)%.(%d+)%.(%d+)$")
|
|
if addr then
|
|
port = tonumber(upper) * 0x100 + tonumber(lower)
|
|
end
|
|
end
|
|
else
|
|
protocol, port, pos = string.unpack(">I4 I4", data, pos)
|
|
if ( protocol == Portmap.PROTOCOLS.tcp ) then
|
|
protocol = "tcp"
|
|
elseif ( protocol == Portmap.PROTOCOLS.udp ) then
|
|
protocol = "udp"
|
|
end
|
|
end
|
|
|
|
program_table[program] = program_table[program] or {}
|
|
program_table[program][protocol] = program_table[program][protocol] or {}
|
|
program_table[program][protocol]["port"] = port
|
|
program_table[program][protocol]["addr"] = addr
|
|
program_table[program][protocol]["owner"] = owner
|
|
program_table[program][protocol]["version"] = program_table[program][protocol]["version"] or {}
|
|
table.insert( program_table[program][protocol]["version"], version )
|
|
-- parts of the code rely on versions being in order
|
|
-- this way the highest version can be chosen by choosing the last element
|
|
table.sort( program_table[program][protocol]["version"] )
|
|
end
|
|
|
|
nmap.registry[comm.ip]['portmapper'] = program_table
|
|
return true, nmap.registry[comm.ip]['portmapper']
|
|
end,
|
|
|
|
--- Calls the portmap callit call and returns the raw response
|
|
--
|
|
-- @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 status true on success, false on failure
|
|
-- @return data string containing the raw response
|
|
Callit = function( self, comm, program, protocol, version )
|
|
if ( not( Portmap.PROTOCOLS[protocol] ) ) then
|
|
return false, ("Portmap.Callit: Protocol %s not supported"):format(protocol)
|
|
end
|
|
|
|
if ( Util.ProgNameToNumber(program) == nil ) then
|
|
return false, ("Portmap.Callit: Unknown program name: %s"):format(program)
|
|
end
|
|
|
|
local data = string.pack(">I4 I4 I4 I4", Util.ProgNameToNumber(program), version, 0, 0 )
|
|
local packet = comm:EncodePacket(nil, Portmap.Procedure[comm.version].CALLIT,
|
|
{ type=Portmap.AuthType.NULL }, data )
|
|
|
|
if (not(comm:SendPacket(packet))) then
|
|
return false, "Portmap.Callit: Failed to send data"
|
|
end
|
|
|
|
data = ""
|
|
local status, data = comm:ReceivePacket()
|
|
if ( not(status) ) then
|
|
return false, "Portmap.Callit: Failed to read data from socket"
|
|
end
|
|
|
|
local pos, header = comm:DecodeHeader( data, 1 )
|
|
if ( not(header) ) then
|
|
return false, "Portmap.Callit: Failed to decode RPC header"
|
|
end
|
|
|
|
if header.type ~= Portmap.MessageType.REPLY then
|
|
return false, "Portmap.Callit: Packet was not a reply"
|
|
end
|
|
|
|
return true, data
|
|
end,
|
|
|
|
|
|
--- 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, comm, program, protocol, version )
|
|
local status, data, response, header, pos, packet
|
|
local xid
|
|
|
|
if ( not( Portmap.PROTOCOLS[protocol] ) ) then
|
|
return false, ("Portmap.GetPort: Protocol %s not supported"):format(protocol)
|
|
end
|
|
|
|
if ( Util.ProgNameToNumber(program) == nil ) then
|
|
return false, ("Portmap.GetPort: Unknown program name: %s"):format(program)
|
|
end
|
|
|
|
data = string.pack(">I4 I4 I4 I4", Util.ProgNameToNumber(program), version,
|
|
Portmap.PROTOCOLS[protocol], 0 )
|
|
packet = comm:EncodePacket(xid, Portmap.Procedure[comm.version].GETPORT,
|
|
{ type=Portmap.AuthType.NULL }, data )
|
|
|
|
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, "Portmap.GetPort: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader( data, 1 )
|
|
|
|
if ( not(header) ) then
|
|
return false, "Portmap.GetPort: Failed to decode RPC header"
|
|
end
|
|
|
|
if header.type ~= Portmap.MessageType.REPLY then
|
|
return false, "Portmap.GetPort: Packet was not a reply"
|
|
end
|
|
|
|
if header.state ~= Portmap.State.MSG_ACCEPTED then
|
|
if (Portmap.RejectMsg[header.denied_state]) then
|
|
return false, string.format("Portmap.GetPort: RPC call failed: %s",
|
|
Portmap.RejectMsg[header.denied_state])
|
|
else
|
|
return false,
|
|
string.format("Portmap.GetPort: RPC call failed: code %d",
|
|
header.state)
|
|
end
|
|
end
|
|
|
|
if header.accept_state ~= Portmap.AcceptState.SUCCESS then
|
|
if (Portmap.AcceptMsg[header.accept_state]) then
|
|
return false, string.format("Portmap.GetPort: RPC accepted state: %s",
|
|
Portmap.AcceptMsg[header.accept_state])
|
|
else
|
|
return false, string.format("Portmap.GetPort: RPC accepted state code %d",
|
|
header.accept_state)
|
|
end
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if ( not(status) ) then
|
|
return false, "Portmap.GetPort: Failed to call GetAdditionalBytes"
|
|
end
|
|
|
|
return true, string.unpack(">I4", data, pos)
|
|
end,
|
|
|
|
}
|
|
|
|
--- Mount class handling communication with the mountd program
|
|
--
|
|
-- Currently supports versions 1 through 3
|
|
-- Can be called either directly or through the static Helper class
|
|
--
|
|
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,
|
|
DUMP = 2,
|
|
UMNT = 3,
|
|
UMNTALL = 4,
|
|
EXPORT = 5,
|
|
},
|
|
|
|
new = function(self,o)
|
|
o = o or {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
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, comm)
|
|
local msg_type = 0
|
|
local packet
|
|
local pos = 1
|
|
local header = {}
|
|
local entries = {}
|
|
local data = ""
|
|
local status
|
|
|
|
if comm.proto ~= "tcp" and comm.proto ~= "udp" then
|
|
return false, "Mount.Export: Protocol should be either udp or tcp"
|
|
end
|
|
|
|
packet = comm:EncodePacket(nil, Mount.Procedure.EXPORT,
|
|
{ type=Portmap.AuthType.UNIX }, nil )
|
|
if (not(comm:SendPacket( packet ))) then
|
|
return false, "Mount.Export: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if ( not(status) ) then
|
|
return false, "Mount.Export: Failed to read data from socket"
|
|
end
|
|
|
|
-- make sure we have at least 24 bytes to unpack the header
|
|
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, "Mount.Export: Failed to decode header"
|
|
end
|
|
|
|
if header.type ~= Portmap.MessageType.REPLY then
|
|
return false, "Mount.Export: packet was not a reply"
|
|
end
|
|
|
|
if header.state ~= Portmap.State.MSG_ACCEPTED then
|
|
if (Portmap.RejectMsg[header.denied_state]) then
|
|
return false, string.format("Mount.Export: RPC call failed: %s",
|
|
Portmap.RejectMsg[header.denied_state])
|
|
else
|
|
return false, string.format("Mount.Export: RPC call failed: code %d",
|
|
header.state)
|
|
end
|
|
end
|
|
|
|
if header.accept_state ~= Portmap.AcceptState.SUCCESS then
|
|
if (Portmap.AcceptMsg[header.accept_state]) then
|
|
return false, string.format("Mount.Export: RPC accepted state: %s",
|
|
Portmap.AcceptMsg[header.accept_state])
|
|
else
|
|
return false, string.format("Mount.Export: RPC accepted state code %d",
|
|
header.accept_state)
|
|
end
|
|
end
|
|
|
|
-- Decode directory entries
|
|
--
|
|
-- [entry]
|
|
-- 4 bytes - value follows (1 if more data, 0 if not)
|
|
-- [Directory]
|
|
-- 4 bytes - value len
|
|
-- len bytes - directory name
|
|
-- ? bytes - fill bytes (see calcFillByte)
|
|
-- [Groups]
|
|
-- 4 bytes - value follows (1 if more data, 0 if not)
|
|
-- [Group] (1 or more)
|
|
-- 4 bytes - group len
|
|
-- len bytes - group value
|
|
-- ? bytes - fill bytes (see calcFillByte)
|
|
while true do
|
|
-- make sure we have atleast 4 more bytes to check for value follows
|
|
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 = Util.unmarshall_uint32(data, pos)
|
|
|
|
if data_follows ~= 1 then
|
|
break
|
|
end
|
|
|
|
--- Export list entry starts here
|
|
local entry = {}
|
|
local len
|
|
|
|
-- make sure we have atleast 4 more bytes to get the length
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
return false, "Mount.Export: Failed to call GetAdditionalBytes"
|
|
end
|
|
pos, len = Util.unmarshall_uint32(data, pos)
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, len )
|
|
if (not(status)) then
|
|
return false, "Mount.Export: Failed to call GetAdditionalBytes"
|
|
end
|
|
pos, entry.name = Util.unmarshall_vopaque(len, data, pos)
|
|
|
|
-- decode groups
|
|
while true do
|
|
local group
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
return false, "Mount.Export: Failed to call GetAdditionalBytes"
|
|
end
|
|
pos, data_follows = Util.unmarshall_uint32(data, pos)
|
|
|
|
if data_follows ~= 1 then
|
|
break
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
return false, "Mount.Export: Failed to call GetAdditionalBytes"
|
|
end
|
|
|
|
pos, len = Util.unmarshall_uint32(data, pos)
|
|
status, data = comm:GetAdditionalBytes( data, pos, len )
|
|
if (not(status)) then
|
|
return false, "Mount.Export: Failed to call GetAdditionalBytes"
|
|
end
|
|
pos, group = Util.unmarshall_vopaque(len, data, pos)
|
|
table.insert( entry, group )
|
|
end
|
|
table.insert(entries, entry)
|
|
end
|
|
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, comm, path)
|
|
local packet, mount_status
|
|
local status, len
|
|
|
|
local data = Util.marshall_vopaque(path)
|
|
|
|
packet = comm:EncodePacket( nil, Mount.Procedure.MOUNT, { type=Portmap.AuthType.UNIX }, data )
|
|
if (not(comm:SendPacket(packet))) then
|
|
return false, "Mount: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if ( not(status) ) then
|
|
return false, "Mount: Failed to read data from socket"
|
|
end
|
|
|
|
local pos, header = comm:DecodeHeader(data)
|
|
if not header then
|
|
return false, "Mount: Failed to decode header"
|
|
end
|
|
|
|
if header.type ~= Portmap.MessageType.REPLY then
|
|
return false, "Mount: Packet was not a reply"
|
|
end
|
|
|
|
if header.state ~= Portmap.State.MSG_ACCEPTED then
|
|
if (Portmap.RejectMsg[header.denied_state]) then
|
|
return false, string.format("Mount: RPC call failed: %s",
|
|
Portmap.RejectMsg[header.denied_state])
|
|
else
|
|
return false, string.format("Mount: RPC call failed: code %d",
|
|
header.state)
|
|
end
|
|
end
|
|
|
|
if header.accept_state ~= Portmap.AcceptState.SUCCESS then
|
|
if (Portmap.AcceptMsg[header.accept_state]) then
|
|
return false, string.format("Mount (%s): RPC accepted state: %s",
|
|
path, Portmap.AcceptMsg[header.accept_state])
|
|
else
|
|
return false, string.format("Mount (%s): RPC accepted state code %d",
|
|
path, header.accept_state)
|
|
end
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
return false, "Mount: Failed to call GetAdditionalBytes"
|
|
end
|
|
pos, mount_status = Util.unmarshall_uint32(data, pos)
|
|
|
|
if (mount_status ~= Mount.StatCode.MNT_OK) then
|
|
if (Mount.StatMsg[mount_status]) then
|
|
return false, string.format("Mount failed: %s",Mount.StatMsg[mount_status])
|
|
else
|
|
return false, string.format("Mount failed: code %d", mount_status)
|
|
end
|
|
end
|
|
|
|
local fhandle
|
|
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 = string.unpack(">I4", data, pos)
|
|
status, data = comm:GetAdditionalBytes( data, pos, len + 4 )
|
|
if (not(status)) then
|
|
return false, "Mount: Failed to call GetAdditionalBytes"
|
|
end
|
|
fhandle, pos = string.unpack( "c" .. len + 4, data, pos )
|
|
elseif ( comm.version < 3 ) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 32 )
|
|
if (not(status)) then
|
|
return false, "Mount: Failed to call GetAdditionalBytes"
|
|
end
|
|
fhandle, pos = string.unpack( "c32", data, pos )
|
|
else
|
|
return false, "Mount failed"
|
|
end
|
|
|
|
return true, fhandle
|
|
end,
|
|
|
|
--- 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, comm, path)
|
|
local packet, status
|
|
local _, pos, data, header, fhandle = "", 1, "", "", {}
|
|
|
|
data = Util.marshall_vopaque(path)
|
|
|
|
packet = comm:EncodePacket( nil, Mount.Procedure.UMNT, { type=Portmap.AuthType.UNIX }, data )
|
|
if (not(comm:SendPacket(packet))) then
|
|
return false, "Unmount: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket( )
|
|
if ( not(status) ) then
|
|
return false, "Unmount: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader( data, pos )
|
|
if not header then
|
|
return false, "Unmount: Failed to decode header"
|
|
end
|
|
|
|
if header.type ~= Portmap.MessageType.REPLY then
|
|
return false, "Unmount: Packet was not a reply"
|
|
end
|
|
|
|
if header.state ~= Portmap.State.MSG_ACCEPTED then
|
|
if (Portmap.RejectMsg[header.denied_state]) then
|
|
return false, string.format("Unmount: RPC call failed: %s",
|
|
Portmap.RejectMsg[header.denied_state])
|
|
else
|
|
return false, string.format("Unmount: RPC call failed: code %d",
|
|
header.state)
|
|
end
|
|
end
|
|
|
|
if header.accept_state ~= Portmap.AcceptState.SUCCESS then
|
|
if (Portmap.AcceptMsg[header.accept_state]) then
|
|
return false, string.format("Unmount (%s): RPC accepted state: %s",
|
|
path, Portmap.AcceptMsg[header.accept_state])
|
|
else
|
|
return false, string.format("Unmount (%s): RPC accepted state code %d",
|
|
path, header.accept_state)
|
|
end
|
|
end
|
|
|
|
return true, ""
|
|
end,
|
|
}
|
|
|
|
--- NFS class handling communication with the nfsd program
|
|
--
|
|
-- Currently supports versions 1 through 3
|
|
-- Can be called either directly or through the static Helper class
|
|
--
|
|
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 =
|
|
{
|
|
-- NFS Version 1
|
|
[1] =
|
|
{
|
|
GETATTR = 1,
|
|
ROOT = 3,
|
|
LOOKUP = 4,
|
|
EXPORT = 5,
|
|
READDIR = 16,
|
|
STATFS = 17,
|
|
},
|
|
|
|
-- NFS Version 2
|
|
[2] =
|
|
{
|
|
GETATTR = 1,
|
|
ROOT = 3,
|
|
LOOKUP = 4,
|
|
EXPORT = 5,
|
|
READDIR = 16,
|
|
STATFS = 17,
|
|
},
|
|
|
|
-- NFS Version 3
|
|
[3] =
|
|
{
|
|
GETATTR = 1,
|
|
SETATTR = 2,
|
|
LOOKUP = 3,
|
|
ACCESS = 4,
|
|
EXPORT = 5,
|
|
READDIR = 16,
|
|
READDIRPLUS = 17,
|
|
FSSTAT = 18,
|
|
FSINFO = 19,
|
|
PATHCONF = 20,
|
|
COMMIT = 21,
|
|
},
|
|
},
|
|
|
|
-- ACCESS values used to check the bit mask.
|
|
AccessBits =
|
|
{
|
|
[3] =
|
|
{
|
|
ACCESS_READ = 0x0001,
|
|
ACCESS_LOOKUP = 0x0002,
|
|
ACCESS_MODIFY = 0x0004,
|
|
ACCESS_EXTEND = 0x0008,
|
|
ACCESS_DELETE = 0x0010,
|
|
ACCESS_EXECUTE = 0x0020,
|
|
},
|
|
},
|
|
|
|
FSinfoBits =
|
|
{
|
|
[3] =
|
|
{
|
|
FSF_LINK = 0x0001,
|
|
FSF_SYMLINK = 0x0002,
|
|
FSF_HOMOGENEOUS = 0x0008,
|
|
FSF_CANSETTIME = 0x0010,
|
|
},
|
|
},
|
|
|
|
new = function(self,o)
|
|
o = o or {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
CheckStat = function (self, procedurename, version, status)
|
|
if (status ~= NFS.StatCode[version].NFS_OK) then
|
|
if (NFS.StatMsg[status]) then
|
|
stdnse.debug4(
|
|
string.format("%s failed: %s", procedurename, NFS.StatMsg[status]))
|
|
else
|
|
stdnse.debug4(
|
|
string.format("%s failed: code %d", procedurename, status))
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end,
|
|
|
|
AccessRead = function (self, mask, version)
|
|
return (mask & NFS.AccessBits[version].ACCESS_READ)
|
|
end,
|
|
|
|
AccessLookup = function (self, mask, version)
|
|
return (mask & NFS.AccessBits[version].ACCESS_LOOKUP)
|
|
end,
|
|
|
|
AccessModify = function (self, mask, version)
|
|
return (mask & NFS.AccessBits[version].ACCESS_MODIFY)
|
|
end,
|
|
|
|
AccessExtend = function (self, mask, version)
|
|
return (mask & NFS.AccessBits[version].ACCESS_EXTEND)
|
|
end,
|
|
|
|
AccessDelete = function (self, mask, version)
|
|
return (mask & NFS.AccessBits[version].ACCESS_DELETE)
|
|
end,
|
|
|
|
AccessExecute = function (self, mask, version)
|
|
return (mask & NFS.AccessBits[version].ACCESS_EXECUTE)
|
|
end,
|
|
|
|
FSinfoLink = function(self, mask, version)
|
|
return (mask & NFS.FSinfoBits[version].FSF_LINK)
|
|
end,
|
|
|
|
FSinfoSymlink = function(self, mask, version)
|
|
return (mask & NFS.FSinfoBits[version].FSF_SYMLINK)
|
|
end,
|
|
|
|
FSinfoHomogeneous = function(self, mask, version)
|
|
return (mask & NFS.FSinfoBits[version].FSF_HOMOGENEOUS)
|
|
end,
|
|
|
|
FSinfoCansettime = function(self, mask, version)
|
|
return (mask & NFS.FSinfoBits[version].FSF_CANSETTIME)
|
|
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
|
|
-- @return entries table containing two table entries <code>attributes</code>
|
|
-- and <code>entries</code>. The attributes entry is only present when
|
|
-- using NFS version 3. The <code>entries</code> field contain one
|
|
-- table for each file/directory entry. It has the following fields
|
|
-- <code>file_id</code>, <code>name</code> and <code>cookie</code>
|
|
--
|
|
ReadDirDecode = function( self, comm, data, pos )
|
|
local response = {}
|
|
local value_follows
|
|
local status, _
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("READDIR", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
if ( 3 == comm.version ) then
|
|
local attrib = {}
|
|
response.attributes = {}
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, value_follows = Util.unmarshall_uint32(data, pos)
|
|
if value_follows == 0 then
|
|
return -1, nil
|
|
end
|
|
status, data = comm:GetAdditionalBytes( data, pos, 84 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, attrib = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
table.insert(response.attributes, attrib)
|
|
-- opaque data
|
|
status, data = comm:GetAdditionalBytes( data, pos, 8 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
_, pos = string.unpack(">I8", data, pos)
|
|
end
|
|
|
|
response.entries = {}
|
|
while true do
|
|
local entry = {}
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, value_follows = Util.unmarshall_uint32(data, pos)
|
|
if ( value_follows == 0 ) then
|
|
break
|
|
end
|
|
|
|
if ( 3 == comm.version ) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 8 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, entry.fileid = Util.unmarshall_uint64(data, pos )
|
|
else
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, entry.fileid = Util.unmarshall_uint32(data, pos)
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, entry.length = Util.unmarshall_uint32(data, pos)
|
|
status, data = comm:GetAdditionalBytes( data, pos, entry.length )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, entry.name = Util.unmarshall_vopaque(entry.length, data, pos)
|
|
if ( 3 == comm.version ) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 8 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, entry.cookie = Util.unmarshall_uint64(data, pos)
|
|
else
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, entry.cookie = Util.unmarshall_uint32(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 <code>decodeReadDir</code>
|
|
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, "ReadDir: No filehandle received"
|
|
end
|
|
|
|
if ( comm.version == 3 ) then
|
|
local opaque_data = 0
|
|
data = file_handle .. string.pack(">I8 I8 I4", cookie, opaque_data, count)
|
|
else
|
|
data = file_handle .. string.pack(">I4 I4", cookie, count)
|
|
end
|
|
packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].READDIR,
|
|
{ type=Portmap.AuthType.UNIX }, 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, "ReadDir: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader( data, pos )
|
|
if not header then
|
|
return false, "ReadDir: Failed to decode header"
|
|
end
|
|
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,
|
|
|
|
LookUpDecode = function(self, comm, data, pos)
|
|
local lookup, status, len, value_follows, _ = {}
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("LOOKUP", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
if (comm.version == 3) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4)
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
_, len = Util.unmarshall_uint32(data, pos)
|
|
status, data = comm:GetAdditionalBytes( data, pos, len + 4)
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
lookup.fhandle, pos = string.unpack( "c" .. len + 4, data, pos)
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4)
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
lookup.attributes = {}
|
|
pos, value_follows = Util.unmarshall_uint32(data, pos)
|
|
if (value_follows ~= 0) then
|
|
status, data = comm:GetAdditionalBytes(data, pos, 84)
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, lookup.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
else
|
|
stdnse.debug4("NFS.LookUpDecode: File Attributes follow failed")
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4)
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
lookup.dir_attributes = {}
|
|
pos, value_follows = Util.unmarshall_uint32(data, pos)
|
|
if (value_follows ~= 0) then
|
|
status, data = comm:GetAdditionalBytes(data, pos, 84)
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, lookup.dir_attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
else
|
|
stdnse.debug4("NFS.LookUpDecode: File Attributes follow failed")
|
|
end
|
|
|
|
elseif (comm.version < 3) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 32)
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
lookup.fhandle, pos = string.unpack("c32", data, pos)
|
|
status, data = comm:GetAdditionalBytes( data, pos, 64 )
|
|
if (not(status)) then
|
|
stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, lookup.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
|
|
else
|
|
stdnse.debug1("NFS.LookUpDecode: NFS unsupported version %d", comm.version)
|
|
return -1, nil
|
|
end
|
|
|
|
return pos, lookup
|
|
end,
|
|
|
|
LookUp = function(self, comm, dir_handle, file)
|
|
local status, packet
|
|
local pos, data = 1, ""
|
|
local header, response = {}, {}
|
|
|
|
if (not(dir_handle)) then
|
|
return false, "LookUp: No dirhandle received"
|
|
end
|
|
|
|
data = Util.marshall_opaque(dir_handle) .. Util.marshall_vopaque(file)
|
|
packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].LOOKUP,
|
|
{type=Portmap.AuthType.UNIX}, data)
|
|
if(not(comm:SendPacket(packet))) then
|
|
return false, "LookUp: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if ( not(status) ) then
|
|
return false, "LookUp: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader(data, pos)
|
|
if not header then
|
|
return false, "LookUp: Failed to decode header"
|
|
end
|
|
pos, response = self:LookUpDecode(comm, data, pos)
|
|
if (not(response)) then
|
|
return false, "LookUp: Failed to decode the LOOKUP section"
|
|
end
|
|
|
|
return true, response
|
|
end,
|
|
|
|
ReadDirPlusDecode = function(self, comm, data, pos)
|
|
local response, status, value_follows, _ = {}
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("READDIRPLUS", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
value_follows, pos = string.unpack(">I4", data, pos)
|
|
if value_follows == 0 then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Attributes follow failed")
|
|
return -1, nil
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 84 )
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
response.attributes = {}
|
|
pos, response.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 8)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
_, pos = string.unpack(">I8", data, pos)
|
|
|
|
response.entries = {}
|
|
while true do
|
|
local entry, len = {}
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
value_follows, pos = string.unpack(">I4", data, pos)
|
|
|
|
if (value_follows == 0) then
|
|
break
|
|
end
|
|
status, data = comm:GetAdditionalBytes(data, pos, 8)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
entry.fileid, pos = string.unpack(">I8", data, pos)
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
entry.length, pos = string.unpack(">I4", data, pos)
|
|
status, data = comm:GetAdditionalBytes( data, pos, entry.length )
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, entry.name = Util.unmarshall_vopaque(entry.length, data, pos)
|
|
status, data = comm:GetAdditionalBytes(data, pos, 8)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
entry.cookie, pos = string.unpack(">I8", data, pos)
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
entry.attributes = {}
|
|
value_follows, pos = string.unpack(">I4", data, pos)
|
|
if (value_follows ~= 0) then
|
|
status, data = comm:GetAdditionalBytes(data, pos, 84)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, entry.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
else
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: %s Attributes follow failed",
|
|
entry.name)
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
entry.fhandle = ""
|
|
value_follows, pos = string.unpack(">I4", data, pos)
|
|
if (value_follows ~= 0) then
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
len = string.unpack(">I4", data, pos)
|
|
status, data = comm:GetAdditionalBytes(data, pos, len + 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
entry.fhandle, pos = string.unpack( "c" .. len + 4, data, pos )
|
|
else
|
|
stdnse.debug4("NFS.ReadDirPlusDecode: %s handle follow failed",
|
|
entry.name)
|
|
end
|
|
table.insert(response.entries, entry)
|
|
end
|
|
|
|
return pos, response
|
|
end,
|
|
|
|
ReadDirPlus = function(self, comm, file_handle)
|
|
local status, packet
|
|
local cookie, opaque_data, dircount, maxcount = 0, 0, 512, 8192
|
|
local pos, data = 1, ""
|
|
local header, response = {}, {}
|
|
|
|
if (comm.version < 3) then
|
|
return false, string.format("NFS version: %d does not support ReadDirPlus",
|
|
comm.version)
|
|
end
|
|
|
|
if not file_handle then
|
|
return false, "ReadDirPlus: No filehandle received"
|
|
end
|
|
|
|
data = file_handle .. string.pack(">I8 I8 I4 I4", cookie, opaque_data, dircount, maxcount)
|
|
|
|
packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].READDIRPLUS,
|
|
{type = Portmap.AuthType.UNIX }, data)
|
|
|
|
if (not(comm:SendPacket(packet))) then
|
|
return false, "ReadDirPlus: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if not status then
|
|
return false, "ReadDirPlus: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader( data, pos )
|
|
if not header then
|
|
return false, "ReadDirPlus: Failed to decode header"
|
|
end
|
|
pos, response = self:ReadDirPlusDecode( comm, data, pos )
|
|
if not response then
|
|
return false, "ReadDirPlus: Failed to decode the READDIR section"
|
|
end
|
|
|
|
return true, response
|
|
end,
|
|
|
|
FsStatDecode = function(self, comm, data, pos)
|
|
local fsstat, status, value_follows = {}
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.FsStatDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("FSSTAT", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
fsstat.attributes = {}
|
|
pos, value_follows = Util.unmarshall_uint32(data, pos)
|
|
if (value_follows ~= 0) then
|
|
status, data = comm:GetAdditionalBytes(data, pos, 84)
|
|
if not status then
|
|
stdnse.debug4("NFS.FsStatDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, fsstat.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
else
|
|
stdnse.debug4("NFS.FsStatDecode: Attributes follow failed")
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 52)
|
|
if not status then
|
|
stdnse.debug4("NFS.FsStatDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, fsstat.tbytes, fsstat.fbytes, fsstat.abytes, fsstat.tfiles,
|
|
fsstat.ffiles, fsstat.afiles = Util.unmarshall_nfssize3(data, pos, 6)
|
|
pos, fsstat.invarsec = Util.unmarshall_uint32(data, pos)
|
|
|
|
return pos, fsstat
|
|
end,
|
|
|
|
FsStat = function(self, comm, file_handle)
|
|
local status, packet
|
|
local pos, data = 1, ""
|
|
local header, response = {}, {}
|
|
|
|
if (comm.version < 3) then
|
|
return false, string.format("NFS version: %d does not support FSSTAT",
|
|
comm.version)
|
|
end
|
|
|
|
if not file_handle then
|
|
return false, "FsStat: No filehandle received"
|
|
end
|
|
|
|
packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].FSSTAT,
|
|
{type = Portmap.AuthType.UNIX}, file_handle)
|
|
|
|
if (not(comm:SendPacket(packet))) then
|
|
return false, "FsStat: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if not status then
|
|
return false, "FsStat: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader(data, pos)
|
|
if not header then
|
|
return false, "FsStat: Failed to decode header"
|
|
end
|
|
|
|
pos, response = self:FsStatDecode(comm, data, pos)
|
|
if not response then
|
|
return false, "FsStat: Failed to decode the FSSTAT section"
|
|
end
|
|
return true, response
|
|
end,
|
|
|
|
FsInfoDecode = function(self, comm, data, pos)
|
|
local fsinfo, status, value_follows = {}
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.FsInfoDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("FSINFO", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
fsinfo.attributes = {}
|
|
pos, value_follows = Util.unmarshall_uint32(data, pos)
|
|
if (value_follows ~= 0) then
|
|
status, data = comm:GetAdditionalBytes(data, pos, 84)
|
|
if not status then
|
|
stdnse.debug4("NFS.FsInfoDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, fsinfo.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
else
|
|
stdnse.debug4("NFS.FsInfoDecode: Attributes follow failed")
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 48)
|
|
if not status then
|
|
stdnse.debug4("NFS.FsStatDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, fsinfo.rtmax, fsinfo.rtpref, fsinfo.rtmult,
|
|
fsinfo.wtmax, fsinfo.wtpref, fsinfo.wtmult,
|
|
fsinfo.dtpref = Util.unmarshall_uint32(data, pos, 7)
|
|
pos, fsinfo.maxfilesize = Util.unmarshall_nfssize3(data, pos)
|
|
pos, fsinfo.time_delta = Util.unmarshall_nfstime(data, pos)
|
|
pos, fsinfo.properties = Util.unmarshall_uint32(data, pos)
|
|
|
|
return pos, fsinfo
|
|
end,
|
|
|
|
FsInfo = function(self, comm, file_handle)
|
|
local status, packet
|
|
local pos, data = 1, ""
|
|
local header, response = {}
|
|
|
|
if (comm.version < 3) then
|
|
return false, string.format("NFS version: %d does not support FSINFO",
|
|
comm.version)
|
|
end
|
|
|
|
if not file_handle then
|
|
return false, "FsInfo: No filehandle received"
|
|
end
|
|
|
|
data = Util.marshall_opaque(file_handle)
|
|
packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].FSINFO,
|
|
{type = Portmap.AuthType.UNIX}, data)
|
|
|
|
if (not(comm:SendPacket(packet))) then
|
|
return false, "FsInfo: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if not status then
|
|
return false, "FsInfo: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader(data, pos)
|
|
if not header then
|
|
return false, "FsInfo: Failed to decode header"
|
|
end
|
|
|
|
pos, response = self:FsInfoDecode(comm, data, pos)
|
|
if not response then
|
|
return false, "FsInfo: Failed to decode the FSINFO section"
|
|
end
|
|
return true, response
|
|
end,
|
|
|
|
PathConfDecode = function(self, comm, data, pos)
|
|
local pconf, status, value_follows = {}
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.PathConfDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("PATHCONF", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
pconf.attributes = {}
|
|
pos, value_follows = Util.unmarshall_uint32(data, pos)
|
|
if (value_follows ~= 0) then
|
|
status, data = comm:GetAdditionalBytes(data, pos, 84)
|
|
if not status then
|
|
stdnse.debug4("NFS.PathConfDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, pconf.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
else
|
|
stdnse.debug4("NFS.PathConfDecode: Attributes follow failed")
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 24)
|
|
if not status then
|
|
stdnse.debug4("NFS.PathConfDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, pconf.linkmax, pconf.name_max, pconf.no_trunc,
|
|
pconf.chown_restricted, pconf.case_insensitive,
|
|
pconf.case_preserving = Util.unmarshall_uint32(data, pos, 6)
|
|
|
|
return pos, pconf
|
|
end,
|
|
|
|
PathConf = function(self, comm, file_handle)
|
|
local status, packet
|
|
local pos, data = 1, ""
|
|
local header, response = {}
|
|
|
|
if (comm.version < 3) then
|
|
return false, string.format("NFS version: %d does not support PATHCONF",
|
|
comm.version)
|
|
end
|
|
|
|
if not file_handle then
|
|
return false, "PathConf: No filehandle received"
|
|
end
|
|
|
|
data = Util.marshall_opaque(file_handle)
|
|
packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].PATHCONF,
|
|
{type = Portmap.AuthType.UNIX}, data)
|
|
|
|
if (not(comm:SendPacket(packet))) then
|
|
return false, "PathConf: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if not status then
|
|
return false, "PathConf: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader(data, pos)
|
|
if not header then
|
|
return false, "PathConf: Failed to decode header"
|
|
end
|
|
|
|
pos, response = self:PathConfDecode(comm, data, pos)
|
|
if not response then
|
|
return false, "PathConf: Failed to decode the PATHCONF section"
|
|
end
|
|
return true, response
|
|
end,
|
|
|
|
AccessDecode = function(self, comm, data, pos)
|
|
local access, status, value_follows = {}
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.AccessDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("ACCESS", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
access.attributes = {}
|
|
pos, value_follows = Util.unmarshall_uint32(data, pos)
|
|
if (value_follows ~= 0) then
|
|
status, data = comm:GetAdditionalBytes(data, pos, 84)
|
|
if not status then
|
|
stdnse.debug4("NFS.AccessDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
pos, access.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
else
|
|
stdnse.debug4("NFS.AccessDecode: Attributes follow failed")
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes(data, pos, 4)
|
|
if not status then
|
|
stdnse.debug4("NFS.AccessDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, access.mask = Util.unmarshall_uint32(data, pos)
|
|
|
|
return pos, access
|
|
end,
|
|
|
|
Access = function(self, comm, file_handle, access)
|
|
local status, packet
|
|
local pos, data = 1, ""
|
|
local header, response = {}, {}
|
|
|
|
if (comm.version < 3) then
|
|
return false, string.format("NFS version: %d does not support ACCESS",
|
|
comm.version)
|
|
end
|
|
|
|
if not file_handle then
|
|
return false, "Access: No filehandle received"
|
|
end
|
|
|
|
data = Util.marshall_opaque(file_handle) .. Util.marshall_uint32(access)
|
|
packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].ACCESS,
|
|
{type = Portmap.AuthType.UNIX}, data)
|
|
|
|
if (not(comm:SendPacket(packet))) then
|
|
return false, "Access: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if not status then
|
|
return false, "Access: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader(data, pos)
|
|
if not header then
|
|
return false, "Access: Failed to decode header"
|
|
end
|
|
|
|
pos, response = self:AccessDecode(comm, data, pos)
|
|
if not response then
|
|
return false, "Access: Failed to decode the FSSTAT 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 <code>transfer_size</code>, <code>block_size</code>,
|
|
-- <code>total_blocks</code>, <code>free_blocks</code> and <code>available_blocks</code>
|
|
-- @return errormsg if status is false
|
|
StatFs = function( self, comm, file_handle )
|
|
|
|
local status, packet
|
|
local pos, data, _ = 1, "", ""
|
|
local header, statfs = {}, {}
|
|
|
|
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, "StatFs: Incorrect filehandle received"
|
|
end
|
|
|
|
data = Util.marshall_opaque(file_handle)
|
|
packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].STATFS, { type=Portmap.AuthType.UNIX }, data )
|
|
if (not(comm:SendPacket( packet ))) then
|
|
return false, "StatFS: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket( )
|
|
if ( not(status) ) then
|
|
return false, "StatFs: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader( data, pos )
|
|
|
|
if not header then
|
|
return false, "StatFs: Failed to decode header"
|
|
end
|
|
|
|
pos, statfs = self:StatFsDecode( comm, data, pos )
|
|
|
|
if not statfs then
|
|
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
|
|
-- @return statfs table with the following fields: <code>type</code>, <code>mode</code>,
|
|
-- <code>nlink</code>, <code>uid</code>, <code>gid</code>, <code>size</code>,
|
|
-- <code>blocksize</code>, <code>rdev</code>, <code>blocks</code>, <code>fsid</code>,
|
|
-- <code>fileid</code>, <code>atime</code>, <code>mtime</code> and <code>ctime</code>
|
|
--
|
|
GetAttrDecode = function( self, comm, data, pos )
|
|
local status
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
stdnse.debug4("GetAttrDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("GETATTR", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
if ( comm.version < 3 ) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 64 )
|
|
elseif (comm.version == 3) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 84 )
|
|
else
|
|
stdnse.debug4("GetAttrDecode: Unsupported version")
|
|
return -1, nil
|
|
end
|
|
if ( not(status) ) then
|
|
stdnse.debug4("GetAttrDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
return Util.unmarshall_nfsattr(data, pos, comm.version)
|
|
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 <code>type</code>, <code>mode</code>,
|
|
-- <code>nlink</code>, <code>uid</code>, <code>gid</code>, <code>size</code>,
|
|
-- <code>blocksize</code>, <code>rdev</code>, <code>blocks</code>, <code>fsid</code>,
|
|
-- <code>fileid</code>, <code>atime</code>, <code>mtime</code> and <code>ctime</code>
|
|
-- @return errormsg if status is false
|
|
GetAttr = function( self, comm, file_handle )
|
|
local data, packet, status, attribs, pos, header
|
|
|
|
data = Util.marshall_opaque(file_handle)
|
|
packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].GETATTR, { type=Portmap.AuthType.UNIX }, data )
|
|
if(not(comm:SendPacket(packet))) then
|
|
return false, "GetAttr: Failed to send data"
|
|
end
|
|
|
|
status, data = comm:ReceivePacket()
|
|
if ( not(status) ) then
|
|
return false, "GetAttr: Failed to read data from socket"
|
|
end
|
|
|
|
pos, header = comm:DecodeHeader( data, 1 )
|
|
if not header then
|
|
return false, "GetAttr: 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
|
|
|
|
return true, attribs
|
|
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: <code>transfer_size</code>, <code>block_size</code>,
|
|
-- <code>total_blocks</code>, <code>free_blocks</code> and <code>available_blocks</code>
|
|
StatFsDecode = function( self, comm, data, pos )
|
|
local status
|
|
local statfs = {}
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
stdnse.debug4("StatFsDecode: Failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
|
|
pos, status = Util.unmarshall_uint32(data, pos)
|
|
if (not self:CheckStat("STATFS", comm.version, status)) then
|
|
return -1, nil
|
|
end
|
|
|
|
status, data = comm:GetAdditionalBytes( data, pos, 20 )
|
|
if (not(status)) then
|
|
stdnse.debug4("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 = Util.unmarshall_uint32(data, pos, 5)
|
|
return pos, statfs
|
|
end,
|
|
}
|
|
|
|
Helper = {
|
|
|
|
--- Lists the NFS exports on the remote host
|
|
-- This function abstracts the RPC communication with the portmapper from the user
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @return status true on success, false on failure
|
|
-- @return result table of string entries or error message on failure
|
|
ShowMounts = function( host, port )
|
|
|
|
local status, result, mounts
|
|
local mountd, mnt_comm
|
|
local mnt = Mount:new()
|
|
local portmap = Portmap:new()
|
|
|
|
status, mountd = Helper.GetProgramInfo( host, port, "mountd")
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.ShowMounts: GetProgramInfo failed")
|
|
return status, "rpc.Helper.ShowMounts: GetProgramInfo failed"
|
|
end
|
|
|
|
mnt_comm = Comm:new('mountd', mountd.version)
|
|
status, result = mnt_comm:Connect(host, mountd.port)
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.ShowMounts: %s", result)
|
|
return false, result
|
|
end
|
|
status, mounts = mnt:Export(mnt_comm)
|
|
mnt_comm:Disconnect()
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.ShowMounts: %s", mounts)
|
|
end
|
|
return status, mounts
|
|
end,
|
|
|
|
--- Mounts a remote NFS export and returns the file handle
|
|
--
|
|
-- This is a high level function to be used by NSE scripts
|
|
-- To close the mounted NFS export use UnmountPath() function
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @param path string containing the path to mount
|
|
-- @return on success a Comm object which can be
|
|
-- used later as a parameter by low level Mount
|
|
-- functions, on failure returns nil.
|
|
-- @return on success the filehandle of the NFS export as
|
|
-- a string, on failure returns the error message.
|
|
MountPath = function(host, port, path)
|
|
local fhandle, status, err
|
|
local mountd, mnt_comm
|
|
local mnt = Mount:new()
|
|
|
|
status, mountd = Helper.GetProgramInfo( host, port, "mountd")
|
|
if not status then
|
|
stdnse.debug4("rpc.Helper.MountPath: GetProgramInfo failed")
|
|
return nil, "rpc.Helper.MountPath: GetProgramInfo failed"
|
|
end
|
|
|
|
mnt_comm = Comm:new("mountd", mountd.version)
|
|
|
|
status, err = mnt_comm:Connect(host, mountd.port)
|
|
if not status then
|
|
stdnse.debug4("rpc.Helper.MountPath: %s", err)
|
|
return nil, err
|
|
end
|
|
|
|
status, fhandle = mnt:Mount(mnt_comm, path)
|
|
if not status then
|
|
mnt_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.MountPath: %s", fhandle)
|
|
return nil, fhandle
|
|
end
|
|
|
|
return mnt_comm, fhandle
|
|
end,
|
|
|
|
--- Unmounts a remote mounted NFS export
|
|
--
|
|
-- This is a high level function to be used by NSE scripts
|
|
-- This function must be used to unmount a NFS point
|
|
-- mounted by MountPath()
|
|
--
|
|
-- @param mnt_comm object returned from a previous call to
|
|
-- MountPath()
|
|
-- @param path string containing the path to unmount
|
|
-- @return true on success or nil on failure
|
|
-- @return error message on failure
|
|
UnmountPath = function(mnt_comm, path)
|
|
local mnt = Mount:new()
|
|
local status, ret = mnt:Unmount(mnt_comm, path)
|
|
mnt_comm:Disconnect()
|
|
if not status then
|
|
stdnse.debug4("rpc.Helper.UnmountPath: %s", ret)
|
|
return nil, ret
|
|
end
|
|
|
|
return status, nil
|
|
end,
|
|
|
|
--- Connects to a remote NFS server
|
|
--
|
|
-- This is a high level function to open NFS connections
|
|
-- To close the NFS connection use NfsClose() function
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @return on success a Comm object which can be
|
|
-- used later as a parameter by low level NFS
|
|
-- functions, on failure returns nil.
|
|
-- @return error message on failure.
|
|
NfsOpen = function(host, port)
|
|
local nfs_comm, nfsd, status, err
|
|
|
|
status, nfsd = Helper.GetProgramInfo(host, port, "nfs")
|
|
if not status then
|
|
stdnse.debug4("rpc.Helper.NfsOpen: GetProgramInfo failed")
|
|
return nil, "rpc.Helper.NfsOpen: GetProgramInfo failed"
|
|
end
|
|
|
|
nfs_comm = Comm:new('nfs', nfsd.version)
|
|
status, err = nfs_comm:Connect(host, nfsd.port)
|
|
if not status then
|
|
stdnse.debug4("rpc.Helper.NfsProc: %s", err)
|
|
return nil, err
|
|
end
|
|
|
|
return nfs_comm, nil
|
|
end,
|
|
|
|
--- Closes the NFS connection
|
|
--
|
|
-- This is a high level function to close NFS connections
|
|
-- This function must be used to close the NFS connection
|
|
-- opened by the NfsOpen() call
|
|
--
|
|
-- @param nfs_comm object returned by NfsOpen()
|
|
-- @return true on success or nil on failure
|
|
-- @return error message on failure
|
|
NfsClose = function(nfs_comm)
|
|
local status, ret = nfs_comm:Disconnect()
|
|
if not status then
|
|
stdnse.debug4("rpc.Helper.NfsClose: %s", ret)
|
|
return nil, ret
|
|
end
|
|
|
|
return status, nil
|
|
end,
|
|
|
|
--- Retrieves NFS storage statistics
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @param path string containing the nfs export path
|
|
-- @return status true on success, false on failure
|
|
-- @return statfs table with the fields <code>transfer_size</code>, <code>block_size</code>,
|
|
-- <code>total_blocks</code>, <code>free_blocks</code> and <code>available_blocks</code>
|
|
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
|
|
stdnse.debug4("rpc.Helper.ExportStats: GetProgramInfo failed")
|
|
return status, "rpc.Helper.ExportStats: GetProgramInfo failed"
|
|
end
|
|
|
|
status, nfsd = Helper.GetProgramInfo( host, port, "nfs", 2)
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.ExportStats: GetProgramInfo failed")
|
|
return status, "rpc.Helper.ExportStats: GetProgramInfo failed"
|
|
end
|
|
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
|
|
stdnse.debug4("rpc.Helper.ExportStats: versions mismatch, nfs v%d - mount v%d",
|
|
nfs_comm.version, mnt_comm.version)
|
|
return false, string.format("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
|
|
stdnse.debug4("rpc.Helper.ExportStats: %s", result)
|
|
return status, result
|
|
end
|
|
status, result = nfs_comm:Connect(host, nfsd.port)
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.ExportStats: %s", result)
|
|
return status, result
|
|
end
|
|
|
|
status, fhandle = mnt:Mount(mnt_comm, path)
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.ExportStats: %s", fhandle)
|
|
return status, fhandle
|
|
end
|
|
status, stats = nfs:StatFs(nfs_comm, fhandle)
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.ExportStats: %s", stats)
|
|
return status, stats
|
|
end
|
|
|
|
status, fhandle = mnt:Unmount(mnt_comm, path)
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.ExportStats: %s", fhandle)
|
|
return status, fhandle
|
|
end
|
|
return true, stats
|
|
end,
|
|
|
|
--- Retrieves a list of files from the NFS export
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @param path string containing the nfs export path
|
|
-- @return status true on success, false on failure
|
|
-- @return table of file table entries as described in <code>decodeReadDir</code>
|
|
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
|
|
stdnse.debug4("rpc.Helper.Dir: GetProgramInfo failed")
|
|
return status, "rpc.Helper.Dir: GetProgramInfo failed"
|
|
end
|
|
|
|
status, nfsd = Helper.GetProgramInfo( host, port, "nfs")
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.Dir: GetProgramInfo failed")
|
|
return status, "rpc.Helper.Dir: GetProgramInfo failed"
|
|
end
|
|
|
|
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
|
|
stdnse.debug4("rpc.Helper.Dir: versions mismatch, nfs v%d - mount v%d",
|
|
nfs_comm.version, mnt_comm.version)
|
|
return false, string.format("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
|
|
stdnse.debug4("rpc.Helper.Dir: %s", result)
|
|
return status, result
|
|
end
|
|
|
|
status, result = nfs_comm:Connect(host, nfsd.port)
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.Dir: %s", result)
|
|
return status, result
|
|
end
|
|
|
|
status, fhandle = mnt:Mount(mnt_comm, path )
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.Dir: %s", fhandle)
|
|
return status, fhandle
|
|
end
|
|
|
|
status, dirs = nfs:ReadDir(nfs_comm, fhandle )
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.Dir: %s", dirs)
|
|
return status, dirs
|
|
end
|
|
|
|
status, fhandle = mnt:Unmount(mnt_comm, path)
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.Dir: %s", fhandle)
|
|
return status, fhandle
|
|
end
|
|
return true, dirs
|
|
end,
|
|
|
|
--- Retrieves NFS Attributes
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @param path string containing the nfs export path
|
|
-- @return status true on success, false on failure
|
|
-- @return statfs table with the fields <code>transfer_size</code>, <code>block_size</code>,
|
|
-- <code>total_blocks</code>, <code>free_blocks</code> and <code>available_blocks</code>
|
|
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
|
|
stdnse.debug4("rpc.Helper.GetAttributes: GetProgramInfo failed")
|
|
return status, "rpc.Helper.GetAttributes: GetProgramInfo failed"
|
|
end
|
|
|
|
status, nfsd = Helper.GetProgramInfo( host, port, "nfs")
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.GetAttributes: GetProgramInfo failed")
|
|
return status, "rpc.Helper.GetAttributes: GetProgramInfo failed"
|
|
end
|
|
|
|
mnt_comm, result = Comm:new('mountd', mountd.version)
|
|
nfs_comm, result = Comm:new('nfs', nfsd.version)
|
|
|
|
-- TODO: recheck the version mismatch when adding NFSv4
|
|
if (nfs_comm.version <= 2 and mnt_comm.version > 2) then
|
|
stdnse.debug4("rpc.Helper.GetAttributes: versions mismatch, nfs v%d - mount v%d",
|
|
nfs_comm.version, mnt_comm.version)
|
|
return false, string.format("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
|
|
stdnse.debug4("rpc.Helper.GetAttributes: %s", result)
|
|
return status, result
|
|
end
|
|
|
|
status, result = nfs_comm:Connect(host, nfsd.port)
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.GetAttributes: %s", result)
|
|
return status, result
|
|
end
|
|
|
|
status, fhandle = mnt:Mount(mnt_comm, path)
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.GetAttributes: %s", fhandle)
|
|
return status, fhandle
|
|
end
|
|
|
|
status, attribs = nfs:GetAttr(nfs_comm, fhandle)
|
|
if ( not(status) ) then
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
stdnse.debug4("rpc.Helper.GetAttributes: %s", attribs)
|
|
return status, attribs
|
|
end
|
|
|
|
status, fhandle = mnt:Unmount(mnt_comm, path)
|
|
|
|
mnt_comm:Disconnect()
|
|
nfs_comm:Disconnect()
|
|
if ( not(status) ) then
|
|
stdnse.debug4("rpc.Helper.GetAttributes: %s", fhandle)
|
|
return status, fhandle
|
|
end
|
|
|
|
return true, attribs
|
|
end,
|
|
|
|
--- Queries the portmapper for a list of programs
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @return status true on success, false on failure
|
|
-- @return table containing the portmapper information as returned by
|
|
-- <code>Portmap.Dump</code>
|
|
RpcInfo = function( host, port )
|
|
local status, result
|
|
local portmap = Portmap:new()
|
|
|
|
mutex "lock"
|
|
|
|
if nmap.registry[host.ip] == nil then
|
|
nmap.registry[host.ip] = {}
|
|
end
|
|
if nmap.registry[host.ip]['portmapper'] == nil then
|
|
nmap.registry[host.ip]['portmapper'] = {}
|
|
elseif next(nmap.registry[host.ip]['portmapper']) ~= nil then
|
|
mutex "done"
|
|
return true, nmap.registry[host.ip]['portmapper']
|
|
end
|
|
|
|
local pversion = 4
|
|
while pversion >= 2 do
|
|
local comm = Comm:new('rpcbind', pversion)
|
|
status, result = comm:Connect(host, port)
|
|
if (not(status)) then
|
|
mutex "done"
|
|
stdnse.debug4("rpc.Helper.RpcInfo: %s", result)
|
|
return status, result
|
|
end
|
|
|
|
status, result = portmap:Dump(comm)
|
|
comm:Disconnect()
|
|
|
|
if status then
|
|
break
|
|
end
|
|
stdnse.debug4("rpc.Helper.RpcInfo: %s", result)
|
|
pversion = pversion - 1
|
|
end
|
|
|
|
mutex "done"
|
|
return status, result
|
|
end,
|
|
|
|
--- Queries the portmapper for a port for the specified RPC program
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @param program string containing the RPC program name
|
|
-- @param protocol string containing either "tcp" or "udp"
|
|
-- @return status true on success, false on failure
|
|
-- @return table containing the portmapper information as returned by
|
|
-- <code>Portmap.Dump</code>
|
|
GetPortForProgram = function( host, port, program, protocol )
|
|
local status, result
|
|
local portmap = Portmap:new()
|
|
local comm = Comm:new('rpcbind', 2)
|
|
|
|
status, result = comm:Connect(host, port)
|
|
if (not(status)) then
|
|
stdnse.debug4("rpc.Helper.GetPortForProgram: %s", result)
|
|
return status, result
|
|
end
|
|
|
|
status, result = portmap:GetPort(comm, program, protocol, 1 )
|
|
comm:Disconnect()
|
|
if (not(status)) then
|
|
stdnse.debug4("rpc.Helper.GetPortForProgram: %s", result)
|
|
end
|
|
|
|
return status, result
|
|
end,
|
|
|
|
--- Get RPC program information
|
|
--
|
|
-- @param host table
|
|
-- @param port table
|
|
-- @param program string containing the RPC program name
|
|
-- @param max_version (optional) number containing highest version to retrieve
|
|
-- @return status true on success, false on failure
|
|
-- @return info table containing <code>port</code>, <code>port.number</code>
|
|
-- <code>port.protocol</code> and <code>version</code>
|
|
GetProgramInfo = function( host, port, program, max_version )
|
|
local status, portmap_table = Helper.RpcInfo(host, port)
|
|
if ( not(status) ) then
|
|
return status, portmap_table
|
|
end
|
|
|
|
-- assume failure
|
|
status = false
|
|
|
|
local tmp = portmap_table[Util.ProgNameToNumber(program)]
|
|
if not tmp then
|
|
return false, "Program not supported by target"
|
|
end
|
|
|
|
local info = {}
|
|
local proginfo
|
|
local ipv6 = nmap.address_family() == "inet6"
|
|
::AF_FALLBACK::
|
|
for _, p in ipairs( RPC_PROTOCOLS ) do
|
|
if ipv6 then
|
|
proginfo = tmp[p .. "6"]
|
|
else
|
|
proginfo = tmp[p]
|
|
end
|
|
if proginfo then
|
|
info.port = {}
|
|
info.port.number = proginfo.port
|
|
info.port.protocol = p
|
|
break
|
|
end
|
|
end
|
|
if ipv6 and not proginfo then
|
|
-- Fall back to trying IPv4
|
|
ipv6 = false
|
|
goto AF_FALLBACK
|
|
end
|
|
|
|
if not proginfo then
|
|
return false, "No transport protocol supported"
|
|
end
|
|
|
|
-- choose the highest version available
|
|
if ( not(RPC_version[program]) ) then
|
|
info.version = proginfo.version[#proginfo.version]
|
|
status = true
|
|
else
|
|
for i=#proginfo.version, 1, -1 do
|
|
if ( RPC_version[program].max >= proginfo.version[i] ) then
|
|
if ( not(max_version) ) then
|
|
info.version = proginfo.version[i]
|
|
status = true
|
|
break
|
|
else
|
|
if ( max_version >= proginfo.version[i] ) then
|
|
info.version = proginfo.version[i]
|
|
status = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return status, info
|
|
end,
|
|
}
|
|
|
|
--- Static class containing mostly conversion functions
|
|
-- and File type codes and permissions emulation
|
|
Util =
|
|
{
|
|
-- Symbolic letters for file permission codes
|
|
Fperm =
|
|
{
|
|
owner =
|
|
{
|
|
-- S_IRUSR
|
|
[0x00000100] = { idx = 1, char = "r" },
|
|
-- S_IWUSR
|
|
[0x00000080] = { idx = 2, char = "w" },
|
|
-- S_IXUSR
|
|
[0x00000040] = { idx = 3, char = "x" },
|
|
-- S_ISUID
|
|
[0x00000800] = { idx = 3, char = "S" },
|
|
},
|
|
group =
|
|
{
|
|
-- S_IRGRP
|
|
[0x00000020] = { idx = 4, char = "r" },
|
|
-- S_IWGRP
|
|
[0x00000010] = { idx = 5, char = "w" },
|
|
-- S_IXGRP
|
|
[0x00000008] = { idx = 6, char = "x" },
|
|
-- S_ISGID
|
|
[0x00000400] = { idx = 6, char = "S" },
|
|
},
|
|
other =
|
|
{
|
|
-- S_IROTH
|
|
[0x00000004] = { idx = 7, char = "r" },
|
|
-- S_IWOTH
|
|
[0x00000002] = { idx = 8, char = "w" },
|
|
-- S_IXOTH
|
|
[0x00000001] = { idx = 9, char = "x" },
|
|
-- S_ISVTX
|
|
[0x00000200] = { idx = 9, char = "t" },
|
|
},
|
|
},
|
|
|
|
-- bit mask used to extract the file type code from a mode
|
|
-- S_IFMT = 00170000 (octal)
|
|
S_IFMT = 0xF000,
|
|
|
|
FileType =
|
|
{
|
|
-- S_IFSOCK
|
|
[0x0000C000] = { char = "s", str = "socket" },
|
|
-- S_IFLNK
|
|
[0x0000A000] = { char = "l", str = "symbolic link" },
|
|
-- S_IFREG
|
|
[0x00008000] = { char = "-", str = "file" },
|
|
-- S_IFBLK
|
|
[0x00006000] = { char = "b", str = "block device" },
|
|
-- S_IFDIR
|
|
[0x00004000] = { char = "d", str = "directory" },
|
|
-- S_IFCHR
|
|
[0x00002000] = { char = "c", str = "char device" },
|
|
-- S_IFIFO
|
|
[0x00001000] = { char = "p", str = "named pipe" },
|
|
},
|
|
|
|
--- Converts a numeric ACL mode to a file type char
|
|
--
|
|
-- @param mode number containing the ACL mode
|
|
-- @return char containing the file type
|
|
FtypeToChar = function(mode)
|
|
local code = mode & Util.S_IFMT
|
|
if Util.FileType[code] then
|
|
return Util.FileType[code].char
|
|
else
|
|
stdnse.debug1("FtypeToChar: Unknown file type, mode: %o", mode)
|
|
return ""
|
|
end
|
|
end,
|
|
|
|
--- Converts a numeric ACL mode to a file type string
|
|
--
|
|
-- @param mode number containing the ACL mode
|
|
-- @return string containing the file type name
|
|
FtypeToString = function(mode)
|
|
local code = mode & Util.S_IFMT
|
|
if Util.FileType[code] then
|
|
return Util.FileType[code].str
|
|
else
|
|
stdnse.debug1("FtypeToString: Unknown file type, mode: %o", mode)
|
|
return ""
|
|
end
|
|
end,
|
|
|
|
--- Converts a numeric ACL mode to a string in an octal
|
|
-- number format.
|
|
--
|
|
-- @param mode number containing the ACL mode
|
|
-- @return string containing the octal ACL mode
|
|
FmodeToOctalString = function(mode)
|
|
local code = mode & Util.S_IFMT
|
|
if Util.FileType[code] then
|
|
code = mode ~ code
|
|
else
|
|
code = mode
|
|
stdnse.debug1("FmodeToOctalString: Unknown file type, mode: %o", mode)
|
|
end
|
|
return stdnse.tooctal(code)
|
|
end,
|
|
|
|
--- Converts a numeric ACL to its character equivalent eg. (rwxr-xr-x)
|
|
--
|
|
-- @param mode number containing the ACL mode
|
|
-- @return string containing the ACL characters
|
|
FpermToString = function(mode)
|
|
local tmpacl = { "-", "-", "-", "-", "-", "-", "-", "-", "-" }
|
|
|
|
for user,_ in pairs(Util.Fperm) do
|
|
local t = Util.Fperm[user]
|
|
for i in pairs(t) do
|
|
local code = mode & i
|
|
if t[code] then
|
|
-- save set-ID and sticky bits
|
|
if tmpacl[t[code].idx] == "x" then
|
|
if t[code].char == "S" then
|
|
tmpacl[t[code].idx] = "s"
|
|
else
|
|
tmpacl[t[code].idx] = t[code].char
|
|
end
|
|
elseif tmpacl[t[code].idx] == "S" then
|
|
if t[code].char == "x" then
|
|
tmpacl[t[code].idx] = "s"
|
|
end
|
|
else
|
|
tmpacl[t[code].idx] = t[code].char
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return table.concat(tmpacl)
|
|
end,
|
|
|
|
--- Converts the NFS file attributes to a string.
|
|
--
|
|
-- An optional second argument is the mactime to use
|
|
--
|
|
-- @param attr table returned by NFS GETATTR or ACCESS
|
|
-- @param mactime to use, the default value is mtime
|
|
-- Possible values: mtime, atime, ctime
|
|
-- @return string containing the file attributes
|
|
format_nfsfattr = function(attr, mactime)
|
|
local time = "mtime"
|
|
if mactime then
|
|
time = mactime
|
|
end
|
|
|
|
return string.format("%s%s uid: %5d gid: %5d %6s %s",
|
|
Util.FtypeToChar(attr.mode),
|
|
Util.FpermToString(attr.mode),
|
|
attr.uid,
|
|
attr.gid,
|
|
Util.SizeToHuman(attr.size),
|
|
Util.TimeToString(attr[time].seconds))
|
|
end,
|
|
|
|
marshall_int32 = function(int32)
|
|
return string.pack(">i4", int32)
|
|
end,
|
|
|
|
unmarshall_int32 = function(data, pos, count)
|
|
local ints = {}
|
|
for i=1,(count or 1) do
|
|
ints[i], pos = string.unpack(">i4", data, pos)
|
|
end
|
|
return pos, table.unpack(ints)
|
|
end,
|
|
|
|
marshall_uint32 = function(uint32)
|
|
return string.pack(">I4", uint32)
|
|
end,
|
|
|
|
unmarshall_uint32 = function(data, pos, count)
|
|
local ints = {}
|
|
for i=1,(count or 1) do
|
|
ints[i], pos = string.unpack(">I4", data, pos)
|
|
end
|
|
return pos, table.unpack(ints)
|
|
end,
|
|
|
|
marshall_int64 = function(int64)
|
|
return string.pack(">i8", int64)
|
|
end,
|
|
|
|
unmarshall_int64 = function(data, pos, count)
|
|
local ints = {}
|
|
for i=1,(count or 1) do
|
|
ints[i], pos = string.unpack(">i8", data, pos)
|
|
end
|
|
return pos, table.unpack(ints)
|
|
end,
|
|
|
|
marshall_uint64 = function(uint64)
|
|
return string.pack(">I8", uint64)
|
|
end,
|
|
|
|
unmarshall_uint64 = function(data, pos, count)
|
|
local ints = {}
|
|
for i=1,(count or 1) do
|
|
ints[i], pos = string.unpack(">I8", data, pos)
|
|
end
|
|
return pos, table.unpack(ints)
|
|
end,
|
|
|
|
marshall_opaque = function(data)
|
|
return data .. string.rep("\0", Util.CalcFillBytes(data:len()))
|
|
end,
|
|
|
|
unmarshall_opaque = function(len, data, pos)
|
|
local opaque, pos = string.unpack("c" .. len, data, pos)
|
|
return pos, opaque
|
|
end,
|
|
|
|
marshall_vopaque = function(data)
|
|
local l = data:len()
|
|
return (
|
|
Util.marshall_uint32(l) .. data ..
|
|
string.rep("\0", Util.CalcFillBytes(l))
|
|
)
|
|
end,
|
|
|
|
unmarshall_vopaque = function(len, data, pos)
|
|
local opaque, pad
|
|
pad = Util.CalcFillBytes(len)
|
|
opaque, pos = string.unpack("c" .. len, data, pos)
|
|
return pos + pad, opaque
|
|
end,
|
|
|
|
unmarshall_nfsftype = function(data, pos, count)
|
|
return Util.unmarshall_uint32(data, pos, count)
|
|
end,
|
|
|
|
unmarshall_nfsfmode = function(data, pos, count)
|
|
return Util.unmarshall_uint32(data, pos, count)
|
|
end,
|
|
|
|
unmarshall_nfssize3 = function(data, pos, count)
|
|
return Util.unmarshall_uint64(data, pos, count)
|
|
end,
|
|
|
|
unmarshall_nfsspecdata3 = function(data, pos)
|
|
local specdata3 = {}
|
|
pos, specdata3.specdata1,
|
|
specdata3.specdata2 = Util.unmarshall_uint32(data, pos, 2)
|
|
return pos, specdata3
|
|
end,
|
|
|
|
--- Unmarshall NFSv3 fileid field of the NFS attributes
|
|
--
|
|
-- @param data The data being processed.
|
|
-- @param pos The position within <code>data</code>
|
|
-- @return pos The new position
|
|
-- @return uint64 The decoded fileid
|
|
unmarshall_nfsfileid3 = function(data, pos)
|
|
return Util.unmarshall_uint64(data, pos)
|
|
end,
|
|
|
|
--- Unmarshall NFS time
|
|
--
|
|
-- @param data The data being processed.
|
|
-- @param pos The position within <code>data</code>
|
|
-- @return pos The new position
|
|
-- @return table The decoded NFS time table.
|
|
unmarshall_nfstime = function(data, pos)
|
|
local nfstime = {}
|
|
pos, nfstime.seconds,
|
|
nfstime.nseconds = Util.unmarshall_uint32(data, pos, 2)
|
|
return pos, nfstime
|
|
end,
|
|
|
|
--- Unmarshall NFS file attributes
|
|
--
|
|
-- @param data The data being processed.
|
|
-- @param pos The position within <code>data</code>
|
|
-- @param number The NFS version.
|
|
-- @return pos The new position
|
|
-- @return table The decoded file attributes table.
|
|
unmarshall_nfsattr = function(data, pos, nfsversion)
|
|
local attr = {}
|
|
pos, attr.type = Util.unmarshall_nfsftype(data, pos)
|
|
pos, attr.mode = Util.unmarshall_nfsfmode(data, pos)
|
|
pos, attr.nlink, attr.uid,
|
|
attr.gid = Util.unmarshall_uint32(data, pos, 3)
|
|
|
|
if (nfsversion < 3) then
|
|
pos, attr.size, attr.blocksize, attr.rdev, attr.blocks,
|
|
attr.fsid, attr.fileid = Util.unmarshall_uint32(data, pos, 6)
|
|
elseif (nfsversion == 3) then
|
|
pos, attr.size = Util.unmarshall_nfssize3(data, pos)
|
|
pos, attr.used = Util.unmarshall_nfssize3(data, pos)
|
|
pos, attr.rdev = Util.unmarshall_nfsspecdata3(data, pos)
|
|
pos, attr.fsid = Util.unmarshall_uint64(data, pos)
|
|
pos, attr.fileid = Util.unmarshall_nfsfileid3(data, pos)
|
|
else
|
|
stdnse.debug4("unmarshall_nfsattr: unsupported NFS version %d",
|
|
nfsversion)
|
|
return -1, nil
|
|
end
|
|
|
|
pos, attr.atime = Util.unmarshall_nfstime(data, pos)
|
|
pos, attr.mtime = Util.unmarshall_nfstime(data, pos)
|
|
pos, attr.ctime = Util.unmarshall_nfstime(data, pos)
|
|
|
|
return pos, attr
|
|
end,
|
|
|
|
--- Returns a string containing date and time
|
|
--
|
|
-- @param number of seconds since some given start time
|
|
-- (the "epoch")
|
|
-- @return string that represents time.
|
|
TimeToString = datetime.format_timestamp,
|
|
|
|
--- Converts the size in bytes to a human readable format
|
|
--
|
|
-- An optional second argument is the size of a block
|
|
-- @usage
|
|
-- size_tohuman(1024) --> 1024.0B
|
|
-- size_tohuman(926548776) --> 883.6M
|
|
-- size_tohuman(246548, 1024) --> 240.8K
|
|
-- size_tohuman(246548, 1000) --> 246.5K
|
|
--
|
|
-- @param size in bytes
|
|
-- @param blocksize represents the number of bytes per block
|
|
-- Possible values are: 1024 or 1000
|
|
-- Default value is: 1024
|
|
-- @return string containing the size in the human readable
|
|
-- format
|
|
SizeToHuman = function(size, blocksize)
|
|
local bs, idx = 1024, 1
|
|
local unit = { "B", "K", "M", "G" , "T"}
|
|
if blocksize and blocksize == 1000 then
|
|
bs = blocksize
|
|
end
|
|
for i=1, #unit do
|
|
if (size > bs and idx < #unit) then
|
|
size = size / bs
|
|
idx = idx + 1
|
|
end
|
|
end
|
|
return string.format("%.1f%s", size, unit[idx])
|
|
end,
|
|
|
|
format_access = function(mask, version)
|
|
local ret, nfsobj = "", NFS:new()
|
|
|
|
if nfsobj:AccessRead(mask, version) ~= 0 then
|
|
ret = "Read "
|
|
else
|
|
ret = "NoRead "
|
|
end
|
|
|
|
if nfsobj:AccessLookup(mask, version) ~= 0 then
|
|
ret = ret .. "Lookup "
|
|
else
|
|
ret = ret .. "NoLookup "
|
|
end
|
|
|
|
if nfsobj:AccessModify(mask, version) ~= 0 then
|
|
ret = ret .. "Modify "
|
|
else
|
|
ret = ret .. "NoModify "
|
|
end
|
|
|
|
if nfsobj:AccessExtend(mask, version) ~= 0 then
|
|
ret = ret .. "Extend "
|
|
else
|
|
ret = ret .. "NoExtend "
|
|
end
|
|
|
|
if nfsobj:AccessDelete(mask, version) ~= 0 then
|
|
ret = ret .. "Delete "
|
|
else
|
|
ret = ret .. "NoDelete "
|
|
end
|
|
|
|
if nfsobj:AccessExecute(mask, version) ~= 0 then
|
|
ret = ret .. "Execute"
|
|
else
|
|
ret = ret .. "NoExecute"
|
|
end
|
|
|
|
return ret
|
|
end,
|
|
|
|
--- Return the pathconf filesystem table
|
|
--
|
|
-- @param pconf table returned by the NFSv3 PATHCONF call
|
|
-- @param nfsversion the version of the remote NFS server
|
|
-- @return fs table that contains the remote filesystem
|
|
-- pathconf information.
|
|
calc_pathconf_table = function(pconf, nfsversion)
|
|
local fs = {}
|
|
if nfsversion ~= 3 then
|
|
return nil, "ERROR: unsupported NFS version."
|
|
end
|
|
|
|
fs.linkmax = pconf.linkmax
|
|
fs.name_max = pconf.name_max
|
|
|
|
if pconf.chown_restricted then
|
|
fs.chown_restricted = "True"
|
|
else
|
|
fs.chown_restricted = "False"
|
|
end
|
|
|
|
return fs, nil
|
|
end,
|
|
|
|
--- Calculate and return the fsinfo filesystem table
|
|
--
|
|
-- @param fsinfo table returned by the NFSv3 FSINFO call
|
|
-- @param nfsversion the version of the remote NFS server
|
|
-- @param human if set show the size in the human
|
|
-- readable format.
|
|
-- @return fs table that contains the remote filesystem
|
|
-- information.
|
|
calc_fsinfo_table = function(fsinfo, nfsversion, human)
|
|
local fs = {}
|
|
local nfsobj = NFS:new()
|
|
if nfsversion ~= 3 then
|
|
return nil, "ERROR: unsupported NFS version."
|
|
end
|
|
|
|
fs.maxfilesize = Util.SizeToHuman(fsinfo.maxfilesize)
|
|
|
|
if nfsobj:FSinfoLink(fsinfo.properties, nfsversion) ~= 0 then
|
|
fs.link = "True"
|
|
else
|
|
fs.link = "False"
|
|
end
|
|
|
|
if nfsobj:FSinfoSymlink(fsinfo.properties, nfsversion) ~= 0 then
|
|
fs.symlink = "True"
|
|
else
|
|
fs.symlink = "False"
|
|
end
|
|
|
|
return fs, nil
|
|
end,
|
|
|
|
--- Calculate and return the fsstat filesystem table
|
|
--
|
|
-- @param stats table returned by the NFSv3 FSSTAT or
|
|
-- NFSv2 STATFS calls
|
|
-- @param nfsversion the version of the remote NFS server
|
|
-- @param human if set show the size in the human
|
|
-- readable format.
|
|
-- @return df table that contains the remote filesystem
|
|
-- attributes.
|
|
calc_fsstat_table = function(stats, nfsversion, human)
|
|
local df, base = {}, 1024
|
|
local size, free, total, avail, used, use
|
|
if (nfsversion == 3) then
|
|
free = stats.fbytes
|
|
size = stats.tbytes
|
|
avail = stats.abytes
|
|
elseif (nfsversion == 2) then
|
|
df.bsize = stats.block_size
|
|
free = stats.free_blocks * df.bsize
|
|
size = stats.total_blocks * df.bsize
|
|
avail = stats.available_blocks * df.bsize
|
|
else
|
|
return nil, "ERROR: unsupported NFS version."
|
|
end
|
|
|
|
if (human) then
|
|
if (df.bsize) then
|
|
df.bsize = Util.SizeToHuman(df.bsize)
|
|
end
|
|
df.size = Util.SizeToHuman(size)
|
|
df.available = Util.SizeToHuman(avail)
|
|
used = size - free
|
|
avail = avail
|
|
df.used = Util.SizeToHuman(used)
|
|
total = used + avail
|
|
else
|
|
free = free / base
|
|
df.size = size / base
|
|
df.available = avail / base
|
|
used = df.size - free
|
|
df.used = used
|
|
total = df.used + df.available
|
|
end
|
|
|
|
use = math.ceil(used * 100 / total)
|
|
df.use = string.format("%.0f%%", use)
|
|
return df, nil
|
|
end,
|
|
|
|
--- Converts a RPC program name to its equivalent number
|
|
--
|
|
-- @param prog_name string containing the name of the RPC program
|
|
-- @return num number containing the program ID
|
|
ProgNameToNumber = function(prog_name)
|
|
local status
|
|
|
|
if not( RPC_PROGRAMS ) then
|
|
status, RPC_PROGRAMS = datafiles.parse_rpc()
|
|
if ( not(status) ) then
|
|
return
|
|
end
|
|
end
|
|
for num, name in pairs(RPC_PROGRAMS) do
|
|
if ( prog_name == name ) then
|
|
return num
|
|
end
|
|
end
|
|
|
|
return
|
|
end,
|
|
|
|
--- Converts the RPC program number to its equivalent name
|
|
--
|
|
-- @param num number containing the RPC program identifier
|
|
-- @return string containing the RPC program name
|
|
ProgNumberToName = function( num )
|
|
local status
|
|
|
|
if not( RPC_PROGRAMS ) then
|
|
status, RPC_PROGRAMS = datafiles.parse_rpc()
|
|
if ( not(status) ) then
|
|
return
|
|
end
|
|
end
|
|
return RPC_PROGRAMS[num]
|
|
end,
|
|
|
|
--
|
|
-- Calculates the number of fill bytes needed
|
|
-- @param length contains the length of the string
|
|
-- @return the amount of pad needed to be dividable by 4
|
|
CalcFillBytes = function(length)
|
|
-- calculate fill bytes
|
|
if math.fmod( length, 4 ) ~= 0 then
|
|
return (4 - math.fmod( length, 4))
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
}
|
|
|
|
return _ENV;
|