mirror of
https://github.com/nmap/nmap.git
synced 2025-12-10 09:49:05 +00:00
Cases where the format string does not contain any placeholders, but values are given anyway. Cases where string.format is used without any placeholders or arguments.
3434 lines
106 KiB
Lua
3434 lines
106 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 http://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 bin = require "bin"
|
|
local bit = require "bit"
|
|
local datafiles = require "datafiles"
|
|
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=2 },
|
|
["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(1, 1024)
|
|
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(1, 1024)
|
|
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 data 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
|
|
--
|
|
-- @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 status, tmp
|
|
|
|
if data:len() - pos + 1 < needed then
|
|
local toread = needed - ( data:len() - pos + 1 )
|
|
status, tmp = self.socket:receive_bytes( toread )
|
|
if status then
|
|
data = data .. tmp
|
|
else
|
|
return false, string.format("getAdditionalBytes() failed to read: %d bytes from the socket",
|
|
needed - ( data:len() - pos ) )
|
|
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 = bin.pack( ">IIIIII", xid, Portmap.MessageType.CALL, RPC_VERSION,
|
|
self.program_id, self.version, procedure )
|
|
if auth.type == Portmap.AuthType.NULL then
|
|
packet = packet .. bin.pack( "IIII", 0, 0, 0, 0 )
|
|
elseif auth.type == Portmap.AuthType.UNIX then
|
|
packet = packet .. Util.marshall_int32(auth.type)
|
|
local blob = (
|
|
Util.marshall_int32(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)
|
|
.. bin.pack( "II", 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 = {}
|
|
|
|
if ( data:len() - pos < HEADER_LEN ) then
|
|
local tmp
|
|
status, tmp = self:GetAdditionalBytes( data, pos, HEADER_LEN - ( data:len() - pos ) )
|
|
if not status then
|
|
stdnse.debug4("Comm.DecodeHeader: failed to call GetAdditionalBytes")
|
|
return -1, nil
|
|
end
|
|
data = data .. tmp
|
|
end
|
|
|
|
pos, header.xid, header.type, header.state = bin.unpack(">III", data, pos)
|
|
|
|
if ( header.state == Portmap.State.MSG_DENIED ) then
|
|
pos, header.denied_state = bin.unpack(">I", data, pos )
|
|
return pos, header
|
|
end
|
|
|
|
pos, header.verifier.flavor = bin.unpack(">I", data, pos)
|
|
pos, header.verifier.length = bin.unpack(">I", 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
|
|
pos, header.verifier.data = bin.unpack("A" .. header.verifier.length - 8, data, pos )
|
|
end
|
|
pos, header.accept_state = bin.unpack(">I", 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
|
|
|
|
pos, tmp = bin.unpack(">i", data, pos )
|
|
length = bit.band( tmp, 0x7FFFFFFF )
|
|
|
|
if ( bit.band( 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 bin.pack(">I", 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,
|
|
},
|
|
|
|
},
|
|
|
|
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>
|
|
-- </code>
|
|
--
|
|
-- Where
|
|
-- o program_id is the number associated with the program
|
|
-- o protocol is either "tcp" or "udp"
|
|
--
|
|
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
|
|
pos, vfollows = bin.unpack( ">I", data, pos )
|
|
if ( vfollows == 0 ) then
|
|
break
|
|
end
|
|
|
|
pos, program, version, protocol, port = bin.unpack(">IIII", data, pos)
|
|
if ( protocol == Portmap.PROTOCOLS.tcp ) then
|
|
protocol = "tcp"
|
|
elseif ( protocol == Portmap.PROTOCOLS.udp ) then
|
|
protocol = "udp"
|
|
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]["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 = bin.pack(">IIII", 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 = bin.pack(">I>I>I>I", 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, select(2, bin.unpack(">I", 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 _, pos, data, header, fhandle = "", 1, "", "", {}
|
|
local status, len
|
|
|
|
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
|
|
|
|
pos, header = comm:DecodeHeader( data, pos )
|
|
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
|
|
|
|
if ( comm.version == 3 ) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 4 )
|
|
if (not(status)) then
|
|
return false, "Mount: Failed to call GetAdditionalBytes"
|
|
end
|
|
_, len = bin.unpack(">I", data, pos )
|
|
status, data = comm:GetAdditionalBytes( data, pos, len + 4 )
|
|
if (not(status)) then
|
|
return false, "Mount: Failed to call GetAdditionalBytes"
|
|
end
|
|
pos, fhandle = bin.unpack( "A" .. len + 4, data, pos )
|
|
elseif ( comm.version < 3 ) then
|
|
status, data = comm:GetAdditionalBytes( data, pos, 32 )
|
|
if (not(status)) then
|
|
return false, "Mount: Failed to call GetAdditionalBytes"
|
|
end
|
|
pos, fhandle = bin.unpack( "A32", data, pos )
|
|
else
|
|
return false, "Mount failed"
|
|
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 bit.band(mask, NFS.AccessBits[version].ACCESS_READ)
|
|
end,
|
|
|
|
AccessLookup = function (self, mask, version)
|
|
return bit.band(mask, NFS.AccessBits[version].ACCESS_LOOKUP)
|
|
end,
|
|
|
|
AccessModify = function (self, mask, version)
|
|
return bit.band(mask, NFS.AccessBits[version].ACCESS_MODIFY)
|
|
end,
|
|
|
|
AccessExtend = function (self, mask, version)
|
|
return bit.band(mask, NFS.AccessBits[version].ACCESS_EXTEND)
|
|
end,
|
|
|
|
AccessDelete = function (self, mask, version)
|
|
return bit.band(mask, NFS.AccessBits[version].ACCESS_DELETE)
|
|
end,
|
|
|
|
AccessExecute = function (self, mask, version)
|
|
return bit.band(mask, NFS.AccessBits[version].ACCESS_EXECUTE)
|
|
end,
|
|
|
|
FSinfoLink = function(self, mask, version)
|
|
return bit.band(mask, NFS.FSinfoBits[version].FSF_LINK)
|
|
end,
|
|
|
|
FSinfoSymlink = function(self, mask, version)
|
|
return bit.band(mask, NFS.FSinfoBits[version].FSF_SYMLINK)
|
|
end,
|
|
|
|
FSinfoHomogeneous = function(self, mask, version)
|
|
return bit.band(mask, NFS.FSinfoBits[version].FSF_HOMOGENEOUS)
|
|
end,
|
|
|
|
FSinfoCansettime = function(self, mask, version)
|
|
return bit.band(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, _ = bin.unpack(">L", 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 = bin.pack("A>L>L>I", file_handle, cookie, opaque_data, count)
|
|
else
|
|
data = bin.pack("A>I>I", file_handle, cookie, count)
|
|
end
|
|
packet = 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
|
|
pos, lookup.fhandle = bin.unpack( "A" .. 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
|
|
pos, lookup.fhandle = bin.unpack("A32", 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
|
|
|
|
pos, value_follows = bin.unpack(">I", 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, _ = bin.unpack(">L", 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
|
|
|
|
pos, value_follows = bin.unpack(">I", 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
|
|
pos, entry.fileid = bin.unpack(">L", 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
|
|
|
|
pos, entry.length = bin.unpack(">I", 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
|
|
pos, entry.cookie = bin.unpack(">L", 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 = {}
|
|
pos, value_follows = bin.unpack(">I", 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 = ""
|
|
pos, value_follows = bin.unpack(">I", 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 = bin.unpack(">I", 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
|
|
pos, entry.fhandle = bin.unpack( "A" .. 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 = bin.pack("A>L>L>I>I", file_handle, 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()
|
|
local comm = Comm:new('rpcbind', 2)
|
|
|
|
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
|
|
|
|
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()
|
|
|
|
mutex "done"
|
|
if (not(status)) then
|
|
stdnse.debug4("rpc.Helper.RpcInfo: %s", result)
|
|
end
|
|
|
|
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
|
|
|
|
local info = {}
|
|
-- assume failure
|
|
status = false
|
|
|
|
for _, p in ipairs( RPC_PROTOCOLS ) do
|
|
local tmp = portmap_table[Util.ProgNameToNumber(program)]
|
|
|
|
if ( tmp and tmp[p] ) then
|
|
info = {}
|
|
info.port = {}
|
|
info.port.number = tmp[p].port
|
|
info.port.protocol = p
|
|
-- choose the highest version available
|
|
if ( not(RPC_version[program]) ) then
|
|
info.version = tmp[p].version[#tmp[p].version]
|
|
status = true
|
|
else
|
|
for i=#tmp[p].version, 1, -1 do
|
|
if ( RPC_version[program].max >= tmp[p].version[i] ) then
|
|
if ( not(max_version) ) then
|
|
info.version = tmp[p].version[i]
|
|
status = true
|
|
break
|
|
else
|
|
if ( max_version >= tmp[p].version[i] ) then
|
|
info.version = tmp[p].version[i]
|
|
status = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
break
|
|
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 = bit.band(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 = bit.band(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 = bit.band(mode, Util.S_IFMT)
|
|
if Util.FileType[code] then
|
|
code = bit.bxor(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 = bit.band(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, count)
|
|
if count then
|
|
return bin.pack(">i" .. count, int32)
|
|
end
|
|
return bin.pack(">i", int32)
|
|
end,
|
|
|
|
unmarshall_int32 = function(data, pos, count)
|
|
if count then
|
|
return bin.unpack(">i" .. count, data, pos)
|
|
end
|
|
return bin.unpack(">i", data, pos)
|
|
end,
|
|
|
|
marshall_uint32 = function(uint32, count)
|
|
if count then
|
|
return bin.pack(">I" .. count, uint32)
|
|
end
|
|
return bin.pack(">I", uint32)
|
|
end,
|
|
|
|
unmarshall_uint32 = function(data, pos, count)
|
|
if count then
|
|
return bin.unpack(">I" .. count, data, pos)
|
|
end
|
|
return bin.unpack(">I", data, pos)
|
|
end,
|
|
|
|
marshall_int64 = function(int64, count)
|
|
if count then
|
|
return bin.pack(">l" .. count, int64)
|
|
end
|
|
return bin.pack(">l", int64)
|
|
end,
|
|
|
|
unmarshall_int64 = function(data, pos, count)
|
|
if count then
|
|
return bin.unpack(">l" .. count, data, pos)
|
|
end
|
|
return bin.unpack(">l", data, pos)
|
|
end,
|
|
|
|
marshall_uint64 = function(uint64, count)
|
|
if count then
|
|
return bin.pack(">L" .. count, uint64)
|
|
end
|
|
return bin.pack(">L", uint64)
|
|
end,
|
|
|
|
unmarshall_uint64 = function(data, pos, count)
|
|
if count then
|
|
return bin.unpack(">L" .. count, data, pos)
|
|
end
|
|
return bin.unpack(">L", data, pos)
|
|
end,
|
|
|
|
marshall_opaque = function(data)
|
|
return bin.pack(">A", data) .. string.rep("\0", Util.CalcFillBytes(data:len()))
|
|
end,
|
|
|
|
unmarshall_opaque = function(len, data, pos)
|
|
return bin.unpack(">A" .. len, data, pos)
|
|
end,
|
|
|
|
marshall_vopaque = function(data)
|
|
local l = data:len()
|
|
return (
|
|
Util.marshall_uint32(l) .. bin.pack(">A", data) ..
|
|
string.rep("\0", Util.CalcFillBytes(l))
|
|
)
|
|
end,
|
|
|
|
unmarshall_vopaque = function(len, data, pos)
|
|
local opaque, pad
|
|
pad = Util.CalcFillBytes(len)
|
|
pos, opaque = bin.unpack(">A" .. 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 = stdnse.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;
|