/nselib/rpc.lua
Lua | 3435 lines | 2596 code | 352 blank | 487 comment | 295 complexity | 5fd291264c62a4720f829206fe648611 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, Apache-2.0, LGPL-2.0, LGPL-2.1, MIT
Large files files are truncated, but you can click here to view the full file
- ---
- -- 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(
- string.format("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(
- string.format("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_hand…
Large files files are truncated, but you can click here to view the full file