1---
2-- RPC Library supporting a very limited subset of operations.
3--
4-- The library works over both the UDP and TCP protocols. A subset of nfs and
5-- mountd procedures are supported. The nfs and mountd programs support
6-- versions 1 through 3. Authentication is supported using the NULL RPC
7-- Authentication protocol
8--
9-- The library contains the following classes:
10-- * <code>Comm </code>
11-- ** Handles network connections.
12-- ** Handles low-level packet sending, receiving, decoding and encoding.
13-- ** Stores rpc programs info: socket, protocol, program name, id and version.
14-- ** Used by Mount, NFS, RPC and Portmap.
15-- * <code>Portmap</code>
16-- ** Contains RPC constants.
17-- ** Handles communication with the portmap RPC program.
18-- * <code>Mount</code>
19-- ** Handles communication with the mount RPC program.
20-- * <code>NFS</code>
21-- ** Handles communication with the nfs RPC program.
22-- * <code>Helper</code>
23-- ** Provides easy access to common RPC functions.
24-- ** Implemented as a static class where most functions accept host and port parameters.
25-- * <code>Util</code>
26-- ** Mostly static conversion routines.
27--
28-- The portmapper dynamically allocates TCP/UDP ports to RPC programs. So in
29-- in order to request a list of NFS shares from the server we need to:
30-- * Make sure that we can talk to the portmapper on port 111 TCP or UDP.
31-- * Query the portmapper for the ports allocated to the NFS program.
32-- * Query the NFS program for a list of shares on the ports returned by the portmap program.
33--
34-- The Helper class contains functions that facilitate access to common
35-- RPC program procedures through static class methods. Most functions accept
36-- host and port parameters. As the Helper functions query the portmapper to
37-- get the correct RPC program port, the port supplied to these functions
38-- should be the rpcbind port 111/tcp or 111/udp.
39--
40-- The following sample code illustrates how scripts can use the <code>Helper</code> class
41-- to interface the library:
42--
43-- <code>
44-- -- retrieve a list of NFS export
45-- status, mounts = rpc.Helper.ShowMounts( host, port )
46--
47-- -- iterate over every share
48-- for _, mount in ipairs( mounts ) do
49--
50--    -- get the NFS attributes for the share
51--    status, attribs = rpc.Helper.GetAttributes( host, port, mount.name )
52--    .... process NFS attributes here ....
53-- end
54-- </code>
55--
56-- RPC transaction IDs (XID) are not properly implemented as a random ID is
57-- generated for each client call. The library makes no attempt to verify
58-- whether the returned XID is valid or not.
59--
60-- Therefore TCP is the preferred method of communication and the library
61-- always attempts to connect to the TCP port of the RPC program first.
62-- This behaviour can be overridden by setting the rpc.protocol argument.
63-- The portmap service is always queried over the protocol specified in the
64-- port information used to call the Helper function from the script.
65--
66-- When multiple versions exists for a specific RPC program the library
67-- always attempts to connect using the highest available version.
68--
69-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
70--
71-- @author Patrik Karlsson <patrik@cqure.net>
72--
73-- @args nfs.version number If set overrides the detected version of nfs
74-- @args mount.version number If set overrides the detected version of mountd
75-- @args rpc.protocol table If set overrides the preferred order in which
76--       protocols are tested. (ie. "tcp", "udp")
77
78local datafiles = require "datafiles"
79local datetime = require "datetime"
80local math = require "math"
81local nmap = require "nmap"
82local stdnse = require "stdnse"
83local string = require "string"
84local table = require "table"
85_ENV = stdnse.module("rpc", stdnse.seeall)
86
87-- Version 0.3
88--
89-- Created 01/24/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
90-- Revised 02/22/2010 - v0.2 - cleanup, revised the way TCP/UDP are handled fo
91--                             encoding an decoding
92-- Revised 03/13/2010 - v0.3 - re-worked library to be OO
93-- Revised 04/18/2010 - v0.4 - Applied patch from Djalal Harouni with improved
94--                             error checking and re-designed Comm class. see:
95--                             http://seclists.org/nmap-dev/2010/q2/232
96-- Revised 06/02/2010 - v0.5 - added code to the Util class to check for file
97--                             types and permissions.
98-- Revised 06/04/2010 - v0.6 - combined Portmap and RPC classes in the
99--                             same Portmap class.
100--
101
102
103-- RPC args using the nmap.registry.args
104RPC_args = {
105  ["rpcbind"] = { proto = 'rpc.protocol' },
106  ["nfs"] = { ver = 'nfs.version' },
107  ["mountd"] = { ver = 'mount.version' },
108}
109
110-- Defines the order in which to try to connect to the RPC programs
111-- TCP appears to be more stable than UDP in most cases, so try it first
112local RPC_PROTOCOLS = (nmap.registry.args and nmap.registry.args[RPC_args['rpcbind'].proto] and
113  type(nmap.registry.args[RPC_args['rpcbind'].proto]) == 'table') and
114nmap.registry.args[RPC_args['rpcbind'].proto] or { "tcp", "udp" }
115
116-- used to cache the contents of the rpc datafile
117local RPC_PROGRAMS
118
119-- local mutex to synchronize I/O operations on nmap.registry[host.ip]['portmapper']
120local mutex = nmap.mutex("rpc")
121
122-- Supported protocol versions
123RPC_version = {
124  ["rpcbind"] = { min=2, max=4 },
125  ["nfs"] = { min=1, max=3 },
126  ["mountd"] = { min=1, max=3 },
127}
128
129-- Low-level communication class
130Comm = {
131
132  --- Creates a new rpc Comm object
133  --
134  -- @param program name string
135  -- @param version number containing the program version to use
136  -- @return a new Comm object
137  new = function(self, program, version)
138    local o = {}
139    setmetatable(o, self)
140    self.__index = self
141    o.program = program
142    o.program_id = Util.ProgNameToNumber(program)
143    o.checkprogver = true
144    o:SetVersion(version)
145    return o
146  end,
147
148  --- Connects to the remote program
149  --
150  -- @param host table
151  -- @param port table
152  -- @param timeout [optional] socket timeout in ms
153  -- @return status boolean true on success, false on failure
154  -- @return string containing error message (if status is false)
155  Connect = function(self, host, port, timeout)
156    local status, err, socket
157    status, err = self:ChkProgram()
158    if (not(status)) then
159      return status, err
160    end
161    status, err = self:ChkVersion()
162    if (not(status)) then
163      return status, err
164    end
165    timeout = timeout or stdnse.get_timeout(host, 10000)
166    local new_socket = function(...)
167      local socket = nmap.new_socket(...)
168      socket:set_timeout(timeout)
169      return socket
170    end
171    if ( port.protocol == "tcp" ) then
172      if nmap.is_privileged() then
173        -- Try to bind to a reserved port
174        for i = 1, 10, 1 do
175          local resvport = math.random(512, 1023)
176          socket = new_socket()
177          status, err = socket:bind(nil, resvport)
178          if status then
179            status, err = socket:connect(host, port)
180            if status or err == "TIMEOUT" then break end
181            socket:close()
182          end
183        end
184      else
185        socket = new_socket()
186        status, err = socket:connect(host, port)
187      end
188    else
189      if nmap.is_privileged() then
190        -- Try to bind to a reserved port
191        for i = 1, 10, 1 do
192          local resvport = math.random(512, 1023)
193          socket = new_socket("udp")
194          status, err = socket:bind(nil, resvport)
195          if status then
196            status, err = socket:connect(host, port)
197            if status or err == "TIMEOUT" then break end
198            socket:close()
199          end
200        end
201      else
202        socket = new_socket("udp")
203        status, err = socket:connect(host, port)
204      end
205    end
206    if (not(status)) then
207      return status, string.format("%s connect error: %s",
208        self.program, err)
209    else
210      self.socket = socket
211      self.host = host
212      self.ip = host.ip
213      self.port = port.number
214      self.proto = port.protocol
215      return status, nil
216    end
217  end,
218
219  --- Disconnects from the remote program
220  --
221  -- @return status boolean true on success, false on failure
222  -- @return string containing error message (if status is false)
223  Disconnect = function(self)
224    local status, err = self.socket:close()
225    if (not(status)) then
226      return status, string.format("%s disconnect error: %s",
227        self.program, err)
228    end
229    self.socket=nil
230    return status, nil
231  end,
232
233  --- Checks if the rpc program is supported
234  --
235  -- @return status boolean true on success, false on failure
236  -- @return string containing error message (if status is false)
237  ChkProgram = function(self)
238    if (not(RPC_version[self.program])) then
239      return false, string.format("RPC library does not support: %s protocol",
240        self.program)
241    end
242    return true, nil
243  end,
244
245  --- Checks if the rpc program version is supported
246  --
247  -- @return status boolean true on success, false on failure
248  -- @return string containing error message (if status is false)
249  ChkVersion = function(self)
250    if not self.checkprogver then return true end
251    if ( self.version > RPC_version[self.program].max or
252        self.version < RPC_version[self.program].min ) then
253      return false, string.format("RPC library does not support: %s version %d",
254        self.program,self.version)
255    end
256    return true, nil
257  end,
258
259  --- Sets the rpc program version
260  --
261  -- @return status boolean true
262  SetVersion = function(self, version)
263    if self.checkprogver then
264      if (RPC_version[self.program] and RPC_args[self.program] and
265          nmap.registry.args and nmap.registry.args[RPC_args[self.program].ver]) then
266        self.version = tonumber(nmap.registry.args[RPC_args[self.program].ver])
267      elseif (not(self.version) and version) then
268        self.version = version
269      end
270    else
271      self.version = version
272    end
273    return true, nil
274  end,
275
276  --- Sets the verification of the specified program and version support
277  -- before trying to connecting.
278  -- @param check boolean to enable or disable checking of program and version support.
279  SetCheckProgVer = function(self, check)
280    self.checkprogver = check
281  end,
282
283  --- Sets the RPC program ID to use.
284  -- @param progid number Program ID to set.
285  SetProgID = function(self, progid)
286    self.program_id = progid
287  end,
288
289  --- Checks if <code>data</code> contains enough bytes to read the <code>needed</code> amount
290  --
291  --  If it doesn't it attempts to read the remaining amount of bytes from the
292  --  socket. Unlike <code>socket.receive_bytes</code>, reading less than
293  --  <code>needed</code> is treated as an error.
294  --
295  -- @param data string containing the current buffer
296  -- @param pos number containing the current offset into the buffer
297  -- @param needed number containing the number of bytes needed to be available
298  -- @return status success or failure
299  -- @return data string containing the data passed to the function and the additional data appended to it or error message on failure
300  GetAdditionalBytes = function( self, data, pos, needed )
301    local toread =  needed - ( data:len() - pos + 1 )
302    -- Do the loop ourselves instead of receive_bytes. Pathological case:
303    -- * read less than needed and timeout
304    -- * receive_bytes returns short but we don't know if it's eof or timeout
305    -- * Try again. If it was timeout, we've doubled the timeout waiting for bytes that aren't coming.
306    while toread > 0 do
307      local status, tmp = self.socket:receive()
308      if status then
309        toread = toread - #tmp
310        data = data .. tmp
311      else
312        return false, string.format("getAdditionalBytes read %d bytes before error: %s",
313          needed - toread, tmp)
314      end
315    end
316    return true, data
317  end,
318
319  --- Creates a RPC header
320  --
321  -- @param xid number. If no xid was provided, a random one will be used.
322  -- @param procedure number containing the procedure to call. Defaults to <code>0</code>.
323  -- @param auth table containing the authentication data to use. Defaults to NULL authentication.
324  -- @return status boolean true on success, false on failure
325  -- @return string of bytes on success, error message on failure
326  CreateHeader = function( self, xid, procedure, auth )
327    local RPC_VERSION = 2
328    local packet
329    -- Defaulting to NULL Authentication
330    local auth = auth or {type = Portmap.AuthType.NULL}
331    local xid = xid or math.random(1234567890)
332    local procedure = procedure or 0
333
334    packet = string.pack( ">I4 I4 I4 I4 I4 I4", xid, Portmap.MessageType.CALL, RPC_VERSION,
335      self.program_id, self.version, procedure )
336    if auth.type == Portmap.AuthType.NULL then
337      packet = packet .. string.pack( ">I4 I4 I4 I4", 0, 0, 0, 0 )
338    elseif auth.type == Portmap.AuthType.UNIX then
339      packet = packet .. Util.marshall_int32(auth.type)
340      local blob = (
341        Util.marshall_int32(math.floor(nmap.clock())) --time
342        .. Util.marshall_vopaque(auth.hostname or 'localhost')
343        .. Util.marshall_int32(auth.uid or 0)
344        .. Util.marshall_int32(auth.gid or 0)
345        )
346      if auth.gids then --len prefix gid list
347        blob = blob .. Util.marshall_int32(#auth.gids)
348        for _,gid in ipairs(auth.gids) do
349          blob = blob .. Util.marshall_int32(gid)
350        end
351      else
352        blob = blob .. Util.marshall_int32(0)
353      end
354      packet = (packet .. Util.marshall_vopaque(blob)
355        .. string.pack( ">I4 I4", 0, 0 ) --AUTH_NULL verf
356        )
357    else
358      return false, "Comm.CreateHeader: invalid authentication type specified"
359    end
360    return true, packet
361  end,
362
363  --- Decodes the RPC header (without the leading 4 bytes as received over TCP)
364  --
365  -- @param data string containing the buffer of bytes read so far
366  -- @param pos number containing the current offset into data
367  -- @return pos number containing the offset after the decoding
368  -- @return header table containing <code>xid</code>, <code>type</code>, <code>state</code>,
369  -- <code>verifier</code> and ( <code>accept_state</code> or <code>denied_state</code> )
370  DecodeHeader = function( self, data, pos )
371    local header = {}
372    local status
373
374    local HEADER_LEN = 20
375
376    header.verifier = {}
377
378    pos = pos or 1
379    if ( data:len() - pos + 1 < HEADER_LEN ) then
380      local tmp
381      status, tmp = self:GetAdditionalBytes( data, pos, HEADER_LEN - ( data:len() - pos ) )
382      if not status then
383        stdnse.debug4("Comm.DecodeHeader: failed to call GetAdditionalBytes")
384        return -1, nil
385      end
386      data = data .. tmp
387    end
388
389    header.xid, header.type, header.state, pos = string.unpack(">I4 I4 I4", data, pos)
390
391    if ( header.state == Portmap.State.MSG_DENIED ) then
392      header.denied_state, pos = string.unpack(">I4", data, pos )
393      return pos, header
394    end
395
396    header.verifier.flavor, pos = string.unpack(">I4", data, pos)
397    header.verifier.length, pos = string.unpack(">I4", data, pos)
398
399    if header.verifier.length - 8 > 0 then
400      status, data = self:GetAdditionalBytes( data, pos, header.verifier.length - 8 )
401      if not status then
402        stdnse.debug4("Comm.DecodeHeader: failed to call GetAdditionalBytes")
403        return -1, nil
404      end
405      header.verifier.data, pos = string.unpack("c" .. header.verifier.length - 8, data, pos )
406    end
407    header.accept_state, pos = string.unpack(">I4", data, pos )
408
409    return pos, header
410  end,
411
412  --- Reads the response from the socket
413  --
414  -- @return status true on success, false on failure
415  -- @return data string containing the raw response or error message on failure
416  ReceivePacket = function( self )
417    local status
418
419    if ( self.proto == "udp" ) then
420      -- There's not much we can do in here to check if we received all data
421      -- as the packet contains no length field. It's up to each decoding function
422      -- to do appropriate checks
423      return self.socket:receive_bytes(1)
424    else
425      local tmp, lastfragment, length
426      local data, pos = "", 1
427
428      -- Maximum number of allowed attempts to parse the received bytes. This
429      -- prevents the code from looping endlessly on invalid content.
430      local retries = 400
431
432      repeat
433        retries = retries - 1
434        lastfragment = false
435        status, data = self:GetAdditionalBytes( data, pos, 4 )
436        if ( not(status) ) then
437          return false, "Comm.ReceivePacket: failed to call GetAdditionalBytes"
438        end
439
440        tmp, pos = string.unpack(">I4", data, pos )
441        length = tmp & 0x7FFFFFFF
442
443        if (tmp & 0x80000000) == 0x80000000 then
444          lastfragment = true
445        end
446
447        status, data = self:GetAdditionalBytes( data, pos, length )
448        if ( not(status) ) then
449          return false, "Comm.ReceivePacket: failed to call GetAdditionalBytes"
450        end
451
452        --
453        -- When multiple packets are received they look like this
454        -- H = Header data
455        -- D = Data
456        --
457        -- We don't want the Header
458        --
459        -- HHHHDDDDDDDDDDDDDDHHHHDDDDDDDDDDD
460        -- ^   ^             ^   ^
461        -- 1   5             18  22
462        --
463        -- eg. we want
464        -- data:sub(5, 18) and data:sub(22)
465        --
466
467        local bufcopy = data:sub(pos)
468
469        if 1 ~= pos - 4 then
470          bufcopy = data:sub(1, pos - 5) .. bufcopy
471          pos = pos - 4
472        else
473          pos = 1
474        end
475
476        pos = pos + length
477        data = bufcopy
478      until (lastfragment == true) or (retries == 0)
479
480      if retries == 0 then
481        return false, "Aborted after too many retries"
482      end
483      return true, data
484    end
485  end,
486
487  --- Encodes a RPC packet
488  --
489  -- @param xid number containing the transaction ID
490  -- @param proc number containing the procedure to call
491  -- @param auth table containing authentication information
492  -- @param data string containing the packet data
493  -- @return packet string containing the encoded packet data
494  EncodePacket = function( self, xid, proc, auth, data )
495    local status, packet = self:CreateHeader( xid, proc, auth )
496    local len
497    if ( not(status) ) then
498      return
499    end
500
501    packet = packet .. ( data or "" )
502    if ( self.proto == "udp") then
503      return packet
504    else
505      -- set the high bit as this is our last fragment
506      len = 0x80000000 + packet:len()
507      return string.pack(">I4", len) .. packet
508    end
509  end,
510
511  SendPacket = function( self, packet )
512    if ( self.host and self.port ) then
513      return self.socket:sendto(self.host, self.port, packet)
514    else
515      return self.socket:send( packet )
516    end
517  end,
518
519  GetSocketInfo = function(self)
520    return self.socket:get_info()
521  end,
522
523}
524
525--- Portmap (rpcbind) class
526Portmap =
527{
528  PROTOCOLS = {
529    ['tcp'] = 6,
530    ['udp'] = 17,
531  },
532
533  -- TODO: add more Authentication Protocols
534  AuthType =
535  {
536    NULL = 0,
537    UNIX = 1,
538  },
539
540  -- TODO: complete Authentication stats and error messages
541  AuthState =
542  {
543    AUTH_OK = 0,
544    AUTH_BADCRED = 1,
545    AUTH_REJECTEDCRED = 2,
546    AUTH_BADVERF = 3,
547    AUTH_REJECTEDVERF = 4,
548    AUTH_TOOWEAK = 5,
549    AUTH_INVALIDRESP = 6,
550    AUTH_FAILED = 7,
551  },
552
553  AuthMsg =
554  {
555    [0] = "Success.",
556    [1] = "bad credential (seal broken).",
557    [2] = "client must begin new session.",
558    [3] = "bad verifier (seal broken).",
559    [4] = "verifier expired or replayed.",
560    [5] = "rejected for security reasons.",
561    [6] = "bogus response verifier.",
562    [7] = "reason unknown.",
563  },
564
565  MessageType =
566  {
567    CALL = 0,
568    REPLY = 1
569  },
570
571  Procedure =
572  {
573    [2] =
574    {
575      GETPORT = 3,
576      DUMP = 4,
577      CALLIT = 5,
578    },
579
580    [3] =
581    {
582      DUMP = 4,
583    },
584
585    [4] =
586    {
587      DUMP = 4,
588    },
589
590  },
591
592  State =
593  {
594    MSG_ACCEPTED = 0,
595    MSG_DENIED = 1,
596  },
597
598  AcceptState =
599  {
600    SUCCESS = 0,
601    PROG_UNAVAIL = 1,
602    PROG_MISMATCH = 2,
603    PROC_UNAVAIL = 3,
604    GARBAGE_ARGS = 4,
605    SYSTEM_ERR = 5,
606  },
607
608  AcceptMsg =
609  {
610    [0] = "RPC executed successfully.",
611    [1] = "remote hasn't exported program.",
612    [2] = "remote can't support version.",
613    [3] = "program can't support procedure.",
614    [4] = "procedure can't decode params.",
615    [5] = "errors like memory allocation failure.",
616  },
617
618  RejectState =
619  {
620    RPC_MISMATCH = 0,
621    AUTH_ERROR = 1,
622  },
623
624  RejectMsg =
625  {
626    [0] = "RPC version number != 2.",
627    [1] = "remote can't authenticate caller.",
628  },
629
630  new = function(self,o)
631    o = o or {}
632    setmetatable(o, self)
633    self.__index = self
634    return o
635  end,
636
637  --- Dumps a list of RCP programs from the portmapper
638  --
639  -- @param comm object handles rpc program information and
640  --  low-level packet manipulation
641  -- @return status boolean true on success, false on failure
642  -- @return result table containing RPC program information or error message
643  --         on failure. The table has the following format:
644  --
645  -- <code>
646  -- table[program_id][protocol]["port"] = <port number>
647  -- table[program_id][protocol]["version"] = <table of versions>
648  -- table[program_id][protocol]["addr"] = <IP address, for RPCv3 and higher>
649  -- </code>
650  --
651  -- Where
652  --  o program_id is the number associated with the program
653  --  o protocol is one of "tcp", "udp", "tcp6", or "udp6", or another netid
654  --    reported by the system.
655  --
656  Dump = function(self, comm)
657    local status, data, packet, response, pos, header
658    local program_table = setmetatable({}, { __mode = 'v' })
659
660    packet = comm:EncodePacket( nil, Portmap.Procedure[comm.version].DUMP,
661      { type=Portmap.AuthType.NULL }, data )
662    if (not(comm:SendPacket(packet))) then
663      return false, "Portmap.Dump: Failed to send data"
664    end
665    status, data = comm:ReceivePacket()
666    if ( not(status) ) then
667      return false, "Portmap.Dump: Failed to read data from socket"
668    end
669
670    pos, header = comm:DecodeHeader( data, 1 )
671    if ( not(header) ) then
672      return false, "Portmap.Dump: Failed to decode RPC header"
673    end
674
675    if header.type ~= Portmap.MessageType.REPLY then
676      return false, "Portmap.Dump: Packet was not a reply"
677    end
678
679    if header.state ~= Portmap.State.MSG_ACCEPTED then
680      if (Portmap.RejectMsg[header.denied_state]) then
681        return false,
682        string.format("Portmap.Dump: RPC call failed: %s",
683          Portmap.RejectMsg[header.denied_state])
684      else
685        return false,
686        string.format("Portmap.Dump: RPC call failed: code %d",
687          header.state)
688      end
689    end
690
691    if header.accept_state ~= Portmap.AcceptState.SUCCESS then
692      if (Portmap.AcceptMsg[header.accept_state]) then
693        return false,
694        string.format("Portmap.Dump: RPC accepted state: %s",
695          Portmap.AcceptMsg[header.accept_state])
696      else
697        return false,
698        string.format("Portmap.Dump: RPC accepted state code %d",
699          header.accept_state)
700      end
701    end
702
703    while true do
704      local vfollows
705      local program, version, protocol, port
706
707      status, data = comm:GetAdditionalBytes( data, pos, 4 )
708      if ( not(status) ) then
709        return false, "Portmap.Dump: Failed to call GetAdditionalBytes"
710      end
711      vfollows, pos = string.unpack(">I4", data, pos)
712      if ( vfollows == 0 ) then
713        break
714      end
715
716      program, version, pos = string.unpack(">I4 I4", data, pos)
717      local addr, owner
718      if comm.version > 2 then
719        local len
720        len, pos = string.unpack(">I4", data, pos)
721        pos, protocol = Util.unmarshall_vopaque(len, data, pos)
722        -- workaround for NetApp 5.0: trim trailing null bytes
723        protocol = protocol:match("[^\0]*")
724        len, pos = string.unpack(">I4", data, pos)
725        pos, addr = Util.unmarshall_vopaque(len, data, pos)
726        len, pos = string.unpack(">I4", data, pos)
727        pos, owner = Util.unmarshall_vopaque(len, data, pos)
728        if protocol:match("^[tu][cd]p6?$") then
729            -- RFC 5665
730            local upper, lower
731            addr, upper, lower = addr:match("^(.-)%.(%d+)%.(%d+)$")
732            if addr then
733              port = tonumber(upper) * 0x100 + tonumber(lower)
734            end
735        end
736      else
737        protocol, port, pos = string.unpack(">I4 I4", data, pos)
738        if ( protocol == Portmap.PROTOCOLS.tcp ) then
739          protocol = "tcp"
740        elseif ( protocol == Portmap.PROTOCOLS.udp ) then
741          protocol = "udp"
742        end
743      end
744
745      program_table[program] = program_table[program] or {}
746      program_table[program][protocol] = program_table[program][protocol] or {}
747      program_table[program][protocol]["port"] = port
748      program_table[program][protocol]["addr"] = addr
749      program_table[program][protocol]["owner"] = owner
750      program_table[program][protocol]["version"] = program_table[program][protocol]["version"] or {}
751      table.insert( program_table[program][protocol]["version"], version )
752      -- parts of the code rely on versions being in order
753      -- this way the highest version can be chosen by choosing the last element
754      table.sort( program_table[program][protocol]["version"] )
755    end
756
757    nmap.registry[comm.ip]['portmapper'] = program_table
758    return true, nmap.registry[comm.ip]['portmapper']
759  end,
760
761  --- Calls the portmap callit call and returns the raw response
762  --
763  -- @param comm object handles rpc program information and
764  --  low-level packet manipulation
765  -- @param program string name of the program
766  -- @param protocol string containing either "tcp" or "udp"
767  -- @param version number containing the version of the queried program
768  -- @return status true on success, false on failure
769  -- @return data string containing the raw response
770  Callit = function( self, comm, program, protocol, version )
771    if ( not( Portmap.PROTOCOLS[protocol] ) ) then
772      return false, ("Portmap.Callit: Protocol %s not supported"):format(protocol)
773    end
774
775    if ( Util.ProgNameToNumber(program) == nil ) then
776      return false, ("Portmap.Callit: Unknown program name: %s"):format(program)
777    end
778
779    local data = string.pack(">I4 I4 I4 I4", Util.ProgNameToNumber(program), version, 0, 0 )
780    local packet = comm:EncodePacket(nil, Portmap.Procedure[comm.version].CALLIT,
781      { type=Portmap.AuthType.NULL }, data )
782
783    if (not(comm:SendPacket(packet))) then
784      return false, "Portmap.Callit: Failed to send data"
785    end
786
787    data = ""
788    local status, data = comm:ReceivePacket()
789    if ( not(status) ) then
790      return false, "Portmap.Callit: Failed to read data from socket"
791    end
792
793    local pos, header = comm:DecodeHeader( data, 1 )
794    if ( not(header) ) then
795      return false, "Portmap.Callit: Failed to decode RPC header"
796    end
797
798    if header.type ~= Portmap.MessageType.REPLY then
799      return false, "Portmap.Callit: Packet was not a reply"
800    end
801
802    return true, data
803  end,
804
805
806  --- Queries the portmapper for the port of the selected program,
807  --  protocol and version
808  --
809  -- @param comm object handles rpc program information and
810  --  low-level packet manipulation
811  -- @param program string name of the program
812  -- @param protocol string containing either "tcp" or "udp"
813  -- @param version number containing the version of the queried program
814  -- @return number containing the port number
815  GetPort = function( self, comm, program, protocol, version )
816    local status, data, response, header, pos, packet
817    local xid
818
819    if ( not( Portmap.PROTOCOLS[protocol] ) ) then
820      return false, ("Portmap.GetPort: Protocol %s not supported"):format(protocol)
821    end
822
823    if ( Util.ProgNameToNumber(program) == nil ) then
824      return false, ("Portmap.GetPort: Unknown program name: %s"):format(program)
825    end
826
827    data = string.pack(">I4 I4 I4 I4", Util.ProgNameToNumber(program), version,
828      Portmap.PROTOCOLS[protocol], 0 )
829    packet = comm:EncodePacket(xid, Portmap.Procedure[comm.version].GETPORT,
830      { type=Portmap.AuthType.NULL }, data )
831
832    if (not(comm:SendPacket(packet))) then
833      return false, "Portmap.GetPort: Failed to send data"
834    end
835
836    data = ""
837    status, data = comm:ReceivePacket()
838    if ( not(status) ) then
839      return false, "Portmap.GetPort: Failed to read data from socket"
840    end
841
842    pos, header = comm:DecodeHeader( data, 1 )
843
844    if ( not(header) ) then
845      return false, "Portmap.GetPort: Failed to decode RPC header"
846    end
847
848    if header.type ~= Portmap.MessageType.REPLY then
849      return false, "Portmap.GetPort: Packet was not a reply"
850    end
851
852    if header.state ~= Portmap.State.MSG_ACCEPTED then
853      if (Portmap.RejectMsg[header.denied_state]) then
854        return false, string.format("Portmap.GetPort: RPC call failed: %s",
855          Portmap.RejectMsg[header.denied_state])
856      else
857        return false,
858        string.format("Portmap.GetPort: RPC call failed: code %d",
859          header.state)
860      end
861    end
862
863    if header.accept_state ~= Portmap.AcceptState.SUCCESS then
864      if (Portmap.AcceptMsg[header.accept_state]) then
865        return false, string.format("Portmap.GetPort: RPC accepted state: %s",
866          Portmap.AcceptMsg[header.accept_state])
867      else
868        return false, string.format("Portmap.GetPort: RPC accepted state code %d",
869          header.accept_state)
870      end
871    end
872
873    status, data = comm:GetAdditionalBytes( data, pos, 4 )
874    if ( not(status) ) then
875      return false, "Portmap.GetPort: Failed to call GetAdditionalBytes"
876    end
877
878    return true, string.unpack(">I4", data, pos)
879  end,
880
881}
882
883--- Mount class handling communication with the mountd program
884--
885-- Currently supports versions 1 through 3
886-- Can be called either directly or through the static Helper class
887--
888Mount = {
889
890  StatMsg = {
891    [1] = "Not owner.",
892    [2] = "No such file or directory.",
893    [5] = "I/O error.",
894    [13] = "Permission denied.",
895    [20] = "Not a directory.",
896    [22] = "Invalid argument.",
897    [63] = "Filename too long.",
898    [10004] = "Operation not supported.",
899    [10006] = "A failure on the server.",
900  },
901
902  StatCode = {
903    MNT_OK = 0,
904    MNTERR_PERM = 1,
905    MNTERR_NOENT = 2,
906    MNTERR_IO = 5,
907    MNTERR_ACCES = 13,
908    MNTERR_NOTDIR = 20,
909    MNTERR_INVAL = 22,
910    MNTERR_NAMETOOLONG = 63,
911    MNTERR_NOTSUPP = 10004,
912    MNTERR_SERVERFAULT = 10006,
913  },
914
915  Procedure =
916  {
917    MOUNT = 1,
918    DUMP = 2,
919    UMNT = 3,
920    UMNTALL = 4,
921    EXPORT = 5,
922  },
923
924  new = function(self,o)
925    o = o or {}
926    setmetatable(o, self)
927    self.__index = self
928    return o
929  end,
930
931  --- Requests a list of NFS export from the remote server
932  --
933  -- @param comm object handles rpc program information and
934  --  low-level packet manipulation
935  -- @return status success or failure
936  -- @return entries table containing a list of share names (strings)
937  Export = function(self, comm)
938    local msg_type = 0
939    local packet
940    local pos = 1
941    local header = {}
942    local entries = {}
943    local data = ""
944    local status
945
946    if comm.proto ~= "tcp" and comm.proto ~= "udp" then
947      return false, "Mount.Export: Protocol should be either udp or tcp"
948    end
949
950    packet = comm:EncodePacket(nil, Mount.Procedure.EXPORT,
951      { type=Portmap.AuthType.UNIX }, nil )
952    if (not(comm:SendPacket( packet ))) then
953      return false, "Mount.Export: Failed to send data"
954    end
955
956    status, data = comm:ReceivePacket()
957    if ( not(status) ) then
958      return false, "Mount.Export: Failed to read data from socket"
959    end
960
961    -- make sure we have at least 24 bytes to unpack the header
962    status, data = comm:GetAdditionalBytes( data, pos, 24 )
963    if (not(status)) then
964      return false, "Mount.Export: Failed to call GetAdditionalBytes"
965    end
966    pos, header = comm:DecodeHeader( data, pos )
967    if not header then
968      return false, "Mount.Export: Failed to decode header"
969    end
970
971    if header.type ~= Portmap.MessageType.REPLY then
972      return false, "Mount.Export: packet was not a reply"
973    end
974
975    if header.state ~= Portmap.State.MSG_ACCEPTED then
976      if (Portmap.RejectMsg[header.denied_state]) then
977        return false, string.format("Mount.Export: RPC call failed: %s",
978          Portmap.RejectMsg[header.denied_state])
979      else
980        return false, string.format("Mount.Export: RPC call failed: code %d",
981          header.state)
982      end
983    end
984
985    if header.accept_state ~= Portmap.AcceptState.SUCCESS then
986      if (Portmap.AcceptMsg[header.accept_state]) then
987        return false, string.format("Mount.Export: RPC accepted state: %s",
988          Portmap.AcceptMsg[header.accept_state])
989      else
990        return false, string.format("Mount.Export: RPC accepted state code %d",
991          header.accept_state)
992      end
993    end
994
995    --  Decode directory entries
996    --
997    --  [entry]
998    --     4 bytes   - value follows (1 if more data, 0 if not)
999    --     [Directory]
1000    --        4 bytes   - value len
1001    --        len bytes - directory name
1002    --        ? bytes   - fill bytes (see calcFillByte)
1003    --     [Groups]
1004    --        4 bytes  - value follows (1 if more data, 0 if not)
1005    --         [Group] (1 or more)
1006    --            4 bytes   - group len
1007    --            len bytes - group value
1008    --            ? bytes   - fill bytes (see calcFillByte)
1009    while true do
1010      -- make sure we have atleast 4 more bytes to check for value follows
1011      status, data = comm:GetAdditionalBytes( data, pos, 4 )
1012      if (not(status)) then
1013        return false, "Mount.Export: Failed to call GetAdditionalBytes"
1014      end
1015
1016      local data_follows
1017      pos, data_follows = Util.unmarshall_uint32(data, pos)
1018
1019      if data_follows ~= 1 then
1020        break
1021      end
1022
1023      --- Export list entry starts here
1024      local entry = {}
1025      local len
1026
1027      -- make sure we have atleast 4 more bytes to get the length
1028      status, data = comm:GetAdditionalBytes( data, pos, 4 )
1029      if (not(status)) then
1030        return false, "Mount.Export: Failed to call GetAdditionalBytes"
1031      end
1032      pos, len = Util.unmarshall_uint32(data, pos)
1033
1034      status, data = comm:GetAdditionalBytes( data, pos, len )
1035      if (not(status)) then
1036        return false, "Mount.Export: Failed to call GetAdditionalBytes"
1037      end
1038      pos, entry.name = Util.unmarshall_vopaque(len, data, pos)
1039
1040      -- decode groups
1041      while true do
1042        local group
1043
1044        status, data = comm:GetAdditionalBytes( data, pos, 4 )
1045        if (not(status)) then
1046          return false, "Mount.Export: Failed to call GetAdditionalBytes"
1047        end
1048        pos, data_follows = Util.unmarshall_uint32(data, pos)
1049
1050        if data_follows ~= 1 then
1051          break
1052        end
1053
1054        status, data = comm:GetAdditionalBytes( data, pos, 4 )
1055        if (not(status)) then
1056          return false, "Mount.Export: Failed to call GetAdditionalBytes"
1057        end
1058
1059        pos, len = Util.unmarshall_uint32(data, pos)
1060        status, data = comm:GetAdditionalBytes( data, pos, len )
1061        if (not(status)) then
1062          return false, "Mount.Export: Failed to call GetAdditionalBytes"
1063        end
1064        pos, group = Util.unmarshall_vopaque(len, data, pos)
1065        table.insert( entry, group )
1066      end
1067      table.insert(entries, entry)
1068    end
1069    return true, entries
1070  end,
1071
1072  --- Attempts to mount a remote export in order to get the filehandle
1073  --
1074  -- @param comm object handles rpc program information and
1075  --  low-level packet manipulation
1076  -- @param path string containing the path to mount
1077  -- @return status success or failure
1078  -- @return fhandle string containing the filehandle of the remote export
1079  Mount = function(self, comm, path)
1080    local packet, mount_status
1081    local status, len
1082
1083    local data = Util.marshall_vopaque(path)
1084
1085    packet = comm:EncodePacket( nil, Mount.Procedure.MOUNT, { type=Portmap.AuthType.UNIX }, data )
1086    if (not(comm:SendPacket(packet))) then
1087      return false, "Mount: Failed to send data"
1088    end
1089
1090    status, data = comm:ReceivePacket()
1091    if ( not(status) ) then
1092      return false, "Mount: Failed to read data from socket"
1093    end
1094
1095    local pos, header = comm:DecodeHeader(data)
1096    if not header then
1097      return false, "Mount: Failed to decode header"
1098    end
1099
1100    if header.type ~= Portmap.MessageType.REPLY then
1101      return false, "Mount: Packet was not a reply"
1102    end
1103
1104    if header.state ~= Portmap.State.MSG_ACCEPTED then
1105      if (Portmap.RejectMsg[header.denied_state]) then
1106        return false, string.format("Mount: RPC call failed: %s",
1107          Portmap.RejectMsg[header.denied_state])
1108      else
1109        return false, string.format("Mount: RPC call failed: code %d",
1110          header.state)
1111      end
1112    end
1113
1114    if header.accept_state ~= Portmap.AcceptState.SUCCESS then
1115      if (Portmap.AcceptMsg[header.accept_state]) then
1116        return false, string.format("Mount (%s): RPC accepted state: %s",
1117          path, Portmap.AcceptMsg[header.accept_state])
1118      else
1119        return false, string.format("Mount (%s): RPC accepted state code %d",
1120          path, header.accept_state)
1121      end
1122    end
1123
1124    status, data = comm:GetAdditionalBytes( data, pos, 4 )
1125    if (not(status)) then
1126      return false, "Mount: Failed to call GetAdditionalBytes"
1127    end
1128    pos, mount_status = Util.unmarshall_uint32(data, pos)
1129
1130    if (mount_status ~= Mount.StatCode.MNT_OK) then
1131      if (Mount.StatMsg[mount_status]) then
1132        return false, string.format("Mount failed: %s",Mount.StatMsg[mount_status])
1133      else
1134        return false, string.format("Mount failed: code %d", mount_status)
1135      end
1136    end
1137
1138    local fhandle
1139    if ( comm.version == 3 ) then
1140      status, data = comm:GetAdditionalBytes( data, pos, 4 )
1141      if (not(status)) then
1142        return false, "Mount: Failed to call GetAdditionalBytes"
1143      end
1144      len = string.unpack(">I4", data, pos)
1145      status, data = comm:GetAdditionalBytes( data, pos, len + 4 )
1146      if (not(status)) then
1147        return false, "Mount: Failed to call GetAdditionalBytes"
1148      end
1149      fhandle, pos = string.unpack( "c" .. len + 4, data, pos )
1150    elseif ( comm.version < 3 ) then
1151      status, data = comm:GetAdditionalBytes( data, pos, 32 )
1152      if (not(status)) then
1153        return false, "Mount: Failed to call GetAdditionalBytes"
1154      end
1155      fhandle, pos = string.unpack( "c32", data, pos )
1156    else
1157      return false, "Mount failed"
1158    end
1159
1160    return true, fhandle
1161  end,
1162
1163  --- Attempts to unmount a remote export in order to get the filehandle
1164  --
1165  -- @param comm object handles rpc program information and
1166  --  low-level packet manipulation
1167  -- @param path string containing the path to mount
1168  -- @return status success or failure
1169  -- @return error string containing error if status is false
1170  Unmount = function(self, comm, path)
1171    local packet, status
1172    local _, pos, data, header, fhandle = "", 1, "", "", {}
1173
1174    data = Util.marshall_vopaque(path)
1175
1176    packet = comm:EncodePacket( nil, Mount.Procedure.UMNT, { type=Portmap.AuthType.UNIX }, data )
1177    if (not(comm:SendPacket(packet))) then
1178      return false, "Unmount: Failed to send data"
1179    end
1180
1181    status, data = comm:ReceivePacket( )
1182    if ( not(status) ) then
1183      return false, "Unmount: Failed to read data from socket"
1184    end
1185
1186    pos, header = comm:DecodeHeader( data, pos )
1187    if not header then
1188      return false, "Unmount: Failed to decode header"
1189    end
1190
1191    if header.type ~= Portmap.MessageType.REPLY then
1192      return false, "Unmount: Packet was not a reply"
1193    end
1194
1195    if header.state ~= Portmap.State.MSG_ACCEPTED then
1196      if (Portmap.RejectMsg[header.denied_state]) then
1197        return false, string.format("Unmount: RPC call failed: %s",
1198          Portmap.RejectMsg[header.denied_state])
1199      else
1200        return false, string.format("Unmount: RPC call failed: code %d",
1201          header.state)
1202      end
1203    end
1204
1205    if header.accept_state ~= Portmap.AcceptState.SUCCESS then
1206      if (Portmap.AcceptMsg[header.accept_state]) then
1207        return false, string.format("Unmount (%s): RPC accepted state: %s",
1208          path, Portmap.AcceptMsg[header.accept_state])
1209      else
1210        return false, string.format("Unmount (%s): RPC accepted state code %d",
1211          path, header.accept_state)
1212      end
1213    end
1214
1215    return true, ""
1216  end,
1217}
1218
1219--- NFS class handling communication with the nfsd program
1220--
1221-- Currently supports versions 1 through 3
1222-- Can be called either directly or through the static Helper class
1223--
1224NFS = {
1225
1226  -- NFS error msg v2 and v3
1227  StatMsg = {
1228    [1] = "Not owner.",
1229    [2] = "No such file or directory.",
1230    [5] = "I/O error.",
1231    [6] = "I/O error. No such device or address.",
1232    [13] = "Permission denied.",
1233    [17] = "File exists.",
1234    [18] = "Attempt to do a cross-device hard link.",
1235    [19] = "No such device.",
1236    [20] = "Not a directory.",
1237    [21] = "Is a directory.",
1238    [22] = "Invalid argument or unsupported argument for an operation.",
1239    [27] = "File too large.",
1240    [28] = "No space left on device.",
1241    [30] = "Read-only file system.",
1242    [31] = "Too many hard links.",
1243    [63] = "The filename in an operation was too long.",
1244    [66] = "An attempt was made to remove a directory that was not empty.",
1245    [69] = "Resource (quota) hard limit exceeded.",
1246    [70] = "Invalid file handle.",
1247    [71] = "Too many levels of remote in path.",
1248    [99] = "The server's write cache used in the \"WRITECACHE\" call got flushed to disk.",
1249    [10001] = "Illegal NFS file handle.",
1250    [10002] = "Update synchronization mismatch was detected during a SETATTR operation.",
1251    [10003] = "READDIR or READDIRPLUS cookie is stale.",
1252    [10004] = "Operation is not supported.",
1253    [10005] = "Buffer or request is too small.",
1254    [10006] = "An error occurred on the server which does not map to any of the legal NFS version 3 protocol error values.",
1255    [10007] = "An attempt was made to create an object of a type not supported by the server.",
1256    [10008] = "The server initiated the request, but was not able to complete it in a timely fashion.",
1257  },
1258
1259  StatCode = {
1260    -- NFS Version 1
1261    [1] = {
1262      NFS_OK        = 0,
1263      NFSERR_PERM   = 1,
1264      NFSERR_NOENT  = 2,
1265      NFSERR_IO     = 5,
1266      NFSERR_NXIO   = 6,
1267      NFSERR_ACCES  = 13,
1268      NFSERR_EXIST  = 17,
1269      NFSERR_NODEV  = 19,
1270      NFSERR_NOTDIR = 20,
1271      NFSERR_ISDIR  = 21,
1272      NFSERR_FBIG   = 27,
1273      NFSERR_NOSPC  = 28,
1274      NFSERR_ROFS   = 30,
1275      NFSERR_NAMETOOLONG = 63,
1276      NFSERR_NOTEMPTY = 66,
1277      NFSERR_DQUOT  = 69,
1278      NFSERR_STALE  = 70,
1279      NFSERR_WFLUSH = 99,
1280    },
1281
1282    -- NFS Version 2
1283    [2] = {
1284      NFS_OK        = 0,
1285      NFSERR_PERM   = 1,
1286      NFSERR_NOENT  = 2,
1287      NFSERR_IO     = 5,
1288      NFSERR_NXIO   = 6,
1289      NFSERR_ACCES  = 13,
1290      NFSERR_EXIST  = 17,
1291      NFSERR_NODEV  = 19,
1292      NFSERR_NOTDIR = 20,
1293      NFSERR_ISDIR  = 21,
1294      NFSERR_FBIG   = 27,
1295      NFSERR_NOSPC  = 28,
1296      NFSERR_ROFS   = 30,
1297      NFSERR_NAMETOOLONG = 63,
1298      NFSERR_NOTEMPTY = 66,
1299      NFSERR_DQUOT  = 69,
1300      NFSERR_STALE  = 70,
1301      NFSERR_WFLUSH = 99,
1302    },
1303
1304    -- NFS Version 3
1305    [3] = {
1306      NFS_OK          = 0,
1307      NFSERR_PERM     = 1,
1308      NFSERR_NOENT    = 2,
1309      NFSERR_IO       = 5,
1310      NFSERR_NXIO     = 6,
1311      NFSERR_ACCES    = 13,
1312      NFSERR_EXIST    = 17,
1313      NFSERR_XDEV     = 18,
1314      NFSERR_NODEV    = 19,
1315      NFSERR_NOTDIR   = 20,
1316      NFSERR_ISDIR    = 21,
1317      NFSERR_INVAL    = 22,
1318      NFSERR_FBIG     = 27,
1319      NFSERR_NOSPC    = 28,
1320      NFSERR_ROFS     = 30,
1321      NFSERR_MLINK    = 31,
1322      NFSERR_NAMETOOLONG = 63,
1323      NFSERR_NOTEMPTY = 66,
1324      NFSERR_DQUOT    = 69,
1325      NFSERR_STALE    = 70,
1326      NFSERR_REMOTE   = 71,
1327      NFSERR_BADHANDLE = 10001,
1328      NFSERR_NOT_SYNC = 10002,
1329      NFSERR_BAD_COOKIE = 10003,
1330      NFSERR_NOTSUPP = 10004,
1331      NFSERR_TOOSMALL = 10005,
1332      NFSERR_SERVERFAULT = 10006,
1333      NFSERR_BADTYPE = 10007,
1334      NFSERR_JUKEBOX = 10008,
1335    },
1336  },
1337
1338  -- Unfortunately the NFS procedure numbers differ in between versions
1339  Procedure =
1340  {
1341    -- NFS Version 1
1342    [1] =
1343    {
1344      GETATTR = 1,
1345      ROOT = 3,
1346      LOOKUP = 4,
1347      EXPORT = 5,
1348      READDIR = 16,
1349      STATFS = 17,
1350    },
1351
1352    -- NFS Version 2
1353    [2] =
1354    {
1355      GETATTR = 1,
1356      ROOT = 3,
1357      LOOKUP = 4,
1358      EXPORT = 5,
1359      READDIR = 16,
1360      STATFS = 17,
1361    },
1362
1363    -- NFS Version 3
1364    [3] =
1365    {
1366      GETATTR = 1,
1367      SETATTR = 2,
1368      LOOKUP = 3,
1369      ACCESS = 4,
1370      EXPORT = 5,
1371      READDIR = 16,
1372      READDIRPLUS = 17,
1373      FSSTAT = 18,
1374      FSINFO = 19,
1375      PATHCONF = 20,
1376      COMMIT = 21,
1377    },
1378  },
1379
1380  -- ACCESS values used to check the bit mask.
1381  AccessBits =
1382  {
1383    [3] =
1384    {
1385      ACCESS_READ    = 0x0001,
1386      ACCESS_LOOKUP  = 0x0002,
1387      ACCESS_MODIFY  = 0x0004,
1388      ACCESS_EXTEND  = 0x0008,
1389      ACCESS_DELETE  = 0x0010,
1390      ACCESS_EXECUTE = 0x0020,
1391    },
1392  },
1393
1394  FSinfoBits =
1395  {
1396    [3] =
1397    {
1398      FSF_LINK        = 0x0001,
1399      FSF_SYMLINK     = 0x0002,
1400      FSF_HOMOGENEOUS = 0x0008,
1401      FSF_CANSETTIME  = 0x0010,
1402    },
1403  },
1404
1405  new = function(self,o)
1406    o = o or {}
1407    setmetatable(o, self)
1408    self.__index = self
1409    return o
1410  end,
1411
1412  CheckStat = function (self, procedurename, version, status)
1413    if (status ~= NFS.StatCode[version].NFS_OK) then
1414      if (NFS.StatMsg[status]) then
1415        stdnse.debug4(
1416          string.format("%s failed: %s", procedurename, NFS.StatMsg[status]))
1417      else
1418        stdnse.debug4(
1419          string.format("%s failed: code %d", procedurename, status))
1420      end
1421
1422      return false
1423    end
1424
1425    return true
1426  end,
1427
1428  AccessRead = function (self, mask, version)
1429    return (mask & NFS.AccessBits[version].ACCESS_READ)
1430  end,
1431
1432  AccessLookup = function (self, mask, version)
1433    return (mask & NFS.AccessBits[version].ACCESS_LOOKUP)
1434  end,
1435
1436  AccessModify = function (self, mask, version)
1437    return (mask & NFS.AccessBits[version].ACCESS_MODIFY)
1438  end,
1439
1440  AccessExtend = function (self, mask, version)
1441    return (mask & NFS.AccessBits[version].ACCESS_EXTEND)
1442  end,
1443
1444  AccessDelete = function (self, mask, version)
1445    return (mask & NFS.AccessBits[version].ACCESS_DELETE)
1446  end,
1447
1448  AccessExecute = function (self, mask, version)
1449    return (mask & NFS.AccessBits[version].ACCESS_EXECUTE)
1450  end,
1451
1452  FSinfoLink = function(self, mask, version)
1453    return (mask & NFS.FSinfoBits[version].FSF_LINK)
1454  end,
1455
1456  FSinfoSymlink = function(self, mask, version)
1457    return (mask & NFS.FSinfoBits[version].FSF_SYMLINK)
1458  end,
1459
1460  FSinfoHomogeneous = function(self, mask, version)
1461    return (mask & NFS.FSinfoBits[version].FSF_HOMOGENEOUS)
1462  end,
1463
1464  FSinfoCansettime = function(self, mask, version)
1465    return (mask & NFS.FSinfoBits[version].FSF_CANSETTIME)
1466  end,
1467
1468  --- Decodes the READDIR section of a NFS ReadDir response
1469  --
1470  -- @param comm object handles rpc program information and
1471  --  low-level packet manipulation
1472  -- @param data string containing the buffer of bytes read so far
1473  -- @param pos number containing the current offset into data
1474  -- @return pos number containing the offset after the decoding
1475  -- @return entries table containing two table entries <code>attributes</code>
1476  --         and <code>entries</code>. The attributes entry is only present when
1477  --         using NFS version 3. The <code>entries</code> field contain one
1478  --         table for each file/directory entry. It has the following fields
1479  --         <code>file_id</code>, <code>name</code> and <code>cookie</code>
1480  --
1481  ReadDirDecode = function( self, comm, data, pos )
1482    local response = {}
1483    local value_follows
1484    local status, _
1485
1486    status, data = comm:GetAdditionalBytes( data, pos, 4 )
1487    if (not(status)) then
1488      stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1489      return -1, nil
1490    end
1491
1492    pos, status = Util.unmarshall_uint32(data, pos)
1493    if (not self:CheckStat("READDIR", comm.version, status)) then
1494      return -1, nil
1495    end
1496
1497    if ( 3 == comm.version ) then
1498      local attrib = {}
1499      response.attributes = {}
1500      status, data = comm:GetAdditionalBytes( data, pos, 4 )
1501      if (not(status)) then
1502        stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1503        return -1, nil
1504      end
1505
1506      pos, value_follows = Util.unmarshall_uint32(data, pos)
1507      if value_follows == 0 then
1508        return -1, nil
1509      end
1510      status, data = comm:GetAdditionalBytes( data, pos, 84 )
1511      if (not(status)) then
1512        stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1513        return -1, nil
1514      end
1515      pos, attrib = Util.unmarshall_nfsattr(data, pos, comm.version)
1516      table.insert(response.attributes, attrib)
1517      -- opaque data
1518      status, data = comm:GetAdditionalBytes( data, pos, 8 )
1519      if (not(status)) then
1520        stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1521        return -1, nil
1522      end
1523      _, pos = string.unpack(">I8", data, pos)
1524    end
1525
1526    response.entries = {}
1527    while true do
1528      local entry = {}
1529      status, data = comm:GetAdditionalBytes( data, pos, 4 )
1530      if (not(status)) then
1531        stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1532        return -1, nil
1533      end
1534
1535      pos, value_follows = Util.unmarshall_uint32(data, pos)
1536      if ( value_follows == 0 ) then
1537        break
1538      end
1539
1540      if ( 3 == comm.version ) then
1541        status, data = comm:GetAdditionalBytes( data, pos, 8 )
1542        if (not(status)) then
1543          stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1544          return -1, nil
1545        end
1546        pos, entry.fileid = Util.unmarshall_uint64(data, pos )
1547      else
1548        status, data = comm:GetAdditionalBytes( data, pos, 4 )
1549        if (not(status)) then
1550          stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1551          return -1, nil
1552        end
1553        pos, entry.fileid = Util.unmarshall_uint32(data, pos)
1554      end
1555
1556      status, data = comm:GetAdditionalBytes( data, pos, 4 )
1557      if (not(status)) then
1558        stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1559        return -1, nil
1560      end
1561
1562      pos, entry.length = Util.unmarshall_uint32(data, pos)
1563      status, data = comm:GetAdditionalBytes( data, pos, entry.length )
1564      if (not(status)) then
1565        stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1566        return -1, nil
1567      end
1568
1569      pos, entry.name = Util.unmarshall_vopaque(entry.length, data, pos)
1570      if ( 3 == comm.version ) then
1571        status, data = comm:GetAdditionalBytes( data, pos, 8 )
1572        if (not(status)) then
1573          stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1574          return -1, nil
1575        end
1576        pos, entry.cookie = Util.unmarshall_uint64(data, pos)
1577      else
1578        status, data = comm:GetAdditionalBytes(  data, pos, 4 )
1579        if (not(status)) then
1580          stdnse.debug4("NFS.ReadDirDecode: Failed to call GetAdditionalBytes")
1581          return -1, nil
1582        end
1583        pos, entry.cookie = Util.unmarshall_uint32(data, pos)
1584      end
1585      table.insert( response.entries, entry )
1586    end
1587    return pos, response
1588  end,
1589
1590  --- Reads the contents inside a NFS directory
1591  --
1592  -- @param comm object handles rpc program information and
1593  --  low-level packet manipulation
1594  -- @param file_handle string containing the filehandle to query
1595  -- @return status true on success, false on failure
1596  -- @return table of file table entries as described in <code>decodeReadDir</code>
1597  ReadDir = function( self, comm, file_handle )
1598    local status, packet
1599    local cookie, count = 0, 8192
1600    local pos, data, _ = 1, "", ""
1601    local header, response = {}, {}
1602
1603    if ( not(file_handle) ) then
1604      return false, "ReadDir: No filehandle received"
1605    end
1606
1607    if ( comm.version == 3 ) then
1608      local opaque_data = 0
1609      data = file_handle .. string.pack(">I8 I8 I4", cookie, opaque_data, count)
1610    else
1611      data = file_handle .. string.pack(">I4 I4", cookie, count)
1612    end
1613    packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].READDIR,
1614      { type=Portmap.AuthType.UNIX }, data )
1615    if(not(comm:SendPacket( packet ))) then
1616      return false, "ReadDir: Failed to send data"
1617    end
1618
1619    status, data = comm:ReceivePacket()
1620    if ( not(status) ) then
1621      return false, "ReadDir: Failed to read data from socket"
1622    end
1623
1624    pos, header = comm:DecodeHeader( data, pos )
1625    if not header then
1626      return false, "ReadDir: Failed to decode header"
1627    end
1628    pos, response = self:ReadDirDecode( comm, data, pos )
1629    if (not(response)) then
1630      return false, "ReadDir: Failed to decode the READDIR section"
1631    end
1632    return true, response
1633  end,
1634
1635  LookUpDecode = function(self, comm, data, pos)
1636    local lookup, status, len, value_follows, _ = {}
1637
1638    status, data = comm:GetAdditionalBytes(data, pos, 4)
1639    if not status then
1640      stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1641      return -1, nil
1642    end
1643
1644    pos, status = Util.unmarshall_uint32(data, pos)
1645    if (not self:CheckStat("LOOKUP", comm.version, status)) then
1646      return -1, nil
1647    end
1648
1649    if (comm.version == 3) then
1650      status, data = comm:GetAdditionalBytes( data, pos, 4)
1651      if (not(status)) then
1652        stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1653        return -1, nil
1654      end
1655      _, len = Util.unmarshall_uint32(data, pos)
1656      status, data = comm:GetAdditionalBytes( data, pos, len + 4)
1657      if (not(status)) then
1658        stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1659        return -1, nil
1660      end
1661      lookup.fhandle, pos = string.unpack( "c" .. len + 4, data, pos)
1662
1663      status, data = comm:GetAdditionalBytes( data, pos, 4)
1664      if (not(status)) then
1665        stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1666        return -1, nil
1667      end
1668
1669      lookup.attributes = {}
1670      pos, value_follows = Util.unmarshall_uint32(data, pos)
1671      if (value_follows ~= 0) then
1672        status, data = comm:GetAdditionalBytes(data, pos, 84)
1673        if (not(status)) then
1674          stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1675          return -1, nil
1676        end
1677        pos, lookup.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
1678      else
1679        stdnse.debug4("NFS.LookUpDecode: File Attributes follow failed")
1680      end
1681
1682      status, data = comm:GetAdditionalBytes( data, pos, 4)
1683      if (not(status)) then
1684        stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1685        return -1, nil
1686      end
1687
1688      lookup.dir_attributes = {}
1689      pos, value_follows = Util.unmarshall_uint32(data, pos)
1690      if (value_follows ~= 0) then
1691        status, data = comm:GetAdditionalBytes(data, pos, 84)
1692        if (not(status)) then
1693          stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1694          return -1, nil
1695        end
1696        pos, lookup.dir_attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
1697      else
1698        stdnse.debug4("NFS.LookUpDecode: File Attributes follow failed")
1699      end
1700
1701    elseif (comm.version < 3) then
1702      status, data = comm:GetAdditionalBytes( data, pos, 32)
1703      if (not(status)) then
1704        stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1705        return -1, nil
1706      end
1707      lookup.fhandle, pos = string.unpack("c32", data, pos)
1708      status, data = comm:GetAdditionalBytes( data, pos, 64 )
1709      if (not(status)) then
1710        stdnse.debug4("NFS.LookUpDecode: Failed to call GetAdditionalBytes")
1711        return -1, nil
1712      end
1713      pos, lookup.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
1714
1715    else
1716      stdnse.debug1("NFS.LookUpDecode: NFS unsupported version %d", comm.version)
1717      return -1, nil
1718    end
1719
1720    return pos, lookup
1721  end,
1722
1723  LookUp = function(self, comm, dir_handle, file)
1724    local status, packet
1725    local pos, data = 1, ""
1726    local header, response = {}, {}
1727
1728    if (not(dir_handle)) then
1729      return false, "LookUp: No dirhandle received"
1730    end
1731
1732    data = Util.marshall_opaque(dir_handle) .. Util.marshall_vopaque(file)
1733    packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].LOOKUP,
1734      {type=Portmap.AuthType.UNIX}, data)
1735    if(not(comm:SendPacket(packet))) then
1736      return false, "LookUp: Failed to send data"
1737    end
1738
1739    status, data = comm:ReceivePacket()
1740    if ( not(status) ) then
1741      return false, "LookUp: Failed to read data from socket"
1742    end
1743
1744    pos, header = comm:DecodeHeader(data, pos)
1745    if not header then
1746      return false, "LookUp: Failed to decode header"
1747    end
1748    pos, response = self:LookUpDecode(comm, data, pos)
1749    if (not(response)) then
1750      return false, "LookUp: Failed to decode the LOOKUP section"
1751    end
1752
1753    return true, response
1754  end,
1755
1756  ReadDirPlusDecode = function(self, comm, data, pos)
1757    local response, status, value_follows, _ = {}
1758
1759    status, data = comm:GetAdditionalBytes(data, pos, 4)
1760    if not status then
1761      stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1762      return -1, nil
1763    end
1764
1765    pos, status = Util.unmarshall_uint32(data, pos)
1766    if (not self:CheckStat("READDIRPLUS", comm.version, status)) then
1767      return -1, nil
1768    end
1769
1770    status, data = comm:GetAdditionalBytes(data, pos, 4)
1771    if not status then
1772      stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1773      return -1, nil
1774    end
1775
1776    value_follows, pos = string.unpack(">I4", data, pos)
1777    if value_follows == 0 then
1778      stdnse.debug4("NFS.ReadDirPlusDecode: Attributes follow failed")
1779      return -1, nil
1780    end
1781
1782    status, data = comm:GetAdditionalBytes( data, pos, 84 )
1783    if not status then
1784      stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1785      return -1, nil
1786    end
1787
1788    response.attributes = {}
1789    pos, response.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
1790
1791    status, data = comm:GetAdditionalBytes(data, pos, 8)
1792    if not status then
1793      stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1794      return -1, nil
1795    end
1796    _, pos = string.unpack(">I8", data, pos)
1797
1798    response.entries = {}
1799    while true do
1800      local entry, len = {}
1801      status, data = comm:GetAdditionalBytes(data, pos, 4)
1802      if not status then
1803        stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1804        return -1, nil
1805      end
1806
1807      value_follows, pos = string.unpack(">I4", data, pos)
1808
1809      if (value_follows == 0) then
1810        break
1811      end
1812      status, data = comm:GetAdditionalBytes(data, pos, 8)
1813      if not status then
1814        stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1815        return -1, nil
1816      end
1817      entry.fileid, pos = string.unpack(">I8", data, pos)
1818
1819      status, data = comm:GetAdditionalBytes(data, pos, 4)
1820
1821      if not status then
1822        stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1823        return -1, nil
1824      end
1825
1826      entry.length, pos = string.unpack(">I4", data, pos)
1827      status, data = comm:GetAdditionalBytes( data, pos, entry.length )
1828      if not status then
1829        stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1830        return -1, nil
1831      end
1832
1833      pos, entry.name = Util.unmarshall_vopaque(entry.length, data, pos)
1834      status, data = comm:GetAdditionalBytes(data, pos, 8)
1835      if not status then
1836        stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1837        return -1, nil
1838      end
1839      entry.cookie, pos = string.unpack(">I8", data, pos)
1840      status, data = comm:GetAdditionalBytes(data, pos, 4)
1841      if not status then
1842        stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1843        return -1, nil
1844      end
1845
1846      entry.attributes = {}
1847      value_follows, pos = string.unpack(">I4", data, pos)
1848      if (value_follows ~= 0) then
1849        status, data = comm:GetAdditionalBytes(data, pos, 84)
1850        if not status then
1851          stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1852          return -1, nil
1853        end
1854        pos, entry.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
1855      else
1856        stdnse.debug4("NFS.ReadDirPlusDecode: %s Attributes follow failed",
1857          entry.name)
1858      end
1859
1860      status, data = comm:GetAdditionalBytes(data, pos, 4)
1861      if not status then
1862        stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1863        return -1, nil
1864      end
1865
1866      entry.fhandle = ""
1867      value_follows, pos = string.unpack(">I4", data, pos)
1868      if (value_follows ~= 0) then
1869        status, data = comm:GetAdditionalBytes(data, pos, 4)
1870        if not status then
1871          stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1872          return -1, nil
1873        end
1874
1875        len = string.unpack(">I4", data, pos)
1876        status, data = comm:GetAdditionalBytes(data, pos, len + 4)
1877        if not status then
1878          stdnse.debug4("NFS.ReadDirPlusDecode: Failed to call GetAdditionalBytes")
1879          return -1, nil
1880        end
1881        entry.fhandle, pos = string.unpack( "c" .. len + 4, data, pos )
1882      else
1883        stdnse.debug4("NFS.ReadDirPlusDecode: %s handle follow failed",
1884          entry.name)
1885      end
1886      table.insert(response.entries, entry)
1887    end
1888
1889    return pos, response
1890  end,
1891
1892  ReadDirPlus = function(self, comm, file_handle)
1893    local status, packet
1894    local cookie, opaque_data, dircount, maxcount = 0, 0, 512, 8192
1895    local pos, data = 1, ""
1896    local header, response = {}, {}
1897
1898    if (comm.version < 3) then
1899      return false, string.format("NFS version: %d does not support ReadDirPlus",
1900        comm.version)
1901    end
1902
1903    if not file_handle then
1904      return false, "ReadDirPlus: No filehandle received"
1905    end
1906
1907    data = file_handle .. string.pack(">I8 I8 I4 I4", cookie, opaque_data, dircount, maxcount)
1908
1909    packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].READDIRPLUS,
1910      {type = Portmap.AuthType.UNIX }, data)
1911
1912    if (not(comm:SendPacket(packet))) then
1913      return false, "ReadDirPlus: Failed to send data"
1914    end
1915
1916    status, data = comm:ReceivePacket()
1917    if not status then
1918      return false, "ReadDirPlus: Failed to read data from socket"
1919    end
1920
1921    pos, header = comm:DecodeHeader( data, pos )
1922    if not header then
1923      return false, "ReadDirPlus: Failed to decode header"
1924    end
1925    pos, response = self:ReadDirPlusDecode( comm, data, pos )
1926    if not response then
1927      return false, "ReadDirPlus: Failed to decode the READDIR section"
1928    end
1929
1930    return true, response
1931  end,
1932
1933  FsStatDecode = function(self, comm, data, pos)
1934    local fsstat, status, value_follows = {}
1935
1936    status, data = comm:GetAdditionalBytes(data, pos, 4)
1937    if not status then
1938      stdnse.debug4("NFS.FsStatDecode: Failed to call GetAdditionalBytes")
1939      return -1, nil
1940    end
1941
1942    pos, status = Util.unmarshall_uint32(data, pos)
1943    if (not self:CheckStat("FSSTAT", comm.version, status)) then
1944      return -1, nil
1945    end
1946
1947    fsstat.attributes = {}
1948    pos, value_follows = Util.unmarshall_uint32(data, pos)
1949    if (value_follows ~= 0) then
1950      status, data = comm:GetAdditionalBytes(data, pos, 84)
1951      if not status then
1952        stdnse.debug4("NFS.FsStatDecode: Failed to call GetAdditionalBytes")
1953        return -1, nil
1954      end
1955      pos, fsstat.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
1956    else
1957      stdnse.debug4("NFS.FsStatDecode: Attributes follow failed")
1958    end
1959
1960    status, data = comm:GetAdditionalBytes( data, pos, 52)
1961    if not status then
1962      stdnse.debug4("NFS.FsStatDecode: Failed to call GetAdditionalBytes")
1963      return -1, nil
1964    end
1965
1966    pos, fsstat.tbytes, fsstat.fbytes, fsstat.abytes, fsstat.tfiles,
1967    fsstat.ffiles, fsstat.afiles = Util.unmarshall_nfssize3(data, pos, 6)
1968    pos, fsstat.invarsec = Util.unmarshall_uint32(data, pos)
1969
1970    return pos, fsstat
1971  end,
1972
1973  FsStat = function(self, comm, file_handle)
1974    local status, packet
1975    local pos, data = 1, ""
1976    local header, response = {}, {}
1977
1978    if (comm.version < 3) then
1979      return false, string.format("NFS version: %d does not support FSSTAT",
1980        comm.version)
1981    end
1982
1983    if not file_handle then
1984      return false, "FsStat: No filehandle received"
1985    end
1986
1987    packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].FSSTAT,
1988      {type = Portmap.AuthType.UNIX}, file_handle)
1989
1990    if (not(comm:SendPacket(packet))) then
1991      return false, "FsStat: Failed to send data"
1992    end
1993
1994    status, data = comm:ReceivePacket()
1995    if not status then
1996      return false, "FsStat: Failed to read data from socket"
1997    end
1998
1999    pos, header = comm:DecodeHeader(data, pos)
2000    if not header then
2001      return false, "FsStat: Failed to decode header"
2002    end
2003
2004    pos, response = self:FsStatDecode(comm, data, pos)
2005    if not response then
2006      return false, "FsStat: Failed to decode the FSSTAT section"
2007    end
2008    return true, response
2009  end,
2010
2011  FsInfoDecode = function(self, comm, data, pos)
2012    local fsinfo, status, value_follows = {}
2013
2014    status, data = comm:GetAdditionalBytes(data, pos, 4)
2015    if not status then
2016      stdnse.debug4("NFS.FsInfoDecode: Failed to call GetAdditionalBytes")
2017      return -1, nil
2018    end
2019
2020    pos, status = Util.unmarshall_uint32(data, pos)
2021    if (not self:CheckStat("FSINFO", comm.version, status)) then
2022      return -1, nil
2023    end
2024
2025    fsinfo.attributes = {}
2026    pos, value_follows = Util.unmarshall_uint32(data, pos)
2027    if (value_follows ~= 0) then
2028      status, data = comm:GetAdditionalBytes(data, pos, 84)
2029      if not status then
2030        stdnse.debug4("NFS.FsInfoDecode: Failed to call GetAdditionalBytes")
2031        return -1, nil
2032      end
2033      pos, fsinfo.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
2034    else
2035      stdnse.debug4("NFS.FsInfoDecode: Attributes follow failed")
2036    end
2037
2038    status, data = comm:GetAdditionalBytes(data, pos, 48)
2039    if not status then
2040      stdnse.debug4("NFS.FsStatDecode: Failed to call GetAdditionalBytes")
2041      return -1, nil
2042    end
2043
2044    pos, fsinfo.rtmax, fsinfo.rtpref, fsinfo.rtmult,
2045    fsinfo.wtmax, fsinfo.wtpref, fsinfo.wtmult,
2046    fsinfo.dtpref = Util.unmarshall_uint32(data, pos, 7)
2047    pos, fsinfo.maxfilesize = Util.unmarshall_nfssize3(data, pos)
2048    pos, fsinfo.time_delta = Util.unmarshall_nfstime(data, pos)
2049    pos, fsinfo.properties = Util.unmarshall_uint32(data, pos)
2050
2051    return pos, fsinfo
2052  end,
2053
2054  FsInfo = function(self, comm, file_handle)
2055    local status, packet
2056    local pos, data = 1, ""
2057    local header, response = {}
2058
2059    if (comm.version < 3) then
2060      return false, string.format("NFS version: %d does not support FSINFO",
2061        comm.version)
2062    end
2063
2064    if not file_handle then
2065      return false, "FsInfo: No filehandle received"
2066    end
2067
2068    data = Util.marshall_opaque(file_handle)
2069    packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].FSINFO,
2070      {type = Portmap.AuthType.UNIX}, data)
2071
2072    if (not(comm:SendPacket(packet))) then
2073      return false, "FsInfo: Failed to send data"
2074    end
2075
2076    status, data = comm:ReceivePacket()
2077    if not status then
2078      return false, "FsInfo: Failed to read data from socket"
2079    end
2080
2081    pos, header = comm:DecodeHeader(data, pos)
2082    if not header then
2083      return false, "FsInfo: Failed to decode header"
2084    end
2085
2086    pos, response = self:FsInfoDecode(comm, data, pos)
2087    if not response then
2088      return false, "FsInfo: Failed to decode the FSINFO section"
2089    end
2090    return true, response
2091  end,
2092
2093  PathConfDecode = function(self, comm, data, pos)
2094    local pconf, status, value_follows = {}
2095
2096    status, data = comm:GetAdditionalBytes(data, pos, 4)
2097    if not status then
2098      stdnse.debug4("NFS.PathConfDecode: Failed to call GetAdditionalBytes")
2099      return -1, nil
2100    end
2101
2102    pos, status = Util.unmarshall_uint32(data, pos)
2103    if (not self:CheckStat("PATHCONF", comm.version, status)) then
2104      return -1, nil
2105    end
2106
2107    pconf.attributes = {}
2108    pos, value_follows = Util.unmarshall_uint32(data, pos)
2109    if (value_follows ~= 0) then
2110      status, data = comm:GetAdditionalBytes(data, pos, 84)
2111      if not status then
2112        stdnse.debug4("NFS.PathConfDecode: Failed to call GetAdditionalBytes")
2113        return -1, nil
2114      end
2115      pos, pconf.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
2116    else
2117      stdnse.debug4("NFS.PathConfDecode: Attributes follow failed")
2118    end
2119
2120    status, data = comm:GetAdditionalBytes(data, pos, 24)
2121    if not status then
2122      stdnse.debug4("NFS.PathConfDecode: Failed to call GetAdditionalBytes")
2123      return -1, nil
2124    end
2125
2126    pos, pconf.linkmax, pconf.name_max, pconf.no_trunc,
2127    pconf.chown_restricted, pconf.case_insensitive,
2128    pconf.case_preserving = Util.unmarshall_uint32(data, pos, 6)
2129
2130    return pos, pconf
2131  end,
2132
2133  PathConf = function(self, comm, file_handle)
2134    local status, packet
2135    local pos, data = 1, ""
2136    local header, response = {}
2137
2138    if (comm.version < 3) then
2139      return false, string.format("NFS version: %d does not support PATHCONF",
2140        comm.version)
2141    end
2142
2143    if not file_handle then
2144      return false, "PathConf: No filehandle received"
2145    end
2146
2147    data = Util.marshall_opaque(file_handle)
2148    packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].PATHCONF,
2149      {type = Portmap.AuthType.UNIX}, data)
2150
2151    if (not(comm:SendPacket(packet))) then
2152      return false, "PathConf: Failed to send data"
2153    end
2154
2155    status, data = comm:ReceivePacket()
2156    if not status then
2157      return false, "PathConf: Failed to read data from socket"
2158    end
2159
2160    pos, header = comm:DecodeHeader(data, pos)
2161    if not header then
2162      return false, "PathConf: Failed to decode header"
2163    end
2164
2165    pos, response = self:PathConfDecode(comm, data, pos)
2166    if not response then
2167      return false, "PathConf: Failed to decode the PATHCONF section"
2168    end
2169    return true, response
2170  end,
2171
2172  AccessDecode = function(self, comm, data, pos)
2173    local access, status, value_follows = {}
2174
2175    status, data = comm:GetAdditionalBytes(data, pos, 4)
2176    if not status then
2177      stdnse.debug4("NFS.AccessDecode: Failed to call GetAdditionalBytes")
2178      return -1, nil
2179    end
2180
2181    pos, status = Util.unmarshall_uint32(data, pos)
2182    if (not self:CheckStat("ACCESS", comm.version, status)) then
2183      return -1, nil
2184    end
2185
2186    access.attributes = {}
2187    pos, value_follows = Util.unmarshall_uint32(data, pos)
2188    if (value_follows ~= 0) then
2189      status, data = comm:GetAdditionalBytes(data, pos, 84)
2190      if not status then
2191        stdnse.debug4("NFS.AccessDecode: Failed to call GetAdditionalBytes")
2192        return -1, nil
2193      end
2194      pos, access.attributes = Util.unmarshall_nfsattr(data, pos, comm.version)
2195    else
2196      stdnse.debug4("NFS.AccessDecode: Attributes follow failed")
2197    end
2198
2199    status, data = comm:GetAdditionalBytes(data, pos, 4)
2200    if not status then
2201      stdnse.debug4("NFS.AccessDecode: Failed to call GetAdditionalBytes")
2202      return -1, nil
2203    end
2204
2205    pos, access.mask = Util.unmarshall_uint32(data, pos)
2206
2207    return pos, access
2208  end,
2209
2210  Access = function(self, comm, file_handle, access)
2211    local status, packet
2212    local pos, data = 1, ""
2213    local header, response = {}, {}
2214
2215    if (comm.version < 3) then
2216      return false, string.format("NFS version: %d does not support ACCESS",
2217        comm.version)
2218    end
2219
2220    if not file_handle then
2221      return false, "Access: No filehandle received"
2222    end
2223
2224    data = Util.marshall_opaque(file_handle) .. Util.marshall_uint32(access)
2225    packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].ACCESS,
2226      {type = Portmap.AuthType.UNIX}, data)
2227
2228    if (not(comm:SendPacket(packet))) then
2229      return false, "Access: Failed to send data"
2230    end
2231
2232    status, data = comm:ReceivePacket()
2233    if not status then
2234      return false, "Access: Failed to read data from socket"
2235    end
2236
2237    pos, header = comm:DecodeHeader(data, pos)
2238    if not header then
2239      return false, "Access: Failed to decode header"
2240    end
2241
2242    pos, response = self:AccessDecode(comm, data, pos)
2243    if not response then
2244      return false, "Access: Failed to decode the FSSTAT section"
2245    end
2246
2247    return true, response
2248  end,
2249
2250  --- Gets filesystem stats (Total Blocks, Free Blocks and Available block) on a remote NFS share
2251  --
2252  -- @param comm object handles rpc program information and
2253  --  low-level packet manipulation
2254  -- @param file_handle string containing the filehandle to query
2255  -- @return status true on success, false on failure
2256  -- @return statfs table with the fields <code>transfer_size</code>, <code>block_size</code>,
2257  --  <code>total_blocks</code>, <code>free_blocks</code> and <code>available_blocks</code>
2258  -- @return errormsg if status is false
2259  StatFs = function( self, comm, file_handle )
2260
2261    local status, packet
2262    local pos, data, _ = 1, "", ""
2263    local header, statfs = {}, {}
2264
2265    if ( comm.version > 2 ) then
2266      return false, ("StatFs: Version %d not supported"):format(comm.version)
2267    end
2268
2269    if ( not(file_handle) or file_handle:len() ~= 32 ) then
2270      return false, "StatFs: Incorrect filehandle received"
2271    end
2272
2273    data = Util.marshall_opaque(file_handle)
2274    packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].STATFS, { type=Portmap.AuthType.UNIX }, data )
2275    if (not(comm:SendPacket( packet ))) then
2276      return false, "StatFS: Failed to send data"
2277    end
2278
2279    status, data = comm:ReceivePacket( )
2280    if ( not(status) ) then
2281      return false, "StatFs: Failed to read data from socket"
2282    end
2283
2284    pos, header = comm:DecodeHeader( data, pos )
2285
2286    if not header then
2287      return false, "StatFs: Failed to decode header"
2288    end
2289
2290    pos, statfs = self:StatFsDecode( comm, data, pos )
2291
2292    if not statfs then
2293      return false, "StatFs: Failed to decode statfs structure"
2294    end
2295    return true, statfs
2296  end,
2297
2298  --- Attempts to decode the attributes section of the reply
2299  --
2300  -- @param comm object handles rpc program information and
2301  --  low-level packet manipulation
2302  -- @param data string containing the full statfs reply
2303  -- @param pos number pointing to the statfs section of the reply
2304  -- @return pos number containing the offset after decoding
2305  -- @return statfs table with the following fields: <code>type</code>, <code>mode</code>,
2306  --  <code>nlink</code>, <code>uid</code>, <code>gid</code>, <code>size</code>,
2307  --  <code>blocksize</code>, <code>rdev</code>, <code>blocks</code>, <code>fsid</code>,
2308  --  <code>fileid</code>, <code>atime</code>, <code>mtime</code> and <code>ctime</code>
2309  --
2310  GetAttrDecode = function( self, comm, data, pos )
2311    local status
2312
2313    status, data = comm:GetAdditionalBytes( data, pos, 4 )
2314    if (not(status)) then
2315      stdnse.debug4("GetAttrDecode: Failed to call GetAdditionalBytes")
2316      return -1, nil
2317    end
2318
2319    pos, status = Util.unmarshall_uint32(data, pos)
2320    if (not self:CheckStat("GETATTR", comm.version, status)) then
2321      return -1, nil
2322    end
2323
2324    if ( comm.version < 3 ) then
2325      status, data = comm:GetAdditionalBytes( data, pos, 64 )
2326    elseif (comm.version == 3) then
2327      status, data = comm:GetAdditionalBytes( data, pos, 84 )
2328    else
2329      stdnse.debug4("GetAttrDecode: Unsupported version")
2330      return -1, nil
2331    end
2332    if ( not(status) ) then
2333      stdnse.debug4("GetAttrDecode: Failed to call GetAdditionalBytes")
2334      return -1, nil
2335    end
2336    return Util.unmarshall_nfsattr(data, pos, comm.version)
2337  end,
2338
2339  --- Gets mount attributes (uid, gid, mode, etc ..) from a remote NFS share
2340  --
2341  -- @param comm object handles rpc program information and
2342  --  low-level packet manipulation
2343  -- @param file_handle string containing the filehandle to query
2344  -- @return status true on success, false on failure
2345  -- @return attribs table with the fields <code>type</code>, <code>mode</code>,
2346  --  <code>nlink</code>, <code>uid</code>, <code>gid</code>, <code>size</code>,
2347  --  <code>blocksize</code>, <code>rdev</code>, <code>blocks</code>, <code>fsid</code>,
2348  --  <code>fileid</code>, <code>atime</code>, <code>mtime</code> and <code>ctime</code>
2349  -- @return errormsg if status is false
2350  GetAttr = function( self, comm, file_handle )
2351    local data, packet, status, attribs, pos, header
2352
2353    data = Util.marshall_opaque(file_handle)
2354    packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].GETATTR, { type=Portmap.AuthType.UNIX }, data )
2355    if(not(comm:SendPacket(packet))) then
2356      return false, "GetAttr: Failed to send data"
2357    end
2358
2359    status, data = comm:ReceivePacket()
2360    if ( not(status) ) then
2361      return false, "GetAttr: Failed to read data from socket"
2362    end
2363
2364    pos, header = comm:DecodeHeader( data, 1 )
2365    if not header then
2366      return false, "GetAttr: Failed to decode header"
2367    end
2368
2369    pos, attribs = self:GetAttrDecode(comm, data, pos )
2370    if not attribs then
2371      return false, "GetAttr: Failed to decode attrib structure"
2372    end
2373
2374    return true, attribs
2375  end,
2376
2377  --- Attempts to decode the StatFS section of the reply
2378  --
2379  -- @param comm object handles rpc program information and
2380  --  low-level packet manipulation
2381  -- @param data string containing the full statfs reply
2382  -- @param pos number pointing to the statfs section of the reply
2383  -- @return pos number containing the offset after decoding
2384  -- @return statfs table with the following fields: <code>transfer_size</code>, <code>block_size</code>,
2385  --  <code>total_blocks</code>, <code>free_blocks</code> and <code>available_blocks</code>
2386  StatFsDecode = function( self, comm, data, pos )
2387    local status
2388    local statfs = {}
2389
2390    status, data = comm:GetAdditionalBytes( data, pos, 4 )
2391    if (not(status)) then
2392      stdnse.debug4("StatFsDecode: Failed to call GetAdditionalBytes")
2393      return -1, nil
2394    end
2395
2396    pos, status = Util.unmarshall_uint32(data, pos)
2397    if (not self:CheckStat("STATFS", comm.version, status)) then
2398      return -1, nil
2399    end
2400
2401    status, data = comm:GetAdditionalBytes( data, pos, 20 )
2402    if (not(status)) then
2403      stdnse.debug4("StatFsDecode: Failed to call GetAdditionalBytes")
2404      return -1, nil
2405    end
2406    pos, statfs.transfer_size, statfs.block_size,
2407    statfs.total_blocks, statfs.free_blocks,
2408    statfs.available_blocks = Util.unmarshall_uint32(data, pos, 5)
2409    return pos, statfs
2410  end,
2411}
2412
2413Helper = {
2414
2415  --- Lists the NFS exports on the remote host
2416  -- This function abstracts the RPC communication with the portmapper from the user
2417  --
2418  -- @param host table
2419  -- @param port table
2420  -- @return status true on success, false on failure
2421  -- @return result table of string entries or error message on failure
2422  ShowMounts = function( host, port )
2423
2424    local status, result, mounts
2425    local mountd, mnt_comm
2426    local mnt = Mount:new()
2427    local portmap = Portmap:new()
2428
2429    status, mountd = Helper.GetProgramInfo( host, port, "mountd")
2430    if ( not(status) ) then
2431      stdnse.debug4("rpc.Helper.ShowMounts: GetProgramInfo failed")
2432      return status, "rpc.Helper.ShowMounts: GetProgramInfo failed"
2433    end
2434
2435    mnt_comm = Comm:new('mountd', mountd.version)
2436    status, result = mnt_comm:Connect(host, mountd.port)
2437    if ( not(status) ) then
2438      stdnse.debug4("rpc.Helper.ShowMounts: %s", result)
2439      return false, result
2440    end
2441    status, mounts = mnt:Export(mnt_comm)
2442    mnt_comm:Disconnect()
2443    if ( not(status) ) then
2444      stdnse.debug4("rpc.Helper.ShowMounts: %s", mounts)
2445    end
2446    return status, mounts
2447  end,
2448
2449  --- Mounts a remote NFS export and returns the file handle
2450  --
2451  -- This is a high level function to be used by NSE scripts
2452  -- To close the mounted NFS export use UnmountPath() function
2453  --
2454  -- @param host table
2455  -- @param port table
2456  -- @param path string containing the path to mount
2457  -- @return on success a Comm object which can be
2458  --         used later as a parameter by low level Mount
2459  --         functions, on failure returns nil.
2460  -- @return on success the filehandle of the NFS export as
2461  --         a string, on failure returns the error message.
2462  MountPath = function(host, port, path)
2463    local fhandle, status, err
2464    local mountd, mnt_comm
2465    local mnt = Mount:new()
2466
2467    status, mountd = Helper.GetProgramInfo( host, port, "mountd")
2468    if not status then
2469      stdnse.debug4("rpc.Helper.MountPath: GetProgramInfo failed")
2470      return nil, "rpc.Helper.MountPath: GetProgramInfo failed"
2471    end
2472
2473    mnt_comm = Comm:new("mountd", mountd.version)
2474
2475    status, err = mnt_comm:Connect(host, mountd.port)
2476    if not status then
2477      stdnse.debug4("rpc.Helper.MountPath: %s", err)
2478      return nil, err
2479    end
2480
2481    status, fhandle = mnt:Mount(mnt_comm, path)
2482    if not status then
2483      mnt_comm:Disconnect()
2484      stdnse.debug4("rpc.Helper.MountPath: %s", fhandle)
2485      return nil, fhandle
2486    end
2487
2488    return mnt_comm, fhandle
2489  end,
2490
2491  --- Unmounts a remote mounted NFS export
2492  --
2493  -- This is a high level function to be used by NSE scripts
2494  -- This function must be used to unmount a NFS point
2495  -- mounted by MountPath()
2496  --
2497  -- @param mnt_comm object returned from a previous call to
2498  --        MountPath()
2499  -- @param path string containing the path to unmount
2500  -- @return true on success or nil on failure
2501  -- @return error message on failure
2502  UnmountPath = function(mnt_comm, path)
2503    local mnt = Mount:new()
2504    local status, ret = mnt:Unmount(mnt_comm, path)
2505    mnt_comm:Disconnect()
2506    if not status then
2507      stdnse.debug4("rpc.Helper.UnmountPath: %s", ret)
2508      return nil, ret
2509    end
2510
2511    return status, nil
2512  end,
2513
2514  --- Connects to a remote NFS server
2515  --
2516  -- This is a high level function to open NFS connections
2517  -- To close the NFS connection use NfsClose() function
2518  --
2519  -- @param host table
2520  -- @param port table
2521  -- @return on success a Comm object which can be
2522  --         used later as a parameter by low level NFS
2523  --         functions, on failure returns nil.
2524  -- @return error message on failure.
2525  NfsOpen = function(host, port)
2526    local nfs_comm, nfsd, status, err
2527
2528    status, nfsd = Helper.GetProgramInfo(host, port, "nfs")
2529    if not status then
2530      stdnse.debug4("rpc.Helper.NfsOpen: GetProgramInfo failed")
2531      return nil, "rpc.Helper.NfsOpen: GetProgramInfo failed"
2532    end
2533
2534    nfs_comm = Comm:new('nfs', nfsd.version)
2535    status, err = nfs_comm:Connect(host, nfsd.port)
2536    if not status then
2537      stdnse.debug4("rpc.Helper.NfsProc: %s", err)
2538      return nil, err
2539    end
2540
2541    return nfs_comm, nil
2542  end,
2543
2544  --- Closes the NFS connection
2545  --
2546  -- This is a high level function to close NFS connections
2547  -- This function must be used to close the NFS connection
2548  --  opened by the NfsOpen() call
2549  --
2550  -- @param nfs_comm object returned by NfsOpen()
2551  -- @return true on success or nil on failure
2552  -- @return error message on failure
2553  NfsClose = function(nfs_comm)
2554    local status, ret = nfs_comm:Disconnect()
2555    if not status then
2556      stdnse.debug4("rpc.Helper.NfsClose: %s", ret)
2557      return nil, ret
2558    end
2559
2560    return status, nil
2561  end,
2562
2563  --- Retrieves NFS storage statistics
2564  --
2565  -- @param host table
2566  -- @param port table
2567  -- @param path string containing the nfs export path
2568  -- @return status true on success, false on failure
2569  -- @return statfs table with the fields <code>transfer_size</code>, <code>block_size</code>,
2570  --  <code>total_blocks</code>, <code>free_blocks</code> and <code>available_blocks</code>
2571  ExportStats = function( host, port, path )
2572    local fhandle
2573    local stats, status, result
2574    local mnt_comm, nfs_comm
2575    local mountd, nfsd = {}, {}
2576    local mnt, nfs = Mount:new(), NFS:new()
2577
2578    status, mountd = Helper.GetProgramInfo( host, port, "mountd", 2)
2579    if ( not(status) ) then
2580      stdnse.debug4("rpc.Helper.ExportStats: GetProgramInfo failed")
2581      return status, "rpc.Helper.ExportStats: GetProgramInfo failed"
2582    end
2583
2584    status, nfsd = Helper.GetProgramInfo( host, port, "nfs", 2)
2585    if ( not(status) ) then
2586      stdnse.debug4("rpc.Helper.ExportStats: GetProgramInfo failed")
2587      return status, "rpc.Helper.ExportStats: GetProgramInfo failed"
2588    end
2589    mnt_comm = Comm:new('mountd', mountd.version)
2590    nfs_comm = Comm:new('nfs', nfsd.version)
2591
2592    -- TODO: recheck the version mismatch when adding NFSv4
2593    if (nfs_comm.version <= 2  and mnt_comm.version > 2) then
2594      stdnse.debug4("rpc.Helper.ExportStats: versions mismatch, nfs v%d - mount v%d",
2595        nfs_comm.version, mnt_comm.version)
2596      return false, string.format("versions mismatch, nfs v%d - mount v%d",
2597        nfs_comm.version, mnt_comm.version)
2598    end
2599    status, result = mnt_comm:Connect(host, mountd.port)
2600    if ( not(status) ) then
2601      stdnse.debug4("rpc.Helper.ExportStats: %s", result)
2602      return status, result
2603    end
2604    status, result = nfs_comm:Connect(host, nfsd.port)
2605    if ( not(status) ) then
2606      mnt_comm:Disconnect()
2607      stdnse.debug4("rpc.Helper.ExportStats: %s", result)
2608      return status, result
2609    end
2610
2611    status, fhandle = mnt:Mount(mnt_comm, path)
2612    if ( not(status) ) then
2613      mnt_comm:Disconnect()
2614      nfs_comm:Disconnect()
2615      stdnse.debug4("rpc.Helper.ExportStats: %s", fhandle)
2616      return status, fhandle
2617    end
2618    status, stats = nfs:StatFs(nfs_comm, fhandle)
2619    if ( not(status) ) then
2620      mnt_comm:Disconnect()
2621      nfs_comm:Disconnect()
2622      stdnse.debug4("rpc.Helper.ExportStats: %s", stats)
2623      return status, stats
2624    end
2625
2626    status, fhandle = mnt:Unmount(mnt_comm, path)
2627    mnt_comm:Disconnect()
2628    nfs_comm:Disconnect()
2629    if ( not(status) ) then
2630      stdnse.debug4("rpc.Helper.ExportStats: %s", fhandle)
2631      return status, fhandle
2632    end
2633    return true, stats
2634  end,
2635
2636  --- Retrieves a list of files from the NFS export
2637  --
2638  -- @param host table
2639  -- @param port table
2640  -- @param path string containing the nfs export path
2641  -- @return status true on success, false on failure
2642  -- @return table of file table entries as described in <code>decodeReadDir</code>
2643  Dir = function( host, port, path )
2644    local fhandle
2645    local dirs, status, result
2646    local mountd, nfsd = {}, {}
2647    local mnt_comm, nfs_comm
2648    local mnt, nfs = Mount:new(), NFS:new()
2649
2650    status, mountd = Helper.GetProgramInfo( host, port, "mountd")
2651    if ( not(status) ) then
2652      stdnse.debug4("rpc.Helper.Dir: GetProgramInfo failed")
2653      return status, "rpc.Helper.Dir: GetProgramInfo failed"
2654    end
2655
2656    status, nfsd = Helper.GetProgramInfo( host, port, "nfs")
2657    if ( not(status) ) then
2658      stdnse.debug4("rpc.Helper.Dir: GetProgramInfo failed")
2659      return status, "rpc.Helper.Dir: GetProgramInfo failed"
2660    end
2661
2662    mnt_comm = Comm:new('mountd', mountd.version)
2663    nfs_comm = Comm:new('nfs', nfsd.version)
2664
2665    -- TODO: recheck the version mismatch when adding NFSv4
2666    if (nfs_comm.version <= 2  and mnt_comm.version > 2) then
2667      stdnse.debug4("rpc.Helper.Dir: versions mismatch, nfs v%d - mount v%d",
2668        nfs_comm.version, mnt_comm.version)
2669      return false, string.format("versions mismatch, nfs v%d - mount v%d",
2670        nfs_comm.version, mnt_comm.version)
2671    end
2672    status, result = mnt_comm:Connect(host, mountd.port)
2673    if ( not(status) ) then
2674      stdnse.debug4("rpc.Helper.Dir: %s", result)
2675      return status, result
2676    end
2677
2678    status, result = nfs_comm:Connect(host, nfsd.port)
2679    if ( not(status) ) then
2680      mnt_comm:Disconnect()
2681      stdnse.debug4("rpc.Helper.Dir: %s", result)
2682      return status, result
2683    end
2684
2685    status, fhandle = mnt:Mount(mnt_comm, path )
2686    if ( not(status) ) then
2687      mnt_comm:Disconnect()
2688      nfs_comm:Disconnect()
2689      stdnse.debug4("rpc.Helper.Dir: %s", fhandle)
2690      return status, fhandle
2691    end
2692
2693    status, dirs = nfs:ReadDir(nfs_comm, fhandle )
2694    if ( not(status) ) then
2695      mnt_comm:Disconnect()
2696      nfs_comm:Disconnect()
2697      stdnse.debug4("rpc.Helper.Dir: %s", dirs)
2698      return status, dirs
2699    end
2700
2701    status, fhandle = mnt:Unmount(mnt_comm, path)
2702    mnt_comm:Disconnect()
2703    nfs_comm:Disconnect()
2704    if ( not(status) ) then
2705      stdnse.debug4("rpc.Helper.Dir: %s", fhandle)
2706      return status, fhandle
2707    end
2708    return true, dirs
2709  end,
2710
2711  --- Retrieves NFS Attributes
2712  --
2713  -- @param host table
2714  -- @param port table
2715  -- @param path string containing the nfs export path
2716  -- @return status true on success, false on failure
2717  -- @return statfs table with the fields <code>transfer_size</code>, <code>block_size</code>,
2718  --  <code>total_blocks</code>, <code>free_blocks</code> and <code>available_blocks</code>
2719  GetAttributes = function( host, port, path )
2720    local fhandle
2721    local attribs, status, result
2722    local mnt_comm, nfs_comm
2723    local mountd, nfsd = {}, {}
2724    local mnt, nfs = Mount:new(), NFS:new()
2725
2726    status, mountd = Helper.GetProgramInfo( host, port, "mountd")
2727    if ( not(status) ) then
2728      stdnse.debug4("rpc.Helper.GetAttributes: GetProgramInfo failed")
2729      return status, "rpc.Helper.GetAttributes: GetProgramInfo failed"
2730    end
2731
2732    status, nfsd = Helper.GetProgramInfo( host, port, "nfs")
2733    if ( not(status) ) then
2734      stdnse.debug4("rpc.Helper.GetAttributes: GetProgramInfo failed")
2735      return status, "rpc.Helper.GetAttributes: GetProgramInfo failed"
2736    end
2737
2738    mnt_comm, result = Comm:new('mountd', mountd.version)
2739    nfs_comm, result = Comm:new('nfs', nfsd.version)
2740
2741    -- TODO: recheck the version mismatch when adding NFSv4
2742    if (nfs_comm.version <= 2  and mnt_comm.version > 2) then
2743      stdnse.debug4("rpc.Helper.GetAttributes: versions mismatch, nfs v%d - mount v%d",
2744        nfs_comm.version, mnt_comm.version)
2745      return false, string.format("versions mismatch, nfs v%d - mount v%d",
2746        nfs_comm.version, mnt_comm.version)
2747    end
2748
2749    status, result = mnt_comm:Connect(host, mountd.port)
2750    if ( not(status) ) then
2751      stdnse.debug4("rpc.Helper.GetAttributes: %s", result)
2752      return status, result
2753    end
2754
2755    status, result = nfs_comm:Connect(host, nfsd.port)
2756    if ( not(status) ) then
2757      mnt_comm:Disconnect()
2758      stdnse.debug4("rpc.Helper.GetAttributes: %s", result)
2759      return status, result
2760    end
2761
2762    status, fhandle = mnt:Mount(mnt_comm, path)
2763    if ( not(status) ) then
2764      mnt_comm:Disconnect()
2765      nfs_comm:Disconnect()
2766      stdnse.debug4("rpc.Helper.GetAttributes: %s", fhandle)
2767      return status, fhandle
2768    end
2769
2770    status, attribs = nfs:GetAttr(nfs_comm, fhandle)
2771    if ( not(status) ) then
2772      mnt_comm:Disconnect()
2773      nfs_comm:Disconnect()
2774      stdnse.debug4("rpc.Helper.GetAttributes: %s", attribs)
2775      return status, attribs
2776    end
2777
2778    status, fhandle = mnt:Unmount(mnt_comm, path)
2779
2780    mnt_comm:Disconnect()
2781    nfs_comm:Disconnect()
2782    if ( not(status) ) then
2783      stdnse.debug4("rpc.Helper.GetAttributes: %s", fhandle)
2784      return status, fhandle
2785    end
2786
2787    return true, attribs
2788  end,
2789
2790  --- Queries the portmapper for a list of programs
2791  --
2792  -- @param host table
2793  -- @param port table
2794  -- @return status true on success, false on failure
2795  -- @return table containing the portmapper information as returned by
2796  -- <code>Portmap.Dump</code>
2797  RpcInfo = function( host, port )
2798    local status, result
2799    local portmap = Portmap:new()
2800
2801    mutex "lock"
2802
2803    if nmap.registry[host.ip] == nil then
2804      nmap.registry[host.ip] = {}
2805    end
2806    if nmap.registry[host.ip]['portmapper'] == nil then
2807      nmap.registry[host.ip]['portmapper'] = {}
2808    elseif next(nmap.registry[host.ip]['portmapper']) ~= nil then
2809      mutex "done"
2810      return true, nmap.registry[host.ip]['portmapper']
2811    end
2812
2813    local pversion = 4
2814    while pversion >= 2 do
2815      local comm = Comm:new('rpcbind', pversion)
2816      status, result = comm:Connect(host, port)
2817      if (not(status)) then
2818        mutex "done"
2819        stdnse.debug4("rpc.Helper.RpcInfo: %s", result)
2820        return status, result
2821      end
2822
2823      status, result = portmap:Dump(comm)
2824      comm:Disconnect()
2825
2826      if status then
2827        break
2828      end
2829      stdnse.debug4("rpc.Helper.RpcInfo: %s", result)
2830      pversion = pversion - 1
2831    end
2832
2833    mutex "done"
2834    return status, result
2835  end,
2836
2837  --- Queries the portmapper for a port for the specified RPC program
2838  --
2839  -- @param host table
2840  -- @param port table
2841  -- @param program string containing the RPC program name
2842  -- @param protocol string containing either "tcp" or "udp"
2843  -- @return status true on success, false on failure
2844  -- @return table containing the portmapper information as returned by
2845  -- <code>Portmap.Dump</code>
2846  GetPortForProgram = function( host, port, program, protocol )
2847    local status, result
2848    local portmap = Portmap:new()
2849    local comm = Comm:new('rpcbind', 2)
2850
2851    status, result = comm:Connect(host, port)
2852    if (not(status)) then
2853      stdnse.debug4("rpc.Helper.GetPortForProgram: %s", result)
2854      return status, result
2855    end
2856
2857    status, result = portmap:GetPort(comm, program, protocol, 1 )
2858    comm:Disconnect()
2859    if (not(status)) then
2860      stdnse.debug4("rpc.Helper.GetPortForProgram: %s", result)
2861    end
2862
2863    return status, result
2864  end,
2865
2866  --- Get RPC program information
2867  --
2868  -- @param host table
2869  -- @param port table
2870  -- @param program string containing the RPC program name
2871  -- @param max_version (optional) number containing highest version to retrieve
2872  -- @return status true on success, false on failure
2873  -- @return info table containing <code>port</code>, <code>port.number</code>
2874  -- <code>port.protocol</code> and <code>version</code>
2875  GetProgramInfo = function( host, port, program, max_version )
2876    local status, portmap_table = Helper.RpcInfo(host, port)
2877    if ( not(status) ) then
2878      return status, portmap_table
2879    end
2880
2881    -- assume failure
2882    status = false
2883
2884    local tmp = portmap_table[Util.ProgNameToNumber(program)]
2885    if not tmp then
2886      return false, "Program not supported by target"
2887    end
2888
2889    local info = {}
2890    local proginfo
2891    local ipv6 = nmap.address_family() == "inet6"
2892    ::AF_FALLBACK::
2893    for _, p in ipairs( RPC_PROTOCOLS ) do
2894      if ipv6 then
2895        proginfo = tmp[p .. "6"]
2896      else
2897        proginfo = tmp[p]
2898      end
2899      if proginfo then
2900        info.port = {}
2901        info.port.number = proginfo.port
2902        info.port.protocol = p
2903        break
2904      end
2905    end
2906    if ipv6 and not proginfo then
2907      -- Fall back to trying IPv4
2908      ipv6 = false
2909      goto AF_FALLBACK
2910    end
2911
2912    if not proginfo then
2913      return false, "No transport protocol supported"
2914    end
2915
2916    -- choose the highest version available
2917    if ( not(RPC_version[program]) ) then
2918      info.version = proginfo.version[#proginfo.version]
2919      status = true
2920    else
2921      for i=#proginfo.version, 1, -1 do
2922        if ( RPC_version[program].max >= proginfo.version[i] ) then
2923          if ( not(max_version) ) then
2924            info.version = proginfo.version[i]
2925            status = true
2926            break
2927          else
2928            if ( max_version >= proginfo.version[i] ) then
2929              info.version = proginfo.version[i]
2930              status = true
2931              break
2932            end
2933          end
2934        end
2935      end
2936    end
2937
2938    return status, info
2939  end,
2940}
2941
2942--- Static class containing mostly conversion functions
2943--  and File type codes and permissions emulation
2944Util =
2945{
2946  -- Symbolic letters for file permission codes
2947  Fperm =
2948  {
2949    owner =
2950    {
2951      -- S_IRUSR
2952      [0x00000100] = { idx = 1, char = "r" },
2953      -- S_IWUSR
2954      [0x00000080] = { idx = 2, char = "w" },
2955      -- S_IXUSR
2956      [0x00000040] = { idx = 3, char = "x" },
2957      -- S_ISUID
2958      [0x00000800] = { idx = 3, char = "S" },
2959    },
2960    group =
2961    {
2962      -- S_IRGRP
2963      [0x00000020] = { idx = 4, char = "r" },
2964      -- S_IWGRP
2965      [0x00000010] = { idx = 5, char = "w" },
2966      -- S_IXGRP
2967      [0x00000008] = { idx = 6, char = "x" },
2968      -- S_ISGID
2969      [0x00000400] = { idx = 6, char = "S" },
2970    },
2971    other =
2972    {
2973      -- S_IROTH
2974      [0x00000004] = { idx = 7, char = "r" },
2975      -- S_IWOTH
2976      [0x00000002] = { idx = 8, char = "w" },
2977      -- S_IXOTH
2978      [0x00000001] = { idx = 9, char = "x" },
2979      -- S_ISVTX
2980      [0x00000200] = { idx = 9, char = "t" },
2981    },
2982  },
2983
2984  -- bit mask used to extract the file type code from a mode
2985  -- S_IFMT = 00170000 (octal)
2986  S_IFMT = 0xF000,
2987
2988  FileType =
2989  {
2990    -- S_IFSOCK
2991    [0x0000C000] = { char = "s", str = "socket" },
2992    -- S_IFLNK
2993    [0x0000A000] = { char = "l", str = "symbolic link" },
2994    -- S_IFREG
2995    [0x00008000] = { char = "-", str = "file" },
2996    -- S_IFBLK
2997    [0x00006000] = { char = "b", str = "block device" },
2998    -- S_IFDIR
2999    [0x00004000] = { char = "d", str = "directory" },
3000    -- S_IFCHR
3001    [0x00002000] = { char = "c", str = "char device" },
3002    -- S_IFIFO
3003    [0x00001000] = { char = "p", str = "named pipe" },
3004  },
3005
3006  --- Converts a numeric ACL mode to a file type char
3007  --
3008  -- @param mode number containing the ACL mode
3009  -- @return char containing the file type
3010  FtypeToChar = function(mode)
3011    local code = mode & Util.S_IFMT
3012    if Util.FileType[code] then
3013      return Util.FileType[code].char
3014    else
3015      stdnse.debug1("FtypeToChar: Unknown file type, mode: %o", mode)
3016      return ""
3017    end
3018  end,
3019
3020  --- Converts a numeric ACL mode to a file type string
3021  --
3022  -- @param mode number containing the ACL mode
3023  -- @return string containing the file type name
3024  FtypeToString = function(mode)
3025    local code = mode & Util.S_IFMT
3026    if Util.FileType[code] then
3027      return Util.FileType[code].str
3028    else
3029      stdnse.debug1("FtypeToString: Unknown file type, mode: %o", mode)
3030      return ""
3031    end
3032  end,
3033
3034  --- Converts a numeric ACL mode to a string in an octal
3035  -- number format.
3036  --
3037  -- @param mode number containing the ACL mode
3038  -- @return string containing the octal ACL mode
3039  FmodeToOctalString = function(mode)
3040    local code = mode & Util.S_IFMT
3041    if Util.FileType[code] then
3042      code = mode ~ code
3043    else
3044      code = mode
3045      stdnse.debug1("FmodeToOctalString: Unknown file type, mode: %o", mode)
3046    end
3047    return stdnse.tooctal(code)
3048  end,
3049
3050  --- Converts a numeric ACL to its character equivalent eg. (rwxr-xr-x)
3051  --
3052  -- @param mode number containing the ACL mode
3053  -- @return string containing the ACL characters
3054  FpermToString = function(mode)
3055    local tmpacl = { "-", "-", "-", "-", "-", "-", "-", "-", "-" }
3056
3057    for user,_ in pairs(Util.Fperm) do
3058      local t = Util.Fperm[user]
3059      for i in pairs(t) do
3060        local code = mode & i
3061        if t[code] then
3062          -- save set-ID and sticky bits
3063          if tmpacl[t[code].idx] == "x" then
3064            if t[code].char == "S" then
3065              tmpacl[t[code].idx] = "s"
3066            else
3067              tmpacl[t[code].idx] = t[code].char
3068            end
3069          elseif tmpacl[t[code].idx] == "S" then
3070            if t[code].char == "x" then
3071              tmpacl[t[code].idx] = "s"
3072            end
3073          else
3074            tmpacl[t[code].idx] = t[code].char
3075          end
3076        end
3077      end
3078    end
3079
3080    return table.concat(tmpacl)
3081  end,
3082
3083  --- Converts the NFS file attributes to a string.
3084  --
3085  -- An optional second argument is the mactime to use
3086  --
3087  -- @param attr table returned by NFS GETATTR or ACCESS
3088  -- @param mactime to use, the default value is mtime
3089  --        Possible values: mtime, atime, ctime
3090  -- @return string containing the file attributes
3091  format_nfsfattr = function(attr, mactime)
3092    local time = "mtime"
3093    if mactime then
3094      time = mactime
3095    end
3096
3097    return string.format("%s%s  uid: %5d  gid: %5d  %6s  %s",
3098      Util.FtypeToChar(attr.mode),
3099      Util.FpermToString(attr.mode),
3100      attr.uid,
3101      attr.gid,
3102      Util.SizeToHuman(attr.size),
3103      Util.TimeToString(attr[time].seconds))
3104  end,
3105
3106  marshall_int32 = function(int32)
3107    return string.pack(">i4", int32)
3108  end,
3109
3110  unmarshall_int32 = function(data, pos, count)
3111    local ints = {}
3112    for i=1,(count or 1) do
3113      ints[i], pos = string.unpack(">i4", data, pos)
3114    end
3115    return pos, table.unpack(ints)
3116  end,
3117
3118  marshall_uint32 = function(uint32)
3119    return string.pack(">I4", uint32)
3120  end,
3121
3122  unmarshall_uint32 = function(data, pos, count)
3123    local ints = {}
3124    for i=1,(count or 1) do
3125      ints[i], pos = string.unpack(">I4", data, pos)
3126    end
3127    return pos, table.unpack(ints)
3128  end,
3129
3130  marshall_int64 = function(int64)
3131    return string.pack(">i8", int64)
3132  end,
3133
3134  unmarshall_int64 = function(data, pos, count)
3135    local ints = {}
3136    for i=1,(count or 1) do
3137      ints[i], pos = string.unpack(">i8", data, pos)
3138    end
3139    return pos, table.unpack(ints)
3140  end,
3141
3142  marshall_uint64 = function(uint64)
3143    return string.pack(">I8", uint64)
3144  end,
3145
3146  unmarshall_uint64 = function(data, pos, count)
3147    local ints = {}
3148    for i=1,(count or 1) do
3149      ints[i], pos = string.unpack(">I8", data, pos)
3150    end
3151    return pos, table.unpack(ints)
3152  end,
3153
3154  marshall_opaque = function(data)
3155    return data .. string.rep("\0", Util.CalcFillBytes(data:len()))
3156  end,
3157
3158  unmarshall_opaque = function(len, data, pos)
3159    local opaque, pos = string.unpack("c" .. len, data, pos)
3160    return pos, opaque
3161  end,
3162
3163  marshall_vopaque = function(data)
3164    local l = data:len()
3165    return (
3166      Util.marshall_uint32(l) .. data ..
3167      string.rep("\0", Util.CalcFillBytes(l))
3168      )
3169  end,
3170
3171  unmarshall_vopaque = function(len, data, pos)
3172    local opaque, pad
3173    pad = Util.CalcFillBytes(len)
3174    opaque, pos = string.unpack("c" .. len, data, pos)
3175    return pos + pad, opaque
3176  end,
3177
3178  unmarshall_nfsftype = function(data, pos, count)
3179    return Util.unmarshall_uint32(data, pos, count)
3180  end,
3181
3182  unmarshall_nfsfmode = function(data, pos, count)
3183    return Util.unmarshall_uint32(data, pos, count)
3184  end,
3185
3186  unmarshall_nfssize3 = function(data, pos, count)
3187    return Util.unmarshall_uint64(data, pos, count)
3188  end,
3189
3190  unmarshall_nfsspecdata3 = function(data, pos)
3191    local specdata3 = {}
3192    pos, specdata3.specdata1,
3193    specdata3.specdata2 = Util.unmarshall_uint32(data, pos, 2)
3194    return pos, specdata3
3195  end,
3196
3197  --- Unmarshall NFSv3 fileid field of the NFS attributes
3198  --
3199  -- @param data   The data being processed.
3200  -- @param pos    The position within <code>data</code>
3201  -- @return pos   The new position
3202  -- @return uint64 The decoded fileid
3203  unmarshall_nfsfileid3 = function(data, pos)
3204    return Util.unmarshall_uint64(data, pos)
3205  end,
3206
3207  --- Unmarshall NFS time
3208  --
3209  -- @param data   The data being processed.
3210  -- @param pos    The position within <code>data</code>
3211  -- @return pos   The new position
3212  -- @return table The decoded NFS time table.
3213  unmarshall_nfstime = function(data, pos)
3214    local nfstime = {}
3215    pos, nfstime.seconds,
3216    nfstime.nseconds = Util.unmarshall_uint32(data, pos, 2)
3217    return pos, nfstime
3218  end,
3219
3220  --- Unmarshall NFS file attributes
3221  --
3222  -- @param data   The data being processed.
3223  -- @param pos    The position within <code>data</code>
3224  -- @param number The NFS version.
3225  -- @return pos   The new position
3226  -- @return table The decoded file attributes table.
3227  unmarshall_nfsattr = function(data, pos, nfsversion)
3228    local attr = {}
3229    pos, attr.type = Util.unmarshall_nfsftype(data, pos)
3230    pos, attr.mode = Util.unmarshall_nfsfmode(data, pos)
3231    pos, attr.nlink, attr.uid,
3232    attr.gid = Util.unmarshall_uint32(data, pos, 3)
3233
3234    if (nfsversion < 3) then
3235      pos, attr.size, attr.blocksize, attr.rdev, attr.blocks,
3236      attr.fsid, attr.fileid = Util.unmarshall_uint32(data, pos, 6)
3237    elseif (nfsversion == 3) then
3238      pos, attr.size = Util.unmarshall_nfssize3(data, pos)
3239      pos, attr.used = Util.unmarshall_nfssize3(data, pos)
3240      pos, attr.rdev = Util.unmarshall_nfsspecdata3(data, pos)
3241      pos, attr.fsid = Util.unmarshall_uint64(data, pos)
3242      pos, attr.fileid = Util.unmarshall_nfsfileid3(data, pos)
3243    else
3244      stdnse.debug4("unmarshall_nfsattr: unsupported NFS version %d",
3245        nfsversion)
3246      return -1, nil
3247    end
3248
3249    pos, attr.atime = Util.unmarshall_nfstime(data, pos)
3250    pos, attr.mtime = Util.unmarshall_nfstime(data, pos)
3251    pos, attr.ctime = Util.unmarshall_nfstime(data, pos)
3252
3253    return pos, attr
3254  end,
3255
3256  --- Returns a string containing date and time
3257  --
3258  -- @param number of seconds since some given start time
3259  --        (the "epoch")
3260  -- @return string that represents time.
3261  TimeToString = datetime.format_timestamp,
3262
3263  --- Converts the size in bytes to a human readable format
3264  --
3265  -- An optional second argument is the size of a block
3266  -- @usage
3267  -- size_tohuman(1024) --> 1024.0B
3268  -- size_tohuman(926548776) --> 883.6M
3269  -- size_tohuman(246548, 1024) --> 240.8K
3270  -- size_tohuman(246548, 1000) --> 246.5K
3271  --
3272  -- @param size in bytes
3273  -- @param blocksize represents the number of bytes per block
3274  --        Possible values are: 1024 or 1000
3275  --        Default value is: 1024
3276  -- @return string containing the size in the human readable
3277  --        format
3278  SizeToHuman = function(size, blocksize)
3279    local bs, idx = 1024, 1
3280    local unit = { "B", "K", "M", "G" , "T"}
3281    if blocksize and blocksize == 1000 then
3282      bs = blocksize
3283    end
3284    for i=1, #unit do
3285      if (size > bs and idx < #unit) then
3286        size = size / bs
3287        idx = idx + 1
3288      end
3289    end
3290    return string.format("%.1f%s", size, unit[idx])
3291  end,
3292
3293  format_access = function(mask, version)
3294    local ret, nfsobj = "", NFS:new()
3295
3296    if nfsobj:AccessRead(mask, version) ~= 0 then
3297      ret = "Read "
3298    else
3299      ret = "NoRead "
3300    end
3301
3302    if nfsobj:AccessLookup(mask, version) ~= 0 then
3303      ret = ret .. "Lookup "
3304    else
3305      ret = ret .. "NoLookup "
3306    end
3307
3308    if nfsobj:AccessModify(mask, version) ~= 0 then
3309      ret = ret .. "Modify "
3310    else
3311      ret = ret .. "NoModify "
3312    end
3313
3314    if nfsobj:AccessExtend(mask, version) ~= 0 then
3315      ret = ret .. "Extend "
3316    else
3317      ret = ret .. "NoExtend "
3318    end
3319
3320    if nfsobj:AccessDelete(mask, version) ~= 0 then
3321      ret = ret .. "Delete "
3322    else
3323      ret = ret .. "NoDelete "
3324    end
3325
3326    if nfsobj:AccessExecute(mask, version) ~= 0 then
3327      ret = ret .. "Execute"
3328    else
3329      ret = ret .. "NoExecute"
3330    end
3331
3332    return ret
3333  end,
3334
3335  --- Return the pathconf filesystem table
3336  --
3337  -- @param pconf table returned by the NFSv3 PATHCONF call
3338  -- @param nfsversion the version of the remote NFS server
3339  -- @return fs table that contains the remote filesystem
3340  --         pathconf information.
3341  calc_pathconf_table = function(pconf, nfsversion)
3342    local fs = {}
3343    if nfsversion ~= 3 then
3344      return nil, "ERROR: unsupported NFS version."
3345    end
3346
3347    fs.linkmax = pconf.linkmax
3348    fs.name_max = pconf.name_max
3349
3350    if pconf.chown_restricted then
3351      fs.chown_restricted = "True"
3352    else
3353      fs.chown_restricted = "False"
3354    end
3355
3356    return fs, nil
3357  end,
3358
3359  --- Calculate and return the fsinfo filesystem table
3360  --
3361  -- @param fsinfo table returned by the NFSv3 FSINFO call
3362  -- @param nfsversion the version of the remote NFS server
3363  -- @param human if set show the size in the human
3364  --        readable format.
3365  -- @return fs table that contains the remote filesystem
3366  --         information.
3367  calc_fsinfo_table = function(fsinfo, nfsversion, human)
3368    local fs = {}
3369    local nfsobj = NFS:new()
3370    if nfsversion ~= 3 then
3371      return nil, "ERROR: unsupported NFS version."
3372    end
3373
3374    fs.maxfilesize = Util.SizeToHuman(fsinfo.maxfilesize)
3375
3376    if nfsobj:FSinfoLink(fsinfo.properties, nfsversion) ~= 0 then
3377      fs.link = "True"
3378    else
3379      fs.link = "False"
3380    end
3381
3382    if nfsobj:FSinfoSymlink(fsinfo.properties, nfsversion) ~= 0 then
3383      fs.symlink = "True"
3384    else
3385      fs.symlink = "False"
3386    end
3387
3388    return fs, nil
3389  end,
3390
3391  --- Calculate and return the fsstat filesystem table
3392  --
3393  -- @param stats table returned by the NFSv3 FSSTAT or
3394  --        NFSv2 STATFS calls
3395  -- @param nfsversion the version of the remote NFS server
3396  -- @param human if set show the size in the human
3397  --        readable format.
3398  -- @return df table that contains the remote filesystem
3399  --         attributes.
3400  calc_fsstat_table = function(stats, nfsversion, human)
3401    local df, base = {}, 1024
3402    local size, free, total, avail, used, use
3403    if (nfsversion == 3) then
3404      free = stats.fbytes
3405      size = stats.tbytes
3406      avail = stats.abytes
3407    elseif (nfsversion == 2) then
3408      df.bsize = stats.block_size
3409      free = stats.free_blocks * df.bsize
3410      size = stats.total_blocks * df.bsize
3411      avail = stats.available_blocks * df.bsize
3412    else
3413      return nil, "ERROR: unsupported NFS version."
3414    end
3415
3416    if (human) then
3417      if (df.bsize) then
3418        df.bsize = Util.SizeToHuman(df.bsize)
3419      end
3420      df.size = Util.SizeToHuman(size)
3421      df.available = Util.SizeToHuman(avail)
3422      used = size - free
3423      avail = avail
3424      df.used = Util.SizeToHuman(used)
3425      total = used + avail
3426    else
3427      free = free / base
3428      df.size = size / base
3429      df.available = avail / base
3430      used = df.size - free
3431      df.used = used
3432      total = df.used + df.available
3433    end
3434
3435    use = math.ceil(used * 100 / total)
3436    df.use = string.format("%.0f%%", use)
3437    return df, nil
3438  end,
3439
3440  --- Converts a RPC program name to its equivalent number
3441  --
3442  -- @param prog_name string containing the name of the RPC program
3443  -- @return num number containing the program ID
3444  ProgNameToNumber = function(prog_name)
3445    local status
3446
3447    if not( RPC_PROGRAMS ) then
3448      status, RPC_PROGRAMS = datafiles.parse_rpc()
3449      if ( not(status) ) then
3450        return
3451      end
3452    end
3453    for num, name in pairs(RPC_PROGRAMS) do
3454      if ( prog_name == name ) then
3455        return num
3456      end
3457    end
3458
3459    return
3460  end,
3461
3462  --- Converts the RPC program number to its equivalent name
3463  --
3464  -- @param num number containing the RPC program identifier
3465  -- @return string containing the RPC program name
3466  ProgNumberToName = function( num )
3467    local status
3468
3469    if not( RPC_PROGRAMS ) then
3470      status, RPC_PROGRAMS = datafiles.parse_rpc()
3471      if ( not(status) ) then
3472        return
3473      end
3474    end
3475    return RPC_PROGRAMS[num]
3476  end,
3477
3478  --
3479  -- Calculates the number of fill bytes needed
3480  -- @param length contains the length of the string
3481  -- @return the amount of pad needed to be dividable by 4
3482  CalcFillBytes = function(length)
3483    -- calculate fill bytes
3484    if math.fmod( length, 4 ) ~= 0 then
3485      return (4 - math.fmod( length, 4))
3486    else
3487      return 0
3488    end
3489  end
3490}
3491
3492return _ENV;
3493