diff --git a/CHANGELOG b/CHANGELOG
index bcc29f4a2..4e14084cb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added a new library, afp.lua, and a script that uses it,
+ afp-showmount. The library is for the Apple Filing Protocol and the
+ script shows shares and their permissions. [Patrik Karlsson]
+
o Fixed a bug with the decoding of NMAP OID component values greater
than 127. [Patrik Karlsson, David]
diff --git a/nselib/afp.lua b/nselib/afp.lua
new file mode 100644
index 000000000..4ee8f5c11
--- /dev/null
+++ b/nselib/afp.lua
@@ -0,0 +1,549 @@
+---
+-- This module was written by Patrik Karlsson and facilitates communication
+-- with the Apple AFP Service. It is not feature complete and is missing several
+-- functions and parameters.
+--
+-- The library currently has enough functionality to query share names and access controls.
+-- More functionality will be added once more scripts that depend on it are developed.
+--
+--
+
+-- Version 0.1
+-- Created 01/03/2010 - v0.1 - created by Patrik Karlsson
+
+module(... or "afp",package.seeall)
+
+-- Table of valid REQUESTs
+local REQUEST = {
+ OpenSession = 0x04,
+ Command = 0x02
+}
+
+-- Table of headers flags to be set accordingly in requests and responses
+local FLAGS = {
+ Request = 0,
+ Response = 1
+}
+
+-- Table of possible AFP_COMMANDs
+COMMAND = {
+ FPCloseVol = 0x02,
+ FPLogin = 0x12,
+ FPGetUserInfo = 0x25,
+ FPGetSrvParms = 0x10,
+ FPOpenVol = 0x18,
+ FPOpenFork = 0x1a,
+ FPGetFileDirParams = 0x22,
+ FPReadExt = 0x3c,
+ FPEnumerateExt2 = 0x44
+}
+
+USER_BITMAP = {
+ UserId = 1,
+ PrimaryGroupId = 2,
+ UUID = 4
+}
+
+VOL_BITMAP = {
+ Attributes = 1,
+ Signature = 2,
+ CreationDate = 4,
+ ModificationDate = 8,
+ BackupDate = 16,
+ ID = 32,
+ BytesFree = 64,
+ BytesTotal = 128,
+ Name = 256,
+ ExtendedBytesFree = 512,
+ ExtendedBytesTotal = 1024,
+ BlockSize = 2048
+}
+
+FILE_BITMAP = {
+ Attributes = 1,
+ DID = 2,
+ CreationDate = 4,
+ ModificationDate = 8,
+ BackupDate = 16,
+ FinderInfo = 32,
+ LongName = 64,
+ ShortName = 128,
+ FileId = 256,
+ DataForkSize = 512,
+ ResourceForkSize = 1024,
+ ExtendedDataForkSize = 2048,
+ LaunchLimit = 4096,
+ UTF8Name = 8192,
+ ExtendedResourceForkSize = 16384,
+ UnixPrivileges = 32768
+}
+
+DIR_BITMAP = {
+ Attributes = 1,
+ DID = 2,
+ CreationDate = 4,
+ ModificationDate = 8,
+ BackupDate = 16,
+ FinderInfo = 32,
+ LongName = 64,
+ ShortName = 128,
+ FileId = 256,
+ OffspringCount = 512,
+ OwnerId = 1024,
+ GroupId = 2048,
+ AccessRights = 4096,
+ UTF8Name = 8192,
+ UnixPrivileges = 32768
+}
+
+PATH_TYPE = {
+ LongNames = 2,
+ UnicodeNames = 3
+}
+
+ACCESS_MODE = {
+ Read = 1,
+ Write = 2,
+ DenyRead = 16,
+ DenyWrite = 32
+}
+
+ACLS = {
+ OwnerSearch = 1,
+ OwnerRead = 2,
+ OwnerWrite = 4,
+
+ GroupSearch = 256,
+ GroupRead = 512,
+ GroupWrite = 1024,
+
+ EveryoneSearch = 65536,
+ EveryoneRead = 131072,
+ EveryoneWrite = 262144,
+
+ UserSearch = 1048576,
+ UserRead = 2097152,
+ UserWrite = 4194304,
+
+ BlankAccess = 268435456,
+ UserIsOwner = 2147483648
+}
+
+-- Each packet contains a sequential request id
+-- this number is used within create_fp_packet and increased by one in each call
+request_id = 1
+
+
+--- Creates an AFP packet
+--
+-- @param command number should be one of the commands in the COMMAND table
+-- @param data_offset number holding the offset to the data
+-- @param data the actual data of the request
+function create_fp_packet( command, data_offset, data )
+
+ local reserved = 0
+ local data = data or ""
+ local data_len = data:len()
+ local header = bin.pack("CC>SIII", FLAGS.Request, command, request_id, data_offset, data_len, reserved)
+ local packet = header .. data
+
+ request_id = request_id + 1
+ return packet
+end
+
+--- Parses the FP header (first 16-bytes of packet)
+--
+-- @param packet string containing the raw packet
+-- @return table with header data containing flags, command,
+-- request_id, error_code, length and reserved fields
+function parse_fp_header( packet )
+
+ local header = {}
+ local pos
+
+ pos, header.flags, header.command, header.request_id = bin.unpack( "CC>S", packet )
+ pos, header.error_code, header.length, header.reserved = bin.unpack( "I>II", packet:sub(5) )
+ header.raw = packet:sub(1,16)
+
+ return header
+
+end
+
+--- Sends an OpenSession AFP request to the server and handles the response
+--
+-- @param socket already connected to the server
+-- @return status (true or false)
+-- @return nil (if status is true) or error string (if status is false)
+function open_session( socket )
+
+ local data_offset = 0
+ local option = 0x01 -- Attention Quantum
+ local option_len = 4
+ local quantum = 1024
+
+ local data = bin.pack( "CCI", option, option_len, quantum )
+ local packet = create_fp_packet( REQUEST.OpenSession, data_offset, data )
+
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("OpenSession error: %d", packet.header.error_code)
+ end
+
+ return true, nil
+end
+
+
+
+--- Sends an FPGetUserInfo AFP request to the server and handles the response
+--
+-- @param socket already connected to the server
+-- @return status (true or false)
+-- @return table with user information containing user_bitmap and
+-- uid fields (if status is true) or error string (if status is false)
+function fp_get_user_info( socket )
+
+ local packet
+ local data_offset = 0
+ local flags = 1 -- Default User
+ local uid = 0
+ local bitmap = USER_BITMAP.UserId
+ local response = {}
+ local pos
+
+ local data = bin.pack( "CCI>S", COMMAND.FPGetUserInfo, flags, uid, bitmap )
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("OpenSession error: %d", packet.header.error_code)
+ end
+
+ pos, response.user_bitmap, response.uid = bin.unpack(">S>I", packet.data)
+
+ return true, response
+end
+
+--- Sends an FPGetSrvrParms AFP request to the server and handles the response
+--
+-- @param socket already connected to the server
+-- @return status (true or false)
+-- @return table with server parameters containing server_time,
+-- vol_count, volumes fields (if status is true) or error string (if status is false)
+--
+function fp_get_srvr_parms(socket)
+
+ local packet
+ local data_offset = 0
+ local response = {}
+ local pos = 0
+
+ local data = bin.pack("CC", COMMAND.FPGetSrvParms, 0)
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("FPGetSrvrParms error: %d", packet.header.error_code)
+ end
+
+ data = packet.data
+ pos, response.server_time, response.vol_count = bin.unpack("IC", data)
+
+ -- we should now be at the leading zero preceeding the first volume name
+ -- next is the length of the volume name, move pos there
+ pos = pos + 1
+
+ stdnse.print_debug("Volumes: %d", response.vol_count )
+ response.volumes = {}
+
+ for i=1, response.vol_count do
+ local _, vol_len = bin.unpack("C", data:sub(pos))
+ local volume_name = data:sub(pos + 1, pos + 1 + vol_len)
+ pos = pos + vol_len + 2
+ table.insert(response.volumes, string.format("%s", volume_name) )
+ stdnse.print_debug("Volume name: %s", volume_name)
+ end
+
+ return true, response
+end
+
+
+--- Sends an FPLogin request to the server and handles the response
+--
+-- This function currently only supports the 3.1 through 3.3 protocol versions
+-- It does not support authentication so the uam parameter is currently ignored
+--
+-- @param socket already connected to the server--
+-- @param afp_version string (AFP3.3|AFP3.2|AFP3.1)
+-- @param uam string containing authentication information (currently ignored)
+-- @return status (true or false)
+-- @return nil (if status is true) or error string (if status is false)
+function fp_login( socket, afp_version, uam )
+
+ local packet
+ local data_offset = 0
+
+ -- currently we only support AFP3.3
+ if afp_version == nil or ( afp_version ~= "AFP3.3" and afp_version ~= "AFP3.2" and afp_version ~= "AFP3.1" ) then
+ return
+ end
+
+ uam = "No User Authent"
+
+ local data = bin.pack( "CCACA", COMMAND.FPLogin, afp_version:len(), afp_version, uam:len(), uam )
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("FPLogin error: %d", packet.header.error_code)
+ end
+
+ return true, nil
+end
+
+--- Reads a AFP packet of the socket
+--
+-- @param socket socket connected to the server
+-- @return table containing data and header fields
+function read_fp_packet( socket )
+
+ local packet = {}
+ local buf = ""
+
+ local catch = function()
+ socket:close()
+ end
+
+ local try = nmap.new_try(catch)
+
+ repeat
+ buf = buf .. try( socket:receive(16) )
+ until buf:len() >= 16 -- make sure we have got atleast the header
+
+ packet.header = parse_fp_header( buf )
+
+ -- if we didn't get the whole packet when reading the header, try to read the rest
+ while buf:len() < packet.header.length + packet.header.raw:len() do
+ buf = buf .. try( socket:receive(packet.header.length) )
+ end
+
+ packet.data = buf:len() > 16 and buf:sub( 17 ) or ""
+
+ return packet
+
+end
+
+--- Sends an FPOpenVol request to the server and handles the response
+--
+-- @param socket already connected to the server
+-- @param bitmap number bitmask of volume information to request
+-- @param volume_name string containing the volume name to query
+-- @return status (true or false)
+-- @return table containing bitmap and volume_id fields
+-- (if status is true) or error string (if status is false)
+function fp_open_vol( socket, bitmap, volume_name )
+
+ local packet
+ local data_offset = 0
+ local pad = 0
+ local response = {}
+ local pos
+
+ local data = bin.pack("CC>SCA", COMMAND.FPOpenVol, pad, bitmap, volume_name:len(), volume_name )
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("FPOpenVol error: %d", packet.header.error_code )
+ end
+
+ pos, response.bitmap, response.volume_id = bin.unpack(">S>S", packet.data)
+
+ return true, response
+
+end
+
+--- Sends an FPGetFileDirParms request to the server and handles the response
+--
+-- Currently only handles a request for the Access rights (file_bitmap must be 0 and dir_bitmap must be 0x1000)
+--
+-- @param socket already connected to the server
+-- @param volume_id number containing the id of the volume to query
+-- @param did number containing the id of the directory to query
+-- @param file_bitmap number bitmask of file information to query
+-- @param dir_bitmap number bitmask of directory information to query
+-- @param path string containing the name of the directory to query
+-- @return status (true or false)
+-- @return table containing file_bitmap, dir_bitmap,
+-- file_type and acls fields
+-- (if status is true) or error string (if status is false)
+function fp_get_file_dir_parms( socket, volume_id, did, file_bitmap, dir_bitmap, path )
+
+ local packet
+ local data_offset = 0
+ local pad = 0
+ local response = {}
+ local pos
+
+ if file_bitmap ~= 0 or dir_bitmap ~= DIR_BITMAP.AccessRights then
+ return false, "Only AccessRights querys are supported (file_bitmap=0, dir_bitmap=DIR_BITMAP.AccessRights)"
+ end
+
+ local data = bin.pack("CC>S>I>S>SCCAC", COMMAND.FPGetFileDirParams, pad, volume_id, did, file_bitmap, dir_bitmap, path.type, path.len, path.name, 0)
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("FPGetFileDirParms error: %d", packet.header.error_code )
+ end
+
+ pos, response.file_bitmap, response.dir_bitmap, response.file_type, pad, response.acls = bin.unpack( ">S>SCC>I", packet.data )
+
+ return true, response
+end
+
+--- Sends an FPEnumerateExt2 request to the server and handles the response
+--
+-- @param socket already connected to the server
+-- @param volume_id number containing the id of the volume to query
+-- @param did number containing the id of the directory to query
+-- @param file_bitmap number bitmask of file information to query
+-- @param dir_bitmap number bitmask of directory information to query
+-- @param req_count number
+-- @param start_index number
+-- @param reply_size number
+-- @param path string containing the name of the directory to query
+-- @return status (true or false)
+-- @return table containing file_bitmap, dir_bitmap,
+-- req_count fields
+-- (if status is true) or error string (if status is false)
+function fp_enumerate_ext2( socket, volume_id, did, file_bitmap, dir_bitmap, req_count, start_index, reply_size, path )
+
+ local packet
+ local data_offset = 0
+ local pad = 0
+ local response = {}
+
+ local data = bin.pack( "CC>S>I>S>S", COMMAND.FPEnumerateExt2, pad, volume_id, did, file_bitmap, dir_bitmap )
+ data = data .. bin.pack( ">S>I>IC>SA", req_count, start_index, reply_size, path.type, path.len, path.name )
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("FPEnumerateExt2 error: %d", packet.header.error_code )
+ end
+
+ _, response.file_bitmap, response.dir_bitmap, response.req_count = bin.unpack(">S>S>S", packet.data)
+
+ return true, response
+
+end
+
+--- Sends an FPOpenFork request to the server and handles the response
+--
+-- @param socket already connected to the server
+-- @param fork number
+-- @param volume_id number containing the id of the volume to query
+-- @param did number containing the id of the directory to query
+-- @param file_bitmap number bitmask of file information to query
+-- @param access_mode number containing bitmask of options from ACCESS_MODE
+-- @param path string containing the name of the directory to query
+-- @return status (true or false)
+-- @return table containing file_bitmap and fork fields (if status is true) or
+-- error string (if status is false)
+function fp_open_fork( socket, fork, volume_id, did, file_bitmap, access_mode, path )
+
+ local packet
+ local data_offset = 0
+ local pad = 0
+ local response = {}
+
+ local data = bin.pack( "CC>S>I>S>S", COMMAND.FPOpenFork, fork, volume_id, did, file_bitmap, access_mode )
+
+ if path.type == PATH_TYPE.LongNames then
+ data = data .. bin.pack( "C>SA", path.type, path.len, path.name )
+ end
+
+ if path.type == PATH_TYPE.UnicodeNames then
+ local unicode_hint = 0x08000103
+ data = data .. bin.pack( "C>I>SA", path.type, unicode_hint, path.len, path.name )
+ end
+
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("FPOpenFork error: %d", packet.header.error_code )
+ end
+
+ _, response.file_bitmap, response.fork = bin.unpack(">S>S", packet.data)
+
+ return true, response
+
+end
+
+--- Sends an FPCloseVol request to the server and handles the response
+--
+-- @param socket already connected to the server
+-- @param volume_id number containing the id of the volume to close
+-- @return status (true or false)
+-- @return nil (if status is true) or error string (if status is false)
+function fp_close_vol( socket, volume_id )
+
+ local packet
+ local data_offset = 0
+ local pad = 0
+ local response = {}
+
+ local data = bin.pack( "CC>S>", COMMAND.FPCloseVol, pad, volume_id )
+
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+ send_fp_packet( socket, packet )
+ packet = read_fp_packet( socket )
+
+ if packet.header.error_code ~= 0 then
+ return false, string.format("FPCloseVol error: %d", packet.header.error_code )
+ end
+
+ return true, nil
+
+end
+
+--- Sends the raw packet over the socket
+--
+-- @param socket already connected to the server
+-- @param packet containing the raw data
+function send_fp_packet( socket, packet )
+
+ local catch = function()
+ socket:close()
+ end
+
+ local try = nmap.new_try(catch)
+ try( socket:send(packet) )
+
+end
+
+
+function fp_read_ext( fork, offset, count )
+
+ local packet
+ local data_offset = 0
+ local pad = 0
+
+ local data = bin.pack( "CC>S>L>L", COMMAND.FPReadExt, pad, fork, offset, count )
+ packet = create_fp_packet( REQUEST.Command, data_offset, data )
+
+ return packet
+
+end
diff --git a/scripts/afp-showmount.nse b/scripts/afp-showmount.nse
new file mode 100644
index 000000000..a2f5703aa
--- /dev/null
+++ b/scripts/afp-showmount.nse
@@ -0,0 +1,194 @@
+description = [[ Shows AFP shares and ACLs ]]
+
+---
+--@output
+-- PORT STATE SERVICE
+-- 548/tcp open afp
+-- | afp-showmount:
+-- | Yoda's Public Folder
+-- | Owner: Search,Read,Write
+-- | Group: Search,Read
+-- | Everyone: Search,Read
+-- | User: Search,Read
+-- | Vader's Public Folder
+-- | Owner: Search,Read,Write
+-- | Group: Search,Read
+-- | Everyone: Search,Read
+-- | User: Search,Read
+-- |_ Options: IsOwner
+
+-- Version 0.1
+-- Created 01/03/2010 - v0.1 - created by Patrik Karlsson
+-- Revised 01/13/2010 - v0.2 - Fixed a bug where a single share wouldn't show due to formatting issues
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"discovery", "safe"}
+
+require 'shortport'
+require 'stdnse'
+require 'afp'
+
+portrule = shortport.portnumber(548, "tcp")
+
+--- Converts a group bitmask of Search, Read and Write to string
+-- eg. WRS, -RS, W-S, etc ..
+--
+-- @param acls number containing bitmasked acls
+-- @return string of ACLS
+function acl_group_to_string( acls )
+
+ local acl_string = ""
+
+ if bit.band( acls, afp.ACLS.OwnerSearch ) == afp.ACLS.OwnerSearch then
+ acl_string = "S"
+ else
+ acl_string = "-"
+ end
+
+ if bit.band( acls, afp.ACLS.OwnerRead ) == afp.ACLS.OwnerRead then
+ acl_string = "R" .. acl_string
+ else
+ acl_string = "-" .. acl_string
+ end
+
+ if bit.band( acls, afp.ACLS.OwnerWrite ) == afp.ACLS.OwnerWrite then
+ acl_string = "W" .. acl_string
+ else
+ acl_string = "-" .. acl_string
+ end
+
+ return acl_string
+end
+
+--- Converts a group bitmask of Search, Read and Write to table
+--
+-- @param acls number containing bitmasked acls
+-- @return table of ACLs
+function acl_group_to_long_string(acls)
+
+ local acl_table = {}
+
+ if bit.band( acls, afp.ACLS.OwnerSearch ) == afp.ACLS.OwnerSearch then
+ table.insert( acl_table, "Search")
+ end
+
+ if bit.band( acls, afp.ACLS.OwnerRead ) == afp.ACLS.OwnerRead then
+ table.insert( acl_table, "Read")
+ end
+
+ if bit.band( acls, afp.ACLS.OwnerWrite ) == afp.ACLS.OwnerWrite then
+ table.insert( acl_table, "Write")
+ end
+
+ return acl_table
+end
+
+--- Converts a numeric acl to string
+--
+-- @param acls number containig acls as recieved from fp_get_file_dir_parms
+-- @return string of ACLs
+function acls_to_string( acls )
+
+ local owner = acl_group_to_string( bit.band( acls, 255 ) )
+ local group = acl_group_to_string( bit.band( bit.rshift(acls, 8), 255 ) )
+ local everyone = acl_group_to_string( bit.band( bit.rshift(acls, 16), 255 ) )
+ local user = acl_group_to_string( bit.band( bit.rshift(acls, 24), 255 ) )
+
+ local blank = bit.band( acls, afp.ACLS.BlankAccess ) == afp.ACLS.BlankAccess and "B" or "-"
+ local isowner = bit.band( acls, afp.ACLS.UserIsOwner ) == afp.ACLS.UserIsOwner and "O" or "-"
+
+ return string.format("Owner: %s; Group: %s; Everyone: %s; User: %s; Options: %s%s", owner, group, everyone, user, blank, isowner )
+
+end
+
+--- Converts a numeric acl to string
+--
+-- @param acls number containig acls as recieved from fp_get_file_dir_parms
+-- @return table of long ACLs
+function acls_to_long_string( acls )
+
+ local owner = acl_group_to_long_string( bit.band( acls, 255 ) )
+ local group = acl_group_to_long_string( bit.band( bit.rshift(acls, 8), 255 ) )
+ local everyone = acl_group_to_long_string( bit.band( bit.rshift(acls, 16), 255 ) )
+ local user = acl_group_to_long_string( bit.band( bit.rshift(acls, 24), 255 ) )
+
+ local blank = bit.band( acls, afp.ACLS.BlankAccess ) == afp.ACLS.BlankAccess and "Blank" or nil
+ local isowner = bit.band( acls, afp.ACLS.UserIsOwner ) == afp.ACLS.UserIsOwner and "IsOwner" or nil
+
+ local options = {}
+
+ if blank then
+ table.insert(options, "Blank")
+ end
+
+ if isowner then
+ table.insert(options, "IsOwner")
+ end
+
+ local acls_tbl = {}
+
+ table.insert( acls_tbl, string.format( "Owner: %s", stdnse.strjoin(",", owner) ) )
+ table.insert( acls_tbl, string.format( "Group: %s", stdnse.strjoin(",", group) ) )
+ table.insert( acls_tbl, string.format( "Everyone: %s", stdnse.strjoin(",", everyone) ) )
+ table.insert( acls_tbl, string.format( "User: %s", stdnse.strjoin(",", user) ) )
+
+ if #options > 0 then
+ table.insert( acls_tbl, string.format( "Options: %s", stdnse.strjoin(",", options ) ) )
+ end
+
+ return acls_tbl
+
+end
+
+action = function(host, port)
+
+ local socket = nmap.new_socket()
+ local status
+ local result = {}
+
+ -- set a reasonable timeout value
+ socket:set_timeout(5000)
+
+ -- do some exception handling / cleanup
+ local catch = function()
+ socket:close()
+ end
+
+ local try = nmap.new_try(catch)
+
+ try( socket:connect(host.ip, port.number, "tcp") )
+
+ response = try( afp.open_session(socket) )
+ response = try( afp.fp_login( socket, "AFP3.1", "No User Authent") )
+ response = try( afp.fp_get_user_info( socket ) )
+ response = try( afp.fp_get_srvr_parms( socket ) )
+
+ volumes = response.volumes
+
+ for _, vol in pairs(volumes) do
+ table.insert( result, vol )
+
+ status, response = afp.fp_open_vol( socket, afp.VOL_BITMAP.ID, vol )
+
+ if status then
+ local vol_id = response.volume_id
+ stdnse.print_debug(string.format("Vol_id: %d", vol_id))
+
+ local path = {}
+ path.type = afp.PATH_TYPE.LongNames
+ path.name = ""
+ path.len = path.name:len()
+
+ response = try( afp.fp_get_file_dir_parms( socket, vol_id, 2, 0, afp.DIR_BITMAP.AccessRights, path ) )
+ local acls = acls_to_long_string(response.acls)
+ acls.name = nil
+ try( afp.fp_close_vol( socket, vol_id ) )
+ table.insert( result, acls )
+ end
+
+ end
+
+ return stdnse.format_output(true, result)
+
+end
\ No newline at end of file