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