mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Add afp.lua and afp-showmount.nse from Patrik Karlsson. See
http://seclists.org/nmap-dev/2010/q1/97.
This commit is contained in:
549
nselib/afp.lua
Normal file
549
nselib/afp.lua
Normal file
@@ -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 <code>create_fp_packet</code> 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 <code>flags</code>, <code>command</code>,
|
||||
-- <code>request_id</code>, <code>error_code</code>, <code>length</code> and <code>reserved</code> 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 <code>user_bitmap</code> and
|
||||
-- <code>uid</code> 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 <code>server_time</code>,
|
||||
-- <code>vol_count</code>, <code>volumes</code> 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 <code>data</code> and <code>header</code> 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 <code>bitmap</code> and <code>volume_id</code> 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 <code>file_bitmap</code>, <code>dir_bitmap</code>,
|
||||
-- <code>file_type</code> and <code>acls</code> 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 <code>file_bitmap</code>, <code>dir_bitmap</code>,
|
||||
-- <code>req_count</code> 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 <code>ACCESS_MODE</code>
|
||||
-- @param path string containing the name of the directory to query
|
||||
-- @return status (true or false)
|
||||
-- @return table containing <code>file_bitmap</code> and <code>fork</code> 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
|
||||
Reference in New Issue
Block a user